tnblog
首页
视频
资源
登录

HugginFace 中文数据关系推断(学习笔记)

2409人阅读 2023/10/30 15:56 总访问:3511413 评论:0 收藏:0 手机
分类: HuggingFace

HugginFace 中文数据关系推断(学习笔记)

实现代码

安装包


加载的环境可以通过如下命令进行安装。

  1. %pip install -q transformers==4.18 datasets==2.4.0 torchtext

准备数据集


使用编码工具。
本章依然使用的是bert-base-chinese编码工具。

  1. from transformers import BertTokenizer
  2. token = BertTokenizer.from_pretrained('bert-base-chinese')
  3. token


进行试算一次,以更清晰的观察输入和输出,代码如下:

  1. #试编码句子
  2. out = token.batch_encode_plus(
  3. batch_text_or_text_pairs=[('不是一切大树,', '都被风暴折断。'),
  4. ('不是一切种子,', '都找不到生根的土壤。')],
  5. truncation=True,
  6. padding='max_length',
  7. max_length=18,
  8. return_tensors='pt',
  9. return_length=True,
  10. )
  11. #查看编码输出
  12. for k, v in out.items():
  13. print(k, v.shape)
  14. #把编码还原为句子
  15. print(token.decode(out['input_ids'][0]))


与情感分类和填空任务不同,这里编码的是句子对,运行结果如下:


确定编码的长度为18个词的长度。

定义数据集


定义本次任务所需要的数据集仍然是ChnSentiCorp数据集。

  1. # 定义数据集
  2. import torch
  3. from datasets import load_dataset
  4. import random
  5. class Dataset(torch.utils.data.Dataset):
  6. def __init__(self, split):
  7. dataset = load_dataset('lansinuote/ChnSentiCorp')[split]
  8. def f(data):
  9. return len(data['text']) > 40
  10. self.dataset = dataset.filter(f)
  11. def __len__(self):
  12. return len(self.dataset)
  13. def __getitem__(self, i):
  14. text = self.dataset[i]['text']
  15. #切分一句话为前半句和后半句
  16. sentence1 = text[:20]
  17. sentence2 = text[20:40]
  18. #随机整数,取值范围为0和1
  19. label = random.randint(0, 1)
  20. #有一半的概率把后半句替换为一句无关的话
  21. if label == 1:
  22. j = random.randint(0, len(self.dataset) - 1)
  23. sentence2 = self.dataset[j]['text'][20:40]
  24. return sentence1, sentence2, label
  25. dataset = Dataset('train')
  26. sentence1, sentence2, label = dataset[7]
  27. len(dataset), sentence1, sentence2, label


在这段代码中,加载了ChnSentiCorp数据集,并使用PyTorch的Dataset对象进行封装,由于本次任务是要判断两句话是否存在相连的关系,如果假设定义每句话的长度为20个字,则原句子最短不能少于40个字,否则不能被切割成两句话。
所以在__init__()函数中加载了ChnSentiCorp数据集后对数据集进行过滤,丢弃了数字少于40个字的句子。
__getitem__()函数中把原句切割成了各20个字的两句话,并且有一半的概率把后半句替换为无关的句子,这样就形成了本次任务中需要的数据结构,即每条数据中包括两句话,并且这两句话分别有50%的概率是相联和无关的关系。
运行结果如下:


可见,训练数据集包括8001条数据,有两句话一个标识。

定义计算设备


能不能支持gpu。

  1. import torch
  2. device = 'cpu'
  3. if torch.cuda.is_available():
  4. device = 'cuda'
  5. device

定义数据整理函数


