[PYTHON] Comment entraîner Kaldi avec JUST Corpus

Comment utiliser JSUT Corpus avec Kaldi

――Kaldi est un kit d'outils qui vous permet de personnaliser le système de reconnaissance vocale à votre guise. Dans cet article, je vais partager comment utiliser le corpus JSUT (Download) du jeu de données audio japonais pour apprendre Kaldi. .. Le corpus JSUT est un corpus audio de 10 heures créé à des fins de recherche. Veuillez noter que l'utilisation commerciale nécessite un contact avec l'auteur.

Les données textuelles sont sous licence CC-BY-SA 4.0, etc. Consultez le fichier LICENCE pour plus de détails. Les données audio ne peuvent être utilisées que dans les cas suivants. Recherche dans les institutions académiques Recherche non commerciale (y compris la recherche dans des organisations commerciales) Utilisation personnelle (y compris les blogs) Si vous souhaitez l'utiliser à des fins commerciales, veuillez voir ci-dessous. La redistribution de ces données audio n'est pas autorisée, mais il est possible de publier une partie du corpus (par exemple, environ 100 phrases) sur votre page Web ou votre blog. Si possible, il serait utile que vous puissiez me contacter lorsque vous publiez vos articles, articles de blog ou autres réalisations. L'étude de la contribution de ce corpus est une information très utile pour nous.

Apprendre Kaldi

