基于论文摘要的文本分类与关键词抽取挑战赛 · 学习笔记

基于论文摘要的文本分类与关键词抽取挑战赛 · 学习笔记

任务1:文本分类

任务1要求:机器通过对论文摘要等信息的理解,判断该论文是否属于医学领域的文献。

阅读要求可知,本赛事中的文本分类任务为二分类任务。分类任务的做法有很多。如传统机器学习可以使用:KNN、逻辑回归、SVM、朴素贝叶斯、决策树、随机森林、AdaBoost、GBDT、LightGBM、XGBoost。本文选取DataWhale提供的逻辑回归代码作为baseline。

但目前在NLP领域,数据量较为充足时深度学习才是主流,使用BERT等预训练模型做分类任务在打榜中是更常见的做法。本文选取原生BERT作为骨干网络,经过换算在A榜的任务1中取得了0.9869的成绩,盲目优化可能对B榜成绩造成影响,因此本文未在网络结构和参数上进行过多的调整。

1.1 基于sklearn的文本分类

接下来,我们先探讨一下baseline的实践思路。(学习DataWhale提供的实践思路与代码)

  1. 数据预处理:首先,对文本数据进行预处理,包括文本清洗(如:去除特殊字符、标点符号)、分词等操作。可以使用常见的NLP工具包,如英文可以使用:NLTK、spaCy,中文可以使用:jieba、HanLP来辅助进行预处理。
  2. 特征提取:使用BOW(词袋模型)或TF-IDF(词频-逆文档频率)方法将文本转换为向量表示。TF-IDF可以计算文本中词语的重要性,而BOW则简单地统计每个词语在文本中的出现次数(词频统计)。可以使用scikit-learn库的TfidfVectorizer或CountVectorizer来实现特征提取。
  3. 构建训练集和测试集:将预处理后的文本数据分割为训练集和测试集,确保数据集的样本分布均匀。
  4. 选择机器学习模型:根据实际情况选择适合的机器学习模型,如朴素贝叶斯、支持向量机(SVM)、随机森林等。这些模型在文本分类任务中表现良好。可以使用scikit-learn库中相应的分类器进行模型训练和评估。
  5. 模型训练和评估:使用训练集对选定的机器学习模型进行训练,然后使用测试集进行评估。评估指标可以选择准确率、精确率、召回率、F1值等。
  6. 调参优化:如果模型效果不理想,可以尝试调整特征提取的参数(如词频阈值、词袋大小等)或机器学习模型的参数,以获得更好的性能。
# 导入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('./基于论文摘要的文本分类与关键词抽取挑战赛公开数据/train.csv')
train['title'] = train['title'].fillna('')
train['abstract'] = train['abstract'].fillna('')

