Em meu trabalho, me deparei com a necessidade de verificar os registros de chamadas para verificar a conformidade dos funcionários com o roteiro de conversação com os clientes. Normalmente, um funcionário é alocado para isso, que passa grande parte do tempo ouvindo gravações de conversas. Nós nos propusemos a reduzir o tempo gasto na verificação usando ferramentas de reconhecimento automático de fala (ASR). Vamos examinar mais de perto uma dessas ferramentas.
Nvidia NeMo é um conjunto de ferramentas de aprendizado de máquina para construir e treinar modelos com GPU.
Os modelos do NeMo usam uma abordagem moderna para o reconhecimento de fala - Connectionist Time Classification (CTC).
Antes da CTC, era usada uma abordagem na qual o arquivo de áudio de entrada era dividido em segmentos de fala separados e os tokens eram preditos a partir deles. Em seguida, os tokens foram combinados, os repetidos foram agrupados em um e o resultado foi alimentado na saída do modelo.
Nesse caso, a precisão do reconhecimento foi prejudicada, pois uma palavra com letras repetidas não foi considerada 100% reconhecida corretamente. Por exemplo, "coOperation" foi reduzido para "coOperation".
Com CTC - ainda prevendo um token por segmento de tempo de fala e, adicionalmente, usando um token vazio para descobrir onde dobrar tokens duplicados. A aparência de um token vazio ajuda a separar letras duplicadas que não devem ser dobradas.
Para minha tarefa, peguei um dos modelos (Jasper 10 × 5) e o treinei do zero. Para o treinamento, foi escolhido um conjunto de dados públicos de conversas telefônicas, contendo gravações de áudio cortadas e suas transcrições.
Para treinar o modelo, você precisa preparar um arquivo de manifesto contendo informações sobre o arquivo de áudio e a transcrição deste arquivo. O arquivo de manifesto tem seu próprio formato:
{{"audio_filepath": "path/to/audio.wav", "duration": 3.45, "text": "sometext"}…{"audio_filepath": "path/to/audio.wav", "duration": 3.45, "text": "sometext"}}
O modelo aceita arquivos de áudio apenas no formato * .wav. É necessário percorrer toda a lista de arquivos de áudio e usar o utilitário de console para recodificar os arquivos de áudio com uma resolução diferente da necessária:
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).