Spoken Language Processing

Huggingface 로 wav2vec2.0 실습 - 영어

햇농nongnong 2022. 8. 18. 18:53

Huggingface


Fairseq

  • facebook 에서 처음에는 shell 을 통해 바로 훈련시키는 fairseq 라는 AI 툴킷을 공개함 
  • 따라서 이 fairseq 를 통해 pretrained 모델을 가져와 fine-tuning 가능했었음

 

Huggingface

  • 그 이후 huggingface 에서 python 에서 pretrained 모델을 가져와 fine-tuning 할 수 있도록 만들어줌
  • Huggingface 는 자연어처리 스타트업에서 개발한 다양한 트랜스포머 모델(transformer.models) 과 학습 스크립트(transformer.Trainer) 를 제공하는 모듈
  • transformers.models : 트랜스포머 기반의 다양한 모델을 pytorch, tensorflow 로 각각 구현해놓은 모듈
    - 각 모델에 맞는 tokenizer 도 구현되어 있음
  • transformers.Trainer : 딥러닝 학습 / 평가에 필요한 optimizer, 가중치 업데이트 등을 수행하는 모듈

 

 

TIMIT dataset 실습


 

데이터셋 로드하기

 

!pip install datasets==1.13.3
!pip install transformers==4.11.3
!pip install soundfile
!pip install jiwer
!pip install torchaudio
!pip install librosa

 

  • jiwer : 단어 오류율 구하기
  • torchaudio & librosa : 음악 파일 로딩, 처리

 

from datasets import load_dataset, load_metric
timit = load_dataset("timit_asr")

 

  • timit 데이터셋의 경우 아주 고맙게도 load_dataset 을 통해 쉽게 로딩 가능
  • 하지만 개인 데이터셋이나 처음 나오는 데이터셋의 경우 pre-processing 과정이 중요하고 까다로움
    ex) 한국어라면 영어, 숫자, 특수문자 다 지워주어야 함
          영어 약자로 된 것, 숫자 읽는법 다 처리해주어야 함
  • pre-processing 은 스스로 만들어야 하는데, list, dict, pandas 등을 이용해서 가능 + editor 이용해 shell 에서도 가능

 

개인적으로 데이터셋을 만든다면 어떤 식으로 만들어야 할까?

 

  • 보편적인 데이터셋 살펴보기

 

  • Train / Test split
    - 아예 처음부터 폴더를 다르게 해서 train, test 로 나눌 수도 있고, 한번에 데이터 로딩한 후 나중에 경로 지정을 다르게 해서 나눌 수도 있음

 

 

  • train과 test 안에 dataset features 들 존재
  • 음성인식에서 중요한 것은 오디오 경로와 그 오디오가 나타내는 target transcription 이 중요함
  • 현재 TIMIT 데이터셋의 경우에는 특이하게 오디오 파일을 통째로 넣어서 벡터가 그대로 들어가 있음
    - 한마디로 path 뿐만 아니라, 그 경로를 librosa 로 로딩해서 오디오로 나오는 긴 숫자 덩어리들이 함께 로드되어있음
    - TIMIT 은 큰 데이터셋이 아니라 작은 데이터셋이기 때문에 위와 같이 오디오 긴 숫자 덩어리 전체를 함께 로드할 수 있었지만, 1000시간 이상의 대규모 데이터셋을 오디오로 다 바꾼 후 처리하면 용량이 커져서 실제로는 너무 느려짐
    - 그래서 많이 쓰는 방법은 numpy 를 이용해 미리 뽑아놓은 후 뽑아놓은 것을 로드하는 방법으로 많이 이용함

 

timit = timit.remove_columns(["phonetic_detail", "word_detail", "dialect_region", "id", "sentence_type", "speaker_id"])

 

  • 즉 결론은, 오디오 경로와 target transcription 이 필요하기 때문에 file, audio, text 외의 뒤에 있는 다른 feature 들 제거

 

from datasets import ClassLabel
import random
import pandas as pd
from IPython.display import display, HTML

def show_random_elements(dataset, num_examples=10):
    assert num_examples <= len(dataset), "Can't pick more elements than there are in the dataset."
    picks = []
    for _ in range(num_examples):
        pick = random.randint(0, len(dataset)-1)
        while pick in picks:
            pick = random.randint(0, len(dataset)-1)
        picks.append(pick)
    
    df = pd.DataFrame(dataset[picks])
    display(HTML(df.to_html()))

 

  • random elements 를 pandas 를 통해 보여주기
    - 그냥 random number 뽑아서 append 해서 보여주는 형태

 

  • 랜덤하게 뽑힌 audio 와 text 쌍을 확인할 수 있음 - 'path', 'array', 'sampling rate', 'text'
    - sampling rate 도 array 를 위해 필수임.
  • 핵심적으로는 'array' 'text' 가 필요
    - 결국 array 와 text 를 mapping 시켜야 함. 
  • 음성인식이 어려운 이유 중 하나는 다른 분야에 대해 mapping 이 매우 까다로움
    - 숫자들 산더미인 array여러 단어로 구성된 text 를 mapping 해주어야 함
    - 기본적인 캐글, 통계데이터셋의 콤마로 구분되어 있는 csv 형태의 데이터셋의 경우, 
    ex) dimension 4, decision 3 : 4개의 영향을 끼치는 요소, 긍정/부정/중립의 3가지 
          4개의 차원에 있는 숫자와 머신러닝 / 회귀를 통해 어떻게든 mapping 가능
  • 하지만, 오디오의 경우 array 도 숫자로 길고, text 도 문자로 길어서 항상 align 의 문제가 있음
    ex) Birthday parties have cupcakes and ice cream.
    - array [ 0.0, -9.1552734... ] 어디서부터 어디까지가 B 이고, 어디서부터 어디까지가 i 이고, r 이고 하는 것들을 처리하기 어려움 : align 의 문제
    - 여기서는 거대한 mapping function 을 통해 mapping 하게 됨

 

