Ai ถอดเสียงร้องภาษาไทย(speech-to-text) ฉบับมือใหม่🎙

Puen Kittipong Tapyou
5 min readJun 21, 2022
https://www.cleanpng.com

สวัสดีครับ ผมปืน🔫 จาก Ai Builder รุ่นที่2ครับผม

วันนี้ผมจะมาแบ่งปัน ความรู้ประสบการณ์ของผมจากการทำโปรเจคในค่าย Ai Builder โดยผมได้เลือกที่จะทำใน task เกี่ยวกับ ASR : Automatic Speech Recognition

เราจะทำอะไรกันหรอ

ในโปรเจคนี้ผมได้เลือกที่จะนำเทคโนโลยี ASR มาดัดแปลงในการถอดเสียงร้องในเพลงโดยเป็นเพลงภาษาไทย 🇹🇭หรือเข้าใจในฉบับย่อว่า ใส่เพลงเข้าไปแล้วออกมาเป็นเนื้อร้องนั่นเอง เนื่องจากผมเป็นคนที่ชอบการฟังเพลงมาก 🎼//ตอนเขียนบทความนี้ก็ยังฟังอยู่นะ แต่ในบางครั้ง เราก็ฟังไม่ออกว่าเขาร้องว่าอะไร ถึงแม้จะเป็นภาษาไทย ผมจึงได้เลือกและทำโปรเจคนี้นั่นเอง😁

ปรับพื้นฐานความเข้าใจเทคโนโลยีกันหน่อยนะ

มาเริ่มทำความเข้าใจเกี่ยวกับ ASRในฉบับย่อกันเลยครับ ASR เป็นเทคโนโลยีที่ใช้ในการเปลี่ยนข้อมูลเสียง(audio)🎤ในงานนี้เสียงที่เราหมายถึงก็คือคำพูด(speech)นะครับ ให้เป็นข้อมูลตัวอักษรหรือข้อความ(text) หรืออาจจะมีบางคำที่ถูกพูดถึงมากก็คือคำว่า speech to text นั่นเองครับ

สิ่งที่เราต้องเตรียมก่อนลงมือทำ

ในการทำASRนั้น เราจำเป็นต้องเตรียมข้อมูล (Data) 📑

โดยเราได้ทำการเลือกเพลงมาจำนวน50เพลง หรือกว่า150นาที🕐 ซึ่งเป็นเพลงที่อยู่เพลลิสต์ของ youtube ที่มียอดคนฟังเป็นจำนวนมาก🎩

พอเราได้ชื่อเพลงมาแล้วผมก็ทำการโหลดไฟล์ mp3 มา พร้อมกับ หาเนื้อเพลงใน internet🌐 เพื่อจะใช้เป็น label ในการเทรนโมเดล

หลังจากการโหลดไฟล์เสียงและเนื้อเพลงเรียบร้อยผมก็พบว่า เราต้องจัดการกับข้อมูลเหล่านี้ยังไง เพื่อให้ได้ข้อมูลที่ดีและมีคุณภาพ หรือข้อมูลสะอาดนั่นเอง🧼

ปัญหาของเสียงดนตรี

ปัญหาแรก เป็นปัญหาของที่เกิดจากเสียงดนตรี คือในเพลงจะมีการบรรเลงดนตรีเพื่อให้เกิดทำนอง สิ่งนั้นแหละที่อาจจะทำให้โมเดลของเราเกิดความสับสน😵‍💫 เช่น บางครั้งดนตรีที่บรรเลงโมเดลอาจจะถูกแปลงหรือถูกเข้าใจว่าเป็นคำพูด ซึ่งแน่นอนว่า สิ่งนี้จะทำให้โมเดลของเรามีข้อผิดพลาดเยอะขึ้นนั่นเอง

วิธีเเก้ของปัญหานี้ก็คือการแยกเสียงดนตรีออกจากเสียงเพลง โดยเราได้เลือกที่จะใช้ tool🛠 ที่มีชื่อว่า Spleeter มาเริ่มกันเลย

