tensor增加一维度_PyTorch搭建聊天机器人(一)词表与数据加载器_百度文 ...

tensor增加⼀维度_PyTorch搭建聊天机器⼈(⼀)词表数据
加载器
国庆⽆聊逛了逛PyTorch的tutorial,其中有⼀篇chatbot的搭建蛮有意思的。
Chatbot Tutorial
作为⼀个蒟蒻⼤⼆,我看了看tutorial涉及到的论⽂,并且⾃⼰按照batch_first=True动⼿写了写,算是有点收获吧。打算写三四篇⽂章总结⼀下技术细节。顺序⼤概是:数据加载器、⽹络前向逻辑、训练逻辑、评估逻辑。
使⽤pycharm编写项⽬,代码分为四个⽂件:process.py、neural_network.py、train.py、evaluate.py。
先⼤致说⼀下搭建chatbot的思路吧,其实很简单:这⾥的chatbot是基于带Luong attention机制的seq2seq。研究过NLP的同学应该对seq2seq很熟悉,它可以将任意长度的时序信息映射到任意长度,在基于深度神经⽹络的机器翻译中使⽤⼴泛。
实际上,中⽂翻译成英⽂就是训练出⼀个中⽂序列到英⽂序列的映射,⽽我们的chatbot不就是⼀个句⼦到句⼦的映射吗?在不考虑上下语
将⽤户说的话翻译成了⽤户境的情况下,聊天机器⼈可以使⽤seq2seq搭建。如此搭建的聊天机器⼈对⽤户输⼊的语句给出的回复更像是将⽤户说的话翻译成了⽤户希望得到的回复。那么假设我们已经对seq2seq很熟悉了,那么只需要使⽤⼀条条对话(下⾯叫dialog或者pair)作为数据,训练这个
希望得到的回复。
seq2seq模型就可以得到这个训练集风格的chatbot了。
tutorial使⽤的数据是Cornell Movie-Dialogs,下载地址。这部分数据的编码格式不是utf-8,如果你对编码转换这部分不感兴趣,可以直接使⽤笔者仓库中./data中的tsv数据。后⾯的程序中将会直接使⽤tsv数据。
笔者仓库链接如下:
LSTM-Kirigaya/chatbot-based-on-seq2seq2g ithub
提前说明⼀下,对话数据集中的每个pair中,我们把第⼀句话成为input_dialog,后⾯⼀句回复的话称为ouput_dialog。
下⾯完成process.py,这个⽂件完成词表建⽴和数据加载器的建⽴。
说明:下⾯所有数据组织都是按照batch_first来的,也就是所有torch张量的第⼀个维度是batch_size
先引⼊需要的库
from itertools import zip_longest
import random
import torch
构建词表
第⼀步我们需要构建词表,因为⽹络中只会传递张量,我们需要通过构建词表将每个单词映射成⼀个个单词索引(后⾯成为index),也就是将⼀句话转化为index序列。
词表中最核⼼的数据是三个python类型的词典:
word2index:单词到其对应的index的映射。
index2word:index到其对应的单词的映射。
word2count:单词到其在数据集中的总数的映射。
构建词表的逻辑也很简单,只需要遍历数据集,每遇到⼀个词表中没有的单词,就根据已经添加单词的总数给与这个新的单词⼀个index,并由此给word2index和index2word两个字典增加新的元素。
程序如下:
# ⽤来构造字典的类
class vocab(object):
def __init__(self, name, pad_token, sos_token, eos_token, unk_token):
self.name = name
self.pad_token = pad_token
self.sos_token = sos_token
self.unk_token = unk_token
引人入胜的书self.word2index = {"PAD" : pad_token, "SOS" : sos_token, "EOS" : eos_token, "UNK" : unk_token}
self.word2count = {"UNK" : 0}
self.index2word = {pad_token : "PAD", sos_token : "SOS", eos_token : "EOS", unk_token : "UNK"}
self.num_words = 4  # 刚开始的四个占位符 pad(0), sos(1), eos(2),unk(3) 代表⽬前遇到的不同的单词数量
# 向voc中添加⼀个单词的逻辑
def addWord(self, word):
if word not in self.word2index:
宜万铁路
self.word2index[word] = self.num_words
self.word2count[word] = 1
飞地经济
self.index2word[self.num_words] = word
self.num_words += 1
else:
self.word2count[word] += 1
# 向voc中添加⼀个句⼦的逻辑
def addSentence(self, sentence):
for word in sentence.split():
self.addWord(word)
# 将词典中词频过低的单词替换为unk_token
# 需要⼀个代表修剪阈值的参数min_count,词频低于这个参数的单词会被替换为unk_token,相应的词典变量也会做出相应的改变    def trim(self, min_count):
immed:  # 如果已经裁剪过了,那就直接返回
return
keep_words = []
keep_num = 0
for word, count in self.word2count.items():
if count >= min_count:
keep_num += 1
# 由于后⾯是通过对keep_word列表中的数据逐⼀统计,所以需要对count>1的单词重复填⼊
for _ in range(count):
keep_words.append(word)
print("keep words: {} / {} = {:.4f}".format(
keep_num, self.num_words - 4, keep_num / (self.num_words - 4)
)
)
# 重构词表
self.word2index  = {"PAD" : self.pad_token, "SOS" : self.sos_token, "EOS" : s_token, "UNK" : self.unk_token}六和
self.word2count = {}
self.index2word = {self.pad_token : "PAD", self.sos_token : "SOS", s_token : "EOS", self.unk_token : "UNK"}
self.num_words = 4
贺麓成for word in keep_words:
self.addWord(word)
# 读⼊数据,统计词频,并返回数据
def load_data(self, path):
pairs = []
for line in open(path, "r", encoding="utf-8"):
try:
input_dialog, output_dialog = line.strip().split("t")
self.addSentence(input_dialog.strip())
self.addSentence(input_dialog.strip())
self.addSentence(output_dialog.strip())
pairs.append([input_dialog, output_dialog])
except:
pass
return pairs
这个词表类,需要五个参数初始化:name、pad_token、sos_token、eos_token、unk_token。分别为词表的名称、填充词的index、句⼦开头标识符的index、句⼦结束标识符的index和未识别单词的index。
主要⽅法说明如下:
__init__:完成词表的初始化。
trim:根据min_count对词表进⾏剪枝。
load_data:载⼊外部tsv数据,完成三个字典的搭建,并返回处理好的pairs。
处理input_dialog和output_dialog
有了词表,我们就可以根据词表把⼀句话转换成index序列,为此我们通过sentenceToIndex函数完成sentence到index sequence的转换,需要说明的是,为了让后续搭建的⽹络知道⼀句话已经结束了,我们需要给每个转换成的index序列的句⼦添加⼀个eos_token作为后缀:
# 将⼀句话转换成id序列(str->list),结尾加上EOS
def sentenceToIndex(sentence, voc):
return [voc.word2index[word] for word in sentence.split()] + [s_token]
接下来我们需要分别处理input_dialog和output_dialog。
处理input_dialog需要⼀个batchInput2paddedTensor函数,这个函数接受batch_size句没有处理过的input_dialog⽂字、将它们转换为index序列、填充pad_token、转换成batch_first=True的torch张量,返回处理好的torch张量和每句话的长度信息。
⼤致过程还是画张图吧。。。
代码如下:
# 将⼀个batch中的input_dialog转化为有pad填充的tensor,并返回tensor和记录长度的变量
# 返回的tensor是batch_first的
def batchInput2paddedTensor(batch, voc):
# 先转换为id序列,但是这个id序列不对齐
batch_index_seqs = [sentenceToIndex(sentence, voc) for sentence in batch]
length_tensor = sor([len(index_seq) for index_seq in batch_index_seqs])
# 下⾯填充0(PAD),使得这个batch中的序列对齐
zipped_list = list(zip_longest(*batch_index_seqs, fillvalue=voc.pad_token))
韩剧顺英的抉择padded_tensor = sor(zipped_list).t()
return padded_tensor, length_tensor
处理output_dialog与input_dialog差不多,只不过需要多返回⼀个mask矩阵,所谓mask矩阵,就是将padded_tensor转换成bool类型。这些返回的量在后续的训练中都会使⽤到。output_dialog的处理如下:
# 将⼀个batch中的output_dialog转化为有pad填充的tensor,并返回tensor、mask和最⼤句长
# 返回的tensor是batch_first的
def batchOutput2paddedTensor(batch, voc):
# 先转换为id序列,但是这个id序列不对齐
batch_index_seqs = [sentenceToIndex(sentence, voc) for sentence in batch]
max_length = max([len(index_seq) for index_seq in batch_index_seqs])
# 下⾯填充0(PAD),使得这个batch中的序列对齐
zipped_list = list(zip_longest(*batch_index_seqs, fillvalue=voc.pad_token))
padded_tensor = sor(zipped_list).t()
# 得到padded_tensor对应的mask
mask = torch.BoolTensor(zipped_list).t()
return padded_tensor, mask, max_length
有了处理pair的函数,我们可以把上⾯的函数整合成⼀个数据加载器loader。数据加载器在深度学习中很重要,我们在训练中需要能够不重复的、快速地获取⼀个batch的格式化数据,这就是loader的功能,惬意舒适(in )的训练中,⼀个设计合理⽽⾼效的loader 是必不可少的。
此处不再解释Python中⽣成器的概念,为了更加节省内存空间,我们将loader做成⼀个⽣成器:

本文发布于:2024-09-21 10:53:02,感谢您对本站的认可!

本文链接:https://www.17tex.com/xueshu/602140.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:数据   词表   需要   单词   搭建   回复   返回   映射
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议