Pytorch版

Readme

结构说明

  • data_precession.py:把数据整合为DataFrame格式
  • predict.py:预测
  • run_log.txt:记录每次运行的日志【时间-最佳分数-模型位置】
  • train_eval.py:训练和验证主文件
  • utils.py:工具类,各种超参数和路径

  • data :存放数据文件夹

  • models:存放各类Bert模型文件夹
  • pretrain_model:预训练模型存放文件夹
  • save_model:保存最佳模型文件夹

文件结构

  1. data_precession.py
  2. predict.py
  3. run_log.txt
  4. train_eval.py
  5. utils.py
  6. ├─data
  7. └─sougou_mini
  8. all_data_df.csv
  9. current_label.json
  10. test.csv
  11. train.csv
  12. ├─models
  13. transformers_bert.py
  14. ├─pretrain_model
  15. └─chinese-bert-base
  16. config.json
  17. pytorch_model.bin
  18. tokenizer.json
  19. tokenizer_config.json
  20. vocab.txt
  21. ├─save_model
  22. bert_model.pth
  23. model.pth

如何快速使用hugging进行训练并预测,如下:

[5]快速使用hugging进行NLP任务

预测代码和结果

  1. #!/usr/bin/env python
  2. # -*- encoding: utf-8 -*-
  3. '''
  4. @File : predict.py
  5. @Contact : htkstudy@163.com
  6. @Modify Time @Author @Version @Desciption
  7. ------------ ------- -------- -----------
  8. 2021/3/26 18:03 Armor(htk) 1.0 None
  9. '''
  10. import json
  11. from Bert_Text_Classifier.utils import *
  12. from Bert_Text_Classifier.train_eval import *
  13. from Bert_Text_Classifier.models.transformers_bert import Bert_Base_Model
  14. def test(model, dataloader, return_pred_df=False):
  15. # load model
  16. checkpoint = torch.load(output_model_path, map_location='cpu')
  17. model.load_state_dict(checkpoint['model_state_dict'])
  18. model.to(device)
  19. # 开始预测
  20. print('-----Testing-----')
  21. pred_label = []
  22. model.eval()
  23. for i, batch in enumerate(tqdm(dataloader)):
  24. batch = tuple(t.to(device) for t in batch)
  25. with torch.no_grad():
  26. logits = model(batch[0], batch[1], batch[2])
  27. logits = logits.detach().cpu().numpy()
  28. preds = np.argmax(logits, axis=1).flatten()
  29. pred_label.extend(preds)
  30. # 预测
  31. pred_df = pd.DataFrame({"class_label":pred_label})
  32. pred_df.to_csv('pred.csv', index=False,encoding='utf-8')
  33. print('Test Completed')
  34. if return_pred_df:
  35. return pred_df
  36. if __name__ == '__main__':
  37. print("Reading test data...")
  38. test_df = pd.DataFrame({"content":["本报讯经过两周的强化训练,改造后的建业队进攻体系今天下午将全新亮相,能否在主场如愿全取3分,打响5月“魔鬼赛程”第一仗,就看建业队中前场的进攻火力是否威猛了。",
  39. "《血吸虫病防治条例》(以下简称《条例》)已经公布。为了便于大家理解《条例》有关精神,中国政府网记者对国务院法制办负责人进行了访谈。",
  40. "新华网消息据俄塔社日前报道,俄罗斯战略火箭军总司令索洛夫佐夫在接受《红星报》采访时说,“白杨—M”洲际弹道导弹近期内将会安装分导弹头。",
  41. "四川省教育厅于日前下发了《四川省实施中职招生“阳光工程”六条规定》,其中明确指出,严禁用经济手段组织生源,个人、生源学校、招生学校不得'买卖学生',严厉打击非法中介、招生贩子。",
  42. "亚之杰汽车贸易有限责任公司随着中国新贵一族的兴起,豪车离我们的真实生活似乎也越来越近。目前,诸多品牌豪车已经相继亮相于北京市场,哪些车型值得选择?不妨一探究竟。"],
  43. "class_label":[0,1,2,3,4]})
  44. print(test_df)
  45. test_set = CustomDataset(test_df, maxlen=192, with_labels=False, model_name=model_name)
  46. val_loader = Data.DataLoader(test_set, batch_size=1, num_workers=0, shuffle=False)
  47. model = Bert_Base_Model(freeze_bert=False, model_name=model_name, hidden_size=768, num_classes=num_labels)
  48. pred_df = test(model, val_loader, return_pred_df=True)
  49. print("打印预测的前10条文本")
  50. # JSON到字典转化
  51. c2label = json.load(open('./data/sougou_mini/current_label.json', 'r',encoding='utf-8'))
  52. # 显示数据类型
  53. for text,label in zip(test_df["content"],pred_df["class_label"]):
  54. print("文本:%s \n预测标签:%s %s \n" %(text,label,c2label[str(label)]))

