تشخیص گفتار فارسی با استفاده از تابع هزینه CTC

مقدمه و معرفی

در این پروژه، به طراحی و پیاده‌سازی یک سیستم تشخیص گفتار فارسی با استفاده از تکنیک‌های یادگیری عمیق پرداخته‌ایم. هدف اصلی این پروژه، توسعه مدلی است که بتواند گفتار فارسی را به متن تبدیل کند. این سیستم می‌تواند در کاربردهای مختلفی نظیر سرویس‌های دستیار صوتی، ترجمه ماشینی و زیرنویس خودکار استفاده شود.

داده‌های پروژه

داده‌های استفاده شده در این پروژه، بخشی از یک پروژه تشخیص گفتار از شرکت Hamtech است که توسط آقای مسعود پرپنچی در مخزن GitHub به اشتراک گذاشته شده است. این مجموعه داده شامل فایل‌های صوتی و متن‌های مربوط به آن‌ها می‌باشد که از این مخزن قابل دسترسی است. از آقای پرپنچی برای اشتراک‌گذاری این مجموعه داده قدردانی می‌کنیم.

مراحل پروژه

مرحله 1: دانلود و آماده‌سازی داده‌ها

در این مرحله، ابتدا داده‌های صوتی و متن مربوطه را از مخزن GitHub دانلود کرده و آنها را به فرمتی مناسب برای آموزش مدل تبدیل می‌کنیم. برای این کار از کتابخانه gdown برای دانلود فایل‌های صوتی و CSV استفاده کرده‌ایم.

کد دانلود و آماده‌سازی داده‌ها:

import gdown
# دانلود فایل‌های صوتی و فایل CSV
url_audio = 'https://drive.google.com/uc?id=1jyvhdZHn0s5Owkr21k5Ff-c96sIQLtEu'
output_audio = 'all_wav.zip'
gdown.download(url_audio, output_audio, quiet=False)
!unzip -q 'all_wav.zip' -d '/content/all_wav'

url_csv = 'https://drive.google.com/uc?id=1vqvn0F0YYhEFbzLgP9wJ36vyInUnO5b5'
output_csv = 'dataset.csv'
gdown.download(url_csv, output_csv, quiet=False)
  • gdown: کتابخانه‌ای است که به ما امکان می‌دهد فایل‌ها را به‌راحتی از گوگل درایو دانلود کنیم.
  • unzip: دستوری در سیستم‌عامل لینوکس است که فایل‌های فشرده با فرمت ZIP را استخراج می‌کند.
 

مرحله 2: پردازش و آماده‌سازی داده‌ها

در این مرحله، داده‌های صوتی و متنی را پردازش کرده و به فرمتی که برای آموزش مدل مناسب باشد تبدیل می‌کنیم. این شامل بارگذاری فایل‌های صوتی، استخراج ویژگی‌های صوتی (مانند MFCC)، و برچسب‌گذاری متن‌ها می‌باشد. همچنین، داده‌های صوتی را با اضافه کردن نویز و جابجایی زمانی تقویت می‌کنیم تا مدل بتواند بهتر تعمیم یابد.

کد پردازش داده‌ها:

# تابع بارگذاری فایل صوتی
def load_audio(file_path, sr=16000):
audio, _ = librosa.load(file_path, sr=sr)
return audio

# تابع استخراج ویژگی‌های صوتی
def extract_features(audio, n_mfcc=20, sr=16000):
mfcc_features = librosa.feature.mfcc(y=audio, sr=sr, n_mfcc=n_mfcc)
delta_mfcc = librosa.feature.delta(mfcc_features)
combined = np.vstack((mfcc_features, delta_mfcc)).T
return combined

# تقویت داده‌های صوتی
def add_noise(audio, noise_factor=0.005):
noise = np.random.randn(len(audio))
augmented_audio = audio + noise_factor * noise
augmented_audio = augmented_audio.astype(type(audio[0]))
return augmented_audio