test = pd.read_csv('./基于论文摘要的文本分类与关键词抽取挑战赛公开数据/test.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('')+ ' ' + train['Keywords'].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[['uuid', 'Keywords', 'label']].to_csv('submit_task1.csv', index=None)
1.2 基于BERT的文本分类

接下来,我们再探讨一下基于BERT的文本分类的实践思路与代码实现。

  1. 导入前置依赖

    #导入前置依赖
    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
    
  2. 设置全局配置

    batch_size = 8
    # 文本的最大长度
    text_max_length = 128
    # 总训练的epochs数,我只是随便定义了个数
    epochs = 15
    # 学习率
    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("./dataset")
    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)batch_size = 8
    # 文本的最大长度
    text_max_length = 128
    # 总训练的epochs数,我只是随便定义了个数
    epochs = 15
    # 学习率
    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("./dataset")
    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)
    
  3. 进行数据读取与数据预处理

    # 读取数据集,进行数据处理
    pd_train_data = pd.read_csv('./dataset/train.csv')
    pd_train_data['title'] = pd_train_data['title'].fillna('')
    pd_train_data['abstract'] = pd_train_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 = pd.read_csv('./dataset/test.csv')
    test_data['title'] = test_data['title'].fillna('')
    test_data['abstract'] = test_data['abstract'].fillna('')
    test_data['text'] = test_data['title'].fillna('') + ' ' +  test_data['author'].fillna('') + ' ' + test_data['abstract'].fillna('')
    
    # 从训练集中随机采样测试集
    validation_data = pd_train_data.sample(frac=validation_ratio)
    train_data = pd_train_data[~pd_train_data.index.isin(validation_data.index)]
    
  4. 构建训练所需的dataset与dataloader

    构建dataset

    # 构建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]
            # 取其内容
            if len(data['text'])<128:
                text = data['text']
            else:
                text_head = data['text'][:64]
                text_tail = data['text'][len(data['text'])-64:len(data['text'])]
                text = text_head + text_tail
            # 根据状态返回内容
            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')
    

    构建tokenizer、collate_fn和dataloader

    #获取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)
    
    
    print("Device:", device)
    
    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)
    
  5. 构建BERT模型

    class MyModel(nn.Module):
    
        def __init__(self):
            super(MyModel, self).__init__()
    
            # 加载bert模型
            self.bert = BertModel.from_pretrained(r"F:\NLP_Test\NLP\2.HuggingFace\NLP_Pre_model\bert-base-uncased")
    
            # 最后的预测层
            self.predictor = nn.Sequential(
                nn.Linear(768, 256),
                nn.Dropout(0.5),
                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, :]
            outputs = self.predictor(outputs)
    
            # 使用线性层来做最终的预测
            return outputs
    
    model = MyModel()
    model = model.to(device)
    
  6. 构建损失函数和优化器

    #定义出损失函数和优化器。这里使用Binary Cross Entropy:
    criteria = nn.BCELoss()
    optimizer = torch.optim.Adam(model.parameters(), lr=lr)
    
  7. 定义验证方法

    #定义一个验证方法,获取到验证集的精准率和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)
    
  8. 定义输入辅助函数

    def to_device(dict_tensors):
        result_tensors = {}
        for key, value in dict_tensors.items():
            result_tensors[key] = value.to(device)
        return result_tensors
    
  9. 模型训练

    # 首先将模型调成训练模式
    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+1, 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
    
  10. 输出结果

    test_dataset = MyDataset('test')
    test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False, collate_fn=collate_fn)
    
    # 加载最好的模型,然后进行测试集的预测
    model = torch.load(model_dir / f"model_best.pt")
    model = model.eval()
    
    results = []
    for inputs, ids in test_loader:
        with torch.no_grad():
            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[['uuid', 'Keywords', 'label']].to_csv('submit_task1.csv', index=None)
    

下表给出基于BERT的文本分类性能(本地验证):

MethodF1
BERT0.9869

任务2:关键词抽取

2.1 基于词频统计+TF-IDF的关键词抽取

我们还是先探讨一下关键词抽取baseline的实践思路。

  1. 数据预处理:定义停用词表,并使用nltk对文本进行分词
  2. 关键词筛选:根据词频筛选出候选关键词
  3. 关键词拼接:将关键词按照submit文件格式进行拼接

在任务1:文本分类的基础上,添加如下代码,即可实现关键词抽取的baseline(学习DataWhale提供的代码)

# 引入分词器
from nltk import word_tokenize, ngrams

# 定义停用词,去掉出现较多,但对文章不关键的词语
stops = [
    'will', 'can', "couldn't", 'same', 'own', "needn't", 'between', "shan't", 'very',
     'so', 'over', 'in', 'have', 'the', 's', 'didn', 'few', 'should', 'of', 'that', 
     'don', 'weren', 'into', "mustn't", 'other', 'from', "she's", 'hasn', "you're",
     'ain', 'ours', 'them', 'he', 'hers', 'up', 'below', 'won', 'out', 'through',
     'than', 'this', 'who', "you've", 'on', 'how', 'more', 'being', 'any', 'no',
     'mightn', 'for', 'again', 'nor', 'there', 'him', 'was', 'y', 'too', 'now',
     'whom', 'an', 've', 'or', 'itself', 'is', 'all', "hasn't", 'been', 'themselves',
     'wouldn', 'its', 'had', "should've", 'it', "you'll", 'are', 'be', 'when', "hadn't",
     "that'll", 'what', 'while', 'above', 'such', 'we', 't', 'my', 'd', 'i', 'me',
     'at', 'after', 'am', 'against', 'further', 'just', 'isn', 'haven', 'down',
     "isn't", "wouldn't", 'some', "didn't", 'ourselves', 'their', 'theirs', 'both',
     're', 'her', 'ma', 'before', "don't", 'having', 'where', 'shouldn', 'under',
     'if', 'as', 'myself', 'needn', 'these', 'you', 'with', 'yourself', 'those',
     'each', 'herself', 'off', 'to', 'not', 'm', "it's", 'does', "weren't", "aren't",
     'were', 'aren', 'by', 'doesn', 'himself', 'wasn', "you'd", 'once', 'because', 'yours',
     'has', "mightn't", 'they', 'll', "haven't", 'but', 'couldn', 'a', 'do', 'hadn',
     "doesn't", 'your', 'she', 'yourselves', 'o', 'our', 'here', 'and', 'his', 'most',
     'about', 'shan', "wasn't", 'then', 'only', 'mustn', 'doing', 'during', 'why',
     "won't", 'until', 'did', "shouldn't", 'which'
]

