HPR4746: Monotono

Di Lee12/10/2026 às 00:0020 visualizzazioni
Foto: CC BY-SA / Hacker Public Radio
🎙 Lee · Hacker Public Radio

Questo programma è stato classificato come "Clean" dal presentatore.

Python e le sue librerie

1. librosa — caricamento e ridimensionamento di file audio

def load_audio(path):
 y, sr = librosa.load(path, sr=SR, mono=True)
 return y, sr

Ogni episodio, indipendentemente dal suo originale sample rate o dal numero di canali, viene caricato in un singolo flusso mono a 16 kHz (SR = 16000). Ciò implica una singola operazione di librosa, di resampling e di conversione stereo in mono. Questo ha permesso a tutti gli strumenti successivi (VAD, tracciamento del pitch, estrazione di MFCC) di operare su un input coerente e prevedibile, indipendentemente da ciò che un encoder MP3 o un host di podcast originale aveva utilizzato.

2. Parselmouth (binding con Praat) — tracciamento del tono e della qualità della voce

def pitch_and_hnr(y, sr):
 snd = parselmouth.Sound(y, sampling_frequency=sr)
 pitch = snd.to_pitch(time_step=PITCH_TIME_STEP, pitch_floor=PITCH_FLOOR, pitch_ceiling=PITCH_CEILING)
 f0 = pitch.selected_array["frequency"]
 times = pitch.xs()
 try:
 harmonicity = snd.to_harmonicity_cc(time_step=PITCH_TIME_STEP)
 hnr = np.array([harmonicity.get_value(t) for t in times])
 hnr = np.nan_to_num(hnr, nan=-999.0)
 exceptException:
 hnr = np.full_like(f0, 999.0) # fallimento se si verificano errori nell'estrazione dell'HNR
 return f0, hnr, times

parselmouth è un wrapper Python per Praat, lo strumento standard nella ricerca fonetica. Questo è il cuore della misurazione della monotonia vocale: to_pitch() fornisce la frequenza fondamentale (F0, approssimativamente "altezza") ogni 20 ms, e to_harmonicity_cc() fornisce il rapporto tra armoniche e rumore (HNR) negli stessi istanti temporali — un punteggio di qualità del segnale utilizzato successivamente per eliminare le stime della frequenza ottenute da audio rumoroso o mal compresso, piuttosto che permettere a un audio scadente di apparire come una "altezza piatta".

3. webrtcvad — determinare quali fotogrammi corrispondono effettivamente alla voce

def vad_speech_mask(y, sr, aggressiveness=2):
 vad = webrtcvad.Vad(aggressiveness)
 pcm16 = (np.clip(y, -1.0, 1.0) * 32767).astype(np.int16)
 n_frames = len(pcm16) // VAD_FRAME_LEN
 mask = np.zeros(n_frames, dtype=bool)
 for i in range(n_frames):
 chunk = pcm16[i * VAD_FRAME_LEN:(i + 1) * VAD_FRAME_LEN]
 raw = struct.pack("%dh" % len(chunk), *chunk)
 try:
 mask[i] = vad.is_speech(raw, sr)
 except Exception:
 mask[i] = False
 return mask, times

webrtcvad è il rilevatore di attività vocale del progetto WebRTC di Google, un classificatore a livello di frame ("questo frammento di 30ms è parlato o no"). Richiede dati PCM a 16 bit grezzi, non float, pertanto sono necessarie le conversioni np.clip e struct.pack.

4. librosa.feature – distinguere la musica dalla voce

flatness = librosa.feature.spectral_flatness(y=y, hop_length=hop)[0]
chroma = librosa.feature.chroma_stft(y=y, sr=sr, hop_length=hop)
onset_env = librosa.onset.onset_strength(y=y, sr=sr, hop_length=hop)
...
chroma_diff = np.mean(np.linalg.norm(np.diff(chroma_win, axis=1), axis=0))
chroma_stability = 1.0 / (1.0 + chroma_diff)
...
scores[w] = 0.4 * chroma_stability + 0.35 * periodicità + 0.25 * min(flat_mean * 3, 1.0)

Le introduzioni/outro dei podcast sono spesso musica, e la musica ha un profilo di frequenza molto diverso rispetto al parlato: includerla distorcerebbe la misurazione del "tono monotono". Invece di addestrare un classificatore, tre caratteristiche di librosa sono state combinate con pesi ottimizzati manualmente: planarità spettrale (la musica tende ad avere una struttura tonale più definita, uno spettro meno simile al rumore), stabilità cromatica (armonia sostenuta rispetto a...

(costanti variazioni dei formanti del parlato), e la periodicità dell'inizio (un ritmo regolare rispetto al ritmo irregolare del parlato).

tentativo non supervisionato di conteggio dei parlanti: librosa.feature.mfcc + sklearn.cluster.KMeans + sklearn.metrics.silhouette_score

mfcc = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=13, hop_length=hop)

