"""
案例:
通过自动模型的方式 完成 NLP常见的6个需求.
我们具体要讲解的6个NLP任务:
1. 文本分类 -> 情感分析
2. 特征抽取
3. 完形填空
4. 阅读理解
5. 文本摘要
6. NER命名实体识别
"""
import warnings
warnings.filterwarnings("ignore") # 忽略警告.
# 导包
import torch # Pytorch深度学习框架, 包括: 张量计算, 自动求导...
# AutoConfig: 自动配置模型参数.
# AutoModel: 自动加载模型 -> 通用的模型加载类.
# AutoTokenizer: 自动加载 (和模型匹配的) 分词器
from transformers import AutoConfig, AutoModel, AutoTokenizer
# AutoModelForSequenceClassification: 用于文本分类的模型.
# AutoModelForMaskedLM: 用于掩码语言建模的模型.
# AutoModelForQuestionAnswering: 用于阅读理解(问答)的模型.
from transformers import AutoModelForSequenceClassification, AutoModelForMaskedLM, AutoModelForQuestionAnswering
# AutoModelForSeq2SeqLM: 用于文本摘要的模型.
# AutoModelForTokenClassification: 用于命名实体识别(token级别分类)的模型.
from transformers import AutoModelForSeq2SeqLM, AutoModelForTokenClassification
from rich import print # 美化输出. 需要额外安装. 例如: pip install rich==15.0.0
# todo 1. 情感分类任务.
# 情绪分类 -> '今天开心极了...' 积极的情绪
# 商品分类 -> '手机壳' -> 数码配件, '连衣裙' -> 服饰
def dm01_text_classification():
# 1. 加载Tokenizer.
# my_tokenizer: 分词器对象, 把文本 -> 转换成 模型可接受的输入格式.
my_tokenizer = AutoTokenizer.from_pretrained(r'C:\Software\CentOS_All\PretrainedModel\chinese_sentiment')
# 2. 加载模型.
my_model = AutoModelForSequenceClassification.from_pretrained(r'C:\Software\CentOS_All\PretrainedModel\chinese_sentiment')
# 3. 文本转张量.
message = '人生该如何起头'
# 思路1: return_tensors = 'pt', 返回的是: 二维张量(tensor)
# 参1 text: 待编码的文本.
# 参2 return_tensors: 返回张量类型, 默认为None, 可选: pt / tf / np
# 参3 padding: 填充方式(是否填充)
# 参4 truncation: 截断方式(是否截断)
# 参5 max_length: (指定输入的)最大长度
msg_tensor1 = my_tokenizer.encode(text=message, return_tensors='pt', padding='max_length', truncation=True, max_length=5)
print(f'msg_tensor1: {msg_tensor1}') # msg_tensor1: tensor([[ 101, 782, 4495, 6421, 102]])
# 思路2: 不用return_tensors = 'pt', 返回的是: 1维列表, 需要手动转成张量.
msg_list2 = my_tokenizer.encode(text=message, padding='max_length', truncation=True, max_length=5)
# print(f'msg_list2: {msg_list2}') # [ 101, 782, 4495, 6421, 102]
# 手动把上述的列表转成 二维张量.
msg_tensor2 = torch.tensor([msg_list2])
# print(f'msg_tensor2: {msg_tensor2}') # tensor([[ 101, 782, 4495, 6421, 102]])
# 4. 将数据输入模型进行推理.
# 4.1 设置模型为: 推理模式(评估模式) -> 会关闭dropout等训练特有的层.
my_model.eval()
# 4.2 直接调用模型(模型推理)
# 结果格式: SequenceClassifierOutput(loss=None, logits=tensor([[-2.6553, -1.7053, 0.0367, 1.9910, 1.4494]], grad_fn=<AddmmBackward0>), hidden_states=None, attentions=None)
result = my_model(msg_tensor2)
print(f'result(包含模型输出的字典): {result}')
# 4.3 使用 return_dict=False 可以返回元组.
# 结果格式: (tensor([[-2.6553, -1.7053, 0.0367, 1.9910, 1.4494]], grad_fn=<AddmmBackward0>),)
result = my_model(msg_tensor2, return_dict=False)
print(f'result(包含模型输出的元组): {result}')
# 5. 上述获取的是 5个分类的概率, 我们需要获取 最大概率对应的索引.
# result_index = torch.argmax(result.logits) # 针对于字典(返回值写法)
result_index = torch.argmax(result[0]) # 针对于元组(返回值写法)
# 最终分类结果, 这里的3(索引)表示: 第4个分类.
print(f'result_index: {result_index}')
print(f'最终分类: star {result_index + 1}')
# todo 2. 特征抽取任务.
def dm02_feature_extraction():
# 1. 加载Tokenizer.
my_tokenizer = AutoTokenizer.from_pretrained(r'C:\Software\CentOS_All\PretrainedModel\bert-base-chinese')
# 2. 加载模型.
my_model = AutoModel.from_pretrained(r'C:\Software\CentOS_All\PretrainedModel\bert-base-chinese')
# 3. 文本转张量.
# 3.1 定义变量, 记录: 待处理的文本.
message = ['你是谁', '人生该如何起头']
# 3.2 把上述的文本 转成 模型可接受的输入张量.
# 返回值是字典形式, 其中各个键的意思是:
# input_ids: 文本的id序列 -> 对应模型的 vocab.txt 文件.
# token_type_ids: 文本的token序列.
# attention_mask: 注意力掩码, 1(真实), 0(填充)
msg_tensor = my_tokenizer.encode_plus(
text = message, # 待处理的文本.
return_tensors='pt', # 返回张量类型, 默认: None, 可选: pt / tf / np
padding='max_length', # 填充方式(是否填充)
truncation=True, # 截断方式(是否截断)
max_length=30, # (指定输入的)最大长度, 一会儿得到句子内容: [CLS] + 句子1 + [SEP] + 句子2 + [SEP] + 填充
)
# msg_tensor: {'input_ids': tensor([[101, 872, 102, 782, 102]]), 'token_type_ids': tensor([[0, 0, 0, 1, 1]]),
# 'attention_mask': tensor([[1, 1, 1, 1, 1]])}
print(f'msg_tensor: {msg_tensor}')
# 4. 给模型喂数据, 提取特征.
my_model.eval()
result = my_model(**msg_tensor)
# 5. 打印结果.
print(f'result: \n{result}')
# 最后1个(隐藏状态)层输出 -> [batch_size 批次大小, seq_len 序列长度, hidden_size 隐藏层的维度]
print(f'result.last_hidden_state: \n{result.last_hidden_state.shape}') # [1, 30, 768]
# 池化层输出 -> [batch_size 批次大小, hidden_size 隐藏层的维度]
print(f'result.pooler_output: \n{result.pooler_output.shape}') # [1, 768]
# todo 3. 完形填空任务.
def dm03_fill_mask():
# 1. 定义变量, 记录: 模型名.
model_name = r'C:\Software\CentOS_All\PretrainedModel\chinese-bert-wwm'
# 2. 加载Tokenizer.
my_tokenizer = AutoTokenizer.from_pretrained(model_name)
# 3. 加载模型.
my_model = AutoModelForMaskedLM.from_pretrained(model_name)
# 4. 文本转张量.
input = my_tokenizer.encode_plus(
text = '我想明天去[MASK]家吃饭', # 待处理的文本
return_tensors='pt', # 返回张量类型, 默认: None, 可选: pt / tf / np
)
# print(f'input: {input}')
# 5. 给模型喂数据.
my_model.eval()
output = my_model(**input)
# 6. 获取预测结果.
# print(f'output: {output}')
# print(f'output.logits: {output.logits.shape}') # [1, 11, 21128]
print(f'output.logits: {output.logits[0][6].shape}') # [21128]
# 7. 获取概率最高的预测结果 -> 即: [MASK]位置的.
mask_predict_idx = torch.argmax(output.logits[0][6]).item()
print(f'mask_predict_idx: {mask_predict_idx}') # 872
# 8. 打印概率最高的字 -> '你'
# print(f'概率最高的字: {my_tokenizer.convert_ids_to_tokens(mask_predict_idx)}') # 你, 接收1个索引.
print(f'概率最高的字: {my_tokenizer.convert_ids_to_tokens([mask_predict_idx])}') # 你, 接收1个索引列表
# todo 4. 阅读理解 -> 也叫:抽取式问答, 即输入一段文本和一个问题, 让模型输出结果.
def dm04_question_answering():
# 1. 定义变量, 记录: 模型名.
model_name = r'C:\Software\CentOS_All\PretrainedModel\chinese_pretrain_mrc_roberta_wwm_ext_large'
# 2. 加载Tokenizer.
my_tokenizer = AutoTokenizer.from_pretrained(model_name)
# 3. 加载模型.
my_model = AutoModelForQuestionAnswering.from_pretrained(model_name)
# 4. 准备文本, 细节: 上下文最好不要加空格, 会影响模型定位, 空格会被删除, 可以换成标点符号.
context = '我叫张三 我是一个程序员 我的喜好是打篮球'
questions = ['我是谁?', '我是做什么的?', '我的爱好是什么?']
# 5. 该模型是针对于每个问题进行问答推理的, 即: 逐个问题生成, 此处要用for循环.
for question in questions:
# 5.1 把上述的问题 转成 ids(模型可接受的输入张量)
# 参1: 当前的问题. 参2: 包含答案的上下文文本. 参3: 返回张量形式.
input = my_tokenizer.encode_plus(question, context, return_tensors='pt')
# input_ids: [CLS] + 问题 + [SEP] + 上下文 + [SEP] 细节: 空格不统计.
# token_type_ids: 0 -> 问题, 1 -> 上下文
# attention_mask: 1 -> 真实, 0 -> 填充
# print(f'input: {input}\n\n\n')
# 5.2 给模型喂数据, 获取结果.
my_model.eval()
output = my_model(**input)
# 5.3 获取结果.
print(f'output: {output}')
print(f'output: {output.start_logits.shape}') # 第1句话: [1, 26], 即: [CLS] + 问题 + [SEP] + 上下文 + [SEP] 第2句话:[1,29], 第3句话: [1, 30]
print(f'output: {output.end_logits.shape}') # 第1句话: [1, 26], 第2句话:[1,29], 第3句话: [1, 30]
# 5.4 计算模型预测的答案(开头的位置索引, 结束的位置索引.)
start, end = torch.argmax(output.start_logits), torch.argmax(output.end_logits) + 1
print(f'start: {start}, end: {end}') # 针对于第1个句子, 这里是: 8, 10
# 5.5 获取模型预测的答案.
answer = my_tokenizer.convert_ids_to_tokens(input.input_ids[0][start:end])
print(f'问题: {question}, 模型预测的答案: {answer}')
print('\n' * 2)
# todo 5. 文本摘要 -> 输入一段文本, 提取其重要信息.
def dm05_summarization():
# 1. 定义输入文本 -> 一段关于BERT模型预训练方法的英文介绍.
text = "BERT is a transformers model pretrained on a large corpus of English data " \
"in a self-supervised fashion. This means it was pretrained on the raw texts " \
"only, with no humans labelling them in any way (which is why it can use lots " \
"of publicly available data) with an automatic process to generate inputs and " \
"labels from those texts. More precisely, it was pretrained with two objectives:Masked " \
"language modeling (MLM): taking a sentence, the model randomly masks 15% of the " \
"words in the input then run the entire masked sentence through the model and has " \
"to predict the masked words. This is different from traditional recurrent neural " \
"networks (RNNs) that usually see the words one after the other, or from autoregressive " \
"models like GPT which internally mask the future tokens. It allows the model to learn " \
"a bidirectional representation of the sentence.Next sentence prediction (NSP): the models" \
" concatenates two masked sentences as inputs during pretraining. Sometimes they correspond to " \
"sentences that were next to each other in the original text, sometimes not. The model then " \
"has to predict if the two sentences were following each other or not."
# 2. 定义变量, 记录: 模型名.
model_name = r'C:\Software\CentOS_All\PretrainedModel\distilbart-cnn-12-6'
# 3. 加载分词器 和 模型.
my_tokenizer = AutoTokenizer.from_pretrained(model_name)
my_model = AutoModelForSeq2SeqLM.from_pretrained(model_name)
# 4. 将输入文本转成模型可接受的输入张量.
input = my_tokenizer(text, return_tensors='pt')
# print(f'input: {input}')
# 5. 把输入文本喂给模型, 获取预测结果.
my_model.eval()
output = my_model.generate(input['input_ids'])
print(f'output: {output}')
# 6. 打印预测结果, 即: 把摘要结果 -> 可读文本.
# 扩展: 如下的方式只能将ids还原为tokens, 如果感兴趣, 可以看一下BPE(Byte-Pair Encoding), 字节对编码(切词), GPT底层用的就是它进行切词的.
# print(my_tokenizer.convert_ids_to_tokens(output[0]))
# 可以用 decode() 解码实现.
# skip_special_tokens: 跳过特殊标记 -> 即: 去掉token前后的特殊字符, 例如: [CLS], [SEP]...
# clean_up_tokenization_spaces: 是否保留原始分词的空格.
print([my_tokenizer.decode(g, skip_special_tokens=True, clean_up_tokenization_spaces=True) for g in output])
print()
print([my_tokenizer.decode(g, skip_special_tokens=False, clean_up_tokenization_spaces=False) for g in output])
# todo 6. NER任务 -> 命名实体识别
def dm06_ner():
# 1. 定义变量, 记录: 模型名.
model_name = r'C:\Software\CentOS_All\PretrainedModel\roberta-base-finetuned-cluener2020-chinese'
# 2. 加载分词器, 模型, 配置文件.
my_tokenizer = AutoTokenizer.from_pretrained(model_name)
my_model = AutoModelForTokenClassification.from_pretrained(model_name)
config = AutoConfig.from_pretrained(model_name)
# 3. 数据张量化 -> 把输入文本 转成模型可接受的输入张量.
input = my_tokenizer.encode_plus('我爱北京天安门, 天安门上太阳升', return_tensors='pt')
print(f'input: {input}')
# 4. 把数据喂给模型, 获取结果.
my_model.eval()
logits = my_model(input.input_ids).logits
print(f'logits: {logits.shape}') # [1, 17, 32] -> [批次大小, 句子长度, 类别数量]
# 5. 处理预处理结果, 并显示.
# 5.1 把输入id 转回 token形式(用于可视化.
# ['[CLS]', '我', '爱', '北', '京', '天', '安', '门', ',', '天', '安', '门', '上', '太', '阳', '升', '[SEP]']
input_tokens = my_tokenizer.convert_ids_to_tokens(input.input_ids[0])
# 5.2 用zip()函数, 把 token 和 预测的标签概率分布 组合到一起.
# 定义变量, 记录结果.
outputs = [] # 格式为: [('我', 'O'), ('爱','O'), ('北京', 'LOC')...]
# 遍历: 每个token(17个) 和 预测的概率标签分布 (17, 32)
for token, value in zip(input_tokens, logits[0]): # logits[0]: [17, 32]
# 过滤掉特殊字符.
if token in my_tokenizer.all_special_tokens:
continue
# 走到这里, 说明不是特殊字符, 就获取: 32个标签的概率分布中, 最大概率标签的索引.
idx = torch.argmax(value).item()
print(f'token: {token}, idx: {idx}')
# 构建元组, 即: (token, 预测标签)
outputs.append((token, config.id2label[idx]))
# 6. 输出结果, 即: 格式为: [('我', 'O'), ('爱','O'), ('北京', 'LOC')...]
print(f'outputs: {outputs}')
# todo 7.测试代码
if __name__ == '__main__':
# 1. 测试: 情感分类(文本分类任务)
# dm01_text_classification()
# 2. 测试: 特征抽取(特征抽取任务)
# dm02_feature_extraction()
# 3. 测试: 完形填空(完形填空任务)
# dm03_fill_mask()
# 4. 测试: 阅读理解(阅读理解任务)
# dm04_question_answering()
# 5. 测试: 文本摘要(文本摘要任务)
# dm05_summarization()
# 6. 测试: NER(命名实体识别)
dm06_ner()