中文文本分类的案例
实现流程
properties
1.获取数据集
2.数据预处理: 实例化dataset,dataloader,注意这里面在dataloder里用了自定义函数进行文本张量化处理
3.搭建模型: 注意,我们这里用bert预训练模型来得到文本的特征表示,然后在经过自定义网络实现分类
4.模型训练: 注意,不训练bert模型的参数,只更新自己定义的网络参数
5.模型测试: 注意,如果在GPU上训练的模型,想在CPU上使用, model.load_state_dict(torch.load(path, map_location="cpu")) #将模型放到cpu上
1 数据预处理
1.1 得到Dataset对象
properties
我们这里是直接应用的datasets第三方库里面的load_dataset的方法,可以直接返回DataSet数据源对象
代码实现
python
def dm_file2dataset():
# 1.加载训练数据集
train_dataset = load_dataset('csv', data_files="./data/train.csv", split="train")
# 1.1第二种加载方式
# train_dataset = load_dataset(path='./data', data_files="train.csv", split="train")
print(f'训练数--》{train_dataset[0]}')
# print(f'训练数据取出前三行数据---》{train_dataset[:3]}')
# 2.加载测试数据集
test_dataset = load_dataset('csv', data_files="./data/test.csv", split="train")
# print(f'test_dataset--》{test_dataset}')
# print(f'测试数据取出前三行数据---》{test_dataset[:3]}')
# 3.加载验证数据集
valid_dataset = load_dataset('csv', data_files="./data/validation.csv", split="train")
# print(f'valid_dataset--》{valid_dataset}')
# print(f'验证数据取出前三行数据--》{valid_dataset[:3]}')
return train_dataset, test_dataset, valid_dataset
1.2 实现自定义函数
properties
在Dataloader里面自动调用,目的是处理dataset里面的数据,进行张量化处理
代码实现
python
def collate_fn1(data):
'''
data:是从dataset里面获取的数据.类型为list,[第一个样本,第二样本,。。。]
# data传过来的数据是list eg: 批次数8,8个字典
# [{'text':'xxxx','label':0} , {'text':'xxxx','label':1}, ...]
'''
# print(f'data-->{len(data)}')
# print(f'data[0]-->{data[0]}')
sents = [item["text"] for item in data]
labels = [item["label"] for item in data]
inputs = my_pre_tokenizer.batch_encode_plus(sents, truncation=True,
max_length=300, padding="max_length",
return_tensors='pt',
return_length=True)
# print(f'inputs-------》{inputs}')
# print(f'inputs["input_ids"]-------》{inputs["input_ids"].shape}')
inputs_ids = inputs["input_ids"]
token_type_ids = inputs["token_type_ids"]
attention_mask = inputs["attention_mask"]
labels = torch.LongTensor(labels)
return inputs_ids, token_type_ids, attention_mask, labels
1.3 得到Dataloader
代码实现
python
def get_dataloader():
# 1.获得dataset的数据源对象
train_dataset = load_dataset('csv', data_files="./data/train.csv", split="train")
# 2.实例化dataloader
my_dataloader = DataLoader(dataset=train_dataset, batch_size=8, shuffle=True,
collate_fn=collate_fn1, drop_last=True)
# # print(f'my_dataloader-->{len(my_dataloader)}')
# for inputs_ids, token_type_ids, attention_mask, labels in my_dataloader:
# print(f'inputs_ids--》{inputs_ids.shape} ')
# print(f'token_type_ids--》{token_type_ids.shape} ')
# print(f'attention_mask--》{attention_mask.shape} ')
# print(f'labels--》{labels.shape} ')
# break
return my_dataloader
2 搭建模型
properties
应用迁移学习的思路:bert预训练模型特征处理+自定义模型(分类)
代码实现
python
# 自定义下游任务模型
class AiModel(nn.Module):
def __init__(self):
super().__init__()
# 定义输出层
# 768代表bert模型的特征表示,2代表二分类
self.linear = nn.Linear(768, 2)
def forward(self, input_ids, token_type_ids, attention_mask):
# 不对预训练模型的参数更新
# my_pre_model代表bert预训练模型的对象
with torch.no_grad():
bert_output = my_pre_model(input_ids=input_ids, attention_mask=attention_mask,token_type_ids=token_type_ids)
# print(f'bert模型的输出结果-->{bert_output}')
# print(f'bert模型的last_hidden_state-->{bert_output.last_hidden_state.shape}')
# print(f'bert模型的pooler_output-->{bert_output.pooler_output.shape}')
# bert模型的输出结果包括:last_hidden_state,pooler_output
# last_hidden_state(最后一层)--》[8,500,768]-->8个样本,每个样本500个单词,每个单词用768维度的向量表示
# pooler_output--》[8,768]-->这个代表8个样本,每个样本都用768维度的向量表示。
# bert模型输出有一个特殊的字符CLS,pooler_output的结果是每一个样本得到CLS这个token对应的向量表示,
# 原始论文中说明:当利用bert模型去做分类任务的时候,一般直接取CLS这个token对应的向量表示当前这个样本的特征,进行实现分类
output = self.linear(bert_output.pooler_output) # output-->[8, 2]
return output
3 模型训练
properties
注意: 1,因为使用的预训练模型,所以在训练的时候,对自定义的模型加上,model.train();2.不更新bert预训练模型的参数《requires_grad=False》3.如果要想在GPU上训练:3.1将预训练模型的对象放到GPU上,3.2自定义的模型对象放到GPU上,3.3模型的输入放到GPU上,eg:input_ids = input_ids.to('cuda');model = model.to('cuda')
代码实现
python
# 定义训练方法
def train_model():
# 1.加载训练数据集
train_dataset = load_dataset('csv', data_files="./data/train.csv", split="train")
# 2.实例化模型
my_model = AiModel().to(device)
# 3. my_pre_model对预训练模型的参数requires_grad设置为False,不计算梯度
for param in my_pre_model.parameters():
# print(f'param--》{param.requires_grad}')
param.requires_grad_(False)
# print(f'param--》{param.requires_grad}')
# p.numel()计算每个参数的元素个数
# total_params = sum(p.numel() for p in my_pre_model.parameters())
# 4.实例化损失函数对象
# mean计算一个批次样本的平均损失,sum是损失之和,
my_crossentropy = nn.CrossEntropyLoss(reduction='mean')
# 5.实例化优化器
my_adamw = AdamW(my_model.parameters(), lr=5e-4)
# 6.设置模型为训练模型
my_model.train()
# 7.开始训练
for epoch_idx in range(3):
start_time = time.time()
# 实例化dataloader
my_dataloader = DataLoader(dataset=train_dataset, batch_size=8,
shuffle=True, collate_fn=collate_fn1,
drop_last=True)
# 内部数据迭代
for i, (inputs_ids, token_type_ids, attention_mask, labels) in enumerate(tqdm(my_dataloader), start=1):
# print(f'labels---》{labels}')
inputs_ids = inputs_ids.to(device)
token_type_ids = token_type_ids.to(device)
attention_mask = attention_mask.to(device)
labels = labels.to(device)
output = my_model(inputs_ids, token_type_ids, attention_mask,)
# print(f'output--》{output.shape}')
# print(f'output--》{output}')
# 计算损失
loss = my_crossentropy(output, labels)
# print(f'loss--》{loss}')
# 梯度清零
my_adamw.zero_grad()
# 反向传播
loss.backward()
# 梯度更新
my_adamw.step()
# 打印日志:每隔5步计算平均准确率
if i % 5 == 0:
tem = torch.argmax(output, dim=-1)
# print(f'tem--》{tem}')
acc = (tem == labels).sum().item() / len(labels)
use_time = time.time() - start_time
print("当前训练的轮次%d,迭代的步数%d,当前的损失%.2f, 当前的准确率%.2f, 时间%d"%(epoch_idx+1, i,
loss, acc, use_time))
# 保存模型
torch.save(my_model.state_dict(), "./AI19_model/classify_%d.bin"%(epoch_idx+1))
4 模型预测
properties
注意: model.eavl()和with torch.no_grad()
#如果在GPU上训练的模型,想在CPU上使用, model.load_state_dict(torch.load(path, map_location="cpu")) #将模型放到cpu上
代码实现
python
# 定义模型评估方法
def test_model():
# 1.加载训练数据集
test_dataset = load_dataset('csv', data_files="./data/test.csv", split="train")
# 2.实例化dataloader
test_dataloader = DataLoader(dataset=test_dataset, batch_size=8, shuffle=True,
collate_fn=collate_fn1, drop_last=True)
# 3.加载训练好的模型
path = './AI19_model/classify_3.bin'
my_model = AiModel()
my_model.load_state_dict(torch.load(path, map_location="cpu")) #将模型放到cpu上
# my_model = my_model.to("cpu")
# 4.定义测试的参数
correct = 0
total = 0
# 5.设定模型为eval
my_model.eval()
# 6.迭代数据送入模型
for i, (inputs_ids, token_type_ids, attention_mask, labels )in enumerate(tqdm(test_dataloader), start=1):
with torch.no_grad():
output = my_model(inputs_ids, token_type_ids, attention_mask,)
# 得到预测的最大概率值的索引
temp = torch.argmax(output, dim=-1)
# 得到预测正确的个数
correct += (temp == labels).sum().item()
# 当前训练的样本个数
total += len(labels)
# 每隔5步打印测试的结果
if i % 5 == 0:
print(f'平均准确率-->{correct/total}')
# 取出一个批次的第一个样本来查验是否预测正确
text_list = my_pre_tokenizer.decode(inputs_ids[0],skip_special_tokens=True)
print(f'原始的文本是--》{text_list}', end=' ')
print(f'模型预测的结果是:{temp[0]}, 真实的结果是:{labels[0]}')