当前位置:   article > 正文

NLP | 基于LLMs的文本分类任务_biomednlp-pubmedbert-base-uncased-abstract base

biomednlp-pubmedbert-base-uncased-abstract base

比赛链接:讯飞开放平台

来源:DataWhale AI夏令营3(NLP

 

Roberta-base(BERT的改进)

①Roberta在预训练的阶段中没有对下一句话进行预测(NSP

②采用了动态掩码 ③使用字符级词级别表征的混合文本编码

论文:https://arxiv.org/pdf/1907.11692.pdf

 

DataWhale Topline的改进:

  特征1:平均池化MeanPooling(768维) -> 全连接层fc(128维)

  特征2:末隐藏层Last_hidden (768维) -> 全连接层fc(128维)

 

运行方式:阿里云机器学习平台PAI-交互式建模DSW

镜像选择pytorch:1.12-gpu-py39-cu113-ubuntu20.04

上传代码,解压指令:

unzip [filename]

运行py脚本指令(遇到网络错误重新运行即可):
 

python [python_filename]

① 数据处理模块

导入需要的模块:

  1. from transformers import AutoTokenizer #文本分词
  2. import pandas as pd
  3. import numpy as np
  4. from tqdm import tqdm #显示进度条
  5. import torch
  6. from torch.nn.utils.rnn import pad_sequence
  7. #填充序列,保证向量中各序列维度的大小一样
  8. MAX_LENGTH = 128 #定义最大序列长度为128

训练集制作:

  1. def get_train(model_name, model_dict):
  2. model_index = model_dict[model_name] # 获取模型索引
  3. train = pd.read_csv('./dataset/train.csv') #读取训练数据为DataFrame
  4. train['content'] = train['title'] + train['author'] + train['abstract']
  5. #将标题、作者和摘要拼接为训练内容
  6. tokenizer = AutoTokenizer.from_pretrained(model_name, max_length=MAX_LENGTH, cache_dir=f'./premodels/{model_name}_saved') # 实例化分词器对象
  7. # 通过分词器对训练数据进行分词,并获取输入ID、注意力掩码和标记类型ID(这个可有可无)
  8. input_ids_list, attention_mask_list, token_type_ids_list = [], [], []
  9. y_train = [] # 存储训练数据的标签
  10. for i in tqdm(range(len(train['content']))): # 遍历训练数据
  11. sample = train['content'][i] # 获取样本内容
  12. tokenized = tokenizer(sample, truncation='longest_first')
  13. #分词处理,【最长优先方式】截断
  14. input_ids, attention_mask = tokenized['input_ids'], tokenized['attention_mask'] # 获取输入ID和注意力掩码
  15. input_ids, attention_mask = torch.tensor(input_ids), torch.tensor(attention_mask) # 转换为PyTorch张量
  16. try:
  17. token_type_ids = tokenized['token_type_ids'] # 获取标记类型ID
  18. token_type_ids = torch.tensor(token_type_ids) # 转换为PyTorch张量
  19. except:
  20. token_type_ids = input_ids #异常处理
  21. input_ids_list.append(input_ids) # 将输入ID添加到列表中
  22. attention_mask_list.append(attention_mask) # 将注意力掩码添加到列表中
  23. token_type_ids_list.append(token_type_ids) # 将标记类型ID添加到列表中
  24. y_train.append(train['label'][i]) # 将训练数据的标签添加到列表中
  25. # 保存 对下述对象进行填充,保证向量中各序列维度的大小一样,生成张量
  26. # 输入 ID input_ids_tensor、
  27. # 注意力掩码 attention_mask_tensor
  28. # 标记类型ID token_type_ids_tensor
  29. input_ids_tensor = pad_sequence(input_ids_list, batch_first=True, padding_value=0)
  30. attention_mask_tensor = pad_sequence(attention_mask_list, batch_first=True, padding_value=0)
  31. token_type_ids_tensor = pad_sequence(token_type_ids_list, batch_first=True, padding_value=0)
  32. x_train = torch.stack([input_ids_tensor, attention_mask_tensor, token_type_ids_tensor], dim=1) # 将输入张量堆叠为一个张量
  33. x_train = x_train.numpy() # 转换为NumPy数组(ndarray)
  34. np.save(f'./models_input_files/x_train{model_index}.npy', x_train) #保存训练数据
  35. y_train = np.array(y_train) # 转换为NumPy数组(ndarray)
  36. np.save(f'./models_input_files/y_train{model_index}.npy', y_train) #保存标签数据

测试集制作:

  1. def get_test(model_name, model_dict):
  2. model_index = model_dict[model_name] # 获取模型索引
  3. test = pd.read_csv('./dataset/testB.csv') # 从CSV文件中读取测试数据为DataFrame
  4. test['content'] = test['title'] + ' ' + test['author'] + ' ' + test['abstract']
  5. # 将标题、作者和摘要拼接为测试内容
  6. tokenizer = AutoTokenizer.from_pretrained(model_name, max_length=MAX_LENGTH,cache_dir=f'./premodels/{model_name}_saved') # 实例化分词器对象
  7. # 通过分词器对测试数据进行分词,创建输入ID、注意力掩码和标记类型ID列表进行记录(可有可无)
  8. input_ids_list, attention_mask_list, token_type_ids_list = [], [], []
  9. for i in tqdm(range(len(test['content']))): # 遍历测试数据
  10. sample = test['content'][i] # 获取样本内容
  11. tokenized = tokenizer(sample, truncation='longest_first')
  12. # 分词处理,使用最长优先方式截断
  13. input_ids, attention_mask = tokenized['input_ids'], tokenized['attention_mask'] # 获取输入ID和注意力掩码
  14. input_ids, attention_mask = torch.tensor(input_ids), torch.tensor(attention_mask) # 转换为PyTorch张量
  15. try:
  16. token_type_ids = tokenized['token_type_ids'] # 获取标记类型ID
  17. token_type_ids = torch.tensor(token_type_ids) # 转换为PyTorch张量
  18. except:
  19. token_type_ids = input_ids #异常处理
  20. input_ids_list.append(input_ids) # 将输入ID添加到列表中
  21. attention_mask_list.append(attention_mask) # 将注意力掩码添加到列表中
  22. token_type_ids_list.append(token_type_ids) # 将标记类型ID添加到列表中
  23. # 保存,对输入ID、注意力掩码、标记类型ID进行填充,保证向量中各序列维度的大小一样,生成张量
  24. input_ids_tensor = pad_sequence(input_ids_list, batch_first=True, padding_value=0)
  25. attention_mask_tensor = pad_sequence(attention_mask_list, batch_first=True, padding_value=0)
  26. token_type_ids_tensor = pad_sequence(token_type_ids_list, batch_first=True, padding_value=0)
  27. x_test = torch.stack([input_ids_tensor, attention_mask_tensor, token_type_ids_tensor], dim=1) # 将输入张量堆叠为一个张量
  28. x_test = x_test.numpy() # 转换为NumPy数组
  29. np.save(f'./models_input_files/x_test{model_index}.npy', x_test) # 保存测试数据

划分训练集和验证集:

  1. def split_train(model_name, model_dict):
  2. # 训练集:验证集 = 9 : 1
  3. split_rate = 0.90
  4. # 处理样本内容
  5. model_index = model_dict[model_name] # 获取模型索引
  6. train = np.load(f'./models_input_files/x_train{model_index}.npy') # 加载训练数据
  7. state = np.random.get_state() # 获取随机数状态,保证样本间的随机是可重复的
  8. # 或者也可以设置经典随机种子random_seed=42
  9. np.random.shuffle(train) # 随机打乱训练数据,数据洗牌
  10. val = train[int(train.shape[0] * split_rate):] # 划分验证集 validation
  11. train = train[:int(train.shape[0] * split_rate)] # 划分训练集 train set
  12. np.save(f'./models_input_files/x_train{model_index}.npy', train) # 保存训练集
  13. np.save(f'./models_input_files/x_val{model_index}.npy', val) # 保存验证集
  14. train = np.load(f'./models_input_files/y_train{model_index}.npy') # 加载标签数据
  15. # 处理样本标签
  16. np.random.set_state(state) # 恢复随机数状态,让样本标签的随机可重复
  17. np.random.shuffle(train) # 随机打乱标签数据
  18. val = train[int(train.shape[0] * split_rate):] # 划分验证集 validation
  19. train = train[:int(train.shape[0] * split_rate)] # 划分训练集 train set
  20. np.save(f'./models_input_files/y_train{model_index}.npy', train) # 保存训练集标签
  21. np.save(f'./models_input_files/y_val{model_index}.npy', val) # 保存验证集标签
  22. print('split done.')

数据处理主函数:

  1. if __name__ == '__main__':
  2. model_dict = {'xlm-roberta-base':1,
  3. 'roberta-base':2,
  4. 'bert-base-uncased':3,
  5. 'microsoft/BiomedNLP-PubMedBERT-base-uncased-abstract-fulltext':4,
  6. 'dmis-lab/biobert-base-cased-v1.2':5,
  7. 'marieke93/MiniLM-evidence-types':6,
  8. 'microsoft/MiniLM-L12-H384-uncased':7,
  9. 'cambridgeltl/SapBERT-from-PubMedBERT-fulltext':8,
  10. 'microsoft/BiomedNLP-PubMedBERT-base-uncased-abstract':9,
  11. 'microsoft/BiomedNLP-PubMedBERT-large-uncased-abstract':10}
  12. model_name = 'roberta-base'
  13. get_train(model_name, model_dict) #读取训练集
  14. get_test(model_name, model_dict) #读取测试集
  15. split_train(model_name, model_dict) #划分训练集和测试集

② 模型训练

导入需要的模块:

  1. import numpy as np
  2. import torch
  3. import torch.nn as nn
  4. from sklearn import metrics
  5. import os
  6. import time
  7. from transformers import AutoModel, AutoConfig
  8. # 导入AutoModel和AutoConfig类,用于加载预训练模型
  9. from tqdm import tqdm #显示进度条

超参数类(可修改的所有超参数):

  1. class opt:
  2. seed = 42 # 随机种子
  3. batch_size = 16 # 批处理大小
  4. set_epoch = 5 # 训练轮数
  5. early_stop = 5 # 提前停止epoch数
  6. learning_rate = 1e-5 # 学习率
  7. weight_decay = 2e-6 # 权重衰减,L2正则化
  8. device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 选择设备,GPU或CPU
  9. gpu_num = 1 # GPU个数
  10. use_BCE = False # 是否使用BCE损失函数
  11. models = ['xlm-roberta-base', 'roberta-base', 'bert-base-uncased',
  12. 'microsoft/BiomedNLP-PubMedBERT-base-uncased-abstract-fulltext', 'dmis-lab/biobert-base-cased-v1.2', 'marieke93/MiniLM-evidence-types',
  13. 'microsoft/MiniLM-L12-H384-uncased','cambridgeltl/SapBERT-from-PubMedBERT-fulltext', 'microsoft/BiomedNLP-PubMedBERT-base-uncased-abstract',
  14. 'microsoft/BiomedNLP-PubMedBERT-large-uncased-abstract'] # 模型名称列表
  15. model_index = 2 # 根据上面选择使用的模型,这里填对应的模型索引
  16. model_name = models[model_index-1] # 使用的模型名称
  17. continue_train = False # 是否继续训练
  18. show_val = False # 是否显示验证过程

定义模型类:

  1. # 定义模型
  2. class MODEL(nn.Module):
  3. def __init__(self, model_index):
  4. super(MODEL, self).__init__()
  5. # 若是第一次下载权重,则下载至同级目录的./premodels/内,以防占主目录的存储空间
  6. self.model = AutoModel.from_pretrained(opt.models[model_index-1], cache_dir='./premodels/'+opt.models[model_index-1]+'_saved', from_tf=False) # 加载预训练语言模型
  7. # 加载模型配置,可以直接获得模型最后一层的维度,而不需要手动修改
  8. config = AutoConfig.from_pretrained(opt.models[model_index-1], cache_dir='./premodels/'+opt.models[model_index-1]+'_saved') # 获取配置
  9. last_dim = config.hidden_size # 最后一层的维度
  10. if opt.use_BCE:out_size = 1 # 损失函数如果使用BCE,则输出大小为1
  11. else :out_size = 2 # 否则则使用CE,输出大小为2
  12. feature_size = 128 # 设置特征的维度大小
  13. self.fc1 = nn.Linear(last_dim, feature_size) # 全连接层1
  14. self.fc2 = nn.Linear(last_dim, feature_size) # 全连接层2
  15. self.classifier = nn.Linear(feature_size, out_size) # 分类器
  16. self.dropout = nn.Dropout(0.3) # Dropout层
  17. def forward(self, x): #BP
  18. input_ids, attention_mask, token_type_ids = x[:,0],x[:,1],x[:,2] # 获取输入
  19. x = self.model(input_ids, attention_mask) # 通过模型
  20. all_token = x[0] # 全部序列分词的表征向量
  21. pooled_output = x[1] # [CLS]的表征向量+一个全连接层+Tanh激活函数
  22. feature1 = all_token.mean(dim=1) # 对全部序列分词的表征向量取均值
  23. feature1 = self.fc1(feature1) # 再输入进全连接层,得到feature1
  24. feature2 = pooled_output # [CLS]的表征向量+一个全连接层+Tanh激活函数
  25. feature2 = self.fc2(feature2) # 再输入进全连接层,得到feature2
  26. feature = 0.5*feature1 + 0.5*feature2 # 加权融合特征
  27. feature = self.dropout(feature) # Dropout
  28. x = self.classifier(feature) # 分类
  29. return x

 

数据加载:

  1. def load_data():
  2. #数据集路径
  3. train_data_path = f'models_input_files/x_train{model_index}.npy'
  4. train_label_path = f'models_input_files/y_train{model_index}.npy'
  5. val_data_path = f'models_input_files/x_val{model_index}.npy'# 验证集
  6. val_label_path = f'models_input_files/y_val{model_index}.npy'# 验证集标签
  7. test_data_path = f'models_input_files/x_test{model_index}.npy'# 测试集输入
  8. #数据集读取
  9. #data=torch.tensor([path],allow_pickle=True).tolist())
  10. train_data = torch.tensor(np.load(train_data_path , allow_pickle=True).tolist())
  11. train_label = torch.tensor(np.load(train_label_path , allow_pickle=True).tolist()).long()
  12. val_data = torch.tensor(np.load(val_data_path , allow_pickle=True).tolist())
  13. val_label = torch.tensor(np.load(val_label_path , allow_pickle=True).tolist()).long()
  14. test_data = torch.tensor(np.load(test_data_path , allow_pickle=True).tolist())
  15. #构造训练集、验证集、测试集
  16. train_dataset = torch.utils.data.TensorDataset(train_data , train_label)
  17. val_dataset = torch.utils.data.TensorDataset(val_data , val_label)
  18. test_dataset = torch.utils.data.TensorDataset(test_data)
  19. return train_dataset, val_dataset, test_dataset # 返回数据集

模型预训练:

  1. def model_pretrain(model_index, train_loader, val_loader):
  2. # 超参数设置
  3. set_epoch = opt.set_epoch # 训练轮数
  4. early_stop = opt.early_stop # 提前停止epoch数
  5. learning_rate = opt.learning_rate # 学习率
  6. weight_decay = opt.weight_decay # 权重衰减
  7. device = opt.device # 设备
  8. gpu_num = opt.gpu_num # GPU个数
  9. continue_train = opt.continue_train # 是否继续训练
  10. model_save_dir = 'checkpoints' # 模型保存路径
  11. # 是否要继续训练,若是,则加载模型进行训练;若否,则跳过训练,直接对测试集进行推理
  12. if not continue_train:
  13. # 判断最佳模型是否已经存在,若存在则直接读取,若不存在则进行训练
  14. if os.path.exists(f'checkpoints/best_model{model_index}.pth'):
  15. best_model = MODEL(model_index)
  16. best_model.load_state_dict(torch.load(f'checkpoints/best_model{model_index}.pth')) # 加载模型
  17. return best_model
  18. else:
  19. pass
  20. # 模型初始化
  21. model = MODEL(model_index).to(device)
  22. if continue_train:
  23. model.load_state_dict(torch.load(f'checkpoints/best_model{model_index}.pth')) # 继续训练加载模型
  24. # 优化器初始化
  25. if device != 'cpu' and gpu_num > 1: # 多张显卡
  26. optimizer = torch.optim.AdamW(model.module.parameters(), lr=learning_rate, weight_decay=weight_decay)
  27. optimizer = torch.nn.DataParallel(optimizer, device_ids=list(range(gpu_num))) # 多GPU
  28. else: # 单张显卡
  29. optimizer = torch.optim.AdamW(model.parameters(), lr=learning_rate, weight_decay=weight_decay) # 单GPU
  30. # 损失函数初始化
  31. if opt.use_BCE:
  32. loss_func = nn.BCEWithLogitsLoss() # BCE损失
  33. else:
  34. loss_func = nn.CrossEntropyLoss() # 交叉熵损失(CE)
  35. # 模型训练
  36. best_epoch = 0 # 最佳epoch
  37. best_train_loss = 100000 # 最佳训练损失
  38. train_acc_list = [] # 训练准确率列表
  39. train_loss_list = [] # 训练损失列表
  40. val_acc_list = [] # 验证准确率列表
  41. val_loss_list = [] # 验证损失列表
  42. start_time = time.time() # 训练开始时间
  43. for epoch in range(set_epoch): # 轮数
  44. model.train() # 模型切换到训练模式
  45. train_loss = 0 # 训练损失
  46. train_acc = 0 # 训练准确率
  47. for x, y in tqdm(train_loader): # 遍历训练集
  48. # 训练前先将数据放到GPU上
  49. x = x.to(device)
  50. y = y.to(device)
  51. outputs = model(x) # 前向传播
  52. if opt.use_BCE: # BCE损失
  53. loss = loss_func(outputs, y.float().unsqueeze(1))
  54. else: # 交叉熵损失
  55. loss = loss_func(outputs, y)
  56. train_loss += loss.item() # 累加训练损失
  57. optimizer.zero_grad() # 清空梯度
  58. loss.backward() # 反向传播
  59. if device != 'cpu' and gpu_num > 1: # 多GPU更新
  60. optimizer.module.step()
  61. else:
  62. optimizer.step() # 单GPU更新
  63. if not opt.use_BCE: # 非BCE损失
  64. _, predicted = torch.max(outputs.data, 1) # 预测结果
  65. else:
  66. predicted = (outputs > 0.5).int() # 预测结果
  67. predicted = predicted.squeeze(1)
  68. train_acc += (predicted == y).sum().item() # 计算训练准确率
  69. average_mode = 'binary'
  70. # 计算F1、Precision、Recall
  71. train_f1 = metrics.f1_score(y.cpu(), predicted.cpu(), average=average_mode)
  72. train_pre = metrics.precision_score(y.cpu(), predicted.cpu(), average=average_mode)
  73. train_recall = metrics.recall_score(y.cpu(), predicted.cpu(), average=average_mode)
  74. train_loss /= len(train_loader) # 平均所有步数的训练损失作为一个epoch的训练损失
  75. train_acc /= len(train_loader.dataset) # 平均所有步数训练准确率作为一个epoch的准确率
  76. train_acc_list.append(train_acc) # 添加训练准确率
  77. train_loss_list.append(train_loss) # 添加训练损失
  78. print('-'*50)
  79. print('Epoch [{}/{}]\n Train Loss: {:.4f}, Train Acc: {:.4f}'.format(epoch + 1, set_epoch, train_loss, train_acc))
  80. print('Train-f1: {:.4f}, Train-precision: {:.4f} Train-recall: {:.4f}'.format(train_f1, train_pre, train_recall))
  81. if opt.show_val: # 显示验证过程
  82. # 验证
  83. model.eval() # 模型切换到评估模式
  84. val_loss = 0 # 验证损失
  85. val_acc = 0 # 验证准确率
  86. for x, y in tqdm(val_loader): # 遍历验证集
  87. # 训练前先将数据放到GPU上
  88. x = x.to(device)
  89. y = y.to(device)
  90. outputs = model(x) # 前向传播
  91. if opt.use_BCE: # BCE损失
  92. loss = loss_func(outputs, y.float().unsqueeze(1))
  93. else: # 交叉熵损失
  94. loss = loss_func(outputs, y)
  95. val_loss += loss.item() # 累加验证损失
  96. if not opt.use_BCE: # 非BCE损失
  97. _, predicted = torch.max(outputs.data, 1)
  98. else:
  99. predicted = (outputs > 0.5).int() # 预测结果
  100. predicted = predicted.squeeze(1)
  101. val_acc += (predicted == y).sum().item() # 计算验证准确率
  102. #计算F1、Precision、Recall
  103. val_f1 = metrics.f1_score(y.cpu(), predicted.cpu(), average=average_mode)
  104. val_pre = metrics.precision_score(y.cpu(), predicted.cpu(), average=average_mode)
  105. val_recall = metrics.recall_score(y.cpu(), predicted.cpu(), average=average_mode)
  106. val_loss /= len(val_loader) # 平均验证损失
  107. val_acc /= len(val_loader.dataset) # 平均验证准确率
  108. val_acc_list.append(val_acc) # 添加验证准确率
  109. val_loss_list.append(val_loss) # 添加验证损失
  110. print('\nVal Loss: {:.4f}, Val Acc: {:.4f}'.format(val_loss, val_acc))
  111. print('Val-f1: {:.4f}, Val-precision: {:.4f} Val-recall: {:.4f}'.format(val_f1, val_pre, val_recall))
  112. if train_loss < best_train_loss: # 更新最佳训练损失
  113. best_train_loss = train_loss
  114. best_epoch = epoch + 1
  115. if device == 'cuda' and gpu_num > 1: # 多GPU保存模型
  116. torch.save(model.module.state_dict(), f'{model_save_dir}/best_model{model_index}.pth')
  117. else:
  118. torch.save(model.state_dict(), f'{model_save_dir}/best_model{model_index}.pth') # 单GPU保存模型
  119. # 提前停止判断
  120. if epoch+1 - best_epoch == early_stop:
  121. print(f'{early_stop} epochs later, the loss of the validation set no longer continues to decrease, so the training is stopped early.')
  122. end_time = time.time()
  123. print(f'Total time is {end_time - start_time}s.')
  124. break
  125. best_model = MODEL(model_index) # 初始化最佳模型
  126. best_model.load_state_dict(torch.load(f'checkpoints/best_model{model_index}.pth')) # 加载模型参数
  127. return best_model # 返回最佳模型

模型推理:

  1. def model_predict(model, model_index, test_loader):
  2. device = 'cuda'
  3. model.to(device) # 模型到GPU
  4. model.eval() # 切换到评估模式
  5. test_outputs = None
  6. with torch.no_grad(): # 禁用梯度计算
  7. for i, data in enumerate(tqdm(test_loader)):
  8. data = data[0].to(device) # 测试数据到GPU
  9. outputs = model(data) # 前向传播
  10. if i == 0:
  11. test_outputs = outputs # 第一个batch直接赋值
  12. else:
  13. test_outputs = torch.cat([test_outputs, outputs], dim=0)
  14. # 其余batch拼接
  15. del data, outputs # 释放不再需要的Tensor
  16. # 保存预测结果
  17. if not opt.use_BCE:
  18. test_outputs = torch.softmax(test_outputs, dim=1) # 转换为概率
  19. torch.save(test_outputs, f'./models_prediction/{model_index}_prob.pth')
  20. # 保存概率

模型训练主函数:

  1. def run(model_index):
  2. # 固定随机种子
  3. seed = opt.seed
  4. torch.seed = seed
  5. np.random.seed(seed)
  6. torch.manual_seed(seed)
  7. torch.cuda.manual_seed(seed)
  8. torch.cuda.manual_seed_all(seed)
  9. torch.backends.cudnn.deterministic = True
  10. train_dataset, val_dataset, test_dataset = load_data() # 加载数据集
  11. # 打印数据集信息
  12. print('-数据集信息:')
  13. print(f'-训练集样本数:{len(train_dataset)},测试集样本数:{len(test_dataset)}')
  14. train_labels = len(set(train_dataset.tensors[1].numpy()))
  15. # 查看训练样本类别均衡状况
  16. print(f'-训练集的标签种类个数为:{train_labels}')
  17. numbers = [0] * train_labels
  18. for i in train_dataset.tensors[1].numpy():
  19. numbers[i] += 1
  20. print(f'-训练集各种类样本的个数:')
  21. for i in range(train_labels):
  22. print(f'-{i}的样本个数为:{numbers[i]}')
  23. batch_size = opt.batch_size # 批处理大小
  24. # 构建DataLoader
  25. train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)
  26. val_loader = torch.utils.data.DataLoader(dataset=val_dataset, batch_size=batch_size, shuffle=True)
  27. test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=False)
  28. best_model = model_pretrain(model_index, train_loader, val_loader)
  29. # 使用验证集评估模型
  30. model_predict(best_model, model_index, test_loader) # 模型推理
  31. if __name__ == '__main__':
  32. model_index = opt.model_index # 获取模型索引
  33. run(model_index) # 运行程序

