――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.
――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.
―― 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
kaldi / egs / csj / jsut``` ***
jsut est une copie du répertoire` `` s5
. *** Veuillez noter que le fichier de lien symbolique existe lors de la copie. *** ***--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]
np.random.seed (graine = 32)
``
--Text et utt2spk sont également créés pour la formation et les tests
--wav.scp devrait ressembler à ceciwav.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 |
...
...
--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.
format texte --``` [Speech ID] [Texte transcrit avec informations sur les paroles de la partie] `` `
par exemple UTT001 Demain + Nomenclature + Auxiliaire / Auxiliaire Auxiliaire Ensoleillé + Nomenclature + Verbe auxiliaire
Supprimez les signes de ponctuation.
À proprement parler, remplacez-le par la balise
Premièrement, afin d'acquérir les informations sur les paroles de la partie, le texte transcrit est analysé morphologiquement (en même temps, la "lecture de mots" est également acquise), et un fichier de transcription '' est créé. Créez un fichier
texte '' à partir de ce fichier transcription ''. Le fichier
transcription '' écrit chaque mot qui a été analysé morphologiquement sous la forme de `` 'mot + lecture katakana + partie mot' ''.
*** MeCab (mecab-ipadic-neologd) *** a été utilisé pour l'analyseur morphologique.
--``` chasen_tagger = MeCab.Tagger ("-d / usr / local / lib / mecab / dic / mecab-ipadic-neologd") Veuillez changer la partie de
en fonction de votre environnement.
――Veuillez noter que le résultat sera différent si vous utilisez un autre analyseur morphologique.
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.
word_reading.txt
Lecture des informations de mots dont la lecture du katakana était inconnue lors de l'analyse morphologique avec MeCab. ―― *** Dans le cas du corpus JSUT, il n'y a pas tellement de mots avec des lectures inconnues en Katakana, donc j'ai pu le faire moi-même, mais je dois réfléchir à la façon d'utiliser un corpus avec trop de mots avec des lectures inconnues. *** CSJ, JNAS, etc. nécessitent la lecture de katakana dans un autre texte, il n'y a donc aucun problème si vous l'utilisez.
Ensuite, écrivez la paire de mots mot / partie du fichier
transcript dans le fichier `` `` text '' --
transcritp``` C'est facile parce que vous supprimez simplement la partie "lecture katakana" du mot du contenu écrit dans le fichier.
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)
--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».
transcript dans le fichier `` lexicon.txt
pour ne pas le dupliquer, et
kana2phone préparé dans la recette CSJ. Nous ajouterons des symboles de prononciation à l'aide d'un fichier appelé `` ''.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.
--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.
jsut / data / train / text```.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)
--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)
--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
nnet3 TDNN + Chain '' pour l'apprentissage. --- *** Cette fois, nous utiliserons le corpus JSUT, nous devons donc changer
`` run.sh``` etc. *** *** wav.scp
, etc. sont également inclus, veuillez donc les mettre sous
kaldi / egs / csj / jsut```. Après avoir modifié les 5 fichiers ci-dessus, exécutez simplement run.sh.src / prepare_data ''. Après avoir exécuté prepare_data.py, un répertoire appelé
jsut / data / lexicon``` sera créé et un fichier appelé
ERROR_v2d``` sera créé. Ce fichier contient des mots pour lesquels aucun élément phonétique n'a pu être ajouté. Ces mots doivent être corrigés manuellement. J'ai préparé la version modifiée dans prepare_data '' au cas où. Vous pouvez l'utiliser en le remplaçant par
jsut / data / lexicon / lexicon.txt`
.--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
--
sudo nvidia-smi -c 3 '' et exécutez la commande.―― É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