很多时候,人们在网上晒各种东西、抒发情感。个体的情感分析可能没有多大用处,但对大多数人的情感进行分析,就能得到比较有趣的结果。想象一下,当一个热点新闻事件出现后,你可以通过分析大多数人的留言感知舆情,了解网络平台中人们的心情。本教程将会教你如何在社交平台上执行类似的分析操作。
用机器学习从文本中读取情绪称为情感分析(sentiment analysis),它是文本分类中突出的用例之一,属于自然语言处理(NLP)非常活跃的研究领域。其它应用比如,检测垃圾邮件、自动标记客户查询以及将文本分类为已定义的主题等。那么,如何做到这一点呢?
在开始之前,首先看看手上有什么数据。本文数据集来自UCI机器学习库中下载的Sentiment Labeled Sentences Data Set数据集。此数据集包括来自IMDb、Amazon和Yelp的标记评论。其中,对于负面情绪,每个评论的得分为0,对于积极的情绪,评分为1。将文件夹解压缩到一个data文件夹中,然后使用Pandas加载数据:
import pandas as pd
filepath_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])
结果如下:
sentence Wow... Loved this place.
label 1
source yelp
Name: 0, dtype: object
使用此数据集,可以训练模型来预测句子的情绪,下面可以考虑如何预测数据。
一种常见方法是计算每个句子中每个单词的频率,并将此计数与数据集中的整个单词组相关联。首先从创建词汇开始,收集好的词汇库在NLP中也被称为语料库。
在这种情况下,词汇表是在文本中出现的单词列表,每个单词都有自己的索引。然后为每个句子创建向量,并计算词汇表中的每个词的频次,得到的向量将具有词汇表的长度和词汇表中每个单词的次数,该向量也被称作特征向量。
在特征向量中,每个维度可以是数字或分类特征,例如建筑物的高度、股票的价格,或者是词汇表中单词的计数。这些特征向量是数据科学和机器学习的关键部分,因为训练的模型是根据特征向量来学习得到。
举例来说明这一点,假设有以下两句话:
sentences = ['John likes ice cream', 'John hates chocolate.']
接下来,可以使用scikit-learn库提供的CurrVoCurrisher来对句子进行矢量化,创建好词汇表后,可以使用该词汇来创建单词频次的特征向量:
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变换后,每个句子对应一个向量,表示句子中每个单词的计数:
vectorizer.transform(sentences).toarray()
输出:
array([[1, 0, 1, 0, 1, 1],
[1, 1, 0, 1, 0, 0]])
现在,可以根据之前的词汇查看每个句子的结果特征向量。例如,如果查看第一列,可以看到两个向量都有是1,这意味着两个句子都有一次出现John,并在词汇表中排在第一位。
以上被认为是一个词袋(BOW))模型,这是NLP中用于创建文本向量的常用方法,每个文档都表示为一个向量。现在就可以将这些向量用作机器学习模型的特征向量。下面进入下一部分内容。
使用机器学习方法时,一个重要的步骤就是定义基线模型。基线模型一般是一个简单的模型,然后进一步开发更高级模型。在这种情况下,将使用基线模型与更高级模型的性能进行比较,这也是本教程的主要内容。
首先,要将数据拆分为训练集和测试集,这样就可以评估训练好模型的准确性、泛化能力和过拟合情况。过拟合是指模型在训练数据上训练得太好,而在测试集上表现很差。有关过拟合(overfitting)处理的方法可以看这篇文章。
首先从数据集中提取Yelp数据集。之后得到句子和标签。.values
返还NumPy array类型,而不是pandas类型对象,这是由于在这种情况下,array类型的数据更易于使用:
from sklearn.model_selection import train_test_split
df_yelp = df[df['source'] == 'yelp']
sentences = df_yelp['sentence'].values
y = df_yelp['label'].values
sentences_train, sentences_test, y_train, y_test = train_test_split(
sentences, y, test_size=0.25, random_state=1000)
之后,将再次使用BOW模型来对句子进行向量化。由于在训练期间没有可用的测试数据,因此仅使用训练数据创建词汇表。使用此词汇表为训练和测试集的每个句子创建特征向量:
from sklearn.feature_extraction.text import CountVectorizer
vectorizer = 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 ''
with 7368 stored elements in Compressed Sparse Row format>
可以看到生成的特征向量有750个样本,这些样本对数据分割后获得的训练样本数。每个样本有1714个维度,这也是词汇量的大小。此外,可以看到得到的是一个稀疏矩阵。
CountVectorizer
执行词语切分,将句子分成一组单词列表,正如之前在词汇表中看到的那样。此外,它还可以删除标点符号和特殊字符,并可以对每个单词应用其他预处理。
注意:
CountVectorizer()
使用了很多额外的参数,例如添加ngrams,这是因为目标是建立一个简单的基线模型。词语切分本身默认为token_pattern=’(?u)\b\w\w+\b
,这是一个正则表达式模式,表示“一个单词是由单词边界包围的2个或更多Unicode字符组成”。
下面将使用[逻辑回归]()分类模型,这是一种常用的分类模型。从数学上讲,实际上是基于输入特征向量0到1之间的回归。通过指定阈值(默认为0.5),将回归模型用于分类。可以使用scikit-learn库中提供的LogisticRegression分类器完成该操作:
from sklearn.linear_model import LogisticRegression
classifier = LogisticRegression()
classifier.fit(X_train, y_train)
score = classifier.score(X_test, y_test)
print("Accuracy:", score)
输出:
Accuracy: 0.796
可以看到,yelp数据的准确度:0.7960,效果不错。将此方法应用于其它数据集上:
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))
输出结果
Accuracy for yelp data: 0.7960
Accuracy for amazon data: 0.7960
Accuracy for imdb data: 0.7487
可以看到,这个相当简单的模型表现不错。之后看看我们是否能够超越这个基线模型。接下来,我们将了解神经网络相关内容以及如何将它们应用于文本分类。
人工智能和深度学习近年来非常火热,这里假设你已经熟悉神经网络相关的基本知识,如果你不了解的话,可以查看博主的这篇文章。此外,随着深度学习方法的兴起,相应的开源工具箱也有很多,比如Tensorflow、Keras、Theano或Caffe等,本文使用keras构建相应的神经网络模型。有关keras的安装和配置可以查阅相关的教程安装,这里不做过多的介绍。下面构建你的第一个Keras模型。
在构建模型之前,需要知道特征向量的输入维度,这仅在输入层需要设定,之后按顺序逐个添加图层,如下所示:
>>> 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,161
Trainable params: 17,161
Non-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.0000
Testing Accuracy: 0.7960
从结果中可以看到,模型已经过拟合了,因为在训练集上达到100%准确度,而测试集上只有79.6%。但该测试集的准确性已经超过了之前使用的基线模型——逻辑回归,这也算是一种进步。
为了实验更加方便,可以使用小的辅助函数,根据历史回调可视化训练和测试数据的损失和准确性。在这种情况下,辅助函数使用matplotlib绘图库绘制模型的准确性:
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)
注意:在训练神经网络时,应该使用单独的测试和验证集。通常会采用在验证集上具有最高精度的模型,然后使用测试集测试该模型,这样可以确保不会过度使用模型。使用验证集来选择最佳模型是数据泄漏的一种形式,以便从数百次训练中选择产生最佳测试分数时的模型。当在该模型中使用训练数据集之外的信息时,会发生数据泄漏。
在这种情况下,测试和验证集是相同的,因为本文采用的样本量较小。正如之前所述,神经网络一般在大量样本数据集上表现最佳。在下一部分中,可以看到将单词表示为向量的不同方式。
Nikolai Janakiev ,机器学习和数据科学
本文由阿里云云栖社区组织翻译。
文章原标题《Practical Text Classification With Python and Keras》,译者:海棠,审校:Uncle_LLD。
文章为简译,更为详细的内容,请查看原文。