HPR4746: מונוטוני

מאת Lee12/10/2026 às 00:0084 צפיות
Foto: CC BY-SA / Hacker Public Radio
🎙 Lee · Hacker Public Radio

התוכנית זו סומנה על ידי המפיץ כ"מתאימה לכל הגילאים".

פייתון וספריותיו

1. librosa — טעינה ושינוי גודל של אודיו

פונקציה: טעינת אודיו
def load_audio(path):
 y, sr = librosa.load(path, sr=SR, mono=True)
 return y, sr

כל פרק, ללא קשר לקצב הדגימה המקורי או למספר הערוצים שלו, נטענות לזרם מונו יחיד בקצב של 16 קילוהרץ (SR = 16000). זהו שורה אחת של librosa, שינוי קצב דגימה וקיפול סטריאו למונו. זה אפשר לכל כלי המשמש לאחר מכן (VAD, מעקב על גובה צליל, חילוץ MFCC) לפעול על קלט עקבי וצפוי, ללא קשר למה שהקודקוד MP3 או מארח הפודקאסט השתמשו בו במקור.

ניתוח קול באמצעות parselmouth (קשרי Praat) - מעקב אחר גובה צליל ואיכות קול

def pitch_and_hnr(y, sr):

parselmouth הוא חבילת Python סביב Praat, הכלי הסטנדרטי במחקר פונטי. זהו הלב של מדידת "מונוטוניות קול": הפונקציה to_pitch() מספקת את התדר הבסיסי (F0, בערך "גובה צליל") כל 20 מילישניות, והפונקציה to_harmonicity_cc() מספקת את היחס בין הרמוניות לרעש (HNR) באותן נקודות זמן — ציון איכות אות המשמש בהמשך כדי לסנן הערכות של גובה צליל המתקבלות מאודיו רועש או דחוס בצורה גרועה, ולא לאפשר לאודיו גרוע להיראות כמו "גובה צליל שטוח".

3. webrtcvad — קביעת אילו פריימים הם למעשה דיבור

def vad_speech_mask(y, sr, aggressiveness=2):

webrtcvad הוא גלאי פעילות קולית, חלק ממיזם WebRTC של גוגל – מחליט ברמת פריימים ("הקטע הזה של 30 מילישניות הוא דיבור או לא"). הוא דורש נתונים גולמיים של 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)

MFCCs (מקדם ספקטרלי של תדרים של מלו) מסכמים את הצורה של ספקטרום של חלון אודיו קצר - "טביעת אצבע" של קול נפוצה במשימות דיבור. הרעיון: לחתוך כל פרק לחלקים של 2 שניות, להפוך כל חלק לוקטור של טביעת אצבע, לתקנן את המאפיינים (z-score), ואז להשתמש ב-KMeans כדי לראות אם החלקים מתחלקים באופן טבעי לקבוצות של 2+ (קולות שונים) ולא לקבוצה אחת (קול אחד לאורך כל הפרק). הציון silhouette_score מעריך כמה הקבוצות מופרדות, ובחור הקבוצה הטובה ביותר - או חוזר לקול אחד אם אין קיבוצה שמספקת רמת איכות מינימלית. החלק הזה של החקירה לא עבד, מכיוון שהוא זיהה רעש כקול אחר.

6. scipy.stats.mannwhitneyu + קליף דלת - השוואה בין שתי קבוצות ללא הנחת התפלגות נורמלית

def cliffs_delta(a, b):

scipy.stats.mannwhitneyu הוא מבחן סטטיסטי: הוא משווה בין שתי קבוצות של מספרים (HPR מול פודקאסטים אחרים, לפי מדדים) מבלי להניח שהם מתפלגים באופן נורמלי — מתאים לנתונים אמיתיים עם חלוקה לא אחידה, כמו מדידות של טווח הצלילים. בספרי לא מספק מדד לגודל ההשפעה יחד עם המבחן, ולכן, "מתוך כל (HPR, פודקאסט אחר) זוג אפשרי, באיזו מידה הערך של HPR קטן יותר?" השימוש בזוג זה (בדיקת מובהקות סטטיסטית וכן מדד לגודל ההשפעה) היה נפוץ, מכיוון שערך p נמוך לבדו לא מציין האם ההבדל באמת משמעותי.

7. קבוצות, חלוקה לקבוצות, וטבלאות צולבות של תוצאות

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