③ 模型评估

  1. import torch
  2. import pandas as pd
  3. from models_training import MODEL # 从本地文件models_training.py中导入MODEL类
  4. from tqdm import tqdm
  5. from sklearn.metrics import classification_report
  6. import numpy as np
  7. # 推理
  8. def inference(model_indexs, use_BCE):
  9. device = 'cuda' # 设备选择为cuda
  10. for model_index in model_indexs:
  11. # 加载模型
  12. model = MODEL(model_index).to(device) # 创建MODEL类的实例,并将模型移至设备(device)
  13. model.load_state_dict(torch.load(f'checkpoints/best_model{model_index}.pth')) # 加载模型的权重参数
  14. model.eval() # 切换到评估模式
  15. # 加载val数据
  16. val_data_path = f'models_input_files/x_val{model_index}.npy' # val数据的路径
  17. val_data = torch.tensor(np.load(val_data_path, allow_pickle=True).tolist()) # 加载val数据,并转换为Tensor格式
  18. val_dataset = torch.utils.data.TensorDataset(val_data) # 创建val数据集
  19. val_loader = torch.utils.data.DataLoader(dataset=val_dataset, batch_size=32, shuffle=False) # 创建val数据的数据加载器
  20. val_outputs = None # 初始化val_outputs变量
  21. with torch.no_grad(): # 禁用梯度计算
  22. for i, data in enumerate(tqdm(val_loader)): # 遍历val_loader,显示进度条
  23. data = data[0].to(device) # 将数据移至GPU
  24. outputs = model(data) # 模型推理,获取输出
  25. if i == 0:
  26. val_outputs = outputs # 若为第一次迭代,直接赋值给val_outputs
  27. else:
  28. val_outputs = torch.cat([val_outputs, outputs], dim=0)
  29. # 否则在dim=0上拼接val_outputs和outputs
  30. del data, outputs # 释放不再需要的Tensor对象
  31. # 输出预测概率
  32. if not use_BCE:
  33. val_outputs = torch.softmax(val_outputs, dim=1) # 对val_outputs进行softmax操作
  34. torch.save(val_outputs, f'evaluate_prediction/{model_index}_prob.pth') # 保存预测概率结果
  35. def run(model_indexs, use_BCE):
  36. # 读取所有的model_prob.pth,并全加在一起
  37. avg_pred = None # 初始化avg_pred变量
  38. for i in model_indexs:
  39. pred = torch.load(f'evaluate_prediction/{i}_prob.pth').data
  40. # 加载预测概率结果
  41. if use_BCE:
  42. # 选取大于0.5的作为预测结果
  43. pred = (pred > 0.5).int() # 将大于0.5的值转换为整数(0或1)
  44. pred = pred.reshape(-1) # 将预测结果进行形状重塑
  45. else:
  46. # 选取最大的概率作为预测结果
  47. pred = torch.argmax(pred, dim=1) # 获取最大概率的索引作为预测结果
  48. pred = pred.cpu().numpy() # 将预测结果转移到CPU上,并转换为NumPy数组
  49. # to_evaluate
  50. # 读取真实标签
  51. val_label_path = f'models_input_files/y_val{i}.npy' # 真实标签的路径
  52. y_true = np.load(val_label_path) # 加载真实标签
  53. # 分类报告
  54. print(f'model_index = {i}:')
  55. print(classification_report(y_true, pred, digits=4))
  56. # 打印分类报告,包括精确度、召回率等指标
  57. zero_acc = 0; one_acc = 0 # 初始化0类和1类的准确率
  58. zero_num = 0; one_num= 0 # 初始化0类和1类的样本数量
  59. for i in range(pred.shape[0]):
  60. if y_true[i] == 0:
  61. zero_num += 1 # 统计0类的样本数量
  62. elif y_true[i] == 1:
  63. one_num += 1 # 统计1类的样本数量
  64. if pred[i] == y_true[i]:
  65. if pred[i] == 0:
  66. zero_acc += 1 # 统计0类的正确预测数量
  67. elif pred[i] == 1:
  68. one_acc += 1 # 统计1类的正确预测数量
  69. zero = np.sum(pred == 0) / pred.shape[0] # 计算预测为0类的样本占比
  70. zero_acc /= zero_num # 计算0类的正确率
  71. print(f'预测0类占比:{zero} 0类正确率:{zero_acc}')
  72. one = np.sum(pred == 1) / pred.shape[0] # 计算预测为1类的样本占比
  73. one_acc /= one_num # 计算1类的正确率
  74. print(f'预测1类占比:{one} 1类正确率:{one_acc}')
  75. print('-' * 80)
  76. if __name__ == '__main__':
  77. use_BCE = False # 是否使用BCE损失函数的标志,这里我只用交叉熵CE,所以是False
  78. inference([2], use_BCE=use_BCE) # 进行推理,传入模型索引和use_BCE标志
  79. model_indexs = [2] # 模型索引列表
  80. run(model_indexs, use_BCE=use_BCE) # 进行运行,传入模型索引和use_BCE标志