def shift_time(audio, shift_max=0.2):
shift = np.random.randint(int(shift_max * 16000))
if np.random.rand() > 0.5:
shift = -shift
augmented_audio = np.roll(audio, shift)
if shift > 0:
augmented_audio[:shift] = 0
else:
augmented_audio[shift:] = 0
return augmented_audio

# آماده‌سازی داده‌ها
augment_functions = [add_noise, shift_time]
X, y, input_lengths, label_lengths = [], [], [], []

for index, row in df.iterrows():
audio_path = row['wav_filename']
audio = load_audio(audio_path)

# ویژگی‌های صوتی اصلی
features = extract_features(audio)
X.append(features)
input_lengths.append(features.shape[0])
label = [char_map.get(c, char_map[' ']) for c in row['transcript']]
y.append(label)
label_lengths.append(len(label))

# ویژگی‌های صوتی تقویت شده
for augment_func in augment_functions:
augmented_audio = augment_func(audio)
features = extract_features(augmented_audio)
X.append(features)
input_lengths.append(features.shape[0])
y.append(label)
label_lengths.append(len(label))

if len(X) == 0 یا len(y) == 0:
raise ValueError("No valid audio files were found. Please check the dataset and the paths.")

X = tf.keras.preprocessing.sequence.pad_sequences(X, padding='post', dtype='float32')
y = tf.keras.preprocessing.sequence.pad_sequences(y, padding='post', value=-1)
  • librosa: کتابخانه‌ای است که برای تحلیل و پردازش فایل‌های صوتی استفاده می‌شود.
  • mfcc_features: ویژگی‌های MFCC (Mel-frequency cepstral coefficients) هستند که برای نمایش ویژگی‌های فرکانسی صوت به کار می‌روند.
  • add_noise: این تابع برای اضافه کردن نویز به سیگنال صوتی به منظور تقویت داده‌ها استفاده می‌شود.
  • shift_time: این تابع برای جابجایی زمانی سیگنال صوتی به منظور تقویت داده‌ها استفاده می‌شود.

مرحله 3: تعریف مد

در این مرحله، یک مدل یادگیری عمیق با استفاده از لایه‌های LSTM دوطرفه، BatchNormalization و TimeDistributed Dense تعریف می‌کنیم. این مدل برای پردازش توالی‌های زمانی طراحی شده و از تابع هزینه CTC برای آموزش استفاده می‌کند.

تعریف مفاهیم مورد استفاده:

  • LSTM دوطرفه (Bidirectional LSTM): LSTM یک نوع از شبکه‌های عصبی بازگشتی (RNN) است که برای مدل‌سازی داده‌های ترتیبی و زمانی به کار می‌رود. LSTM دوطرفه به این معناست که مدل به جلو و عقب در طول توالی حرکت می‌کند و اطلاعات را از هر دو جهت جمع‌آوری می‌کند. این به مدل کمک می‌کند تا اطلاعات متنی بیشتری را برای هر توالی در نظر بگیرد.
  • BatchNormalization: این لایه برای نرمال‌سازی ورودی هر لایه در شبکه عصبی استفاده می‌شود. نرمال‌سازی به بهبود سرعت آموزش و پایداری مدل کمک می‌کند. BatchNormalization باعث می‌شود که توزیع ورودی به هر لایه به طور ثابت در طول آموزش تغییر نکند.
  • TimeDistributed Dense: این لایه به هر گام زمانی در توالی به طور جداگانه اعمال می‌شود. به عبارت دیگر، این لایه یک لایه چگال (Dense) را به تمام گام‌های زمانی در توالی ورودی اعمال می‌کند. این لایه به مدل اجازه می‌دهد که پیش‌بینی‌های جداگانه‌ای برای هر گام زمانی انجام دهد.
  • کد تعریف مدل:
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, LSTM, Dense, Masking, TimeDistributed, Activation, Bidirectional, BatchNormalization, Lambda

