【自然语言处理(NLP)】基于Transformer的英文自动文摘

时间:2022-10-17 11:25:20

【自然语言处理(NLP)】基于Transformer的英文自动文摘


作者简介:在校大学生一枚,华为云享专家,阿里云专家博主,腾云先锋(TDP)成员,云曦智划项目总负责人,全国高等学校计算机教学与产业实践资源建设专家委员会(TIPCC)志愿者,以及编程爱好者,期待和大家一起学习,一起进步~ . 博客主页ぃ灵彧が的学习日志 . 本文专栏人工智能 . 专栏寄语:若你决定灿烂,山无遮,海无拦 . 【自然语言处理(NLP)】基于Transformer的英文自动文摘

(文章目录)


前言

(一)、任务描述

本示例教程介绍如何使用飞桨完成一个机器翻译任务。

我们将会使用飞桨提供的开源框架完成基于Transformer的英文文本自动文摘模型。飞桨框架实现了Transformer的基本层,因此可以直接调用。


(二)、环境配置

本示例基于飞桨开源框架2.0版本。

import paddle
import paddle.nn.functional as F
import re
import numpy as np

print(paddle.__version__)

# cpu/gpu环境选择,在 paddle.set_device() 输入对应运行设备。
# device = paddle.set_device('gpu')

输出结果如下图1所示: 【自然语言处理(NLP)】基于Transformer的英文自动文摘


一、数据准备


(一)、数据集加载

统计数据集信息,确定句子长度,我们采用包含90%句子长度的长度值作为句子的长度

# 统计数据集中句子的长度等信息 en代表输入长文本 ch代表输出摘要
lines =  open('data/data80310/train.txt','r',encoding='utf-8').readlines()
print(len(lines))
datas = []
dic_content = {}
dic_summary = {}
for line in lines:
    ll = line.strip().split('\t')
    if len(ll)<2:
        continue
    datas.append([ll[0].split(' '),ll[1].split(' ')])
    # print(ll[0])
    if len(ll[0].split(' ')) not in dic_content:
        dic_content[len(ll[0].split(' '))] = 1
    else:
        dic_content[len(ll[0].split(' '))] +=1
    if len(ll[1].split(' ')) not in dic_summary:
        dic_summary[len(ll[1].split(' '))] = 1
    else:
        dic_summary[len(ll[1].split(' '))] +=1
keys_en = list(dic_content.keys())
keys_en.sort()
count = 0
# print('输入长度统计:')
for k in keys_en:
    count += dic_content[k]
print(k,dic_content[k],count/len(lines))

keys_cn = list(dic_summary.keys())
keys_cn.sort()
count = 0
# print('输出长度统计:')
for k in keys_cn:
    count += dic_summary[k]
print(k,dic_summary[k],count/len(lines))
 
en_length = 95
cn_length = 25

输出结果如下图2所示:

【自然语言处理(NLP)】基于Transformer的英文自动文摘


(二)、构建英文词表词表

# 构建英文词表
en_vocab = {}

en_vocab['<pad>'], en_vocab['<bos>'], en_vocab['<eos>'] = 0, 1, 2

idx = 3
for en, cn in datas:
    # print(en,cn)
    for w in en:
        if w not in en_vocab:
            en_vocab[w] = idx
            idx += 1
    for w in cn:
        if w not in en_vocab:
            en_vocab[w] = idx
            idx += 1

print(len(list(en_vocab)))

'''
英文词表长度:71428
'''

输出结果如下图3所示:

【自然语言处理(NLP)】基于Transformer的英文自动文摘


(三)、创建数据集

接下来根据词表,我们将会创建一份实际的用于训练的用numpy array组织起来的数据集。

  • 所有的句子都通过补充成为了长度相同的句子。
  • 对于英文句子(源语言),我们将其反转了过来,这会带来更好的翻译的效果。
  • 所创建的padded_cn_label_sents是训练过程中的预测的目标,即,每个英文的当前词去预测下一个词是什么词。