④ 测试集推理

  1. import torch
  2. import pandas as pd
  3. import warnings # 过滤警告
  4. warnings.filterwarnings('ignore')
  5. def run(model_indexs, use_BCE):
  6. # 记录模型数量
  7. model_num = len(model_indexs)
  8. # 读取所有的model_prob.pth,并全加在一起
  9. for i in model_indexs:
  10. # 加载模型在训练完成后对测试集推理所得的预测文件
  11. pred = torch.load(f'./models_prediction/{i}_prob.pth', map_location='cpu').data
  12. # 这里的操作是将每个模型对测试集推理的概率全加在一起
  13. if i == model_indexs[0]:
  14. avg_pred = pred
  15. else:
  16. avg_pred += pred
  17. # 取平均
  18. avg_pred /= model_num # 使用全加在一起的预测概率除以模型数量
  19. if use_BCE:
  20. # 选取概率大于0.5的作为预测结果
  21. pred = (avg_pred > 0.5).int()
  22. pred = pred.reshape(-1)
  23. else:
  24. # 后处理 - 根据标签数目的反馈,对预测阈值进行调整
  25. pred[:, 0][pred[:, 0]>0.001] = 1
  26. pred[:, 1][pred[:, 1]>0.999] = 1.2
  27. # 选取最大的概率作为预测结果
  28. pred = torch.argmax(avg_pred, dim=1)
  29. pred = pred.cpu().numpy()
  30. # to_submit
  31. # 读取test.csv文件
  32. test = pd.read_csv('./dataset/testB_submit_exsample.csv')
  33. # 开始写入预测结果
  34. for i in range(len(pred)):
  35. test['label'][i] = pred[i]
  36. print(test['label'].value_counts())
  37. # 保存为提交文件
  38. test.to_csv(f'submit.csv',index=False)
  39. if __name__ == '__main__':
  40. run([2], use_BCE=False)
  41. # run([1,2,3,4,5,6,7,8,9,10], use_BCE=False)

 