# تعریف مدل
input_data = Input(name='the_input', shape=(None, 40))
masking_layer = Masking(mask_value=0.0)(input_data)
bilstm_layer_1 = Bidirectional(LSTM(256, return_sequences=True))(masking_layer)
batch_norm_1 = BatchNormalization()(bilstm_layer_1)
bilstm_layer_2 = Bidirectional(LSTM(256, return_sequences=True))(batch_norm_1)
batch_norm_2 = BatchNormalization()(bilstm_layer_2)
time_dense = TimeDistributed(Dense(len(char_map) + 1))(batch_norm_2)
y_pred = Activation('softmax', name='activation')(time_dense)

# تعریف تابع هزینه CTC
def ctc_lambda_func(args):
y_pred, labels, input_length, label_length = args
return tf.keras.backend.ctc_batch_cost(labels, y_pred, input_length, label_length)

# کامپایل مدل
labels = Input(name='the_labels', shape=[None], dtype='float32')
input_length = Input(name='input_length', shape=[1], dtype='int64')
label_length = Input(name='label_length', shape=[1], dtype='int64')
loss_out = Lambda(ctc_lambda_func, output_shape=(1,), name='ctc')([y_pred, labels, input_length, label_length])

model = Model(inputs=[input_data, labels, input_length, label_length], outputs=loss_out)
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.0005), loss={'ctc': lambda y_true, y_pred: y_pred})

مرحله 4: تقسیم داده‌ها و تعریف مولد داده

در این مرحله، داده‌های پردازش شده را به مجموعه‌های آموزش و اعتبارسنجی تقسیم می‌کنیم و یک مولد داده برای پردازش داده‌ها در حین آموزش تعریف می‌کنیم.

تعریف مفاهیم مورد استفاده:

  • train_test_split: این تابع داده‌ها را به دو مجموعه آموزشی و آزمایشی تقسیم می‌کند. این کار برای ارزیابی عملکرد مدل بر روی داده‌هایی که در طی آموزش ندیده است، استفاده می‌شود.
  • data_generator: این تابع یک مولد داده است که به طور مداوم داده‌ها را در طول آموزش به مدل تغذیه می‌کند. این مولد داده به ویژه برای کار با مجموعه داده‌های بزرگ که نمی‌توانند به طور کامل در حافظه قرار گیرند، مفید است.

کد تقسیم داده‌ها و تعریف مولد داده:

from sklearn.model_selection import train_test_split

# تقسیم داده‌ها به مجموعه‌های آموزش و اعتبارسنجی
X_train, X_val, y_train, y_val, input_length_train, input_length_val, label_length_train, label_length_val = train_test_split(
X, y, input_lengths, label_lengths, test_size=0.2, random_state=42)

# تعریف مولد داده
def data_generator(X, y, input_lengths, label_lengths, batch_size=16):
while True:
for i in range(0, len(X), batch_size):
X_batch = X[i:i+batch_size]
y_batch = y[i:i+batch_size]
input_lengths_batch = input_lengths[i:i+batch_size]
label_lengths_batch = label_lengths[i:i+batch_size]
yield (
{
'the_input': np.array(X_batch),
'the_labels': np.array(y_batch),
'input_length': np.array(input_lengths_batch),
'label_length': np.array(label_lengths_batch)
},
{'ctc': np.zeros([len(X_batch)])}
)

train_gen = data_generator(X_train, y_train, input_length_train, label_length_train, batch_size=16)
val_gen = data_generator(X_val, y_val, input_length_val, label_length_val, batch_size=16)

مرحله 5: تعریف callbacks و آموزش مدل

در این مرحله، callbacks برای متوقف کردن زودهنگام و ذخیره بهترین مدل تعریف شده و مدل آموزش داده می‌شود.

