본문 바로가기
Kospeech

[Python/AI/ASR] Kospeech - Intro ~ Preprocess

by hwanikim 2024. 10. 14.

Kospeech Github 주소: https://github.com/sooftware/kospeech

 

GitHub - sooftware/kospeech: Open-Source Toolkit for End-to-End Korean Automatic Speech Recognition leveraging PyTorch and Hydra

Open-Source Toolkit for End-to-End Korean Automatic Speech Recognition leveraging PyTorch and Hydra. - sooftware/kospeech

github.com

 


Data-set 주소 (AIhub): https://www.aihub.or.kr/aihubdata/data/view.do?currMenu=115&topMenu=100&aihubDataSe=data&dataSetSn=123

 

AI-Hub

분야한국어 유형 오디오 , 텍스트 구축년도 : 2018 갱신년월 : 2019-05 조회수 : 65,690 다운로드 : 14,692 용량 : 다운로드 관심데이터 등록 관심 141

www.aihub.or.kr

 

 

컴퓨터 스펙

OS Ubuntu
GPU RTX 2070 SUPER
CPU Intel Core i7-8700K(3.70GHz)
RAM 16GB
CUDA 12.1
cuDNN 8.9.0

 

 

음성 데이터 

KsponSpeech

┣ KsponSpeech

┣ KsponSpeech_eval

┗ KsponSpeech_scripts

 

KsponSpeech (한국어 음성 분야)

┣ KsponSpeech_01 - KsponSpeech0001 ~ KsponSpeech0124 - KsponSpeech_000001.pcm KsponSpeech_000001.txt 

┣ KsponSpeech_02

┣ KsponSpeech_03

┣ KsponSpeech_04

 KsponSpeech_05

 

이런식으로 데이터가 저장되어 있다.

 

작업 폴더 

작업 폴더는 다음과 같이 만들었다.

kospeech_env

 KsponSpeech

 KsponSpeech_eval

 KsponSpeech_scripts

 kospeech

Kospeech 학습 준비

아나콘다 3 가상환경 만들기

python 3.8에서 작업을 할 예정이고, 가상환경 이름은 kospeech로 정했다.

conda create -n kospeech python=3.8
conda activate kospeech

Kospeech Github 폴더 다운

~/kospeech_env 폴더 안에서 kospeech github를 다운받는다.

git clone https://github.com/sooftware/kospeech.git

 

KsponSpeech Data Preprocess

Kospeech에서 음성을 학습하기 위해서 적절하게 음성의 형태를 바꿔야한다. KsponSpeech Data는 다양한 길이의 음성 파일과 텍스트 전사로 이루어져있는데 이 데이터를 일관된 형태로 정리하여 입력 형태를 맞춘다. 또한, Kospeech는 음성 데이터를 텍스트로 변환하는 음성 인식 모델을 학습하는데, 전처리 과정에서 출력 단위를 문자(chararter), 서브워드(subword), 음소(grapheme)등으로 설정할 수 있다. 텍스트 전사본에 대해서도 발음 기반(phonetic) 전사와 맞춤법 기반(spelling) 전사 모드를 선택하여 데이터에 맞는 전처리 방식을 적용할 수 있다. 추가적인 사항은 아래의 링크를 통해서 참고하면 된다.

https://github.com/sooftware/kospeech/tree/latest/dataset/kspon

 

kospeech/dataset/kspon at latest · sooftware/kospeech

Open-Source Toolkit for End-to-End Korean Automatic Speech Recognition leveraging PyTorch and Hydra. - sooftware/kospeech

github.com

 

 

Python Library Install

전처리 코드를 실행하기 전에 필요한 라이브러리를 다운받아야 한다. ~/kospeech_env/kospeech/dataset/kspon의 폴더 내부에 requirements.txt라는 파일이 있는데 이 파일을 통해 전처리에 필요한 라이브러리를 자동적으로 다운받을 수 있다.

pip install -r requirements.txt

