基于论文摘要的文本分类与关键词抽取挑战赛
赛事背景
赛题地址:https://challenge.xfyun.cn/topic/info?type=abstract-of-the-paper&option=ssgy&ch=ZuoaKcY
医学领域的文献库中蕴含了丰富的疾病诊断和治疗信息,如何高效地从海量文献中提取关键信息,进行疾病诊断和治疗推荐,对于临床医生和研究人员具有重要意义。
赛事任务
本任务分为两个子任务:
- 机器通过对论文摘要等信息的理解,判断该论文是否属于医学领域的文献。
- 提取出该论文关键词。
任务一:使用预训练的BERT模型解决文本二分类问题
#导入前置依赖
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, BertTokenizer
from pathlib import Path
batch_size = 16
# 文本的最大长度
# text_max_length = 128
text_max_length = 256
# 总训练的epochs数,我只是随便定义了个数
epochs = 100
# 学习率
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("./基于论文摘要的文本分类与关键词抽取挑战赛公开数据")
# 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('./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('./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('') + '[SEP]' + pd_train_data['author'].fillna('') + '[SEP]' + pd_train_data['abstract'].fillna('')
test_data['text'] = test_data['title'].fillna('') + '[SEP]' + test_data['author'].fillna('') + '[SEP]' + test_data['abstract'].fillna('')
for idx, (X_train_i,X_test_i) in enumerate(kf.split(pd_train_data)):
train_data = pd_train_data.iloc[X_train_i]
validation_data = pd_train_data.iloc[X_test_i]
train_dataset = MyDataset('train')
validation_dataset = MyDataset('validation')
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)
# model = Bert_model()
model = MyModel()
model = model.to(device)
#定义出损失函数和优化器。这里使用Binary Cross Entropy:
criteria = nn.BCELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
# 首先将模型调成训练模式
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))
ppp = str(idx)+'my_model_best.pt'
# 保存最好的模型
if accuracy > best_accuracy:
torch.save(model, model_dir / ppp)
best_accuracy = accuracy
test_label_all_ = []
for i in range(4):
p = str(i)+'my_model_best.pt'
model = torch.load(model_dir / p)
model = model.eval()
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_label_all_.append(test_label)
test_data['label'] = np.array(test_label_all_+test_label_all).sum(axis=0)>=5
test_data[['uuid', 'label']].to_csv('submit_task8.csv', index=None)
test_label_all = []
for i in range(5):
p = str(i)+'Bert_model_best.pt'
model = torch.load(model_dir / p)
model = model.eval()
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_label_all.append(test_label)
任务二:使用预训练的BERT模型解决关键词提取
# 导入pandas用于读取表格数据
import pandas as pd
# 导入BOW(词袋模型),可以选择将CountVectorizer替换为TfidfVectorizer(TF-IDF(词频-逆文档频率)),注意上下文要同时修改,亲测后者效果更佳
from sklearn.feature_extraction.text import TfidfVectorizer
# 导入Bert模型
from sentence_transformers import SentenceTransformer
# 导入计算相似度前置库,为了计算候选者和文档之间的相似度,我们将使用向量之间的余弦相似度,因为它在高维度下表现得相当好。
from sklearn.metrics.pairwise import cosine_similarity
# 过滤警告消息
from warnings import simplefilter
from sklearn.exceptions import ConvergenceWarning
simplefilter("ignore", category=ConvergenceWarning)
# 读取数据集
test = pd.read_csv('./testB.csv')
test['title'] = test['title'].fillna('')
test['abstract'] = test['abstract'].fillna('')
test['text'] = test['title'].fillna('') + ' ' +test['abstract'].fillna('')
这里使用distiluse-base-multilingual-cased
model = SentenceTransformer(r'xlm-r-distilroberta-base-paraphrase-v1')
提取关键词:获取文本内容的embedding,同与文本标题的embedding进行比较,文章的关键词往往与标题内容有很强的相似性,使用余弦相似度进行度量,选取topk个作为关键词。
test_words = []
for row in tqdm(test.iterrows()):
# 读取第每一行数据的标题与摘要并提取关键词
# 修改n_gram_range来改变结果候选词的词长大小。例如,如果我们将它设置为(3,3),那么产生的候选词将是包含3个关键词的短语。
n_gram_range = (1,3)
# 这里我们使用TF-IDF算法来获取候选关键词
count = TfidfVectorizer(ngram_range=n_gram_range, stop_words='english').fit([row[1].text])
candidates = count.get_feature_names_out()
# 将文本标题以及候选关键词/关键短语转换为数值型数据(numerical data)。我们使用BERT来实现这一目的
title_embedding = model.encode([row[1].title])
candidate_embeddings = model.encode(candidates)
# 通过修改这个参数来更改关键词数量
top_n = 40
# 利用文章标题进一步提取关键词
distances = cosine_similarity(title_embedding, candidate_embeddings)
keywords = [candidates[index] for index in distances.argsort()[0][-top_n:]]
if len( keywords) == 0:
keywords = ['A', 'B']
test_words.append('; '.join( keywords))
与第一个任务的结果合并,提取结果。
sub = pd.read_csv('submit_task8.csv')
sub['Keywords'] = test_words
sub[['uuid', 'Keywords', 'label']].to_csv('submit_task8.csv', index=None)