تعریف مفاهیم مورد استفاده:

  • EarlyStopping: این callback برای متوقف کردن زودهنگام آموزش زمانی که بهبود قابل توجهی در اعتبارسنجی مشاهده نمی‌شود، استفاده می‌شود.
  • ModelCheckpoint: این callback برای ذخیره مدل در زمان بهبود عملکرد آن بر روی مجموعه اعتبارسنجی استفاده می‌شود.

کد تعریف callbacks و آموزش مدل:

from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoin

# تعریف callbacks
early_stopping = EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True)
model_checkpoint = ModelCheckpoint('/content/asr_best_model.keras', monitor='val_loss', save_best_only=True)

# آموزش مدل
history = model.fit(train_gen, steps_per_epoch=len(X_train) // 16, epochs=30, validation_data=val_gen, validation_steps=len(X_val) // 16, callbacks=[early_stopping, model_checkpoint])

مرحله 6: پیش‌بینی بر روی نمونه جدید

در این مرحله، تابعی برای پیش‌بینی متن بر اساس نمونه صوتی تعریف شده و مدل بر روی یک نمونه جدید آزمایش می‌شود.

تعریف مفاهیم مورد استفاده:

  • ctc_decode: این تابع برای رمزگشایی پیش‌بینی‌های مدل با استفاده از الگوریتم CTC به کار می‌رود.
  • get_value: این تابع برای گرفتن مقدار نهایی از یک تانسور استفاده می‌شود.

کد پیش‌بینی بر روی نمونه جدید:

# تابع پیش‌بینی بر روی نمونه جدید
def predict_sample(sample_index):
sample_features = X[sample_index]
sample_input_length = np.array([sample_features.shape[0]])

sample_features = np.expandانسژexpand_dims(sample_features, axis=0)
sample_input_length = np.array([sample_features.shape[1]], dtype=np.int32)

# پیش‌بینی با استفاده از beam search decoding
preds = inference_model.predict(sample_features)
decoded_pred = tf.keras.backend.ctc_decode(preds, input_length=sample_input_length, greedy=False, beam_width=20, top_paths=1)[0][0]
decoded_pred = tf.keras.backend.get_value(decoded_pred)

# اطمینان حاصل کردن از یک‌بعدی بودن decoded_pred
decoded_pred = decoded_pred.flatten()

# تبدیل پیش‌بینی رمزگشایی شده به متن
predicted_text = ''.join([index_map[i] for i in decoded_pred if i != -1])

# متن اصلی از داده‌ها
actual_text = df.iloc[sample_index]['transcript']

print(f"Predicted text: {predicted_text}")
print(f"Actual text: {actual_text}")

# آزمایش مدل بر روی یک نمونه جدید
predict_sample(25)

نتیجه‌گیری

در این پروژه، با استفاده از تکنیک‌های یادگیری عمیق و تابع هزینه CTC، یک سیستم تشخیص گفتار فارسی طراحی و پیاده‌سازی کردیم. مدل توانست گفتار فارسی را به متن تبدیل کند و در مراحل مختلف با موفقیت آموزش داده شد. این سیستم می‌تواند در کاربردهای مختلفی نظیر سرویس‌های دستیار صوتی، ترجمه ماشینی و زیرنویس خودکار مورد استفاده قرار گیرد. با توجه به نتایج به دست آمده، می‌توان این پروژه را بهبود داده و دقت آن را افزایش داد. به عنوان مثال، می‌توان از تکنیک‌های پیشرفته‌تری مانند توجه (Attention) و مدل‌های پیش‌آموزش دیده (Pre-trained Models) استفاده کرد. از تمامی همکاران و اساتید که در انجام این پروژه کمک کردند، سپاسگزاریم و به ویژه از آقای اخوان پور برای راهنمایی و آقای پرپنچی برای اشتراک‌گذاری داده‌های مورد استفاده در این پروژه قدردانی می‌کنیم. عوامل دست اندر کار : محمد مهدی خرّم آبادی، طاها ذوالقدری. پیوند ها :  GitHub Dataset