实现可以语音交流的问答机器人。
1. 讯飞语音识别(输入)
2. seq2seq 问答
3. 讯飞语音合成(输出)
4. 整合
思想:从麦克风输入,通过科大讯飞的在线语音识别SDK(需要联网),将语音输入保存为txt文件。
首先,需要一个科大讯飞的帐号,然后需要创建应用(可能需要等待一段时间审核),选择Linux应用平台,不创建应用是不能下载sdk的,有了自己的应用后进行SDK下载。
如图所示,我们选择语音听写和在线语音合成这两个免费的项目,然后将SDK下载下来。 一个文件名以Linux开头,包含了专属ID的文件夹,本人将其改名成了xunfei。文件夹下有bin、doc、include、libs、samples五个子文件夹。
我们主要看samples文件夹,里面也有五个文件夹,我们只需要iat_online_record_sample(在线语言识别)和tts_online_sample(在线语音合成)。
语言识别测试:
可以先测试一下,64位ubuntu系统,在iat_online_record_sample 文件夹下,输入:
source 64bit_make.sh //运行64bit_make.sh
cd ../../bin //切换到bin目录下
./iat_online_record_sample //运行iat_online_record_sample可执行文件
需要联网,我们选择不上传用户词汇表,和从micphone输入,我们就可以对着电脑说一段话,15秒后会打印出语音识别结果。
修改语言识别保存到txt:
我们回到iat_online_record_sample文件夹下,打开iat_online_record_sample.c文件,注释掉 是否上传用户词表和录音文件与麦克风输入的选择(aud_src=1 直接选择了麦克风输入),appid是自己的appid(原来的iat_online_record_sample.c中已经写好了),main函数改成下面的样子:
/* main thread: start/stop record ; query the result of recgonization.
* record thread: record callback(data write)
* helper thread: ui(keystroke detection)
*/
int main(int argc, char* argv[])
{
int ret = MSP_SUCCESS;
int upload_on = 1; /* whether upload the user word */
/* login params, please do keep the appid correct */
const char* login_params = "appid = 你自己的appid, work_dir = .";
int aud_src = 0; /* from mic or file */
/*
* See "iFlytek MSC Reference Manual"
*/
const char* session_begin_params =
"sub = iat, domain = iat, language = zh_cn, "
"accent = mandarin, sample_rate = 16000, "
"result_type = plain, result_encoding = utf8";
/* Login first. the 1st arg is username, the 2nd arg is password
* just set them as NULL. the 3rd arg is login paramertes
* */
ret = MSPLogin(NULL, NULL, login_params);
if (MSP_SUCCESS != ret) {
printf("MSPLogin failed , Error code %d.\n",ret);
goto exit; // login fail, exit the program
}
/*
printf("Want to upload the user words ? \n0: No.\n1: Yes\n");
scanf("%d", &upload_on);
if (upload_on)
{
printf("Uploading the user words ...\n");
ret = upload_userwords();
if (MSP_SUCCESS != ret)
goto exit;
printf("Uploaded successfully\n");
}
*/
//printf("Where the audio comes from?\n"
// "0: From a audio file.\n1: From microphone.\n");
//scanf("%d", &aud_src);
aud_src=1; //从microphone输入
if(aud_src != 0) {
printf("Demo recognizing the speech from microphone\n");
printf("Speak in 15 seconds\n");
demo_mic(session_begin_params);
printf("15 sec passed\n");
} else {
printf("Demo recgonizing the speech from a recorded audio file\n");
demo_file("wav/iflytek02.wav", session_begin_params);
}
exit:
MSPLogout(); // Logout...
return 0;
}
为了让语音识别能保存到txt中,showresult函数修改为:
static void show_result(char *string, char is_over)
{
printf("\rResult: [ %s ]", string);
if(is_over)
putchar('\n');
//2019.1.12
FILE *fp;
fp = fopen("recognition.txt", "w");
fprintf(fp, string);
fclose(fp);
}
另外,demo_mic函数中可以修改说话的时间,默认十五秒。
修改完成后,再一次生成可执行文件,然后测试一下:
source 64bit_make.sh //运行64bit_make.sh
cd ../../bin //切换到bin目录下
./iat_online_record_sample //运行iat_online_record_sample可执行文件
识别效果如图所示:
同时,在讯飞的bin下会生成一个recognition.txt,保存了我们语音输入的话。
思想:读取语音识别得到的txt,通过训练好的seq2seq模型,得出对这句话的回答。
seq2seq 问答机器人来源于开源项目 dynamic-seq2seq-master:https://github.com/yanwii/dynamic-seq2seq
小黄鸡的对话语料库,本身就答非所问地,训练完结果更加答非所问。
环境为tensorflow1.4, 修改后可以在python3下运行,print加括号,以及在utils.py中将
self.data = zip(self.Q, self.A) 修改为 self.data = list(zip(self.Q, self.A))
用法:
python3 prepare_dialog.py 10000 //挑选对话语料中的前10000个为训练样本,全部训练样本太大。
python3 seq2seq.py retrain //重新训练。
训练中可能遇到错误
Invalid argument: logits and labels must have the same first dimension, got logits shape [20,9003] and labels shape [36]
大致意思是计算logits[20,9003]与标签label[36] 不对应,经过仔细看,发现在dynamic_seq2seq_model.py中,_init_decoder函数下,解码最多只能比编码长度大20倍。
maximum_iterations = tf.round(tf.reduce_max(self.encoder_inputs_length) * 20)
改为如下,解码比编码大40倍即可。
maximum_iterations = tf.round(tf.reduce_max(self.encoder_inputs_length) * 40)
对于训练过程,如果希望快速收敛,可以先将学习率调高训练,然后训练到一半,调小学习率再进行训练。同样是在dynamic_seq2seq_model.py,_init_optimizer函数中。
optimizer = tf.train.GradientDescentOptimizer(1) #初始训练时候学习率设置为1
#当损失经过明显下降后,ctrl+c取消,然后调整学习率到0.1 python3 seq2seq.py train 继续训练
#optimizer = tf.train.GradientDescentOptimizer(0.1)
#可以用不同的优化器,例如
#optimizer = tf.train.AdamOptimizer(1)
测试:
主函数中当运行附带参数为infer时候,会打印出seq.predict(' xxxx ')的回答,‘xxxx’就是我们说的话,以后通过读取语音识别的txt文件获取。我们运行
python3 seq2seq.py infer
即可以得到回答。
修改从txt来到txt去:
将主函数改成如下:
if __name__== '__main__':
seq = Seq2seq()
r = open('xunfei/recognition.txt') #讯飞的目录,我这个是相对路径
sequence=r.read()
answer=seq.predict(sequence)
w = open('xunfei/response.txt','w') #讯飞的目录
w.write(answer)
思想:从txt中读取文本,语音合成并输出
有了用讯飞语言识别的经验,语音合成也是类似。
切换到xunfei/sample/tts_online_sample 目录下,将主函数改为:
int main(int argc, char* argv[])
{
int ret = MSP_SUCCESS;
const char* login_params = "appid = ‘自己的appid’, work_dir = .";//登录参数,appid与msc库绑定,请勿随意改动
/*
* rdn: 合成音频数字发音方式
* volume: 合成音频的音量
* pitch: 合成音频的音调
* speed: 合成音频对应的语速
* voice_name: 合成发音人
* sample_rate: 合成音频采样率
* text_encoding: 合成文本编码格式
*
*/
const char* session_begin_params = "voice_name = vixk, text_encoding = utf8, sample_rate = 16000, speed = 50, volume = 50, pitch = 50, rdn = 2";
const char* filename = "tts_sample.wav"; //合成的语音文件名称
// 19.1.11
char read_result[100] = "";
FILE *fp = NULL;
fp = fopen("response.txt","rb");
fscanf(fp , "%s" , read_result);
fclose(fp);
// 19.1.11
const char* text = read_result; //合成文本
/* 用户登录 */
ret = MSPLogin(NULL, NULL, login_params);//第一个参数是用户名,第二个参数是密码,第三个参数是登录参数,用户名和密码可在http://www.xfyun.cn注册获取
if (MSP_SUCCESS != ret)
{
printf("MSPLogin failed, error code: %d.\n", ret);
goto exit ;//登录失败,退出登录
}
printf("\n###########################################################################\n");
printf("## 语音合成(Text To Speech,TTS)技术能够自动将任意文字实时转换为连续的 ##\n");
printf("## 自然语音,是一种能够在任何时间、任何地点,向任何人提供语音信息服务的 ##\n");
printf("## 高效便捷手段,非常符合信息时代海量数据、动态更新和个性化查询的需求。 ##\n");
printf("###########################################################################\n\n");
/* 文本合成 */
printf("开始合成 ...\n");
ret = text_to_speech(text, filename, session_begin_params);
if (MSP_SUCCESS != ret)
{
printf("text_to_speech failed, error code: %d.\n", ret);
}
printf("合成完毕\n");
exit:
//printf("按任意键退出 ...\n");
//getchar();
MSPLogout(); //退出登录
return 0;
}
其实就加了一段:
char read_result[100] = "";
FILE *fp = NULL;
fp = fopen("response.txt","rb");
fscanf(fp , "%s" , read_result);
fclose(fp);
用于从文本中读入字符串转化为音频信号。
source 64bit_make.sh
会在bin 文件夹下生成一个tts_online_sample的可执行文件,运行这个程序会将response.txt 中的话变成一个tts_sample.wav的音频文件。
4. 整合和音频播放
涉及到相对目录 ,我目录列表为如下,把讯飞的sdk整个移动到dynamic-seq2seq-master 文件夹下了。
├── action.py
├── chat.py
├── _config.yml
├── data
├── dialog
├── dynamic_seq2seq_model.py
├── model
├── prepare_dialog.py
├── preprocess.py
├── README.md
├── seq2seq.py
├── test.py
├── utils.py
└── xunfei
新建了chat.py
#coding=utf-8
import os
import pyaudio
import wave
from seq2seq import Seq2seq
def play_wav(file):
# 定义数据流块
chunk = 1024
# 只读方式打开wav文件
f = wave.open(file, "rb")
p = pyaudio.PyAudio()
# 打开数据流
stream = p.open(format=p.get_format_from_width(f.getsampwidth()),
channels=f.getnchannels(),
rate=f.getframerate(),
output=True)
# 读取数据
data = f.readframes(chunk)
# 播放
while data != b"":
stream.write(data)
data = f.readframes(chunk)
# 停止数据流
stream.stop_stream()
stream.close()
# 关闭 PyAudio
p.terminate()
if __name__ == '__main__':
ret = os.system('xunfei/bin/iat_online_record_sample ')
seq = Seq2seq()
with open('recognition.txt', 'r') as f:
sequence = f.read()
answer = seq.predict(sequence)
with open('response.txt', 'w') as f:
f.write(answer[:-7])
ret = os.system('xunfei/bin/tts_online_sample')
play_wav('tts_sample.wav')
play_wav用于播放wav文件
f.write(answer) 写到txt里是 —— 我是人__EOS__ ,讯飞语音合成会把下划线读出来。。
f.write(answer[:-7]) 写到txt里是 —— 我是人 ,这样就不会读下划线下划线了。
讯飞语音是真的强,但是相比之下,seq2seq 的问答系统就没那么好了,基本上处于答非所问的阶段。
寻找更好的语料库,做成ROS包,用于服务机器人。