自然语言处理 期末大作业
数据科学与计算机学院
17341190 叶盛源
实
验
环
境
本次实验是在windows10操作系统下进行的。使用的IDE为pycharm
我使用的神经网络框架搭配为pytorch+python3.6。
其他需要安装的包在代码包requirement.txt中包含,可以直接导入。在这里列出生关键的一些依赖包的名
称和版本号:
实
验
原
理
本次实验的目标是用编码器和解码器实现一个中文到英文的神经网络的翻译模型。
编码器使用LSTM的序列神经网络,将 我们的目标句子通过时间序列输入,最 终将一个中文的文本句子
编码成一个特定维数的向量。我们 这里使用的是双向的LSTM模型,最后将前 向和后向的隐藏层输出值
对应位置求和。
jieba==0.39
matplotlib==3.1.1
nltk==3.4.5
numpy==1.17.4
torch==1.3.1
torchvision==0.4.2
接着我们要构建一个解码器模型, 解码器我们使用了一个单向的LSTM网络, 我们将编码器最后一个时
刻输出的结果,也就是中文句子的编码结果作为解码器的初始状态,从这个状态开始进行翻译。我们每
次只翻译一个词语,所以时间步数为1。
我们可以选择标准的答案或者上一个时刻翻译出来的结果作为输入:
Teacher Forcing:直接使用训练数据的标准答案(ground truth)的对应上一项作为当前时间步的输入;
Curriculum Learning:使用一个概率p,随机决定选择使用ground truth还是前一个时间步模型生成的预
测,来作为当前时间步的输入。
之后我们还可以加入一个注意力机制( Attention)来加强 解码器翻译 的能力,我 们将编码器 的每个输出
保存下来,将他们和当前解码的词语进行一个评分计算注意力权值,然后将编码器的每个时间片的输出
结果加权求和,得到一个context_vector 这个vector和原来的解码器的向量做一个拼接,这样就更多的使
用到了源语言的语义,可以取得比之前更好的实验效果。如下图:
最后我们将拼接得到的输出向量 用 一个线性映射到和词语数量相同的维度,然后用softmax给每个词语一
个概率值,用交叉熵作为误差函数,反向传播正确的词语优化翻译结果。
最后我们可以得到一个编码器和解码器构成的简单神经网络翻译模型:
数据
预
处
理
首先我们需要将文本的文档做一个预处理,因为文本是一个训练集,所以处理过程比较简单。我们读取
文件之后,遍历每一个句子,先分别对中英文的数据进行分词,然后把分词的结果加入到词典中。然后
把文本对应的词语换成数字替代,这样可以在后续的实验中比较方便的读取数据。
之后我们还要处理测试数据,我们测试数据的词典必须要用从训练数据中得到的词典,要不训练集不管
怎么训练都无法预测到不存在的词语。
在分词之后我们还需要对数据添加一些特殊的符号:
def process_train(filename,lag):
word_dic = {'<BOS>': 0, '<EOS>': 1, '<UKN>': 2, '<PAD>': 3}
with open('./dataset_10000/'+filename, 'r', encoding='utf-8') as dataset:
with open('./preprocessing/' + filename, 'w', encoding='utf-8') as
data_encoded:
n = 4
for line in dataset.readlines():
if lag == 0:
sentence = jieba.lcut(line.strip())
elif lag == 1:
sentence = jieba.lcut(line.strip().lower()) # 如果是英文的话就要都
改成小写
sentence = '<BOS> ' + " ".join(sentence) + ' <EOS>'
sentence = sentence.split()
# 构建训练集上的字典
for word in sentence:
if word not in word_dic:
word_dic[word] = n
n += 1
data_encoded.write(str(word_dic[word])+' ')
data_encoded.write('\n')
with open('./preprocessing/word_dic_' + filename, 'w', encoding='utf-8') as
num2word_file:
for word in word_dic:
num2word_file.write(str(word_dic[word]) + ' ' + word + '\n')
return word_dic
1. 其中<pad>加在较短序列后,直到同一batch内每个样本序列等长。
2. 每个句子的开头要是<bos>
3. 每个句子的结尾是<eos>
4. 不存在的词语用<UNK>表示
因为我们定义了词典来给每个词语编号,所以我们需要给每个词语一个下标,因此我们从上到下依次定
义为0,1,2,3加入到我们的词典中。
padding的时候我们需要找到最长的句子长度作为max_length,然后以这个为参照进行句子的padding:
经过上述两个函数,我们就对数据处理完毕了,我们将预处理好的文本和词典用文件形式保存下来,在
模型的训练和测试中会使用到
定
义
模
型
我们使用这里使用的深度学习框架是pytorch,我们需要先定义一个model.py文件来存放我们的模型。模
型分两部分,分别是编码器和解码器,而解码器中,注意力机制需要一个线性层,并且有一些矩阵的乘
法运算,因此我将Attention层提出为单独一个模块。
编码器
首先定义编码器,编码器我们使用 一个双向的LSTM模型,我们首先使用一个 词嵌入的方法,将词语嵌
入到一个特定维度的向量空间中,我这里设置是600维的词语空间。
接着我们定义一个lstm层,设置为双向,隐藏层的节点数为300,然后与 用线性投影到词语的数量,这样
输出的结果就可以直接返回使用,后面也不需要再加全连接层来改变输出维度:
定义好网络 后,我们就需要将每层的 网络连接起来。为了方便 查看,我直接在代码中对每个输入和输出
的维 度大小做了详细的注释,可以直接看着下面的代码。我们先用正态分布初始化LSTM的初始状态,
然后对输入x做一个词语的嵌入。对于双向的LSTM输出结果,我们用求 和的方法把他们连到一起,最后
返回结果。
def padding(batch_data, pad):
#在词表中默认<PAD> 为3 <EOS> 为1
padding_data = list()
# 统计最长的句子长度
max_length = 0
for data in batch_data:
max_length = len(data) if len(data) > max_length else max_length
for data in batch_data:
# 如果没到最长的长度,就进行padding
if len(data) < max_length:
data = data + (max_length-len(data))*[pad]
padding_data.append(data)
return np.array(padding_data, dtype='int64'),max_length
self.embedding = nn.Embedding(self.vocb_size, self.vocb_dim) # 对中文词嵌入
self.lstm = nn.LSTM(self.vocb_dim, self.hidden_dim, bidirectional=True)
def forward(self, x):