📥ติดตั้งเครื่องมือกัน

!apt install ffmpeg
!pip install spleeter

เมื่อติดตั้งแล้วก็แยกเสียงกันเลย👩‍🎤

!spleeter separate -p spleeter:2stems -o output audio_example.mp3

สิ่งที่จะได้จากการรันโค๊ดนี้คือ ไฟล์ที่มีชื่อว่า accompaniment.wav คือไฟล์เสียงดนตรี และ vocal.wav คือเสียงร้อง แน่นอนว่า เราจะไม่สนใจไฟล์เสียงดนตรี เอาแต่ไฟล์เสียงร้องนั่นเองครับ

ปัญหาเกี่ยวกับความยาวของเพลง

ปัญหาที่สอง คือเพลงทั่วไปในปัจจุบันมีความยาวเฉลี่ยที่ 2.5 ถึง 4นาที 🕐ซึ่งมันถือว่าใหญ่มาก เมือเทียบกับเสียงที่ใช้ในการเทรนอ้างอิงจาก https://medium.com/airesearch-in-th/airesearch-in-th-3c1019a99cd ในรูป(fig A) จะเห็นว่าความยาวเสียงเฉลี่ยที่ 2–6 วินาที

fig A

เมื่อเราพบว่าข้อมูลของเรามันยาวเกินไป เราก็ต้องแบ่งสิครับ โดยผมได้เลือกตัดเพลง เป็นท่อนๆ เฉลี่ย 2–6 วินาทีเช่นเดียวกันนั่นเอง

หลายคนอาจคิดว่า ก็หา dataset ที่มี timestamp📌 สิ

แน่นอนว่าผมก็คิดเหมือนทุกคนครับ 🤞โดยผมได้ลองสำรวจเว็บไซต์ต่างๆที่รวบรวม timestamp ของเพลง เช่น musicenc megalobiz เมื่อผมลองใช้ bs4ในการ scarpe ข้อมูลเหล่านั้น และนำไฟล์เสียงที่ผ่านการแยกเสียงร้องมาตัดให้ตรงเวลา กลับพบว่า ในหนึ่งท่อนของเพลง จะมีข้อความที่หาย หรือเกินมาอย่างน้อย1–2คำเสมอ เช่น น้องปืนชอบกินขนม ไฟล์เสียงที่ถูกตัดออกมาก็จะเป็น น้องปืนชอบกิน

ขณะที่คำว่าขนมมันหายไป🙀ทำให้เมื่อโมเดลได้รับข้อมูลนี้ เขาก็จะเข้าใจว่าเสียงที่ได้ยินนั้นมีคำว่าขนมซึ่งมันไม่มีนั่นก็หมายความว่า ข้อมูลที่เราได้ จะมี label ที่ผิดนั่นเอง ส่งผลให้มีความผิดพลาดสูง

แล้วถ้าการ scrape timestamp มามันไม่ตรงแล้วคุณทำยังไง

ทุกคนรู้จักเทคโนโลยีที่เรียกว่า Handcraft ไหมครับ ใช่ครับ🥺ในเมื่อสิ่งที่มีไม่สามารถตอบสนองความต้องการของเราได้ เราก็เลยต้องลงมือตัดเพลงด้วยตัวเองยังไงล่ะครับ โดยผมได้ใช้เครื่องมือที่ชื่อ Audacity เป็นเครื่องมือที่ใช้ในการจัดการไฟล์เสียง

แล้วในเมื่อข้อมูลมันเยอะผมทำยังไงให้เสร็จหรอ คำตอบของคำถามคือน้องนนนี่น้องอยู่ Ai Builder รุ่นที่ 2 ครับ น้องทำงานที่ใช้ dataset เหมือนกับผม เราเลยตกลงกันว่าเราแบ่งกันทำคนละครึ่งแล้วกันนะ

หลังจากการเสียเหงื่อให้กับไฟล์เสียง

เมื่อclean dataในส่วนของไฟล์เสียงเสร็จแล้ว ก็มาผจญภัยในโหมดของข้อความ(label)กันต่อครับ

