HPR4746: 단조

출처: Hacker Public Radio원본 출처에서 열기 ↗
작성 Lee12/10/2026 às 00:005 조회
Foto: CC BY-SA / Hacker Public Radio
🎙 Lee · Hacker Public Radio

이 프로그램은 호스트에 의해 "Clean" (무해)로 분류되었습니다.

파이썬과 그 라이브러리

1. librosa — 오디오 로딩 및 리샘플링

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

각 에피소드는 원본 샘플 레이트나 채널 수와 관계없이 16kHz (SR = 16000) 단일 모노 스트림으로 로드됩니다. 이는 librosa, 리샘플링 및 스테레오를 모노로 변환하는 한 줄의 작업입니다. 이를 통해 모든 후속 도구(VAD, 피치 추적, MFCC 추출)가 MP3 인코더 또는 팟캐스트 호스트가 원래 사용한 내용에 관계없이 일관성 있고 예측 가능한 입력을 사용하도록 작동합니다.

2. 파르트마운트 (음성 결합) — 음높이 및 음질 추적

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) # 실패 시 HNR 추출 오류 처리
 return f0, hnr, times

파르셀마우스는 음성 연구의 표준 도구인 프라트(Praat)를 감싸는 파이썬 래퍼입니다. 이는 전체 음성 단조 측정의 핵심입니다. `to_pitch()`는 20ms마다 기본 주파수(F0, 대략 "음높이")를 제공하고, `to_harmonicity_cc()`는 동일한 시간 스탬프에서 하모닉-투-노이즈 비율(HNR)을 제공합니다. 이는 나중에 노이즈가 있거나 압축이 잘못된 오디오에서 얻은 음높이 추정을 제거하는 데 사용되는 신호 품질 점수입니다.

3. webrtcvad — 실제 음성인 프레임을 결정

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는 구글의 WebRTC 프로젝트에서 나온 음성 활동 감지기입니다. 이는 프레임별로 분류하는 방식("이 30ms 분의 데이터가 음성인지 아닌지")을 사용합니다. 따라서 부동 소수점 숫자가 아닌, 16비트 PCM 바이트를 직접 사용해야 하므로, np.clip 및 struct.pack 변환이 필요합니다.

4. librosa.feature: 음악과 음성을 구별하는 방법

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 * periodicity + 0.25 * min(flat_mean * 3, 1.0)

팟캐스트의 소개/기타 부분에서 음악이 사용되는 경우가 많으며, 음악은 음성과는 매우 다른 음높이 프로필을 가지고 있습니다. 따라서, "단조" 측정을 왜곡할 수 있습니다. 분류기를 훈련하는 대신, 세 가지 librosa 특징을 수동으로 조정된 가중치와 함께 사용했습니다: 스펙트럴 플랫니스(음악은 일반적으로 더 많은 음높이 구조를 가지며, 노이즈와 유사한 스펙트럼은 덜함), 크로마 안정성(지속적인 화음 대 음성).

(끊임없이 변하는 음성 파형), 그리고 발음의 주기성 (규칙적인 리듬 대, 언어의 불규칙한 리듬).

librosa.feature.mfcc + sklearn.cluster.KMeans + sklearn.metrics.silhouette_score — 비감독적 화자 개수 추정 시도

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

MFCC (멜 주파수 세프스트럴 계수)는 짧은 오디오 창의 스펙트럼의 형태를 요약합니다. — 음성 작업에서 널리 사용되는 대략적인 "음성 지문"입니다. 아이디어: 각 에피소드를 2초 분할, 각 분할을 지문 벡터로 변환, 특징을 표준화(Z-점수), 그런 다음 KMeans를 사용하여 분할이 자연스럽게 2개 이상의 클러스터(다른 음성)로 분할되는지 아니면 1개(전체적으로 하나의 음성)로 분할되는지 확인합니다. silhouette_score는 클러스터가 얼마나 잘 분리되는지 점수를 매기고, 최적의 k를 선택하거나 최소 품질 기준을 충족하지 않는 경우 "1명의 화자"로 돌아갑니다. 이 부분의 조사는 작동하지 않았습니다. 왜냐하면 노이즈를 다른 화자로 인식했기 때문입니다.

6. scipy.stats.mannwhitneyu + 수동으로 작성된 Cliff의 델타 — 정규 분포를 가정하지 않고 두 그룹을 비교

def cliffs_delta(a, b):

