编者按:自然语言处理(NLP)是数据科学研究的一个温床,它最常见的应用之一是情感分析。从民意调查到指定整个营销策略,现在,这种技术已经完全重塑了企业的工作方式,这也是为什么每个数据科学家都要对它有所了解的原因之一。
本文将围绕以twitter为代表英语文本进行分析,在文中,我们会逐步介绍常规情感分析所需的一系列步骤:从数据预处理开始,到探索文本数据,再到结合上下文产生理解,然后从数据中提取数字特征,并最终用这些特征集来训练模型,使它具备识别情感的能力。
实践是检验真理的唯一标准,它同样也是学习的一个好方法。为了让读者更贴近教程,这里我们选用Analytics Vidhya上的一个竞赛任务:Twitter情感分析。里面包含下文使用的数据集。
在开始任务前,我们先来读读任务描述,理解任务目标:
这项任务的目的是检测推文中的仇恨言论。为了简单起见,这里我们只把包含种族主义和性别歧视的推文视为仇恨言论,因此,任务目标是从给定推文中分类出包含种族主义、性别歧视观点的推文。
任务提供的训练样本有其相应的分类标签,其中标签“1”表示推文包含种族主义/性别歧视观点,标签“2”则是不包含。我们的目标是用训练集训练模型,让它在测试集上准确预测推文标签。
当我们进行探索时,电脑中的新问题就像现实生活中的陌生环境,我们对它一无所知,但它却处处包含我们所需的信息。
上图是两个办公室,一个凌乱不堪,一个整洁有序,你觉得自己在哪儿能更快找到文档呢?这不是我们自己的房间,所以毫无疑问的,第二个。同理,如果我们的数据能以结构化的格式排列,模型也能更容易从中找到正确信息。
数据预处理是我们面对任何问题时必不可少的一步,在这个任务中,我们要做的是清除和推文情感不怎么相关的各种噪声,比如标点符号、特殊字符、数字和表示语气的词。这些内容在文本上下文中没有太大权重,再加上我们之后要提取数字特征,去除这些内容有助于之后获得质量更高的特征空间。
首先,我们检查数据,并加载必要的库:
import re
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import string
import nltk
import warnings
warnings.filterwarnings("ignore", category=DeprecationWarning)
%matplotlib inline
train = pd.read_csv('train_E6oV3lV.csv')
test = pd.read_csv('test_tweets_anuFYb8.csv')
记得检查一下训练集的前几行,看看有没有问题:
train.head()
可以看到,数据一共有三列:id、标签和推文文本。标签是二进制目标变量,而推文包含我们将清洗和预处理的文字。以下是数据预处理的几个要求:
为了照顾隐私,数据集把用户名一致标记为@user,这个句柄对推文内容毫无用处,需要删除。
文中的标点符号、特殊字符和数字也没有多大含义,也起不到区分作用,需要删除。
对于情感分析,一些非常短的词也没有实际价值,比如“pdx”“his”“all”,需要删除。
一旦我们执行了上述三个步骤,我们就可以将每个推文分成单个单词或词例,这是任何NLP任务中必须的步骤。
第4条推文中出现了一个“love”,考虑到其他推文中可能会出现类似的“loves”“loving”“lovable”,我们可以把它们都缩成词根“love”,减少数据中唯一单词的总数,同时也不会丢失大量信息。
1. 删除推文句柄@user
如上所述,推文包含许多Twitter句柄(@user),我们需要删掉它们。为了方便起见,这个步骤可以结合训练集和测试集,把两边的句柄同时删掉,防止之后进行重复操作。
combi = train.append(test, ignore_index=True)
下面是我们定义的函数,用于删除推文中不需要的内容。它有两个参数,一个是原始文本字符串,另一个是要从字符串中删去的内容:
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,用它存储清洗后的推文:
#remove twitter handles(@user)
combi [ 'tidy_tweet' ] = np.vectorize(remove_pattern)(combi [ 'tweet' ],“@ [\ w] *” )
2. 删除标点符号、数字和特殊字符
之前提到了,标点符号、数字和特殊字符也没有多大用处,所以在删除句柄时,我们可以一并把它们处理了:
# remove special characters, numbers, punctuations
combi['tidy_tweet'] = combi['tidy_tweet'].str.replace("[^a-zA-Z#]", " ")
3. 删除短词
同理,一些过短的单词也是我们的删除目标,但执行这一步时我们要小心,因为有些包含意义的单词本身也很短。经过综合评判,最后我们把长度为3及以下的词语统统删去:
combi['tidy_tweet'] = combi['tidy_tweet'].apply(lambda x: ' '.join([w for w in x.split() if len(w)>3]))
处理完毕后,我们需要回过头来重新检查数据集的前几行,看看有没有错误。
combi.head()
如上图所示,我们可以明显看出原始推文和tidy_tweet之间的区别,后者删除了大量不必要的噪声,只保留重要单词。
4. 分词
现在,我们已经对数据完成清洗,接下来就是在本文中标记出单独的单词或词例,所谓分词就是把文本切分成一个个单独的词的过程。
tokenized_tweet = combi['tidy_tweet'].apply(lambda x: x.split())
tokenized_tweet.head()
5. 提取词干
词干是词的一部分,由词根和词缀构成,放在我们的任务中,就是把英文单词的后缀“ing”“ly”“es”“s”等剥离。
from nltk.stem.porter import *
stemmer = PorterStemmer()
tokenized_tweet = tokenized_tweet.apply(lambda x: [stemmer.stem(i) for i in x]) # stemming
tokenized_tweet.head()
到目前为止,我们已经完成对数据的预处理,之后就是探索这些数据,从中获得重要理解。在开始探索前,结合任务目标,我们可能会产生一些新的问题:
整个数据集中出现频率最高的单词是哪个?
仇恨言论和非仇恨言论推文中,出现频率最高的单词又分别是哪个?
一条推文中有几个词例?
哪种趋势和数据集本身有关?
哪种趋势和这两种言论有关?它们和相应情感相符吗?
1. 找出推文中的常用词:WordCloud
为了查看单体在整个训练集中的分布情况,一种可行的方法是绘制WordCloud图,这是一种可视化形式,能把出现频率较高的词放大,把出现频率最高的较低的词缩小。
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”最为常见,从它们身上我们找不到和仇恨言论有关的内容。所以接下来,我们会为标签为“1”和“0”的两类数据单独绘制WordCloud,并以此查看词频分布情况。
2. 非种族主义/性别歧视推文中的单词
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”最常见。
3. 种族主义/性别歧视推文的单词
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数据中添加主题标签/趋势。
4. 主题标签对推文情感的影响
twitter里的主题标签#就相当于国内社交平台的话题,表示推文包含某个特定时间点的热搜内容。这是文本中一项重要内容,我们可以利用这些标签探索它们和推文情感的关系。
例如,下面是我们数据集中的一条推文:
这段内容涉及性别歧视,而它的主题标签也传达了同样的意思。我们把这些标签放进两类推特文本的表格中,看看这些内容的出现情况。
# function to collect hashtags
def 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
# extracting hashtags from non racist/sexist tweets
HT_regular = hashtag_extract(combi['tidy_tweet'][combi['label'] == 0])
# extracting hashtags from racist/sexist tweets
HT_negative = hashtag_extract(combi['tidy_tweet'][combi['label'] == 1])
# unnesting list
HT_regular = sum(HT_regular,[])
HT_negative = sum(HT_negative,[])
非种族主义/性别歧视推文
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()
非种族主义/性别歧视推文出现频率较高的主题标签都是积极的、正面的,这不难理解。那么我们来看看种族主义/性别歧视推文中的情况。
种族主义/性别歧视推文
b = nltk.FreqDist(HT_negative)
e = pd.DataFrame({'Hashtag': list(b.keys()), 'Count': list(b.values())})
# selecting top 10 most frequent hashtags
e = 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()
正如预期的那样,大多数标签都是负面的,也有一些中性标签。所以我们把这些主题标签保留下来是个明确的选择,它们确实包含区分仇恨言论和非仇恨言论的关键信息。
如果要分析预处理数据,首先我们要把它们转换为特征。现在,构建文本特征的方法有很多,比如词袋模型、TF-IDF和词嵌入。在这个问题下,我们主要介绍前两种方法。
1. 词袋特征
词袋(Bag of Words,简称BoW)是一种统计某个词在一份文档中出现次数的算法,统计所得的词频数据可以用于比较文档并测量其相似性。假设我们手头有一个语料库C {d1,d2…..dD}(D个文本),它包含N个唯一词例(单词),那么它的词袋矩阵大小M就等于N×D。矩阵M中的每一行包含文档D(i)中的词例出现频率。
让我们用一个直观的例子来理解这一点。假设我们有两个文本:
D1:He is a lazy boy. She is also lazy.
D2:Smith is a lazy person.
它们包含的唯一词例是[‘He’,’She’,’lazy’,’boy’,’Smith’,’person’],所以D=2,N=6,矩阵M=2×6,也就是:
上述矩阵中的列可用作构建分类模型的特征,当然,提到词袋,首选sklearn,它的CountVectorizer函数可以直接创建词袋特征。我们设max_features = 1000,取词频排名前1000的词例。
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 matrix
bow = bow_vectorizer.fit_transform(combi['tidy_tweet'])
2. TF-IDF特征
这是另一种基于词频的方法,它和词袋方法的不同之处在于它不仅考虑单个文档(或推文)中单词的出现次数,而且考虑整个语料库。
虽然是基于词频,但TF-IDF测量的是相关性,而非频率。首先,它会统计某一特定文档中的词的出现次数,但是,由于“and”、“the”之类的词所有文档中都频繁出现,这些词的频率需要调整。这就是逆文档频率的部分。出现某一个词的文档数量越多,这个词作为信号的价值就越小。这样做的目的是仅留下独特的高频词用作标记。每个词的TF-IDF相关性是一种归一化的数据格式,总和也是1。
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 matrix
tfidf = tfidf_vectorizer.fit_transform(combi['tidy_tweet'])
现在,数据已经处理好了,数据的数字特征也已经被提取出来,最后就是用逻辑回归构建模型。
1. 用词袋特征构建模型
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import f1_score
train_bow = bow[:31962,:]
test_bow = bow[31962:,:]
# splitting data into training and validation set
xtrain_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 model
prediction = lreg.predict_proba(xvalid_bow) # predicting on the validation set
prediction_int = prediction[:,1] >= 0.3 # if prediction is greater than or equal to 0.3 than 1 else 0
prediction_int = prediction_int.astype(np.int)
f1_score(yvalid, prediction_int) # calculating f1 score
输出:0.525。
这个模型的F1 score是0.525,我们把它放到测试集上进行预测:
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
得分:0.537。
2. TF-IDF特征
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.3
prediction_int = prediction_int.astype(np.int)
f1_score(yvalid, prediction_int)
输出:0.538,最终得分:0.547,比起词袋特征有所改善。
以上就是twitter情感分析模型构建的全部教程,希望本文对你有帮助。
原文地址:www.analyticsvidhya.com/blog/2018/07/hands-on-sentiment-analysis-dataset-python/