- tqdm: 반복 작업을 수행할 때 작업의 진행 상태를 시각적으로 보여주는 라이브러리
- gluonnlp: 어휘 사전 구축, 데이터셋 전처리, 임베딩 및 텍스트 변환과 같은 기능을 제공해주는 라이브러리
- sentencepiece: 텍스트 데이터를 서브워드 단위로 분할하는 토크나이저
- hgtk: 한글을 분해하고 조립하는 등의 한글 관련 작업을 지원하는 라이브러리
- pandas: 테이블 형식의 데이터를 다루기 위한 라이브러리

 

Launch preprocess

전처리 코드를 실행하기 위해서 ~/kospeech_env/kospeech/dataset/ksponmain.py 코드를 실행해야한다.

전처리 하는 코드를 실행하기 위해서 ~/kospeech_env/kospeech/dataset 경로에서 다음과 같이 명령어를 실행하면 된다.

python main.py --dataset_path "/home/user/kospeech_env/KsponSpeech/" --vocab_dest "/home/user/kospeech_env" --output_unit "(character/subword/grapheme)" --savepath "/home/user/kospeech_env" --preprocess_mode "(phonetic/spelling)" --vocab_size 5000
python main.py --dataset_path "/home/user/kospeech_env/KsponSpeech/" --vocab_dest "/home/user/kospeech_env" --output_unit "character" --savepath "/home/user/kospeech_env" --preprocess_mode "phonetic" --vocab_size 5000
--dataset_path 원본 데이터셋이 저장된 경로 설정 (ex. /home/user/kospeech_env/KpsponSpeech/)
--vocab_dest 생성된 character/subword/grapheme 레이블 파일을 저장할 경로 (ex. /home/user/kospeech_env)
--output_unit 전처리할 때 사용할 출력 단위 (ex. character / subword / grapheme 중 택1)
--savepath 전처리된 데이터를 저장할 경로 (ex. /home/user/kospeech_env)
--preprocess_mode 전처리 모드 (ex. phonetic / spelling 중 택1)
--vocab_size 서브워드 방식에서 사용하는 어휘 크기 (default = 5000)

Output

aihub_labels.csv (파일 경로: /home/user/kospeech_env/aihub_labels.csv)

- 문자와 그에 대응하는 ID가 저장된 문자 레이블 파일

transcripts.txt (파일 경로: /home/user/kospeech_env/transcripts.txt)

- 각 오디오 파일의 경로와 그에 해당하는 전사본이 문자 ID로 변환된 상태로 저장된 파일

 

코드 설명

~kospeech/dataset/kspon/main.py

import argparse
from preprocess.grapheme import sentence_to_grapheme
from preprocess.preprocess import preprocess
from preprocess.character import generate_character_labels, generate_character_script
from preprocess.subword import train_sentencepiece, sentence_to_subwords


def _get_parser():
    parser = argparse.ArgumentParser(description='KsponSpeech Preprocess')
    parser.add_argument('--dataset_path', type=str, default='E:/KsponSpeech/original')
    parser.add_argument('--vocab_dest', type=str, default='E:/KsponSpeech')
    parser.add_argument('--output_unit', type=str, default='character')
    parser.add_argument('--savepath', type=str, default='./data')
    parser.add_argument('--preprocess_mode', type=str, default='phonetic')
    parser.add_argument('--vocab_size', type=int, default=5000)
    return parser


def log_info(opt):
    print("Dataset Path : %s" % opt.dataset_path)
    print("Vocab Destination : %s" % opt.vocab_dest)
    print("Save Path : %s" % opt.savepath)
    print("Output-Unit : %s" % opt.output_unit)
    print("Preprocess Mode : %s" % opt.preprocess_mode)


