网站建设ssc源码平台,flash网站在线diy源码,怎么做五个页面网站,广告平台投放暑期参加了Datawhale的第三期AI夏令营#xff0c;学习的是NLP方向#xff0c;在此期间#xff0c;我们通过比赛打榜的形式进行NLP的学习。今天#xff0c;主要分享和记录一下这一期夏令营的学习历程和笔记。 文章目录 赛题背景赛题任务赛题数据集评价指标解题思路任务一学习的是NLP方向在此期间我们通过比赛打榜的形式进行NLP的学习。今天主要分享和记录一下这一期夏令营的学习历程和笔记。 文章目录 赛题背景赛题任务赛题数据集评价指标解题思路任务一机器学习方法Baseline1. 导入模块2.特征提取2.1 基于 TF-IDF 提取2.2 基于 BOW2.3 停用词 3.划分数据集4.选择机器学习模型5. 数据探索5.1 使用pandas读取数据 6. 数据清洗7. 特征工程8. 模型训练与验证9. 结果输出完整代码如下 二、任务一实践2.1 替换模型2.2 替换特征提取的模型2.3 F1_score评测2.4 数据增强2.5 提交结果 三、深度学习方法3.1 解题思路3.2 BERT介绍3.3 预训练 微调范式3.4 Transformer 与 Attention3.5 预训练任务3.6 实现流程3.6.1 导入模块3.6.2 设置全局配置3.6.3 数据收集与准备3.6.4 构建训练所需的dataloader与dataset3.6.5 定义模型3.6.6 定义出损失函数和优化器3.6.7 定义验证函数3.6.8 模型训练、评估3.6.9 结果输出 赛题背景
该NLP夏令营主要通过比赛打榜的形式进行学习而我们这一期NLP的赛题是基于论文摘要的文本分类。医学领域的文献库中蕴含了丰富的疾病诊断和治疗信息如何高效地从海量文献中提取关键信息进行疾病诊断和治疗推荐对于临床医生和研究人员具有重要意义。所以我们这一期的主要任务是构建一个高精度的模型来实现对论文摘要的文本分类具体点就是通过分析论文摘要将论文分为两类一类是医学类论文一类是非医学类论文。
赛题任务
机器通过对论文摘要等信息的理解判断该论文是否属于医学领域的文献。 任务示例 输入 论文信息格式如下 Inflammatory Breast Cancer: What to Know About This Unique, Aggressive Breast Cancer. [Arjun Menta, Tamer M Fouad, Anthony Lucci, Huong Le-Petross, Michael C Stauder, Wendy A Woodward, Naoto T Ueno, Bora Lim] Inflammatory breast cancer (IBC) is a rare form of breast cancer that accounts for only 2% to 4% of all breast cancer cases. Despite its low incidence, IBC contributes to 7% to 10% of breast cancer caused mortality. Despite ongoing international efforts to formulate better diagnosis, treatment, and research, the survival of patients with IBC has not been significantly improved, and there are no therapeutic agents that specifically target IBC to date. The authors present a comprehensive overview that aims to assess the present and new management strategies of IBC. Breast changes; Clinical trials; Inflammatory breast cancer; Trimodality care. 输出 是(1)
赛题数据集
训练集与测试集数据为CSV格式文件各字段分别是标题、作者、摘要、关键词。
数据集下载链接:https://aistudio.baidu.com/datasetdetail/231041
评价指标
本次竞赛的评价标准采用F1_score分数越高效果越好。
解题思路
文献领域分类 针对文本分类任务可以提供两种实践思路一种是使用传统的特征提取方法如TF-IDF/BOW结合机器学习模型另一种是使用预训练的BERT模型进行建模。 使用特征提取 机器学习的思路步骤如下
数据预处理首先对文本数据进行预处理包括文本清洗如去除特殊字符、标点符号、分词等操作。可以使用常见的NLP工具包如NLTK或spaCy来辅助进行预处理。特征提取使用TF-IDF词频-逆文档频率或BOW词袋模型方法将文本转换为向量表示。TF-IDF可以计算文本中词语的重要性而BOW则简单地统计每个词语在文本中的出现次数。可以使用scikit-learn库的TfidfVectorizer或CountVectorizer来实现特征提取。构建训练集和测试集将预处理后的文本数据分割为训练集和测试集确保数据集的样本分布均匀。选择机器学习模型根据实际情况选择适合的机器学习模型如朴素贝叶斯、支持向量机SVM、随机森林等。这些模型在文本分类任务中表现良好。可以使用scikit-learn库中相应的分类器进行模型训练和评估。模型训练和评估使用训练集对选定的机器学习模型进行训练然后使用测试集进行评估。评估指标可以选择准确率、精确率、召回率、F1值等。调参优化如果模型效果不理想可以尝试调整特征提取的参数如词频阈值、词袋大小等或机器学习模型的参数以获得更好的性能。 我们Baseline选择使用机器学习方法在解决机器学习问题时一般会遵循以下流程
任务一机器学习方法Baseline
在这个 Baseline 中我们使用机器学习的LogisticRegression模型也就是逻辑回归模型。
1. 导入模块
导入我们本次Baseline代码所需的模块
#import 相关库
# 导入pandas用于读取表格数据
import pandas as pd# 导入BOW词袋模型可以选择将CountVectorizer替换为TfidfVectorizerTF-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, categoryConvergenceWarning)2.特征提取
特征提取是机器学习任务中的一个重要步骤。我们将训练数据的每一个维度称为一个特征例如如果我们想要基于二手车的品牌、价格、行驶里程数三个变量来预测二手车的价格则品牌、价格、行驶里程数为该任务的三个特征。所谓特征提取即从训练数据的特征集合中创建新的特征子集的过程。提取出来的特征子集特征数一般少于等于原特征数但能够更好地表征训练数据的情况使用提取出的特征子集能够取得更好的预测效果。对于 NLP、CV 任务我们通常需要将文本、图像特征提取为计算机可以处理的数值向量特征。我们一般可以使用 sklearn 库中的 feature_extraction 包来实现文本与图片的特征提取。
在 NLP 任务中特征提取一般需要将自然语言文本转化为数值向量表示常见的方法包括基于 TF-IDF词频-逆文档频率提取或基于 BOW词袋模型提取等两种方法均在 sklearn.feature_extraction 包中有所实现。
2.1 基于 TF-IDF 提取
TF-IDF(term frequency–inverse document frequency)是一种用于信息检索与数据挖掘的常用加权技术其中TF 指 term frequence即词频指某个词在文章中出现次数与文章总词数的比值IDF 指 inverse document frequence即逆文档频率指包含某个词的文档数占语料库总文档数的比例。例如假设语料库为 {“今天 天气 很好”,“今天 心情 很 不好”, “明天 天气 不好”}每一个句子为一个文档则“今天”的 TF 和 IDF 分别为 T F ( 今天 ∣ 文档 1 词在文档一的出现频率 文档一的总词数 1 3 TF(今天|文档1 \frac{词在文档一的出现频率}{文档一的总词数} \frac{1}{3} TF(今天∣文档1文档一的总词数词在文档一的出现频率31 T F ( 今天 ∣ 文档 2 词在文档二的出现频率 文档二的总词数 1 4 TF(今天|文档2 \frac{词在文档二的出现频率}{文档二的总词数} \frac{1}{4} TF(今天∣文档2文档二的总词数词在文档二的出现频率41 T F ( 今天 ∣ 文档 3 0 TF(今天|文档3 0 TF(今天∣文档30 I D F ( 今天 l o g 语料库文档总数 出现该词的文档数 l o g 3 2 IDF(今天 log\frac{语料库文档总数}{出现该词的文档数} log\frac{3}{2} IDF(今天log出现该词的文档数语料库文档总数log23 每个词最终的 IF-IDF 即为 TF 值乘以 IDF 值。计算出每个词的 TF-IDF 值后使用 TF-IDF 计算得到的数值向量替代原文本即可实现基于 TF-IDF 的文本特征提取。
我们可以使用 sklearn.feature_extraction.text 中的 TfidfVectorizer 类来简单实现文档基于 TF-IDF 的特征提取
# 首先导入该类
from sklearn.feature_extraction.text import TfidfVectorizer# 假设我们已从本地读取数据为 DataFrame 类型并已经过基本预处理data 为已处理的 DataFrame 数据
# 实例化一个 TfidfVectorizer 对象并使用 fit 方法来拟合数据
vector TfidfVectorizer().fit(data[text])# 拟合之后调用 transform 方法即可得到提取后的特征数据
train_vector vector.transform()2.2 基于 BOW
BOWBag of Words是一种常用的文本表示方法其基本思想是假定对于一个文本忽略其词序和语法、句法仅仅将其看做是一些词汇的集合而文本中的每个词汇都是独立的。简单说就是讲每篇文档都看成一个袋子因为里面装的都是词汇所以称为词袋Bag of words即因此而来然后看这个袋子里装的都是些什么词汇将其分类。具体而言词袋模型表示一个文本首先会维护一个词库词库里维护了每一个词到一个数值向量的映射关系。例如最简单的映射关系是独热编码假设词库里一共有四个词今天、天气、很、不好那么独热编码会将四个词分别编码为 今天—— 1 , 0 , 0 , 0 天气—— 0 , 1 , 0 , 0 很—— 0 , 0 , 1 , 0 不好—— 0 , 0 , 0 , 1 今天——1,0,0,0\\ 天气——0,1,0,0\\ 很 ——0,0,1,0\\ 不好——0,0,0,1 今天——1,0,0,0天气——0,1,0,0很——0,0,1,0不好——0,0,0,1 而使用词袋模型就会将上述这句话编码为 B O W ( S e n t e n c e E m b e d d i n g ( 今天 ) E m b e d d i n g ( 天气 ) E m b e d d i n g ( 很 ) E m b e d d i n g ( 不好 ) ( 1 , 1 , 1 , 1 BOW(Sentence Embedding(今天) Embedding(天气) Embedding(很) Embedding(不好) (1,1,1,1 BOW(SentenceEmbedding(今天)Embedding(天气)Embedding(很)Embedding(不好)(1,1,1,1 我们一般使用 sklearn.feature_extraction.text 中的 CountVectorizer 类来简单实现文档基于频数统计的 BOW 特征提取其主要方法与 TfidfVectorizer 的主要使用方法一致
# 首先导入该类
from sklearn.feature_extraction.text import CountVectorizer# 假设我们已从本地读取数据为 DataFrame 类型并已经过基本预处理data 为已处理的 DataFrame 数据
# 实例化一个 CountVectorizer 对象并使用 fit 方法来拟合数据
vector CountVectorizer().fit(data[text])# 拟合之后调用 transform 方法即可得到提取后的特征数据
train_vector vector.transform()2.3 停用词
停用词(Stop Words)是自然语言处理领域的一个重要工具通常被用来提升文本特征的质量或者降低文本特征的维度。
当我们在使用TF-IDF或者BOW模型来表示文本时总会遇到一些问题。
在特定的NLP任务中一些词语不能提供有价值的信息作用、可以忽略。这种情况在生活里也非常普遍。以本次学习任务为例我们希望医学类的词语在特征提取时被突出对于不是医学类词语的数据就应该考虑让他在特征提取时被淡化同时一些日常生活中使用频率过高而普遍出现的词语我们也应该选择忽略这些词语以防对我们的特征提取产生干扰。
举个例子我们依然以讲解BOW模型时举的这个例子介绍 B O W ( S e n t e n c e E m b e d d i n g ( 今天 ) E m b e d d i n g ( 天气 ) E m b e d d i n g ( 很 ) E m b e d d i n g ( 不好 ) ( 1 , 1 , 1 , 1 BOW(Sentence Embedding(今天) Embedding(天气) Embedding(很) Embedding(不好) (1,1,1,1 BOW(SentenceEmbedding(今天)Embedding(天气)Embedding(很)Embedding(不好)(1,1,1,1 当我们需要对这句话进行情感分类时我们就需要突出它的情感特征也就是我们希望‘不好’这个词在经过BOW模型编码后的值能够大一点
但是如果我们不使用停用词那么“今天天气很好还是不好”这句话经过BOW模型编码后的值就会与上面这句话的编码高度相似从而严重影响模型判断的结果。
那么我们如何使用停用词解决这个问题呢理想一点我们将除了情感元素的词语全部停用也就是编码时不考虑仅保留情感词语也就是判断句子中好这个词出现的多还是少很明显好这个词出现的多那情感很显然就是正向的。
对于本次任务而言日常生活中出现的词语可能都对模型分类很难有太大帮助例如oragainand下面贴出我收集的一些对任务可能没有那么多帮助的词语文件。 stop.txt文件链接链接: https://pan.baidu.com/s/1mQ50_gsKZHWERHzfiDnheg?pwdqzuc 提取码: qzuc 如何使用这个文件呢利用下面所示方法读取该文件
stops [i.strip() for i in open(rstop.txt,encodingutf-8).readlines()] 读取这个文件后在使用CountVectorizer()方法时指定stop_words参数为stops就可以。
vector CountVectorizer(stop_wordsstops).fit(train[text])3.划分数据集
在机器学习任务中我们一般会有三个数据集训练集、验证集、预测集。训练集为我们训练模型的拟合数据是我们前期提供给模型的输入验证集一般是我们自行划分出来验证模型效果以选取最优超参组合的数据集测试集是最后检验模型效果的数据集。例如在本期竞赛任务中比赛方提供的 test.csv 就是预测集我们最终的任务是建立一个模型在预测集上实现较准确的预测。但是预测集一般会限制预测次数例如在本期比赛中每人每天仅能提交三次但是我们知道机器学习模型一般有很多超参数为了选取最优的超参组合我们一般需要多次对模型进行验证即提供一部分数据让已训练好的模型进行预测来找到预测准确度最高的模型。
因此我们一般会将比赛方提供的训练集也就是 train.csv 进行划分划分为训练集和验证集。我们会使用划分出来的训练集进行模型的拟合和训练而使用划分出来的验证集验证不同参数及不同模型的效果来找到最优的模型及参数再在比赛方提供的预测集上预测最终结果。
划分数据集的方法有很多基本原则是同分布采样。即我们划分出来的验证集和训练集应当是同分布的以免验证不准确事实上最终的预测集也应当和训练集、验证集同分布。此处我们介绍交叉验证即对于一个样本总量为 T 的数据集我们一般随机采样 10%~20%也就是 0.1T~0.2T 的样本数作为验证集而取其他的数据为训练集。如要了解更多的划分方法可查阅该博客https://blog.csdn.net/hcxddd/article/details/119698879。我们可以使用 sklearn.model_selection 中的 train_test_split 函数便捷实现数据集的划分
baseline中并没有划分验证集你也可以自行划分验证集来观察训练中的准确率
from sklearn.model_selection import train_test_split# 该函数将会根据给定比例将数据集划分为训练集与验证集
trian_data, eval_data train_test_split(data, test_size 0.2)
# 参数 data 为总数据集可以是 DataFrame 类型
# 参数 test_size 为划分验证集的占比此处选择0.2即划分20%样本作为验证集 4.选择机器学习模型
我们可以选择多种机器学习模型来拟合训练数据不同的业务场景、不同的训练数据往往最优的模型也不同。常见的模型包括线性模型、逻辑回归、决策树、支持向量机、集成模型、神经网络等。想要深入学习各种机器学习模型的同学推荐学习《西瓜书》或《统计学习方法》。 Sklearn 封装了多种机器学习模型常见的模型都可以在 sklearn 中找到sklearn 根据模型的类别组织在不同的包中此处介绍几个常用包
sklearn.linear_model线性模型如线性回归、逻辑回归、岭回归等sklearn.tree树模型一般为决策树sklearn.neighbors最近邻模型常见如 K 近邻算法sklearn.svm支持向量机sklearn.ensemble集成模型如 AdaBoost、GBDT等 本案例中我们使用简单但拟合效果较好的逻辑回归模型作为 Baseline 的模型。此处简要介绍其原理。
逻辑回归模型即 Logistic Regression实则为一个线性分类器通过 Logistic 函数(或 Sigmoid 函数)将数据特征映射到01区间的一个概率值样本属于正例的可能性通过与 0.5 的比对得出数据所属的分类。逻辑回归的数学表达式为 f ( z ) 1 1 e − z f(z) \frac{1}{1 e^{-z}} f(z)1e−z1 z w T x w 0 z w^Tx w_0 zwTxw0
逻辑回归模型简单、可并行化、可解释性强同时也往往能够取得不错的效果是较为通用的模型。 我们可以使用 sklearn.linear_model.LogisticRegression来调用已实现的逻辑回归模型
# 引入模型
model LogisticRegression()
# 可以在初始化时控制超参的取值此处使用默认值具体参数可以查阅官方文档# 开始训练这里可以考虑修改默认的batch_size与epoch来取得更好的效果
# 此处的 train_vector 是已经经过特征提取的训练数据
model.fit(train_vector, train[label])# 利用模型对测试集label标签进行预测此处的 test_vector 同样是已经经过特征提取的测试数据
test[label] model.predict(test_vector)事实上sklearn 提供的多种机器学习模型都封装成了类似的类绝大部分使用方法均和上述一致即先实例化一个模型对象再使用 fit 函数拟合训练数据最后使用 predict 函数预测测试数据即可。
5. 数据探索
数据探索性分析是通过了解数据集了解变量间的相互关系以及变量与预测值之间的关系对已有数据在尽量少的先验假设下通过作图、制表、方程拟合、计算特征量等手段探索数据的结构和规律的一种数据分析方法从而帮助我们后期更好地进行特征工程和建立模型是机器学习中十分重要的一步。 本次baseline实践中我们使用pandas来读取数据以及数据探索。
5.1 使用pandas读取数据
在这部分内容里我们利用pd.read_csv)方法对赛题数据进行读取pd.read_csv)参数为需要读取的数据地址读取后返回一个DataFrame 数据
import pandas as pd
train pd.read_csv(./基于论文摘要的文本分类与关键词抽取挑战赛公开数据/train.csv)
train[title] train[title].fillna()
train[abstract] train[abstract].fillna()test pd.read_csv(./基于论文摘要的文本分类与关键词抽取挑战赛公开数据/testB.csv)
test[title] test[title].fillna()
test[abstract] test[abstract].fillna()通过pandas提供的一些方法我们可以在本地快速查看数据的一些特征 通过DataFrame.apply(len).describe()方法查看数据长度
print(train[text].apply(len).describe())count 6000.000000
mean 1620.251500
std 496.956005
min 286.000000
25% 1351.750000
50% 1598.500000
75% 1885.000000
max 10967.000000
Name: text, dtype: float64观察输出发现数据长度平均值在1620左右 通过DataFrame.value_counts()方法查看数据数量
print(train[label].value_counts())label
0 3079
1 2921
Name: count, dtype: int64观察输出发现0和1标签分布的比较均匀也就是说我们不必担心数据分布不均而发生过拟合保证模型的泛化能力。
6. 数据清洗
数据和特征决定了机器学习的上限而模型和算法只是逼近这个上限而已。俗话说garbage in, garbage out。分析完数据后特征工程前必不可少的步骤是对数据清洗。
数据清洗的作用是利用有关技术如数理统计、数据挖掘或预定义的清理规则将脏数据转化为满足数据质量要求的数据。主要包括缺失值处理、异常值处理、数据分桶、特征归一化/标准化等流程。
同时由于表格中存在较多列我们将这些列的重要内容组合在一起生成一个新的列方便训练。
# 提取文本特征生成训练集与测试集
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()在实践学习中有些同学反映对于fillna()方法存在疑惑pandas中fillna()方法能够使用指定的方法填充NA/NaN值。如果数据集中某行缺少title author abstract中的内容我们需要利用fillna()来保证不会出现报错。
7. 特征工程
特征工程指的是把原始数据转变为模型训练数据的过程目的是获取更好的训练数据特征。 特征工程能使得模型的性能得到提升有时甚至在简单的模型上也能取得不错的效果。 这里我们选择使用BOW将文本转换为向量表示
#特征工程
vector CountVectorizer().fit(train[text])
train_vector vector.transform(train[text])
test_vector vector.transform(test[text])8. 模型训练与验证
特征工程也好数据清洗也罢都是为最终的模型来服务的模型的建立和调参决定了最终的结果。模型的选择决定结果的上限 如何更好的去达到模型上限取决于模型的调参。
建模的过程需要我们对常见的线性模型、非线性模型有基础的了解。模型构建完成后需要掌握一定的模型性能验证的方法和技巧。
# 模型训练
model LogisticRegression()# 开始训练这里可以考虑修改默认的batch_size与epoch来取得更好的效果
model.fit(train_vector, train[label])9. 结果输出
提交结果需要符合提交样例结果
# 利用模型对测试集label标签进行预测
test[label] model.predict(test_vector)
test[Keywords] test[title].fillna()
# 生成任务一推测结果
test[[uuid, Keywords, label]].to_csv(submit_task1.csv, indexNone)完整代码如下
# 导入pandas用于读取表格数据
import pandas as pd# 导入BOW词袋模型可以选择将CountVectorizer替换为TfidfVectorizerTF-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, categoryConvergenceWarning)# 读取数据集
train pd.read_csv(./基于论文摘要的文本分类与关键词抽取挑战赛公开数据/train.csv)
train[title] train[title].fillna()
train[abstract] train[abstract].fillna()test pd.read_csv(./基于论文摘要的文本分类与关键词抽取挑战赛公开数据/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)
# 因为任务一并不涉及关键词提取而提交中需要这一行所以我们用title列填充Keywords列
test[Keywords] test[title].fillna()
# 生成任务一推测结果
test[[uuid, Keywords, label]].to_csv(submit_task1.csv, indexNone)二、任务一实践
2.1 替换模型
刚开始只替换SVM、KNN、决策树、随机森林以及朴素贝叶斯等经典模型其它地方不做修改先找到一个分类性能最好的基础模型再尝试调优。
SVM
# SVM
# 导入pandas用于读取表格数据
import pandas as pd# 导入BOW词袋模型可以选择将CountVectorizer替换为TfidfVectorizerTF-IDF词频-逆文档频率注意上下文要同时修改亲测后者效果更佳
from sklearn.feature_extraction.text import CountVectorizer# 导入LogisticRegression回归模型
from sklearn.svm import SVC
# 过滤警告消息
from warnings import simplefilter
from sklearn.exceptions import ConvergenceWarning
simplefilter(ignore, categoryConvergenceWarning)# 设置惩罚系数
svc SVC(kernellinear,C1)# 读取数据集
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[abstract].fillna() train[Keywords].fillna()
test[text] test[title].fillna() test[abstract].fillna()# 停止词剔除噪音数据
stops [i.strip() for i in open(r/home/aistudio/stop.txt,encodingutf-8).readlines()]
vector CountVectorizer(stop_wordsstops).fit(train[text])
train_vector vector.transform(train[text])
test_vector vector.transform(test[text])# 引入模型
model svc# 开始训练这里可以考虑修改默认的batch_size与epoch来取得更好的效果
model.fit(train_vector, train[label])# 利用模型对测试集label标签进行预测
test[label] model.predict(test_vector)
test[Keywords] test[title].fillna()
print(test)
test[[uuid,Keywords,label]].to_csv(submit_task.csv_svm, indexNone)KNN
# KNN
# 导入pandas用于读取表格数据
import pandas as pd# 导入BOW词袋模型可以选择将CountVectorizer替换为TfidfVectorizerTF-IDF词频-逆文档频率注意上下文要同时修改亲测后者效果更佳
from sklearn.feature_extraction.text import CountVectorizer# 导入KNN
from sklearn.neighbors import KNeighborsClassifier
# 过滤警告消息
from warnings import simplefilter
from sklearn.exceptions import ConvergenceWarning
simplefilter(ignore, categoryConvergenceWarning)# 读取数据集
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[abstract].fillna() train[Keywords].fillna()
test[text] test[title].fillna() test[abstract].fillna()# 停止词剔除噪音数据
stops [i.strip() for i in open(r/home/aistudio/stop.txt,encodingutf-8).readlines()]
vector CountVectorizer(stop_wordsstops).fit(train[text])
train_vector vector.transform(train[text])
test_vector vector.transform(test[text])# 引入模型
# model KNeighborsClassifier(n_neighbors3) # k3
model KNeighborsClassifier(n_neighbors5) # k5# 开始训练这里可以考虑修改默认的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_task_knn5.csv, indexNone)决策树
# 决策树
# 导入pandas用于读取表格数据
import pandas as pd# 导入BOW词袋模型可以选择将CountVectorizer替换为TfidfVectorizerTF-IDF词频-逆文档频率注意上下文要同时修改亲测后者效果更佳
from sklearn.feature_extraction.text import CountVectorizer# 导入决策树
from sklearn.tree import DecisionTreeClassifier
# 过滤警告消息
from warnings import simplefilter
from sklearn.exceptions import ConvergenceWarning
simplefilter(ignore, categoryConvergenceWarning)# 读取数据集
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[abstract].fillna() train[Keywords].fillna()
test[text] test[title].fillna() test[abstract].fillna()# 停止词剔除噪音数据
stops [i.strip() for i in open(r/home/aistudio/stop.txt,encodingutf-8).readlines()]
vector CountVectorizer(stop_wordsstops).fit(train[text])
train_vector vector.transform(train[text])
test_vector vector.transform(test[text])# 引入模型
model DecisionTreeClassifier(max_depth5)# 开始训练这里可以考虑修改默认的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_task_tree5.csv, indexNone)随机森林
# 随机森林
# 导入pandas用于读取表格数据
import pandas as pd
# 导入BOW词袋模型可以选择将CountVectorizer替换为TfidfVectorizerTF-IDF词频-逆文档频率注意上下文要同时修改亲测后者效果更佳
# from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfVectorizer
# 导入随机森林
from sklearn.ensemble import RandomForestClassifier
# 过滤警告消息
from warnings import simplefilter
from sklearn.exceptions import ConvergenceWarning
simplefilter(ignore, categoryConvergenceWarning)# 读取数据集
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[abstract].fillna() train[Keywords].fillna()
test[text] test[title].fillna() test[abstract].fillna()# 停止词剔除噪音数据
stops [i.strip() for i in open(r/home/aistudio/stop.txt,encodingutf-8).readlines()]
vector TfidfVectorizer(stop_wordsstops, ngram_range(1, 2), max_features1000).fit(train[text])
train_vector vector.transform(train[text])
test_vector vector.transform(test[text])# 引入模型
model RandomForestClassifier(n_estimators100) # 设置随机森林的树的数量为100# 开始训练这里可以考虑修改默认的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_task_random_forest.csv, indexNone)朴素贝叶斯
#朴素贝叶斯# 导入pandas用于读取表格数据
import pandas as pd# 导入BOW词袋模型可以选择将CountVectorizer替换为TfidfVectorizerTF-IDF词频-逆文档频率注意上下文要同时修改亲测后者效果更佳
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB # 朴素贝叶斯分类器# 过滤警告消息
from warnings import simplefilter
from sklearn.exceptions import ConvergenceWarning
simplefilter(ignore, categoryConvergenceWarning)# 读取数据集
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[abstract].fillna() train[Keywords].fillna()
test[text] test[title].fillna() test[abstract].fillna()# 停止词剔除噪音数据
stops [i.strip() for i in open(r/home/aistudio/stop.txt,encodingutf-8).readlines()]
vector TfidfVectorizer(stop_wordsstops).fit(train[text])
train_vector vector.transform(train[text])
test_vector vector.transform(test[text])# 引入模型model MultinomialNB() # 改用朴素贝叶斯分类器# 开始训练这里可以考虑修改默认的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_task_MultinomialNB.csv, indexNone)2.2 替换特征提取的模型
# KNN
# 导入pandas用于读取表格数据
import pandas as pd
# 导入BOW词袋模型可以选择将CountVectorizer替换为TfidfVectorizerTF-IDF词频-逆文档频率注意上下文要同时修改亲测后者效果更佳
# from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfVectorizer
# 导入KNN
from sklearn.neighbors import KNeighborsClassifier
# 过滤警告消息
from warnings import simplefilter
from sklearn.exceptions import ConvergenceWarning
simplefilter(ignore, categoryConvergenceWarning)# 读取数据集
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[abstract].fillna() train[Keywords].fillna()
test[text] test[title].fillna() test[abstract].fillna()# 停止词剔除噪音数据
stops [i.strip() for i in open(r/home/aistudio/stop.txt,encodingutf-8).readlines()]
vector TfidfVectorizer(stop_wordsstops).fit(train[text])
train_vector vector.transform(train[text])
test_vector vector.transform(test[text])# 引入模型
# model KNeighborsClassifier(n_neighbors3) # k3
model KNeighborsClassifier(n_neighbors5) # k5# 开始训练这里可以考虑修改默认的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_task_knn5-1.csv, indexNone)替换词袋模型BOW为TF-IDF(词频-逆文档频率)后确实精度有所提升。 2.3 F1_score评测
每天3次的提交次数进行模型调优确实不太够我甚至还开了小号报名比赛但也就一天6次。因此我自己讲训练集重新按照8:2的比例进行划分然后自行进行精确度和f1-score的测试等效果好再提交。打印下准确度和F1-Score看看
# KNN
# 导入pandas用于读取表格数据
import pandas as pd
# 导入BOW词袋模型可以选择将CountVectorizer替换为TfidfVectorizerTF-IDF词频-逆文档频率注意上下文要同时修改亲测后者效果更佳
# from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfVectorizer
# 导入KNN
from sklearn.neighbors import KNeighborsClassifier
# 过滤警告消息
from warnings import simplefilter
from sklearn.exceptions import ConvergenceWarning
simplefilter(ignore, categoryConvergenceWarning)
from sklearn.metrics import accuracy_score, f1_score
from sklearn.model_selection import train_test_split
data pd.read_csv(/home/aistudio/data/data231041/train.csv)
data[text] data[title].fillna() data[abstract].fillna() data[Keywords].fillna()
X data.text
y data.label# 以8:2的比例划分训练集
X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42)# 停止词剔除噪音数据
stops [i.strip() for i in open(r/home/aistudio/stop.txt,encodingutf-8).readlines()]
vector TfidfVectorizer(stop_wordsstops).fit(train[text])
X_train_vector vector.transform(X_train)
X_test_vector vector.transform(X_test)#训练模型
n_neighbors 5
knn KNeighborsClassifier(n_neighborsn_neighbors)
knn.fit(X_train_vector, y_train)
y_pred knn.predict(X_test_vector)
#查看各项得分
print(y_pred,y_pred)
print(y_test,y_test)
print(score on train set, knn.score(X_train_vector, y_train))
print(score on test set, knn.score(X_test_vector, y_test))
print(accuracy score, accuracy_score(y_test, y_pred))
print(f1-score: , f1_score(y_test, y_pred))2.4 数据增强
#朴素贝叶斯# 导入pandas用于读取表格数据
import pandas as pd# 导入BOW词袋模型可以选择将CountVectorizer替换为TfidfVectorizerTF-IDF词频-逆文档频率注意上下文要同时修改亲测后者效果更佳
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB # 朴素贝叶斯分类器# 过滤警告消息
from warnings import simplefilter
from sklearn.exceptions import ConvergenceWarning
simplefilter(ignore, categoryConvergenceWarning)# 读取数据集
train pd.read_csv(/home/aistudio/data/data231041/train.csv)
train[title] train[title].fillna()
train[abstract] train[abstract].fillna()# 数据增强
train_x pd.read_csv(/home/aistudio/data/data231041/keywords.csv)
train_x[keywords] train_x[Keywords].fillna()
train[Keywords] train[Keywords] train_x[Keywords]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()# 停止词剔除噪音数据
stops [i.strip() for i in open(r/home/aistudio/stop.txt,encodingutf-8).readlines()]
vector TfidfVectorizer(stop_wordsstops).fit(train[text])
train_vector vector.transform(train[text])
test_vector vector.transform(test[text])# 引入模型model MultinomialNB() # 改用朴素贝叶斯分类器# 开始训练这里可以考虑修改默认的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_task_MultinomialNB1.csv, indexNone)做了数据增强后精度会有所提升在这里发现作者这一栏的信息还是有用的去了在朴素贝叶斯上精度反而变低了最终精度可以达到0.83425。 2.5 提交结果
模型F1-Score评价LogisticRegression0.67116逻辑回归模型效果最差也可能是我没有对它进行调整不过感觉肯定调整后也没其它模型分类性能好SVM0.6778我一直认为SVM才是机器学习里面分类的天花板模型但是跑出来的效果也不是很理想所以果断换了模型不知道为啥这么低可能是不适合该文本而分类任务KNN0.67538-0.72763KNN算法模型是我用的最多的所以我果断在KNN上进行了尝试发现K设置为5的时候效果最好大于小于5都不太行Random forest0.75322随机森林的效果比其它都好但是我设置树的数量为100且修改了特征提取模型的参数才达到了0.75的分数如果增加树的数量比如200那么分数会变低Multinomial NB0.82041这里为使用了朴素贝叶斯中的多项式朴素贝叶斯算法模型不做任何参数上的调整直接替换模型精度都可以达到0.82041效果非常好。
三、深度学习方法
3.1 解题思路
使用预训练的BERT模型进行建模的思路步骤如下
数据预处理首先对文本数据进行预处理包括文本清洗如去除特殊字符、标点符号、分词等操作。可以使用常见的NLP工具包如NLTK或spaCy来辅助进行预处理。构建训练所需的dataloader与dataset构建Dataset类时需要定义三个方法__init__getitem len其中__init__方法完成类初始化__getitem__要求返回返回内容和label__len__方法返回数据长度构造Dataloader在其中完成对句子进行编码、填充、组装batch等动作定义预测模型利用预训练的BERT模型来解决文本二分类任务我们将使用BERT模型编码中的[CLS]向量来完成二分类任务 [CLS]就是classification的意思可以理解为用于下游的分类任务。
主要用于以下两种任务 单文本分类任务对于文本分类任务BERT模型在文本前插入一个[CLS]符号并将该符号对应的输出向量作为整篇文本的语义表示用于文本分类如下图所示。可以理解为与文本中已有的其它字/词相比这个无明显语义信息的符号会更“公平”地融合文本中各个字/词的语义信息。 在模型设计中思路就体现为我们取出文本数据经过向量化后的[CLS]向量然后经过二分类预测层得到最终的结果。
outputs self.bert(**src).last_hidden_state[:, 0, :]
self.predictor(outputs)
self.predictor nn.Sequential(nn.Linear(768, 256),nn.ReLU(),nn.Linear(256, 1),nn.Sigmoid())模型训练和评估使用训练集对选定的机器学习模型进行训练然后使用测试集进行评估。评估指标可以选择准确率、精确率、召回率、F1值等。调参优化如果模型效果不理想可以尝试调整特征提取的参数如词频阈值、词袋大小等或机器学习模型的参数以获得更好的性能。
在这个进阶实践中我们使用深度学习方法一般会遵循以下流程 在进阶Baseline中我们会使用Bert模型该模型介绍如下
3.2 BERT介绍
BERT是一个经典的深度学习、预训练模型。2018年由 Google 团队发布的论文《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》提出了预训练模型 BERTBidirectional Encoder Representations from Transformers在自然语言处理领域掀起了巨大浪潮。该模型实现了包括 GLUE、MultiNLI 等七个自然语言处理评测任务的 the-state-of-art最优表现堪称里程碑式的成果。自 BERT 推出以来预训练微调的模式开始成为自然语言处理任务的主流标志着各种自然语言处理任务的重大进展以及预训练模型的统治地位建立一直到去年 ChatGPT 的发布将研究范式带向大语言模型提示工程但时至今天BERT 仍然是自然语言处理领域最常用、最重要的预训练模型之一。
本次进阶 Baseline 拟采用 BERT 作为进阶模型指导大家如何部署应用 BERT 来完成本期竞赛任务此处简要介绍 BERT 的原理及其思想。
3.3 预训练 微调范式
自然语言处理领域一直在发展变化能够在各种任务上达到最优效果的模型、算法也层出不穷。最早的范式是文本表示 机器学习如基础 Baseline 所演示的方法通过将自然语言文本表示为数值向量再建立统计机器学习模型实习下游任务。但随着深度学习的发展自2013年神经网络词向量登上时代舞台神经网络逐渐成为了 NLP 的核心方法NLP 的核心研究范式逐渐向深度学习演化。 深度学习的研究方法主要是通过多层的神经网络来端到端处理下游任务将文本表示、特征工程、建模预测都融合在深度神经网络中减少了人工特征构建的过程显著提升了自然语言处理能力。神经网络词向量是其中的核心部分即文本通过神经网络后的向量表示这些向量表示能够蕴含深层语义且维度合适后续研究往往可以直接使用以替代传统的文本表示方法典型的应用如 Word2Vec 。
但是Word2Vec 是静态词向量即对于每一个词有一个固定的向量表示无法解决一词多义、复杂特征等问题。2018年ELMo 模型的提出拉开了动态词向量、预训练模型的时代大幕。ELMo 模型基于双向 LSTM 架构在训练数据上基于语言模型进行预训练再针对下游任务进行微调表现出了更加优越的性能标志着预训练微调范式的诞生。
所谓预训练微调范式指先在海量文本数据上进行预训练再针对特定的下游任务进行微调。预训练一般基于语言模型即给定上一个词预测下一个词。语言模型可以在所有文本数据上建模无需人工标注因此很容易在海量数据上进行训练。通过在海量数据上进行预训练模型可以学习到深层的自然语言逻辑。再通过在指定的下游任务上进行微调即针对部分人工标注的任务数据进行特定训练如文本分类、文本生成等来训练模型执行下游任务的能力。 预训练微调范式一定程度上缓解了标注数据昂贵的问题显著提升了模型性能但是ELMo 使用的双向 LSTM 架构存在难以解决长期依赖、并行效果差的天生缺陷ELMo 本身也保留了词向量作为特征输入的应用并没能一锤定音地敲定预训练微调范式的主流地位。2017年Transformer 模型的提出为自然语言处理领域带来了一个新的重要成员——Attention 架构。基于 Attention 架构同样在2018年OpenAI 提出的 GPT 模型基于 Transformer 模型结合 ELMo 模型提出的预训练微调范式进一步刷新了众多自然语言处理任务的上限。2023年爆火出圈的 ChatGPT 就是以 GPT 模型作为基础架构的。
从静态编码到神经网络计算的静态词向量再到基于双向 LSTM 架构的预训练微调范式又诞生了基于 Transformer的预训练微调模式预训练模型逐步成为自然语言处理的主流。但真正奠定预训练微调范式的重要地位的还是之后提出的 BERT。BERT 可以说是综合了 ELMo 和 GPT使用预训练微调范式基于 Transformer 架构而抛弃了存在天生缺陷的 LSTM又针对 GPT 仅能够捕捉单向语句关系的缺陷提出了能够捕捉深层双向语义关系的 MLM 预训练任务从而将预训练模型推向了一个高潮。
3.4 Transformer 与 Attention
BERT 乃至目前正火的 LLM 的成功都离不开 Attention 机制与基于 Attention 机制搭建的 Transformer 架构。此处简单介绍 Transformer 与 Attention 机制。
在 Attention 机制提出之前深度学习主要有两种基础架构卷积神经网络CNN与循环神经网络RNN。其中CNN 在 CV 领域表现突出而 RNN 及其变体 LSTM 在 NLP 方向上一枝独秀。然而RNN 架构存在两个天然缺陷① 序列依序计算的模式限制了计算机并行计算的能力导致 RNN 为基础架构的模型虽然参数量不算特别大但计算时间成本却很高。 ② RNN 难以捕捉长序列的相关关系。在 RNN 架构中距离越远的输入之间的关系就越难被捕捉同时 RNN 需要将整个序列读入内存依次计算也限制了序列的长度。
针对上述两个问题 2017年 Vaswani 等人发表了论文《Attention Is All You Need》创造性提出了 Attention 机制并完全抛弃了 RNN 架构。Attention 机制最先源于计算机视觉领域其核心思想为当我们关注一张图片我们往往无需看清楚全部内容而仅将注意力集中在重点部分即可。而在自然语言处理领域我们往往也可以通过将重点注意力集中在一个或几个 token从而取得更高效高质的计算效果。
Attention 机制的特点是通过计算 Query (查询值)与Key(键值) 的相关性为真值 加权求和从而拟合序列中每个词同其他词的相关关系。其大致计算过程如图 具体而言可以简单理解为一个输入序列通过不同的参数矩阵映射为 Q、K、V 三个矩阵其中Q 是计算注意力的另一个句子或词组V 为待计算句子K 为待计算句子中每个词的对应键。通过对 Q 和 K 做点积可以得到待计算句子V的注意力分布即哪些部分更重要哪些部分没有这么重要基于注意力分布对 V 做加权求和即可得到输入序列经过注意力计算后的输出其中与 Q 即计算注意力的另一方越重要的部分得到的权重就越高。
而 Transformer 正是基于 Attention 机制搭建了 Encoder-Decoder编码器-解码器结构主要适用于 Seq2Seq序列到序列任务即输入是一个自然语言序列输出也是一个自然语言序列。其整体架构如下
Transformer 由一个 Encoder一个 Decoder 外加一个 Softmax 分类器与两层编码层构成。上图中左侧方框为 Encoder右侧方框为 Decoder。
由于是一个 Seq2Seq 任务在训练时Transformer 的训练语料为若干个句对具体子任务可以是机器翻译、阅读理解、机器对话等。在原论文中是训练了一个英语与德语的机器翻译任务。在训练时句对会被划分为输入语料和输出语料输入语料将从左侧通过编码层进入 Encoder输出语料将从右侧通过编码层进入 Decoder。Encoder 的主要任务是对输入语料进行编码再输出给 DecoderDecoder 再根据输出语料的历史信息与 Encoder 的输出进行计算输出结果再经过一个线性层和 Softmax 分类器即可输出预测的结果概率整体逻辑如下图 Transformer 整体是一个很值得探究的话题此处不再赘述如有感兴趣的同学欢迎阅读原论文《Attention Is All You Need》(https://arxiv.org/pdf/1706.03762.pdf) 与基于 Pytorch 的 Transformer 源码解读:https://github.com/datawhalechina/thorough-pytorch/blob/main/source/%E7%AC%AC%E5%8D%81%E7%AB%A0/Transformer%20%E8%A7%A3%E8%AF%BB.md
3.5 预训练任务
BERT 的模型架构直接使用了 Transformer 的 Encoder 作为整体架构其最核心的思想在于提出了两个新的预训练任务——MLMMasked Language Model掩码模型和 NSPNext Sentence Prediction下个句子预测而不是沿用传统的 LM语言模型。 MLM 任务是 BERT 能够深层拟合双向语义特征的基础。简单来讲MLM 任务即以一定比例对输入语料的部分 token 进行遮蔽替换为 MASK标签再让模型基于其上下文预测还原被遮蔽的单词即做一个完形填空任务。由于在该任务中模型需要针对 (MASK) 标签左右的上下文信息来预测标签本身从而会充分拟合双向语义信息。
例如原始输入为 I like you。以30%的比例进行遮蔽那么遮蔽之后的输入可能为I (MASK) you。而模型的任务即为基于该输入预测出 (MASK) 标签对应的单词为 like。
NSP 任务是 BERT 用于解决句级自然语言处理任务的预训练任务。BERT 完全采用了预训练微调的范式因此着重通过预训练生成的模型可以解决各种多样化的下游任务。MLM 对 token 级自然语言处理任务如命名实体识别、关系抽取等效果极佳但对于句级自然语言处理任务如句对分类、阅读理解等由于预训练与下游任务的模式差距较大因此无法取得非常好的效果。NSP 任务是将输入语料都整合成句对类型句对中有一半是连贯的上下句标记为 IsNext一半则是随机抽取的句对标记为 NotNext。模型则需要根据输入的句对预测是否是连贯上下句即预测句对的标签。
例如原始输入句对可能是 (I like you ; Because you are so good) 以及 (I like you; Today is a nice day)。而模型的任务即为对前一个句对预测 IsNext 标签对后一个句对预测 NotNext 标签。 基于上述两个预训练任务BERT 可以在预训练阶段利用大量无标注文本数据实现深层语义拟合从而取得良好的预测效果。同时BERT 追求预训练与微调的深层同步由于 Transformer 的架构可以很好地支持各类型的自然语言处理任务从而在 BERT 中微调仅需要在预训练模型的最顶层增加一个 SoftMax 分类层即可。同样值得一提的是由于在实际下游任务中并不存在 MLM 任务的遮蔽因此在策略上进行了一点调整即对于选定的遮蔽词仅 80% 的遮蔽被直接遮蔽其余将有 10% 被随机替换10% 被还原为原单词。
3.6 实现流程
3.6.1 导入模块
导入我们本次Baseline代码所需的模块
#import 相关库
#导入前置依赖
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 Path3.6.2 设置全局配置
batch_size 16
# 文本的最大长度
text_max_length 128
# 总训练的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)3.6.3 数据收集与准备
在赛题主页下载数据读取数据集数据预处理考虑数据扩增
# 读取数据集进行数据处理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() 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()# 从训练集中随机采样测试集
validation_data pd_train_data.sample(fracvalidation_ratio)
train_data pd_train_data[~pd_train_data.index.isin(validation_data.index)]3.6.4 构建训练所需的dataloader与dataset
定义dataset Pytorch 中自定义dataset需要继承torch.utils.data.Dataset继承Dataset这个类时它要求你必须重写__getitem__(self, index)、 __len__(self) 两个方法前者通过提供索引返回数据也就是提供 DataLoader获取数据的方式后者返回数据集的长度DataLoader依据 len 确定自身索引采样器的长度。 而本次教程中我们在初始化类方法__init__中引入我们的训练数据重写__getitem__(self, index)方法保证能够取出index对应行的text 与label 值也就是我们训练需要的数据以及训练希望得到的结果在__len__ 方法中我们需要返回数据集的总长度这里直接利用Dataframe数据的len()方法就能完成。
# 构建Dataset
class MyDataset(Dataset):def __init__(self, modetrain):super(MyDataset, self).__init__()self.mode mode# 拿到对应的数据if mode train:self.dataset train_dataelif mode validation:self.dataset validation_dataelif mode test:# 如果是测试模式则返回内容和uuid。拿uuid做target主要是方便后面写入结果。self.dataset test_dataelse: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做为targetlabel data[uuid]else:label data[label]# 返回内容和labelreturn text, labeldef __len__(self):return len(self.dataset)
train_dataset MyDataset(train)
validation_dataset MyDataset(validation)train_dataset.__getitem__(0)构造Dataloader 在Dataloader中我们需要利用Dataloader来加载训练数据与训练目标需要注意的是加载完成后的数据需要为tensor张量形式因此在下文中我们定义了collate_fn来帮助完成batch组装以及将文本内容向量化而文本内容向量化这部分内容我们利用bert模型来完成而label值的向量化我们直接使用torch.LongTensor()方法完成。
#获取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的结果即可# paddingmax_length 不够长度的进行填充# truncationTrue 长度过长的进行裁剪src tokenizer(text, paddingmax_length, max_lengthtext_max_length, return_tensorspt, truncationTrue)return src, torch.LongTensor(label)train_loader DataLoader(train_dataset, batch_sizebatch_size, shuffleTrue, collate_fncollate_fn)
validation_loader DataLoader(validation_dataset, batch_sizebatch_size, shuffleFalse, collate_fncollate_fn)inputs, targets next(iter(train_loader))
print(inputs:, inputs)
print(targets:, targets)3.6.5 定义模型
pytorch中定义模型需要继承nn.Module类其中需要至少定义两个方法一个是初始化模型结构的方法__init__另一个方法forward来完成推理流程。
#定义预测模型该模型由bert模型加上最后的预测层组成
class MyModel(nn.Module):def __init__(self):super(MyModel, self).__init__()# 加载bert模型self.bert BertModel.from_pretrained(bert-base-uncased, mirrortuna)# 最后的预测层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)3.6.6 定义出损失函数和优化器
#定义出损失函数和优化器。这里使用Binary Cross Entropy
criteria nn.BCELoss()
optimizer torch.optim.Adam(model.parameters(), lrlr)3.6.7 定义验证函数
# 由于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 0for 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_numreturn total_correct / len(validation_dataset), total_loss / len(validation_dataset)3.6.8 模型训练、评估
# 首先将模型调成训练模式
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 1if step % log_per_step 0:print(Epoch {}/{}, Step: {}/{}, total loss:{:.4f}.format(epoch1, epochs, i, len(train_loader), total_loss))total_loss 0del inputs, targets# 一个epoch后使用过验证集进行验证accuracy, validation_loss validate()print(Epoch {}, accuracy: {:.4f}, validation loss: {:.4f}.format(epoch1, accuracy, validation_loss))torch.save(model, model_dir / fmodel_{epoch}.pt)# 保存最好的模型if accuracy best_accuracy:torch.save(model, model_dir / fmodel_best.pt)best_accuracy accuracy
#加载最好的模型然后进行测试集的预测
model torch.load(model_dir / fmodel_best.pt)
model model.eval()test_dataset MyDataset(test)
test_loader DataLoader(test_dataset, batch_sizebatch_size, shuffleFalse, collate_fncollate_fn)3.6.9 结果输出
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, indexNone)本文章仅记录学习参考AI夏令营第三期 - 基于论文摘要的文本分类与关键词抽取挑战赛教程