Kaggle自然语言处理入门之推特灾难文本分类

前言

第一次处理文本的时候还是做毕业论文时,那个时候什么也不懂,为了方便,用EXCEl进行了文本的预处理,然后用在线词云进行了画图和分词,导出的结果用ROST-CM做了情感分析和社会网络分析,最后用Gephi做了一个图,留下了不小的遗憾,好像非计算机专业的我就是入不了这个门。所以在Kaggle入门的路上看见NLP相关的东西总是有点忐忑,但是好奇心还是驱使我点开了这个数据集,这也成了我Kaggle入门以来花的时间最多的一个项目。

数据集描述

该任务全名为Natural Language Processing with Disaster Tweets。它的数据集与其他的kaggle数据集一样,分为train.csv,test.csv, samle_submission.csv三个文件。训练集文件中包含了7613条数据,由id列、关键词列、地点列、文字内容列、标签列组成。其中地点列空缺较多,大概不能提取出什么信息,关键词列表示文字内容列的关键词是什么,比如:
在这里插入图片描述
这一条推文说Aba这个地方发生了一场火灾,这个ablaze就是指着火的意思,它是整条内容的关键词,表示这条推特是灾害发生类的推特,因此它被标记为1。当然同时这个词语也可以成为“把人惹火”中的关键词,这种就是灾害无关的推特,它应该被标记为0所以我们的任务就是建立一个模型,能够把真正发生灾害的推特找出来标记为1,区别出那些无关灾害的推特标记为0。 而test.csv文件中就有3263条这样的推特等待我们标记。

此外,被标记0的数据有4000多条,而标记1的数据有3000多条,这也就预示着可能我们的分类器会往更保守的方向进行预测。

数据预处理

数据预处理是至关重要的部分,现在回头思考才发现,其实什么样的输入决定了你应该进行什么样的预处理。什么意思呢?首先我们肯定要进行文本清洗,这里举例一些有问题的文本内容:
@sunkxssedharry will you wear shorts for race ablaze ?

#PreviouslyOnDoyinTv: Toke Makinwa s marriage crisis sets Nigerian Twitter ablaze… http://t.co/CMghxBa2XI

Horrible Accident Man Died In Wings Of 聣脹脧Airplane聣脹聺 29-07-2015. WTF You Can聣脹陋t Believe Your EYES 聣脹脪… http://t.co/6fFyLAjWpS

这样的文本让人觉得头大,我们可能会理所当然地把@xxxx,#xxxxx,httpxxxxxx,以及没有列在上面的&xxxx和这个乱码聣脹x都去掉。但是,在经过试验之后会发现,其实这些我们看来的噪声在机器那边可能也是有价值的可以学习的特征。

事情是这样的,最开始的时候我试了kaggle官网的示例教程,它在不做数据清洗的情况下,直接用sklearn的CountVectorizer转换器把所有的文字变成了0,1编码,这样得到3万多不同的特征,做了一个岭回归,最后得到的分数有0.78057,我觉得这个教程它有问题好吧,结果在我经过一番数据清洗建立tf-idf的向量表示之后,再喂给分类器的得分竟然还没有它高! 原因其实有两个,一个就是一开始我没有注意这个任务要求评估的是F1分数,即精确率和召回率的综合估计,另外一个就是特征的缩减!我清洗后形成的tf-idf向量只有1万2千的特征,在我不断的放宽标准到1万4千的特征的时候,我的XGBoost分类器的分数终于超过了教程。。。有的时候特征的数量真的是可以为所欲为的

以上为发牢骚,本例我试了三种数据处理方法,

第一种是td-idf转换,首先得对文本进行简单清洗和停用词处理,再分词合并,转换为td-idf向量,即该词在语句出现的频率和在整篇语料钟出现频率的综合指标,然后把它喂给连续的分类器(预测结果为概率值,如xgboostlightgbm)(分数0.78302)。

第二种就是CountVectorizer,只对文本进行简单清洗不进行去除停用词等操作,再喂给朴素贝叶斯分类器中的BernoulliNB(分数0.79681)。

第三种同时也是效果最棒的一种方法就是用谷歌预训练好的BERT模型和配套分词py文件进行深度学习(分数0.84186)。

可以看出不同的模型需要的数据预处理方法不尽相同,想要万能的方案不太可能,只能自己一种种尝试过才知道优劣。

理想方案

这里我只介绍第二种方案,第三种不在我们的考虑之内,尽管我用Kaggle的背景板做了,因为深度学习真的很吃显卡和时间,而这个BERT模型包含了335,142,913个训练参数,我的可怜巴巴的1660ti根本跑不起来,用kaggle背景板虽然能跑,但整个训练过程(我只训练了三次模型)花了我大概20分钟,别说调整具体参数了,虽然它的效果确实是最好的,我跑出来的成绩到达了101名,排除前面那些作弊的大概是极限成绩了。

第二种朴素贝叶斯的方法可以算作常态方法下的极限成绩(排除深度学习和特殊特征构建)。

那么和我之前的文章一样,我把可执行的源代码完整的贴出来,首先是简单的文本清洗:

import numpy as np
import pandas as pd
import re
#读取数据集
train_df = pd.read_csv("/newstart/code/NLP/train.csv")
test_df = pd.read_csv("/newstart/code/NLP/test.csv")
#写清洗函数
def clean_text(rawText):
    rawText[:] = [re.sub(r'https?:\/\/.*\/\w*', 'URL', text) for text in rawText]
    rawText[:] = [re.sub(r'@\w+([-.]\w+)*', '', text) for text in rawText]
    rawText[:] = [re.sub(r'&\w+([-.]\w+)*', '', text) for text in rawText]
#调用函数就地工作
clean_text(train_df['text'])
clean_text(test_df['text'])

这里把htttp转化为了URL统一标识,去掉了@和&这两个相对于#来说没有价值的噪音,除了在测试中发现这条规律,其实在数据探索初期也可以发现,@后跟的都是用户名,&后跟的大部分都是amp,而#号后面可能还会跟一些关键词。所以这里我们保留了#xxxx这个相对有用的特征。

from sklearn import feature_extraction,model_selection,preprocessing
count_vectorizer = feature_extraction.text.CountVectorizer()      #创建转换器
example_train_vectors = count_vectorizer.fit_transform(train_df["text"][0:5]) #构建示例转换向量
print(example_train_vectors[0].todense().shape)  #打印出示例的维度
print(example_train_vectors[0].todense())            #打印出示例向量具体内容
train_vectors = count_vectorizer.fit_transform(train_df["text"])  #进行训练集文本的拟合和转换
test_vectors = count_vectorizer.transform(test_df["text"])       #进行测试集文本的转换

运行结果如下:
(1, 54)
[[0 0 0 1 1 1 0 0 0 0 0 0 1 1 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 1 0
0 0 0 1 0 0 0 0 0 0 0 0 0 1 1 0 1 0]]
可以看到前五行文本内容总共有54个单词,第一行已经被转换成了54个词的表示形式,0代表这个词语没有出现,1代表这个词语有出现,第一行总共有13个1,也就是13个词构成的文本。另外需要说明的是之所以测试集上只用了transform而不是fit_transform是因为我们假设训练集和测试集分布相同。

from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.naive_bayes import BernoulliNB
y_label = train_df['target']   #提取标签
X_train, X_test, y_train, y_test = train_test_split(train_vectors, y_label, test_size=0.1, random_state=7) #划分训练集和测试集
clf = BernoulliNB() #构建朴素贝叶斯分类器
clf.fit(train_vectors,y_label)  #拟合模型

这里用BernoulliNB贝叶斯分类器而不用另外两种,首先因为它是离散值,所以不能用GaussianNB,其次MultinomialNB的结果稍差于BernoulliNB。

import sklearn
y_pred = clf.predict(X_test) #进行自己划分的验证集上的预测

acc = accuracy_score(y_test, y_pred) #准确率
print("Accuracy: %.2f%%" % (acc * 100.0))
f1 = sklearn.metrics.f1_score(y_test, y_pred) #F1分数
print("f1_score: %.2f%%" % (f1 * 100.0))
from  sklearn.metrics import classification_report
 
print(classification_report(y_test, y_pred)) #形成报表

结果如下:
在这里插入图片描述
可以看到我们在1分类上的召回率和F1分数都比较低,前面也说过因为1分类较少,导致我们分类器可能做出保守判断,没有把全部的“犯人”都抓出来。此外这里的表现虽然比MultinomialNB高了九个百分点,但提交到kaggle结果里就差了0.0几,说明kaggle的测试集上其实不是很符合这个伯努利分布。

import seaborn as sns
from sklearn.metrics import accuracy_score, confusion_matrix
import matplotlib.pyplot as plt
conf_mat = confusion_matrix(y_test, y_pred) #构建混淆矩阵
fig, ax = plt.subplots(figsize=(10,8))
sns.heatmap(conf_mat,cmap="Oranges") #画热力图,颜色为orange
plt.ylabel('actually',fontsize=18) #纵轴为实际上的类别
plt.xlabel('predict',fontsize=18) #横轴为预测的类别

在这里插入图片描述
由于我们这里是二分类矩阵,所以画这样一个图好像没啥意义,但多分类任务就会看的比较清楚比如哪类和哪类混淆了导致判断错误比较多。

sample_submission = pd.read_csv("/newstart/code/NLP/sample_submission.csv")
Y = clf.predict(test_vectors)
sample_submission["target"] = Y
sample_submission.head()
sample_submission.to_csv("submission17.csv", index=False)

最后还是一样的生成csv文件提交,这里利用现成的直接改一列比之前新建一个要方便不少。

总结

在这次分类中呢,我们只是尽可能利用了训练集中包含的文本内容列,没有对关键词和地点列进行深挖,也没有做情感分析和主题建模,可能还有进一步挖掘的空间吧。另外在探索的时候发现,停用词库的选择其实也会影响到最后的结果,比如genism它将fire加入了停用词库,而nltk的停用词库没有把它包含进来。还有深度学习这个东西是真的很吃硬件啊!特别是LSTM模型训练的太慢了。。最后,很多东西不能只觉得它是什么样就是什么样,要试过才知道,有时候你认为的噪声反而是有用的信息呢。

  • 5
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值