I coefficienti MFCC (Mel-frequency cepstral) riassumono la forma dello spettro di una breve finestra audio — un "impronta vocale" approssimativa ampiamente utilizzata nei compiti di elaborazione del parlato. L'idea: dividere ogni episodio in segmenti di 2 secondi, trasformare ciascuno in un vettore di impronta, standardizzare le caratteristiche (z-score) e quindi utilizzare KMeans per vedere se i segmenti si dividono naturalmente in 2+ cluster (diverse voci) piuttosto che 1 (una singola voce per tutta la durata). silhouette_score valuta quanto sono ben separati i cluster e seleziona il migliore k — oppure torna a "1 parlante" se nessun clustering supera una soglia minima di qualità. Questa parte dell'indagine non ha funzionato, poiché ha rilevato il rumore come un altro parlante.

6. scipy.stats.mannwhitneyu + un delta di Cliff scritto a mano — confronto di due gruppi senza assumere una distribuzione normale

def cliffs_delta(a, b):

scipy.stats.mannwhitneyu è un test statistico: confronta due gruppi di numeri (HPR rispetto a podcast di confronto, per metrica) senza assumere che siano distribuiti normalmente — adatto per dati reali e distorti, come le misurazioni dell'intervallo di frequenza. SciPy non fornisce una misura della dimensione dell'effetto insieme ad esso, quindi il delta di Cliff — "tra tutte le possibili coppie (episodio HPR, episodio di confronto), con quale frequenza il valore di HPR è il più piccolo?" Questa coppia (un test di significatività più un numero di dimensione dell'effetto) è stata utilizzata in tutto perché un valore p basso da solo non dice se la differenza è effettivamente significativa.

7. pandas — raggruppamento, stratificazione e creazione di tabelle di frequenza

print(df.groupby("corpus")["retained_fraction"].describe())
 ...
 print(pd.crosstab(df["corpus"], df["register"]))

Una volta che i dati/risultati.jsonl (un oggetto JSON per ogni episodio analizzato) sono stati caricati in un pandas.DataFrame, due comandi brevi rispondono a due domande diverse che altrimenti richiederebbero codice personalizzato: .groupby(...).describe() fornisce un riepilogo della distribuzione (conteggio, media, deviazione standard, quartili) della qualità audio per ogni corpus, in un'unica chiamata, e pd.crosstab() crea una tabella di frequenza incrociata tra HPR e il registro tipico/non tipico, fornendo un rapido controllo per verificare che le due raccolte di podcast avessero un mix di genere simile.

8. matplotlib — grafici a scatola per il confronto delle notizie principali

fig, ax = plt.subplots(figsize=(6, 4))
data = [df[(df.corpus == c) & (df[metric].notna())][metric].values for c in ["hpr", "comparison"]]
ax.boxplot(data, tick_labels=["HPR", "Comparison"], showmeans=True)

Il grafico a scatola e baffi per la metrica "HPR per intervallo di frequenza", confrontato con altri podcast, mostra la media e la mediana.

9. Vettori di parole di Spacy — misurazione della deriva tematica, non solo della scelta delle parole

nlp = get_nlp() # spacy.load("en_core_web_md")
 ...
 chunk_docs = [nlp(c) for c in chunks]
 chunk_vecs = []
 for d in chunk_docs:
 v = d.vector
 norm = np.linalg.norm(v)
 se norm > 1e-6:
 chunk_vecs.append(v / norm)
 ...
 adj_sims = [float(np.dot(V[i], V[i +1])) for i in range(n - 1)]

Ogni frammento di trascrizione di circa 130 parole viene incorporato come un singolo vettore utilizzando il modello inglese di media complessità di spaCy (en_core_web_md, che include vettori di parole integrati), quindi normalizzato secondo la norma L2, in modo che il prodotto scalare tra due vettoriisla loro similarità coseno. Confrontare la similarità tra blocchi consecutivi fornisce un punteggio che indica "se l'argomento cambia appena tra un blocco e l'altro".

10. ricchezza lessicale — diversità del vocabolario che non compromette la lunghezza del testo

lr = LexicalRichness(testo)
try:
 mtld = lr.mtld(threshold=0.72)
except Exception:
 mtld = None

Un punteggio ingenuo di diversità del vocabolario (parole uniche ÷ parole totali) peggiora man mano che il testo si allunga, a causa del modo in cui è definito il parametro — e non perché il parlante stia effettivamente ripetendosi più.

MTLD (Measure of Textual Lexical Diversity) evita questo problema calcolando la diversità lessicale del testo esaminando l'intero testo e contando quante parole sono necessarie prima che la diversità scenda al di sotto di una soglia, quindi calcolando una media — una metrica progettata specificamente per garantire la comparabilità tra un episodio di 10 minuti e uno di 40 minuti.

11. vaderSentiment — punteggio di sentiment basato su lessico

vader = get_vader() # SentimentIntensityAnalyzer()
 sentimenti = [vader.polarity_scores(c)["compound"] for c in chunks]
 ...
 "sentiment_std": float(np.std(sentimenti)) se len(sentimenti) > 1 altrimenti None

Il punteggio VADER analizza il sentimento di ogni frammento da -1 a +1 utilizzando un lessico e un insieme di regole create manualmente (non una rete neurale) – scelte per la loro velocità, determinismo e costo contenuto, in modo da poter essere applicati a ogni frammento in centinaia di episodi. Il valore utilizzato non è la media del sentimento, ma ildeviazione standardanalisi del sentimento in segmenti specifici di un episodio: l'analisi del sentimento è coerente

tono costante o varia?

12. zlib (libreria standard) — rapporto di compressione come indicatore di entropia

def compression_ratio(text):
 raw = text.encode("utf-8")
 if len(raw) < 200:
 return None
 compressed = zlib.compress(raw, level=9)
 return len(compressed) / len(raw)

Non è stata utilizzata alcuna libreria NLP di terze parti — solo il modulo standard zlib. Un compressore di tipo gzip individua e sfrutta pattern ripetuti; se riesce a comprimere un trascritto in modo significativo, significa che questo era relativamente ripetitivo e prevedibile, e se riesce a comprimerlo solo in minima parte, significa che il linguaggio era più vario. Questo fornisce approssimativamente un'indicazione di "monotonia verbale".

13. re (libreria standard) — trasformare "HPR favorisce gli host singoli" in un'euristica verificabile

MULTI_PRESENTER_PATTERNS = [

Invece del tentativo di raggruppamento acustico degli altoparlanti (elemento 5, che si è rivelato inaffidabile), un risultato più affidabile è arrivato dal testo semplice: i metadati dell'episodio di HPR (estratti da un repository di conoscenza locale) contengono riassunti scritti dagli host, e un breve elenco di modelli regex ("ospite", "intervista", "mi unisce", ecc.) più il conteggio dei profili dei corrispondenti collegati, era sufficiente per identificare gli episodi con più presentatori.

14. multiprocessing.Pool — eseguire la stessa analisi su centinaia di file senza attendere centinaia di volte

work = [(r, local_paths[r["id"]]) for r in batch]

Ogni pipeline di elaborazione (analisi dell'intonazione, conteggio dei parlanti) utilizzava la stessa struttura: Pool(workers).imap_unordered(...) per eseguire la funzione per episodio su più core CPU contemporaneamente, inviando i risultati a un file JSON-lines man mano che terminano, piuttosto che aspettare che l'intero batch sia completato. imap_unordered è stato scelto rispetto a map per evitare che un singolo episodio lento ostacolasse la scrittura dei risultati già completati.

15. subprocess + un ciclo batch riassumibile — operare entro un limite di spazio su disco

def load_done_ids(results_path):

Tutti i podcast (~220GB) non entravano nel disco locale (~54GB liberi). La soluzione è stata un ciclo: scp (tramite subprocess) un batch di audio, analizzarlo, eliminarlo e passare al batch successivo. load_done_ids() rende l'intera pipeline riassumibile leggendo nuovamente ciò che è già nel file di output, in modo che un crash o un arresto intenzionale a metà non perda i risultati completati.

Link

Sì, il mondo affascinante di Hacker Public Radio: un piccolo angolo dove gli esperti di tecnologia ✨️ condividono i loro segreti magici ✨️ a vicenda, in un tono monotono più affascinante che si possa immaginare. Chi avrebbe mai pensato che i podcast "comunitari" infiniti potessero essere così... assolutamente affascinanti? 📻️🎙️ A quanto pare, tutti desiderano contribuire a questo caos una volta all'anno—che privilegio! 😂
https://hackerpublicradio.org/
#HackerPublicRadio #comunitàtecnologica #podcast #MonotoneMagic #contenuto coinvolgente #HackerNews #ngated

Fornirefeedbacksu questo episodio.

Fonte
Hacker Public Radio
Apri l'originale ↗

⚙Tradotto automaticamente.

Questa notizia è stata utile?

Dibattiti 0

Sii il primo a contribuire al dibattito.

Condividi le tue informazioni e promuovi il tuo argomento

Non esitare a pubblicare informazioni o dati che possano essere utili.

Per partecipare al dibattito, accedi o crea un account gratuito.