很多时候,人们在网上晒各种东西、抒发情感。个体的情感分析可能没有多大用处,但对大多数人的情感进行分析,就能得到比较有趣的结果。想象一下,当一个热点新闻事件出现后,你可以通过分析大多数人的留言感知舆情,了解网络平台中人们的心情。本教程将会教你如何在社交平台上执行类似的分析操作。 用机器学习从文本中读取情绪称为情感分析(sentiment analysis),它是文本分类中突出的用例之一,属于自然语言处理(NLP)非常活跃的研究领域。其它应用比如,检测垃圾邮件、自动标记客户查询以及将文本分类为已定义的主题等。那么,如何做到这一点呢?
选择数据集
在开始之前,首先看看手上有什么数据。本文数据集来自UCI机器学习库中下载的Sentiment Labeled Sentences Data Set数据集。此数据集包括来自IMDb、Amazon和Yelp的标记评论。其中,对于负面情绪,每个评论的得分为0,对于积极的情绪,评分为1。将文件夹解压缩到一个data文件夹中,然后使用Pandas加载数据:
代码语言:javascript复制import pandas as pdfilepath_dict = {'yelp': 'data/sentiment_analysis/yelp_labelled.txt', 'amazon': 'data/sentiment_analysis/amazon_cells_labelled.txt', 'imdb': 'data/sentiment_analysis/imdb_labelled.txt'}df_list = []for source, filepath in filepath_dict.items():
df = pd.read_csv(filepath, names=['sentence', 'label'], sep='t')
df['source'] = source # Add another column filled with the source name
df_list.append(df)df = pd.concat(df_list)
print(df.iloc[0])
结果如下:
代码语言:javascript复制sentence Wow... Loved this place.
label 1source yelp
Name: 0, dtype: object
使用此数据集,可以训练模型来预测句子的情绪,下面可以考虑如何预测数据。 一种常见方法是计算每个句子中每个单词的频率,并将此计数与数据集中的整个单词组相关联。首先从创建词汇开始,收集好的词汇库在NLP中也被称为语料库。 在这种情况下,词汇表是在文本中出现的单词列表,每个单词都有自己的索引。然后为每个句子创建向量,并计算词汇表中的每个词的频次,得到的向量将具有词汇表的长度和词汇表中每个单词的次数,该向量也被称作特征向量。 在特征向量中,每个维度可以是数字或分类特征,例如建筑物的高度、股票的价格,或者是词汇表中单词的计数。这些特征向量是数据科学和机器学习的关键部分,因为训练的模型是根据特征向量来学习得到。 举例来说明这一点,假设有以下两句话:
代码语言:javascript复制sentences = ['John likes ice cream', 'John hates chocolate.']
接下来,可以使用scikit-learn库提供的CurrVoCurrisher来对句子进行矢量化,创建好词汇表后,可以使用该词汇来创建单词频次的特征向量:
代码语言:javascript复制from sklearn.feature_extraction.text import CountVectorizer
vectorizer = CountVectorizer(min_df=0, lowercase=False)
vectorizer.fit(sentences)
vectorizer.vocabulary_输出:
{'John': 0, 'chocolate': 1, 'cream': 2, 'hates': 3, 'ice': 4, 'likes': 5}
这个词汇表也可以作为每个单词的索引。上述句子中是由五个单词组成,每个单词代表词汇表中的一个单词。当使用该词汇表对两个句子进行CountVectorizer变换后,每个句子对应一个向量,表示句子中每个单词的计数:
代码语言:javascript复制vectorizer.transform(sentences).toarray()输出:
array([[1, 0, 1, 0, 1, 1],
[1, 1, 0, 1, 0, 0]])
现在,可以根据之前的词汇查看每个句子的结果特征向量。例如,如果查看第一列,可以看到两个向量都有是1,这意味着两个句子都有一次出现John,并在词汇表中排在第一位。 以上被认为是一个词袋(BOW))模型,这是NLP中用于创建文本向量的常用方法,每个文档都表示为一个向量。现在就可以将这些向量用作机器学习模型的特征向量。下面进入下一部分内容。
定义基线模型(baseline model)
使用机器学习方法时,一个重要的步骤就是定义基线模型。基线模型一般是一个简单的模型,然后进一步开发更高级模型。在这种情况下,将使用基线模型与更高级模型的性能进行比较,这也是本教程的主要内容。
首先,要将数据拆分为训练集和测试集,这样就可以评估训练好模型的准确性、泛化能力和过拟合情况。过拟合是指模型在训练数据上训练得太好,而在测试集上表现很差。有关过拟合(overfitting)处理的方法可以看这篇文章。
首先从数据集中提取Yelp数据集。之后得到句子和标签。.values
返还NumPy array类型,而不是pandas类型对象,这是由于在这种情况下,array类型的数据更易于使用:
from sklearn.model_selection import train_test_splitdf_yelp = df[df['source'] == 'yelp']sentences = df_yelp['sentence'].values
y = df_yelp['label'].valuessentences_train, sentences_test, y_train, y_test = train_test_split(
sentences, y, test_size=0.25, random_state=1000)
之后,将再次使用BOW模型来对句子进行向量化。由于在训练期间没有可用的测试数据,因此仅使用训练数据创建词汇表。使用此词汇表为训练和测试集的每个句子创建特征向量:
代码语言:javascript复制from sklearn.feature_extraction.text import CountVectorizervectorizer = CountVectorizer()
vectorizer.fit(sentences_train)X_train = vectorizer.transform(sentences_train)
X_test = vectorizer.transform(sentences_test)
X_trai
n输出:
<750x1714 sparse matrix of type '<class 'numpy.int64'>'
with 7368 stored elements in Compressed Sparse Row format>
可以看到生成的特征向量有750个样本,这些样本对数据分割后获得的训练样本数。每个样本有1714个维度,这也是词汇量的大小。此外,可以看到得到的是一个稀疏矩阵。
CountVectorizer
执行词语切分,将句子分成一组单词列表,正如之前在词汇表中看到的那样。此外,它还可以删除标点符号和特殊字符,并可以对每个单词应用其他预处理。
注意:
CountVectorizer()
使用了很多额外的参数,例如添加ngrams,这是因为目标是建立一个简单的基线模型。词语切分本身默认为token_pattern=’(?u)bww b
,这是一个正则表达式模式,表示“一个单词是由单词边界包围的2个或更多Unicode字符组成”。
下面将使用[逻辑回归]()分类模型,这是一种常用的分类模型。从数学上讲,实际上是基于输入特征向量0到1之间的回归。通过指定阈值(默认为0.5),将回归模型用于分类。可以使用scikit-learn库中提供的LogisticRegression分类器完成该操作:
代码语言:javascript复制from sklearn.linear_model import LogisticRegressionclassifier = LogisticRegression()
classifier.fit(X_train, y_train)
score = classifier.score(X_test, y_test)print("Accuracy:", score)输出:
Accuracy: 0.796
可以看到,yelp数据的准确度:0.7960,效果不错。将此方法应用于其它数据集上:
代码语言:javascript复制for source in df['source'].unique():
df_source = df[df['source'] == source]
sentences = df_source['sentence'].values
y = df_source['label'].values sentences_train, sentences_test, y_train, y_test = train_test_split(
sentences, y, test_size=0.25, random_state=1000) vectorizer = CountVectorizer()
vectorizer.fit(sentences_train)
X_train = vectorizer.transform(sentences_train)
X_test = vectorizer.transform(sentences_test) classifier = LogisticRegression()
classifier.fit(X_train, y_train)
score = classifier.score(X_test, y_test)
print('Accuracy for {} data: {:.4f}'.format(source, score))
输出结果
代码语言:javascript复制Accuracy for yelp data: 0.7960
Accuracy for amazon data: 0.7960
Accuracy for imdb data: 0.7487
可以看到,这个相当简单的模型表现不错。之后看看我们是否能够超越这个基线模型。接下来,我们将了解神经网络相关内容以及如何将它们应用于文本分类。
构建第一个Keras模型
人工智能和深度学习近年来非常火热,这里假设你已经熟悉神经网络相关的基本知识,如果你不了解的话,可以查看博主的这篇文章。此外,随着深度学习方法的兴起,相应的开源工具箱也有很多,比如Tensorflow、Keras、Theano或Caffe等,本文使用keras构建相应的神经网络模型。有关keras的安装和配置可以查阅相关的教程安装,这里不做过多的介绍。下面构建你的第一个Keras模型。 在构建模型之前,需要知道特征向量的输入维度,这仅在输入层需要设定,之后按顺序逐个添加图层,如下所示:
代码语言:javascript复制>>> from keras.models import Sequential>>> from keras import layers>>> input_dim = X_train.shape[1] # Number of features>>> model = Sequential()>>> model.add(layers.Dense(10, input_dim=input_dim, activation='relu'))>>> model.add(layers.Dense(1, activation='sigmoid'))
Using TensorFlow backend.
在开始模型训练之前,需要配置学习过程,通过.compile()
完成。此方法指定具体的优化方法和损失函数。
此外,可以添加用于评估的指标。本文使用二进制交叉熵作为损失函数和Adam优化器。Keras还具有.summary()
函数,可以概述模型和用于训练的参数数量:
>>> model.compile(loss='binary_crossentropy',
... optimizer='adam',
... metrics=['accuracy'])>>> model.summary()
_________________________________________________________________
Layer (type) Output Shape Param # =================================================================
dense_1 (Dense) (None, 10) 17150 _________________________________________________________________
dense_2 (Dense) (None, 1) 11 =================================================================
Total params: 17,161Trainable params: 17,161Non-trainable params: 0_______________________________
可以看到,每个特征向量有1714个维度、5个节点。之后需要为每个特征维度和每个节点考虑1714 * 5 = 8570
个参数的权重(weight),然后为每个节点增加5个额外偏差(bias),总共得到8575个参数。在最后一个节点中,有另外5个权重和一个偏差,总共得到6个参数。现在开始使用.fit()
函数进行训练。
由于神经网络中的训练是一个迭代过程,因此需要指定模型训练的迭代次数。完成一次迭代通常称为epochs
。我们运行100个epoch,以便能够看到每个epoch后训练损失和准确性如何变化。
另一个需要设定的参数是batchsize
,它负责设置在一个epoch中使用多少样本。由于本文数据集比较小,可以将该数值设置比较小:
>>> history = model.fit(X_train, y_train,... epochs=100,... verbose=False,... validation_data=(X_test, y_test)... batch_size=10)
现在可以使用.evaluate()
函数来评估模型的准确性,可以在训练数据和测试数据执行此操作。一般而言,训练数据的准确度高于测试数据。否则,出现过拟合的可能性就越大。
请注意,如果重新运行.fit()
函数,将从之前训练计算出的权重开始。确保在再次开始训练模型之前再次编译模型。下面评估模型的准确度:
>>> loss, accuracy = model.evaluate(X_train, y_train, verbose=False)>>> print("Training Accuracy: {:.4f}".format(accuracy))>>> loss, accuracy = model.evaluate(X_test, y_test, verbose=False)>>> print("Testing Accuracy: {:.4f}".format(accuracy))
Training Accuracy: 1.0000Testing Accuracy: 0.7960
从结果中可以看到,模型已经过拟合了,因为在训练集上达到100%准确度,而测试集上只有79.6%。但该测试集的准确性已经超过了之前使用的基线模型——逻辑回归,这也算是一种进步。 为了实验更加方便,可以使用小的辅助函数,根据历史回调可视化训练和测试数据的损失和准确性。在这种情况下,辅助函数使用matplotlib绘图库绘制模型的准确性:
代码语言:javascript复制import matplotlib.pyplot as plt
plt.style.use('ggplot')def plot_history(history):
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
x = range(1, len(acc) 1) plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.plot(x, acc, 'b', label='Training acc')
plt.plot(x, val_acc, 'r', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.subplot(1, 2, 2)
plt.plot(x, loss, 'b', label='Training loss')
plt.plot(x, val_loss, 'r', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
要使用此功能,只需使用plot_history()
即可:
>>> plot_history(history)
基线模型的准确率和loss损失
从上可以看到,模型已经训练了很长时间,在训练集上达到了100%的准确性。模型何时开始过拟合的一个判断方法是验证数据集上的损失曲线再次开始上升(20-40 epoch)。这个时刻也是阻止模型的一个好时机,可以提前停止训练(early stop)。
注意:在训练神经网络时,应该使用单独的测试和验证集。通常会采用在验证集上具有最高精度的模型,然后使用测试集测试该模型,这样可以确保不会过度使用模型。使用验证集来选择最佳模型是数据泄漏的一种形式,以便从数百次训练中选择产生最佳测试分数时的模型。当在该模型中使用训练数据集之外的信息时,会发生数据泄漏。
在这种情况下,测试和验证集是相同的,因为本文采用的样本量较小。正如之前所述,神经网络一般在大量样本数据集上表现最佳。在下一部分中,可以看到将单词表示为向量的不同方式。