训练环境为sougou文本分类数据集,共5个分类【体育,健康,教育,军事,汽车】
最佳分数 bert_best_score = 0.824

image.png

Tensorflow版

参考:tensorflow 2.0+ 基于BERT模型的文本分类
文章有部分代码有错误,以及TF2参数的变更。

简介

基于 RNNs/LSTMs 的方法

大多数较旧的语言建模方法都基于 RNN(recurrent neural network)。简单的 RNN 存在梯度消失/梯度爆炸问题,所以无法对较长的上下文依赖关系进行建模。它们大多数被所谓的长短期记忆网络模型(LSTMs) 所取代, 该神经网络也是 RNN 的一个变种形式,但可以捕获文档中较长的上下文。然而,LSTM 只能单向处理序列,因此基于 LSTM 的最先进方法演变为双向 LSTM,此结构可以从左到右以及从右到左读取上下文。基于LSTM有非常成功的模型,如ELMO或 ULMFIT,这些模型仍然适用于现在的NLP任务。

基于transformers架构的方法

双向 LSTM 的主要限制之一是其顺序性,这使得并行训练非常困难, transformer 架构通过注意力机制(Vashvani et al. 2017) 完全取代LSTM来解决这一个问题。在注意力机制中,我们把整个序列看作一个整体, 因此并行训练要容易得多。我们可以对整个文档上下文进行建模,并使用大型数据集以无人监督学习的方式进行预训练,并微调下游任务。

最先进的transformers模型

有很多基于transformers的语言模型。最成功的是以下这些(截至2020年4月)

这些模型之间略有差异,而BERT一直被认为是许多 NLP 任务中最先进的模型。但现在看来,它已被同样来自谷歌的 XLNet 所超越。XLNet 利用置换语言建模,该模型对句子中所有可能的单词排列进行自动回归模型。我们将在本文中使用基于 BERT 的语言模型。

BERT

BERT (Bidirectional Encoder Representations from Transformers) (Devlint et al., 2018) 是一种预训练语言表示的方法。我们不会讨论太多细节,但与原始transformers (Vaswani et al., 2017) 的主要区别是, BERT没有解码器, 但在基本版本中堆叠了12个编码器,而且在更大的预训练模型中会增加编码器的数量。这种架构不同于 OpenAI 的 GPT-2,它是适合自然语言生成 (NLG) 的自回归语言模型。

Tokenizer

官方 BERT 语言模型是使用切片词汇预训练与使用, 不仅token 嵌入, 而且有区分成对序列的段嵌入, 例如问答系统。由于注意力机制在上下文评估中不考虑位置,因此需要把位置信息嵌入才能将位置感知注入 BERT 模型。
需要注意的是,BERT限制序列的最大长度为 512 个token。对于比最大允许输入短的序列,我们需要添加 [PAD],另一方面,如果序列更长,我们需要剪切序列。对于较长的文本段,您需要了解此对序列最大长度的 BERT 限制,请参阅此 GitHub issue 以了解进一步的解决方案。
非常重要的还有所谓的特殊token,例如 [CLS] token和 [SEP] token。[CLS] token将插入序列的开头,[SEP] token位于末尾。如果我们处理序列对,我们将在最后一个序列对的末尾添加额外的 [SEP] token。

