做网站dreamwa,图书网站开发介绍,汉口网站推广公司,龙岩公司网站建设目录 1. 准备工作2. 资源导入3. 数据处理4. 绘制词云图5. 数据可视化5.1 词数和字符数可视化5.2 元特征可视化5.3 类别可视化 6. 词元分析6.1 一元语法统计6.2 多元语法统计 7. 命名实体识别8. 推文主题提取9. 构建模型9.1 数据划分与封装9.2 模型训练与验证 10. 模型评估11. 测… 目录 1. 准备工作2. 资源导入3. 数据处理4. 绘制词云图5. 数据可视化5.1 词数和字符数可视化5.2 元特征可视化5.3 类别可视化 6. 词元分析6.1 一元语法统计6.2 多元语法统计 7. 命名实体识别8. 推文主题提取9. 构建模型9.1 数据划分与封装9.2 模型训练与验证 10. 模型评估11. 测试集分类12. 参考文献 竞赛的数据集中训练集有7613个样本测试集有3263个样本。训练集中的target用于标识推文是否属于灾难推文1表示灾难推文0表示非灾难推文最终任务是判断测试集中的推文样本是否为灾难推文评估指标是F1值详细竞赛信息见竞赛官网。竞赛使用的模型是DistilBert由于DIstilBERT是 transformers 库中的模型且 transformers 库基于深度学习框架因此需要安装 PyTorch 或者 TensorFlow本文的代码基于PyTorch。提交代码后最终评分F1值为0.82071排名242提交时间2024年10月24日。所使用的代码已上传值Gitee点击直达。
1. 准备工作
安装nltk库之后需下载对应资源
nltk.download(wordnet)在 kaggle 中运行时记得手动解压本地Jupyter不用手动解压
!unzip /usr/share/nltk_data/corpora/wordnet.zip -d /usr/share/nltk_data/corpora/安装en_core_web_sm要求python版本需3.9
pip install https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-2.2.5/en_core_web_sm-2.2.5.tar.gzen_core_web_sm 是 spaCy 提供的一个英文语言预训练模型spaCy 是一个流行的开源自然语言处理NLP库广泛用于各种 NLP 任务。注安装完 en_core_web_sm-2.2.5 后会出现 numpy 版本太高2.x导致不兼容的问题需降到1.x降到1.x之后又有问题报错如下
blis 1.0.1 requires numpy3.0.0,2.0.0, but you have numpy 1.24.4 which is incompatible.查阅后发现blis是spacy中的一个依赖库且我的spacy版本过高3.8.2需要降到2.3.5才行此时numpy1.24.4才能用。
2. 资源导入
import numpy as np
import pandas as pd
import nltk
# 资源下载
nltk.download(wordnet)
# 在 kaggle 中运行时记得手动解压
# !unzip /usr/share/nltk_data/corpora/wordnet.zip -d /usr/share/nltk_data/corpora/
from nltk.corpus import wordnet
from nltk.corpus import stopwords
from nltk.stem import WordNetLemmatizer
import re
from wordcloud import WordCloud
from wordcloud import STOPWORDS
import matplotlib.pyplot as plt
from PIL import Image
import seaborn as snsfrom tqdm.notebook import tqdm
tqdm.pandas()
import time
import datetime
from transformers import AdamW, get_linear_schedule_with_warmup
from sklearn.metrics import accuracy_score, f1_scoreimport warnings
warnings.filterwarnings(ignore)train pd.read_csv(D:/Desktop/kaggle数据集/nlp-disaster/train.csv)
test pd.read_csv(D:/Desktop/kaggle数据集/nlp-disaster/test.csv)
print(训练集大小{}.format(train.shape))
print(测试集大小{}.format(test.shape))训练集大小(7613, 5)
测试集大小(3263, 4)3. 数据处理
定义函数数据处理函数包扩小写转换、移除停用词、移除网址、移除标点和特殊符号、词形还原。统计词频发现amp;出现的频率很高且该符号仅表示html标签中的符所以需移除。 将词性转换为 WordNet 所需的格式def get_wordnet_pos(word):#----------------------------------------------------------------------------------------## 使用 pos_tag 为单词生成词性标注[0][1][0]用于获取词性标签# 最后将词性标签取其首字母并转换为大写#----------------------------------------------------------------------------------------#tag nltk.pos_tag([word])[0][1][0].upper()tag_dict {J: wordnet.ADJ,N: wordnet.NOUN,V: wordnet.VERB,R: wordnet.ADV}#----------------------------------------------------------------------------------------## tag_dict.get(tag, wordnet.NOUN)根据词性标签首字母返回相应的 WordNet 词性# 如果 tag 不在 tag_dict 中则默认返回 wordnet.NOUN名词#----------------------------------------------------------------------------------------#return tag_dict.get(tag, wordnet.NOUN)def text_preprocessing(df, column):# 初始化词形还原器和停用词lemmatizer WordNetLemmatizer()stop_words set(stopwords.words(english))# 移除 amp; 在html标签中用于表示 符df[column] df[column].progress_apply(lambda x: x.replace(amp;, ))# 转换为小写df[column] df[column].progress_apply(lambda x: str(x).lower())# 移除停用词# .join(...)将经过过滤后的单词列表重新连接成一个字符串单词之间用空格分隔df[column] df[column].progress_apply(lambda x: .join([word for word in x.split() if word not in stop_words]))#----------------------------------------------------------------------------------------## 移除网址# re.sub(pattern, replacement, string)其中 pattern 是要匹配的正则表达式# replacement 是替换的字符串string 是要处理的文本#----------------------------------------------------------------------------------------#df[column] df[column].progress_apply(lambda x: re.sub(rhttps?://\S|www\.\S, , x))# 移除标点和特殊字符df[column] df[column].progress_apply(lambda x: re.sub(r[^a-zA-Z\s], , x)) # 词形还原例如got - getdf[column] df[column].progress_apply(lambda x: .join([lemmatizer.lemmatize(word, get_wordnet_pos(word)) for word in x.split()]))return df执行数据处理函数
train_cleaned train.copy()
train_cleaned text_preprocessing(train_cleaned,text)注由于初始安装的 ipywidgets 版本是8.1.5此时只能显示 tqdm 的进度条但是不能显示 tqdm.notebook 的进度条需要降级到7.8.1才行conda 安装不了就用 pip。
4. 绘制词云图
绘制灾难推文词云图
# 提取灾难推文并生成字符串以空格分隔
disaster_strings .join(text for text in train_cleaned.loc[train_cleaned[target] 1, text])
# width 和 height 表示生成的词云图的宽度和高度尽量与figsize保持比例一致避免出现图形的拉伸和压缩
wordcloud WordCloud(width800, height600, max_font_size220, background_colorwhite).generate(disaster_strings)
plt.figure(figsize(8,6))
# interpolationbilinear: 使用双线性插值以使图像边缘更加平滑
plt.imshow(wordcloud, interpolationbilinear)
plt.axis(off)绘制非灾难推文词云图
# mask np.array(Image.open(./twitter_mask.jpg))
# 提取非灾难推文并生成字符串以空格分隔
not_disaster_strings .join(text for text in train_cleaned.loc[train_cleaned[target] 0, text])
#--------------------------------------------------------------------------------------------------------------------------------#
# width 和 height 表示生成的词云图的宽度和高度尽量与figsize保持比例一致避免出现图形的拉伸和压缩
# 注意stopwords 不设置或者设置为 one WordCloud 会默认设置为 stopwordsSTOPWORDS 而 TOPWORDS 含
# like 和 get这两个词是 ot Disaster 出现频率最高的两个词从而导致 ike 和 get不显示需设置为stopwordsset()才行
#--------------------------------------------------------------------------------------------------------------------------------#
wordcloud WordCloud(width800, height600, max_font_size220, background_colorwhite,stopwordsset()).generate(not_disaster_strings)
plt.figure(figsize(8,6))
# interpolationbilinear: 使用双线性插值以使图像边缘更加平滑
plt.imshow(wordcloud, interpolationbilinear)
plt.axis(off)5. 数据可视化
5.1 词数和字符数可视化
可视化推文字符数
plt.figure(figsize(6,4))
train_sent train[text].str.len()
sns.boxplot(xtarget,ytrain_sent,datatrain, palettesns.color_palette())
plt.xlabel(Tweet Category)
plt.ylabel(Tweet Length by character)
plt.show()由图可知灾难推文的字符整体上比非灾难推文的字符多这是因为灾难推文大多来自新闻机构所以推文编写更正式字数更长。而非灾难推文大多来自个人用户内容较少且拼写错误较多。 可视化推文词数
plt.figure(figsize(6,4))
# map 用于计算每个列表的长度
train_sent train[text].str.split().map(lambda x : len(x))
sns.boxplot(xtarget, ytrain_sent, datatrain, palettesns.color_palette())
plt.xlabel(Tweet Category)
plt.ylabel(Tweet length by word)
plt.show()灾难推文的词数比非灾难推文的词数略多一点点。
5.2 元特征可视化
元特征统计
train_data train.copy()
test_data test.copy()# 词数统计
train_data[word_count] train_data[text].apply(lambda x: len(str(x).split()))
test_data[word_count] test_data[text].apply(lambda x: len(str(x).split()))# 唯一词统计
# set 中的每个元素值唯一重复的元素会被自动移除
train_data[unique_word_count] train_data[text].apply(lambda x: len(set(str(x).split())))
test_data[unique_word_count] test_data[text].apply(lambda x: len(set(str(x).split())))# 停用词统计这里使用 wordcloud 的 STOPWORDS 要比 nltk 的 stopwords 运算速度要快
train_data[stop_word_count] train_data[text].apply(lambda x: len([w for w in str(x).lower().split() if w in STOPWORDS]))
test_data[stop_word_count] test_data[text].apply(lambda x: len([w for w in str(x).lower().split() if w in STOPWORDS]))# 平均词长度每条推文一个均值
train_data[mean_word_length] train_data[text].apply(lambda x: np.mean([len(w) for w in str(x).split()]))
test_data[mean_word_length] test_data[text].apply(lambda x: np.mean([len(w) for w in str(x).split()]))# 字符数量
train_data[char_count] train_data[text].apply(lambda x: len(str(x)))
test_data[char_count] test_data[text].apply(lambda x: len(str(x)))可视化
METAFEATURES [word_count, unique_word_count, stop_word_count,mean_word_length,char_count]
# 布尔 Series用于标识灾难推文
DISASTER_TWEETS train_data[target] 1
fig, axes plt.subplots(ncols2, nrowslen(METAFEATURES), figsize(18, 35))
for i, feature in enumerate(METAFEATURES):#----------------------------------------------------------------------------------------## statdensity绘制概率密度分布比直方图更容易观察数据的分布聚集情况# 原使用 distplot由于已经被弃用因此用 histplot 代替#----------------------------------------------------------------------------------------#sns.histplot(xtrain_data.loc[~DISASTER_TWEETS][feature], labelNot Disaster, axaxes[i][0], colorgreen,statdensity, kdeTrue)sns.histplot(xtrain_data.loc[DISASTER_TWEETS][feature], labelDisaster, axaxes[i][0], colorred, statdensity, kdeTrue)sns.histplot(train_data[feature], labelTraining, axaxes[i][1], kdeTrue, statdensity)sns.histplot(test_data[feature], labelTest, axaxes[i][1], kdeTrue, statdensity)for j in range(2):axes[i][j].set_xlabel()axes[i][j].legend()axes[i][0].set_title(f{feature} Distribution in Original Training Set)axes[i][1].set_title(f{feature} Training Test Set Distribution)plt.show()运行结果原图太长截取局部
由图可知词数、唯一词数量和平均词长度均大致服从正态分布。且所有元特征在 train 和 test 中的分布相似说明 train 和 test 来自同一个样本集。
5.3 类别可视化
print(f灾难推文数量{(train[target]1).sum()})
print(f非灾难推文数量{(train[target]0).sum()})fig, axes plt.subplots(ncols2, figsize(12, 5))# count()[id]: 对每个类别计算每一列的非空值数量选择 id 列的计数结果返回值是一个包含每个类别样本数量的 Series
axes[0].pie(train_data.groupby(target).count()[id], labels[Not Disaster, Disaster], autopct%1.2f%%)
sns.countplot(xtrain_data[target], huetrain_data[target], axaxes[1])axes[1].set_xlabel()
axes[0].set_ylabel()
axes[1].set_ylabel()
axes[1].set_xticklabels([Not Disaster (4342), Disaster (3271)])axes[0].set_title(Target Distribution in Training Set)
axes[1].set_title(Target Count in Training Set)plt.show()6. 词元分析
6.1 一元语法统计
统计灾难推文和非灾难推文中出现频次较高的词Unigrams一元语法
from collections import Counter, defaultdictlis [train_cleaned[train_cleaned[target] 0][text],train_cleaned[train_cleaned[target] 1][text]
]
fig, axes plt.subplots(1, 2, figsize(18, 8))
axes axes.flatten()for i, j in zip(lis, axes):new i.str.split()# 将每个推文转为一个单独的列表new new.values.tolist()#----------------------------------------------------------------------------------------## 外层循环 for i in new 遍历 new 中的每一个内层列表 i即每条推文的单词列表# 内层循环 for word in i 遍历内层列表 i 中的每一个单词 word#----------------------------------------------------------------------------------------#corpus [word for i in new for word in i]# 使用 Counter 统计 corpus 中每个单词的出现次数以键值对方式存储counter Counter(corpus)# 按出现频率排序most counter.most_common()x, y [], []# 遍历前30个出现频率最高的词不能是停用词for word, count in most[:30]:x.append(word)y.append(count)sns.barplot(xy, yx, paletteplasma, axj)
axes[0].set_title(Non Disaster Tweets)
axes[1].set_title(Disaster Tweets)axes[0].set_xlabel(Count)
axes[0].set_ylabel(Word)
axes[1].set_xlabel(Count)
axes[1].set_ylabel(Word);
fig.suptitle(Most Common Unigrams, fontsize24, vabaseline);很明显灾难推文中有 fire、kill、bomb 等词表示灾难而非灾难推文中的词比较通用。
6.2 多元语法统计
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
统计较高词组n-grams词频
Paramsn需要统计的词组的次词数title统计图标题def ngrams(n, title):fig, axes plt.subplots(1, 2, figsize(18, 8))axes axes.flatten()for i, j in zip(lis, axes):# 用于获取给定文本语料库中出现频率最高的词组def _get_top_ngram(corpus, nNone):# ngram_range设置提取的 n-grams 范围vectorizer CountVectorizer(ngram_range(n, n),stop_wordsenglish)#----------------------------------------------------------------------------------------## fit_transform()首先对传入的文本进行分词根据空格和标点符号将文本拆分为词汇# 并将所有输入文本转换为小写字母并构建词汇表fit 阶段然后将文本转换为词频向量transform 阶段# 最后返回一个稀疏矩阵仅存储非零元素#----------------------------------------------------------------------------------------#bag_of_words vectorizer.fit_transform(i)# 计算每个词在所有文本中出现的总次数二维数组sum_words bag_of_words.sum(axis0)#----------------------------------------------------------------------------------------## 创建一个列表其中每个元素是一个元组包含每个词和其对应的频率# vocabulary_是一个字典包含了模型中识别的所有词单元或多元的映射。# 这个字典的键是词或词组值是对应的列索引索引位置#----------------------------------------------------------------------------------------#words_freq [(word, sum_words[0, idx])for word, idx in vectorizer.vocabulary_.items()]# 对 words_freq 按照词频排序words_freq sorted(words_freq, keylambda x: x[1], reverseTrue)# 返回出现频率最高的 15 个词组return words_freq[:15]top_n_bigrams _get_top_ngram(i, n)# map(list, ...)将 zip() 的结果转换为列表。即 x 将成为包含所有 n-gram 的列表y 将成为包含对应频率的列表x, y map(list, zip(*top_n_bigrams))sns.barplot(xy, yx, paletteplasma, axj)axes[0].set_title(Non Disaster Tweets)axes[1].set_title(Disaster Tweets)axes[0].set_xlabel(Count)axes[0].set_ylabel(Words)axes[1].set_xlabel(Count)axes[1].set_ylabel(Words)fig.suptitle(title, fontsize24, vabaseline)plt.subplots_adjust(wspace0.5)ngrams(2, Most Common Bigrams)ngrams(3, Most Common Trigrams)7. 命名实体识别
命名实体识别Named Entity Recognition, NER是一种自然语言处理技术用于识别文本中具有特定意义的实体通常包括人名、地名、组织名、日期、时间、货币等。
import en_core_web_sm
nlp en_core_web_sm.load() import matplotlib.patches as mpatches
fig, axes plt.subplots(1, 2, figsize(14, 6))
axes axes.flatten()
for i, j in zip(lis, axes):def _get_ner(i):#----------------------------------------------------------------------------------------## 处理输入文本 text生成一个 doc 对象# 对象包含了对输入文本的各种分析和处理结果#----------------------------------------------------------------------------------------#doc nlp(i)# 获取 doc 中的所有命名实体X.label_ 提取每个实体的标签如人名、地名等return [X.label_ for X in doc.ents]# 对 text 中的每个文本应用 _get_ner 函数ent i.apply(lambda x: _get_ner(x))# 将嵌套列表扁平化。ent 现在是一个包含所有命名实体标签的单一列表ent [x for sub in ent for x in sub]counter Counter(ent)count counter.most_common()[:15]#----------------------------------------------------------------------------------------## *: 将 count 中的每个元组解包为独立的参数传递给 zip# zip 函数用于压缩将多个可迭代对象如列表、元组返回一个迭代器# map(list, ...)将 zip() 的结果转换为列表。即 x 将成为包含所有 n-gram 的列表y 将成为包含对应频率的列表#----------------------------------------------------------------------------------------#x, y map(list, zip(*count))sns.barplot(xy, yx, axj, paletteplasma)axes[0].set_title(Non Disaster Tweets)
axes[1].set_title(Disaster Tweets)
axes[0].set_xlabel(Count)
axes[0].set_ylabel(Named-Entity)
axes[1].set_xlabel(Count)
axes[1].set_ylabel(Named-Entity)
fig.suptitle(Common Named-Entity Counts, fontsize24, vabaseline)# 创建图例每个 Patch 对象代表一个图例项。每个 Patch 对象都有一个 label 属性用于描述不同命名实体的类型
patch1 mpatches.Patch(labelPERSON : People, including fictional)
patch2 mpatches.Patch(labelORG : Companies, agencies, institutions, etc.)
patch3 mpatches.Patch(labelCARDINAL : Numerals that dont fall under another type.)
patch4 mpatches.Patch(labelGPE : Countries, cities, states.)
patch5 mpatches.Patch(labelNORP : Nationalities or religious or political groups.)
patch6 mpatches.Patch(labelTIME : Times smaller than a day.)
patch7 mpatches.Patch(labelQUANTITY : Measurements, as of weight or distance.)
patch8 mpatches.Patch(labelORDINAL : “first”, “second”, etc.)
patch9 mpatches.Patch(labelLOC : Non-GPE locations, mountain ranges, bodies of water.)
patch10 mpatches.Patch(labelFAC : Buildings, airports, highways, bridges, etc.)
patch11 mpatches.Patch(labelPRODUCT : Objects, vehicles, foods, etc. (Not services.))
patch12 mpatches.Patch(labelEVENT : Named hurricanes, battles, wars, sports events, etc.)
patch13 mpatches.Patch(labelLANGUAGE : Any named language.)
patch14 mpatches.Patch(labelPERCENT : Percentage, including ”%“.)
patch15 mpatches.Patch(labelDATE : Absolute or relative dates or periods.)
#----------------------------------------------------------------------------------------#
# bbox_to_anchor(1.05, 0.85): 设置图例的位置。(1.05, 0.85) 表示图例位于图形的右侧稍微偏上
# 第一个值表示水平位置取值范围通常是 0 到 1表示相对于绘图区域的宽度。值为 0 表示左边缘1 表示右边缘
# locupper left: 将图例的左上角对齐到 bbox_to_anchor 指定的位置
#----------------------------------------------------------------------------------------#
plt.legend(handles[patch1, patch2, patch3, patch4, patch5, patch6, patch7, patch8, patch9, patch10, patch11, patch12, patch13, patch14, patch15, ],bbox_to_anchor(1.05, 0.85), locupper left, borderaxespad0.)可以发现在灾难推文中GPE实体中的国家、城市、州比非灾难推文更常见。此外国籍或宗教或政治团体名称更有可能在灾难推文中被提及。
8. 推文主题提取
使用TF-IDF技术提取推文主题TF-IDFTerm Frequency-Inverse Document Frequency词频-逆文档频率是一种用于信息检索与数据挖掘的常用加权技。TF-IDF 能够减少高频词的影响通过计算词频TF和逆文档频率IDF的乘积来评估一个词对于一个文档集或语料库的重要程度。 代码实现
from sklearn.decomposition import NMF输出10个主题的前10个关键词主题的类型和内容由训练过程中的数据决定
Paramstext推文数据num_topic: 输出的主题词数量按权重排序由大到小输出topic推文类型def display_topics(text, num_topic, topic):#----------------------------------------------------------------------------------------## 创建 TF-IDF 向量化器# max_df0.90: 忽略在 90% 以上文档中出现的词汇# min_df25: 仅保留在至少 25 个文档中出现的词汇# max_features5000: 最多保留 5000 个词汇# use_idfTrue: 使用逆文档频率IDF加权#----------------------------------------------------------------------------------------#tfidf_vectorizer TfidfVectorizer(max_df0.90, min_df25, max_features5000, use_idfTrue)# 转换文本数据为 TF-IDF 矩阵(稀疏矩阵值表示在相应文档中的权重)tfidf tfidf_vectorizer.fit_transform(text)# 从 TfidfVectorizer 中获取特征词的名称tfidf_feature_names tfidf_vectorizer.get_feature_names_out()#----------------------------------------------------------------------------------------## n_components: 指定要提取的主题数量# initnndsvd: 使用非负奇异值分解作为初始化算法#----------------------------------------------------------------------------------------#nmf NMF(n_components10, random_state0,initnndsvd).fit(tfidf)print(topic)#----------------------------------------------------------------------------------------## 输出每个主题中权重最高的前10个主题词# nmf.components_ NMF 模型生成的主题矩阵# topic_idx 为主题索引topic 为对应的主题词权重# argsort()返回数组值从小到大排序后的索引值# -num_topic - 1倒数第num_topic 1个位置切片不包含结束位置的元素# -1切片的方向为从右到左#----------------------------------------------------------------------------------------#for topic_idx, topic in enumerate(nmf.components_):print(Topic %d: % (topic_idx1))print( .join([tfidf_feature_names[i] for i in topic.argsort()[:-num_topic - 1:-1]]))display_topics(lis[0], 10, Non Disaster Topics\n)Non Disaster TopicsTopic 1:
im traumatise still drown gonna disaster dead burning feel attack
Topic 2:
like video youtube look feel sinking fatality siren well war
Topic 3:
new emergency full quarantine content read post many storm re
Topic 4:
get blown demolish lol electrocute good day someone think let
Topic 5:
amp rt please back curfew explode fire derail flood life
Topic 6:
scream fuck love phone face song loud as hit baby
Topic 7:
body bag cross shoulder bagging full lady read ebay re
Topic 8:
via youtube god change news obliteration stop service military rescue
Topic 9:
one love see make fire come say time day would
Topic 10:
go quarantine content many explode let reddit top make delugedisplay_topics(lis[1], 10,Disaster Topics\n)Disaster TopicsTopic 1:
fire forest truck evacuate wild california service set say amp
Topic 2:
hiroshima atomic bombing year japan bomb still anniversary war today
Topic 3:
california wildfire home northern late raze news abc time earthquake
Topic 4:
via wave attack israeli evacuation heat car food stop earthquake
Topic 5:
kill suicide attack police bomber saudi mosque people crash suspect
Topic 6:
mh family malaysia pm confirm wreckage debris legionnaire conclusively be
Topic 7:
disaster nuclear obama natural declares typhoondevastated saipan sign northern collapse
Topic 8:
flood storm train rain derail amp issue warn violent rescue
Topic 9:
building burning like collapse life people two im bridge car
Topic 10:
get watch minute swallow sandstorm airport go im like mass由主题词可知灾难主题的关键词较为明显而非灾难主题则是更个人化的话题。
9. 构建模型
BERTBidirectional Encoder Representations from Transformers双向编码器表征法是一种基于Transformer架构的预训练语言模型能够有效捕捉文本中的上下文关系理解句子的语义。这里选择使用 DistilBERT 模型DistilBERT 采用知识蒸馏的方法进行训练即通过使用较大的教师模型如 BERT指导较小的学生模型DistilBERT学习从而有效保留了大量的语言理解能力。DistilBERT的参数比 bert-base-uncased 少 40%运行速度提高了 60%同时保留了 97% 的 BERT 性能根据 GLUE 语言理解基准测试。 查询是否能使用GPU
import torch
if torch.cuda.is_available(): device torch.device(cuda) print(There are %d GPU(s) available. % torch.cuda.device_count())print(We will use the GPU:, torch.cuda.get_device_name(0))
else:print(No GPU available, using the CPU instead.)device torch.device(cpu)9.1 数据划分与封装
加载DistilBERT模型和分词器
from transformers import DistilBertTokenizer, DistilBertForSequenceClassification
model_name distilbert-base-uncased
tokenizer DistilBertTokenizer.from_pretrained(model_name)
model DistilBertForSequenceClassification.from_pretrained(model_name, num_labels2)
model.to(device)
print()tokenizer 会将类似 “#Nothing” 的词划分为 [‘#’, ‘nothing’] 两个词由于原数据已经去除标点符因此可以直接对原数据使用 tokenizer无需先进行数据清洗。
tokenizer(Nothing remains the same for long){input_ids: [101, 2498, 3464, 1996, 2168, 2005, 2146, 102], attention_mask: [1, 1, 1, 1, 1, 1, 1, 1]}input_ids: 表示单词在词汇表中的位置。101 是特殊的起始标记[CLS]102 是结束标记[SEP]。 attention_mask: 指示哪些标记应该被模型关注的列表。值为 1 的位置表示该位置的词应该被注意而值为 0 的位置表示该位置的词是填充padding不应被模型关注。
print(max len of tweets,max([len(x.split()) for x in train.text]))
max_length 31将训练集的推文数据转换为 BERT 输入格式
#--------------------------------------------------------------------------------------------------------------------------------#
# add_special_tokens: 在输入序列的开始和结束添加特殊标记
# max_length: 指定输入序列的最大长度为 31
# return_tensorspt: 指定返回的张量格式为 PyTorch 的格式
#--------------------------------------------------------------------------------------------------------------------------------#
X tokenizer(texttrain[text].tolist(),add_special_tokensTrue,max_length31,truncationTrue,paddingTrue, return_tensorspt,return_token_type_ids False,return_attention_mask True,verbose True)X[input_ids].shapetorch.Size([7613, 31])封装数据集时训练集中的shuffle参数设置为True随机打乱数据可以防止模型学习到数据的顺序从而提高模型的泛化能力验证集和测试集shuffle参数设置为False能够保证测试集预测结果的一致性和可比性。
from sklearn.model_selection import train_test_split
from torch.utils.data import DataLoader, TensorDataset
input_ids torch.tensor(X[input_ids])
attention_mask torch.tensor(X[attention_mask])
labels torch.tensor(train[target].values)
# 切分数据
train_inputs, val_inputs, train_labels, val_labels, train_masks, val_masks train_test_split(input_ids, labels, attention_mask, test_size0.2, random_state42)
# 创建 TensorDataset
train_dataset TensorDataset(train_inputs, train_masks, train_labels)
val_dataset TensorDataset(val_inputs, val_masks, val_labels)
# 创建 DataLoader
train_dataloader DataLoader(train_dataset, batch_size32, shuffleTrue)
validation_dataloader DataLoader(val_dataset, batch_size32, shuffleFalse)
print(f训练集大小{len(train_inputs)})
print(f验证集大小{len(val_inputs)})定义功能函数
# 计算准确率
def flat_accuracy(preds, labels):# axis1在每一行中寻找最大值的索引pred_flat np.argmax(preds, axis1)labels_flat labelsreturn accuracy_score(labels_flat, pred_flat)
# 计算F1
def flat_f1(preds, labels):pred_flat np.argmax(preds, axis1).flatten()labels_flat labels.flatten()return f1_score(labels_flat, pred_flat)
# 将以秒为单位的时间转换为格式为 hh:mm:ss 的字符串
def format_time(elapsed): elapsed_rounded int(round((elapsed)))return str(datetime.timedelta(secondselapsed_rounded))
# 定义优化器
optimizer AdamW(model.parameters(), lr 6e-6, eps 1e-8)9.2 模型训练与验证
模型训练 模型训练函数
Paramsmodel 预定义模型dataloader 批处理数据optimizer 优化器
Returnsavg_train_loss本轮次遍历一遍训练集的平均损失avg_train_accuracy本轮次遍历一遍训练集准确率def model_train(model, train_dataloader, optimizer):# 设置模型为训练模式model.train()total_train_accuracy 0total_train_loss 0for step, batch in enumerate(train_dataloader):b_input_ids batch[0].to(device).to(torch.int64)b_input_mask batch[1].to(device).to(torch.int64)b_labels batch[2].to(device).to(torch.int64)# 清除上一次迭代的梯度信息防止梯度累积model.zero_grad() #-------------------------------------------------------------------------------------------------## 执行前向传播评估模型在该训练批次上的表现# 注这里的outputs包含 loss 和 logits而一般的神经网络只有logits#-------------------------------------------------------------------------------------------------#outputs model(b_input_ids, attention_maskb_input_mask, labelsb_labels)# 模型中传入了labels模型会默认计算交叉熵损失loss outputs.loss logits outputs.logits # 累加损失total_train_loss loss.item()#-------------------------------------------------------------------------------------------------## 将 logits 和 labels 移至 CPU# 对于logits使用 detach() 能关闭对该张量的梯度计算否则会报错# RuntimeError: Cant call numpy() on Tensor that requires grad. Use tensor.detach().numpy() instead.#-------------------------------------------------------------------------------------------------#logits logits.detach().cpu().numpy()label_ids b_labels.cpu().numpy()#-------------------------------------------------------------------------------------------------## 计算损失函数相对于模型参数的梯度并将这些梯度存储在每个参数的 .grad 属性中。# 随后优化器会使用这些梯度来更新模型参数从而逐步最小化损失函数实现模型的训练#-------------------------------------------------------------------------------------------------#loss.backward()# 裁剪梯度防止梯度爆炸问题# clip_grad_norm_(model.parameters(), 1.0) 表示如果模型参数的梯度范数超过 1.0则将其缩放到 1.0torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)# 使用优化器 optimizer 更新模型参数optimizer.step()# 更新学习率scheduler.step()# 计算该step的准确率total_train_accuracy flat_accuracy(logits, label_ids)avg_train_accuracy total_train_accuracy / len(train_dataloader)avg_train_loss total_train_loss / len(train_dataloader)return avg_train_accuracy, avg_train_loss模型验证 模型评估函数
Paramsmodel 预定义模型dataloader 批处理数据
Returnsavg_val_accuracy本轮次遍历一遍验证集的平均损失avg_val_loss本轮次遍历一遍验证集准确率avg_val_f1本轮次遍历一遍验证集的 f1 值def model_validate(model, validation_dataloader):# 设置模型为测试模式model.eval()total_val_loss 0total_val_accuracy 0total_val_f1 0for batch in validation_dataloader:b_input_ids batch[0].to(device)b_input_mask batch[1].to(device)b_labels batch[2].to(device)# 禁止梯度反传无需计算梯度with torch.no_grad(): #-------------------------------------------------------------------------------------------------## 注这里的outputs包含 loss 和 logits而一般的神经网络只有logits#-------------------------------------------------------------------------------------------------#outputs model(b_input_ids, attention_maskb_input_mask, labelsb_labels)loss outputs.loss logits outputs.logits # 累加损失total_val_loss loss.item()#-------------------------------------------------------------------------------------------------## 将 logits 和 labels 移至 CPU#-------------------------------------------------------------------------------------------------#logits logits.cpu().numpy()label_ids b_labels.cpu().numpy()# 累加准确率和f1值total_val_accuracy flat_accuracy(logits, label_ids)total_val_f1 flat_f1(logits, label_ids)avg_val_accuracy total_val_accuracy / len(validation_dataloader)avg_val_f1 total_val_f1 / len(validation_dataloader)avg_val_loss total_val_loss / len(validation_dataloader)return avg_val_accuracy, avg_val_f1, avg_val_lossepochs 10
total_steps len(train_dataloader) * epochs
# 创建一个学习率调度器
scheduler get_linear_schedule_with_warmup(optimizer, num_warmup_steps 0, num_training_steps total_steps)
train_accuracies []
train_losses []
val_accuracies []
val_losses []
val_f1s []
for epoch in range(epochs): t0 time.time()# 模型训练train_accuracy, train_loss model_train(model, train_dataloader, optimizer)#-------------------------------------------------------------------------------------------------## append(): 将整个对象作为一个单独的元素添加到列表中# extend(): 将可迭代对象中的每个元素逐个添加到列表中#-------------------------------------------------------------------------------------------------#train_accuracies.append(train_accuracy)train_losses.append(train_loss)# 模型验证val_accuracy, val_f1, val_loss model_validate(model, validation_dataloader)val_accuracies.append(val_accuracy)val_f1s.append(val_f1)val_losses.append(val_loss)print([第{}轮训练完成训练集中 Accuracy{:.3f}, 验证集中 Accuracy{:.3f} F1{:.3f}] 耗时{}.format(epoch1, train_accuracy, val_accuracy, val_f1, format_time(time.time()-t0)))
print(训练完成)[第1轮训练完成训练集中 Accuracy0.986, 验证集中 Accuracy0.811 F10.765] 耗时0:03:25
[第2轮训练完成训练集中 Accuracy0.987, 验证集中 Accuracy0.805 F10.773] 耗时0:03:25
[第3轮训练完成训练集中 Accuracy0.990, 验证集中 Accuracy0.803 F10.769] 耗时0:03:45
[第4轮训练完成训练集中 Accuracy0.991, 验证集中 Accuracy0.809 F10.765] 耗时0:04:08
[第5轮训练完成训练集中 Accuracy0.990, 验证集中 Accuracy0.792 F10.764] 耗时0:04:45
[第6轮训练完成训练集中 Accuracy0.990, 验证集中 Accuracy0.808 F10.772] 耗时0:05:08
[第7轮训练完成训练集中 Accuracy0.992, 验证集中 Accuracy0.815 F10.774] 耗时0:04:58
[第8轮训练完成训练集中 Accuracy0.993, 验证集中 Accuracy0.812 F10.774] 耗时0:05:00
[第9轮训练完成训练集中 Accuracy0.993, 验证集中 Accuracy0.810 F10.772] 耗时0:05:03
[第10轮训练完成训练集中 Accuracy0.992, 验证集中 Accuracy0.812 F10.772] 耗时11:22:25
训练完成10. 模型评估
可视化损失值和准确率
def loss_acc_plot(train_losses, val_losses, train_accuracies, val_accuracies):plt.figure(figsize(12, 4))plt.subplot(1, 2, 1)# 默认情况下plt.plot 会将 train_losses 的索引作为 X 轴的值plt.plot(train_losses, labelTrain Loss)plt.plot(val_losses, labelValidation Loss)plt.xlabel(Epoch)plt.ylabel(Loss)plt.legend()plt.subplot(1, 2, 2)plt.plot(train_accuracies, labelTrain Accuracy)plt.plot(val_accuracies, labelValidation Accuracy)plt.xlabel(Epoch)plt.ylabel(Accuracy)plt.legend()loss_acc_plot(train_losses, val_losses, train_accuracies, val_accuracies)# 可视化 F1 值
plt.plot(val_f1s,-o)
plt.xlabel(Epoch)
plt.ylabel(F1);注在本地CPU跑的F1值在0.77左右但是在kaggle中GPU跑的F1值在0.8左右原因未知。 绘制混淆矩阵
from sklearn.metrics import confusion_matrix
all_predictions []
all_labels []
for batch in validation_dataloader:b_input_ids batch[0].to(device)b_input_mask batch[1].to(device)b_labels batch[2].to(device)# 禁止梯度反传无需计算梯度with torch.no_grad(): #-------------------------------------------------------------------------------------------------## 注这里的outputs包含 loss 和 logits而一般的神经网络只有logits#-------------------------------------------------------------------------------------------------#outputs model(b_input_ids, attention_maskb_input_mask, labelsb_labels)loss outputs.loss logits outputs.logits #-------------------------------------------------------------------------------------------------## 将 logits 和 labels 移至 CPU#-------------------------------------------------------------------------------------------------#logits logits.cpu().numpy()label_ids b_labels.cpu().numpy()all_labels.extend(label_ids)predicted np.argmax(logits, axis1).flatten()all_predictions.extend(predicted)cm confusion_matrix(all_labels, all_predictions)
plt.figure(figsize(5, 5))
sns.heatmap(cm, annotTrue, fmtd, cmapBlues, cbarFalse, xticklabels[Not Disaster,Disaster], yticklabels[Not Disaster,Disaster])
plt.xlabel(Predicted Label)
plt.ylabel(True Label)
plt.title(Confusion Matrix);横轴为预测类别纵轴为实际类别。对标线上的值表示模型正确预测的样本数量非对角线上的值表示模型错误预测的样本数量。
11. 测试集分类
测试集数据处理
X_test tokenizer(texttest[text].tolist(),add_special_tokensTrue,max_length31,truncationTrue,paddingTrue, return_tensorspt,return_token_type_ids False,return_attention_mask True,verbose True)# 将 input_ids 和 attention_mask 转为张量
input_ids torch.tensor(X_test[input_ids])
attention_mask torch.tensor(X_test[attention_mask])# 创建 DataLoader
test_dataset TensorDataset(input_ids, attention_mask)
test_dataloader DataLoader(test_dataset, batch_size32, shuffleFalse)数据分类
# 设置模型为测试模式
model.eval()
all_test_pred []for batch in test_dataloader:b_input_ids batch[0].to(device)b_input_mask batch[1].to(device)# 禁止梯度反传with torch.no_grad(): #-------------------------------------------------------------------------------------------------## 注这里的outputs包含 loss 和 logits而一般的神经网络只有logits#-------------------------------------------------------------------------------------------------#outputs model(b_input_ids, attention_maskb_input_mask)loss outputs.loss logits outputs.logits # 将 logits 移至 CPU:logits logits.cpu().numpy()predicted np.argmax(logits, axis1)all_test_pred.extend(np.argmax(logits, axis1))生成提交文件
submission pd.read_csv(D:/Desktop/kaggle数据集/nlp-disaster/sample_submission.csv)
submission[target] all_test_pred
# submission.to_csv(/kaggle/working/submission.csv, indexFalse)
print(Submission file created!)12. 参考文献
词云绘制、数据可视化参考自 [1] Prediction of Tweets using BERT Model 词元分析、命名实体识别、推文主题提取、构建模型参考自 [2] Disaster Tweets NLP: EDA BERT With Transformers 和 [3] KerasNLP starter notebook Disaster Tweets