定义一个数据整理函数,它具有批量编码一批文本数据的功能,代码如下:

  1. #数据整理函数
  2. def collate_fn(data):
  3. sents = [i[:2] for i in data]
  4. labels = [i[2] for i in data]
  5. #编码
  6. data = token.batch_encode_plus(batch_text_or_text_pairs=sents,
  7. truncation=True,
  8. padding='max_length',
  9. max_length=45,
  10. return_tensors='pt',
  11. return_length=True,
  12. add_special_tokens=True)
  13. #input_ids:编码之后的数字
  14. #attention_mask:是补零的位置是0,其他位置是1
  15. #token_type_ids:第一个句子和特殊符号的位置是0,第二个句子的位置是1
  16. input_ids = data['input_ids'].to(device)
  17. attention_mask = data['attention_mask'].to(device)
  18. token_type_ids = data['token_type_ids'].to(device)
  19. labels = torch.LongTensor(labels).to(device)
  20. return input_ids, attention_mask, token_type_ids, labels


在这段代码中,入参的data表示一批数据,取出其中的句子对和标识,分别为两个list,其中句子对的list中为一个一个tuple,每个tuple中包括两个句子,即一对句子。
在制作数据集时已经明确两个句子各有20个字,但在经历编码时每个字并不一定会被编码成一个词,此外在编码时还需要往句子中插入一些特殊福好,如标识句子开始的[CLS]标识一个句子的结束的[SEP],所以编码的结果并不能确定为40个词,因此在编码时需要留下一定的容差,让编码结果中能囊括两个句子的所有信息,如果过有多余的位置,则可以以[PAD]填充。
综上所述,使用编码工具编码这一批句子对时,在参数中指定了编码后的结果为确定的45个词,超过45个词的句子将被截断,而不足45个词的句子将被补充PAD,直到45个词。
在编码时,通过参数return_tensors='pt'让编码结果为PyTorchTensor格式,这免去了后续转换数据格式的麻烦。
之后取出编码的结果,不妨假定一批数据,让数据整理函数进行试算,以观察数据整理的输入和输出,代码如下:

  1. #数据整理函数试算
  2. #模拟一批数据
  3. data = [('酒店还是非常的不错,我预定的是套间,服务', '非常好,随叫随到,结帐非常快。', 0),
  4. ('外观很漂亮,性价比感觉还不错,功能简', '单,适合出差携带。蓝牙摄象头都有了。', 0),
  5. ('《穆斯林的葬礼》我已闻名很久,只是一直没', '怎能享受4星的服务,连空调都不能用的。', 1)]
  6. #试算
  7. input_ids, attention_mask, token_type_ids, labels = collate_fn(data)
  8. #把编码还原为句子
  9. print(token.decode(input_ids[0]))
  10. input_ids.shape, attention_mask.shape, token_type_ids.shape, labels


编码之后的结果都是确定的45个词

定义数据集加载器

  1. #数据加载器
  2. loader = torch.utils.data.DataLoader(dataset=dataset,
  3. batch_size=8,
  4. collate_fn=collate_fn,
  5. shuffle=True,
  6. drop_last=True)
  7. len(loader)


可见,训练数据集加载器一共有1000个批次。
定义好了数据加载器之后,可以查看一批数据样本,代码如下:

  1. #查看数据样例
  2. for i, (input_ids, attention_mask, token_type_ids,
  3. labels) in enumerate(loader):
  4. break
  5. input_ids.shape, attention_mask.shape, token_type_ids.shape, labels


这个结果其实就是数据整理函数的计算结果,只是句子的数量更多。

定义模型

加载预训练模型

  1. #加载预训练模型
  2. from transformers import BertModel
  3. pretrained = BertModel.from_pretrained('bert-base-chinese')
  4. #统计参数量
  5. sum(i.numel() for i in pretrained.parameters()) / 10000


在代码的最后,输出了模型的参数量,运行结果如下:


可见bert-base-chinese的参数量约为1亿个,在本次任务中选择不训练它,代码如下:

  1. #不训练预训练模型,不需要计算梯度
  2. for param in pretrained.parameters():
  3. param.requires_grad_(False)


运行结果如下:


样例数据为8句话的编码结果,从预训练模型的计算结果可以看出,这也是8句话的结果,每句话包括45个词,每个词被抽成了一个768维的向量。到此为止,通过预训练模型成功地把8句话转换为一个特征向量矩阵,可以接入下游任务模型做分类或者回归任务。

定义下游任务模型


完成以上工作后,现在可以定义下游任务模型了,对于本章的任务来讲,需要计算一个二分类的结果,并且需要和数据集中真实的label保持一致,代码如下:

  1. #定义下游任务模型
  2. class Model(torch.nn.Module):
  3. def __init__(self):
  4. super().__init__()
  5. self.fc = torch.nn.Linear(768, 2)
  6. def forward(self, input_ids, attention_mask, token_type_ids):
  7. #使用预训练模型抽取数据特征
  8. with torch.no_grad():
  9. out = pretrained(input_ids=input_ids,
  10. attention_mask=attention_mask,
  11. token_type_ids=token_type_ids)
  12. #对抽取的特征只取第一个字的结果做分类即可
  13. out = self.fc(out.last_hidden_state[:, 0])
  14. out = out.softmax(dim=1)
  15. return out
  16. model = Model()
  17. #设定计算设备
  18. model.to(device)
  19. #试算
  20. model(input_ids=input_ids,
  21. attention_mask=attention_mask,
  22. token_type_ids=token_type_ids).shape


在这段代码中,定义了下游任务模型,该模型只包括一个全连接的线性神经网络,权重矩阵为768x2,所以它能够把一个768维度的向量转换到二维空间中。
下游任务模型丢弃了44个词的特征,只取得了第1个词(索引为0)的特征向量,对应了编码结果中的[CLS],把特征向量矩阵变成了16x768。相当于把每句话变成了一个768维度的向量。

之所以只取了第1个词的特征做后续的判断计算,这和预训练模型BERT的训练方法有关系。


之后再使用自己的全连接线性神经网络把16x768特征矩阵转换到16x2,即为要求的二分类结果。


可见,这就是要求的16句话的二分类的结果。

训练和测试

训练

  1. #第9章/训练
  2. from transformers import AdamW
  3. from transformers.optimization import get_scheduler
  4. def train():
  5. #定义优化器
  6. optimizer = AdamW(model.parameters(), lr=5e-5)
  7. #定义loss函数
  8. criterion = torch.nn.CrossEntropyLoss()
  9. #定义学习率调节器
  10. scheduler = get_scheduler(name='linear',
  11. num_warmup_steps=0,
  12. num_training_steps=len(loader),
  13. optimizer=optimizer)
  14. #模型切换到训练模式
  15. model.train()
  16. #按批次遍历训练集中的数据
  17. for i, (input_ids, attention_mask, token_type_ids,
  18. labels) in enumerate(loader):
  19. #模型计算
  20. out = model(input_ids=input_ids,
  21. attention_mask=attention_mask,
  22. token_type_ids=token_type_ids)
  23. #计算loss并使用梯度下降法优化模型参数
  24. loss = criterion(out, labels)
  25. loss.backward()
  26. optimizer.step()
  27. scheduler.step()
  28. optimizer.zero_grad()
  29. #输出各项数据的情况,便于观察
  30. if i % 20 == 0:
  31. out = out.argmax(dim=1)
  32. accuracy = (out == labels).sum().item() / len(labels)
  33. lr = optimizer.state_dict()['param_groups'][0]['lr']
  34. print(i, loss.item(), lr, accuracy)
  35. train()


在这段代码中,首先定义了优化器、loss计算函数、学习率调节器。
训练完成后见下图所示:


由于只是简单的一层全连接神经网络,所以训练的难度很低。
学习率在慢慢下降。

测试