模型优化的思路:

超参数调整、最大序列长度调整、损失函数更改、模型参数冻结

特征工程、模型集成、对比学习、提示学习サ

 

ChatGML2-6B

LLMs:自回归模型

Pretrained => prompt、finetune => RLHF 强化对齐学习

 

LoRA低秩适应:冻结预训练好的模型权重参数,在冻结原模型参数的情况下,通过往模型中加入额外的网络层,并只训练这些新增的网络层参数。

「instruction --> 」「input: X」「output: Y」

P-tuning v2:在原有的大型语言模型上添加一些新的参数,这些新的参数可以帮助模型更好地理解和处理特定的任务。

微调应用:垂直领域、个性化

 

在阿里云Pytorch环境中,克隆代码、下载chatglm2-6b模型,

安装依赖,并且运行训练脚本。

xfg_train.sh

  1. CUDA_VISIBLE_DEVICES=0 python src/train_bash.py \
  2. --model_name_or_path chatglm2-6b \ 本地模型的目录
  3. --stage sft \ 微调方法
  4. --use_v2 \ 使用glm2模型微调,默认值true
  5. --do_train \ 是否训练,默认值true
  6. --dataset paper_label \ 数据集名字
  7. --finetuning_type lora \
  8. --lora_rank 8 \ LoRA 微调中的秩大小
  9. --output_dir ./output/label_xfg \ 输出lora权重存放目录
  10. --per_device_train_batch_size 4 \ 用于训练的批处理大小
  11. --gradient_accumulation_steps 4 \ 梯度累加次数
  12. --lr_scheduler_type cosine \
  13. --logging_steps 10 \ 日志输出间隔
  14. --save_steps 1000 \ 断点保存间隔
  15. --learning_rate 5e-5 \ 学习率
  16. --num_train_epochs 4.0 \ 训练轮数
  17. --fp16 是否使用 fp16 半精度 默认值:False