# 定义方法按照词频筛选关键词

def extract_keywords_by_freq(title, abstract):
    ngrams_count = list(ngrams(word_tokenize(title.lower()), 2)) + list(ngrams(word_tokenize(abstract.lower()), 2))
    ngrams_count = pd.DataFrame(ngrams_count)
    ngrams_count = ngrams_count[~ngrams_count[0].isin(stops)]
    ngrams_count = ngrams_count[~ngrams_count[1].isin(stops)]
    ngrams_count = ngrams_count[ngrams_count[0].apply(len) > 3]
    ngrams_count = ngrams_count[ngrams_count[1].apply(len) > 3]
    ngrams_count['phrase'] = ngrams_count[0] + ' ' + ngrams_count[1]
    ngrams_count = ngrams_count['phrase'].value_counts()
    ngrams_count = ngrams_count[ngrams_count > 1]
    return list(ngrams_count.index)[:5]

## 对测试集提取关键词   

test_words = []
for row in test.iterrows():
    # 读取第每一行数据的标题与摘要并提取关键词
    prediction_keywords = extract_keywords_by_freq(row[1].title, row[1].abstract)
    # 利用文章标题进一步提取关键词
    prediction_keywords = [x.title() for x in prediction_keywords]
    # 如果未能提取到关键词
    if len(prediction_keywords) == 0:
        prediction_keywords = ['A', 'B']
    test_words.append('; '.join(prediction_keywords))
    
test['Keywords'] = test_words
test[['uuid', 'Keywords', 'label']].to_csv('submit_task2.csv', index=None)

下表给出baseline的性能(本地验证):

MethodAccuracy
baseline0.22%

可以看出,baseline完全无法解决A榜的关键词抽取问题,主要原因在于A榜的关键词有可能存在于正文中,但数据集未提供正文,因此baseline无法抽取。(baseline只能抽取见过的单词)

2.2 基于DistilRoBERTa 关键词抽取

接下来,我们再探讨一下基于DistilRoBERTa的关键词抽取的实践思路与代码实现。

  1. 导入前置依赖

    # 导入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)
    
    
  2. 读取数据集并处理

    # 定义停用词,去掉出现较多,但对文章不关键的词语
    stops =[i.strip() for i in open(r'stop.txt',encoding='utf-8').readlines()] 
    
    # 读取数据集
    test = pd.read_csv('./dataset/test.csv')
    test['title'] = test['title'].fillna('')
    test['abstract'] = test['abstract'].fillna('')
    
    test['text'] = test['title'].fillna('') + ' ' +test['abstract'].fillna('')
    
  3. 提取关键词

    Bert预训练模型方面,笔者使用distiluse-base-multilingual-cased,因为Datawhale声称在相似性任务中其表现出了很好的性能,而相似性任务正适用于提取关键词。

    由于transformer模型有token长度限制,所以在输入大型文档时,可能会遇到一些错误。在这种情况下,可以考虑将文档分割成几个小的段落,并对其产生的向量进行平均池化(mean pooling ,要取平均值)。

    但是在本竞赛中,该问题的影响不大,经过前期的数据分析可知75%的数据在258个单词之内,因此长本文对本竞赛的性能影响较为有限,暂时不考虑复杂的做法。(数据分析将会在下一轮笔记中补充)

    在本方案中,提取关键词任务被转化为关键词与标题相似度任务。

    1. 基于sklearn使用TF-IDF算法来获取候选关键词
    2. 分别获取标题和候选关键词的embedding
    3. 通过embedding计算标题与候选关键词之间的距离(计算方式有:曼哈顿距离、欧氏距离、切比雪夫距离、余弦相似度等)。这里我们采用余弦相似度作为距离计算方法。
    4. 对候选关键词进行排序,并提取Top_N个关键词作为最终关键词。
    model = SentenceTransformer('xlm-r-distilroberta-base-paraphrase-v1')
    i = 1
    j = 5
    for row in test.iterrows():
        # 读取第每一行数据的标题与摘要并提取关键词
        # 修改n_gram_range来改变结果候选词的词长大小。例如,如果我们将它设置为(3,3),那么产生的候选词将是包含3个关键词的短语。
        n_gram_range = (i, j)
        # 这里我们使用TF-IDF算法来获取候选关键词
        count = TfidfVectorizer(ngram_range=n_gram_range, stop_words=stops).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 = 15
        # 利用文章标题进一步提取关键词
        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))
    
    test['Keywords'] = test_words
    test[['uuid', 'Keywords']].to_csv(f'distilroberta-({i},{j})key.csv', index=None)
    