――En général, si vous voulez apprendre Kaldi, Julius, etc. et reconnaître avec précision des voix telles que des conversations quotidiennes, vous avez besoin de milliers d'heures de corpus vocal. Les corpus suivants sont connus comme corpus vocaux pour la recherche. En japonais, le corpus CSJ est le plus grand corpus (probablement) et a un volume de données d'environ 600 heures. Ces corpus sont chargés et ne peuvent pas être utilisés facilement. Alors cette fois, j'aimerais utiliser le corpus JSUT qui est distribué gratuitement et partager l'utilisation de base de Kaldi. --CSJ (corpus de langue japonaise parlée) --JNAS (article de journal lisant un corpus vocal) --S-JNAS (corpus vocal pour les personnes âgées lisant des articles de journaux) --CE JC (corpus de conversations quotidiennes japonais) ―― Il est assez difficile et difficile d'apprendre Kaldi à partir de zéro en utilisant le corpus CSJ. Par conséquent, nous avons préparé une collection de programmes nécessaires pour utiliser le corpus appelé *** Recipe ***. Actuellement, Kaldi n'a que des recettes CSJ pour le corpus CSJ (il y en a beaucoup d'autres en anglais), donc cette fois j'aimerais personnaliser les recettes CSJ afin de pouvoir utiliser le corpus JSUT.

Installez Kaldi

Environnement d'exécution

Maintenance de JUST corpus

―― Tout d'abord, vous devez préparer le corpus JSUT afin qu'il puisse être utilisé dans Kaldi. Si vous pouvez faire cela, le reste sera appris automatiquement avec la puissance de la recette. Tout ce que vous avez à faire est de maintenir le JSUT de la même manière que CSJ est entré. ―― Les cinq fichiers suivants doivent être préparés en grande taille. - wav.scp - text - lexicon.txt - utt2spk - spk2utt

Comment créer wav.scp

--wav.scp: fichier texte avec le chemin vers le fichier audio

python


import os,sys
import glob
from sklearn.model_selection import train_test_split
import subprocess
import numpy as np
np.random.seed(seed=32)

def sort_file(fname):
    subprocess.call(f'sort {fname} > {fname}.sorted',shell=True)
    subprocess.call(f'rm {fname}',shell=True)
    subprocess.call(f'mv {fname}.sorted {fname}',shell=True)

def convert_wav(wav_data_path,out_dir):
    '''
    * sampling frequency must be 16kHz
    * wav file of JSUT is 48.1kHz, so convert to 16kHz using sox
        e.g. FILE_ID sox [input_wavfilename] -r 16000 [output_wavfilename]
    '''
    for wav_data in wav_data_path:
        fname = wav_data.split('/')[-1]
        subprocess.call(f'sox {wav_data} -r 16000 {out_dir}/{fname}',shell=True)
        subprocess.call(f'chmod 774 {out_dir}/{fname}',shell=True)

def make_wavscp(wav_data_path_list,out_dir,converted_jsut_data_dir):
    '''
    wav.scp: format -> FILE_ID cat PATH_TO_WAV |
    '''
    out_fname = f'{out_dir}/wav.scp'
    with open(out_fname,'w') as out:
        for wav_data_path in wav_data_path_list:
            file_id = wav_data_path.split('/')[-1].split('.')[0]
            out.write(f'{file_id} cat {converted_jsut_data_dir}/{file_id}.wav |\n')
    sort_file(out_fname)


#Répertoire actuel-> kaldi/egs/csj/jsut  (jsut est le même que s5, vient de changer le nom du répertoire. Lors de la copie depuis s5, assurez-vous d'hériter du lien symbolique.(cp -Ajouter une option comme un))
data_dir    = './data'
train_dir   = f'{data_dir}/train'
eval_dir    = f'{data_dir}/eval'
original_jsut_data_dir  = '/path/to/JSUT/corpus'
converted_jsut_data_dir = '/path/to/converted/JSUT/corpus'
# make wav.scp of train and eval
wav_data_path = glob.glob(f'{original_jsut_data_dir}/*/wav/*.wav')
# convert JSUT wav data to 16kHz
convert_wav(wav_data_path,converted_jsut_data_dir)
# split data  [train_size = 7196, test_size = 500]
train_wav_data_list, eval_wav_data_list = train_test_split(wav_data_path, test_size=500)
make_wavscp(train_wav_data_list,train_dir,converted_jsut_data_dir)
make_wavscp(eval_wav_data_list,eval_dir,converted_jsut_data_dir)

―― La commande sox est utilisée lors de la conversion de 44,1 kHz à 16 kHz. Vous pouvez également le faire avec ffmpeg. --``` sox [nom du fichier vocal que vous voulez convertir] -r 16000 [nom du fichier vocal après la conversion]

wav.scp


BASIC5000_0051 cat /home/kaldi/egs/csj/jsut/JSUT/BASIC5000_0051.wav |
BASIC5000_0053 cat /home/kaldi/egs/csj/jsut/JSUT/BASIC5000_0053.wav |
BASIC5000_0082 cat /home/kaldi/egs/csj/jsut/JSUT/BASIC5000_0082.wav |
BASIC5000_0094 cat /home/kaldi/egs/csj/jsut/JSUT/BASIC5000_0094.wav |
BASIC5000_0101 cat /home/kaldi/egs/csj/jsut/JSUT/BASIC5000_0101.wav |
...
...

Comment créer du texte

--text: En termes simples, il s'agit d'un texte de transcription (transcription) pour la voix. En d'autres termes, c'est une version textuelle des mots prononcés. Le texte transcrit est fourni dans le corpus JSUT depuis le début. S'il s'agit d'un corpus vocal, au moins la voix et la transcription sont incluses.

python


def make_transcript(transcript_data_path_list,train_dir,eval_dir,error_dir,eval_wav_data_list):
    '''
    text: format -> UTT_ID  TRANSCRIPT
        * UTT_ID == FILE_ID (one wav file <-> one utterance)

    transcript_data_path_list:Fichier texte de transcription JUST Corpus(transcript_utf8.txt)Liste des chemins vers(transcript_utf8.Il y a plusieurs txt)
    train_dir:Pour s'entraîner
    '''
    # change hankaku to zenkaku
    ZEN = "".join(chr(0xff01 + i) for i in range(94))
    HAN = "".join(chr(0x21 + i) for i in range(94))
    HAN2ZEN = str.maketrans(HAN,ZEN)

    eval_utt_id_list = []
    for eval_wav_data in eval_wav_data_list:
        eval_utt_id_list.append(eval_wav_data.split('/')[-1].split('.')[0])

    word_reading_fname = './word_reading.txt'
    word_reading_dict = {}  # {'word':'reading'}
    with open(word_reading_fname,'r') as f:
        lines = f.readlines()
        for line in lines:
            split_line = line.strip().split('+')
            word_reading_dict[split_line[0]] = split_line[1]

    out_train_fname = f'{train_dir}/transcript'
    out_eval_fname  = f'{eval_dir}/transcript'
    out_no_reading_word_fname = f'{error_dir}/no_reading_word.txt'
    no_reading_word_list = []
    chasen_tagger = MeCab.Tagger ("-d /usr/local/lib/mecab/dic/mecab-ipadic-neologd")
    with open(out_train_fname,'w') as out_train, open(out_eval_fname,'w') as out_eval,\
            open(out_no_reading_word_fname,'w') as no_reading:
        for transcript_data_path in transcript_data_path_list:
            with open(transcript_data_path,'r') as trans:
                line = trans.readline()
                while line:
                    split_line = line.strip().split(':')
                    utt_id = split_line[0]
                    transcript = split_line[1].translate(HAN2ZEN)
                    transcript = transcript.replace('・',' ').replace('-',' ').replace('』',' ').replace('『',' ').replace('」',' ').replace('「',' ')
                    node = chasen_tagger.parseToNode(transcript)
                    transcript_line = []
                    while node:
                        feature = node.feature
                        if feature != 'BOS/EOS,*,*,*,*,*,*,*,*':
                            surface = node.surface
                            split_feature = feature.split(',')
                            reading = split_feature[-1]
                            part_of_speech = '/'.join(split_feature[:2]).replace('/*','')
                            # extract no reading word to error/no_reading_word_list.txt
                            if reading == '*':
                                if surface not in no_reading_word_list:
                                    no_reading_word_list.append(surface)
                                    no_reading.write(f'{surface}\n')
                            if surface == '、' or surface == '。' or surface == ',' or surface == '.':
                                transcript_line.append('<sp>')
                            elif surface != '-':
                                if reading == '*':
                                    reading = word_reading_dict[surface]
                                    transcript_line.append('{}+{}+{}'.format(surface,reading,part_of_speech))
                                else:
                                    transcript_line.append('{}+{}+{}'.format(surface,reading,part_of_speech))
                        node = node.next
                    transcript_line = ' '.join(transcript_line)
                    if utt_id in eval_utt_id_list:
                        out_eval.write(f'{utt_id}  {transcript_line}\n')
                    else:
                        out_train.write(f'{utt_id}  {transcript_line}\n')
                    line = trans.readline()
    sort_file(out_train_fname)
    sort_file(out_eval_fname)

data_dir    = './data'
train_dir   = f'{data_dir}/train'
eval_dir    = f'{data_dir}/eval'
original_jsut_data_dir  = '/path/to/JSUT/corpus'
# split data  [train_size = 7196, test_size = 500]
train_wav_data_list, eval_wav_data_list = train_test_split(wav_data_path, test_size=500)
# make text of train and eval
transcript_data_path = glob.glob(f'{original_jsut_data_dir}/*/transcript_utf8.txt')
make_transcript(transcript_data_path,train_dir,eval_dir,error_dir,eval_wav_data_list)
make_text(train_dir,eval_dir)

――La demi-largeur et la pleine largeur conviennent, mais il est plus pratique de la rendre pleine largeur, afin qu'elle soit convertie en pleine largeur.

python


def make_text(train_dir,eval_dir):
    train_transcript_fname  = f'{train_dir}/transcript'
    eval_transcript_fname   = f'{eval_dir}/transcript'
    out_train_fname         = f'{train_dir}/text'
    out_eval_fname          = f'{eval_dir}/text'
    with open(train_transcript_fname,'r') as trian_trans, open(eval_transcript_fname,'r') as eval_trans, \
            open(out_train_fname,'w') as out_train, open(out_eval_fname,'w') as out_eval:
        train_trans_line = trian_trans.readline()
        while train_trans_line:
            split_train_trans_line = train_trans_line.strip().split(' ')
            # if <sp> is in End of Sentence then remove it.
            if split_train_trans_line[-1] == "<sp>":
                split_train_trans_line.pop(-1)
            out_train.write(split_train_trans_line[0]+' ')  # write utt_id
            for i,word in enumerate(split_train_trans_line[2:]):
                if word == '<sp>':
                    out_train.write(' <sp>')
                else:
                    split_word = word.split('+')
                    out_train.write(' {}+{}'.format(split_word[0],split_word[2]))
            out_train.write('\n')
            train_trans_line = trian_trans.readline()

        eval_trans_line = eval_trans.readline()
        while eval_trans_line:
            split_eval_trans_line = eval_trans_line.strip().split(' ')
            # if <sp> is in End of Sentence then remove it.
            if split_eval_trans_line[-1] == "<sp>":
                split_eval_trans_line.pop(-1)
            out_eval.write(split_eval_trans_line[0]+' ')  # write utt_id
            for i,word in enumerate(split_eval_trans_line[2:]):
                if word == '<sp>':
                    out_eval.write(' <sp>')
                else:
                    split_word = word.split('+')
                    out_eval.write(' {}+{}'.format(split_word[0],split_word[2]))
            out_eval.write('\n')
            eval_trans_line = eval_trans.readline()

    sort_file(out_train_fname)
    sort_file(out_eval_fname)


data_dir    = './data'
train_dir   = f'{data_dir}/train'
eval_dir    = f'{data_dir}/eval'
make_text(train_dir,eval_dir)

Comment créer lexicon.txt

--lexicon.txt: Un fichier texte comme un dictionnaire de mots dans lequel 'mot + symbole de prononciation de mot partiel' ' est écrit pour chaque ligne. Les symboles alphabétiques sont ajoutés à l'aide de l'information «Lecture de Katakana».

python


def make_lexicon(train_dir,lexicon_dir):
    '''
    lexicon: format -> 'word'+'part of speech'
    '''
    transcript_fname        = f'{train_dir}/transcript'
    out_lexicon_fname       = f'{lexicon_dir}/lexicon.txt'
    out_lexicon_htk_fname   = f'{lexicon_dir}/lexicon_htk.txt'
    with open(transcript_fname,'r') as trans, open(out_lexicon_fname,'w') as out:
        trans_line = trans.readline()
        while trans_line:
            split_trans_line = trans_line.strip().split(' ')[2:]
            for word in split_trans_line:
                if word != '<sp>':
                    out.write(word+'\n')
            trans_line = trans.readline()

    subprocess.call(f'sort -u {out_lexicon_fname} > {out_lexicon_htk_fname}',shell=True)
    subprocess.call(f'./local/csj_make_trans/vocab2dic.pl -p local/csj_make_trans/kana2phone -e ./data/lexicon/ERROR_v2d -o {out_lexicon_fname} {out_lexicon_htk_fname}',shell=True)
    subprocess.call(f"cut -d'+' -f1,3- {out_lexicon_fname} >{out_lexicon_htk_fname}",shell=True)
    subprocess.call(f"cut -f1,3- {out_lexicon_htk_fname} | perl -ape 's:\t: :g' >{out_lexicon_fname}",shell=True)


data_dir    = './data'
train_dir   = f'{data_dir}/train'
lexicon_dir = f'{data_dir}/lexicon'
# make lexicon fomr data/train/transcript
make_lexicon(train_dir,lexicon_dir)

--lexicon.txt est créé uniquement à partir du texte d'entraînement (jsut / data / train / transcript). Vous ne pourrez pas faire une évaluation correcte en utilisant le texte de test.

Comment créer utt2spk

--utt2spk: un fichier texte qui enregistre une paire d'ID de parole et d'ID de locuteur. L'ID de parole a également été utilisé dans le fichier text et `` wav.scp. Dans le cas du corpus JSUT, l'ID de parole et l'ID de fichier sont identiques. C'est parce qu'un fichier audio et un discours. Dans le corpus CSJ, etc., un fichier vocal contient plusieurs énoncés, donc l'ID d'énoncé = l'ID de fichier ne tient pas.

python



def make_utt2spk(dir):
    '''
    In JSUT corpus, speaker number is one person.
    It is not good for training Acoustic Model.
    '''
    text_fname            = f'{dir}/text'
    out_utt2spk_fname     = f'{dir}/utt2spk'
    speaker_id = "jsut_speaker"
    with open(text_fname,'r') as text, open(out_utt2spk_fname,'w') as out:
        text_line = text.readline()
        while text_line:
            utt_id = text_line.split(' ')[0]
            out.write(f'{utt_id} {speaker_id}\n')
            text_line = text.readline()


data_dir    = './data'
train_dir   = f'{data_dir}/train'
eval_dir    = f'{data_dir}/eval'
# make utt2spk
make_utt2spk(train_dir)
make_utt2spk(eval_dir)

Comment créer spk2utt

--spk2utt: l'opposé de utt2spk

python



def make_spk2utt(dir):
    utt2spk_fname     = f'{dir}/utt2spk'
    out_spk2utt_fname = f'{dir}/spk2utt'
    with open(utt2spk_fname,'r') as utt2spk, open(out_spk2utt_fname,'w') as out:
        speaker_utt_dict = {}   # {'speaker_id':'utt_id'}
        utt2spk_line = utt2spk.readline()
        while utt2spk_line:
            split_utt2spk_line = utt2spk_line.strip().split(' ')
            utt_id = split_utt2spk_line[0]
            spk_id = split_utt2spk_line[1]
            if spk_id in speaker_utt_dict:
                speaker_utt_dict[spk_id].append(utt_id)
            else:
                speaker_utt_dict[spk_id] = [utt_id]
            utt2spk_line = utt2spk.readline()

        for spk_id, utt_id_list in speaker_utt_dict.items():
            out.write(f'{spk_id}')
            for utt_id in utt_id_list:
                out.write(f' {utt_id}')
            out.write('\n')

data_dir    = './data'
train_dir   = f'{data_dir}/train'
eval_dir    = f'{data_dir}/eval'
# make spk2utt
make_ spk2utt(train_dir)
make_ spk2utt(eval_dir)

Commencer à apprendre

--Clonez Kaldi depuis Github, installez les outils nécessaires, etc., et créez d'abord un répertoire pour jsut.

#Déplacer vers le répertoire où se trouve la recette CSJ
cd /home/kaldi/egs/csj

#Copiez le répertoire s5 avec le nom jsut(Assurez-vous de copier avec l'option a)
cp -a s5 jsut

--Bien qu'il soit possible d'accélérer l'apprentissage par un traitement parallèle, il peut ne pas être possible de bien apprendre en raison du nombre insuffisant de threads du processeur. Pour le moment, les paramètres sont définis de manière à ce que le traitement parallèle tel que le traitement parallèle ne soit pas effectué autant que possible, donc cela prendra un certain temps, mais je pense que vous pouvez apprendre avec un PC avec quelques performances. --Paramètre pour changer le nombre de processus parallèles (défini dans run.sh) - --nj N

Précision lors de l'entraînement uniquement avec le corpus JUST

―― Étant donné que la quantité de données n'est que d'environ 10 heures, je n'ai pas pu apprendre du tout. - WER = 70.78

Recommended Posts

Comment entraîner Kaldi avec JUST Corpus
Comment essayer l'algorithme des amis d'amis avec pyfof
J'ai essayé d'entraîner la fonction péché avec chainer
Comment supprimer la chaîne de caractères spécifiée avec la commande sed! !! !!
Ajoutez simplement le pilote à la touche de forme avec mélangeur
[Introduction à Python] Comment itérer avec la fonction range?
Comment créer un sous-menu avec le plug-in [Blender]
[Python] Comment spécifier l'emplacement de téléchargement avec youtube-dl
[Python] Comment réécrire le style de table avec python-pptx [python-pptx]
Comment apprendre le SVM structuré de ChainCRF avec PyStruct
[Comment!] Apprenez et jouez à Super Mario avec Tensorflow !!
Comment mettre à jour avec SQLAlchemy?
Comment lancer avec Theano
Comment modifier avec SQLAlchemy?
Comment séparer les chaînes avec ','
Comment faire RDP sur Fedora31
Comment utiliser le décorateur
Comment supprimer avec SQLAlchemy?
Comment augmenter l'axe
Comment démarrer la première projection
J'ai essayé de simuler la propagation de l'infection avec Python
Comment manipuler le DOM dans iframe avec Selenium
Comment entrer dans l'environnement de développement Python avec Vagrant
[Introduction à Python] Comment obtenir des données avec la fonction listdir
Comment annuler RT avec Tweepy
Comment calculer le coefficient d'autocorrélation
Python: comment utiliser async avec
Comment utiliser la fonction zip
Comment utiliser le module optparse
Pour utiliser virtualenv avec PowerShell
Comment installer python-pip avec ubuntu20.04LTS
Comment gérer les données déséquilibrées
Comment lire l'ensemble de données SNLI
Comment obtenir la version Python
Comment démarrer avec Scrapy
Comment démarrer avec Python
Comment démarrer avec Django
Comment augmenter les données avec PyTorch
Comment écraser la sortie sur la console
Comment installer mysql-connector avec pip3
Comment INNER JOIN avec SQL Alchemy
Comment utiliser le module ConfigParser
Comment installer Anaconda avec pyenv
[Hugo] Résumé de la façon d'ajouter des pages au site créé avec Learn
[Introduction à Python] Comment fractionner une chaîne de caractères avec la fonction split
Comment obtenir l'ID de Type2Tag NXP NTAG213 avec nfcpy
Comment faire une commande pour lire le fichier de paramètres avec pyramide
[Mémo] Comment utiliser BeautifulSoup4 (2) Afficher l'en-tête de l'article avec les demandes
[Python] Explique comment utiliser la fonction format avec un exemple
Comment surveiller l'état d'exécution de sqlldr avec la commande pv
[Mémo] Comment utiliser BeautifulSoup4 (3) Afficher l'en-tête de l'article avec class_
Comment envoyer une requête à l'API DMM (FANZA) avec python
Comment effectuer un traitement arithmétique avec le modèle Django