课程简介
“手把手带你学NLP”是基于飞桨PaddleNLP的系列实战我的项目。本系列由百度多位资深工程师精心打造,提供了从词向量、预训练语言模型,到信息抽取、情感剖析、文本问答、结构化数据问答、文本翻译、机器同传、对话零碎等实际我的项目的全流程解说,旨在帮忙开发者更全面清晰地把握百度飞桨框架在NLP畛域的用法,并可能触类旁通、灵便应用飞桨框架和PaddleNLP进行NLP深度学习实际。
6月,百度飞桨 & 自然语言解决部携手推出了12节NLP视频课,课程中具体解说了本实际我的项目。
观看课程回放请戳:https://aistudio.baidu.com/ai...
欢送来课程QQ群(群号:758287592)交换吧~~
背景介绍
机器翻译是利用计算机将一种自然语言(源语言)转换为另一种自然语言(目标语言)的过程。
本我的项目是机器翻译畛域支流模型 Transformer 的 PaddlePaddle 实现,快来基于此我的项目搭建本人的翻译模型吧。
Transformer 是论文《 Attention Is All You Need 》中提出的用以实现机器翻译(Machine Translation)等序列到序列(Seq2Seq)学习工作的一种全新网络结构,其齐全应用注意力(Attention)机制来实现序列到序列的建模。
图1:Transformer 网络结构图
相较于此前 Seq2Seq 模型中宽泛应用的循环神经网络(Recurrent Neural Network, RNN),应用Self Attention进行输出序列到输入序列的变换次要具备以下劣势:
计算复杂度小
特色维度为 d 、长度为 n 的序列,在 RNN 中计算复杂度为 O(n d d) (n 个工夫步,每个工夫步计算 d 维的矩阵向量乘法),在Transformer中计算复杂度为 O(n n d) (n 个工夫步两两计算 d 维的向量点积或其余相关度函数),n 通常要小于 d 。
计算并行度高
RNN 中以后工夫步的计算要依赖前一个工夫步的计算结果;Self-Attention 中各工夫步的计算只依赖输出,不依赖之前工夫步输入,各工夫步能够齐全并行。
容易学习长距离依赖(long-range dependencies)
RNN 中相距为 n 的两个地位间的关联须要 n 步能力建设;Self-Attention 中任何两个地位都间接相连;门路越短信号流传越容易。Transformer 构造已被广泛应用在 Bert 等语义示意模型中,获得了显著效果。
疾速实际
本示例展现了以Transformer为代表的预训练模型如何Finetune实现机器翻译工作。
我的项目基于飞桨PaddleNLP编写,GitHub地址:
https://github.com/PaddlePaddle/PaddleNLP
PaddleNLP官网文档:
https://paddlenlp.readthedocs.io
残缺代码请戳:
https://github.com/PaddlePaddle/PaddleNLP/tree/develop/examples/machine_translation/transformer
深度学习工作Pipeline
图2:深度学习工作Pipeline
2.1 数据预处理
本教程应用CWMT数据集中的中文英文的数据作为训练语料, CWMT数据集蕴含900万+样本,品质较高,非常适合来训练Transformer机器翻译模型。
中文须要Jieba+BPE,英文须要BPE。
BPE(Byte Pair Encoding)
BPE劣势:
压缩词表;
肯定水平上缓解OOV(out of vocabulary)问题。
图3:learn BPE
图4:Apply BPE
# 自定义读取本地数据的办法def read(src_path, tgt_path, is_predict=False): # 是否为测试集,测试集tgt为空if is_predict: with open(src_path, 'r', encoding='utf8') as src_f: for src_line in src_f.readlines(): src_line = src_line.strip() if not src_line: continue yield {'src':src_line, 'tgt':''}else: with open(src_path, 'r', encoding='utf8') as src_f, open(tgt_path, 'r', encoding='utf8') as tgt_f: for src_line, tgt_line in zip(src_f.readlines(), tgt_f.readlines()): src_line = src_line.strip() if not src_line: continue tgt_line = tgt_line.strip() if not tgt_line: continue yield {'src':src_line, 'tgt':tgt_line}# 过滤掉长度 ≤min_len或者≥max_len 的数据 def min_max_filer(data, max_len, min_len=0): # 获取每条src和tgt的最小长度和最大长度(+1是为了或者),过滤掉不满足长度范畴的样本. data_min_len = min(len(data[0]), len(data[1])) + 1 data_max_len = max(len(data[0]), len(data[1])) + 1 return (data_min_len >= min_len) and (data_max_len <= max_len)# 数据预处理过程,包含jieba分词、bpe分词和词表。!bash preprocess.sh
2.2 结构Dataloader
咱们定义create_data_loader函数,用来创立训练集、验证集所须要的DataLoader对象。
DataLoader对象用于产生一个个batch的数据。上面对函数中调用的PaddleNLP内置函数作简略阐明:
paddlenlp.data.Vocab.load_vocabulary:Vocab词表类,汇合了一系列文本token与ids之间映射的一系列办法,反对从文件、字典、json等一系形式构建词表
paddlenlp.datasets.load_dataset:从本地文件创建数据集时,举荐依据本地数据集的格局给出读取function并传入 load_dataset()中创立数据集
paddlenlp.data.Pad:padding 操作,用于对齐同一batch内的句子长度。
图6:结构Dataloader的流程
图7:Dataloader细节
# 创立训练集、验证集的dataloader。测试集的dataloader相似。def create_data_loader(args):# 通过paddlenlp.datasets.load_dataset从本地文件创建数据集:依据本地数据集的格局给出读取function并传入 load_dataset()中创立数据集train_dataset = load_dataset(read, src_path=args.training_file.split(',')[0], tgt_path=args.training_file.split(',')[1], lazy=False)dev_dataset = load_dataset(read, src_path=args.training_file.split(',')[0], tgt_path=args.training_file.split(',')[1], lazy=False)# 通过Paddlenlp.data.Vocab.load_vocabulary从本地创立词表src_vocab = Vocab.load_vocabulary( args.src_vocab_fpath, bos_token=args.special_token[0], eos_token=args.special_token[1], unk_token=args.special_token[2])trg_vocab = Vocab.load_vocabulary( args.trg_vocab_fpath, bos_token=args.special_token[0], eos_token=args.special_token[1], unk_token=args.special_token[2])# 将词表的大小补足为pad_factor的倍数,为了Tranformer的减速。padding_vocab = ( lambda x: (x + args.pad_factor - 1) // args.pad_factor * args.pad_factor)args.src_vocab_size = padding_vocab(len(src_vocab))args.trg_vocab_size = padding_vocab(len(trg_vocab))def convert_samples(sample): source = sample['src'].split() target = sample['tgt'].split() # 将tokens转化为词表对应的ids source = src_vocab.to_indices(source) target = trg_vocab.to_indices(target) return source, target# 训练集dataloader和验证集dataloaderdata_loaders = []for i, dataset in enumerate([train_dataset, dev_dataset]):# 通过Dataset的map办法将样本token转换为id;通过Dataset的filter办法过滤掉不符合条件的样本 dataset = dataset.map(convert_samples, lazy=False).filter( partial(min_max_filer, max_len=args.max_length)) # 批采样器BatchSampler组batch batch_sampler = BatchSampler(dataset,batch_size=args.batch_size, shuffle=True,drop_last=False) # 结构Dataloader用于后续迭代取数据进行训练/验证/测试 data_loader = DataLoader( dataset=dataset, batch_sampler=batch_sampler, collate_fn=partial( prepare_train_input, bos_idx=args.bos_idx, eos_idx=args.eos_idx, pad_idx=args.bos_idx), num_workers=0, return_list=True) data_loaders.append(data_loader)return data_loadersdef prepare_train_input(insts, bos_idx, eos_idx, pad_idx): # 通过paddlenlp.data.Pad来padding,用于对齐同一batch中样本的长度word_pad = Pad(pad_idx)src_word = word_pad([inst[0] + [eos_idx] for inst in insts])trg_word = word_pad([[bos_idx] + inst[1] for inst in insts])# 扩大维度用于后续计算Losslbl_word = np.expand_dims( word_pad([inst[1] + [eos_idx] for inst in insts]), axis=2)data_inputs = [src_word, trg_word, lbl_word]return data_inputs
2.3 搭建模型
PaddleNLP提供Transformer API供调用:
paddlenlp.transformers.TransformerModel:Transformer模型的实现
paddlenlp.transformers.InferTransformerModel:Transformer模型用于生成工作
paddlenlp.transformers.CrossEntropyCriterion:计算穿插熵损失
paddlenlp.transformers.position_encoding_init:Transformer 地位编码的初始化
图8:模型搭建
图9:Encoder-Decoder示意图
2.4 训练模型
运行do_train函数, 在do_train函数中,配置优化器、损失函数,以及评估指标Perplexity;
Perplexity,即困惑度,罕用来掂量语言模型优劣,即句子的通顺度,个别用于机器翻译和文本生成等畛域。Perplexity越小,句子越通顺,该语言模型越好。
图10:训练模型
def do_train(args):random_seed = eval(str(args.random_seed))if random_seed is not None: paddle.seed(random_seed)# 获取Dataloader(train_loader), (eval_loader) = create_data_loader(args)# 申明模型transformer = TransformerModel( src_vocab_size=args.src_vocab_size, trg_vocab_size=args.trg_vocab_size, max_length=args.max_length + 1, n_layer=args.n_layer, n_head=args.n_head, d_model=args.d_model, d_inner_hid=args.d_inner_hid, dropout=args.dropout, weight_sharing=args.weight_sharing, bos_id=args.bos_idx, eos_id=args.eos_idx)# 定义Losscriterion = CrossEntropyCriterion(args.label_smooth_eps, args.bos_idx) # 定义学习率的衰减策略scheduler = paddle.optimizer.lr.NoamDecay( args.d_model, args.warmup_steps, args.learning_rate, last_epoch=0)# 定义优化器optimizer = paddle.optimizer.Adam( learning_rate=scheduler, beta1=args.beta1, beta2=args.beta2, epsilon=float(args.eps), parameters=transformer.parameters())step_idx = 0# 按epoch迭代训练for pass_id in range(args.epoch): batch_id = 0 for input_data in train_loader: # 从训练集Dataloader按batch取数据 (src_word, trg_word, lbl_word) = input_data # 取得模型输入的logits logits = transformer(src_word=src_word, trg_word=trg_word) # 计算loss sum_cost, avg_cost, token_num = criterion(logits, lbl_word) # 计算梯度 avg_cost.backward() # 更新参数 optimizer.step() # 梯度清零 optimizer.clear_grad() batch_id += 1 step_idx += 1 scheduler.step() do_train(args) [2021-06-18 22:38:55,597] [ INFO] - step_idx: 0, epoch: 0, batch: 0, avg loss: 10.513082, ppl: 36793.687500 [2021-06-18 22:38:56,783] [ INFO] - step_idx: 9, epoch: 0, batch: 9, avg loss: 10.506249, ppl: 36543.164062 [2021-06-18 22:38:58,032] [ INFO] - step_idx: 19, epoch: 0, batch: 19, avg loss: 10.464736, ppl: 35057.187500 [2021-06-18 22:38:59,032] [ INFO] - validation, step_idx: 19, avg loss: 10.454649, ppl: 34705.347656
2.5 预测和评估
模型最终训练的成果个别可通过测试集来进行测试,机器翻译畛域个别计算BLEU值。
预测后果中每行输入是对应行输出的得分最高的翻译,对于应用 BPE 的数据,预测出的翻译后果也将是 BPE 示意的数据,要还原成原始的数据(这里指 tokenize 后的数据)能力进行正确的评估。
图11:预测和评估
入手试一试
是不是感觉很乏味呀。小编强烈建议初学者参考下面的代码亲手敲一遍,因为只有这样,能力加深你对代码的了解呦。
本次我的项目对应的代码:
https://aistudio.baidu.com/ai...
来定制本人的翻译零碎吧。
更多PaddleNLP信息,欢送拜访GitHub点star珍藏后体验:
https://github.com/PaddlePadd...