细心的读者应该发现了,这里已经将关键词抽取任务转化为了文本相似度任务。因此可以直接类比出以下其他方案:

  1. 比较候选关键词与摘要之间的相似度
  2. 比较候选关键词与标题+摘要之间的相似度
  3. 比较候选关键词与标题+摘要+作者之间的相似度(比较作者的相似度,我个人主观上认为不合理论,性能表现我猜测也不会好,但是大家可以试一下)

但不管是基于skleanrn的关键词抽取还是基于DistilRoBERTa的关键词抽取,本质上都要求标题或摘要内包含关键词,无法筛选出正文中的关键词(因为数据集没有提供正文)。如何克服该缺点,笔者认为是比较关键的。(尽管Datawhale社群内有人反映B榜会更新数据,但是我个人认为主办方B榜提供的数据仍然可能和A榜类似。)

下表给出基于DistilRoBERTa 关键词抽取的性能结果(本地验证):

MethodAccuracy
1-gram-Top154.86%
2-gram-Top155.49%
3-gram-Top151.65%
4-gram-Top150.16%
5-gram-Top150.06%
(1,3)-gram-Top154.42%
(1,5)-gram-Top151.23%

(备注:N-gram代表关键词由N个词组成,例如1-gram代表关键词均由1个词组成,(1,5)-gram代表关键词可能由1~5个词组成,Top-N代表候选N个关键词)

观察上表可以发现,2-gram-Top15方案得到了最佳准确率5.49%,1-gram-Top15方案的**4.86%**紧随其后,而3/4/5-gram-Top15方案表现均不尽人意。说明在A榜测试集中,关键词主要由1~2个单词组成,如何做好1/2-gram的将拉开大部分参赛选手,而做好3/4/5-gram则能角逐决赛。

为此笔者快速验证了简单融合方案,性能如下表所示(本地验证):

MethodAccuracy
1-gram + 2-gram10.32%
1-gram + 2-gram + 3-gram11.96%
1-gram + … +5-gram(all-gram)12.17%

观察上表不难发现,增幅最大的是1-gram-Top15 + 2-gram-Top15方案,后续方案对准确率确有提升,但是导致候选关键词增长过多。由于题面限制,后两种方案无法使用(使用需要通过某种算法进一步筛选关键词),第一种方案使用也不够优雅。因此,笔者认为应该探索更为有效的关键词抽取方案,融合方案应当作为最后一步增强性能的手段使用,早期进行融合方案无助于比赛成绩提升。

2.3 任务2 存在评价缺陷

任务2的评价方式如下图所示:

img

这让我产生了一个疑惑,按照该评价方式,完全可以抽取足够多的关键词来提升正确率。例如:如果某篇文献有3个关键词,一共预测出5个关键词,其中3个关键词和文献的关键词匹配,按照公式计算,相当于这篇文献的正确率是100%。因此,我快速基于DistilRoBERTa的关键词抽取,做了一轮验证。

验证结果如下(所有验证均为本地验证,与平台验证有差距,但不多):

1-gram2-gram
Top-154.86%5.49%
Top-506.64%8.61%
Top-1007.16%10.08%

(备注:1-gram代表关键词均为1个单词组成,2-gram代表关键词均为2个单词组成)

观察上表可以发现,随着候选关键词的增多,准确率也在不断提高,显然任务2存在评价缺陷。经过向科大讯飞官方反映后,对题面作出了限制,尽管没有解决评价缺陷的问题,但是限制了靠无限增多候选关键词刷分的BUG。

在这里插入图片描述

本学习笔记未完待续,以下为尚待补充的内容:

  1. 数据集数据分析
  2. 基于KeyBERT的关键词抽取
  3. XXXX
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值