def main():
    parser = _get_parser()
    opt = parser.parse_args()
    log_info(opt)

    audio_paths, transcripts = preprocess(opt.dataset_path, opt.preprocess_mode)

    if opt.output_unit == 'character':
        generate_character_labels(transcripts, opt.vocab_dest)
        generate_character_script(audio_paths, transcripts, opt.vocab_dest)

    elif opt.output_unit == 'subword':
        train_sentencepiece(transcripts, opt.savepath, opt.vocab_size)
        sentence_to_subwords(audio_paths, transcripts, opt.savepath)

    elif opt.output_unit == 'grapheme':
        sentence_to_grapheme(audio_paths, transcripts, opt.vocab_dest)

    else:
        raise ValueError("Unsupported preprocess method : {0}".format(opt.output_unit))


if __name__ == '__main__':
    main()

_get_parser() 함수

위 코드에서 인자들을 받기 위한 함수 Launch Preprocess 부분 참고

log_info(opt) 함수

opt는 명령줄 인자들이 저장된 객체이다. 코드를 실행했을때, 경로들과 설정들을 출력해주는 코드

main() 함수

parser = _get_parser()

_get_parser() 함수를 호출하여 ArgumentParser 객체를 생성. 이 parser는 명령줄에서 받을 인자들을 정의한 객체.

 

opt = parser.parse_args()

명령줄에서 전달된 인자들을 파싱하여 opt 객체에 저장. opt는 사용자가 입력한 모든 인자 값을 포함

 

log_info(opt)

설명 생략

 

audio_paths, transcripts = preprocess(opt.dataset_path, opt.preprocess_mode)

preprocess()함수를 호출하여 데이터셋을 전처리한다. preprocess 함수는 opt.dataset_path 경로에 있는 데이터셋을 읽어, 오디오 파일 경로(audio_path)와 해당 텍스트 전사본(ranscripts)을 반환. 이때, 전처리 방식은 opt.preprocess_mode(phonetic / spelling)에 따라 다르다.

 

if 문

사용자가 명령줄에서 입력한 output_unit에 따라 데이터 처리 방식을 결정. opt.output_unit에 저장된 값에 따라 세 가지 처리 방식 중(character / subword / grapheme) 하나를 선택한다.

 

Character Mode

아 모 몬 소리야 칠 십 퍼센트 확률이라니

 

Subword Mode

▁아 ▁모 ▁ 몬 ▁소리 야 ▁ 칠 ▁ 십 ▁퍼 센트 ▁확 률 이라 니

 

Grapheme Mode

ㅇㅏ ㅁㅗ ㅁㅗㄴ ㅅㅗㄹㅣㅇㅑ ㅊㅣㄹ ㅅㅣㅂ ㅍㅓㅅㅔㄴㅌㅡ ㅎㅘㄱㄹㅠㄹㅇㅣㄹㅏㄴㅣ

 

~kospeech/dataset/kspon/preprocess/preprocess.py

import os
import re


def bracket_filter(sentence, mode='phonetic'):
    new_sentence = str()

    if mode == 'phonetic':
        flag = False

        for ch in sentence:
            if ch == '(' and flag is False:
                flag = True
                continue
            if ch == '(' and flag is True:
                flag = False
                continue
            if ch != ')' and flag is False:
                new_sentence += ch

    elif mode == 'spelling':
        flag = True

        for ch in sentence:
            if ch == '(':
                continue
            if ch == ')':
                if flag is True:
                    flag = False
                    continue
                else:
                    flag = True
                    continue
            if ch != ')' and flag is True:
                new_sentence += ch

    else:
        raise ValueError("Unsupported mode : {0}".format(mode))

    return new_sentence


def special_filter(sentence, mode='phonetic', replace=None):
    SENTENCE_MARK = ['?', '!', '.']
    NOISE = ['o', 'n', 'u', 'b', 'l']
    EXCEPT = ['/', '+', '*', '-', '@', '$', '^', '&', '[', ']', '=', ':', ';', ',']

    new_sentence = str()
    for idx, ch in enumerate(sentence):
        if ch not in SENTENCE_MARK:
            if idx + 1 < len(sentence) and ch in NOISE and sentence[idx + 1] == '/':
                continue

        if ch == '#':
            new_sentence += '샾'

        elif ch == '%':
            if mode == 'phonetic':
                new_sentence += replace
            elif mode == 'spelling':
                new_sentence += '%'

        elif ch not in EXCEPT:
            new_sentence += ch

    pattern = re.compile(r'\s\s+')
    new_sentence = re.sub(pattern, ' ', new_sentence.strip())
    return new_sentence