padded_en_sents = []
padded_cn_sents = []
padded_cn_label_sents = []
for en, cn in datas:
    if len(en)>en_length:
        en = en[:en_length]
    if len(cn)>cn_length:
        cn = cn[:cn_length]
    padded_en_sent = en + ['<eos>'] + ['<pad>'] * (en_length - len(en))
    #padded_en_sent.reverse()

    padded_cn_sent = ['<bos>'] + cn + ['<eos>'] + ['<pad>'] * (cn_length - len(cn))
    padded_cn_label_sent = cn + ['<eos>'] + ['<pad>'] * (cn_length - len(cn) + 1)
    
    padded_en_sents.append(np.array([en_vocab[w] for w in padded_en_sent]))
    padded_cn_sents.append(np.array([en_vocab[w] for w in padded_cn_sent]) )
    padded_cn_label_sents.append(np.array([en_vocab[w] for w in padded_cn_label_sent]))

train_en_sents = np.array(padded_en_sents)
train_cn_sents = np.array(padded_cn_sents)
train_cn_label_sents = np.array(padded_cn_label_sents)
 
print(train_en_sents.shape)
print(train_cn_sents.shape)
print(train_cn_label_sents.shape)

输出结果如下图4所示:

【自然语言处理(NLP)】基于Transformer的英文自动文摘


二、构建基于Transformer的机器翻译模型

(一)、定义超参数

  • 首先定义超参数,用于后续模型的设计与训练

embedding_size = 128
hidden_size = 512
num_encoder_lstm_layers = 1
vocab_size = len(list(en_vocab))
epochs = 20
batch_size = 16

(二)、Encoder部分

  • 使用TransformerEncoder定义Encoder

# encoder: simply learn representation of source sentence
class Encoder(paddle.nn.Layer):
    def __init__(self,vocab_size, embedding_size,num_layers=2,head_number=2,middle_units=512):
        super(Encoder, self).__init__()
        self.emb = paddle.nn.Embedding(vocab_size, embedding_size,)
        """
        d_model (int) - 输入输出的维度。
        nhead (int) - 多头注意力机制的Head数量。
        dim_feedforward (int) - 前馈神经网络中隐藏层的大小。
        """
        encoder_layer = paddle.nn.TransformerEncoderLayer(embedding_size, head_number, middle_units)
        self.encoder = paddle.nn.TransformerEncoder(encoder_layer, num_layers) 

    def forward(self, x):
        x = self.emb(x)
        en_out = self.encoder(x)
        return en_out

(三)、Decoder部分

  • 使用TransformerDecoder定义Decoder

class Decoder(paddle.nn.Layer):
    def __init__(self,vocab_size, embedding_size,num_layers=2,head_number=2,middle_units=512):
        super(Decoder, self).__init__()
        self.emb = paddle.nn.Embedding(vocab_size, embedding_size)
        
        decoder_layer = paddle.nn.TransformerDecoderLayer(embedding_size, head_number, middle_units)
        self.decoder = paddle.nn.TransformerDecoder(decoder_layer, num_layers) 
   
        # for computing output logits
        self.outlinear =paddle.nn.Linear(embedding_size, vocab_size)

    def forward(self, x,  encoder_outputs):
        x = self.emb(x)
        # dec_input, enc_output,self_attn_mask,  cross_attn_mask
        de_out = self.decoder(x, encoder_outputs)
        output = self.outlinear(de_out)
        output = paddle.squeeze(output)
        return  output

三、模型训练

接下来我们开始训练模型。

  • 在每个epoch开始之前,我们对训练数据进行了随机打乱。

  • 我们通过多次调用atten_decoder,在这里实现了解码时的recurrent循环。

  • teacher forcing策略: 在每次解码下一个词时,我们给定了训练数据当中的真实词作为了预测下一个词时的输入。相应的,你也可以尝试用模型预测的结果作为下一个词的输入。(或者混合使用)