เนื้อเพลงที่ถูกแบ่งมาเป็นท่อน ผมพบว่า มีเครื่องหมายพิเศษ🤩 เข้ามาด้วย เช่น . , โดยเครื่องหมายเหล่านี้ก็เกิดจาการเชื่อมต่อประโยคต่างๆในเนื้อเพลง เราก็แค่ลบออกไป โล่งใจว่าลบแค่สองเครื่องหมาย แต่ช้าแต่ มันมีอักษรภาษาไทยที่มีอยู่ในประโยค สิ่งนั้นก็คือ เป็นการเพิ่มคำซ้ำในประโยคเช่น สิ่งต่างๆ เป็น สิ่งต่างต่าง และสิ่งที่ เรามักเห็นคนพิมพ์ผิดเยอะมาก (ผมเองก็ใช้ผิด) คือ สระ มักถูกเขียนในรูปของสระ เ เ เเละสระ เป็น ํ า

แบ่ง train valid test

ผมได้แบ่งข้อมูล เป็น Train set, Valid set, Test set ในอัตราส่วน 50 : 25 : 25 ตามลำดับ และทำการสลับข้อมูลในแต่ละชุด(shuffle)📚

สำรวจข้อมูลของเรากันเถอะ

หลังจากผ่านขั้นตอนการเก็บและทำความสะอาดข้อมูลแล้ว ก็จะได้โครงสร้างข้อมูลที่เราใช้จะอยู่ในรูปของ datafarame ดังนี้

ซึ่งมีชื่อ colums คือ path คือชื่อของไฟล์นั้น และ sentence คือประโยคหรือท่อนเพลงที่ตรงกับเสียงในแถวเดียวกัน✉️

เมื่อทำการสำรวจข้อมูลพบว่า มีข้อมูลทั้งหมด 1,307 + 436 + 436 = 2,179 rows นั่นเอง

จำนวนคำต่อประโยค
ความยาวไฟล์เสียง(วินาที)ต่อประโยค

ในกราฟจะเป็นการแสดงจำนวนคำในแต่ละประโยค ซึ่ง เฉลี่ยคือ 4 — 10 คำในประโยค และจำนวนวินาทีเฉลี่ยต่อประโยคคือ 2–6 วินาทีหรือทั้งหมดประมาณ 2.42ชั่วโมง

ระหว่างทำผมก็สงสัยว่า ข้อความที่ทำมาทั้งหมด มีคำอะไรที่เยอะๆ บ้าง ผลลัพธ์ที่ได้ก็คือ[(‘เธอ’, 359), (‘ฉัน’, 333), (‘ไม่’, 315), (‘จะ’, 234), (‘ที่’, 191), (‘มี’, 154), (‘ได้’, 144), (‘ให้’, 134), (‘ก็’, 130), (‘ว่า’, 129)] //ที่เห็นคือคำที่มีจำนวนเยอะที่สุดพร้อมกับจำนวนที่พบนั่นเอง

ทุกคนอาจจะมองยาก ผมจึงทำให้มันสามารถเข้าใจได้ง่ายๆโดยการทำ word cloud☁️ ครับผม

พร้อมเทรนแล้วค๊าบ

ในโปรเจคนี้ผมได้ตัดสินใจที่จะใช้ airesearch/wav2vec2-large-xlsr-53-th เป็นmodel ที่ถูกfinetune มาจาก facebook/wav2vec2-large-xlsr-53 ที่ถูกเทรนบนข้อมูล Common Voice 7.0 ภาษาไทย มาเป็น Pre-trained Modelในงานของเรา

มาทำความรู้จักwav2vec2

wav2vec2 เป็น pretrained model สำหรับการรู้จำเสียง ซึ่งผ่านการเทรนกับข้อมูลเสียงที่ไม่มีlabel(unlabel speech) ซึ่งให้ความผิดพลาดคำ (WER : word error rate) ไม่ถึง5%😲 ใน dataset ของ LibriSpeech