def sentence_filter(raw_sentence, mode, replace=None):
    return special_filter(bracket_filter(raw_sentence, mode), mode, replace)


def preprocess(dataset_path, mode='phonetic'):
    print('preprocess started..')

    audio_paths = list()
    transcripts = list()

    percent_files = {
        '087797': '퍼센트',
        '215401': '퍼센트',
        '284574': '퍼센트',
        '397184': '퍼센트',
        '501006': '프로',
        '502173': '프로',
        '542363': '프로',
        '581483': '퍼센트'
    }

    for folder in os.listdir(dataset_path):
        # folder : {KsponSpeech_01, ..., KsponSpeech_05}
        if not folder.startswith('KsponSpeech'):
            continue
        path = os.path.join(dataset_path, folder)
        for idx, subfolder in enumerate(os.listdir(path)):
            path = os.path.join(dataset_path, folder, subfolder)

            for jdx, file in enumerate(os.listdir(path)):
                if file.endswith('.txt'):
                    with open(os.path.join(path, file), "r", encoding='cp949') as f:
                        raw_sentence = f.read()
                        if file[12:18] in percent_files.keys():
                            new_sentence = sentence_filter(raw_sentence, mode, percent_files[file[12:18]])
                        else:
                            new_sentence = sentence_filter(raw_sentence, mode=mode)

                    audio_paths.append(os.path.join(folder, subfolder, file))
                    transcripts.append(new_sentence)

                else:
                    continue

    return audio_paths, transcripts

bracket_filter() ~ sentence_filter()

두 가지 모드(phonetic / Spelling)로 변환하는 과정을 거친다. 아래를 참고하면 된다.

Raw Data

b/ (70%)/(칠 십 퍼센트) 확률이라니 아/ (뭐+ 뭔)/(모+ 몬) 소리야 진짜 (100%)(백 프로)가 왜 안돼? n/

 

special_filter - Delete noise labels, such as b/, n/, / ..

(70%)/(칠 십 퍼센트) 확률이라니 아/ (뭐+ 뭔)/(모+ 몬) 소리야 진짜 (100%)(백 프로)가 왜 안돼?

 

special_filter - Delete labels such as '/', '*', '+', etc. (used for interjection representation) 

(70%)/(칠 십 퍼센트) 확률이라니 아 (뭐 뭔)/(모 몬) 소리야 진짜 (100%)(백 프로)가 왜 안돼?


sentence_filter(bracket_filter) - phonetic Mode

칠 십 퍼센트 확률이라니 아 모 몬 소리야 진짜 백 프로가 왜 안돼?

 

sentence_filter(bracket_filter) - Spelling Mode

70% 확률이라니 아 뭐 뭔 소리야 진짜 100%가 왜 안돼?

 

preprocess(dataset_path, mode='phonetic') 함수

데이터셋 폴더 구조를 순회하면서, .txt 파일(전사본)을 읽어 필터링한 후, 해당 오디오 파일 경로와 함께 리스트로 반환.

전체 코드 흐름

  1. 폴더 구조 탐색: 데이터셋 경로에서 상위 폴더와 하위 폴더를 순차적으로 탐색
  2. .txt 파일 읽기: 각 폴더 내 .txt 파일을 열어 내용 읽기
  3. 텍스트 필터링: 전사본에서 소음이나 특수 기호를 제거하고, % 기호를 파일에 따라 퍼센트 또는 프로로 변환(Phonetic Mode / Spelling Mode)
  4. 결과 저장: 필터링된 전사본을 transcripts 리스트에 저장하고, 해당 오디오 파일 경로를 audio_paths 리스트에 저장
  5. 리턴: 필터링된 전사본과 오디오 경로를 반환