导入数据

  1. import pandas as pd
  2. train_df = pd.read_csv('./csv_data/train.csv')
  3. testB_df = pd.read_csv('./csv_data/testB.csv')

制作数据集

  1. res = [] #存储数据样本
  2. for i in range(len(train_df)):# 遍历训练数据的每一行
  3. paper_item = train_df.loc[i] # 获取当前行的数据
  4. # 创建一个字典,包含LoRA的指令、输入和输出信息
  5. tmp = {
  6. "instruction": "Please judge whether it is a medical field paper according to the given paper title and abstract, output 1 or 0, the following is the paper title and abstract -->",
  7. "input": f"title:{paper_item[1]},abstract:{paper_item[3]}",
  8. "output": str(paper_item[5])
  9. }
  10. res.append(tmp) # 将字典添加到结果列表中
  11. import json #用于保存数据集
  12. # 将制作好的数据集保存到data目录下
  13. with open('./data/paper_label.json', mode='w', encoding='utf-8') as f:
  14. json.dump(res, f, ensure_ascii=False, indent=4)

修改data/data_info.json

  1. {
  2. "paper_label": {
  3. "file_name": "paper_label.json"
  4. }
  5. }

加载训练好的LoRA权重,进行预测

  1. from peft import PeftModel
  2. from transformers import AutoTokenizer, AutoModel, GenerationConfig, AutoModelForCausalLM
  3. # 定义预训练模型的路径
  4. model_path = "../chatglm2-6b"
  5. model = AutoModel.from_pretrained(model_path, trust_remote_code=True).half().cuda()
  6. tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
  7. # 加载 label lora权重
  8. model = PeftModel.from_pretrained(model, './output/label_xfg').half()
  9. model = model.eval()
  10. # 使用加载的模型和分词器进行聊天,生成回复
  11. response, history = model.chat(tokenizer, "你好", history=[])
  12. response