ถึงเวลาออกรบแล้วGPU //เริ่มเทรน💻

📥ติดตั้ง Library

!pip install torch==1.9.0
!pip install datasets==1.11.0
!pip install transformers==4.9.1
!pip install jiwer

จากนั้นทำการimport lib

import pandas as pd
import numpy as np
from datasets import (
load_dataset,
load_from_disk,
load_metric,)
from transformers import (
Wav2Vec2CTCTokenizer,
Wav2Vec2FeatureExtractor,
Wav2Vec2Processor,
Wav2Vec2ForCTC,
TrainingArguments,
Trainer,)
import torchaudio
from pythainlp.tokenize import word_tokenize, syllable_tokenize

PREPROCESS DATA เป็นขั้นตอนของการตัดคำนั่นเอง✂️

ทุกคนครับ ผมมีเรื่องจะบอกครับ ภาษาไทย🇹🇭ไม่เหมือนภาษาอังกฤษครับ นั่นคือภาษาอังกฤษมีช่องว่างระหว่างคำ เช่น I run in the park every morning แต่ในภาษาไทยเราจะเขียนว่า ฉันวิ่งในสวนสาธารณะทุกเช้า จะเห็นว่าไม่มีช่องว่างระหว่างคำเลย

สิ่งที่เราต้องทำ เราเรียกว่าการตัดคำนั่นเอง(word tokenize) ในบทความนี้เราได้เลือกใช้ library ที่ชื่อว่า pythainlp เป็นlibสำหรับการจัดการกับภาษาไทย

📥ติดตั้ง library

!pip install pythainlp

✂️พร้อมลงมือตัดคำแล้ว

from pythainlp import word_tokenizeoutput = word_tokenize("ฉันวิ่งในสวนสาธารณะทุกเช้า")
print(output)

สิ่งที่เราจะได้หลังจากการตัดคำก็คือlistของคำในประโยคนั้น [‘ฉัน’, ‘วิ่ง’, ‘ใน’, ‘สวนสาธารณะ’, ‘ทุก’, ‘เช้า’]

def preprocess_data(example, tok_func = word_tokenize):
example[‘sentence’] = ‘ ‘.join(tok_func(example[‘sentence’]))
return exampledatasets = datasets.map(preprocess_data)

เมื่อได้ dataset ที่ผ่านการตัดคำแล้ว เราก็ต้องทำการ encode หรือจะพูดให้เข้าใจว่าเปลี่ยนคำให้อยู่ในรูปของตัวเลข🔢นั่นเอง อักษรรรรรรร

tokenizer = Wav2Vec2CTCTokenizer.from_pretrained(
"airesearch/wav2vec2-large-xlsr-53-th")

สกัดคุณสมบัติ(feature extraction)

นำข้อมูลที่เป็นคำ โดยผ่านการ encode มาสกัดคุณสมบัติ หรือมันคือการเปลี่ยเสียงให้อยู่ในรูปของตัวเลข🛠

feature_extractor = Wav2Vec2FeatureExtractor(feature_size=1,
sampling_rate=16000,
padding_value=0.0,
do_normalize=True,
return_attention_mask=False)
processor = Wav2Vec2Processor(feature_extractor=feature_extractor,
tokenizer=tokenizer)

Load metric

ในการวัดผลของโมเดล เราจะเลือกใช้ evaluation metric ที่มีชื่อว่า WER : word error rate

WER

S คำที่ถูกแทนที่
D คำที่ถูกลบ
I คำที่แทรกเข้ามา
C คำที่ถูกต้อง
N จำนวนทั้งหมด(N=S+D+C)

การใช้งานก็คือ

wer_metric = load_metric(“wer”)
pred = ['สวัสดี ค่า ทุก โคน']
ref = ['สวัสดี ค่ะ ทุก คน'])
wer_metric.compute(predictions=pred,references=ref)

สิ่งที่เราจะได้จากwer.compute()คือ ตัวเลขของ wer ซึ่งจากในตัวอย่าง wer คือ 0.50