最后,对训练好的模型进行测试,以验证训练的有效性,代码如下图所示:

  1. #第9章/测试
  2. def test():
  3. #定义测试数据集加载器
  4. loader_test = torch.utils.data.DataLoader(dataset=Dataset('test'),
  5. batch_size=32,
  6. collate_fn=collate_fn,
  7. shuffle=True,
  8. drop_last=True)
  9. #下游任务模型切换到运行模式
  10. model.eval()
  11. correct = 0
  12. total = 0
  13. #按批次遍历测试集中的数据
  14. for i, (input_ids, attention_mask, token_type_ids,
  15. labels) in enumerate(loader_test):
  16. #计算5个批次即可,不需要全部遍历
  17. if i == 5:
  18. break
  19. print(i)
  20. #计算
  21. with torch.no_grad():
  22. out = model(input_ids=input_ids,
  23. attention_mask=attention_mask,
  24. token_type_ids=token_type_ids)
  25. pred = out.argmax(dim=1)
  26. #统计正确率
  27. correct += (pred == labels).sum().item()
  28. total += len(labels)
  29. print(correct / total)
  30. test()


定义了测试数据集和加载器,并取出5个批次的数据让模型进行预测,最后统计正确率并输出,运行结果如下:


欢迎加群讨论技术,1群:677373950(满了,可以加,但通过不了),2群:656732739

评价

HugginFace 初探

HugginFace 初探[TOC] 安装环境python环境是3.6。import sys sys.version 安装torch,简单起见,避免环境问题,并且计...

HugginFace 使用编码工具(学习笔记)

HugginFace 使用编码工具(学习笔记)[TOC] 安装环境# 我这里的python是3.11 %pip install -q transformers==4.18 datasets...

HugginFace 使用数据集(学习笔记)

HugginFace 使用数据集(学习笔记)[TOC] 数据集工具介绍HuggingFace 提供了统一的数据集处理工具,让不同的数据集通过统一...

HugginFace 使用评价指标工具(学习笔记)

HugginFace 使用评价指标工具(学习笔记)[TOC] 评价指标工具介绍在训练和测试一个模型时往往需要计算不同的评价指标,如正...

HugginFace 使用管道工具(学习笔记)

HugginFace 使用管道工具(学习笔记)[TOC] 管道工具介绍HuggingFace 有一个巨大的模型库,其中一些是已经非常成熟的经典模...

HugginFace 使用训练工具(学习笔记)

HugginFace 使用训练工具(学习笔记)[TOC] 训练工具介绍HuggingFace提供了巨大的模型库,但我们往往还需要对特定的数据集进...

HugginFace 中文情感分类(学习笔记)

HugginFace 中文情感分类(学习笔记)[TOC] 数据集介绍本章使用的是lansinuote/ChnSentiCorp数据集,这是一个情感分类数据集...

HugginFace 中文填空(学习笔记)

HugginFace 中文填空(学习笔记)[TOC] 数据集介绍本章使用的仍然是情感分类数据集,每条包括一句购物评价一集以及是不是好...

HugginFace 中文命名实体识别(学习笔记)

HugginFace 中文命名实体识别(学习笔记)[TOC] 任务简介简单来说就是的识别人名、机构名、地名。数据集的介绍本章所使用的...
这一世以无限游戏为使命!
排名
2
文章
642
粉丝
44
评论
93
docker中Sware集群与service
尘叶心繁 : 想学呀!我教你呀
一个bug让程序员走上法庭 索赔金额达400亿日元
叼着奶瓶逛酒吧 : 所以说做程序员也要懂点法律知识
.net core 塑形资源
剑轩 : 收藏收藏
映射AutoMapper
剑轩 : 好是好,这个对效率影响大不大哇,效率高不高
ASP.NET Core 服务注册生命周期
剑轩 : http://www.tnblog.net/aojiancc2/article/details/167
ICP备案 :渝ICP备18016597号-1
网站信息:2018-2025TNBLOG.NET
技术交流:群号656732739
联系我们:contact@tnblog.net
公网安备:50010702506256
欢迎加群交流技术