【Python机器学习】序列到序列建模——使用序列到序列网络构建一个聊天机器人
为了寻聊天机器人,下面使用康奈尔电影对话语料库训练一个序列到序列的网络来“适当的”湖大问题或语句。以下聊天机器人示例采用的是Keras blog中的序列到序列的示例。
为训练准备语料库
首先,需要加载语料库并从中生成训练集,训练数据将决定编码器和解码器在训练阶段和生成阶段所支持的字符集。要注意实现代码不支持在训练阶段未包含的字符。使用整个Cornmell Movie Dialog数据集可能需要大量的计算,因为一些序列有超过2000个词条——2000个时刻,需要一段时间才能展开。但是大多数对话样本都是基于少于100个字符的。对于本例,可以通过将样本限制为少于100个字符、删除奇怪字符和只允许使用小写字符,对对话语料库进行预处理。通过这些处理,可以限制字符的种类。
下面将遍历语料库文件并生成训练对(技术上来说是三元组形式:输入文本、带有初始词条的目标文本和目标文本)。在阅读语料库时,还将生成一组输入字符和目标字符,然后将使用这些字符对样本进行独热编码。输入字符和目标字符的数量不必完全匹配。但是在生成阶段,不能读取和生成不包含在训练集中的字符。下面的代码给出的结果是输入文本和目标文本(字符串)的两个列表:
from nlpia.loaders import get_data
df=get_data('moviedialog')
#数组保存从语料库文件中读取的输入文本和目标文本
input_texts,target_texts=[],[]
#这个集合保存输入文本和目标文本中出现过的字符
input_vocabulary=set()
output_vocabulary=set()
#目标序列用start和stop词条进行注释,这里定义了表示词条的字符。这些词条不能作为普通序列文本的一部分,而应该仅仅作为初始词条和终止词条而使用
start_token='\t'
stop_token='\n'
#max_training_samples定义了训练使用的行数。它是用户定义的最大值和从文件中加载的总行数中较小的数
max_training_samples=min(25000,len(df)-1)
for input_text,target_text in zip(df.statement,df.reply):
#target_text需要用起始词条和终止词条进行包装
target_text=start_token+target_text+stop_token
input_texts.append(input_text)
target_texts.append(target_text)
#编译词汇表——input_text中出现过的唯一字符的集合
for char in input_text:
if char not in input_vocabulary:
input_vocabulary.add(char)
for char in target_text:
if char not in output_vocabulary:
output_vocabulary.add(char)
建立字符字典
这里需要将输入文本和目标文本的每个字符转换为表示每个字符的独热向量。为了生成独热向量,需要生成词条字典(用于输入文本和目标文本),其中每个字符都被映射到一个索引。另外,还将生成反向字典(索引映射为字符),在生成阶段将使用该反向字典将生成的索引转换为字符,具体代码:
#将字符集转换为排序后的字符列表,然后使用该列表生成字典
input_vocabulary=sorted(input_vocabulary)
output_vocabulary=sorted(output_vocabulary)
#对于输入数据和目标数据,确定唯一字符的最大数量,用于构建一个独热矩阵
input_vocab_size=len(input_vocabulary)
output_vocab_size=len(output_vocabulary)
#对于输入数据和目标数据,还需确定序列词条的最大数量
max_encoder_seq_length=max([len(txt) for txt in input_texts])
max_decoder_seq_length=max([len(txt) for txt in target_texts])
#循环遍历input_vocabulary和output_vocabulary来创建查找字典,用于生成独热向量
input_token_index=dict([(char,i) for i,char in enumerate(input_vocabulary)])
target_token_index=dict([(char,i) for i,char in enumerate(output_vocabulary)])
#循环变量新创建的字典以创建反向查找表
reverse_input_char_index=dict((i,char) for char,i in input_token_index.items())
reverse_target_char_index=dict((i,char) for char,i in target_token_index.items())
生成独热编码训练集
下一步,将输入文本和目标文本转换为独热编码的“张量”。为了做到这一点,需要循环遍历每个输入样本和目标样本以及每个样本的每个字符,并对每个字符进行独热编码。每个字符由一个n*1向量编码(其中n是唯一的输入字符或目标字符的个数),然后针对每个样本将所有向量组合以创建一个矩阵,并将所有样本组合以创建要训练的张量。具体代码:
import numpy as np
encoder_input_data=np.zeros((len(input_texts),max_encoder_seq_length,input_vocab_size),dtype='float32')
#训练的张量初始化形状为(num_samples,max_len_sequence,num_unique_tokens_in_vocab)的零张量
decoder_input_data=np.zeros((len(input_texts),max_decoder_seq_length,output_vocab_size),dtype='float32')
decoder_target_data=np.zeros((len(input_texts),max_decoder_seq_length,output_vocab_size),dtype='float32')
#对训练样本进行寻遍历,输入文本和目标文本需要对应
for i , (input_text,target_text) in enumerate(zip(input_texts,target_texts)):
#循环遍历每个样本的每个字符
for t,char in enumerate(input_text):
encoder_input_data[i,t,input_token_index[char]]=1
#对解码器的训练数据,这里将创建decoder_input_data和decoder_target_data
for t,char in enumerate(target_text):
decoder_input_data[i,t,target_token_index[char]]=1
if t>0:
decoder_target_data[i,t-1,target_token_index[char]]=1
训练序列到序列聊天机器人
完成所有准备训练集的工作——将预处理的语料库转换为输入样本和目标样本,创建索引查询字典,并将样本转换为独热张量之后,下面就是训练聊天机器人了。
一旦model.fit函数完成了训练,下面就拥有了一个基于序列到序列的完全训练好的聊天机器人:
from keras.api.models import Model
from keras.api.layers import Input,LSTM,Dense
#批处理大小为64个样本,增加批处理大小可以加快训练速度,但也需要更多的内存
batch_size=64
#100个训练周期
epochs=100
#神经元维数设置为256
num_neurons=256
encoder_inputs=Input(shape=(None,input_vocab_size))
encoder=LSTM(num_neurons,return_state=True)
encoder_outputs,state_h,state_c=encoder(encoder_inputs)
encoder_states=[state_h,state_c]
decoder_inputs=Input(shape=(None,output_vocab_size))
decoder_lstm=LSTM(num_neurons,return_sequences=True,return_state=True)
decoder_outputs,_,_=decoder_lstm(decoder_inputs,initial_state=encoder_states)
decoder_dense=Dense(output_vocab_size,activation='softmax')
decoder_outputs=decoder_dense(decoder_outputs)
model=Model([encoder_inputs,decoder_inputs],decoder_outputs)
model.compile(optimizer='rmsprop',loss='categorical_crossentropy',metrics=['acc'])
model.fit([encoder_input_data,decoder_input_data],
decoder_target_data,
batch_size=batch_size,
epochs=epochs,
validation_split=0.1)
但是训练的时间会非常长,100次训练的时间可能达到了10小时以上:
组装序列生成模型
因为没有特定的目标文本和状态输入解码器,所以所拥有的只是输入和一个初始词条,下面开始组装序列生成模型:
encoder_model=Model(encoder_inputs,encoder_states)
thought_input=[
Input(shape=(num_neurons,)),Input(shape=(num_neurons,))
]
decoder_outputs,state_h,state_c=decoder_lstm(decoder_inputs,initial=thought_input)
decoder_states=[state_h,state_c]
decoder_outputs=decoder_dense(decoder_outputs)
decoder_model=Model(
inputs=[decoder_inputs]+thought_input,
output=[decoder_outputs]+decoder_states
)
预测输出序列
decoder_sequence函数是聊天机器人生成回复的核心。它接受独热编码的输入序列,生成思想向量,并使用思想向量通过之前训练好的网络生成合适的回复:
def decode_sequence(input_seq):
#生成思想向量作为解码器的输入
thought=encoder_model.predict(input_seq)
#与训练相反,target_seq一开始是一个零张量
target_seq=np.zeros((1,1,output_vocab_size))
#解码器的第一个输入词条是初始词条
target_seq[0,0,target_token_index[stop_token]]=1.0
stop_condition=False
generated_sequence=''
while not stop_condition:
#将已生成的词条和最新状态传递给解码器,以预测下一个序列
output_tokens,h,c=decoder_model.predict([target_seq]+thought)
generated_token_idx=np.argmax(output_tokens[0,-1:])
generated_char=reverse_target_char_index[generated_token_idx]
generated_sequence=generated_sequence+generated_char
if (generated_char==stop_token or len(generated_sequence)>max_decoder_seq_length):
#将stop_condition设置为True将停止循环
stop_condition=True
# 更新目标序列,并使用最后生成的词条作为下一生成步骤的输入
target_seq=np.zeros((1,1,output_vocab_size))
target_seq[0,0,generated_token_idx]=1.0
#更新思想向量状态
thought=[h,c]
return generated_sequence
生成回复
现在,定义一个辅助函数response(),用于将输入字符串(例如来自用户的语句)转换为聊天机器人的回复。该函数首先将用户输入的文本转换为由独热编码向量组成的序列。然后将这个独热向量的张量传递给前面定义的decode_sequence()函数。它将输入文本编码成思想向量,并将这些思想向量生成文本:
def responce(input_text):
input_seq=np.zeros((1,max_encoder_seq_length,input_vocab_size),dtype='float32')
for t,char in enumerate(input_text):
input_seq[0,t,input_token_index[char]]=1.0
decode_sentence=decode_sequence(input_seq)
print('回复:',decode_sentence)
与聊天机器人交谈
上面已经完成了训练和测试聊天机器人的所有必要步骤,下面就可以尝试沟通:
responce("what id the internet?")