หลังจากที่เราเตรียมdataและ metricเรียบร้อยแล้ว ก็ถึงส่วนของการ เตรียมโมเดลที่เราจะเทรนกันแล้ว

ตั้งค่า model

ขั้นแรกคือการตั้งค่าโมเดล ซึ่งในกรณีของเรานี้ จะ finetune บน airesearch/wav2vec2-large-xlsr-53-th

model = Wav2Vec2ForCTC.from_pretrained(
“airesearch/wav2vec2-large-xlsr-53-th”,
attention_dropout=0.1,
hidden_dropout=0.1,
feat_proj_dropout=0.0,
mask_time_prob=0.05,
layerdrop=0.1,
gradient_checkpointing=True,
ctc_loss_reduction=”mean”,
pad_token_id=processor.tokenizer.pad_token_id,)

ใช้เวลาโหลดสักครู่ Wait……..

จากนั้นก็ทำการตั้งค่า Arguments ที่จะใช้ในการเทรนด์ครับผม ซึ่งสามารถตั้งค่า parameter ต่างๆเช่น output_dir คือpathของโมเดลเมื่อเทรนเสร็จ , batch_size ยิ่งเยอะยิ่งเทรนเร็ว ,metricที่จะใช้ในการ evaluate เป็นต้น

training_args = TrainingArguments(
output_dir="/wav2vec2-large-xlsr-53-thai-finetune",
group_by_length=True,
per_device_train_batch_size=16,
gradient_accumulation_steps=4,
per_device_eval_batch_size=16,
metric_for_best_model='wer',
evaluation_strategy="steps",
eval_steps=50,
logging_strategy="steps",
logging_steps=50,
save_strategy="steps",
save_steps=500,
num_train_epochs=100,
learning_rate=1e-4,
warmup_steps=50,
save_total_limit=3,)

หลังจากที่ทุกอย่างพร้อมแล้ว ก็ทำการนำทุกอย่างเข้าไปในTrainer เพื่อทำการเทรน โดยประกอบด้วย dataset, model, metric, tokenizerและ argument

trainer = Trainer(model=model,
data_collator=data_collator,
args=training_args,
compute_metrics=partial(compute_metrics,
metric=wer_metric,
processor=processor),
train_dataset=prepared_datasets[“train”],
eval_dataset=prepared_datasets[“validation”],
tokenizer=processor.feature_extractor,)

หลักจากโมเดลและเทรนเนอร์พร้อมแล้ว ก็เอาเทรนเนอร์ไปเทรนโมเดลกันเถอะ เพียงแค่พิมพ์คำว่า

trainer.train()

ลุ้นมากกกกกกกกกกกกกกกกกกก🫣

เมื่อผ่านการเทรน มา 4 ชั่วโมงครึ่ง จำนวน 2000 step เราก็พบว่า wer คือ 0.42 ซึ่งก็ถือว่ายังมีความผิดพลาดเยอะเมื่อใช้งานจริง😕

error analysis

หลังจากได้ผลการทดลองมาแล้ว ผมก็อยากรู้ว่าประโยคที่มี wer เยอะ มันคืออะไร และเพราะอะไร🤔

ประโยคที่ผมจะยกตัวอย่างก็คือ

ตารางแสดงผลลัพธ์ที่ความผิดพลาดสูง

จากตัวอย่างประโยค จะ นานแสนนาน เท่าไร จะเห็นได้ว่า ผลลัพธ์คือ จะ นาน แสน เดิน เท่า เนื่องจากเมื่อคำว่า นาน ถูกทายเป็นคำว่า เดิน ทำให้เมื่อเกิดการตัดคำ คำว่า นานแสนนาน ซึ่งเป็น1คำ แต่เมื่อผลลัพธ์ออกมา จะกลายเป็น3คำเพราะการตัดคำ ไม่มีคำว่า นานแสน หรือ นานแสนเดิน จึงถูกแบ่งคำเป็น นาน แสน เเละ เดิน ทำให้เกิดWERที่เพิ่มขึ้น🧐

