انتشر استخدام تطبيقات المساعدة على الهواتف الذّكيّة، وأصبح من المألوف علينا إرسال طلباتنا بشكلٍ صوتيٍّ لنرى الاستجابة النّصية جاهزةً على أجهزتنا، فمثلاً نحن عندما نستيقظ صباحاً نودّ الاستعلام عن الطّقس فنقول لجهاز الموبايل عبر برنامج غوغل فويس Google Voice
“Hey Google. What’s the weather like today?”
فيقوم غوغل بكتابة نمطِ الطّقس الكامل، وهذا ما يوفّر لنا الكثيرَ من الوقت فيمكن إلقاء نظرةٍ سريعةٍ على الشّاشة، والعودة إلى العمل بدلاً من كتابة كامل الاستعلام عن الطّقس على محرّك البحث غوغل.
لكنّ السّؤال المثير للعجب، هو أنّه کیف لبرنامج غوغل أن يحوّل الاستعلاماتِ الصّوتیّة إلى نصوص؟؟
إنّ غوغل یستخدم مزيجًا من تقنیّات التعلّم العميق ومعالجة اللّغات الطّبيعیّة (NLP)، لتحليل استعلامنا واسترداد الإجابة وتقديمها بشكلٍ نصّ.
يتمّ استخدام نفس مفهوم تحويل الكلام إلى نصٍّ في جميع تقنيّات التعرّف على الكلام الشائعة الأخرى ، مثل ألكسا Alexa من أمازون Amazon ، وسيري Siri من آبّل Apple ، وما إلى ذلك، وقد تختلف الدلالات من شركةٍ إلى أخرى، لكنّ الفكرة العامّة تظلّ كما هي.
والآن عزيزي القارئ كيف يمكننا بناء نموذج تحويل الكلام إلى نص خاص بنا باستخدام لغة بايثون ومهارات التعلّم العميق.
علينا الاطّلاع أولاً على أساسيّات أنظمة التعرّف على الكلام، أي مقدّمة لمعالجة الإشارات فستكون معالجة الإشارة الصّوتيّة أمراً جوهريّاً عندما نطّبق نموذج تحويل الكلام إلى نصٍّ خاص بنا باستخدام لغة بايثون.
المحتويات
ما هي الإشارة الصّوتيّة؟
إنّ أيّ جسمٍ يهتزّ يُنتج موجاتٍ صوتيّة. هل فكرت يومًا كيف يمكننا سماع صوت شخصٍ ما؟ إنّه بسبب الموجات الصّوتيّة.
عندما يهتزّ جسم ما، تتأرجح جزيئات الهواء ذهاباً وإياباً عن موضعها وتنقل طاقتها إلى الجزيئات المجاورة، مما يؤدّي إلى انتقال الطّاقة من جزيء إلى آخر ينتج بدوره موجة صوتيّة.
معاملات الإشارة الصّوتيّة
المطال Amplitude : مقدار إزاحة الهواء عن وضعيّة الخمود، وهو صفة تميّز الصّوت المرتفع من الصوت المنخفض.
قمّة ومنخفض الموجة Crest and Trough: القمّة هي أعلى نقطة في الموجة، بينما أسفل الموجة هو أدنى نقطة فيها.
الطّول الموجيّ Wavelength: يُعرّف الطّول الموجيّ بالمسافة بين قمّتين أو منخفضين متتاليين.
الدّورة Cycle: هي حركة الإشارة للقمّة والمنخفض مرّة واحدة، ولكلّ إشارة صوتيّة عدد من الدّورات.
تردّد الإشارة Frequency: يشير التردّد إلى سرعة تغيّر الإشارة عبر الزّمن، أي عدد المرّات التي تكررت فيها الإشارة نفسها خلال ثانية واحدة، ويكون تردّد الصّوت النّاعم أعلى من تردّد الصّوت الخشن.
أنواع الإشارات
تنقسم الإشارات بشكل أساسي إلى نوعين: الإشارات الرّقميّة والإشارات التّشابهيّة.
الإشارات الرّقميّة:
الإشارة الرّقميّة هي تمثيل غير مترابط للإشارة عبر فترة من الزّمن. حيث يوجد عدد محدود من العيّنات بين أيّة فترتين زمنيّتين.
على سبيل المثال ، يشکّل متوسّط علامات طلّاب كليّة الهندسة من الرّتبة العليا والمتوسّطة على مدار السنوات الدراسيّة إشارة رقميّة، نظرًا لأنّه ينتج عنه عدد محدود من العيّنات.
الإشارات التّشابهيّة
تُعرّف الإشارة التّشابهيّة بأنّها تمثيل مستمرّ للإشارة خلال مدّة زمنيّة، نلاحظ وجود عددٍ غير منتهٍ من العيّنات بين كلّ فترتين زمنيّتين في الإشارة التّشابهيّة على عكس الإشارة الرّقميّة التي تحوي عدداً محدداً من العيّنات.
على سبيل المثال، تشکّل إشارة الصّوت إشارة تشابهيّة لأنّها تمثيل مستمرّ للإشارة.
ما هو مفهوم أخذ العيّنات وما هي أهميّته؟
الإشارة الصّوتية تمثيل مستمرّ للمطالات التي تختلف وفقاً للزّمن، وتكون الفترات الزّمنيّة متقاربة جداً حيث يُقاس الزّمن بالـ بيكو ثانية.
إنّ التّعامل مع الإشارات التّشابهيّة يأخذ حجماً كبيراً من الذّاكرة، لأنّ الإشارة التّشابهيّة تحوي عدداً غير منتهٍ من العیّنات، وتتطلّب معالجة هذه العيّنات تعقيداتٍ حسابيّة كبيرة.
لذلك نحتاج إلى تقنيّة لتحويل الإشارة التّشابهيّة إلى إشارة رقميّة يمكن معالجتها بسهولة.
إنّ أخذ عيّنات الإشارة يعني تحويل الإشارة من إشارة تشابهيّة إلى إشارة رقمية، وذلك باختيار عددٍ محدّد من عيّنات الإشارة التّشابهيّة في الثّانية.
إذاً نستطيع تحويل الإشارة الصّوتيّة إلى إشارة رقميّة، من خلال أخذ العيّنات فيصبح تخزينها في الذّاكرة والتّعامل معها ومعالجتها أكثر فعاليّة.
عزيزي القارئ دعنا نتابع سويةً كيفيّة تخزين الإشارة الصّوتيّة في الذّاكرة من خلال أخذ العيّنات.
يعرّف معدّل أخذ العیّنات أو تكرار أخذها على أنّه عدد العيّنات المختارة في الثّانية، فكلّما كان معدّل أخذ العیّنات كبيراً، كلّما ضمنّا عدم تماثل الإشارة الرّقمية النّاتجة عن الإشارة التّشابهيّة مع إشارة رقميّة ناتجة عن إشارة تشابهيّة أخرى.
تقانات استخلاص ميّزات الإشارة الصّوتيّة
إنّ الخطوة الأولى في التعرّف على الصّوت هي استخلاص الميزات التي ستشكل دخل نموذج التعرف عليھ ، لذلك سنتكلّم عن الطّرق المختلفة لاستخلاص الميّزات من الإشارة الصّوتيّة.
المجال الزّمنيّ
يتمّ تمثيل الإشارة الصّوتية في المجال الزّمنيّ كدالّة من المطالات بدلالة الزمن.
بتعبيرٍ بسيط يمكننا القول أنّ الإشارة في المجال الزّمنيّ هي رسم بياني بين المطال والزّمن.
وتكون الميّزات في هذه الحالة هي المطالات المأخوذة خلال فترات زمنيّة مختلفة.
إنّ تمثيل الإشارة في المجال الزّمنيّ يتجاهل معلومات معدّل الإشارة، مما يجعلنا نرغب في تمثيل الإشارة في المجال التردّدي.
المجال التردّدي
يتمّ تمثيل الإشارة الصّوتية في المجال التردّدي كدالّة من المطالات بدلالة التردّد، أي ببساطة يمكننا القول أنّ الإشارة الصّوتية في المجال التردّدي هي رسم بيانيّ بين المطال والتردّد. وتكون الميّزات في هذا التمثيل هي المطالات المسجلة عند تردّدات مختلفة.
تكمن محدودیّة هذا التمثيل بأنّه يتجاهل ترتيب تسلسل الإشارة المأخوذ بعين الاعتبار في المجال الزّمنيّ.
ملاحظة: عزيزي القارئ لا بدّ أن ننتبه أنّ تحليل الإشارة في المجال الزّمنيّ يتجاهل تمامًا مكوّن التردّد، بينما تحليل الإشارة في المجال التردّدي لا يعطي اهتماماً لمكوّن الزّمن.
نريد أن نأخذ التردّدات المعتمدة على الزّمن وذلك بالاستعانة بمخطّط الطّيف.
مخطّط الطّيف
هل سمعتم بمخطّط الطّيف؟ يعرّف مخطّط الطّيف أنّه رسم ثنائيّ البعد بين الزّمن والتردّد، حيث تمثّل كلّ نقطة في هذا الرّسم مطال تردّد معيّن في زمن معيّن من حيث كثافة اللّون.
إذاً يمكننا القول أنّ مخطّط الطّيف يشكّل نطاقاً واسعاً من الألوان للتردّدات المختلفة مع الزّمن.
التعرّف على الكلام المنطوق
تقوم الشّركات البرمجيّة بتحديثات مستمرّة لتنتج واجهاتٍ جديدة على أجهزتنا الموبايل، لكنّ الشّاشة التي يحلم بها الجميع أن تكون شاشة متفاعلة مع كلامنا وليس فقط مع صورنا وكتاباتنا.
أصدرت مكتبة تنسر فلو tensorflow التي يمكن التعامل معها في لغة البايثون python مجموعةَ بيانات للأوامر الكلاميّة المنطوقة متضمّنة 65000 صوتاً مدّة كلّ صوت ثانية واحدة كما يتألّف كلّ صوت من 30 كلمة قصيرة مسجلّة لآلاف الأشخاص المختلفين.
والآن حان الوقت لبناء نظام التعرّف على الكلام الذي يفهم الأوامر المنطوقة البسيطة.
تطبيق نموذج التعرّف على الكلام المنطوق بلغة البايثون
تضمين المكتبات:
تُستخدم مكتبة تحليل الصوت والموسيقى LibROSA ومكتبة الحسابات العلمية SciPy لمعالجة الإشارات الصّوتيّة، ويمكن تضمينهم باستخدام تعليمة التّضمينimport وكذلك نحتاج لتضمين بعضَ المكتبات الأخرى، مثل مكتبة التّعامل مع نظام التّشغيل os ومكتبةُ بايثون العدديَّةُ numpy و مكتبة الإظهار display ومكتبة الرّسم البيانيّ matplotlib.
import os
import librosa
import IPython.display as ٨ipd
import matplotlib.pyplot as plt
import numpy as np
from scipy.io import wavfile import warnings
warnings.filterwarnings("ignore")
يساعدنا استكشاف البيانات وإظهارها على فهم البيانات، وبالتّالي نستطيع إجراءَ المعالجة المُسبقة عليها بطريقةٍ أفضل.
إظهار الإشارة الصّوتيّة في المجال الزّمنيّ
نستخدم المكتبة ليبروسا librosa لتحميل الملف الصّوتيّ الذي يمثل الإشارة الصّوتيّة، ثمّ نرسم هذه الإشارة بالاستعانة بمكتبة الرّسم البيانيّ matplotlib.
نلاحظ في تعليمة librosa.load معامل sr وهو يعبر عن معدل أخذ العينات للإشارات الصوتية.
train_audio_path = '/content/train/audio'
samples, sample_rate = librosa.load('/content/train/audio/yes/0a7c2a8d_nohash_0.wav', sr = 16000)
fig = plt.figure(figsize=(14, 8))
ax1 = fig.add_subplot(211)
ax1.set_title('Raw wave of ' + '../input/train/audio/yes/0a7c2a8d_nohash_0.wav')
ax1.set_xlabel('time')
ax1.set_ylabel('Amplitude')
ax1.plot(np.linspace(0, sample_rate/len(samples), sample_rate), samples)
أخذ العیّنات
لنرَ معدّل أخذ العيّنات أي عدد العيّنات المأخوذة في الثّانية الواحدة نطبّق التّعليمات التّالية:
ipd.Audio(samples, rate=sample_rate)
print(sample_rate)
تغيير معدّل أخذ العیّنات
ممّا سبق يمكننا أن نفهم أنّ معدّل أخذ العيّنات للإشارة هو 16000 هرتز، لنغير معدّل أخذ العيّنات إلى 8000 هرتز لأنّ معظم التردّدات المتعلّقة بالكلام في مجموعة البيانات المستخدمة موجودة عند 8000 هرتز.
samples = librosa.resample(samples, sample_rate, 8000)
ipd.Audio(samples, rate=8000)
إنّ مجموعة البيانات التي نعمل عليها عبارة عن مجموعة أوامر مسجّلة صوتيّاً، لذلك دعونا نعرف عدد التّسجيلات الصّوتيّة لكلّ أمر command الموجودة في مجموعة البيانات وذلك برسم مخطّط بيانيّ بالاستفادة من مكتبة الرّسم matplotlib.
labels=os.listdir(train_audio_path)
#find count of each label and plot bar graph
no_of_recordings=[]
for label in labels:
waves = [f for f in os.listdir(train_audio_path + '/'+ label) if f.endswith('.wav')]
no_of_recordings.append(len(waves))
#plot
plt.figure(figsize=(30,5))
index = np.arange(len(labels))
plt.bar(index, no_of_recordings)
plt.xlabel('Commands', fontsize=12)
plt.ylabel('No of recordings', fontsize=12)
plt.xticks(index, labels, fontsize=15, rotation=60)
plt.title('No. of recordings for each command')
plt.show()
labels=["yes", "no", "up", "down", "left", "right", "on", "off", "stop", "go"]
مدّة التّسجيلات الصّوتيّة
لنلقِ نظرةً على توزيع مدّة التّسجيلات من خلال رسم مخطّط الهستوغرام للتّسجيلات الصّوتيّة وذلك من خلال التّعليمات التّالية :
duration_of_recordings=[]
for label in labels:
waves = [f for f in os.listdir(train_audio_path + '/'+ label) if f.endswith('.wav')]
for wav in waves:
sample_rate, samples = wavfile.read(train_audio_path + '/' + label + '/' + wav)
duration_of_recordings.append(float(len(samples)/sample_rate))
plt.hist(np.array(duration_of_recordings))
المعالجة المُسبقة للإشارات الصّوتيّة
لاحظنا في فقرات عرض واستكشاف البيانات، أنّ مدّة بعض التّسجيلات أقلّ من ثانية واحدة وأنّ معدّل أخذ العيّنات لبعض التّسجيلات مرتفع جداً. لذا دعونا نقرأ الموجات الصّوتيّة ونعالجها من خلال:
إعادة أخذ العيّنات لتخفيض معدّل أخذها .
حذف الأوامر القصيرة التي تقلّ عن ثانية واحدة.
تتمثّل معالجة الإشارات الصّوتييّة بالشّفرة البرمجيّة التّالية:
train_audio_path = '/content/train/audio/'
all_wave = []
all_label = []
for label in labels:
print(label)
waves = [f for f in os.listdir(train_audio_path + '/'+ label) if f.endswith('.wav')]
for wav in waves:
samples, sample_rate = librosa.load(train_audio_path + '/' + label + '/' + wav, sr = 16000)
samples = librosa.resample(samples, sample_rate, 8000)
if(len(samples)== 8000) :
all_wave.append(samples)
all_label.append(label)
ثمّ نقوم بتشفير أصناف الخرج إلى أرقام صحيحة كما في الشّفرة التّالية:
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
y=le.fit_transform(all_label)
classes= list(le.classes_)
سنحوّل الرّقم الصّحيح المشفّر إلى شعاعٍ بطريقة تحويل one hot vector لأنّ المسألة تصنيف متعدّد multi classification.
from keras.utils import np_utils
y=np_utils.to_categorical(y, num_classes=len(labels))
نعيد تشكيلَ أبعاد المصفوفة بما يتناسب مع دخل conv1D ولذلك نجعل المصفوفة ثنائيّة البعد بثلاثة أبعاد من خلال التّعليمة التّالية.
all_wave = np.array(all_wave).reshape(-1,8000,1)
نقسّم البيانات إلى بيانات تدريب بنسبة 80% وبيانات تحقّق بنسبة 20% من خلال التّعليمات التّالية:
from sklearn.model_selection import train_test_split
x_tr, x_val, y_tr, y_val = train_test_split(np.array(all_wave),np.array(y),stratify=y,test_size = 0.2,random_state=777,shuffle=True)
بنية النّموذج
سنبني نموذج تحويل الصّوت إلى نصّ باستخدام شبكة الطّي العصبونيّة Conv1d التي تنجز عملية الطّي على امتداد ذي بعد واحد.
بناء النّموذج
سيتمّ بناء النّموذج باستخدام مكتبة كيراس.
يكون شكل الدّخل في النّموذج 8000*1 لأن معدّل العيّنات في الإشارة الصّوتيّة هو 8000 ، يتألّف النّموذج من أربع طبقات طيّ كما في الشّكل (9) ولكلّ طبقة طيّ طبقة تجمِيعٌ وفقَ القيمةِ الكُبرى max pooling.
from keras.layers import Dense, Dropout, Flatten, Conv1D, Input, MaxPooling1D
from keras.models import Model
from keras.callbacks import EarlyStopping, ModelCheckpoint
from keras import backend as K
K.clear_session()
inputs = Input(shape=(8000,1))
#First Conv1D layer
conv = Conv1D(8,13, padding='valid', activation='relu', strides=1)(inputs)
conv = MaxPooling1D(3)(conv)
conv = Dropout(0.3)(conv)
#Second Conv1D layer
conv = Conv1D(16, 11, padding='valid', activation='relu', strides=1)(conv)
conv = MaxPooling1D(3)(conv)
conv = Dropout(0.3)(conv)
#Third Conv1D layer
conv = Conv1D(32, 9, padding='valid', activation='relu', strides=1)(conv)
conv = MaxPooling1D(3)(conv)
conv = Dropout(0.3)(conv)
#Fourth Conv1D layer
conv = Conv1D(64, 7, padding='valid', activation='relu', strides=1)(conv)
conv = MaxPooling1D(3)(conv)
conv = Dropout(0.3)(conv)
#Flatten layer
conv = Flatten()(conv)
#Dense Layer 1
conv = Dense(256, activation='relu')(conv)
conv = Dropout(0.3)(conv)
#Dense Layer 2
conv = Dense(128, activation='relu')(conv)
conv = Dropout(0.3)(conv)
outputs = Dense(len(labels), activation='softmax')(conv)
model = Model(inputs, outputs)
model.summary()
عند بناء النّموذج الموضّح بالشّفرة البرمجيّة سيكون ملخّص النّموذج كما يلي :
Model: “model”
_________________________________________________________________
Layer (type) Output Shape Param #
===============================================================
input_1 (InputLayer) [(None, 8000, 1)] 0
_________________________________________________________________
conv1d (Conv1D) (None, 7988, 8) 112
_________________________________________________________________
max_pooling1d (MaxPooling1D) (None, 2662, 8) 0
_________________________________________________________________
dropout (Dropout) (None, 2662, 8) 0
_________________________________________________________________
conv1d_1 (Conv1D) (None, 2652, 16) 1424
_________________________________________________________________
max_pooling1d_1 (MaxPooling1 (None, 884, 16) 0
_________________________________________________________________
dropout_1 (Dropout) (None, 884, 16) 0
_________________________________________________________________
conv1d_2 (Conv1D) (None, 876, 32) 4640
_________________________________________________________________
max_pooling1d_2 (MaxPooling1 (None, 292, 32) 0
_________________________________________________________________
dropout_2 (Dropout) (None, 292, 32) 0
_________________________________________________________________
conv1d_3 (Conv1D) (None, 286, 64) 14400
_________________________________________________________________
max_pooling1d_3 (MaxPooling1 (None, 95, 64) 0
_________________________________________________________________
dropout_3 (Dropout) (None, 95, 64) 0
_________________________________________________________________
flatten (Flatten) (None, 6080) 0
_________________________________________________________________
dense (Dense) (None, 256) 1556736
_________________________________________________________________
dropout_4 (Dropout) (None, 256) 0
_________________________________________________________________
dense_1 (Dense) (None, 128) 32896
_________________________________________________________________
dropout_5 (Dropout) (None, 128) 0
_________________________________________________________________
dense_2 (Dense) (None, 10) 1290
================================================================
Total params: 1,611,498
Trainable params: 1,611,498
Non-trainable params: 0
سنختار تابعَ التّكلفة الانتروبيا المتقاطعة الفئويّة categorecal cross entropy لأنّ المسألة هي تصنيف متعدّد الأصناف multi classificaton.
model.compile(loss='categorical_crossentropy',optimizer='adam',metrics=['accuracy'])
التوقّف المبکّر والنّقاط العلاّمة checkpoints هي عملیّات لإيقاف تدريب الشّبكة العصبونیّة في الوقت المناسب وحفظ أفضل نموذج بعد كلّ دورة epoch.
es = EarlyStopping(monitor='val_loss', mode='min', verbose=1, patience=10, min_delta=0.0001)
mc = ModelCheckpoint('best_model.hdf5', monitor='val_acc', verbose=1, save_best_only=True, mode='max')
دعنا ندرّب النّموذج بتقسيم عیّنات التّدريب إلى دفعاتٍ كلّ دفعة بحجم 32.
history=model.fit(x_tr, y_tr ,epochs=100, callbacks=[es,mc], batch_size=32, validation_data=(x_val,y_val))
لقد تمّ التّوقف عند الدّورة 39
Epoch 00039: early stopping
سنعتمد على الرّسوم البيانیّة لفهم أداء النّموذج خلال فترة زمنیّة معيّنة برسم منحني تابع الخسارة لكل من عينات التدريب والاختبار.
from matplotlib import pyplot
pyplot.plot(history.history['loss'], label='train')
pyplot.plot(history.history['val_loss'], label='test')
pyplot.legend()
pyplot.show()
نلاحظ أن منحنيات الخسارة لبيانات التدريب والاختبار متقاربة مما يدلّ على أنّ النموذج تدّرب وأصبح جاهزاً.
بعد حفظ النموذج نستخدم التعليمة التالية لتحميله.
from keras.models import load_model
model=load_model('best_model.hdf5')
والآن لنعرّف التابع الذي يعطي النّص من أجل تسجيلٍ صوتيٍّ ما.
def predict(audio):
prob=model.predict(audio.reshape(1,8000,1))
index=np.argmax(prob[0])
return classes[index]
يمكنكم الاطلاع على كامل الشيفرة البرمجية في مستودع الجت هب الخاص بموقع الذكاء الاصطناعي باللغة العربية
الخلاصــــة
قدّمنا في هذه المقالة كيفیّة بناء نموذج تعلّم عميق للتعرّف على الصّوت وتحويله إلى نصٍّ وذلك خطوةً بخطوة.
حيث عرضنا خصائصَ الإشارات الصّوتيّة وطرقَ معالجتها في المجال الزّمنيّ والتردّدي ثمّ انتقلنا إلى بناء نموذج شبكة الطّيّ العصبونيّة وضبطنا معاملاته بحيث حصلنا على أفضل النّتائج في تحويل الصّوت إلى نصٍّ على مجموعة بيانات معياريّة من موقع كاغل kaggle.
يسرّني أن أرى تعليقاتكم حول هذا المقال في عرض التعليقات في الأسفل.
المراجــــــع
1-Learn how to Build your own Speech-to-Text Model (using Python)
2- https://www.kaggle.com/c/tensorflow-speech-recognition-challenge.
9 تعليقات
مفيد جداً جداً جزاكم الله خيراً.
مقالة رائعة وشرح أروع كل التحية والتقدير لكم دكتورة دانيا
شكرا جزيلا لكم ولكن لدي سؤال
كيف اقوم بتدريب هذه الشبكة على dataset خاص بي
كيف ادرب الشبكة على dataset خاصة فيني
اهلا بك
أولا تحصلل مجموعة بياناتك يعني يجب أن تعرف معدل العينات في إشارتك الصوتية بنفس الطريقة التي استخدمتها.
ثم تجعل شكل الدخل للنموذج متوافق مع عدد العينات التي في مثالنا 8000
و تقسم بياناتك الى بيانات تدريب وبيانات اختبار كما فعلنا في مثالنا في هذا المقال. وباتباع نفس الخطوات وصولا الى تعليمة fit المعبرة عن التدريب تخصل على النتيجة لكن يجب أن تراعي أن عدد الفلاتر وحجمها هو بارامتر تجريبي وفقا لبياناتك فقد تغير في قيم هذه البارامترات.
بالتوفيق..
السلام عليكم
هل من ثمة خطأ في الشيفرة البرمجية في الفقرة: “إظهار الإشارة الصّوتيّة في المجال الزّمنيّ” ؟
بالضبط في هذا السطر:
“ax1.plot(np.linspace(0, sample_rate/len(samples), sample_rate), samples)”
أعتقد بأن الصواب يجب أن يكون كالتالي:
“ax1.plot(np.linspace(0, len(samples)/sample_rate, len(samples)), samples)”
صحيح؟
السلام عليكم
وجزاك الله خير
هل مدة التعرف فقط ثانية واحدة!
وهل استطيع تمديدها بحيث اتكلم وقت ما اريد ولا اتقيد بالثانية