encoder = Encoder(vocab_size, embedding_size)
decoder = Decoder(vocab_size, embedding_size)

opt = paddle.optimizer.Adam(learning_rate=0.0001,
                            parameters=encoder.parameters() + decoder.parameters())

for epoch in range(epochs):
    print("epoch:{}".format(epoch))

    # shuffle training data
    perm = np.random.permutation(len(train_en_sents))
    train_en_sents_shuffled = train_en_sents[perm]
    train_cn_sents_shuffled = train_cn_sents[perm]
    train_cn_label_sents_shuffled = train_cn_label_sents[perm]
    # print(train_en_sents_shuffled.shape[0],train_en_sents_shuffled.shape[1])
    for iteration in range(train_en_sents_shuffled.shape[0] // batch_size):
        x_data = train_en_sents_shuffled[(batch_size*iteration):(batch_size*(iteration+1))]
        sent = paddle.to_tensor(x_data)
        en_repr = encoder(sent)

        x_cn_data = train_cn_sents_shuffled[(batch_size*iteration):(batch_size*(iteration+1))]
        x_cn_label_data = train_cn_label_sents_shuffled[(batch_size*iteration):(batch_size*(iteration+1))]
 
        loss = paddle.zeros([1]) 
        for i in range( cn_length + 2):
            cn_word = paddle.to_tensor(x_cn_data[:,i:i+1])
            cn_word_label = paddle.to_tensor(x_cn_label_data[:,i])

            logits = decoder(cn_word, en_repr)
            step_loss = F.cross_entropy(logits, cn_word_label)
            loss += step_loss

        loss = loss / (cn_length + 2)
        if(iteration % 50 == 0):
            print("iter {}, loss:{}".format(iteration, loss.numpy()))

        loss.backward()
        opt.step()
        opt.clear_grad()
print("训练完成")

部分输出结果如下图6所示:

【自然语言处理(NLP)】基于Transformer的英文自动文摘


四、使用模型进行机器翻译

  • 随机从训练集中抽取几句话来进行预测

encoder.eval()
decoder.eval()

num_of_exampels_to_evaluate = 10

indices = np.random.choice(len(train_en_sents),  num_of_exampels_to_evaluate, replace=False)
x_data = train_en_sents[indices]
sent = paddle.to_tensor(x_data)
en_repr = encoder(sent)

word = np.array(
    [[en_vocab['<bos>']]] * num_of_exampels_to_evaluate
)
word = paddle.to_tensor(word)
 

decoded_sent = []
for i in range(cn_length + 2):
    logits  = decoder(word, en_repr)
    word = paddle.argmax(logits, axis=1)
    decoded_sent.append(word.numpy())
    word = paddle.unsqueeze(word, axis=-1)

results = np.stack(decoded_sent, axis=1)

for i in range(num_of_exampels_to_evaluate):
    print('---------------------')
    en_input = " ".join(datas[indices[i]][0])
    ground_truth_translate = "".join(datas[indices[i]][1])
    model_translate = ""
    for k in results[i]:
        w = list(en_vocab)[k]
        if w != '<pad>' and w != '<eos>':
            model_translate += w
    print(en_input)
    print("true: {}".format(ground_truth_translate))
    print("pred: {}".format(model_translate))


输出结果如下图7所示:

【自然语言处理(NLP)】基于Transformer的英文自动文摘


总结

本系列文章内容为根据清华社出版的《自然语言处理实践》所作的相关笔记和感悟,其中代码均为基于百度飞桨开发,若有任何侵权和不妥之处,请私信于我,定积极配合处理,看到必回!!!

最后,引用本次活动的一句话,来作为文章的结语~( ̄▽ ̄~)~:

【**学习的最大理由是想摆脱平庸,早一天就多一份人生的精彩;迟一天就多一天平庸的困扰。**】

【自然语言处理(NLP)】基于Transformer的英文自动文摘