Bei meiner Arbeit sah ich mich mit der Notwendigkeit konfrontiert, die Anruflisten auf Übereinstimmung der Mitarbeiter mit dem Konversationsskript mit Kunden zu überprüfen. In der Regel wird hierfür ein Mitarbeiter zugewiesen, der viel Zeit damit verbringt, Aufzeichnungen von Gesprächen anzuhören. Wir haben uns die Aufgabe gestellt, den Zeitaufwand für die Überprüfung mithilfe von ASR-Tools (Automatic Speech Recognition) zu reduzieren. Wir werden uns eines dieser Tools genauer ansehen.
Nvidia NeMo ist eine Reihe von Tools für maschinelles Lernen zum Erstellen und Trainieren von GPU- basierten Modellen.
Die Modelle in NeMo verwenden einen modernen Ansatz zur Spracherkennung - Connectionist Time Classification (CTC).
Vor CTC wurde ein Ansatz verwendet, bei dem die eingegebene Audiodatei in separate Sprachsegmente unterteilt und Token daraus vorhergesagt wurden. Dann wurden die Token kombiniert, die wiederholten wurden zu einem zusammengefasst und das Ergebnis wurde der Modellausgabe zugeführt.
In diesem Fall litt die Erkennungsgenauigkeit, da ein Wort mit wiederholten Buchstaben nicht als 100% korrekt erkannt angesehen wurde. Zum Beispiel wurde "Zusammenarbeit" auf "Zusammenarbeit" reduziert.
Mit CTC - immer noch ein Token pro Zeitsegment der Sprache vorhersagen und zusätzlich ein leeres Token verwenden, um herauszufinden, wo doppelte Token gefaltet werden sollen. Das Erscheinen eines leeren Tokens hilft dabei, doppelte Buchstaben zu trennen, die nicht gefaltet werden sollten.
Für meine Aufgabe nahm ich eines der Modelle (Jasper 10 × 5) und trainierte es von Grund auf neu. Für das Training wurde ein öffentlicher Datensatz mit Telefongesprächen ausgewählt, der geschnittene Audioaufnahmen und deren Transkriptionen enthält.
Um das Modell zu trainieren, müssen Sie eine Manifestdatei vorbereiten, die Informationen zur Audiodatei und zur Transkription dieser Datei enthält. Die Manifestdatei hat ein eigenes Format:
{{"audio_filepath": "path/to/audio.wav", "duration": 3.45, "text": "sometext"}…{"audio_filepath": "path/to/audio.wav", "duration": 3.45, "text": "sometext"}}
Das Modell akzeptiert Audiodateien nur im * .wav-Format. Es ist erforderlich, die gesamte Liste der Audiodateien zu durchlaufen und das Konsolendienstprogramm zu verwenden, um Audiodateien mit einer anderen als der erforderlichen Auflösung neu zu codieren:
def convertToWav(self, ext):
if not os.path.exists(self.datadir + '/dataset'):
tar = tarfile.open(self.an4Path);
tar.extractall(path=self.datadir);
sphList = glob.glob(self.datadir + '/dataset/**/*' + ext, recursive=True);
for sph in sphList:
wav = sph[:-4] + '.wav';
cmd = ["sox", sph, wav];
subprocess.run(cmd);
print('renamed ' + ext + ' to ' + wav);
, getduration(filename=audiopath) Librosa, :
def buildManifest(self, transcript_path, manifest_path, wav_path):
with open(transcript_paths, 'r') as fin:
with open(manifest_path, 'w') as fout:
for line in fin:
transcript = line[: line.find('(')-1].lower();
transcript = transcript.replace('<s>', '').replace('</s>', '');
transcript = transcript.strip();
file_id = line[line.find('(')+1 : -2];
audio_path = os.path.join(self.datadir, wav_paths, file_id[file_id.find('-')+1 : file_id.rfind('-')], file_id +'.wav');
duration = librosa.core.get_duration(filename=audio_path);
metadata = {
"audio_filepath": audio_path,
"duration": duration,
"text": transcript
}
print(metadata);
json.dump(metadata, fout);
fout.write('\n');
, :
config.yaml:
name: &name "Jasper10x5"
model:
sample_rate: &sample_rate 16000
labels: &labels [" ", "a", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "'"]
preprocessor:
_target_: nemo.collections.asr.modules.AudioToMelSpectrogramPreprocessor
normalize: "per_feature"
sample_rate: *sample_rate
features: &n_mels 64
n_fft: 512
frame_splicing: 1
dither: 0.00001
stft_conv: false
. pytorch_lighting :
import nemo;
class NemoASR:
def __init__(self, dataDir):
self.datadir = dataDir;
self.CONF_PATH = './config.yaml';
yaml = YAML(typ="safe");
with open(self.CONF_PATH) as f:
self.CONFIG = yaml.load(f);
def train(self, transcriptionPATH, manifestPATH, wavPATH, testTranscriptionPATH, testManifestPATH, testWavPATH):
print("begin train");
train_transcripts = self.datadir + transcriptionPATH;
train_manifest = self.datadir + manifestPATH;
if not os.path.isfile(train_manifest):
self.buildManifest(train_transcripts, train_manifest, wavPATH);
test_transcripts = self.datadir + testTranscriptionPATH;
test_manifest = self.datadir + testManifestPATH;
if not os.path.isfile(test_manifest):
self.buildManifest(test_transcripts, test_manifest, testWavPATH);
# params from ./config.yaml
self.CONFIG['model']['train_ds']['manifest_filepath'] = train_manifest;
self.CONFIG['model']['validation_ds']['manifest_filepath'] = test_manifest;
trainer = pl.Trainer(max_epochs=500, gpus=1);
self.model = nemo_asr.models.EncDecCTCModel(cfg=DictConfig(self.CONFIG['model']), trainer=trainer);
trainer.fit(self.model);
print("end train");
#-------------------------------------------------------------
nemoASR = NemoASR('.');
if (nemoASR.checkExistsDataSet()):
print('dataset loaded');
nemoASR.train('./dataset/etc/train.transcription', './dataset/train_manifest.json','./dataset/wav/an4_clstk', './dataset/etc/test.transcription', './dataset/test_manifest.json', './dataset/wav/an4test_clstk');
nemoASR.model.save_to('./model.sbc');
:
files = ['./an4/wav/an4_clstk/mgah/cen2-mgah-b.wav'];
for fname, transcription in zip(files, nemoASR.model.transcribe(paths2audio_files=files)):
print(f"Audio in {fname} was recognized as: {transcription}");
, .
NeMo :
GPU;
, ;
.
, , -.
ASR . .
, (TTS) (speaker recognition).