간단한 전처리해주기

 

import re
chars_to_ignore_regex = '[\,\?\.\!\-\;\:\"]'

def remove_special_characters(batch):
    batch["text"] = re.sub(chars_to_ignore_regex, '', batch["text"]).lower() + " "
    return batch

 

  • 정규표현식 활용해서 전처리해주기
  • 먼저 batch 를 넣어주고, batch 에다가 text 를 새로 만들어줌
    - 이 때 chars_to_ignore_regex 에 해당되는 index 를 다 제거해서 새로운 text 를 만들어주는 것임
  • re.sub(chars_to_ignore_regex, '', batch["text"]).lower() + " "
    - batch["text"] 의 데이터셋에서 chars_to_ignore_regex 의 인덱스들을 '' 로 바꿔주라는 명령 ('':빈칸)
    - 그리고 알파벳을 다 소문자로 바꾼 후 빈칸 한 칸을 넣어줌

 

vocab dictionary 만들기

 

def extract_all_chars(batch):
  all_text = " ".join(batch["text"])
  vocab = list(set(all_text))
  return {"vocab": [vocab], "all_text": [all_text]}

 

  • 이제 문제는 text 의 문자들을 다 숫자로 만들어주어야 함.
  • 총 단어를 모아서 독립적인 단어들을 숫자로 mapping 한 사전을 만들기 위해, 단어를 쭉 모아서 중복 제거하기
  • all_text = " ".join(batch["text"]) : batch["text"] 에 있는 모든 dummy text 를 글자 통 all_text 에 다 집어넣기
  • 그 다음 set() 을 이용해서 중복되는 것 제거
  • 이제 vocab 완성됨

 

vocabs = timit.map(extract_all_chars, batched=True, batch_size=-1, keep_in_memory=True, remove_columns=timit.column_names["train"])

 

 

 

 

  • 경우에 따라 test set 의 단어는 넣지 않고 vocab 만들자라는 사람도 있는데, 일단 여기서는 train, test 데이터셋의 vocab 모두 넣음

 

vocab_list = list(set(vocabs["train"]["vocab"][0]) | set(vocabs["test"]["vocab"][0]))
vocab_dict = {v: k for k, v in enumerate(vocab_list)}

 

  • vocab_dict 를 통해 이제 드디어 문자를 숫자로 바꿀 수가 있어서, array 의 숫자와 text 의 변환된 숫자와의 mapping 이 가능하다.
  • 이러한 vocab_dict 도 만드려는 유형에 따라 다양하다.
    - 한국어의 경우에도 형태소 등등 개수 조정 가능
    - 음절 단위 / 단어 단위 / 대어휘음성인식시스템 - 20~30만개 등등 dict 안의 개수 조정가능
    - 이 예시에서는 30개 정도로 간단하게 만든 vocab_dict

 

vocab_dict["[UNK]"] = len(vocab_dict)
vocab_dict["[PAD]"] = len(vocab_dict)
len(vocab_dict) # 30

 

  • 현재까지 vocab_dict 는 총 28개였음
  • unknwon 토큰을 len(vocab_dict), 즉 dict 의 28의 값으로 넣어주고, padding 토큰을 그 이후 len(vocab_dict) 값인 29 의 값으로 넣어준다. 
  • 그래서 최종으로는  [UNK], [PAD] 를 추가한 0~29 의 30개의 vocab_dict 완성

 

import json
with open('vocab.json', 'w') as vocab_file:
    json.dump(vocab_dict, vocab_file)

 

  • 이제 vocab.json 이라는 이름의 파일을 작성
    - 만들어 놓은 vocab_dict 를 위 파일인 vocab_file (=vocab.json) 에 넣어준다.

 

from transformers import Wav2Vec2CTCTokenizer

tokenizer = Wav2Vec2CTCTokenizer("./vocab.json", unk_token="[UNK]", pad_token="[PAD]", word_delimiter_token="|")

from transformers import Wav2Vec2FeatureExtractor

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

 

 

  • wav2vecCTCTokenizer = 언어학 파트

 

 

 

 

 

 Reference