จากการที่มีคำที่ระบุถูกต้อง แต่ว่าไม่มีคำนั้นในพจนานุกรม📚ของการตัดคำเช่น นานแสน ทำให้ผมจึงเลือกใช้ CER : Character Error Rate มาเป็นมาตรวัดผล ซึ่งพบว่า มีผลลัพธ์ที่น่าภูมิใจอย่างมาก คือ 0.33

นอกจาประเด็นของการตัดคำแล้วยังมี ปัจจัยอย่างอื่น เช่นประโยค เพียงแค่ เธอ สบตา ผมได้ลองไปเปิดไฟล์เสียง แล้วพบว่า เพลงนี้ถูกเผยแพร่ในปี 2014😵 ซึ่งจะสังเกตจากในเพลงพบว่า เทคโนโลยีการอัดเพลงสมัยนั้น ยังไม่ได้มีคุณภาพสูงทำให้ เมื่อเรานำไฟล์เสียงไปทำการแยกเสียงร้องกับเสียงดนตรี แล้วพบว่า มีเสียงดนตรีโดดออกมาอย่างชัดเจนคือเสียงของกลอง และคำร้องบางคำมีเสียงที่เบา ทำให้โมเดลไม่สามารถเข้าใจคำร้องนั้นได้ หรือได้ยินเป็นอย่างอื่น ผมจึงตั้งสมมติฐานว่า อาจจะเกิดจากคุณภาพของไฟล์เสียงนั่นเอง

แล้วจะรู้ได้อย่างไรว่าโมเดลของเราประสบความสำเร็จหรือไม่😬

สิ่งที่จะทำในขั้นตอนนี้คือการนำโมเดลของเราไปเปรียบเทียบกับโมเดลอื่น (baseline) โดยผมเองได้เลือก airesearch/wav2vec2-large-xlsr-53-th ที่เป็น pre-train ของเรา

จะเห็นได้ว่า เมื่อเรา finetune ทับโมเดลที่ถูกเทรนในภาษาที่เราต้องการในตัวอย่างนี้คือภาษาไทย จะพบว่า เมื่อเราเปลี่ยนจาก domain ของคำพูด mono tone มาเป็น domain ของเสียงร้อง ทำให้มีประสิทธิภาพในการใช้ระบุเสียงร้องได้ดีกว่าโมเดลเสียงพูด mono tone นั่นเอง🥰 สังเกตได้จาก WER และ CER ของโมเดลของเราน้อยกว่า airesearch/wav2vec2-large-xlsr-53-th 0.60 และ 0.36 ตามลำดับ *ยิ่งน้อยยิ่งดี

โมเดลสำเร็จแล้ว ก็ถึงเวลา Deployให้ได้ลองใช้

ในบทความนี้เราจะdeploy บน streamlit ซึ่งมี elements ให้ใช้มากมาย จากการที่ผมได้ลองตกแต่ง interfaceของผมทั้งวัน ก็ได้ออกมาเป็นแบบนี้

ซึ่งเราสามารถใส่URL ของเพลงในyoutube จากนั้น ระบบก็จะทำการเรียกโมเดลมาเพื่อทำการระบุเสียงร้องในเพลงนั่นเอง

และผมก็ได้ทำกล่องผลลัพธ์เพิ่มขึ้นมาก็คือ output จากโมเดล airesearch/wav2vec2-large-xlsr-53-th เพื่อให้ง่ายต่อการเปรียบเทียบผลลัพธ์

สามารถลองไปเล่นได้ที่🔗

จบไปแล้วสำหรับเนื้อหาเกี่ยวกับการทำ Ai ถอดเสียงร้องภาษาไทย🎉

หลังจากที่ผมได้เข้าค่าย Ai Builder ผมได้รับประสบการณ์และความรู้ต่างๆมากมายๆ ได้เรียนรู้ตั้งแต่ต้นทางของการทำAi จนถึงปลายทางการนำไปประยุกต์ใช้ ได้รู้จักเพื่อนที่มีความสนใจในด้านเดียวกัน ขอบคุณครับ🙏

--

--