scipy.stats.mannwhitneyu는 통계적 검정입니다. 두 숫자 그룹(HPR 대 비교 팟캐스트, 특정 지표 기준)을 비교하지만, 이들이 정규 분포를 따른다고 가정하지 않습니다. 이는 음계가 있는 실제 데이터, 예를 들어 음높이 측정과 같이 적합합니다. SciPy는 이와 함께 효과 크기 측정을 제공하지 않으므로, Cliff의 델타("모든 가능한 (HPR 에피소드, 비교 에피소드) 쌍 중에서, HPR의 값이 더 작은 경우가 얼마나 됩니까?")를 사용합니다. 이 조합(유의성 검사와 효과 크기 숫자)은 계속 사용되었는데, 이는 낮은 p-값만으로는 차이가 실제로 중요할 만큼 충분히 큰지 여부를 말하지 않기 때문입니다.

7. pandas를 사용한 그룹화, 분할 및 교차 분석 결과 처리

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

데이터/결과.jsonl (분석된 에피소드당 하나의 JSON 객체)를 pandas.DataFrame으로 로드하면, 다음과 같은 두 개의 한 줄 명령어로 두 가지 다른 질문에 대한 답변을 얻을 수 있습니다: .groupby(...).describe()는 코퍼스별 오디오 품질 분포에 대한 요약 (개수, 평균, 표준 편차, 사분위)을 한 번에 제공하며, pd.crosstab()은 HPR을 남성적/여성적 어휘 스타일과 비교하여 교차 분석을 수행합니다. 이는 두 개의 팟캐스트 컬렉션이 유사한 성별 어휘 스타일을 가지고 있는지 빠르게 확인하는 데 유용합니다.

8. matplotlib — 상위 뉴스 비교를 위한 박스 플롯

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

각 음역 범위별 박스 플롯, HPR, 비교 팟캐스트와의 비교, 평균 및 중앙값 표시.

9. Spacy 단어 벡터 — 주제 변화 측정, 단어 선택만 측정하는 것

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)
 if norm > 1e-6:
 chunk_vecs.append(v / norm)
 ...
 adj_sims = [float(np.dot(V[i], V[i + 1])) for i in range(n - 1)]

각 약 130단어 분량의 텍스트는 spaCy의 중간 영어 모델(en_core_web_md)을 사용하여 단일 벡터로 변환되며, 이 모델은 내장된 단어 벡터를 제공합니다. 그런 다음 L2 정규화를 통해 두 벡터 간의 내적을 계산할 수 있습니다.이것은그들의 코사인 유사도. 연속적인 덩어리의 유사성을 비교하면 "주제가 한 덩어리에서 다음 덩어리로 거의 이동하지 않는지"에 대한 점수를 얻을 수 있습니다.

10. 어휘의 다양성 — 텍스트 길이를 조작하지 않고 나타내는 어휘의 다양성

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

어리석은 어휘 다양성 점수(고유 단어 ÷ 총 단어)는 텍스트가 길어질수록 악화되는 경향이 있습니다. 이는 점수가 정의되는 방식 때문이며, 실제로 화자가 더 자주 반복하는 것은 아닙니다.

MTLD (텍스트 어휘 다양성 측정)은 텍스트 전체를 분석하여, 다양성이 특정 임계값을 밑돌 때까지 필요한 단어 수를 세고, 이를 평균하는 방식으로 작동합니다. 이 방식은 10분 분량의 에피소드와 40분 분량의 에피소드 간의 비교 가능성을 유지하도록 특별히 설계되었습니다.

11. vaderSentiment — 어휘 기반 감성 점수

vader = get_vader()  # SentimentIntensityAnalyzer()
 감성 점수 = [vader.polarity_scores(c)["compound"] for c in chunks]
 ...
 "sentiment_std": float(np.std(감성 점수)) if len(감성 점수) > 1 else None

VADER은 손으로 구축된 어휘 및 규칙 세트를 사용하여 각 텍스트 조각의 감성을 -1에서 +1 사이로 평가합니다(신경망이 아닌). 이 방법은 빠르고, 결정적이며, 수백 개의 에피소드당 각 조각별로 실행하기에 충분히 저렴합니다. 사용되는 값은 평균 감성이 아니라표준 편차에피소드의 특정 부분에서 감정의 흐름을 파악하는 방법: 감정적

음파가 지속적으로 움직이는지, 아니면 평평하게 유지되는지?

12. zlib (표준 라이브러리) — 압축 비율을 엔트로피 지표로 사용

압축 비율(text):
raw = text.encode("utf-8")
raw의 길이가 200자보다 짧으면:
반환 None
compressed = zlib.compress(raw, level=9)
반환 compressed의 길이 / raw의 길이

세 번째 외부 NLP 라이브러리는 전혀 사용되지 않았습니다. 표준 라이브러리의 zlib 모듈만 사용되었습니다. gzip 스타일의 압축기는 반복되는 패턴을 찾아 활용합니다. 만약 트랜스크립트를 크게 압축할 수 있다면, 해당 트랜스크립트는 비교적 반복적이고 예측 가능했으며, 압축이 거의 불가능하다면, 문구가 더 다양했다는 것을 의미합니다. 이는 대략적으로 "구어의 단조로움"을 나타냅니다.

