简介
自然语言处理是当今十分热门的数据科学研究项目。情感分析则是自然语言处理中一个很常见的实践。例如可以借助民意测试来构建完整的市场策略,该领域已经极大的改变了当前的商业运行模式,所以每一个数据科学家都应该熟悉该领域的内容。
成千上万的文本数据可以在短时间内分析出情感类型(甚至是其他的特征,包括命名实体,话题,主题等等)。相比而言,如果使用人工来做这件事情,那将消耗一个团队的人数小时的时间。
接下来,我们将按照处理一般情感分析问题的方法来开展我们的工作。首先,我们需要预处理和清理Twitter的原始数据。然后,我们需要观察清洗好的数据,并依靠直观感觉来做一些常识。之后,我们要量化特征,并使用量化后的数据集来训练模型,最终实现标记tweet的情感。
这可以说是NLP中最有意思的挑战了,我实在有点迫不及待的跟你一起开始这次探索之旅!
内容的小目录
- 1.理解问题
- 2.预处理和数据清洗
- 3.制造故事和可视化效果
- 4.从清洗好的推文中提取特征
- 5.训练模型:情感分析
- 6.下一步要做什么
1.理解问题
在开始我们的工作之前,让我们再来看一遍问题,这对于确定我们的目标是非常重要的。问题是这样的: 这项任务的目标是检测出tweets中的负面言论。简单起见,我们认为包含负面言论的可以认为是种族主义或者性别歧视相关的内容。所以,任务就转化成了区分种族主义和性别歧视与其他的内容的分类任务。
在正式的情况下,给定的训练样本应该都已经标注好了,标签‘1’表示种族主义/性别歧视,标签‘0’表示不是种族主义/性别歧视。你的目标就是在给定的测试集上预测标签。 注意:本实验的评测标准为F1-score
Twitter情感分析
从我个人来说,负面言论,网络暴力,社交网络霸凌这些事情已经成为了非常尖锐的问题,能够做一个分析系统,去检测这些内容将会大大的发挥作用,肃清网络空间,清除网络暴力,还给网络一片净土。这也是我乐于接受这个任务的原因。现在就让我们来看看详细的步骤该怎么做吧。
2.Tweets 预处理和数据清洗
让我们看一下下面两张图片,这是处于两种不同状态的办公室。一个凌乱不堪,一个清理的干净整洁。
假如你要在这个办公室里寻找某个文件,在那个环境下你更容易找到?当然是后面那个干净整洁的,每一个物品都归置到了合适的位置。数据清洗跟这个过程十分相似。如果数据能够被规整成结构化的格式,那从中找到正确的信息将轻而易举。
预处理数据是个必要的步骤,这是为了数据挖掘做准备。这会让提取信息和机器学习算法的处理变得简单。如果跳过这一步,那么很大概率你会碰上充满噪声和偏差的数据集。这一步的目的就是把那些噪声信息剔除掉,因为噪声信息对于情感分析没有什么贡献,比如那些标点符号,特殊字符,数字,以及对文本的权重贡献很低的内容。
在后续的步骤中,我们会从数据集中提取数字特征。这个特征空间是使用数据集中所有不重复的单词构建的。所以如果我们对数据预处理做得好,那之后我们也会获得一个品质更好的特征空间。
首先,让我们读出数据,并加载必要的依赖库。你可以在下面的地址下载数据集:数据集下载地址
代码语言:javascript复制import reimport pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as snsimport stringimport nltkimport warnings
warnings.filterwarnings("ignore", category=DeprecationWarning)
%matplotlib inline
代码语言:javascript复制train = pd.read_csv('train_E6oV3lV.csv')test = pd.read_csv('test_tweets_anuFYb8.csv')
让我们来看下数据
代码语言:javascript复制train.head()
数据情况
可以看到,数据包含三列,id,label和tweet.label是一个二进制数值,tweet包含了我们需要清理的评论内容。 看了头部数据之后,我们大概可以开始清理数据了,清理大概可以从下面几个方面入手:
- 由于隐私保护的问题,Twitter的用户名已经被隐去,取而代之的是‘@user’。 这个标签没有任何实际意义。
- 我们也考虑去掉标点符号,数字甚至特殊字符,他们也对区分不同的评论起不到任何作用。
- 大多数太短的词起不到什么作用,比如‘pdx’,‘his’,‘all’。所以我们也把这些词去掉。
- 执行完上述三步之后,我们就可以把每一条评论切分成独立的单词或者符号了,这在所有NLP任务中都是必须的一步。
- 在第四个数据中,有一个单词‘love’.与此同时,在余下的语料中我们可能会有更多的单词,例如loves,loving,lovable等等。这些词其实都是一个词。如果我们能把这些词都归到它们的根源上,也就是都转换成love,那么我们就可以大大降低不同单词的数量,而不会损失太多信息。
A)移除Twitter标识@user
如上所述,这些评论文本包含很多Twitter标记,这些都是Twitter上面的用户信息。我们需要把这些内容删掉,他们对于情感分析没有什么帮助。 方便起见,先把训练集和测试集合起来。避免在训练集和测试集上重复操作的麻烦。
代码语言:javascript复制combi = train.append(test, ignore_index=True)
下面是一个自定义的方法,用于正则匹配删除文本中不想要的内容。它需要两个参数,一个是原始文本,一个是正则规则。这个方法的返回值是原始字符串清除匹配内容后剩下的字符。在我们的实验中,我们将使用这个方法来去除@user标记
代码语言:javascript复制def remove_pattern(input_txt, pattern):
r = re.findall(pattern, input_txt) for i in r:
input_txt = re.sub(i, '', input_txt)
return input_txt
现在,我们新建一列tidy_tweet ,用于存放处理后的内容。就是上面说的去掉Twitter标记的内容。
代码语言:javascript复制# remove twitter handles (@user)combi['tidy_tweet'] = np.vectorize(remove_pattern)(combi['tweet'], "@[w]*")
B)去除标点符号,数字和特殊字符
这些字符都是没有意义的。跟上面的操作一样,我们把这些字符也都剔除掉。 使用替换方法,去掉这些非字母内容
代码语言:javascript复制# remove special characters, numbers, punctuationscombi['tidy_tweet'] = combi['tidy_tweet'].str.replace("[^a-zA-Z#]", " ")
C) 移除短单词
这里要注意到底多长的单词应该移除掉。我的选择是小于等于三的都去掉。例如hmm,oh这样的都没啥用,删掉这些内容好一些
代码语言:javascript复制combi['tidy_tweet'] = combi['tidy_tweet'].apply(lambda x: ' '.join([w for w in x.split() if len(w)>3]))
我们再来看一下数据集的头几行
代码语言:javascript复制combi.head()
此时,清洗前后的数据对比已经非常明显了。重要的单词被留了下来,噪声内容被剔除了。
D)符号化
下面我们要把清洗后的数据集符号化。符号指的是一个个的单词,符号化的过程就是把字符串切分成符号的过程。
代码语言:javascript复制tokenized_tweet = combi['tidy_tweet'].apply(lambda x: x.split())
tokenized_tweet.head()
E)提取词干
提取词干说的是基于规则从单词中去除后缀的过程。例如,play,player,played,plays,playing都是play的变种。
代码语言:javascript复制from nltk.stem.porter import *
stemmer = PorterStemmer()
tokenized_tweet = tokenized_tweet.apply(lambda x: [stemmer.stem(i) for i in x]) # stemmingtokenized_tweet.head()
提取词干之后
现在,我们把这些符号重新拼回去。使用nltk的MosesDetokenizer方法很容易做到。
代码语言:javascript复制from nltk.tokenize.moses import MosesDetokenizer
detokenizer = MosesDetokenizer()for i in range(len(tokenized_tweet)):
tokenized_tweet[i] = detokenizer.detokenize(tokenized_tweet[i], return_str=True)
combi['tidy_tweet'] = tokenized_tweet
3.制造故事和可视化效果
在这一小节,我们要深入了解数据。不论是文本数据还是其他数据,探索并进行数据可视化都是一个快速深入了解的必要手段。不必局限于本教程所述的几种方式,你可以放开手脚尝试更多的方法。 开始探索之前,我们先来思考几个关于数据方面的问题:
- 数据集中最常见的单词有哪些?
- 数据集上表述正面和负面言论的常见词汇有哪些?
- 评论一般有多少主题标签?
- 我的数据集跟哪些趋势相关?
- 哪些趋势跟情绪相关?他们和情绪是吻合的吗?
A)使用 词云 来了解评论中最常用的词汇
现在,我想了解一下定义的情感在给定的数据集上是如何分布的。一种方法是画出词云来了解单词分布。 词云指的是一种用单词绘制的图像。出现频率越高的词在图案中越大,出现频率越低的词在图案中越小。 下面就来绘制基于我们的数据的词云图像。
代码语言:javascript复制all_words = ' '.join([text for text in combi['tidy_tweet']])from wordcloud import WordCloud
wordcloud = WordCloud(width=800, height=500, random_state=21, max_font_size=110).generate(all_words)
plt.figure(figsize=(10, 7))
plt.imshow(wordcloud, interpolation="bilinear")
plt.axis('off')
plt.show()
词云效果图
可以看到大部分词汇都是正能量的或者中性的。happy和love是高频词汇。从这上面似乎看不出这些内容跟种族歧视或者性别歧视有什么关系。所以,我们应该分开去画词云,分成是种族歧视/性别歧视或者不是两种数据。 B)非种族歧视/性别歧视的数据
代码语言:javascript复制normal_words =' '.join([text for text in combi['tidy_tweet'][combi['label'] == 0]])
wordcloud = WordCloud(width=800, height=500, random_state=21, max_font_size=110).generate(normal_words)
plt.figure(figsize=(10, 7))
plt.imshow(wordcloud, interpolation="bilinear")
plt.axis('off')
plt.show()
词云
可以看出,这个词云上的词基本都是正能量或者中性词。happy,smile,love都是高频词。大多数高频词跟我们的分类是吻合的,都属于积极向上的情感。如法炮制,我们将画出负能量情感词云。我们希望能看到那些负面的,种族歧视,性别歧视相关的词。 C)种族歧视/性别歧视数据
代码语言:javascript复制negative_words = ' '.join([text for text in combi['tidy_tweet'][combi['label'] == 1]])
wordcloud = WordCloud(width=800, height=500,
random_state=21, max_font_size=110).generate(negative_words)
plt.figure(figsize=(10, 7))
plt.imshow(wordcloud, interpolation="bilinear")
plt.axis('off')
plt.show()
词云
可以明显看到,图上的词基本都有负面含义。看起来我们的数据集还不错。下一步,我们将进行分析这些Twitter数据上的主题标签。 D)理解主题标签/流行趋势对于评论情感的影响 主题标签指的是Twitter中用来在一段时间内标注某些流行趋势的词语。我们需要对这些标签进行检测,看看他们是不是会对情感分析任务产生影响,是否能够对区分评论有帮助。 带有#前缀的词就是主题标签,例如,下面的情况:
这个例子似乎是带有性别歧视的内容,而主题标签看起来也有这个意思。 我们存下所有主题标签,并分成两类,一类是非歧视内容中的标签,一类是带有歧视内容中的标签。
代码语言:javascript复制# function to collect hashtagsdef hashtag_extract(x):
hashtags = [] # Loop over the words in the tweet
for i in x:
ht = re.findall(r"#(w )", i)
hashtags.append(ht) return hashtags
代码语言:javascript复制# extracting hashtags from non racist/sexist tweetsHT_regular = hashtag_extract(combi['tidy_tweet'][combi['label'] == 0])# extracting hashtags from racist/sexist tweetsHT_negative = hashtag_extract(combi['tidy_tweet'][combi['label'] == 1])# unnesting listHT_regular = sum(HT_regular,[])
HT_negative = sum(HT_negative,[])
现在,我们已经准备好了主题标签,可以把top n的标签画出来。 先来看一下非歧视组的标签吧。
代码语言:javascript复制a = nltk.FreqDist(HT_regular)
d = pd.DataFrame({'Hashtag': list(a.keys()), 'Count': list(a.values())})# selecting top 10 most frequent hashtags d = d.nlargest(columns="Count", n = 10)
plt.figure(figsize=(16,5))
ax = sns.barplot(data=d, x= "Hashtag", y = "Count")
ax.set(ylabel = 'Count')
plt.show()
TOP 10 标签
所有标签都是正向的,这符合预期。现在再来画另外一组,我们期望得到的都是负面的标签。
代码语言:javascript复制b = nltk.FreqDist(HT_negative)
e = pd.DataFrame({'Hashtag': list(b.keys()), 'Count': list(b.values())})# selecting top 10 most frequent hashtagse = e.nlargest(columns="Count", n = 10)
plt.figure(figsize=(16,5))
ax = sns.barplot(data=e, x= "Hashtag", y = "Count")
ax.set(ylabel = 'Count')
plt.show()
TOP 10 标签
正如我们所预料的,大部分词都是负面的,只有少数属于中性的。所有,留下这些标签用于后续的计算是个好主意。下面,我们将开始从符号化数据中提取标签。
4.从清洗后的推文中提取特征
要分析清洗后的数据,就要把它们转换成特征。根据用途来说,文本特征可以使用很多种算法来转换。比如词袋模型(Bag-Of-Words),TF-IDF,word Embeddings之类的方法。 在本文中,我使用了Bag-Of-Words和TF-IDF两个方法。
词袋特征
Bag-Of-Words是一种数字化表达特征的方式。假设有一个语料集合C,其中有D篇文章(d1,d2,...dD),在C中有N个不重复的符号。那么这N个符号(即单词)构成一个列表,那么词袋模型的矩阵M的大小就是D*N.M中的每一行记录了一篇文章D(i)中对应符号的词频。
让我们用一个简单的例子来加强理解。假设我们只有两篇文章 D1: He is a lazy boy. She is also lazy.
D2: Smith is a lazy person. 构建包含所有去重单词的list = [‘He’,’She’,’lazy’,’boy’,’Smith’,’person’] 那么,在这个语料C上,D=2,N=6 词袋模型的矩阵M的大小就是2*6
矩阵
现在,这个矩阵就可以作为特征矩阵来构建一个分类模型了。 使用sklearn的CountVectorizer方法可以轻松的构建词袋模型。 设置参数max_features = 1000 ,只取词频前1000的词。
代码语言:javascript复制from sklearn.feature_extraction.text import CountVectorizer
bow_vectorizer = CountVectorizer(max_df=0.90, min_df=2, max_features=1000, stop_words='english')# bag-of-words feature matrixbow = bow_vectorizer.fit_transform(combi['tidy_tweet'])
TF-IDF特征
这个方法也是基于词频的。但是它跟词袋模型还有一些区别,主要是它还考虑了一个单词在整个语料库上的情况而不是单一文章里的情况。 TF-IDF方法会对常用的单词做惩罚,降低它们的权重。同时对于某些在整个数据集上出现较少,但是在部分文章中表现较好的词给予了较高的权重。 来深入了解一下TF-IDF:
- TF = 单词t在一个文档中出现的次数 / 文档中全部单词的数目
- IDF = log(N/n),N是全部文档数目,n是单词t出现的文档数目
- TF-IDF = TF*IDF
from sklearn.feature_extraction.text import TfidfVectorizer
tfidf_vectorizer = TfidfVectorizer(max_df=0.90, min_df=2, max_features=1000, stop_words='english')# TF-IDF feature matrixtfidf = tfidf_vectorizer.fit_transform(combi['tidy_tweet'])
5.训练模型:情感分析
到目前为止,所有预处理都已经完成了。下面我们就开始基于这两个特征集合开始训练预测模型。 我们使用逻辑回归来构建模型。逻辑回归大意,会拟合数据到一个逻辑方程式上,从而预测事件发生的概率。 下面这个方程就是逻辑回归使用的式子:
逻辑回归方程
要了解更多逻辑回归的内容,可以阅读以下内容:逻辑回归文献地址 注意:如果你想尝试其他的机器学习算法,诸如随机森林,支持向量机,XGBoot,下面这个地址可以提供一套成熟的课程来帮助你在情感分析上进行实践。 地址:https://trainings.analyticsvidhya.com.
A)使用词袋模型特征集合构建模型
代码语言:javascript复制from sklearn.linear_model import LogisticRegressionfrom sklearn.model_selection import train_test_splitfrom sklearn.metrics import f1_score
train_bow = bow[:31962,:]
test_bow = bow[31962:,:]# splitting data into training and validation setxtrain_bow, xvalid_bow, ytrain, yvalid = train_test_split(train_bow, train['label'], random_state=42, test_size=0.3)
lreg = LogisticRegression()
lreg.fit(xtrain_bow, ytrain) # training the modelprediction = lreg.predict_proba(xvalid_bow) # predicting on the validation setprediction_int = prediction[:,1] >= 0.3 # if prediction is greater than or equal to 0.3 than 1 else 0prediction_int = prediction_int.astype(np.int)
f1_score(yvalid, prediction_int) # calculating f1 score
输出结果:0.53 训练出的逻辑回归模型在验证集上给出的f1值为0.53。现在,我们使用这个模型来预测测试集数据。
代码语言:javascript复制test_pred = lreg.predict_proba(test_bow)
test_pred_int = test_pred[:,1] >= 0.3
test_pred_int = test_pred_int.astype(np.int)test['label'] = test_pred_int
submission = test[['id','label']]
submission.to_csv('sub_lreg_bow.csv', index=False) # writing data to a CSV file
在公开的排行榜上,f1值是0.567.现在我们再来训练基于TF-IDF特征集的模型,看看表现如何。
B)使用TF-IDF特征集合构建模型
代码语言:javascript复制train_tfidf = tfidf[:31962,:]
test_tfidf = tfidf[31962:,:]
xtrain_tfidf = train_tfidf[ytrain.index]
xvalid_tfidf = train_tfidf[yvalid.index]
lreg.fit(xtrain_tfidf, ytrain)
prediction = lreg.predict_proba(xvalid_tfidf)
prediction_int = prediction[:,1] >= 0.3prediction_int = prediction_int.astype(np.int)
f1_score(yvalid, prediction_int)
输出结果:0.544 这个结果跟公开排行榜上的0.564更接近了,看起来比词袋模型有所提升。
6.下一步要做什么?
再打一遍广告,如果你想更进一步的研究情感分析,我们为你准备了一系列的课程,可以在下面这个地址访问:https://trainings.analyticsvidhya.com/.
这个课程包括了更多高级技术,比如特征提取的时候使用word2vec模型,更多机器学习算法,模型调参等等内容。 在课程中,你会学习到下面的内容:(翻译按:反反复复啊)
- 使用Embeddings(word2vec,doc2vec)来创建更好的特征
- 实践先进的机器学习算法,比如SVM,RF,XGBoost
- 模型调参
- 自定义指标
结语
在本文中,我们学习了如何实际解决情感分析的问题。我们对数据进行预处理并进行了探索。然后我们使用词袋模型,TF-IDF方法提取特征。最后构建了两个分类模型。
你觉得这篇文章有用吗?你有什么好的技巧吗?你在特征提取环节使用过什么其他方法吗?欢迎来讨论和分享你的经验在这个地址。。。,我们很期待跟你进行讨论。