预测函数:

  1. def predict(text):
  2. # 使用加载的模型和分词器进行聊天,生成回复
  3. response, history = model.chat(tokenizer, f"Please judge whether it is a medical field paper according to the given paper title and abstract, output 1 or 0, the following is the paper title and abstract -->{text}", history=[],
  4. temperature=0.01)
  5. return response

预测,导出csv

  1. from tqdm import tqdm #预测过程的进度条
  2. label = [] #存储预测结果
  3. for i in tqdm(range(len(testB_df))): # 遍历测试集中的每一条样本
  4. test_item = testB_df.loc[i] # 测试集中的每一条样本
  5. # 构建预测函数的输入:prompt
  6. test_input = f"title:{test_item[1]},author:{test_item[2]},abstract:{test_item[3]}"
  7. label.append(int(predict(test_input)))# 预测结果存入lable列表
  8. testB_df['label'] = label # 把label列表存入testB_df
  9. # task1虽然只需要label,但需要有一个keywords列,用个随意的字符串代替
  10. testB_df['Keywords'] = ['tmp' for _ in range(2000)]
  11. # 制作submit,提交submit
  12. submit = testB_df[['uuid', 'Keywords', 'label']]
  13. submit.to_csv('submit.csv', index=False)

提交结果:

7661b0e382974b8c935fa8a6e0827141.png

 ライト

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/348173
推荐阅读
相关标签
  

闽ICP备14008679号