표준 라이브러리 (re)를 사용하여 "HPR은 단독 호스트를 선호합니까?"라는 질문을 검증 가능한 규칙으로 변환

MULTI_PRESENTER_PATTERNS = [

음향 스피커 클러스터링 시도(5번 항목, 신뢰성이 떨어짐) 대신, 텍스트 기반 분석에서 더 신뢰할 수 있는 결과를 얻을 수 있었습니다: HPR의 자체 에피소드 메타데이터(로컬 지식 기반에서 가져옴)에는 호스트가 작성한 요약이 포함되어 있으며, "게스트", "인터뷰", "저와 함께" 등의 정규 표현식 패턴과 링크된 교정기 프로필 수를 세는 것만으로도 다중 발표 에피소드를 식별하는 데 충분했습니다.

14. multiprocessing.Pool — 수백 개의 파일을 분석하는 데 걸리는 시간을 줄이기 위해 동일한 분석을 실행

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

각 배치 파이프라인(음향 톤 분석, 화자 수 파악)은 동일한 구조를 사용했습니다: Pool(workers).imap_unordered(...)를 사용하여 여러 CPU 코어에서 동시에 각 에피소드별 함수를 실행하고, 각 함수가 완료될 때마다 결과를 JSON-lines 파일로 스트리밍했습니다. 전체 배치를 기다리는 대신, imap_unordered를 선택하여 단일 느린 에피소드가 이미 완료된 결과를 쓰는 것을 막았습니다.

15. subprocess + 요약 가능한 배치 루프 — 디스크 공간 제한 내에서 작업

def load_done_ids(results_path):
 만약 results_path가 존재하지 않으면,
 return set()
 done = set()
 with open(results_path) as f:
 for line in f:
 ...
 done.add(json.loads(line)["id"])
 return done
 
 ...
 rows = [r for r in rows if r["id"] not in done]
 ...
 for batch_start in range(0, len(rows), args.batch_size):
 batch = rows[batch_start:batch_start + args.batch_size]
 local_paths = fetch_batch(batch, args.scratch) # 원격 호스트에서 scp
 ...
 for p in local_paths.values():
 os.remove(p) # 다음 배치 전에 삭제

모든 팟캐스트 (~220GB)는 로컬 디스크 (~54GB 사용 가능)에 저장할 수 없었습니다. 해결책은 다음과 같은 루프였습니다: scp (subprocess를 통해)를 사용하여 오디오 파일을 한 번에 전송하고, 분석한 후 삭제하고, 다음 파일로 이동합니다. load_done_ids()는 이미 출력 파일에 있는 내용을 다시 읽어 전체 파이프라인을 재개할 수 있도록 합니다. 따라서 오류 또는 중간 중단이 발생해도 완료된 작업은 손실되지 않습니다.

링크

네, 흥미진진한 해커 퍼블릭 라디오의 세계: 기술 마법사들이 서로에게 가장 흥미로운 톤으로, 마치 마법처럼, 그들의 비밀을 공유하는 아늑한 작은 공간. 끝없는 "커뮤니티" 팟캐스트가 이렇게... 정말 흥미로운 것인지 누가 알았을까요? 📻️🎙️ 아마도 모든 사람들이 매년 한 번, 이 혼란에 기여하고 싶어하는 것 같아요—정말 멋진 일이죠! 😂
해커 퍼블릭 라디오는 해커, 개발자, 보안 전문가들이 실시간으로 소통하고 정보를 교환하는 온라인 라디오 방송입니다. 다양한 주제를 다루며, 특히 보안, 개발, 인터넷 문화 관련 토론이 활발합니다. 청취자들은 실시간 채팅을 통해 방송에 참여하고, 질문을 하거나 의견을 공유할 수 있습니다. 또한, 해커 퍼블릭 라디오는 다양한 오디오 콘텐츠를 제공하며, 녹음된 에피소드들을 웹사이트와 앱을 통해 청취할 수 있습니다.
#해커공개라디오 #기술커뮤니티 #팟캐스트 #단조로운매력 #흥미로운콘텐츠 #해커뉴스 #ngated

제공피드백이 에피소드에 대해.

출처
Hacker Public Radio
원본 열기 ↗

⚙콘텐츠가 기계 번역되었습니다.

이 뉴스가 유용했나요?

토론 0

Seja o primeiro a contribuir com o debate.

정보를 공유하고 논거를 홍보하세요

유용한 정보나 데이터를 게시하는 것을 주저하지 마세요.

토론에 참여하려면 로그인하거나 무료 계정을 만드세요.