官方教程翻译
This commit is contained in:
parent
3135c14103
commit
86a6cf72e6
|
@ -0,0 +1,107 @@
|
|||
# 描述
|
||||
|
||||
在本教程竞赛中,我们对情感分析进行了一些“深入”研究。谷歌的 Word2Vec 是一种受深度学习启发的方法,专注于单词的含义。 Word2Vec 试图理解单词之间的意义和语义关系。它的工作方式类似于深度方法,例如循环神经网络或深度神经网络,但计算效率更高。本教程重点介绍用于情感分析的 Word2Vec。
|
||||
|
||||
情感分析是机器学习中的一个挑战性课题。人们用语言来表达自己的情感,这种语言经常被讽刺,二义性和文字游戏所掩盖,所有这些都会对人类和计算机产生误导。还有另一个 Kaggle 电影评论情绪分析竞赛。在本教程中,我们将探讨如何将 Word2Vec 应用于类似的问题。
|
||||
|
||||
在过去的几年里,深度学习在新闻中大量出现,甚至进入纽约时报的头版。这些机器学习技术受到人类大脑架构的启发,并且由于计算能力的最新进展而实现,由于图像识别,语音处理和自然语言任务的突破性结果,已经成为浪潮。最近,深度学习方法赢得了几项 Kaggle 比赛,包括药物发现任务和猫狗图像识别。
|
||||
|
||||
### 教程概览
|
||||
|
||||
本教程将帮助你开始使用 Word2Vec 进行自然语言处理。 它有两个目标:
|
||||
|
||||
基本自然语言处理:本教程的第 1 部分适用于初学者,涵盖了本教程后续部分所需的基本自然语言处理技术。
|
||||
|
||||
文本理解的深度学习:在第 2 部分和第 3 部分中,我们深入研究如何使用 Word2Vec 训练模型以及如何使用生成的单词向量进行情感分析。
|
||||
|
||||
由于深度学习是一个快速发展的领域,大量的工作尚未发表,或仅作为学术论文存在。 本教程的第 3 部分比说明性更具探索性 - 我们尝试了几种使用 Word2Vec 的方法,而不是为你提供使用输出的方法。
|
||||
|
||||
为了实现这些目标,我们依靠 IMDB 情绪分析数据集,其中包含 100,000 个多段电影评论,包括正面和负面。
|
||||
|
||||
### 致谢
|
||||
|
||||
此数据集是与以下出版物一起收集的:
|
||||
|
||||
> [Andrew L. Maas, Raymond E. Daly, Peter T. Pham, Dan Huang, Andrew Y. Ng, and Christopher Potts. (2011). "Learning Word Vectors for Sentiment Analysis." The 49th Annual Meeting of the Association for Computational Linguistics (ACL 2011).](http://ai.stanford.edu/~ang/papers/acl11-WordVectorsSentimentAnalysis.pdf)
|
||||
|
||||
如果你将数据用于任何研究应用,请发送电子邮件给该论文的作者。 该教程由 [Angela Chapman](http://www.linkedin.com/pub/angela-chapman/5/330/b97) 在 2014 年夏天在 Kaggle 实习期间开发。
|
||||
|
||||
### 什么是深度学习
|
||||
|
||||
术语“深度学习”是在2006年创造的,指的是具有多个非线性层并且可以学习特征层次结构的机器学习算法[1]。
|
||||
|
||||
大多数现代机器学习依赖于特征工程或某种级别的领域知识来获得良好的结果。 在深度学习系统中,情况并非如此 - 相反,算法可以自动学习特征层次结构,这些层次结构表示抽象级别增加的对象。 虽然许多深度学习算法的基本要素已存在多年,但由于计算能力的提高,计算硬件成本的下降以及机器学习研究的进步,它们目前正日益受到欢迎。
|
||||
|
||||
深度学习算法可以按其架构(前馈,反馈或双向)和训练协议(监督,混合或无监督)进行分类[2]。
|
||||
|
||||
一些好的背景材料包括:
|
||||
|
||||
[1] ["Deep Learning for Signal and Information Processing", by Li Deng and Dong Yu (out of Microsoft)](http://cs.tju.edu.cn/web/docs/2013-Deep%20Learning%20for%20Signal%20and%20Information%20Processing.pdf)
|
||||
|
||||
[2] ["Deep Learning Tutorial" (2013 Presentation by Yann LeCun and Marc'Aurelio Ranzato)](http://www.cs.nyu.edu/~yann/talks/lecun-ranzato-icml2013.pdf)
|
||||
|
||||
### Word2Vec 适合哪里?
|
||||
|
||||
Word2Vec的工作方式类似于深度方法,如循环神经网络或深度神经网络,但它实现了某些算法,例如分层 softmax,使计算效率更高。
|
||||
|
||||
对于 Word2Vec 以及本文的更多信息,请参阅本教程的第 2 部分,以及这篇论文:[Efficient Estimation of Word Representations in Vector Space](http://arxiv.org/pdf/1301.3781v3.pdf)
|
||||
|
||||
在本教程中,我们使用混合方法进行训练 - 由无监督的片段(Word2Vec)和监督学习(随机森林)组成。
|
||||
|
||||
### 库和包
|
||||
|
||||
以下列表并不是详尽无遗的。
|
||||
|
||||
Python 中:
|
||||
|
||||
Theano 提供非常底层的基本功能,用于构建深度学习系统。 你还可以在他们的网站上找到一些很好的教程。
|
||||
Caffe 是 Berkeley 视觉和学习中心的深度学习框架。
|
||||
Pylearn2 包装了 Theano,似乎更加用户友好。
|
||||
OverFeat 用于赢得 Kaggle 猫和狗的比赛。
|
||||
|
||||
Lua 中:
|
||||
|
||||
Torch 是一个受欢迎的包,并附带一个教程。
|
||||
|
||||
R 中:
|
||||
|
||||
截至 2014 年 8 月,有一些软件包刚刚开始开发,但没有可以在教程中使用的,非常成熟的包。
|
||||
|
||||
其他语言也可能有很好的包,但我们还没有对它们进行过研究。
|
||||
|
||||
### 更多教程
|
||||
|
||||
O'Reilly 博客有一系列深度学习文章和教程:
|
||||
|
||||
[什么是深度学习,为什么要关心?](http://radar.oreilly.com/2014/07/what-is-deep-learning-and-why-should-you-care.html)
|
||||
[如何构建和运行你的第一个深度学习网络](http://radar.oreilly.com/2014/07/how-to-build-and-run-your-first-deep-learning-network.html)
|
||||
[网络广播:如何起步深入学习计算机视觉](http://www.oreilly.com/pub/e/3121)
|
||||
|
||||
还有[几个使用 Theano 的教程](http://deeplearning.net/tutorial/)。
|
||||
|
||||
如果你想从零开始创建神经网络,请查看 Geoffrey Hinton 的 [Coursera 课程](https://www.coursera.org/course/neuralnets)。
|
||||
|
||||
对于 NLP,请查看斯坦福大学最近的这个讲座:[没有魔法的 NLP 的深度学习](http://techtalks.tv/talks/deep-learning-for-nlp-without-magic-part-1/58414/)。
|
||||
|
||||
这本免费的在线书籍还介绍了用于深度学习的神经网络:[神经网络和深度学习](http://neuralnetworksanddeeplearning.com/)。
|
||||
|
||||
### 配置你的系统
|
||||
|
||||
如果你之前没有安装过 Python 模块,请查看[此教程](http://programminghistorian.org/lessons/installing-python-modules-pip),提供了从终端(在 Mac / Linux 中)或命令提示符(在 Windows 中)安装模块的指南。
|
||||
|
||||
运行本教程需要安装以下软件包。 在大多数(或所有)情况下,我们建议你使用[`pip`](https://pypi.python.org/pypi/pip)来安装软件包。
|
||||
|
||||
* [pandas](http://pandas.pydata.org/pandas-docs/stable/install.html)
|
||||
* [numpy](http://docs.scipy.org/doc/numpy/user/install.html)
|
||||
* scipy
|
||||
* [scikit-learn ](http://scikit-learn.org/stable/install.html)
|
||||
* [Beautiful Soup](http://www.crummy.com/software/BeautifulSoup/bs4/doc/)
|
||||
* [NLTK](http://www.nltk.org/install.html)
|
||||
* [Cython](http://docs.cython.org/src/quickstart/install.html)
|
||||
* [gensim](http://radimrehurek.com/gensim/install.html)
|
||||
|
||||
Word2Vec 可以在`gensim`包中找到。 请注意,到目前为止,我们只在 Mac OS X 上成功运行了本教程,而不是 Windows。
|
||||
|
||||
如果你在 Mac Mavericks(10.9)上安装软件包时遇到问题,[本教程](http://hackercodex.com/guide/python-development-environment-on-mac-osx/)包含正确配置系统的说明。
|
||||
|
||||
本教程中的代码是为 Python 2.7 开发的。
|
|
@ -0,0 +1,346 @@
|
|||
## 第一部分:写给入门者的词袋
|
||||
|
||||
### 什么是 NLP
|
||||
|
||||
NLP(自然语言处理)是一组用于处理文本问题的技术。这个页面将帮助你从加载和清理IMDB电影评论来起步,然后应用一个简单的[词袋](http://en.wikipedia.org/wiki/Bag-of-words_model)模型,来获得令人惊讶的准确预测,评论是点赞还是点踩。
|
||||
|
||||
### 在你开始之前
|
||||
|
||||
本教程使用 Python。如果你之前没有使用过 Python,我们建议你前往[泰坦尼克号竞赛 Python 教程](http://www.kaggle.com/c/titanic-gettingStarted),熟悉一下(查看随机森林介绍)。
|
||||
|
||||
如果你已熟悉 Python 并使用基本的 NLP 技术,则可能需要跳到第 2 部分。
|
||||
|
||||
本教程的这一部分不依赖于平台。在本教程中,我们将使用各种 Python 模块进行文本处理,深度学习,随机森林和其他应用。详细信息请参阅“配置你的系统”页面。
|
||||
|
||||
有很多很好的教程,以及实际上用 Python 写的关于 NLP 和文本处理的[整本书](http://www.nltk.org/book/)。本教程绝不是详尽无遗的 - 只是为了帮助你以电影评论起步。
|
||||
|
||||
### 代码
|
||||
|
||||
第 1 部分的教程代码就在[这里](https://github.com/wendykan/DeepLearningMovies/blob/master/BagOfWords.py)。
|
||||
|
||||
### 读取数据
|
||||
|
||||
可以从“数据”页面下载必要的文件。你需要的第一个文件是`unlabeledTrainData`,其中包含 25,000 个 IMDB 电影评论,每个评论都带有正面或负面情感标签。
|
||||
|
||||
接下来,将制表符分隔文件读入 Python。为此,我们可以使用泰坦尼克号教程中介绍的`pandas`包,它提供了`read_csv`函数,用于轻松读取和写入数据文件。如果你之前没有使用过`pandas`,则可能需要安装它。
|
||||
|
||||
```py
|
||||
# 导入 pandas 包,然后使用 "read_csv" 函数读取标记的训练数据
|
||||
import pandas as pd
|
||||
train = pd.read_csv("labeledTrainData.tsv", header=0, \
|
||||
delimiter="\t", quoting=3)
|
||||
```
|
||||
|
||||
这里,`header=0`表示文件的第一行包含列名,`delimiter=\t`表示字段由制表符分隔,`quoting=3`让 Python 忽略双引号,否则试图读取文件时,可能会遇到错误。
|
||||
|
||||
我们可以确保读取 25,000 行和 3 列,如下所示:
|
||||
|
||||
```py
|
||||
>>> train.shape
|
||||
(25000, 3)
|
||||
|
||||
>>> train.columns.values
|
||||
array([id, sentiment, review], dtype=object)
|
||||
```
|
||||
|
||||
这三列被称为`"id"`,`"sentiment"`和`"array"`。 现在你已经读取了培训集,请查看几条评论:
|
||||
|
||||
```py
|
||||
print train["review"][0]
|
||||
```
|
||||
|
||||
提醒一下,这将显示名为`"review"`的列中的第一个电影评论。 你应该看到一个像这样开头的评论:
|
||||
|
||||
```py
|
||||
"With all this stuff going down at the moment with MJ i've started listening to his music, watching the odd documentary here and there, watched The Wiz and watched Moonwalker again. Maybe i just want to get a certain insight into this guy who i thought was really cool in the eighties just to maybe make up my mind whether he is guilty or innocent. Moonwalker is part biography, part feature film which i remember going to see at the cinema when it was originally released. Some of it has subtle messages about MJ's feeling towards the press and also the obvious message of drugs are bad m'kay. <br/><br/>..."
|
||||
```
|
||||
|
||||
有 HTML 标签,如`"<br/>"`,缩写,标点符号 - 处理在线文本时的所有常见问题。 花一些时间来查看训练集中的其他评论 - 下一节将讨论如何为机器学习整理文本。
|
||||
|
||||
### 数据清理和文本预处理
|
||||
|
||||
删除 HTML 标记:`BeautifulSoup`包
|
||||
|
||||
首先,我们将删除 HTML 标记。 为此,我们将使用[`BeautifulSoup`库](http://www.crummy.com/software/BeautifulSoup/bs4/doc/)。 如果你没有安装,请从命令行(不是从 Python 内部)执行以下操作:
|
||||
|
||||
```
|
||||
$ sudo pip install BeautifulSoup4
|
||||
```
|
||||
|
||||
然后,从 Python 中加载包并使用它从评论中提取文本:
|
||||
|
||||
```py
|
||||
# Import BeautifulSoup into your workspace
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
# Initialize the BeautifulSoup object on a single movie review
|
||||
example1 = BeautifulSoup(train["review"][0])
|
||||
|
||||
# Print the raw review and then the output of get_text(), for
|
||||
# comparison
|
||||
print train["review"][0]
|
||||
print example1.get_text()
|
||||
```
|
||||
|
||||
调用`get_text()`会为你提供不带标签的评论文本。如果你浏览`BeautifulSoup`文档,你会发现它是一个非常强大的库 - 比我们对此数据集所需的功能更强大。但是,使用正则表达式删除标记并不是一种可靠的做法,因此即使对于像这样简单的应用程序,通常最好使用像`BeautifulSoup`这样的包。
|
||||
|
||||
处理标点符号,数字和停止词:NLTK 和正则表达式
|
||||
|
||||
在考虑如何清理文本时,我们应该考虑我们试图解决的数据问题。对于许多问题,删除标点符号是有意义的。另一方面,在这种情况下,我们正在解决情感分析问题,并且有可能`"!!!"`或者`":-("`可以带有情感,应该被视为单词。在本教程中,为简单起见,我们完全删除了标点符号,但这是你可以自己玩的东西。
|
||||
|
||||
与之相似,在本教程中我们将删除数字,但还有其他方法可以处理它们,这些方法同样有意义。例如,我们可以将它们视为单词,或者使用占位符字符串(例如`"NUM"`)替换它们。
|
||||
|
||||
要删除标点符号和数字,我们将使用一个包来处理正则表达式,称为`re`。Python 内置了该软件包;无需安装任何东西。对于正则表达式如何工作的详细说明,请参阅[包文档](https://docs.python.org/2/library/re.html#)。现在,尝试以下方法:
|
||||
|
||||
```py
|
||||
import re
|
||||
# 使用正则表达式执行查找和替换
|
||||
letters_only = re.sub("[^a-zA-Z]", # 要查找的模式串
|
||||
" ", # 要替换成的模式串
|
||||
example1.get_text() ) # 要从中查找的字符串
|
||||
print letters_only
|
||||
```
|
||||
|
||||
正则表达式的完整概述超出了本教程的范围,但是现在知道`[]`表示分组成员而`^`表示“不”就足够了。 换句话说,上面的`re.sub()`语句说:“查找任何不是小写字母(`a-z`)或大写字母(`A-Z`)的内容,并用空格替换它。”
|
||||
|
||||
我们还将我们的评论转换为小写并将它们分成单个单词(在 NLP 术语中称为“[分词](http://en.wikipedia.org/wiki/Tokenization)”):
|
||||
|
||||
```py
|
||||
lower_case = letters_only.lower() # 转换为小写
|
||||
words = lower_case.split() # 分割为单词
|
||||
```
|
||||
|
||||
最后,我们需要决定如何处理那些没有多大意义的经常出现的单词。 这样的词被称为“停止词”;在英语中,它们包括诸如“a”,“and”,“is”和“the”之类的单词。方便的是,Python 包中内置了停止词列表。让我们从 Python 自然语言工具包(NLTK)导入停止词列表。 如果你的计算机上还没有该库,则需要安装该库;你还需要安装附带的数据包,如下所示:
|
||||
|
||||
```py
|
||||
import nltk
|
||||
nltk.download() # 下载文本数据集,包含停止词
|
||||
```
|
||||
|
||||
现在我们可以使用`nltk`来获取停止词列表:
|
||||
|
||||
```py
|
||||
from nltk.corpus import stopwords # 导入停止词列表
|
||||
print stopwords.words("english")
|
||||
```
|
||||
|
||||
这将允许你查看英语停止词列表。 要从我们的电影评论中删除停止词,请执行:
|
||||
|
||||
```py
|
||||
# 从 "words" 中移除停止词
|
||||
words = [w for w in words if not w in stopwords.words("english")]
|
||||
print words
|
||||
```
|
||||
|
||||
这会查看`words`列表中的每个单词,并丢弃在停止词列表中找到的任何内容。 完成所有这些步骤后,你的评论现在应该是这样的:
|
||||
|
||||
```py
|
||||
[u'stuff', u'going', u'moment', u'mj', u've', u'started', u'listening', u'music', u'watching', u'odd', u'documentary', u'watched', u'wiz', u'watched', u'moonwalker', u'maybe', u'want', u'get', u'certain', u'insight', u'guy', u'thought', u'really', u'cool', u'eighties', u'maybe', u'make', u'mind', u'whether', u'guilty', u'innocent', u'moonwalker', u'part', u'biography', u'part', u'feature', u'film', u'remember', u'going', u'see', u'cinema', u'originally', u'released', u'subtle', u'messages', u'mj', u'feeling', u'towards', u'press', u'also', u'obvious', u'message', u'drugs', u'bad', u'm', u'kay',.....]
|
||||
```
|
||||
|
||||
不要担心在每个单词之前的`u`;它只是表明 Python 在内部将每个单词表示为 [unicode 字符串](https://docs.python.org/2/howto/unicode.html#python-2-x-s-unicode-support)。
|
||||
|
||||
我们可以对数据做很多其他的事情 - 例如,Porter Stemming(词干提取)和 Lemmatizing(词形还原)(都在 NLTK 中提供)将允许我们将`"messages"`,`"message"`和`"messaging"`视为同一个词,这当然可能很有用。 但是,为简单起见,本教程将就此打住。
|
||||
|
||||
### 把它们放在一起
|
||||
|
||||
现在我们有了清理评论的代码 - 但我们需要清理 25,000 个训练评论! 为了使我们的代码可重用,让我们创建一个可以多次调用的函数:
|
||||
|
||||
```py
|
||||
def review_to_words( raw_review ):
|
||||
# 将原始评论转换为单词字符串的函数
|
||||
# 输入是单个字符串(原始电影评论),
|
||||
# 输出是单个字符串(预处理过的电影评论)
|
||||
# 1. 移除 HTML
|
||||
review_text = BeautifulSoup(raw_review).get_text()
|
||||
#
|
||||
# 2. 移除非字母
|
||||
letters_only = re.sub("[^a-zA-Z]", " ", review_text)
|
||||
#
|
||||
# 3. 转换为小写,分成单个单词
|
||||
words = letters_only.lower().split()
|
||||
#
|
||||
# 4. 在Python中,搜索集合比搜索列表快得多,
|
||||
# 所以将停止词转换为一个集合
|
||||
stops = set(stopwords.words("english"))
|
||||
#
|
||||
# 5. 删除停止词
|
||||
meaningful_words = [w for w in words if not w in stops]
|
||||
#
|
||||
# 6. 将单词连接成由空格分隔的字符串,
|
||||
# 并返回结果。
|
||||
return( " ".join( meaningful_words ))
|
||||
```
|
||||
|
||||
这里有两个新元素:首先,我们将停止词列表转换为不同的数据类型,即集合。 这是为了速度;因为我们将调用这个函数数万次,所以它需要很快,而 Python 中的搜索集合比搜索列表要快得多。
|
||||
|
||||
其次,我们将这些单词合并为一段。 这是为了使输出更容易在我们的词袋中使用,在下面。 定义上述函数后,如果你为单个评论调用该函数:
|
||||
|
||||
```py
|
||||
clean_review = review_to_words( train["review"][0] )
|
||||
print clean_review
|
||||
```
|
||||
|
||||
它应该为你提供与前面教程部分中所做的所有单独步骤完全相同的输出。 现在让我们遍历并立即清理所有训练集(这可能需要几分钟,具体取决于你的计算机):
|
||||
|
||||
```py
|
||||
# 根据 dataframe 列大小获取评论数
|
||||
num_reviews = train["review"].size
|
||||
|
||||
# 初始化空列表来保存清理后的评论
|
||||
clean_train_reviews = []
|
||||
|
||||
# 遍历每个评论;创建索引 i
|
||||
# 范围是 0 到电影评论列表长度
|
||||
for i in xrange( 0, num_reviews ):
|
||||
# 为每个评论调用我们的函数,
|
||||
# 并将结果添加到清理后评论列表中
|
||||
clean_train_reviews.append( review_to_words( train["review"][i] ) )
|
||||
```
|
||||
|
||||
有时等待冗长的代码的运行会很烦人。 编写提供状态更新的代码会很有帮助。 要让 Python 在其处理每 1000 个评论后打印状态更新,请尝试在上面的代码中添加一两行:
|
||||
|
||||
```py
|
||||
print "Cleaning and parsing the training set movie reviews...\n"
|
||||
clean_train_reviews = []
|
||||
for i in xrange( 0, num_reviews ):
|
||||
# 如果索引被 1000 整除,打印消息
|
||||
if( (i+1)%1000 == 0 ):
|
||||
print "Review %d of %d\n" % ( i+1, num_reviews )
|
||||
clean_train_reviews.append( review_to_words( train["review"][i] ))
|
||||
```
|
||||
|
||||
### 从词袋创建特征(使用`sklearn`)
|
||||
|
||||
现在我们已经整理了我们的训练评论,我们如何将它们转换为机器学习的某种数字表示?一种常见的方法叫做词袋。词袋模型从所有文档中学习词汇表,然后通过计算每个单词出现的次数对每个文档进行建模。例如,考虑以下两句话:
|
||||
|
||||
句子1:`"The cat sat on the hat"`
|
||||
|
||||
句子2:`"The dog ate the cat and the hat"`
|
||||
|
||||
从这两个句子中,我们的词汇如下:
|
||||
|
||||
`{ the, cat, sat, on, hat, dog, ate, and }`
|
||||
|
||||
为了得到我们的词袋,我们计算每个单词出现在每个句子中的次数。在句子 1 中,“the”出现两次,“cat”,“sat”,“on”和“hat”每次出现一次,因此句子 1 的特征向量是:
|
||||
|
||||
`{ the, cat, sat, on, hat, dog, ate, and }`
|
||||
|
||||
句子 1:`{ 2, 1, 1, 1, 1, 0, 0, 0 }`
|
||||
|
||||
同样,句子 2 的特征是:`{ 3, 1, 0, 0, 1, 1, 1, 1}`
|
||||
|
||||
在 IMDB 数据中,我们有大量的评论,这将为我们提供大量的词汇。要限制特征向量的大小,我们应该选择最大词汇量。下面,我们使用 5000 个最常用的单词(记住已经删除了停止词)。
|
||||
|
||||
我们将使用 scikit-learn 中的`feature_extraction`模块来创建词袋特征。如果你学习了泰坦尼克号竞赛中的随机森林教程,那么你应该已经安装了 scikit-learn;否则你需要[安装它](http://scikit-learn.org/stable/install.html)。
|
||||
|
||||
```py
|
||||
print "Creating the bag of words...\n"
|
||||
from sklearn.feature_extraction.text import CountVectorizer
|
||||
|
||||
# 初始化 "CountVectorizer" 对象,
|
||||
# 这是 scikit-learn 的一个词袋工具。
|
||||
vectorizer = CountVectorizer(analyzer = "word", \
|
||||
tokenizer = None, \
|
||||
preprocessor = None, \
|
||||
stop_words = None, \
|
||||
max_features = 5000)
|
||||
|
||||
# fit_transform() 有两个功能:
|
||||
# 首先,它拟合模型并学习词汇;
|
||||
# 第二,它将我们的训练数据转换为特征向量。
|
||||
# fit_transform 的输入应该是字符串列表。
|
||||
train_data_features = vectorizer.fit_transform(clean_train_reviews)
|
||||
|
||||
# Numpy 数组很容易使用,因此将结果转换为数组
|
||||
train_data_features = train_data_features.toarray()
|
||||
```
|
||||
|
||||
要查看训练数据数组现在的样子,请执行以下操作:
|
||||
|
||||
```py
|
||||
>>> print train_data_features.shape
|
||||
(25000, 5000)
|
||||
```
|
||||
|
||||
它有 25,000 行和 5,000 个特征(每个词汇一个)。
|
||||
|
||||
请注意,`CountVectorizer`有自己的选项来自动执行预处理,标记化和停止词删除 - 对于其中的每一个,我们不指定`None`,可以使用内置方法或指定我们自己的函数来使用。 详细信息请参阅[函数文档](http://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.CountVectorizer.html)。 但是,我们想在本教程中编写我们自己的数据清理函数,来向你展示如何逐步完成它。
|
||||
|
||||
现在词袋模型已经训练好了,让我们来看看词汇表:
|
||||
|
||||
```py
|
||||
# 看看词汇表中的单词
|
||||
vocab = vectorizer.get_feature_names()
|
||||
print vocab
|
||||
```
|
||||
|
||||
如果你有兴趣,还可以打印词汇表中每个单词的计数:
|
||||
|
||||
```py
|
||||
import numpy as np
|
||||
|
||||
# 求和词汇表中每个单词的计数
|
||||
dist = np.sum(train_data_features, axis=0)
|
||||
|
||||
# 对于每个词,打印它和它在训练集中的出现次数
|
||||
for tag, count in zip(vocab, dist):
|
||||
print count, tag
|
||||
```
|
||||
|
||||
### 随机森林
|
||||
|
||||
到了这里,我们有词袋的数字训练特征和每个特征向量的原始情感标签,所以让我们做一些监督学习! 在这里,我们将使用我们在泰坦尼克号教程中介绍的随机森林分类器。 随机森林算法包含在 scikit-learn 中([随机森林](http://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestClassifier.html)使用许多基于树的分类器来进行预测,因此是“森林”)。 下面,我们将树的数量设置为 100 作为合理的默认值。 更多树可能(或可能不)表现更好,但肯定需要更长时间来运行。 同样,每个评论所包含的特征越多,所需的时间就越长。
|
||||
|
||||
```py
|
||||
print "Training the random forest..."
|
||||
from sklearn.ensemble import RandomForestClassifier
|
||||
|
||||
# 使用 100 棵树初始化随机森林分类器
|
||||
forest = RandomForestClassifier(n_estimators = 100)
|
||||
|
||||
# 使用词袋作为特征并将情感标签作为响应变量,使森林拟合训练集
|
||||
# 这可能需要几分钟来运行
|
||||
forest = forest.fit( train_data_features, train["sentiment"] )
|
||||
```
|
||||
|
||||
### 创建提交
|
||||
|
||||
剩下的就是在我们的测试集上运行训练好的随机森林并创建一个提交文件。 如果你还没有这样做,请从“数据”页面下载`testData.tsv`。 此文件包含另外 25,000 条评论和标签;我们的任务是预测情感标签。
|
||||
|
||||
请注意,当我们使用词袋作为测试集时,我们只调用`transform`,而不是像训练集那样调用`fit_transform`。 在机器学习中,你不应该使用测试集来拟合你的模型,否则你将面临[过拟合](http://blog.kaggle.com/2012/07/06/the-dangers-of-overfitting-psychopathy-post-mortem/)的风险。 出于这个原因,我们将测试集保持在禁止状态,直到我们准备好进行预测。
|
||||
|
||||
```py
|
||||
# 读取测试数据
|
||||
test = pd.read_csv("testData.tsv", header=0, delimiter="\t", \
|
||||
quoting=3 )
|
||||
|
||||
# 验证有 25,000 行和 2 列
|
||||
print test.shape
|
||||
|
||||
# 创建一个空列表并逐个附加干净的评论
|
||||
num_reviews = len(test["review"])
|
||||
clean_test_reviews = []
|
||||
|
||||
print "Cleaning and parsing the test set movie reviews...\n"
|
||||
for i in xrange(0,num_reviews):
|
||||
if( (i+1) % 1000 == 0 ):
|
||||
print "Review %d of %d\n" % (i+1, num_reviews)
|
||||
clean_review = review_to_words( test["review"][i] )
|
||||
clean_test_reviews.append( clean_review )
|
||||
|
||||
# 获取测试集的词袋,并转换为 numpy 数组
|
||||
test_data_features = vectorizer.transform(clean_test_reviews)
|
||||
test_data_features = test_data_features.toarray()
|
||||
|
||||
# 使用随机森林进行情感标签预测
|
||||
result = forest.predict(test_data_features)
|
||||
|
||||
# 将结果复制到带有 "id" 列和 "sentiment" 列的 pandas dataframe
|
||||
output = pd.DataFrame( data={"id":test["id"], "sentiment":result} )
|
||||
|
||||
# 使用 pandas 编写逗号分隔的输出文件
|
||||
output.to_csv( "Bag_of_Words_model.csv", index=False, quoting=3 )
|
||||
```
|
||||
|
||||
恭喜,你已准备好第一次提交! 尝试不同的事情,看看你的结果如何变化。 你可以以不同方式清理评论,为词袋表示选择不同数量的词汇表单词,尝试 Porter Stemming,不同的分类器或任何其他的东西。 要在不同的数据集上试用你的 NLP 招式,你还可以参加我们的[烂番茄比赛](https://www.kaggle.com/c/sentiment-analysis-on-movie-reviews)。 或者,如果你为完全不同的东西做好了准备,请访问深度学习和词向量页面。
|
|
@ -0,0 +1,246 @@
|
|||
## 第二部分:词向量
|
||||
|
||||
### 代码
|
||||
|
||||
第二部分的教程代码[在这里](https://github.com/wendykan/DeepLearningMovies/blob/master/Word2Vec_AverageVectors.py)。
|
||||
|
||||
### 分布式词向量简介
|
||||
|
||||
本教程的这一部分将重点介绍使用 Word2Vec 算法创建分布式单词向量。 (深度学习的概述,以及其他一些教程的链接,请参阅“什么是深度学习?”页面)。
|
||||
|
||||
第 2 部分和第 3 部分比第 1 部分假设你更熟悉Python。我们在双核 Macbook Pro 上开发了以下代码,但是,我们还没有在 Windows 上成功运行代码。如果你是 Windows 用户并且使其正常运行,请在论坛中留言如何进行操作!更多详细信息,请参阅“配置系统”页面。
|
||||
|
||||
[Word2vec](https://code.google.com/p/word2vec/),由 Google 于 2013 年发表,是一种神经网络实现,可以学习单词的[分布式表示](http://www.cs.toronto.edu/~bonner/courses/2014s/csc321/lectures/lec5.pdf)。在此之前已经提出了用于学习单词表示的其他深度或循环神经网络架构,但是这些的主要问题是训练模型所需时长间。 Word2vec 相对于其他模型学习得快。
|
||||
|
||||
Word2Vec 不需要标签来创建有意义的表示。这很有用,因为现实世界中的大多数数据都是未标记的。如果给网络足够的训练数据(数百亿个单词),它会产生特征极好的单词向量。具有相似含义的词出现在簇中,并且簇具有间隔,使得可以使用向量数学来再现诸如类比的一些词关系。着名的例子是,通过训练好的单词向量,“国王 - 男人 + 女人 = 女王”。
|
||||
|
||||
查看 [Google 的代码,文章和附带的论文](https://code.google.com/p/word2vec/)。 [此演示](https://docs.google.com/file/d/0B7XkCwpI5KDYRWRnd1RzWXQ2TWc/edit)也很有帮助。 原始代码是 C 写的,但它已被移植到其他语言,包括 Python。 我们鼓励你使用原始 C 工具,但如果你是初学程序员(我们必须手动编辑头文件来编译),请注意它不是用户友好的。
|
||||
|
||||
最近斯坦福大学的工作也将[深度学习应用于情感分析](http://nlp.stanford.edu/sentiment/);他们的代码以 Java 提供。 但是,他们的方法依赖于句子解析,不能直接应用于任意长度的段落。
|
||||
|
||||
分布式词向量强大,可用于许多应用,尤其是单词预测和转换。 在这里,我们将尝试将它们应用于情感分析。
|
||||
|
||||
### 在 Python 中使用 word2vec
|
||||
|
||||
在 Python 中,我们将使用`gensim`包中的 word2vec 的优秀实现。 如果你还没有安装`gensim`,则需要[安装](http://radimrehurek.com/gensim/install.html)它。 [这里](http://radimrehurek.com/2014/02/word2vec-tutorial/)有一个包含 Python Word2Vec 实现的优秀教程。
|
||||
|
||||
虽然 Word2Vec 不像许多深度学习算法那样需要图形处理单元(GPU),但它是计算密集型的。 Google 的版本和 Python 版本都依赖于多线程(在你的计算机上并行运行多个进程以节省时间)。 为了在合理的时间内训练你的模型,你需要安装 cython([这里是指南](http://docs.cython.org/src/quickstart/install.html))。 Word2Vec 可在没有安装 cython 的情况下运行,但运行它需要几天而不是几分钟。
|
||||
|
||||
### 为训练模型做准备
|
||||
|
||||
现在到了细节! 首先,我们使用`pandas`读取数据,就像我们在第 1 部分中所做的那样。与第 1 部分不同,我们现在使用`unlabeledTrain.tsv`,其中包含 50,000 个额外的评论,没有标签。 当我们在第 1 部分中构建词袋模型时,额外的未标记的训练评论没有用。 但是,由于 Word2Vec 可以从未标记的数据中学习,现在可以使用这些额外的 50,000 条评论。
|
||||
|
||||
```py
|
||||
import pandas as pd
|
||||
|
||||
# 从文件读取数据
|
||||
train = pd.read_csv( "labeledTrainData.tsv", header=0,
|
||||
delimiter="\t", quoting=3 )
|
||||
test = pd.read_csv( "testData.tsv", header=0, delimiter="\t", quoting=3 )
|
||||
unlabeled_train = pd.read_csv( "unlabeledTrainData.tsv", header=0,
|
||||
delimiter="\t", quoting=3 )
|
||||
|
||||
# 验证已读取的评论数量(总共 100,000 个)
|
||||
print "Read %d labeled train reviews, %d labeled test reviews, " \
|
||||
"and %d unlabeled reviews\n" % (train["review"].size,
|
||||
test["review"].size, unlabeled_train["review"].size )
|
||||
```
|
||||
|
||||
我们为清理数据而编写的函数也与第 1 部分类似,尽管现在存在一些差异。 首先,为了训练 Word2Vec,最好不要删除停止词,因为算法依赖于句子的更广泛的上下文,以便产生高质量的词向量。 因此,我们将在下面的函数中,将停止词删除变成可选的。 最好不要删除数字,但我们将其留作读者的练习。
|
||||
|
||||
```py
|
||||
# Import various modules for string cleaning
|
||||
from bs4 import BeautifulSoup
|
||||
import re
|
||||
from nltk.corpus import stopwords
|
||||
|
||||
def review_to_wordlist( review, remove_stopwords=False ):
|
||||
# 将文档转换为单词序列的函数,可选地删除停止词。 返回单词列表。
|
||||
#
|
||||
# 1. 移除 HTML
|
||||
review_text = BeautifulSoup(review).get_text()
|
||||
#
|
||||
# 2. 移除非字母
|
||||
review_text = re.sub("[^a-zA-Z]"," ", review_text)
|
||||
#
|
||||
# 3. 将单词转换为小写并将其拆分
|
||||
words = review_text.lower().split()
|
||||
#
|
||||
# 4. 可选地删除停止词(默认为 false)
|
||||
if remove_stopwords:
|
||||
stops = set(stopwords.words("english"))
|
||||
words = [w for w in words if not w in stops]
|
||||
#
|
||||
# 5. 返回单词列表
|
||||
return(words)
|
||||
```
|
||||
|
||||
接下来,我们需要一种特定的输入格式。 Word2Vec 需要单个句子,每个句子都是一列单词。 换句话说,输入格式是列表的列表。
|
||||
|
||||
如何将一个段落分成句子并不简单。 自然语言中有各种各样的问题。 英语句子可能以“?”,“!”,“"”或“.”等结尾,并且间距和大写也不是可靠的标志。因此,我们将使用 NLTK 的`punkt`分词器进行句子分割。为了使用它,你需要安装 NLTK 并使用`nltk.download()`下载`punkt`的相关训练文件。
|
||||
|
||||
```py
|
||||
# 为句子拆分下载 punkt 分词器
|
||||
import nltk.data
|
||||
nltk.download()
|
||||
|
||||
# 加载 punkt 分词器
|
||||
tokenizer = nltk.data.load('tokenizers/punkt/english.pickle')
|
||||
|
||||
# 定义一个函数将评论拆分为已解析的句子
|
||||
def review_to_sentences( review, tokenizer, remove_stopwords=False ):
|
||||
# 将评论拆分为已解析句子的函数。
|
||||
# 返回句子列表,其中每个句子都是单词列表
|
||||
# 1. 使用 NLTK 分词器将段落拆分为句子
|
||||
raw_sentences = tokenizer.tokenize(review.strip())
|
||||
#
|
||||
# 2. 遍历每个句子
|
||||
sentences = []
|
||||
for raw_sentence in raw_sentences:
|
||||
# 如果句子为空,则跳过
|
||||
if len(raw_sentence) > 0:
|
||||
# 否则,调用 review_to_wordlist 来获取单词列表
|
||||
sentences.append( review_to_wordlist( raw_sentence, \
|
||||
remove_stopwords ))
|
||||
# 返回句子列表(每个句子都是单词列表,
|
||||
# 因此返回列表的列表)
|
||||
return sentences
|
||||
```
|
||||
|
||||
现在我们可以应用此函数,来准备 Word2Vec 的输入数据(这将需要几分钟):
|
||||
|
||||
```py
|
||||
sentences = [] # 初始化空的句子列表
|
||||
|
||||
print "Parsing sentences from training set"
|
||||
for review in train["review"]:
|
||||
sentences += review_to_sentences(review, tokenizer)
|
||||
|
||||
print "Parsing sentences from unlabeled set"
|
||||
for review in unlabeled_train["review"]:
|
||||
sentences += review_to_sentences(review, tokenizer)
|
||||
```
|
||||
|
||||
你可能会从`BeautifulSoup`那里得到一些关于句子中 URL 的警告。 这些都不用担心(尽管你可能需要考虑在清理文本时删除 URL)。
|
||||
|
||||
我们可以看一下输出,看看它与第 1 部分的不同之处:
|
||||
|
||||
```py
|
||||
>>> # 检查我们总共有多少句子 - 应该是 850,000+ 左右
|
||||
... print len(sentences)
|
||||
857234
|
||||
|
||||
>>> print sentences[0]
|
||||
[u'with', u'all', u'this', u'stuff', u'going', u'down', u'at', u'the', u'moment', u'with', u'mj', u'i', u've', u'started', u'listening', u'to', u'his', u'music', u'watching', u'the', u'odd', u'documentary', u'here', u'and', u'there', u'watched', u'the', u'wiz', u'and', u'watched', u'moonwalker', u'again']
|
||||
|
||||
>>> print sentences[1]
|
||||
[u'maybe', u'i', u'just', u'want', u'to', u'get', u'a', u'certain', u'insight', u'into', u'this', u'guy', u'who', u'i', u'thought', u'was', u'really', u'cool', u'in', u'the', u'eighties', u'just', u'to', u'maybe', u'make', u'up', u'my', u'mind', u'whether', u'he', u'is', u'guilty', u'or', u'innocent']
|
||||
```
|
||||
|
||||
需要注意的一个小细节是 Python 列表中`+=`和`append`之间的区别。 在许多应用中,这两者是可以互换的,但在这里它们不是。 如果要将列表列表附加到另一个列表列表,`append`仅仅附加外层列表; 你需要使用`+=`才能连接所有内层列表。
|
||||
|
||||
> 译者注:原文中这里的解释有误,已修改。
|
||||
|
||||
### 训练并保存你的模型
|
||||
|
||||
使用精心解析的句子列表,我们已准备好训练模型。 有许多参数选项会影响运行时间和生成的最终模型的质量。 以下算法的详细信息,请参阅 [word2vec API 文档](http://radimrehurek.com/gensim/models/word2vec.html)以及 [Google 文档](https://code.google.com/p/word2vec/)。
|
||||
|
||||
+ 架构:架构选项是 skip-gram(默认)或 CBOW。 我们发现 skip-gram 非常慢,但产生了更好的结果。
|
||||
+ 训练算法:分层 softmax(默认)或负采样。 对我们来说,默认效果很好。
|
||||
+ 对频繁词汇进行下采样:Google 文档建议值介于`.00001`和`.001`之间。 对我们来说,接近0.001的值似乎可以提高最终模型的准确性。
|
||||
+ 单词向量维度:更多特征会产生更长的运行时间,并且通常(但并非总是)会产生更好的模型。 合理的值可能介于几十到几百;我们用了 300。
|
||||
+ 上下文/窗口大小:训练算法应考虑多少个上下文单词? 10 似乎适用于分层 softmax(越多越好,达到一定程度)。
|
||||
+ 工作线程:要运行的并行进程数。 这是特定于计算机的,但 4 到 6 之间应该适用于大多数系统。
|
||||
+ 最小词数:这有助于将词汇量的大小限制为有意义的单词。 在所有文档中,至少没有出现这个次数的任何单词都将被忽略。 合理的值可以在 10 到 100 之间。在这种情况下,由于每个电影出现 30 次,我们将最小字数设置为 40,来避免过分重视单个电影标题。 这导致了整体词汇量大约为 15,000 个单词。 较高的值也有助于限制运行时间。
|
||||
|
||||
选择参数并不容易,但是一旦我们选择了参数,创建 Word2Vec 模型就很简单:
|
||||
|
||||
```py
|
||||
# 导入内置日志记录模块并配置它,以便 Word2Vec 创建良好的输出消息
|
||||
import logging
|
||||
logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s',\
|
||||
level=logging.INFO)
|
||||
|
||||
# 设置各种参数的值
|
||||
num_features = 300 # 词向量维度
|
||||
min_word_count = 40 # 最小单词数
|
||||
num_workers = 4 # 并行运行的线程数
|
||||
context = 10 # 上下文窗口大小
|
||||
downsampling = 1e-3 # 为频繁词设置下采样
|
||||
|
||||
# 初始化并训练模型(这需要一些时间)
|
||||
from gensim.models import word2vec
|
||||
print "Training model..."
|
||||
model = word2vec.Word2Vec(sentences, workers=num_workers, \
|
||||
size=num_features, min_count = min_word_count, \
|
||||
window = context, sample = downsampling)
|
||||
|
||||
# 如果你不打算再进一步训练模型,
|
||||
# 则调用 init_sims 将使模型更具内存效率。
|
||||
model.init_sims(replace=True)
|
||||
|
||||
# 创建有意义的模型名称并保存模型以供以后使用会很有帮助。
|
||||
# 你可以稍后使用 Word2Vec.load() 加载它
|
||||
model_name = "300features_40minwords_10context"
|
||||
model.save(model_name)
|
||||
```
|
||||
|
||||
在双核 Macbook Pro 上,使用 4 个工作线程来运行,花费不到 15 分钟。 但是,它会因你的计算机而异。 幸运的是,日志记录功能可以打印带有信息的消息。
|
||||
|
||||
如果你使用的是 Mac 或 Linux 系统,则可以使用终端内(而不是来自 Python 内部)的`top`命令,来查看你的系统是否在模型训练时成功并行化。 键入:
|
||||
|
||||
```
|
||||
> top -o cpu
|
||||
```
|
||||
|
||||
在模型训练时进入终端窗口。 对于 4 个 worker,列表中的第一个进程应该是 Python,它应该显示 300-400% 的 CPU 使用率。
|
||||
|
||||

|
||||
|
||||
如果你的 CPU 使用率较低,则可能是你的计算机上的 cython 无法正常运行。
|
||||
|
||||
### 探索模型结果
|
||||
|
||||
恭喜你到目前为止成功通过了一切! 让我们来看看我们在 75,000 个训练评论中创建的模型。
|
||||
|
||||
`doesnt_match`函数将尝试推断集合中哪个单词与其他单词最不相似:
|
||||
|
||||
```py
|
||||
>>> model.doesnt_match("man woman child kitchen".split())
|
||||
'kitchen'
|
||||
```
|
||||
|
||||
我们的模型能够区分意义上的差异! 它知道男人,女人和孩子彼此更相似,而不是厨房。 更多的探索表明,该模型对意义上更微妙的差异敏感,例如国家和城市之间的差异:
|
||||
|
||||
```py
|
||||
>>> model.doesnt_match("france england germany berlin".split())
|
||||
'berlin'
|
||||
```
|
||||
|
||||
...虽然我们使用的训练集相对较小,但肯定不完美:
|
||||
|
||||
```py
|
||||
>>> model.doesnt_match("paris berlin london austria".split())
|
||||
'paris'
|
||||
```
|
||||
|
||||
我们还可以使用`most_similar`函数来深入了解模型的单词簇:
|
||||
|
||||
```py
|
||||
>>> model.most_similar("man")
|
||||
[(u'woman', 0.6056041121482849), (u'guy', 0.4935004413127899), (u'boy', 0.48933547735214233), (u'men', 0.4632953703403473), (u'person', 0.45742249488830566), (u'lady', 0.4487500488758087), (u'himself', 0.4288588762283325), (u'girl', 0.4166809320449829), (u'his', 0.3853422999382019), (u'he', 0.38293731212615967)]
|
||||
|
||||
>>> model.most_similar("queen")
|
||||
[(u'princess', 0.519856333732605), (u'latifah', 0.47644317150115967), (u'prince', 0.45914226770401), (u'king', 0.4466976821422577), (u'elizabeth', 0.4134873151779175), (u'antoinette', 0.41033703088760376), (u'marie', 0.4061327874660492), (u'stepmother', 0.4040161967277527), (u'belle', 0.38827288150787354), (u'lovely', 0.38668593764305115)]
|
||||
```
|
||||
|
||||
鉴于我们特定的训练集,“Latifah”与“女王”的相似性最高,也就不足为奇了。
|
||||
|
||||
或者,与情感分析更相关:
|
||||
|
||||
```py
|
||||
>>> model.most_similar("awful")
|
||||
[(u'terrible', 0.6812670230865479), (u'horrible', 0.62867271900177), (u'dreadful', 0.5879652500152588), (u'laughable', 0.5469599962234497), (u'horrendous', 0.5167273283004761), (u'atrocious', 0.5115568041801453), (u'ridiculous', 0.5104714632034302), (u'abysmal', 0.5015234351158142), (u'pathetic', 0.4880446791648865), (u'embarrassing', 0.48272213339805603)]
|
||||
```
|
||||
|
||||
因此,似乎我们有相当好的语义意义模型 - 至少和词袋一样好。 但是,我们如何才能将这些花哨的分布式单词向量用于监督学习呢? 下一节将对此进行一次尝试。
|
|
@ -0,0 +1,300 @@
|
|||
## 第三部分:词向量的更多乐趣
|
||||
|
||||
### 代码
|
||||
|
||||
第三部分的代码在[这里](https://github.com/wendykan/DeepLearningMovies/blob/master/Word2Vec_BagOfCentroids.py)。
|
||||
|
||||
### 单词的数值表示
|
||||
|
||||
现在我们有了训练好的模型,对单词有一些语义理解,我们应该如何使用它? 如果你看它的背后,第 2 部分训练的 Word2Vec 模型由词汇表中每个单词的特征向量组成,存储在一个名为`syn0`的[`numpy`](http://www.numpy.org/)数组中:
|
||||
|
||||
```py
|
||||
>>> # Load the model that we created in Part 2
|
||||
>>> from gensim.models import Word2Vec
|
||||
>>> model = Word2Vec.load("300features_40minwords_10context")
|
||||
2014-08-03 14:50:15,126 : INFO : loading Word2Vec object from 300features_40min_word_count_10context
|
||||
2014-08-03 14:50:15,777 : INFO : setting ignored attribute syn0norm to None
|
||||
|
||||
>>> type(model.syn0)
|
||||
<type 'numpy.ndarray'>
|
||||
|
||||
>>> model.syn0.shape
|
||||
(16492, 300)
|
||||
```
|
||||
|
||||
`syn0`中的行数是模型词汇表中的单词数,列数对应于我们在第 2 部分中设置的特征向量的大小。将最小单词计数设置为 40 ,总词汇量为 16,492 个单词,每个词有 300 个特征。 可以通过以下方式访问单个单词向量:
|
||||
|
||||
```py
|
||||
>>> model["flower"]
|
||||
```
|
||||
|
||||
...返回一个 1x300 的`numpy`数组。
|
||||
|
||||
### 从单词到段落,尝试 1:向量平均
|
||||
|
||||
IMDB 数据集的一个挑战是可变长度评论。 我们需要找到一种方法来获取单个单词向量并将它们转换为每个评论的长度相同的特征集。
|
||||
|
||||
由于每个单词都是 300 维空间中的向量,我们可以使用向量运算来组合每个评论中的单词。 我们尝试的一种方法是简单地平均给定的评论中的单词向量(为此,我们删除了停止词,这只会增加噪音)。
|
||||
|
||||
以下代码基于第 2 部分的代码构建了特征向量的平均值。
|
||||
|
||||
```py
|
||||
import numpy as np # Make sure that numpy is imported
|
||||
|
||||
def makeFeatureVec(words, model, num_features):
|
||||
# 用于平均给定段落中的所有单词向量的函数
|
||||
#
|
||||
# 预初始化一个空的 numpy 数组(为了速度)
|
||||
featureVec = np.zeros((num_features,),dtype="float32")
|
||||
#
|
||||
nwords = 0.
|
||||
#
|
||||
# Index2word 是一个列表,包含模型词汇表中的单词名称。
|
||||
# 为了获得速度,将其转换为集合。
|
||||
index2word_set = set(model.index2word)
|
||||
#
|
||||
# 遍历评论中的每个单词,如果它在模型的词汇表中,
|
||||
# 则将其特征向量加到 total
|
||||
for word in words:
|
||||
if word in index2word_set:
|
||||
nwords = nwords + 1.
|
||||
featureVec = np.add(featureVec,model[word])
|
||||
#
|
||||
# 将结果除以单词数来获得平均值
|
||||
featureVec = np.divide(featureVec,nwords)
|
||||
return featureVec
|
||||
|
||||
|
||||
def getAvgFeatureVecs(reviews, model, num_features):
|
||||
# 给定一组评论(每个评论都是单词列表),计算每个评论的平均特征向量并返回2D numpy数组
|
||||
#
|
||||
# 初始化计数器
|
||||
counter = 0.
|
||||
#
|
||||
# 为了速度,预分配 2D numpy 数组
|
||||
reviewFeatureVecs = np.zeros((len(reviews),num_features),dtype="float32")
|
||||
#
|
||||
# 遍历评论
|
||||
for review in reviews:
|
||||
#
|
||||
# 每 1000 个评论打印一次状态消息
|
||||
if counter%1000. == 0.:
|
||||
print "Review %d of %d" % (counter, len(reviews))
|
||||
#
|
||||
# 调用生成平均特征向量的函数(定义如上)
|
||||
reviewFeatureVecs[counter] = makeFeatureVec(review, model, \
|
||||
num_features)
|
||||
#
|
||||
# 增加计数器
|
||||
counter = counter + 1.
|
||||
return reviewFeatureVecs
|
||||
```
|
||||
|
||||
现在,我们可以调用这些函数来为每个段落创建平均向量。 以下操作将需要几分钟:
|
||||
|
||||
```py
|
||||
# ****************************************************************
|
||||
# 使用我们在上面定义的函数,
|
||||
# 计算训练和测试集的平均特征向量。
|
||||
# 请注意,我们现在删除停止词。
|
||||
|
||||
clean_train_reviews = []
|
||||
for review in train["review"]:
|
||||
clean_train_reviews.append( review_to_wordlist( review, \
|
||||
remove_stopwords=True ))
|
||||
|
||||
trainDataVecs = getAvgFeatureVecs( clean_train_reviews, model, num_features )
|
||||
|
||||
print "Creating average feature vecs for test reviews"
|
||||
clean_test_reviews = []
|
||||
for review in test["review"]:
|
||||
clean_test_reviews.append( review_to_wordlist( review, \
|
||||
remove_stopwords=True ))
|
||||
|
||||
testDataVecs = getAvgFeatureVecs( clean_test_reviews, model, num_features )
|
||||
```
|
||||
|
||||
接下来,使用平均段落向量来训练随机森林。 请注意,与第 1 部分一样,我们只能使用标记的训练评论来训练模型。
|
||||
|
||||
```py
|
||||
# 使用 100 棵树让随机森林拟合训练数据
|
||||
from sklearn.ensemble import RandomForestClassifier
|
||||
forest = RandomForestClassifier( n_estimators = 100 )
|
||||
|
||||
print "Fitting a random forest to labeled training data..."
|
||||
forest = forest.fit( trainDataVecs, train["sentiment"] )
|
||||
|
||||
# 测试和提取结果
|
||||
result = forest.predict( testDataVecs )
|
||||
|
||||
# 写出测试结果
|
||||
output = pd.DataFrame( data={"id":test["id"], "sentiment":result} )
|
||||
output.to_csv( "Word2Vec_AverageVectors.csv", index=False, quoting=3 )
|
||||
```
|
||||
|
||||
我们发现这产生了比偶然更好的结果,但是表现比词袋低了几个百分点。
|
||||
|
||||
由于向量的元素平均值没有产生惊人的结果,或许我们可以以更聪明的方式实现? 加权单词向量的标准方法是应用[“tf-idf”](http://en.wikipedia.org/wiki/Tf%E2%80%93idf)权重,它衡量给定单词在给定文档集中的重要程度。 在 Python 中提取 tf-idf 权重的一种方法,是使用 scikit-learn 的[`TfidfVectorizer`](http://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.TfidfVectorizer.html),它具有类似于我们在第 1 部分中使用的`CountVectorizer`的接口。但是,当我们尝试以这种方式加权我们的单词向量时,我们发现没有实质的性能改善。
|
||||
|
||||
### 从单词到段落,尝试 2:聚类
|
||||
|
||||
Word2Vec 创建语义相关单词的簇,因此另一种可能的方法是利用簇中单词的相似性。 以这种方式来分组向量称为“向量量化”。 为了实现它,我们首先需要找到单词簇的中心,我们可以通过使用[聚类算法](http://scikit-learn.org/stable/modules/clustering.html)(如 [K-Means](http://en.wikipedia.org/wiki/K-means_clustering))来完成。
|
||||
|
||||
在 K-Means 中,我们需要设置的一个参数是“K”,或者是簇的数量。 我们应该如何决定要创建多少个簇? 试错法表明,每个簇平均只有5个单词左右的小簇,比具有多个词的大簇产生更好的结果。 聚类代码如下。 我们使用 [scikit-learn 来执行我们的 K-Means](http://scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html)。
|
||||
|
||||
具有较大 K 的 K-Means 聚类可能非常慢;以下代码在我的计算机上花了 40 多分钟。 下面,我们给 K-Means 函数设置一个计时器,看看它需要多长时间。
|
||||
|
||||
```py
|
||||
from sklearn.cluster import KMeans
|
||||
import time
|
||||
|
||||
start = time.time() # Start time
|
||||
|
||||
# 将“k”(num_clusters)设置为词汇量大小的 1/5,或每个簇平均 5 个单词
|
||||
word_vectors = model.syn0
|
||||
num_clusters = word_vectors.shape[0] / 5
|
||||
|
||||
# 初始化 k-means 对象并使用它来提取质心
|
||||
kmeans_clustering = KMeans( n_clusters = num_clusters )
|
||||
idx = kmeans_clustering.fit_predict( word_vectors )
|
||||
|
||||
# 获取结束时间并打印该过程所需的时间
|
||||
end = time.time()
|
||||
elapsed = end - start
|
||||
print "Time taken for K Means clustering: ", elapsed, "seconds."
|
||||
```
|
||||
|
||||
现在,每个单词的聚类分布都存储在`idx`中,而原始 Word2Vec 模型中的词汇表仍存储在`model.index2word`中。 为方便起见,我们将它们压缩成一个字典,如下所示:
|
||||
|
||||
```py
|
||||
# 创建单词/下标字典,将每个词汇表单词映射为簇编号
|
||||
word_centroid_map = dict(zip( model.index2word, idx ))
|
||||
```
|
||||
|
||||
这有点抽象,所以让我们仔细看看我们的簇包含什么。 你的簇可能会有所不同,因为 Word2Vec 依赖于随机数种子。 这是一个循环,打印出簇 0 到 9 的单词:
|
||||
|
||||
```py
|
||||
# 对于前 10 个簇
|
||||
for cluster in xrange(0,10):
|
||||
#
|
||||
# 打印簇编号
|
||||
print "\nCluster %d" % cluster
|
||||
#
|
||||
# 找到该簇编号的所有单词,然后将其打印出来
|
||||
words = []
|
||||
for i in xrange(0,len(word_centroid_map.values())):
|
||||
if( word_centroid_map.values()[i] == cluster ):
|
||||
words.append(word_centroid_map.keys()[i])
|
||||
print words
|
||||
```
|
||||
|
||||
结果很有意思:
|
||||
|
||||
```py
|
||||
Cluster 0
|
||||
[u'passport', u'penthouse', u'suite', u'seattle', u'apple']
|
||||
|
||||
Cluster 1
|
||||
[u'unnoticed']
|
||||
|
||||
Cluster 2
|
||||
[u'midst', u'forming', u'forefront', u'feud', u'bonds', u'merge', u'collide', u'dispute', u'rivalry', u'hostile', u'torn', u'advancing', u'aftermath', u'clans', u'ongoing', u'paths', u'opposing', u'sexes', u'factions', u'journeys']
|
||||
|
||||
Cluster 3
|
||||
[u'lori', u'denholm', u'sheffer', u'howell', u'elton', u'gladys', u'menjou', u'caroline', u'polly', u'isabella', u'rossi', u'nora', u'bailey', u'mackenzie', u'bobbie', u'kathleen', u'bianca', u'jacqueline', u'reid', u'joyce', u'bennett', u'fay', u'alexis', u'jayne', u'roland', u'davenport', u'linden', u'trevor', u'seymour', u'craig', u'windsor', u'fletcher', u'barrie', u'deborah', u'hayward', u'samantha', u'debra', u'frances', u'hildy', u'rhonda', u'archer', u'lesley', u'dolores', u'elsie', u'harper', u'carlson', u'ella', u'preston', u'allison', u'sutton', u'yvonne', u'jo', u'bellamy', u'conte', u'stella', u'edmund', u'cuthbert', u'maude', u'ellen', u'hilary', u'phyllis', u'wray', u'darren', u'morton', u'withers', u'bain', u'keller', u'martha', u'henderson', u'madeline', u'kay', u'lacey', u'topper', u'wilding', u'jessie', u'theresa', u'auteuil', u'dane', u'jeanne', u'kathryn', u'bentley', u'valerie', u'suzanne', u'abigail']
|
||||
|
||||
Cluster 4
|
||||
[u'fest', u'flick']
|
||||
|
||||
Cluster 5
|
||||
[u'lobster', u'deer']
|
||||
|
||||
Cluster 6
|
||||
[u'humorless', u'dopey', u'limp']
|
||||
|
||||
Cluster 7
|
||||
[u'enlightening', u'truthful']
|
||||
|
||||
Cluster 8
|
||||
[u'dominates', u'showcases', u'electrifying', u'powerhouse', u'standout', u'versatility', u'astounding']
|
||||
|
||||
Cluster 9
|
||||
[u'succumbs', u'comatose', u'humiliating', u'temper', u'looses', u'leans']
|
||||
```
|
||||
|
||||
我们可以看到这些簇的质量各不相同。 有些是有道理的 - 簇 3 主要包含名称,而簇 6- 8包含相关的形容词(簇 6 是我最喜欢的)。 另一方面,簇 5 有点神秘:龙虾和鹿有什么共同之处(除了是两只动物)? 簇 0 更糟糕:阁楼和套房似乎属于一个东西,但它们似乎不属于苹果和护照。 簇 2 包含......可能与战争有关的词? 也许我们的算法在形容词上效果最好。
|
||||
|
||||
无论如何,现在我们为每个单词分配了一个簇(或“质心”),我们可以定义一个函数将评论转换为质心袋。 这就像词袋一样,但使用语义相关的簇而不是单个单词:
|
||||
|
||||
```py
|
||||
def create_bag_of_centroids( wordlist, word_centroid_map ):
|
||||
#
|
||||
# 簇的数量等于单词/质心映射中的最大的簇索引
|
||||
num_centroids = max( word_centroid_map.values() ) + 1
|
||||
#
|
||||
# 预分配质心向量袋(为了速度)
|
||||
bag_of_centroids = np.zeros( num_centroids, dtype="float32" )
|
||||
#
|
||||
# 遍历评论中的单词。如果单词在词汇表中,
|
||||
# 找到它所属的簇,并将该簇的计数增加 1
|
||||
for word in wordlist:
|
||||
if word in word_centroid_map:
|
||||
index = word_centroid_map[word]
|
||||
bag_of_centroids[index] += 1
|
||||
#
|
||||
# 返回“质心袋”
|
||||
return bag_of_centroids
|
||||
```
|
||||
|
||||
上面的函数将为每个评论提供一个`numpy`数组,每个数组的特征都与簇数相等。 最后,我们为训练和测试集创建了质心袋,然后训练随机森林并提取结果:
|
||||
|
||||
```py
|
||||
# 为训练集质心预分配一个数组(为了速度)
|
||||
train_centroids = np.zeros( (train["review"].size, num_clusters), \
|
||||
dtype="float32" )
|
||||
|
||||
# 将训练集评论转换为质心袋
|
||||
counter = 0
|
||||
for review in clean_train_reviews:
|
||||
train_centroids[counter] = create_bag_of_centroids( review, \
|
||||
word_centroid_map )
|
||||
counter += 1
|
||||
|
||||
# 对测试评论重复
|
||||
test_centroids = np.zeros(( test["review"].size, num_clusters), \
|
||||
dtype="float32" )
|
||||
|
||||
counter = 0
|
||||
for review in clean_test_reviews:
|
||||
test_centroids[counter] = create_bag_of_centroids( review, \
|
||||
word_centroid_map )
|
||||
counter += 1
|
||||
```
|
||||
|
||||
```py
|
||||
# 拟合随机森林并提取预测
|
||||
forest = RandomForestClassifier(n_estimators = 100)
|
||||
|
||||
# 拟合可能需要几分钟
|
||||
print "Fitting a random forest to labeled training data..."
|
||||
forest = forest.fit(train_centroids,train["sentiment"])
|
||||
result = forest.predict(test_centroids)
|
||||
|
||||
# 写出测试结果
|
||||
output = pd.DataFrame(data={"id":test["id"], "sentiment":result})
|
||||
output.to_csv( "BagOfCentroids.csv", index=False, quoting=3 )
|
||||
```
|
||||
|
||||
我们发现与第 1 部分中的词袋相比,上面的代码给出了相同(或略差)的结果。
|
||||
|
||||
### 深度和非深度学习方法的比较
|
||||
|
||||
你可能会问:为什么词袋更好?
|
||||
|
||||
最大的原因是,在我们的教程中,平均向量和使用质心会失去单词的顺序,这使得它与词袋的概念非常相似。性能相似(在标准误差范围内)的事实使得所有三种方法实际上相同。
|
||||
|
||||
一些要尝试的事情:
|
||||
|
||||
首先,在更多文本上训练 Word2Vec 应该会大大提高性能。谷歌的结果基于从超过十亿字的语料库中学到的单词向量;我们标记和未标记的训练集合在一起只有 1800 万字左右。方便的是,Word2Vec 提供了加载由谷歌原始 C 工具输出的任何预训练模型的函数,因此也可以用 C 训练模型然后将其导入 Python。
|
||||
|
||||
其次,在已发表的文献中,分布式单词向量技术已被证明优于词袋模型。在本文中,在 IMDB 数据集上使用了一种名为段落向量的算法,来生成迄今为止最先进的一些结果。在某种程度上,它比我们在这里尝试的方法更好,因为向量平均和聚类会丢失单词顺序,而段落向量会保留单词顺序信息。
|
|
@ -0,0 +1,9 @@
|
|||
# Kaggle word2vec NLP 教程
|
||||
|
||||
> 原文:[Bag of Words Meets Bags of Popcorn](https://www.kaggle.com/c/word2vec-nlp-tutorial)
|
||||
>
|
||||
> 译者:[飞龙](https://github.com/wizardforcel)
|
||||
>
|
||||
> 协议:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/)
|
||||
>
|
||||
> 自豪地采用[谷歌翻译](https://translate.google.cn/)
|
Loading…
Reference in New Issue