使用transformers库时,我们首先加载要使用的模型的标记器。然后,我们将按如下方式进行:

  1. from transformers import BertTokenizer
  2. # 调用transformers 的tokenizer
  3. tokenizer = BertTokenizer.from_pretrained(args.model_path)
  4. # 虚构样本
  5. max_len = 96
  6. test_sequence = "曝梅西已通知巴萨他想离开"
  7. # add special tokens 所谓的特殊token
  8. test_sentence_with_special_tokens = '[CLS]' + test_sequence + '[SEP]'
  9. tokenized = tokenizer.tokenize(test_sentence_with_special_tokens)
  10. print('tokenized', tokenized)
  11. # convert tokens to ids in WordPiece
  12. input_ids = tokenizer.convert_tokens_to_ids(tokenized)
  13. # precalculation of pad length, so that we can reuse it later on
  14. padding_length = args.max_len - len(input_ids)
  15. # 这边和参考内容不一样,参考内容的attention_mask代码顺序出错了
  16. # attention should focus just on sequence with non padded tokens
  17. attention_mask = [1] * len(input_ids)
  18. # map tokens to WordPiece dictionary and add pad token for those text shorter than our max length
  19. input_ids = input_ids + ([0] * padding_length)
  20. # attention should focus just on sequence with non padded tokens
  21. attention_mask = attention_mask + ([0] * padding_length)
  22. # do not focus attention on padded tokens
  23. token_type_ids = [0] * args.max_len
  24. # token types, needed for example for question answering, for our purpose we will just set 0 as we have just one sequence
  25. bert_input = {
  26. "token_ids": input_ids,
  27. "token_type_ids": token_type_ids,
  28. "attention_mask": attention_mask
  29. }
  30. print('encoded', bert_input)

OUTPUT:

  1. tokenized ['[CLS]', '曝', '梅', '西', '已', '通', '知', '巴', '萨', '他', '想', '离', '开', '[SEP]']
  2. encoded {'token_ids': [101, 3284, 3449, 6205, 2347, 6858, 4761, 2349, 5855, 800, 2682, 4895, 2458, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}

在实际编码中,我们将只是用tokenizer.encode_plus函数,为我们完成上述所有步骤

  1. bert_inputs = tokenizer.encode_plus(
  2. test_sequence,
  3. padding='max_length',
  4. truncation=True,
  5. # add_special_tokens=True, # add [CLS], [SEP]
  6. max_length=args.max_len, # max length of the text that can go to BERT
  7. pad_to_max_length=True, # add [PAD] tokens
  8. return_attention_mask=True, # add attention mask to not focus on pad tokens
  9. )
  10. print('encoded', bert_input)

预训练

预训练是BERT训练的第一阶段,它以无监督的方式完成,由两个主要任务组成:

  • masked language modelling (MLM)
  • next sentence prediction (NSP)

从高级别开始,在 MLM 任务中,我们用 [MASK] token替换序列中的一定数量的token。然后我们尝试预测掩蔽的token,MLM 有一些额外的规则,所以描述不完全精确,请查看原始论文(Devlin et al., 2018)以了解更多详细信息。
当我们选择句子对为下一个句子预测,我们将选择上一个句子之后的实际句子的50%标记为IsNext,其他 50% 我们从语料库中选择另一个句子, 与上一个句子无关,标记为NotNext。
这两个任务都可以在文本语料库上执行,而无需标记样本,因此作者使用了诸如BooksCorpus (800m words), English Wikipedia (2500m words)等数据集。

微调(Fine-tuning)

一旦我们自己预训练了模型,或者加载了已预训练过的模型(例如BERT-based-uncased、BERT-based-chinese),我们就可以开始对下游任务(如问题解答或文本分类)的模型进行微调。我们可以看到,BERT 可以将预训练的 BERT 表示层嵌入到许多特定任务中,对于文本分类,我们将只在顶部添加简单的 softmax 分类器。预训练阶段需要显著的计算能力 (BERT base: 4 days on 16 TPUs; BERT large 4 days on 64 TPUs)。所以保存预训练的模型,然后微调一个特定的数据集非常有用。与预训练不同,微调不需要太多的计算能力,即使在单个 GPU 上,也可以在几个小时内完成微调过程。当对文本分类进行微调时,我们可以选择几个方式,请参阅下图 (Sun et al. 2019)