לאחר טעינת קובץ הנתונים/תוצאות (one JSON object per analyzed episode) לתוך 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(נתונים, תוויות_ציר=["HPR", "Comparison"], showmeans=True)

גרף תיבות לפי מדד טווח, HPR, לצד פודקאסטים אחרים, כולל ממוצע וערך ממוצע.

9. וקטורים של מילים – מדידת שינוי נושאי, ולא רק בחירת מילים

nlp = get_nlp() # spacy.load("en_core_web_md")
 ...
 חלקי-מסמכים = [nlp(c) עבור c ב-chunks]
 וקטורים של חלקי-מסמכים = []
 עבור d ב-חלקי-מסמכים:
 v = d.vector
 norm = np.linalg.norm(v)
 אם norm > 1e-6:
 הוסף את v / norm
 ...
 דמיון קשרי = [float(np.dot(V[i], V[i +1])) עבור i ב-range(n -1)]

כל קטע של כ-130 מילים מהתמליל מוטמע כווקטור בודד באמצעות מודל האנגלית של spaCy (en_core_web_md, שמכיל וקטורים מובנים של מילים), ולאחר מכן מנורמלים באמצעות L2, כך שתוצר של מכפלה פנימית בין שני וקטוריםאיןהדמיון הקוסינוסי שלהם. השוואה בין חלקי טקסט סמוכים מספקת ציון של "האם הנושא משתנה מעט בין החלקים הסמוכים".

10. עומק אוצר מילים — מגוון אוצר מילים שלא מעוות את אורך התמליל

lr = עשׁירות מילולית (טקסט)
try:
 mtld = lr.mtld(סף=0.72)
exceptException:
 mtld = None

מדד מגוון אוצר מילים פשוט (מילים ייחודיות חלקי סך המילים) הולך ויורד ככל שהטקסט מתארך, זאת בשל ההגדרה של המדד – ולא בגלל שהדובר באמת חוזר על עצמו יותר.

MTLD (מדד של מגוון מילולי של טקסט) עושה זאת על ידי סריקת הטקסט וחישוב כמה מילים נדרשות עד שהמגוון יורד מתחת לסף מסוים, ולאחר מכן ממוצע — מדד שתוכנן במיוחד כדי לשמור על השוואה בין פרק של 10 דקות לפרק של 40 דקות.

11. vaderSentiment — ציון רגשות המבוסס על אוצר מילים

vader = get_vader() # SentimentIntensityAnalyzer()
 רמות סנטימנט: [vader.polarity_scores(c)["compound"] עבור c בכל החלקים]
 ...
 "sentiment_std": float(np.std(רמות סנטימנט)) אם len(רמות סנטימנט) > 1 אחרת None,

VADER מעריך את הסנטימנט של כל חלק בסולם של -1 עד +1 באמצעות אוצר מילים וסט כללים שנבנו באופן ידני (ולא רשת נוירונים) – שנבחרו בשל מהירותם, אופי דטרמיניסטי שלהם ועלותם הזולה מספיק כדי לפעול עבור כל חלק בהתבסס על מאות פרקים. הערך שמשתמשים בו אינו ממוצע הסנטימנט – אלאסטיית תקןהבעות רגש לאורך חלקים של הפרק: האם הרגש

האם הצליל נשמע באופן קבוע, או שהוא משתנה?

12. zlib (ספריה סטנדרטית) — יחס דחיסה כמדד לאנטרופיה

def יחס דחיסה(טקסט):
 מקור = טקסט.קידוד("utf-8")
 אם אורך(מקור) < 200:
 החזר None
 דחוס = zlib.דחיסה(מקור, רמה=9)
 החזר אורך(דחוס) / אורך(מקור)

לא השתמשו בספריה חיצונית של עיבוד שפה טבעית – רק במודול הסטנדרטי zlib. אלגוריתם דחיסה בסגנון gzip מזהה ומנצל דפוסים חוזרים; אם הוא מצליח לדחוס תמליל באופן משמעותי, אז התמליל היה חוזר על עצמו והיה צפוי, ואם הוא מצליח לדחוס אותו רק מעט, אז השפה הייתה מגוונת יותר. זה בערך אומר לכם "מונוטוניות מילולית".

התאמה ל-re (ספריה סטנדרטית) - המרת "האם HPR מעדיף מנחים בודדים" לבדיקה פונקציונלית

