import torch
import torch.nn as nn
import torch.optim as optim
from tqdm import tqdm
from datasets import load_dataset
from transformers import BertTokenizer, BertModel
from torch.utils.data import DataLoader
# 0.测试load_dataset方法
def dm01():
# data = load_dataset('csv', data_files='../data/train.csv')
# print("data-->", data)
# data = load_dataset('csv', data_files='../data/train.csv', split='train')
# print("data-->", data)
# 注意:split,只能是train
data = load_dataset('csv', data_files='../data/test.csv', split='train')
print("data-->", data)
print(data[0:4])
# 加载预训练模型
tokenizer = BertTokenizer.from_pretrained("../models/bert-base-chinese")
# 这里导入模型要使用BertModel,而不是BertModelForSequenceClassification
# 因为,这里使用预训练模型进行特征提取,下游自己指定多分类输出层(线性层)
bert_model = BertModel.from_pretrained("../models/bert-base-chinese")
# 1.数据预处理
def collate_fn1(batch):
# 打印数据
# print("batch-->", batch)
# 从样本中提取文本和标签
sentences = [row['text'] for row in batch]
labels = [row['label'] for row in batch]
# print("sentences-->", sentences)
# print("labels-->", labels)
# 对文本进行数值化+张量化,对标签进行张量化
data = tokenizer.batch_encode_plus(sentences,
padding='max_length',
max_length=300,
truncation=True,
return_tensors='pt')
# print("data-->", data)
# input_ids:数据数值化+张量化后的结果
# token_type_ids:文本(句子)类型,0代表一个句子
# attention_mask:注意力掩码,0代表填充的,1代表有值(需要掩码)
input_ids = data['input_ids']
token_type_ids = data['token_type_ids']
attention_mask = data['attention_mask']
# 对label进行张量化
labels = torch.tensor(labels)
# 返回数据
return input_ids, token_type_ids, attention_mask, labels
def load_data():
# 使用load_dataset加载数据,得到的就是一个Dataset对象
mydataset = load_dataset('csv', data_files='../data/train.csv', split='train')
# 把Dataset转换为DataLoader对象
# collate_fn:对数据进行二次处理的函数,就是这一批次的数据,需要接受一个函数地址
mydataloader = DataLoader(dataset=mydataset, batch_size=4, shuffle=True, drop_last=True, collate_fn=collate_fn1)
# print("mydataloader-->", mydataloader)
# 遍历
for input_ids, token_type_ids, attention_mask, labels in mydataloader:
print("input_ids-->", input_ids)
print("token_type_ids-->", input_ids)
print("attention_mask-->", input_ids)
print("labels-->", input_ids)
break
# 2.模型构建
class MyModel(nn.Module):
def __init__(self):
super().__init__()
# 自定义下游输出层(线性层)
# in_features:输入维度,就是Bert模型的768维度
# out_features:输出类别,也就是下游的任务类别,这里是2分类
self.linear = nn.Linear(in_features=768, out_features=2)
def forward(self, input_ids, token_type_ids, attention_mask):
# 先试用Bert模型进行特征提取(可以冻结,也可以不冻结,如果效果不好,就不冻结)
with torch.no_grad():
out = bert_model(input_ids=input_ids,
token_type_ids=token_type_ids,
attention_mask=attention_mask)
# print("out1-->", out)
# print("out2-->", out.last_hidden_state.shape) # [4, 300, 768]
# :代表取4句话
# 0:300个token中,取第0个,也就是CLS
# out.last_hidden_state[:, 0]
# out.last_hidden_state[:, 0, :]
# print("out3-->", out.pooler_output.shape) # [4, 768]
# 然后把特征送给下游的自定义输出线性层,实现二分类任务
output = self.linear(out.pooler_output)
# print("output-->", output, output.shape) # [4, 2]
return output
def test_model():
# 数据
mydataset = load_dataset('csv', data_files='../data/train.csv', split='train')
mydataloader = DataLoader(dataset=mydataset, batch_size=4, shuffle=True, drop_last=True, collate_fn=collate_fn1)
# 模型
model = MyModel()
# 遍历
for input_ids, token_type_ids, attention_mask, labels in mydataloader:
model(input_ids=input_ids, token_type_ids=token_type_ids, attention_mask=attention_mask)
break
# 3.模型训练
def train_model():
# 3+2+5
# 3:模型,损失函数、优化器
# 2:内外两层循环
# 5:内层循环中,5个步骤:前向传播、损失计算、梯度清零、反向传播、梯度更新
# 模型
model = MyModel()
# 损失函数
criterion = nn.CrossEntropyLoss()
# 优化器
optimizer = optim.AdamW(params=model.parameters(), lr=5e-4)
# 轮次
epochs = 1
# 冻结预训练模型的参数(Bert模型)
# 双重保险
for param in bert_model.parameters():
param.requires_grad_(False)
# 设置模型为训练模型
model.train()
# 加载数据
mydataset = load_dataset('csv', data_files='../data/train.csv', split='train')
# 把数据转换为DataLoader
mydataloader = DataLoader(dataset=mydataset,
shuffle=True,
drop_last=True,
batch_size=4,
collate_fn=collate_fn1)
# 外层变量轮次
for epoch in range(epochs):
# 内层变量批次
# tqdm:进度条,pip install tqdm
for i, (input_ids, token_type_ids, attention_mask, labels) in enumerate(tqdm(mydataloader), start=1):
# 前向传播
output = model(input_ids=input_ids,
token_type_ids=token_type_ids,
attention_mask=attention_mask)
# output.shape->[4,2]
# 损失计算
loss = criterion(output, labels)
# print("loss-->", loss)
# 梯度清零
optimizer.zero_grad()
# 反向传播
loss.backward()
# 梯度更新
optimizer.step()
# 打印日志
if i % 5 == 0:
pred = torch.argmax(output, dim=-1)
accuracy = (pred == labels).sum().item() / len(labels)
print("epoch:", epoch + 1, "loss:", loss.item(), "accuracy:", accuracy)
# 模型保存
torch.save(model.state_dict(), '../save_model/model_%d.pth' % (epoch + 1))
# 4.模型评估
def model_eval():
# 加载数据
mydataset = load_dataset("csv", data_files='../data/test.csv', split='train')
# 转换为DataLoader
mydataloader = DataLoader(dataset=mydataset,
shuffle=False,
drop_last=True,
batch_size=4,
collate_fn=collate_fn1)
# 准备模型
model = MyModel()
# 加载刚刚预训练好的模型参数
model.load_state_dict(torch.load('../save_model/model_1.pth'))
# 准备参数:total,accuracy
num = 0
total = 0
# 设置模型为评估模式
model.eval()
# 遍历
for i, (input_ids, token_type_ids, attention_mask, labels) in enumerate(tqdm(mydataloader), start=1):
# 把数据送给模型,得到预测值
output = model(input_ids=input_ids, token_type_ids=token_type_ids, attention_mask=attention_mask)
# 基于预测结果,得到预测值
y_pred = torch.argmax(output, dim=-1)
num += (y_pred == labels).sum().item()
total += len(labels)
# 计算准确率
print("accuracy-->", num / total)
if __name__ == '__main__':
# dm01()
# load_data()
# test_model()
# train_model()
model_eval()
pass