任务一:
比赛链接:
任务描述与分析:
任务一为通过论文作者,标题和关键词确定文章类型,我们主要目标是应尽量突出那些有鲜明文章特色的词语,来确保分类尽可能准确。
baseline模型(基于BOW特征提取的方法)
# 导入pandas用于读取表格数据 import pandas as pd # 导入BOW(词袋模型),可以选择将CountVectorizer替换为TfidfVectorizer(TF-IDF(词频-逆文档频率)),注意上下文要同时修改,亲测后者效果更佳 from sklearn.feature_extraction.text import CountVectorizer # 导入LogisticRegression回归模型 from sklearn.linear_model import LogisticRegression # 过滤警告消息 from warnings import simplefilter from sklearn.exceptions import ConvergenceWarning simplefilter("ignore", category=ConvergenceWarning) # 读取数据集 train = pd.read_csv('/home/aistudio/data/data231041/train.csv') train['title'] = train['title'].fillna('') train['abstract'] = train['abstract'].fillna('') test = pd.read_csv('/home/aistudio/data/data231041/testB.csv') test['title'] = test['title'].fillna('') test['abstract'] = test['abstract'].fillna('') # 提取文本特征,生成训练集与测试集 train['text'] = train['title'].fillna('') + ' ' + train['author'].fillna('') + ' ' + train['abstract'].fillna('')+ ' ' + train['Keywords'].fillna('') test['text'] = test['title'].fillna('') + ' ' + test['author'].fillna('') + ' ' + test['abstract'].fillna('') vector = CountVectorizer().fit(train['text']) train_vector = vector.transform(train['text']) test_vector = vector.transform(test['text']) # 引入模型 model = LogisticRegression() # 开始训练,这里可以考虑修改默认的batch_size与epoch来取得更好的效果 model.fit(train_vector, train['label']) # 利用模型对测试集label标签进行预测 test['label'] = model.predict(test_vector) test['Keywords'] = test['title'].fillna('') test[['uuid','Keywords','label']].to_csv('submit_task1.csv', index=None)
基于TF-IDF特征提取的方法(0.67116→0.76324)
baseline模型基于BOW方法提取特征数据,简单来说BOW是一种统计某个词在文章中出现次数的方法,这样的缺陷是有些不是很重要的日常词所占的权重会很大,这样当然不利于我们的模型性能。
在浏览文档后,我决定使用基于TF-IDF的方法,TF-IDF计算权重的方法是通过文档频率和逆文档频率相乘得到的。
文档频率:某个词的文档频率为
逆文档频率则为
TF-IDF值则为文档频率乘以逆文档频率
这样每个词在每个文档中都会有一个TF-IDF值,由于像and这种常用词的逆文档频率会比其他特征词低,这样可以有效提高模型性能。
提交后分数上涨了10%
# 导入pandas用于读取表格数据 import pandas as pd # 导入BOW(词袋模型),可以选择将CountVectorizer替换为TfidfVectorizer(TF-IDF(词频-逆文档频率)),注意上下文要同时修改,亲测后者效果更佳 from sklearn.feature_extraction.text import CountVectorizer from sklearn.feature_extraction.text import TfidfVectorizer # 导入LogisticRegression回归模型 from sklearn.linear_model import LogisticRegression # 过滤警告消息 from warnings import simplefilter from sklearn.exceptions import ConvergenceWarning simplefilter("ignore", category=ConvergenceWarning) # 读取数据集 train = pd.read_csv('data/train.csv') stops =[i.strip() for i in open(r'data/stop.txt',encoding='utf-8').readlines()] train['title'] = train['title'].fillna('') train['abstract'] = train['abstract'].fillna('') test = pd.read_csv('data/testB.csv') test['title'] = test['title'].fillna('') test['abstract'] = test['abstract'].fillna('') # 提取文本特征,生成训练集与测试集 train['text'] = train['title'].fillna('') + ' ' + train['author'].fillna('') + ' ' + train['abstract'].fillna('')+ ' ' + train['Keywords'].fillna('') test['text'] = test['title'].fillna('') + ' ' + test['author'].fillna('') + ' ' + test['abstract'].fillna('') vector = TfidfVectorizer(stop_words=stops).fit(train['text']) train_vector = vector.transform(train['text']) test_vector = vector.transform(test['text']) # 引入模型 model = LogisticRegression() # 开始训练,这里可以考虑修改默认的batch_size与epoch来取得更好的效果 model.fit(train_vector, train['label']) # 利用模型对测试集label标签进行预测 test['label'] = model.predict(test_vector) test['Keywords'] = test['title'].fillna('') test[['uuid','Keywords','label']].to_csv('submit_task1.csv', index=None)
使用bert预处理模型的方法(0.76324→0.99751)
bert模型是一种预训练+微调的语言模型,它有一些独到优势:
1.无需人工标注,这样可以节省人力,同时可以更好地让模型在大量数据上训练,再在下游针对具体的自然语言处理任务进行微调
2.Attention机制,使得模型更加注重于关键词语,为关键词语赋予更多权重,有效提高模型性能
3.新增两个预训练任务,MLM和NSP任务,为模型能更好地处理下游具体任务提供了保障
# 导入前置依赖 import os import pandas as pd import torch from torch import nn from torch.utils.data import Dataset, DataLoader # 用于加载bert模型的分词器 from transformers import AutoTokenizer # 用于加载bert模型 from transformers import BertModel from pathlib import Path batch_size = 16 # 文本的最大长度 text_max_length = 128 # 总训练的epochs数,我只是随便定义了个数 epochs = 50 # 学习率 lr = 3e-5 # 取多少训练集的数据作为验证集 validation_ratio = 0.1 device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') # 每多少步,打印一次loss log_per_step = 50 # 数据集所在位置 dataset_dir = Path("data") os.makedirs(dataset_dir) if not os.path.exists(dataset_dir) else '' # 模型存储路径 model_dir = Path("./model/bert_checkpoints") # 如果模型目录不存在,则创建一个 os.makedirs(model_dir) if not os.path.exists(model_dir) else '' print("Device:", device) # 读取数据集,进行数据处理 pd_train_data = pd.read_csv('data/train.csv') pd_train_data['title'] = pd_train_data['title'].fillna('') pd_train_data['abstract'] = pd_train_data['abstract'].fillna('') test_data = pd.read_csv('data/testB.csv') test_data['title'] = test_data['title'].fillna('') test_data['abstract'] = test_data['abstract'].fillna('') pd_train_data['text'] = pd_train_data['title'].fillna('') + ' ' + pd_train_data['author'].fillna('') + ' ' + \ pd_train_data['abstract'].fillna('') + ' ' + pd_train_data['Keywords'].fillna('') test_data['text'] = test_data['title'].fillna('') + ' ' + test_data['author'].fillna('') + ' ' + test_data[ 'abstract'].fillna('') + ' ' + pd_train_data['Keywords'].fillna('') # 从训练集中随机采样测试集 validation_data = pd_train_data.sample(frac=validation_ratio) train_data = pd_train_data[~pd_train_data.index.isin(validation_data.index)] # 构建Dataset class MyDataset(Dataset): def __init__(self, mode='train'): super(MyDataset, self).__init__() self.mode = mode # 拿到对应的数据 if mode == 'train': self.dataset = train_data elif mode == 'validation': self.dataset = validation_data elif mode == 'test': # 如果是测试模式,则返回内容和uuid。拿uuid做target主要是方便后面写入结果。 self.dataset = test_data else: raise Exception("Unknown mode {}".format(mode)) def __getitem__(self, index): # 取第index条 data = self.dataset.iloc[index] # 取其内容 text = data['text'] # 根据状态返回内容 if self.mode == 'test': # 如果是test,将uuid做为target label = data['uuid'] else: label = data['label'] # 返回内容和label return text, label def __len__(self): return len(self.dataset) train_dataset = MyDataset('train') validation_dataset = MyDataset('validation') train_dataset.__getitem__(0) # 获取Bert预训练模型 tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased") # 接着构造我们的Dataloader。 # 我们需要定义一下collate_fn,在其中完成对句子进行编码、填充、组装batch等动作: def collate_fn(batch): """ 将一个batch的文本句子转成tensor,并组成batch。 :param batch: 一个batch的句子,例如: [('推文', target), ('推文', target), ...] :return: 处理后的结果,例如: src: {'input_ids': tensor([[ 101, ..., 102, 0, 0, ...], ...]), 'attention_mask': tensor([[1, ..., 1, 0, ...], ...])} target:[1, 1, 0, ...] """ text, label = zip(*batch) text, label = list(text), list(label) # src是要送给bert的,所以不需要特殊处理,直接用tokenizer的结果即可 # padding='max_length' 不够长度的进行填充 # truncation=True 长度过长的进行裁剪 src = tokenizer(text, padding='max_length', max_length=text_max_length, return_tensors='pt', truncation=True) return src, torch.LongTensor(label) train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, collate_fn=collate_fn) validation_loader = DataLoader(validation_dataset, batch_size=batch_size, shuffle=False, collate_fn=collate_fn) inputs, targets = next(iter(train_loader)) print("inputs:", inputs) print("targets:", targets) # 定义预测模型,该模型由bert模型加上最后的预测层组成 class MyModel(nn.Module): def __init__(self): super(MyModel, self).__init__() # 加载bert模型 self.bert = BertModel.from_pretrained('bert-base-uncased', mirror='tuna') # 最后的预测层 self.predictor = nn.Sequential( nn.Linear(768, 256), nn.ReLU(), nn.Linear(256, 1), nn.Sigmoid() ) def forward(self, src): """ :param src: 分词后的推文数据 """ # 将src直接序列解包传入bert,因为bert和tokenizer是一套的,所以可以这么做。 # 得到encoder的输出,用最前面[CLS]的输出作为最终线性层的输入 outputs = self.bert(**src).last_hidden_state[:, 0, :] # 使用线性层来做最终的预测 return self.predictor(outputs) model = MyModel() model = model.to(device) # 定义出损失函数和优化器。这里使用Binary Cross Entropy: criteria = nn.BCELoss() optimizer = torch.optim.Adam(model.parameters(), lr=lr) # 由于inputs是字典类型的,定义一个辅助函数帮助to(device) def to_device(dict_tensors): result_tensors = {} for key, value in dict_tensors.items(): result_tensors[key] = value.to(device) return result_tensors # 定义一个验证方法,获取到验证集的精准率和loss def validate(): model.eval() total_loss = 0. total_correct = 0 for inputs, targets in validation_loader: inputs, targets = to_device(inputs), targets.to(device) outputs = model(inputs) loss = criteria(outputs.view(-1), targets.float()) total_loss += float(loss) correct_num = (((outputs >= 0.5).float() * 1).flatten() == targets).sum() total_correct += correct_num return total_correct / len(validation_dataset), total_loss / len(validation_dataset) # 首先将模型调成训练模式 model.train() # 清空一下cuda缓存 if torch.cuda.is_available(): torch.cuda.empty_cache() # 定义几个变量,帮助打印loss total_loss = 0. # 记录步数 step = 0 # 记录在验证集上最好的准确率 best_accuracy = 0 # 开始训练 for epoch in range(epochs): model.train() for i, (inputs, targets) in enumerate(train_loader): # 从batch中拿到训练数据 inputs, targets = to_device(inputs), targets.to(device) # 传入模型进行前向传递 outputs = model(inputs) # 计算损失 loss = criteria(outputs.view(-1), targets.float()) loss.backward() optimizer.step() optimizer.zero_grad() total_loss += float(loss) step += 1 if step % log_per_step == 0: print("Epoch {}/{}, Step: {}/{}, total loss:{:.4f}".format(epoch + 1, epochs, i, len(train_loader), total_loss)) total_loss = 0 del inputs, targets # 一个epoch后,使用过验证集进行验证 accuracy, validation_loss = validate() print("Epoch {}, accuracy: {:.4f}, validation loss: {:.4f}".format(epoch + 1, accuracy, validation_loss)) torch.save(model, model_dir / f"model_{epoch}.pt") # 保存最好的模型 if accuracy > best_accuracy: torch.save(model, model_dir / f"model_best.pt") best_accuracy = accuracy # 加载最好的模型,然后进行测试集的预测 model = torch.load(model_dir / f"model_best.pt") model = model.eval() test_dataset = MyDataset('test') test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False, collate_fn=collate_fn) results = [] for inputs, ids in test_loader: outputs = model(inputs.to(device)) outputs = (outputs >= 0.5).int().flatten().tolist() ids = ids.tolist() results = results + [(id, result) for result, id in zip(outputs, ids)] test_label = [pair[1] for pair in results] test_data['label'] = test_label test_data['Keywords'] = test_data['title'].fillna('') test_data[['uuid', 'Keywords', 'label']].to_csv('submit_task1.csv', index=None)
深度学习Topline(0.99751→1)
深度学习一个Topline模型的理论和bert模型相似,只是在bert模型的基础上有了些许调整
其大概步骤为
1.数据预处理,将文本数据转化为对应模型的数字序列,并生成掩码ID,最后将它们转化为torch张量,以便输入神经网络中进行训练
2.配置神经网络层和参数,进行训练
3.将训练结果整理保存
具体代码和教程请参考
大语言模型Topline(0.99751→1)
随着ChatGPT的出现,我们对大语言模型这个词已经不陌生了,单纯的大语言模型,我们可以简单理解为一个由庞大的语料库训练成的能理解人类语言基本模式的模型,但是要完成一些具体任务,我们还需要进行对应的操作:指令微调和RLHF(一个强化学习过程),指令微调可以让我们将大语言模型训练成更适合我们需要的样子,例如医疗,法律等等方面,而RLHF则让模型在使用过程中能够不断优化,具体的用大语言处理文本分类任务的教程请参考
常见问题与解决方法
bert模型本地无法运行
由于bert模型所需环境配置较高,可以租用算力来跑程序,可以进入autodl官网,注册后根据需要租用
进入控制台,打开JupyterLab
再在终端导入所需库后上传相关数据集就可以运行程序了
比赛感受
1.认识了很多大佬,能够互相学习
2.了解了NLP任务比赛流程
3.学习了一些处理NLP任务的常用模型和方法
4.获得了一些解决问题的新方法,如跑不动程序去租用算力(以前从来没遇到过跑不动程序的情况)
5.了解了大语言模型的工作流程,对学习路径有了更明确的规划