MULTI_PRESENTER_PATTERNS = [

במקום ניסיון קיבוץ דוברים על בסיס צליל (פריט 5, שהתברר כלא אמין), מקור מידע מהימן יותר הגיע מטקסט פשוט: הנתונים של הפרקים עצמם של HPR (שנלקחו ממחסן ידע מקומי) כוללים סיכומים שנכתבו על ידי המנחים, ורשימה קצרה של דפוסי regex ("אורח", "ראיון", "הצטרף אלי", וכו'), בנוסף לספירת פרופילי דוברים מקושרים, היה מספיק כדי לסמן פרקים עם מספר מנחים.

14. multiprocessing.Pool - הרצת ניתוח זהה על מאות קבצים ללא המתנה של מאות פעמים

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

כל תהליך (ניתוח צלילים, ספירת דוברים) השתמש באותו צורה: Pool(workers).imap_unordered(...) כדי להפעיל את הפונקציה לכל פרק במקביל על מספר ליבות מעבד, תוך העברת התוצאות לקובץ JSON-lines כאשר כל אחת מסיימת, ולא בזמן שהן ממתינות לכל התהליך. imap_unordered נבחר על פני map כדי למנוע ממקרה איטי אחד להאט את כתיבת התוצאות שכבר הסתיימו.

15. subprocess + לולאה בטוחה — עבודה תוך הגבלת שטח אחסון

def טעינת מזהים שהושלמו (נתיב_תוצאות):
אם הנתיב לא קיים:
החזר מערך ריק
הגדר מערך ריק
פתח את הקובץ בנתיב
עבור כל שורה בקובץ:
הוסף את המזהה מהשורה למערך
החזר את המערך
...
שורה = [שורה עבור שורה במערך השורה אם שורה["מזהה"] לא נמצא במערך]
...
עבור התחלה_קבוצה במגוון (0, אורך(שורה), גודל_קבוצה):
צור קבוצה = שורה[התחלה_קבוצה:התחלה_קבוצה + גודל_קבוצה]
קבל נתיבים מקומיים (קבוצה, נתיב_השריט) # העתקה באמצעות scp מהמחשב המרוחק
...
עבור כל p בנתיבים המקומיים:
מחק את p # מחק לפני הקבוצה הבאה

כל הפודקאסטים (כ-220GB) לא התאימו למחרוזת המקומית (כ-54GB פנויים). הפתרון היה לולאה: העברת קבוצה אחת של אודיו באמצעות scp (דרך subprocess), ניתוח, מחיקה והעברה לקבוצה הבאה. הפונקציה load_done_ids() מאפשרת לכל התהליך להתחיל מחדש על ידי קריאה מהקובץ הפלט, כך שכישלון או עצירה מכוונת באמצע לא יגרמו לאובדן של תוכן שהושלם.

קישורים

עולם הרדיו הציבורי של האקרים: פינה קטנה ומעניינת שבה גאוני הטכנולוגיה ✨️ חושפים את הסודות הקסומים שלהם ✨️ אחד לשני, בצורה המעניינת ביותר שניתן לדמיין. מי חשב שפודקאסטים של "קהילה" אינסופיים יכולים להיות כל כך... מרתקים? 📻️🎙️ נראה שכולם רק רוצים לתרום לקהל הזה פעם בשנה - איזו הזדמנות! 😂
https://hackerpublicradio.org/
עולם הרדיו הציבורי של האקרים: פינה קטנה ומעניינת שבה גאוני הטכנולוגיה ✨️ חושפים את הסודות הקסומים שלהם ✨️ אחד לשני, בצורה המעניינת ביותר שניתן לדמיין. מי חשב שפודקאסטים של "קהילה" אינסופיים יכולים להיות כל כך... מרתקים? 📻️🎙️ נראה שכולם רק רוצים לתרום לקהל הזה פעם בשנה - איזו הזדמנות! 😂

ספקמשובבפרק הזה.

מקור
Hacker Public Radio
פתיחת המקור ↗

⚙התוכן תורגם אוטומטית במכונה.

הידיעה הזו הייתה מועילה?

דיונים 0

היו הראשונים לתרום לדיון.

שתפו מידע וקדמו את הטיעון שלכם

אל תהססו לפרסם מידע או נתונים מועילים.

כדי להצטרף לדיון, התחברו או צרו חשבון חינם.