28
README.md
|
@ -1,9 +1,10 @@
|
|||
# Kaggle
|
||||
|
||||

|
||||
|
||||
* [ApacheCN 组织资源](https://github.com/apachecn/home): https://github.com/apachecn/home
|
||||
> **你已经抓住了石头,现在是挥舞它的时候了!**
|
||||
|
||||
> **欢迎任何人参与和完善:一个人可以走的很快,但是一群人却可以走的更远**
|
||||
* [ApacheCN 组织资源](https://github.com/apachecn/home): https://github.com/apachecn/home
|
||||
* <strong>ApacheCN - Kaggle组队群【686932392】<a target="_blank" href="//shang.qq.com/wpa/qunwpa?idkey=716b584bbd7cdf64e961b499c7fb5891faf1f6c92dad026e3c596a57c834f1ec"><img title="ApacheCN - Kaggle组队群【686932392】" src="http://www.apachecn.org/wp-content/uploads/2017/10/ApacheCN-group.png" alt="ApacheCN - Kaggle组队群【686932392】" /></a></strong></li>
|
||||
* [Kaggle](https://www.kaggle.com) 是一个流行的数据科学竞赛平台。
|
||||
* [GitHub 入门操作指南](docs/GitHub/README.md) 和 [Kaggle 入门操作指南](docs/kaggle-quickstart.md),适合于学习过 [MachineLearning(机器学习实战)](https://github.com/apachecn/MachineLearning) 的小盆友
|
||||
|
@ -63,12 +64,10 @@
|
|||
|
||||
`推荐比赛 Featured` 是瞄准商业问题带有奖金的公开竞赛。如果有幸赢得比赛,不但可以获得奖金,模型也可能会被竞赛赞助商应用到商业实践中呢。
|
||||
|
||||
> Kaggle
|
||||
|
||||
* [**Mercari 价格推荐挑战**](/competitions/featured/mercari-price-suggestion-challenge)
|
||||
* [**Home Credit Default Risk**](/competitions/featured/home-credit-default-risk)
|
||||
|
||||
> 天池
|
||||
### 天池
|
||||
|
||||
* [**天池入门教程: O2O优惠券-使用新人赛**](https://tianchi.aliyun.com/notebook/detail.html?spm=5176.11409386.4851167.7.65c91d07FiVHVN&id=4796)
|
||||
* [**天池第一名: O2O优惠券-预测用户领取优惠劵后是否核销**](https://github.com/wepe/O2O-Coupon-Usage-Forecast)
|
||||
|
@ -80,6 +79,12 @@
|
|||
* [论坛](https://www.kaggle.com/discussion): 发帖回帖讨论的平台
|
||||
* [招聘](https://www.kaggle.com/jobs): 企业招聘数据科学家的位置
|
||||
|
||||
## 解决方案列表
|
||||
|
||||
* [解决方案列表](docs/writeup-list.md)
|
||||
|
||||
如果解决方案太大,可以先放在这个列表中。以后再逐步整合到这个仓库。
|
||||
|
||||
## 机器学习算法
|
||||
|
||||
> 常用算法选择
|
||||
|
@ -124,9 +129,17 @@
|
|||
- 统计型:与业务强关联
|
||||
- 组合特征
|
||||
|
||||
## 活动时间
|
||||
## 贡献指南
|
||||
|
||||
时间段:2017-11-15 ~ 2017-11-30
|
||||
> **欢迎任何人参与和完善:一个人可以走的很快,但是一群人却可以走的更远**
|
||||
|
||||
本项目接受大家提交 WriteUp(题解)。
|
||||
|
||||
WriteUp 需要带有预处理过程,从你能下载到的原始数据开始,并且带有验证过程和评价指标。
|
||||
|
||||
请放在`/competitions/{分类}/{名称}`目录下。
|
||||
|
||||
其中分类一共有六个,请见上面,名称是 URL 中`/c/`后面的部分。
|
||||
|
||||
## 联系方式
|
||||
|
||||
|
@ -148,6 +161,7 @@
|
|||
* [@zehuichen](https://github.com/zehuichen123)(loveSnowBest)
|
||||
* [@谈笑风生](https://github.com/zhu1040028623)(谈笑风生)
|
||||
* [@诺木人](https://github.com/1mrliu)(诺木人)
|
||||
* [@飞龙](https://github.com/wizardforcel)
|
||||
|
||||
> 加入方式
|
||||
|
||||
|
|
|
@ -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/)
|
|
@ -0,0 +1,241 @@
|
|||
# 解决方案列表
|
||||
|
||||
## 结构化数据/时间序列
|
||||
|
||||
1. 2018科大讯飞AI营销算法大赛
|
||||
|
||||
Rank1:https://zhuanlan.zhihu.com/p/47807544
|
||||
|
||||
2. 2018 IJCAI 阿里妈妈搜索广告转化预测
|
||||
|
||||
Rank1:https://github.com/plantsgo/ijcai-2018
|
||||
|
||||
Rank2:
|
||||
|
||||
+ https://github.com/YouChouNoBB/ijcai-18-top2-single-mole-solution
|
||||
|
||||
+ https://blog.csdn.net/Bryan__/article/details/80600189
|
||||
|
||||
Rank3: https://github.com/luoda888/2018-IJCAI-top3
|
||||
|
||||
Rank8: https://github.com/fanfanda/ijcai_2018
|
||||
|
||||
Rank8: https://github.com/Gene20/IJCAI-18
|
||||
|
||||
Rank9(第一赛季)https://github.com/yuxiaowww/IJCAI-18-TIANCHI
|
||||
|
||||
Rank29: https://github.com/bettenW/IJCAI18_Tianchi_Rank29
|
||||
|
||||
Rank41: https://github.com/cmlaughing/IJCAI-18
|
||||
|
||||
Rank48: https://github.com/YunaQiu/IJCAI-18alimama
|
||||
|
||||
Rank53: https://github.com/altmanWang/IJCAI-18-CVR
|
||||
|
||||
Rank60: https://github.com/Chenyaorui/ijcai_2018
|
||||
|
||||
Rank81: https://github.com/wzp123456/IJCAI_18
|
||||
|
||||
Rank94: https://github.com/Yangtze121/-IJCAI-18-
|
||||
|
||||
3. 2018腾讯广告算法大赛
|
||||
|
||||
Rank3: https://github.com/DiligentPanda/Tencent_Ads_Algo_2018
|
||||
|
||||
rank6: https://github.com/nzc/tencent-contest
|
||||
|
||||
Rank7: https://github.com/guoday/Tencent2018_Lookalike_Rank7th
|
||||
|
||||
Rank9: https://github.com/ouwenjie03/tencent-ad-game
|
||||
|
||||
Rank10: https://github.com/keyunluo/Tencent2018_Lookalike_Rank10th
|
||||
|
||||
rank10(初赛): https://github.com/ShawnyXiao/2018-Tencent-Lookalike
|
||||
|
||||
Rank11:
|
||||
|
||||
+ https://github.com/liupengsay/2018-Tencent-social-advertising-algorithm-contest
|
||||
|
||||
+ https://my.oschina.net/xtzggbmkk/blog/1865680
|
||||
|
||||
Rank26: https://github.com/zsyandjyhouse/TencentAD_contest
|
||||
|
||||
Rank33: https://github.com/John-Yao/Tencent_Social_Ads2018
|
||||
|
||||
Rank69: https://github.com/BladeCoda/Tencent2018_Final_Phrase_Presto
|
||||
|
||||
4. 2018JDATA 用户购买时间预测
|
||||
|
||||
Rank9:https://zhuanlan.zhihu.com/p/45141799
|
||||
|
||||
5. 2018 DF风机叶片开裂预警
|
||||
|
||||
Rank2:https://github.com/SY575/DF-Early-warning-of-the-wind-power-system
|
||||
|
||||
6. 2018 DF光伏发电量预测
|
||||
|
||||
Rank1:https://zhuanlan.zhihu.com/p/44755488?utm_source=qq&utm_medium=social&utm_oi=623925402599559168
|
||||
|
||||
https://mp.weixin.qq.com/s/Yix0xVp2SiqaAcuS6Q049g
|
||||
|
||||
7. AI全球挑战者大赛-违约用户风险预测
|
||||
|
||||
Rank1:https://github.com/chenkkkk/User-loan-risk-prediction
|
||||
|
||||
8. 2016融360-用户贷款风险预测
|
||||
|
||||
Rank7:https://github.com/hczheng/Rong360
|
||||
|
||||
9. 2016 CCF-020优惠券使用预测
|
||||
|
||||
Rank1: https://github.com/wepe/O2O-Coupon-Usage-Forecast
|
||||
|
||||
10. 2016 ccf-农产品价格预测
|
||||
|
||||
Rank2: https://github.com/xing89qs/CCF_Product
|
||||
|
||||
Rank35: https://github.com/wqlin/ccf-price-prediction
|
||||
|
||||
11. 2016 ccf-客户用电异常
|
||||
|
||||
Rank4: https://github.com/AbnerYang/2016CCF-StateGrid
|
||||
|
||||
12. 2016 ccf-搜狗的用户画像比赛
|
||||
|
||||
Rank1: https://github.com/hengchao0248/ccf2016_sougou
|
||||
|
||||
Rank3: https://github.com/AbnerYang/2016CCF-SouGou
|
||||
|
||||
Rank5:
|
||||
|
||||
+ https://github.com/dhdsjy/2016_CCFsougou
|
||||
|
||||
+ https://github.com/dhdsjy/2016_CCFsougou2
|
||||
|
||||
+ https://github.com/prozhuchen/2016CCF-sougou
|
||||
|
||||
+ https://github.com/coderSkyChen/2016CCF_BDCI_Sougou
|
||||
|
||||
13. 2016 ccf-联通的用户轨迹
|
||||
|
||||
RankX: https://github.com/xuguanggen/2016CCF-unicom
|
||||
|
||||
14. 2016 ccf-Human or Robots
|
||||
|
||||
Rank6: https://github.com/pickou/ccf_human_or_robot
|
||||
|
||||
15. 菜鸟-需求预测与分仓规划
|
||||
|
||||
Rank6: https://github.com/wepe/CaiNiao-DemandForecast-StoragePlaning
|
||||
|
||||
Rank10: https://github.com/xing89qs/TianChi_CaiNiao_Season2
|
||||
|
||||
## NLP
|
||||
|
||||
1. 智能客服问题相似度算法设计——第三届魔镜杯大赛
|
||||
|
||||
rank6 https://github.com/qrfaction/paipaidai
|
||||
|
||||
rank12
|
||||
|
||||
+ https://www.jianshu.com/p/827dd447daf9
|
||||
|
||||
+ https://github.com/LittletreeZou/Question-Pairs-Matching
|
||||
|
||||
|
||||
## CV
|
||||
|
||||
1. Kaggle-TGS
|
||||
|
||||
Rank1: http://t.cn/EzkDlOC
|
||||
Rank9: http://t.cn/EznzvYv
|
||||
Rank11:https://github.com/iasawseen/Kaggle-TGS-salt-solution
|
||||
Rank14:https://github.com/lRomul/argus-tgs-salt
|
||||
Rank15:https://github.com/adam9500370/Kaggle-TGS
|
||||
Rank22: http://t.cn/EzYkR6i
|
||||
Rank56 https://github.com/Gary-Deeplearning/TGS-Salt
|
||||
|
||||
2. Kaggle Google地标检索
|
||||
|
||||
Rank1: http://t.cn/R1i7Xiy
|
||||
Rank14: http://t.cn/R1nQriY
|
||||
|
||||
3. Lyft感知挑战赛
|
||||
|
||||
赛题: http://t.cn/RBtrJcE
|
||||
Rank4: http://t.cn/RBtrMdw
|
||||
http://t.cn/RBJnlug
|
||||
|
||||
4. (Kaggle)CVPR 2018 WAD视频分割
|
||||
|
||||
Rank2: http://t.cn/Ehp4Ggm
|
||||
|
||||
5. Kaggle Google AI Open Images
|
||||
|
||||
Rank15: http://t.cn/RF1jnis
|
||||
|
||||
6. Quick, Draw! Kaggle Competition Starter Pack
|
||||
http://t.cn/EZAoZDM
|
||||
|
||||
7. Kaggle植物幼苗图像分类挑战赛
|
||||
|
||||
Rank1: http://t.cn/RBssjf6
|
||||
|
||||
8. Kaggle Airbus Ship Detection Challenge (Kaggle卫星图像船舶检测比赛)
|
||||
|
||||
Rank8: https://github.com/SeuTao/Kaggle_Airbus2018_8th_code
|
||||
Rank21:https://github.com/pascal1129/kaggle_airbus_ship_detection
|
||||
|
||||
|
||||
9. kaggle RSNA Pneumonia Detection
|
||||
|
||||
Rank1:https://github.com/i-pan/kaggle-rsna18
|
||||
Rank2:https://github.com/SeuTao/Kaggle_TGS2018_4th_solution
|
||||
Rank3:https://github.com/pmcheng/rsna-pneumonia
|
||||
Rank6:https://github.com/pfnet-research/pfneumonia
|
||||
Rank10:https://github.com/alessonscap/rsna-challenge-2018
|
||||
|
||||
10. kaggle Carvana Image Masking Challenge
|
||||
|
||||
Rank1:https://github.com/asanakoy/kaggle_carvana_segmentation
|
||||
Rank3:https://github.com/lyakaap/Kaggle-Carvana-3rd-place-solution
|
||||
|
||||
11. kaggle Statoil/C-CORE Iceberg Classifier Challenge
|
||||
|
||||
Rank4: https://github.com/asydorchuk/kaggle/blob/master/statoil/README.md
|
||||
|
||||
12. kaggle 2018 Data Science Bowl
|
||||
|
||||
Rank1: https://github.com/selimsef/dsb2018_topcoders
|
||||
Rank2: https://github.com/jacobkie/2018DSB
|
||||
Rank3: https://github.com/Lopezurrutia/DSB_2018
|
||||
Rank4: https://github.com/pdima/kaggle_2018_data_science_bowl_solution
|
||||
Rank5: https://github.com/mirzaevinom/data_science_bowl_2018
|
||||
|
||||
|
||||
## 经验文章
|
||||
|
||||
1. 介绍featexp 一个帮助理解特征的工具包 http://www.sohu.com/a/273552971_129720
|
||||
|
||||
2. Ask Me Anything session with a Kaggle Grandmaster Vladimir I. Iglovikov
|
||||
|
||||
PDF:https://pan.baidu.com/s/1XkFwko_YrI5TfjjIai7ONQ
|
||||
|
||||
## 其它资源列表
|
||||
|
||||
1. 数据比赛资讯:https://github.com/iphysresearch/DataSciComp
|
||||
|
||||
2. Kaggle top方案整理:https://github.com/EliotAndres/kaggle-past-solutions
|
||||
|
||||
3. [Data competition Top Solution 数据竞赛Top解决方案开源整理](https://github.com/Smilexuhc/Data-Competition-TopSolution)
|
||||
|
||||
## 大佬的Git
|
||||
|
||||
1. 植物 :https://github.com/plantsgo
|
||||
2. wepon :https://github.com/wepe
|
||||
3. Snake:https://github.com/luoda888
|
||||
4. Drop-out:https://github.com/drop-out
|
||||
5. 金老师的知乎:https://zhuanlan.zhihu.com/jlbookworm
|
||||
6. 渣大:https://github.com/nzc
|
||||
7. 郭大:https://github.com/guoday
|
||||
|
|
@ -0,0 +1,964 @@
|
|||
# Kaggle 官方教程:嵌入
|
||||
|
||||
> 原文:[Embeddings](https://www.kaggle.com/learn/embeddings)
|
||||
>
|
||||
> 译者:[飞龙](https://github.com/wizardforcel)
|
||||
>
|
||||
> 协议:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/)
|
||||
|
||||
P.S...
|
||||
|
||||
本课程仍处于测试阶段,因此我很乐意收到你的反馈意见。 如果你有时间填写[本课程的超短期调查](https://form.jotform.com/82826168584267),我将非常感激。 你也可以在下面的评论中或在[学习论坛](https://www.kaggle.com/learn-forum)上留下公开反馈。
|
||||
|
||||
## 一、嵌入层
|
||||
|
||||
欢迎阅读嵌入主题的第一课。 在本课程中,我将展示如何使用`tf.keras` API 实现带嵌入层的模型。 嵌入是一种技术,使深度神经网络能够处理稀疏的类别变量。
|
||||
|
||||
### 稀疏类别变量
|
||||
|
||||
我的意思是一个具有许多可能值(高基数)的类别变量,其中少数(通常只有 1)存在于任何给定的观察中。 一个很好的例子是词汇。 英语中的词汇是成千上万的,但一条推文可能只有十几个词。 词嵌入是将深度学习应用于自然语言的关键技术。 但其他例子还有很多。
|
||||
|
||||
例如,[洛杉矶餐馆检查](https://www.kaggle.com/meganrisdal/la-county-restaurant-inspections-and-violations)的这个数据集有几个稀疏的分类变量,包括:
|
||||
|
||||
+ `employee_id`:卫生部门的哪些员工进行了这次检查? (约 250 个不同的值)
|
||||
+ `facility_zip`:餐厅的邮政编码是什么? (约 3,000 个不同的值)
|
||||
+ `owner_name`:谁拥有餐厅? (约 35,000 个不同的值)
|
||||
|
||||
对于将任何这些变量用作网络的输入,嵌入层是个好主意。
|
||||
|
||||
在本课程中,我将使用 [MovieLens 数据集](https://www.kaggle.com/grouplens/movielens-20m-dataset)作为示例。
|
||||
|
||||
### MovieLens
|
||||
|
||||
MovieLens 数据集由用户给电影的评分组成。这是一个示例:
|
||||
|
||||
|
||||
| | userId | movieId | rating | y | title | year |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| 12904240 | 85731 | 1883 | 4.5 | 0.974498 | Labyrinth | 1986 |
|
||||
| 6089380 | 45008 | 1221 | 4.5 | 0.974498 | Femme Nikita, La (Nikita) | 1990 |
|
||||
| 17901393 | 125144 | 3948 | 4.0 | 0.474498 | The Alamo | 1960 |
|
||||
| 9024816 | 122230 | 3027 | 3.5 | -0.025502 | Toy Story 2 | 1999 |
|
||||
| 11655659 | 21156 | 5202 | 3.0 | -0.525502 | My Big Fat Greek Wedding |
|
||||
|
||||
评分范围是 0.5 到 5。我们的目标是预测由给定用户`ui`给出的,特定电影`mj`的评分。 (列`y`只是评分列的副本,减去了平均值 - 这在以后会有用。)
|
||||
|
||||
`userId`和`movieId`都是稀疏分类变量。 它们有许多可能的值:
|
||||
|
||||
```
|
||||
138,493 个独立用户评分了 26,744 个不同的电影(总评分是 20,000,263 个)
|
||||
```
|
||||
|
||||
### 在 Keras 中创建评分预测模型
|
||||
|
||||
我们想要构建一个模型,它接受用户`ui`和电影`mj`,并输出 0.5-5 的数字,表示我们认为该用户将为该电影评分多少星。
|
||||
|
||||
> 注:你可能已经注意到`MovieLens`数据集包含每部电影的信息,例如标题,发行年份,一组流派和用户指定的标签。 但就目前而言,我们不会试图利用任何额外的信息。
|
||||
|
||||
我说我们需要一个嵌入层来处理这些输入。 为什么? 让我们回顾一些替代方案,看看为什么它们不起作用。
|
||||
|
||||
### 坏主意 #1:使用用户和电影 ID 作为数值输入
|
||||
|
||||
为什么不将用户 ID 和电影 ID 作为数值来输入,然后添加一些密集层?即:
|
||||
|
||||
```py
|
||||
model = keras.Sequential([
|
||||
# 2 个输入值:用户 ID 和电影 ID
|
||||
keras.layers.Dense(256, input_dim=2, activation='relu'),
|
||||
keras.layers.Dense(32, activation='relu'),
|
||||
# 单个输出节点,包含预测的评分
|
||||
keras.layers.Dense(1)
|
||||
])
|
||||
```
|
||||
|
||||
用最简单的术语来说,神经网络的原理是对输入进行数学运算。 但分配给用户和电影的 ID 的实际数值是没有意义的。《辛德勒的名单》的 id 为 527,而《非常嫌疑犯》的 id 为 50,但这并不意味着《辛德勒的名单》比《非常嫌疑犯》大十倍。
|
||||
|
||||
### 坏主意 #2:独热编码的用户和电影输入
|
||||
|
||||
如果你不熟悉单热编码,可能需要查看我们的课程[“使用独热编码处理类别数据”](https://www.kaggle.com/dansbecker/using-categorical-data-with-one-hot-encoding)。
|
||||
|
||||
在该课程中,我们说独热编码是“类别数据的标准方法”。 那么为什么这是一个坏主意呢? 让我们看看模型是什么样子,它接受独热编码的用户和电影。
|
||||
|
||||
```py
|
||||
input_size = n_movies + n_users
|
||||
print("Input size = {:,} ({:,} movies + {:,} users)".format(
|
||||
input_size, n_movies, n_users,
|
||||
))
|
||||
model = keras.Sequential([
|
||||
# 一个带有 128 个单元的隐藏层
|
||||
keras.layers.Dense(128, input_dim=input_size, activation='relu'),
|
||||
# 单个输出节点,包含预测的评分
|
||||
keras.layers.Dense(1)
|
||||
])
|
||||
model.summary()
|
||||
'''
|
||||
Input size = 165,237 (26,744 movies + 138,493 users)
|
||||
_________________________________________________________________
|
||||
Layer (type) Output Shape Param #
|
||||
=================================================================
|
||||
dense_3 (Dense) (None, 128) 21150464
|
||||
_________________________________________________________________
|
||||
dense_4 (Dense) (None, 1) 129
|
||||
=================================================================
|
||||
Total params: 21,150,593
|
||||
Trainable params: 21,150,593
|
||||
Non-trainable params: 0
|
||||
_________________________________________________________________
|
||||
'''
|
||||
```
|
||||
|
||||
这里的一个基本问题是扩展和效率。 我们模型的单个输入是包含 165,237 个数字的向量(其中我们知道 165,235 是零)。 我们整个 2000 万个评分实例数据集的特征数据,将需要一个大小为 20,000,000 x 165,237 或大约 3 万亿个数字的二维数组。 但愿你能把这一切都放进内存中!
|
||||
|
||||
此外,在我们的模型上进行训练和推断将是低效的。 为了计算我们的第一个隐藏层的激活,我们需要将我们的 165k 输入乘以大约 2100 万个权重 - 但是这些乘积的绝大多数都将为零。
|
||||
|
||||
对于具有少量可能值的分类变量,例如`{Red, Yellow, Green}`或`{Monday, Tuesday, Wednesday, Friday, Saturday, Sunday}`,独热编码是合适的。 但在像我们的电影推荐问题的情况下,它并不是那么好,其中变量有数十或数十万个可能的值。
|
||||
|
||||
### 好主意:嵌入层
|
||||
|
||||
简而言之,嵌入层将一组离散对象(如单词,用户或电影)中的每个元素映射到实数的密集(嵌入)向量。
|
||||
|
||||
> 注:一个关键的实现细节是嵌入层接受被嵌入实体的索引作为输入(即我们可以将`userId`和`movieId`作为输入)。 你可以将其视为一种“查找表”。 这比采用独热向量并进行巨大的矩阵乘法要有效得多!
|
||||
|
||||
例如,如果我们为电影学习大小为 8 的嵌入,则《律政俏佳人》(`index = 4352`)的嵌入可能如下所示:
|
||||
|
||||
```
|
||||
[1.624,−0.612,−0.528,−1.073,0.865,−2.302,1.745,−0.761]
|
||||
```
|
||||
|
||||
它们来自哪里? 我们使用随机噪声为每个用户和电影初始化嵌入,然后我们将它们训练,作为整体评分预测模型训练过程的一部分。
|
||||
|
||||
他们的意思是什么? 如果对象的嵌入有任何好处,它应该捕获该对象的一些有用的潜在属性。 但这里的关键词是潜在,也就是隐藏的。 由模型来发现实体的任何属性,并在嵌入空间中对它们编码,对预测任务有用。 听起来很神秘? 在后面的课程中,我将展示一些解释习得的嵌入的技术,例如使用 t-SNE 算法将它们可视化。
|
||||
|
||||
### 实现它
|
||||
|
||||
我希望我的模型是这样:
|
||||
|
||||

|
||||
|
||||
需要注意的一个关键点是,这个网络不仅仅是从输入到输出的一堆层级。 我们将用户和电影视为单独的输入,只有在每个输入经过自己的嵌入层之后才会聚集在一起。
|
||||
|
||||
这意味着[`keras.Sequential`](https://www.tensorflow.org/api_docs/python/tf/keras/Sequential)类(你可能从我们的[图像数据深度学习课程](https://www.kaggle.com/learn/deep-learning)中熟悉它)将无法工作。 我们需要使用[`keras.Model`](https://www.tensorflow.org/api_docs/python/tf/keras/Model)类转向更强大的“函数式 API”。 函数式 API 的更多详细信息,请查看 [Keras 的指南](https://keras.io/getting-started/functional-api-guide/)。
|
||||
|
||||
这是代码:
|
||||
|
||||
```py
|
||||
hidden_units = (32,4)
|
||||
movie_embedding_size = 8
|
||||
user_embedding_size = 8
|
||||
|
||||
# 每个实例将包含两个输入:单个用户 ID 和单个电影 ID
|
||||
user_id_input = keras.Input(shape=(1,), name='user_id')
|
||||
movie_id_input = keras.Input(shape=(1,), name='movie_id')
|
||||
user_embedded = keras.layers.Embedding(df.userId.max()+1, user_embedding_size,
|
||||
input_length=1, name='user_embedding')(user_id_input)
|
||||
movie_embedded = keras.layers.Embedding(df.movieId.max()+1, movie_embedding_size,
|
||||
input_length=1, name='movie_embedding')(movie_id_input)
|
||||
# 连接嵌入(并删除无用的额外维度)
|
||||
concatenated = keras.layers.Concatenate()([user_embedded, movie_embedded])
|
||||
out = keras.layers.Flatten()(concatenated)
|
||||
|
||||
# 添加一个或多个隐层
|
||||
for n_hidden in hidden_units:
|
||||
out = keras.layers.Dense(n_hidden, activation='relu')(out)
|
||||
|
||||
# 单一输出:我们的预测评分
|
||||
out = keras.layers.Dense(1, activation='linear', name='prediction')(out)
|
||||
|
||||
model = keras.Model(
|
||||
inputs = [user_id_input, movie_id_input],
|
||||
outputs = out,
|
||||
)
|
||||
model.summary(line_length=88)
|
||||
'''
|
||||
________________________________________________________________________________________
|
||||
Layer (type) Output Shape Param # Connected to
|
||||
========================================================================================
|
||||
user_id (InputLayer) (None, 1) 0
|
||||
________________________________________________________________________________________
|
||||
movie_id (InputLayer) (None, 1) 0
|
||||
________________________________________________________________________________________
|
||||
user_embedding (Embedding) (None, 1, 8) 1107952 user_id[0][0]
|
||||
________________________________________________________________________________________
|
||||
movie_embedding (Embedding) (None, 1, 8) 213952 movie_id[0][0]
|
||||
________________________________________________________________________________________
|
||||
concatenate (Concatenate) (None, 1, 16) 0 user_embedding[0][0]
|
||||
movie_embedding[0][0]
|
||||
________________________________________________________________________________________
|
||||
flatten (Flatten) (None, 16) 0 concatenate[0][0]
|
||||
________________________________________________________________________________________
|
||||
dense_5 (Dense) (None, 32) 544 flatten[0][0]
|
||||
________________________________________________________________________________________
|
||||
dense_6 (Dense) (None, 4) 132 dense_5[0][0]
|
||||
________________________________________________________________________________________
|
||||
prediction (Dense) (None, 1) 5 dense_6[0][0]
|
||||
========================================================================================
|
||||
Total params: 1,322,585
|
||||
Trainable params: 1,322,585
|
||||
Non-trainable params: 0
|
||||
________________________________________________________________________________________
|
||||
'''
|
||||
```
|
||||
|
||||
### 训练
|
||||
|
||||
我们将编译我们的模型,来最小化平方误差('MSE')。 我们还将绝对值误差('MAE')作为在训练期间报告的度量标准,因为它更容易解释。
|
||||
|
||||
> 需要考虑的事情:我们知道评分只能取值`{0.5,1,1.5,2,2.5,3,3.5,4,4.5,5}` - 所以为什么不将其视为 10 类的多类分类问题 ,每个可能的星级评分一个?
|
||||
|
||||
```py
|
||||
model.compile(
|
||||
# 技术说明:使用嵌入层时,我强烈建议使用
|
||||
# tf.train 中发现的优化器之一:
|
||||
# https://www.tensorflow.org/api_guides/python/train#Optimizers
|
||||
# 传入像 'adam' 或 'SGD' 这样的字符串,会加载一个 keras 优化器
|
||||
# (在 tf.keras.optimizers 下寻找)。 对于像这样的问题,它们似乎要慢得多,
|
||||
# 因为它们无法有效处理稀疏梯度更新。
|
||||
tf.train.AdamOptimizer(0.005),
|
||||
loss='MSE',
|
||||
metrics=['MAE'],
|
||||
)
|
||||
```
|
||||
|
||||
让我们训练模型:
|
||||
|
||||
> 注:我传入`df.y`而不是`df.rating`,作为我的目标变量。`y`列只是评分的“中心”版本 - 即评分列减去其在训练集上的平均值。 例如,如果训练集中的总体平均评分是 3 星,那么我们将 3 星评分翻译为 0, 5星评分为 2.0 等等,来获得`y`。 这是深度学习中的常见做法,并且往往有助于在更少的时期内获得更好的结果。 对于更多详细信息,请随意使用我在 MovieLens 数据集上执行的所有预处理来检查[这个内核](https://www.kaggle.com/colinmorris/movielens-preprocessing)。
|
||||
|
||||
```py
|
||||
history = model.fit(
|
||||
[df.userId, df.movieId],
|
||||
df.y,
|
||||
batch_size=5000,
|
||||
epochs=20,
|
||||
verbose=0,
|
||||
validation_split=.05,
|
||||
);
|
||||
```
|
||||
|
||||
为了判断我们的模型是否良好,有一个基线是有帮助的。 在下面的单元格中,我们计算了几个虚拟基线的误差:始终预测全局平均评分,以及预测每部电影的平均评分:
|
||||
|
||||
+ 训练集的平均评分:3.53 星
|
||||
+ 总是预测全局平均评分,结果为 MAE=0.84,MSE=1.10
|
||||
+ 预测每部电影的平均评分,结果为 MAE=0.73,MSE=0.88
|
||||
|
||||
|
||||
这是我们的嵌入模型的绝对误差随时间的绘图。 为了进行比较,我们的最佳基线(预测每部电影的平均评分)用虚线标出:
|
||||
|
||||

|
||||
|
||||
与基线相比,我们能够将平均误差降低超过 0.1 星(或约 15%)。不错!
|
||||
|
||||
### 示例预测
|
||||
|
||||
让我们尝试一些示例预测作为健全性检查。 我们首先从数据集中随机挑选一个特定用户。
|
||||
|
||||
```
|
||||
用户 #26556 评分了 21 个电影(平均评分为 3.7)
|
||||
```
|
||||
|
||||
| | userId | movieId | rating | title | year |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 4421455 | 26556 | 2705 | 5.0 | Airplane! | 1980 |
|
||||
| 14722970 | 26556 | 2706 | 5.0 | Airplane II: The Sequel | 1982 |
|
||||
| 7435440 | 26556 | 2286 | 4.5 | Fletch | 1985 |
|
||||
| 16621016 | 26556 | 2216 | 4.5 | History of the World: Part I | 1981 |
|
||||
| 11648630 | 26556 | 534 | 4.5 | Six Degrees of Separation | 1993 |
|
||||
| 14805184 | 26556 | 937 | 4.5 | Mr. Smith Goes to Washington | 1939 |
|
||||
| 14313285 | 26556 | 2102 | 4.5 | Strangers on a Train | 1951 |
|
||||
| 13671173 | 26556 | 2863 | 4.5 | Dr. No | 1962 |
|
||||
| 13661434 | 26556 | 913 | 4.0 | Notorious | 1946 |
|
||||
| 11938282 | 26556 | 916 | 4.0 | To Catch a Thief | 1955 |
|
||||
| 2354167 | 26556 | 3890 | 4.0 | Diamonds Are Forever | 1971 |
|
||||
| 16095891 | 26556 | 730 | 4.0 | Spy Hard | 1996 |
|
||||
| 16265128 | 26556 | 3414 | 3.5 | Network | 1976 |
|
||||
| 13050537 | 26556 | 1414 | 3.5 | Waiting for Guffman | 1996 |
|
||||
| 9891416 | 26556 | 2907 | 3.5 | Thunderball | 1965 |
|
||||
| 3496223 | 26556 | 4917 | 3.5 | Gosford Park | 2001 |
|
||||
| 1996728 | 26556 | 1861 | 3.0 | On the Waterfront | 1954 |
|
||||
| 15893218 | 26556 | 1082 | 2.5 | A Streetcar Named Desire | 1951 |
|
||||
| 13875921 | 26556 | 3445 | 2.5 | Keeping the Faith | 2000 |
|
||||
| 13163853 | 26556 | 1225 | 2.0 | The Day the Earth Stood Still | 1951 |
|
||||
| 7262983 | 26556 | 2348 | 0.5 | A Civil Action | 1998 |
|
||||
|
||||
用户 26556 给电影《空前绝后满天飞》和《空前绝后满天飞 II》打了两个完美的评分。很棒的选择! 也许他们也会喜欢《白头神探》系列 - 另一系列由 Leslie Nielsen 主演的恶搞电影。
|
||||
|
||||
我们没有那么多关于这个用户讨厌什么的证据。 我们不根据他们的少数低评分做推断,用户不喜欢什么的更好推断是,他们甚至没有评价的电影类型。 让我们再举几个电影的例子,根据用户的评分历史记录,他们似乎不太可能看过。
|
||||
|
||||
```py
|
||||
candidate_movies = movies[
|
||||
movies.title.str.contains('Naked Gun')
|
||||
| (movies.title == 'The Sisterhood of the Traveling Pants')
|
||||
| (movies.title == 'Lilo & Stitch')
|
||||
].copy()
|
||||
|
||||
preds = model.predict([
|
||||
[uid] * len(candidate_movies), # 用户 ID
|
||||
candidate_movies.index, # 电影 ID
|
||||
])
|
||||
# 注意:记住我们在 'y' 上训练,这是评分列的中心为 0 的版本。
|
||||
# 要将我们模型的输出值转换为 [0.5, 5] 原始的星级评分范围,
|
||||
# 我们需要通过添加均值来对值“去中心化”
|
||||
row = df.iloc[0] # rating 和 y 之间的差对于所有行都是相同的,所以我们可以使用第一行
|
||||
y_delta = row.rating - row.y
|
||||
candidate_movies['predicted_rating'] = preds + y_delta
|
||||
# 添加一列,带有我们的预测评分(对于此用户)
|
||||
# 和电影对于数据集中所有用户的总体平均评分之间的差
|
||||
candidate_movies['delta'] = candidate_movies['predicted_rating'] - candidate_movies['mean_rating']
|
||||
candidate_movies.sort_values(by='delta', ascending=False)
|
||||
```
|
||||
|
||||
| | title | year | mean_rating | n_ratings | predicted_rating | delta |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| movieId | | | | | | |
|
||||
| 366 | Naked Gun 33 1/3: The Final Insult | 1994 | 2.954226 | 13534.0 | 3.816926 | 0.862699 |
|
||||
| 3776 | The Naked Gun 2 1/2: The Smell of Fear | 1991 | 3.132616 | 4415.0 | 3.946124 | 0.813508 |
|
||||
| 3775 | The Naked Gun: From the Files of Police Squad! | 1988 | 3.580381 | 6973.0 | 4.236419 | 0.656037 |
|
||||
| 5347 | Lilo & Stitch | 2002 | 3.489323 | 4402.0 | 3.971318 | 0.481995 |
|
||||
| 10138 | The Sisterhood of the Traveling Pants | 2005 | 3.369987 | 773.0 | 2.041227 | -1.328760 |
|
||||
|
||||
看起来很合理! 对于《白头神探》系列中的每部电影,我们对此用户的预测评分,大约比数据集中平均评分高一星,而我们的“out of left field”使它们的预测评分低于平均值。
|
||||
|
||||
### 你的回合
|
||||
|
||||
前往[练习笔记本](https://www.kaggle.com/kernels/fork/1598432),进行嵌入层的实践练习。
|
||||
|
||||
## 二、用于推荐问题的矩阵分解
|
||||
|
||||
在上一课中,我们训练了一个模型来预测在 MovieLens 数据集中,用户给电影的评分。 提醒一下,模型看起来像这样:
|
||||
|
||||

|
||||
|
||||
我们为电影和用户查找嵌入向量,将它们连接在一起。 然后我们添加一些隐层。 最后,这些在一个输出节点汇集在一起来预测评分。
|
||||
|
||||
在本节课中,我将展示一个更简单的架构,来解决同样的问题:矩阵分解。 更简单可以是一件非常好的事情! 有时,简单的模型会快速收敛到适当的解决方案,一个更复杂的模型可能会过拟合或无法收敛。
|
||||
|
||||
这是我们的矩阵分解模型的样子:
|
||||
|
||||

|
||||
|
||||
### 点积
|
||||
|
||||
让我们回顾一下数学。 如果你是线性代数专家,请跳过本节。
|
||||
|
||||
两个长度为`n`的向量`a`和`b`的点积定义为:
|
||||
|
||||

|
||||
|
||||
结果是单个标量(不是向量)。
|
||||
|
||||
点积仅为相同长度的矢量而定义。 这意味着我们需要为电影嵌入和用户嵌入使用相同的大小。
|
||||
|
||||
例如,假设我们已经训练了大小为 4 的嵌入,并且电影 Twister 由向量表示:
|
||||
|
||||
```
|
||||
m_Twister=[ 1.0 −0.5 0.3 −0.1 ]
|
||||
```
|
||||
|
||||
用户 Stanley 表示为:
|
||||
|
||||
```
|
||||
u_Stanley=[ −0.2 1.5 −0.1 0.9 ]
|
||||
```
|
||||
|
||||
我们认为 Stanley 会给 Twister 什么评分? 我们可以将模型的输出计算为:
|
||||
|
||||
```
|
||||
m_Twister · u_Stanley
|
||||
= (1.0·−0.2)+(−0.5·1.5)+(0.3·−0.1)+(−0.1·0.9)
|
||||
= −1.07
|
||||
```
|
||||
|
||||
因为我们正在在评分列的中心版本上训练,所以我们的模型输出的比例为 0 等于训练集中的总体平均评分(约 3.5)。 因此我们预测 Stanley 将给 Twister `3.5+(−1.07)=2.43`星。
|
||||
|
||||
### 为什么
|
||||
|
||||
有一个直观的解释,支持了以这种方式组合我们的嵌入向量的决定。 假设我们的电影嵌入空间的维度对应于以下变化的轴:
|
||||
|
||||
维度 1:多么令人激动?
|
||||
维度 2:多么浪漫?
|
||||
维度 3:目标受众有多成熟?
|
||||
维度 4:多么好笑?
|
||||
|
||||
因此,Twister 是一部令人激动的灾难电影,`m1`的正值为 1.0。
|
||||
|
||||
简单来说,`u1`告诉我们“这个用户对动作片的看法如何?”。 他们喜欢它吗?讨厌它?还是不喜欢也不讨厌?
|
||||
|
||||
Stanley 的向量告诉,我们他是浪漫和喜剧的忠实粉丝,并且略微不喜欢动作和成熟的内容。 如果我们给他一部类似于最后一部的电影,除了它有很多浪漫元素,会怎么样?
|
||||
|
||||
```
|
||||
m_Titanic=[ 1.0 1.1 0.3 −0.1 ]
|
||||
```
|
||||
|
||||
不难预测这会如何影响我们的评分输出。 我们给 Stanley 更多他喜欢的东西,所以他的预测评分会增加。
|
||||
|
||||
```
|
||||
predicted_rating(Stanley,Titanic)
|
||||
= m_Titanic·u_Stanley+3.5
|
||||
=(1.0·−0.2)+(1.1·1.5)+(0.3·−0.1)+(−0.1·0.9)+3.5
|
||||
=4.83 stars
|
||||
```
|
||||
|
||||
> 注:在实践中,我们的电影嵌入的维度的含义不会那么明确,但我们的电影嵌入空间和用户嵌入空间的含义从根本上联系在一起,这仍然是正确的:`ui`总是代表“这个用户多么喜欢某个电影,其质量由`mi`代表?“ (希望这也提供了一些直觉,为什么电影嵌入空间和用户嵌入空间在这个技巧中必须大小相同。)
|
||||
|
||||
### 实现它
|
||||
|
||||
创建此模型的代码,类似于我们在上一课中编写的代码,除了我使用点积层来组合用户和电影嵌入层的输出(而不是连接它们,并输入到密集层)。
|
||||
|
||||
```py
|
||||
movie_embedding_size = user_embedding_size = 8
|
||||
|
||||
# 每个实例由两个输入组成:单个用户 ID 和单个电影 ID
|
||||
user_id_input = keras.Input(shape=(1,), name='user_id')
|
||||
movie_id_input = keras.Input(shape=(1,), name='movie_id')
|
||||
user_embedded = keras.layers.Embedding(df.userId.max()+1, user_embedding_size,
|
||||
input_length=1, name='user_embedding')(user_id_input)
|
||||
movie_embedded = keras.layers.Embedding(df.movieId.max()+1, movie_embedding_size,
|
||||
input_length=1, name='movie_embedding')(movie_id_input)
|
||||
|
||||
dotted = keras.layers.Dot(2)([user_embedded, movie_embedded])
|
||||
out = keras.layers.Flatten()(dotted)
|
||||
|
||||
model = keras.Model(
|
||||
inputs = [user_id_input, movie_id_input],
|
||||
outputs = out,
|
||||
)
|
||||
model.compile(
|
||||
tf.train.AdamOptimizer(0.001),
|
||||
loss='MSE',
|
||||
metrics=['MAE'],
|
||||
)
|
||||
model.summary(line_length=88)
|
||||
'''
|
||||
________________________________________________________________________________________
|
||||
Layer (type) Output Shape Param # Connected to
|
||||
========================================================================================
|
||||
user_id (InputLayer) (None, 1) 0
|
||||
________________________________________________________________________________________
|
||||
movie_id (InputLayer) (None, 1) 0
|
||||
________________________________________________________________________________________
|
||||
user_embedding (Embedding) (None, 1, 8) 1107952 user_id[0][0]
|
||||
________________________________________________________________________________________
|
||||
movie_embedding (Embedding) (None, 1, 8) 213952 movie_id[0][0]
|
||||
________________________________________________________________________________________
|
||||
dot (Dot) (None, 1, 1) 0 user_embedding[0][0]
|
||||
movie_embedding[0][0]
|
||||
________________________________________________________________________________________
|
||||
flatten (Flatten) (None, 1) 0 dot[0][0]
|
||||
========================================================================================
|
||||
Total params: 1,321,904
|
||||
Trainable params: 1,321,904
|
||||
Non-trainable params: 0
|
||||
________________________________________________________________________________________
|
||||
'''
|
||||
```
|
||||
|
||||
让我们训练它。
|
||||
|
||||
```py
|
||||
history = model.fit(
|
||||
[df.userId, df.movieId],
|
||||
df.y,
|
||||
batch_size=5000,
|
||||
epochs=20,
|
||||
verbose=0,
|
||||
validation_split=.05,
|
||||
);
|
||||
```
|
||||
|
||||
让我们将这个模型随时间的误差,与我们在上一课中训练的深度神经网络进行比较:
|
||||
|
||||

|
||||
|
||||
我们新的,更简单的模型(蓝色)看起来非常好。
|
||||
|
||||
然而,即使我们的嵌入相当小,两种模型都会产生一些明显的过拟合。 也就是说,训练集上的误差 - 实线 - 明显好于看不见的数据。 我们将在练习中尽快解决这个问题。
|
||||
|
||||
### 你的回合
|
||||
|
||||
前往[练习笔记本](https://www.kaggle.com/kernels/fork/1598589),进行矩阵分解的实践练习。
|
||||
|
||||
## 三、使用 Gensim 探索嵌入
|
||||
|
||||
早些时候,我们训练了一个模型,使用一个网络,它带有为每个电影和用户学习的嵌入,来预测用户为电影提供的评分。 嵌入是强大的!但他们实际如何工作?
|
||||
|
||||
以前,我说嵌入捕获了它们所代表的对象的“含义”,并发现了有用的潜在结构。 让我们来测试吧!
|
||||
|
||||
### 查询嵌入
|
||||
|
||||
让我们加载我们之前训练过的模型,这样我们就可以研究它学到的嵌入权重。
|
||||
|
||||
```py
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from matplotlib import pyplot as plt
|
||||
import tensorflow as tf
|
||||
from tensorflow import keras
|
||||
|
||||
input_dir = '../input/movielens-preprocessing'
|
||||
model_dir = '../input/movielens-spiffy-model'
|
||||
model_path = os.path.join(model_dir, 'movie_svd_model_32.h5')
|
||||
model = keras.models.load_model(model_path)
|
||||
```
|
||||
|
||||
嵌入权重是模型内部的一部分,因此我们必须进行一些挖掘才能访问它们。 我们将获取负责嵌入电影的层,并使用`get_weights()`方法获取其学习的权重。
|
||||
|
||||
```py
|
||||
emb_layer = model.get_layer('movie_embedding')
|
||||
(w,) = emb_layer.get_weights()
|
||||
w.shape
|
||||
# (26744, 32)
|
||||
```
|
||||
|
||||
对于那么多电影,我们的权重矩阵有 26,744 行。 每行是 32 个数字 - 我们的电影嵌入的大小。
|
||||
|
||||
我们来看一个示例电影向量:
|
||||
|
||||
```py
|
||||
w[0]
|
||||
'''
|
||||
array([-0.08716497, -0.25286013, -0.52679837, -0.2602235 , -0.4349191 ,
|
||||
-0.48805636, -0.30346015, -0.1416321 , 0.08305884, -0.17578898,
|
||||
-0.36220485, 0.14578693, 0.37118354, -0.02961254, -0.063666 ,
|
||||
-0.5223456 , 0.0526049 , 0.47991064, -0.19034313, -0.3271599 ,
|
||||
0.32792446, -0.3794548 , -0.55778086, -0.42602876, 0.14532137,
|
||||
0.21002969, -0.32203963, -0.46950188, -0.22500233, -0.08298543,
|
||||
-0.00373308, -0.3885791 ], dtype=float32)
|
||||
'''
|
||||
```
|
||||
|
||||
这是什么电影的嵌入? 让我们加载我们的电影元数据的数据帧。
|
||||
|
||||
```py
|
||||
movies_path = os.path.join(input_dir, 'movie.csv')
|
||||
movies_df = pd.read_csv(movies_path, index_col=0)
|
||||
movies_df.head()
|
||||
```
|
||||
|
||||
|
||||
| | movieId | title | genres | key | year | n_ratings | mean_rating |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| 0 | 0 | Toy Story | Adventure|Animation|Children|Comedy|Fantasy | Toy Story | 1995 | 49695 | 3.921240 |
|
||||
| 1 | 1 | Jumanji | Adventure|Children|Fantasy | Jumanji | 1995 | 22243 | 3.211977 |
|
||||
| 2 | 2 | Grumpier Old Men | Comedy|Romance | Grumpier Old Men | 1995 | 12735 | 3.151040 |
|
||||
| 3 | 3 | Waiting to Exhale | Comedy|Drama|Romance | Waiting to Exhale | 1995 | 2756 | 2.861393 |
|
||||
| 4 | 4 | Father of the Bride Part II | Comedy | Father of the Bride Part II | 1995 | 12161 | 3.064592 |
|
||||
|
||||
当然,这是《玩具总动员》! 我应该在任何地方认出这个向量。
|
||||
|
||||
好吧,我很滑稽。此时很难利用这些向量。 我们从未告诉模型如何使用任何特定嵌入维度。 我们只让它学习它认为有用的任何表示。
|
||||
|
||||
那么我们如何检查这些表示是否合理且连贯?
|
||||
|
||||
### 向量相似度
|
||||
|
||||
测试它的一种简单方法是,查看嵌入空间中电影对有多么接近或远离。 嵌入可以被认为是智能的距离度量。 如果我们的嵌入矩阵是良好的,它应该将类似的电影(如《玩具总动员》和《怪物史莱克》)映射到类似的向量。
|
||||
|
||||
```py
|
||||
i_toy_story = 0
|
||||
i_shrek = movies_df.loc[
|
||||
movies_df.title == 'Shrek',
|
||||
'movieId'
|
||||
].iloc[0]
|
||||
|
||||
toy_story_vec = w[i_toy_story]
|
||||
shrek_vec = w[i_shrek]
|
||||
|
||||
print(
|
||||
toy_story_vec,
|
||||
shrek_vec,
|
||||
sep='\n',
|
||||
)
|
||||
'''
|
||||
[-0.08716497 -0.25286013 -0.52679837 -0.2602235 -0.4349191 -0.48805636
|
||||
-0.30346015 -0.1416321 0.08305884 -0.17578898 -0.36220485 0.14578693
|
||||
0.37118354 -0.02961254 -0.063666 -0.5223456 0.0526049 0.47991064
|
||||
-0.19034313 -0.3271599 0.32792446 -0.3794548 -0.55778086 -0.42602876
|
||||
0.14532137 0.21002969 -0.32203963 -0.46950188 -0.22500233 -0.08298543
|
||||
-0.00373308 -0.3885791 ]
|
||||
[ 0.0570179 0.5991162 -0.71662885 0.22245468 -0.40536046 -0.33602375
|
||||
-0.24281627 0.08997302 0.03362623 -0.12569055 -0.2764452 -0.12710975
|
||||
0.48197436 0.2724923 0.01551001 -0.20889504 -0.04863157 0.39106563
|
||||
-0.24811408 -0.05642252 0.24475795 -0.53363544 -0.2281187 -0.17529544
|
||||
0.21050802 -0.37807122 0.03861505 -0.27024794 -0.24332719 -0.17732081
|
||||
0.07961234 -0.39079434]
|
||||
'''
|
||||
```
|
||||
|
||||
逐个维度地比较,这些看起来大致相似。 如果我们想为它们的相似度分配一个数字,我们可以计算这两个向量之间的欧氏距离。 (这是我们传统的“乌鸦飞过的”两点之间的距离的概念。容易在 1,2 或 3 维上进行研究。在数学上,我们也可以将它扩展到 32 维,虽然需要好运来可视化它。)
|
||||
|
||||
```py
|
||||
from scipy.spatial import distance
|
||||
|
||||
distance.euclidean(toy_story_vec, shrek_vec)
|
||||
# 1.4916094541549683
|
||||
```
|
||||
|
||||
这与我们认为非常不同的一对电影相比如何?
|
||||
|
||||
```py
|
||||
i_exorcist = movies_df.loc[
|
||||
movies_df.title == 'The Exorcist',
|
||||
'movieId'
|
||||
].iloc[0]
|
||||
|
||||
exorcist_vec = w[i_exorcist]
|
||||
|
||||
distance.euclidean(toy_story_vec, exorcist_vec)
|
||||
# 2.356588363647461
|
||||
```
|
||||
|
||||
更远了,和我们期待的一样。
|
||||
|
||||
### 余弦距离
|
||||
|
||||
如果你看看[`scipy.spatial`模块的文档](https://docs.scipy.org/doc/scipy-0.14.0/reference/spatial.distance.html),你会发现人们用于不同任务的距离,实际上有很多不同的衡量标准。
|
||||
|
||||
在判断嵌入的相似性时,使用[余弦相似性](https://en.wikipedia.org/wiki/Cosine_similarity)更为常见。
|
||||
|
||||
简而言之,两个向量的余弦相似度范围从 -1 到 1,并且是向量之间的角度的函数。 如果两个向量指向同一方向,则它们的余弦相似度为 1。如果它们指向相反的方向,它为 -1。 如果它们是正交的(即成直角),则它们的余弦相似度为 0。
|
||||
|
||||
余弦距离定义为 1 减去余弦相似度(因此范围从 0 到 2)。
|
||||
|
||||
让我们计算电影向量之间的几个余弦距离:
|
||||
|
||||
```py
|
||||
print(
|
||||
distance.cosine(toy_story_vec, shrek_vec),
|
||||
distance.cosine(toy_story_vec, exorcist_vec),
|
||||
sep='\n'
|
||||
)
|
||||
'''
|
||||
0.3593705892562866
|
||||
0.811933159828186
|
||||
'''
|
||||
```
|
||||
|
||||
> 注:为什么在使用嵌入时常用余弦距离? 与许多深度学习技巧一样,简短的答案是“凭经验,它能用”。 在即将进行的练习中,你将进行一些实践调查,更深入地探讨这个问题。
|
||||
|
||||
哪部电影与《玩具总动员》最相似? 在嵌入空间中哪些电影落在 Psycho 和 Scream 之间? 我们可以编写一堆代码来解决这样的问题,但这样做非常繁琐。 幸运的是,已经有一个库可以完成这类工作:Gensim。
|
||||
|
||||
## 使用 Gensim 探索嵌入
|
||||
|
||||
我将使用我们的模型的电影嵌入和相应电影的标题,来实例化`WordEmbeddingsKeyedVectors`。
|
||||
|
||||
> 注:你可能会注意到,Gensim 的文档及其许多类和方法名称都指的是词嵌入。 虽然库最常用于文本领域,但我们可以使用它来探索任何类型的嵌入。
|
||||
|
||||
```py
|
||||
from gensim.models.keyedvectors import WordEmbeddingsKeyedVectors
|
||||
|
||||
# 将数据集中的电影限制为至少具有这么多评分
|
||||
threshold = 100
|
||||
mainstream_movies = movies_df[movies_df.n_ratings >= threshold].reset_index(drop=True)
|
||||
|
||||
movie_embedding_size = w.shape[1]
|
||||
kv = WordEmbeddingsKeyedVectors(movie_embedding_size)
|
||||
kv.add(
|
||||
mainstream_movies['key'].values,
|
||||
w[mainstream_movies.movieId]
|
||||
)
|
||||
```
|
||||
|
||||
好的,哪个电影与《玩具总动员》最相似?
|
||||
|
||||
```py
|
||||
kv.most_similar('Toy Story')
|
||||
'''
|
||||
/opt/conda/lib/python3.6/site-packages/gensim/matutils.py:737: FutureWarning: Conversion of the second argument of issubdtype from `int` to `np.signedinteger` is deprecated. In future, it will be treated as `np.int64 == np.dtype(int).type`.
|
||||
if np.issubdtype(vec.dtype, np.int):
|
||||
[('Toy Story 2', 0.9583659172058105),
|
||||
('Toy Story 3', 0.9159570932388306),
|
||||
('Finding Nemo', 0.882755696773529),
|
||||
('Monsters, Inc.', 0.8684015870094299),
|
||||
("A Bug's Life", 0.8322919607162476),
|
||||
('The Incredibles', 0.8271597623825073),
|
||||
('Ratatouille', 0.8149864673614502),
|
||||
('Up', 0.802034318447113),
|
||||
('WALL·E', 0.7794805765151978),
|
||||
('The Iron Giant', 0.7664535641670227)]
|
||||
'''
|
||||
```
|
||||
|
||||
哇,这些都很棒! 《玩具总动员 2》是与玩具总动员最相似的电影,这是完全合理的。 其余大多数都是具有类似计算机动画风格的动画儿童电影。
|
||||
|
||||
所以它学到了关于三维动画儿童电影的一些东西,但也许这只是一个侥幸。 让我们来看看几个不同类型的电影的最近邻居:
|
||||
|
||||
```
|
||||
/opt/conda/lib/python3.6/site-packages/gensim/matutils.py:737: FutureWarning: Conversion of the second argument of issubdtype from `int` to `np.signedinteger` is deprecated. In future, it will be treated as `np.int64 == np.dtype(int).type`.
|
||||
if np.issubdtype(vec.dtype, np.int):
|
||||
```
|
||||
|
||||

|
||||
|
||||
小众的性爱剧,风骚的半吊子喜剧,老派音乐剧,超级英雄电影......我们的嵌入能够支持各种各样的电影类型!
|
||||
|
||||
### 语义向量数学
|
||||
|
||||
`most_similar`方法接受可选的第二个参数`negative`。 如果我们调用`kv.most_similar(a, b)`,那么它将找到最接近`a-b`的向量,而不是找到最接近`a`的向量。
|
||||
|
||||
你为什么想这么做? 事实证明,对嵌入向量进行加法和减法通常会产生令人惊讶的有意义的结果。 例如,你将如何填写以下等式?
|
||||
|
||||
```
|
||||
Scream = Psycho + ________
|
||||
```
|
||||
|
||||
Scream 和 Psycho 的相似之处在于它们是恐怖片和惊悚片之间的某个地方的暴力恐怖电影。 最大的区别是 Scream 有喜剧元素。 因此,如果你将 Psycho 与喜剧结合起来,我会说 Scream 就是你所得到的。
|
||||
|
||||
但我们实际上可以通过向量数学(在重新排列之后)让 Gensim 为我们填补空白:
|
||||
|
||||
```
|
||||
________ = Scream - Psycho
|
||||
```
|
||||
|
||||
```py
|
||||
kv.most_similar(
|
||||
positive = ['Scream'],
|
||||
negative = ['Psycho (1960)']
|
||||
)
|
||||
'''
|
||||
/opt/conda/lib/python3.6/site-packages/gensim/matutils.py:737: FutureWarning: Conversion of the second argument of issubdtype from `int` to `np.signedinteger` is deprecated. In future, it will be treated as `np.int64 == np.dtype(int).type`.
|
||||
if np.issubdtype(vec.dtype, np.int):
|
||||
[('Scream 3', 0.6535503268241882),
|
||||
('Scream 2', 0.6417772769927979),
|
||||
('Piranha (Piranha 3D)', 0.6411199569702148),
|
||||
('Freddy vs. Jason', 0.6275623440742493),
|
||||
('Final Destination 5', 0.6264907121658325),
|
||||
('Booty Call', 0.6207411289215088),
|
||||
("Charlie's Angels", 0.6146069765090942),
|
||||
('Mortal Kombat', 0.6145076155662537),
|
||||
('Deuce Bigalow: Male Gigolo', 0.6140967607498169),
|
||||
('Final Destination 2', 0.612423300743103)]
|
||||
'''
|
||||
```
|
||||
|
||||
如果你熟悉这些电影,你会发现,从 Psycho 到 Scream 的缺失成分是喜剧(也是 90 年代后期的青少年电影)。
|
||||
|
||||
### 类比解决
|
||||
|
||||
用于进入美国大学和学院的 SAT 考试提出了类似的问题:
|
||||
|
||||
```
|
||||
shower : deluge :: _____ : stare
|
||||
```
|
||||
|
||||
(意思是“shower”(淋浴)对于“deluge”(洪水),相当于“_____”对于“stare”(凝视))
|
||||
|
||||
为了解决这个问题,我们找到了“deluge”和“shower”之间的关系,并将其应用于“stare”。 “shower”是一种温和的“deluge”形式。 什么是温和的“stare”的形式? 这里一个好的答案是“glance”(一瞥)或“look”(看)。
|
||||
|
||||
令人惊讶的是,这种方法很有效,但人们发现这些通常可以通过单词嵌入的简单向量数学来有效地解决。 我们可以通过嵌入来解决电影类比问题吗? 我们试试吧。这样如何:
|
||||
|
||||
```
|
||||
Brave : Cars 2 :: Pocahontas : _____
|
||||
```
|
||||
|
||||
答案不明确。 一种解释是,《勇敢传说》(Brave)就像《赛车总动员 2》(Cars 2),除了后者主要针对男孩,而前者可能对女孩更具吸引力,因为它是女性主角。 所以也许答案应该像《风中奇缘》(Pocahontas)一样,90 年代中期的传统动画儿童电影,但更像是一部“男孩电影”。《大力士》?《狮子王》?
|
||||
|
||||
让我们问一下他们的想法。
|
||||
|
||||
在向量数学方面,我们可以将其构建为......
|
||||
|
||||
```
|
||||
Cars 2 = Brave + X
|
||||
_____ = Pocahontas + X
|
||||
```
|
||||
|
||||
重新排列之后,我们得到:
|
||||
|
||||
```
|
||||
____ = Pocahontas + (Cars 2 - Brave)
|
||||
```
|
||||
|
||||
我们可以通过将两部电影(《风中奇缘》和《赛车总动员 2》)传递给`most_similar`的`positive`,将《勇敢传说》作为`negative`参数,来解决这个问题:
|
||||
|
||||
```py
|
||||
kv.most_similar(
|
||||
['Pocahontas', 'Cars 2'],
|
||||
negative = ['Brave']
|
||||
)
|
||||
'''
|
||||
/opt/conda/lib/python3.6/site-packages/gensim/matutils.py:737: FutureWarning: Conversion of the second argument of issubdtype from `int` to `np.signedinteger` is deprecated. In future, it will be treated as `np.int64 == np.dtype(int).type`.
|
||||
if np.issubdtype(vec.dtype, np.int):
|
||||
[("A Kid in King Arthur's Court", 0.8660464882850647),
|
||||
('Land Before Time III: The Time of the Great Giving', 0.8655920624732971),
|
||||
('Free Willy 2: The Adventure Home', 0.8606677651405334),
|
||||
('3 Ninjas Knuckle Up', 0.8496973514556885),
|
||||
('3 Ninjas Kick Back', 0.8479241132736206),
|
||||
('The Return of Jafar', 0.8474882245063782),
|
||||
("Beethoven's 2nd", 0.8443870544433594),
|
||||
('Air Bud: Golden Receiver', 0.84358811378479),
|
||||
('Meet the Deedles', 0.8370730876922607),
|
||||
('All Dogs Go to Heaven 2', 0.8368842601776123)]
|
||||
'''
|
||||
```
|
||||
|
||||
这与我们的预测无关:4 部最接近的电影确实是 90 年代的儿童动画电影。 在那之后,结果有点令人困惑。
|
||||
|
||||
我们的模型是错的,还是我们是错的? 在《赛车总动员 2》和《勇敢传说》之间,我们未能解释的另一个区别是前者是续集,而后者则不是。 我们的结果中有 7/10 也是续集。 这告诉我们关于我们学习的嵌入的一些有趣内容(最终,关于预测电影偏好的问题)。 “Sequelness”是我们模型的一个重要特性 - 这表明我们数据中的一些变化,是因为有些人倾向于比其他人更喜欢续集。
|
||||
|
||||
### 你的回合
|
||||
|
||||
前往[练习笔记本](https://www.kaggle.com/kernels/fork/1598893),进行一些动手实践,使用 gensim 探索嵌入。
|
||||
|
||||
## 四、将 t-SNE 用于可视化
|
||||
|
||||
在上一课中,我们查看了我们学习的电影嵌入的一些示例,测量了电影对之间的距离,查找了与某些电影最相似的电影,并且通过向量数学组合了电影语义。 这些是调试嵌入模型或理解嵌入模型的好方法。 但它也非常耗时。
|
||||
|
||||
在本课程中,你将学习如何使用 t-SNE 算法可视化嵌入。 这是一种很好的廉价技术,用于理解嵌入的本质。
|
||||
|
||||
### t-SNE
|
||||
|
||||
可视化 1 维或 2 维的数据很容易 - 但目前尚不清楚如何可视化 8 维或 32 维的嵌入。 t-SNE 是一种降维算法,通常用于可视化。 它学习从一组高维向量到较小维数(通常为 2)的空间映射,这有望很好地表示高维空间。
|
||||
|
||||
是什么让映射成为“良好的表示”? 简而言之,t-SNE 试图确保如果高维向量`u`和`v`靠近在一起,则`map(u)`和`map(v)`在 2d 映射空间中靠近在一起。
|
||||
|
||||
### 代码
|
||||
|
||||
首先,我们将加载我们的预训练嵌入,就像之前一样。
|
||||
|
||||
```py
|
||||
%matplotlib inline
|
||||
import random
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from matplotlib import pyplot as plt
|
||||
import tensorflow as tf
|
||||
from tensorflow import keras
|
||||
|
||||
input_dir = '../input/movielens-preprocessing'
|
||||
model_dir = '../input/movielens-spiffy-model'
|
||||
model_path = os.path.join(model_dir, 'movie_svd_model_32.h5')
|
||||
|
||||
model = keras.models.load_model(model_path)
|
||||
emb_layer = model.get_layer('movie_embedding')
|
||||
(w,) = emb_layer.get_weights()
|
||||
|
||||
movies_path = os.path.join(input_dir, 'movie.csv')
|
||||
movies_df = pd.read_csv(movies_path, index_col=0)
|
||||
```
|
||||
|
||||
正如我们在前面的课程中看到的那样,我们的数据集中有很多不起眼的电影,评分很少(有时只有一个)。 我们对这些电影知之甚少,因为它们的嵌入效果和随机一样。 我们可以通过仅仅选择满足一定流行度阈值的电影,来弄清楚我们的可视化。
|
||||
|
||||
```py
|
||||
threshold = 100
|
||||
mainstream_movies = movies_df[movies_df.n_ratings >= threshold].reset_index(drop=True)
|
||||
print("Went from {} to {} movies after applying threshold".format(
|
||||
len(movies_df), len(mainstream_movies),
|
||||
))
|
||||
w_full = w
|
||||
w = w[mainstream_movies.movieId]
|
||||
df = mainstream_movies
|
||||
# 在应用阈值后,电影从 26744 变为 8546 部
|
||||
```
|
||||
|
||||
我们将使用 scikit-learn 的 t-SNE 实现。
|
||||
|
||||
我提到 t-SNE 在特征空间中试图保持实体之间的“接近度”。 我们在之前的课程中看到,有许多竞争性的距离概念。 默认情况下,t-SNE 使用欧氏距离。 但是因为已知余弦距离适用于嵌入,我们将在创建模型时传递`metric ="cosine"`。
|
||||
|
||||
```py
|
||||
from sklearn.manifold import TSNE
|
||||
|
||||
# 1,000 次迭代的默认值可以得到很好的结果,
|
||||
# 但是我的训练时间更长,只是为了一些微小的改进。
|
||||
# 注意:这需要近一个小时!
|
||||
tsne = TSNE(random_state=1, n_iter=15000, metric="cosine")
|
||||
|
||||
embs = tsne.fit_transform(w)
|
||||
# 为方便起见,添加到数据帧
|
||||
df['x'] = embs[:, 0]
|
||||
df['y'] = embs[:, 1]
|
||||
```
|
||||
|
||||
这是我们将电影映射到的二维向量样本:
|
||||
|
||||
```py
|
||||
embs[:5]
|
||||
'''
|
||||
array([[ -93.78184 , 74.296936 ],
|
||||
[ -78.09159 , -107.294334 ],
|
||||
[ 27.506392 , -73.33844 ],
|
||||
[ -7.8512335, -82.217896 ],
|
||||
[ -10.345706 , -71.288704 ]], dtype=float32)
|
||||
'''
|
||||
```
|
||||
|
||||
这种降维的全部意义在于可视化,所以让我们使用 matplotlib 绘制我们电影的散点图,使用我们新的二维映射。
|
||||
|
||||
```py
|
||||
FS = (10, 8)
|
||||
fig, ax = plt.subplots(figsize=FS)
|
||||
# 使点变得半透明,以便我们可以直观地识别具有高密度重叠点的区域
|
||||
ax.scatter(df.x, df.y, alpha=.1);
|
||||
```
|
||||
|
||||

|
||||
|
||||
### 它有效嘛
|
||||
|
||||
单凭形状很难判断。 良好的理智检查是识别,我们强烈认为应该靠近的一些电影分组,看看它们是否在二维空间中接近。
|
||||
|
||||
例如,所有的哈利波特电影都应该互相接近,对吧?
|
||||
|
||||
```py
|
||||
# 这个和其他几个辅助函数在上面的代码单元中定义。
|
||||
# 如果你对它们的实现方式感到好奇,请点击上面的“代码”按钮。
|
||||
plot_by_title_pattern('Harry Potter', figsize=(15, 9), bg_alpha=.05, text=False);
|
||||
```
|
||||
|
||||

|
||||
|
||||
上面的绘图中,8 个哈利波特电影中的每一个都有一个绿点 - 但它们是如此接近,它们无法在这个刻度上区分。 是个好的标志!
|
||||
|
||||
让我们放大一下,仔细看看。
|
||||
|
||||
```py
|
||||
plot_region_around('Harry Potter and the Order of the Phoenix', 4);
|
||||
```
|
||||
|
||||

|
||||
|
||||
哈利波特的电影不仅紧密聚集在一起,而且大致按发布顺序排列!
|
||||
|
||||
### 局部和全局结构
|
||||
|
||||
t-SNE 的一个关键特性使它非常适合可视化,它擅长在多个尺度上捕获簇。 我们已经看到,我们的映射成功捕获了小而紧凑的局部结构。 那些包含更多松散的相关电影的大型结构呢?
|
||||
|
||||
我们在上面已经看到了这方面的一个小例子:与哈利波特电影最接近的邻居是饥饿游戏系列的电影 - 另一组基于一系列青年幻想小说的电影。这说得通!
|
||||
|
||||
小众流派如何? 纪录片落在哪里?
|
||||
|
||||
```py
|
||||
docs = df[ (df.genres == 'Documentary') ]
|
||||
plot_with_annotations(docs.index, text=False, alpha=.4, figsize=(15, 8));
|
||||
```
|
||||
|
||||

|
||||
|
||||
太好了! 它不是一个紧密的簇,但这里肯定有较强的规律。
|
||||
|
||||
并且重申一下:我们从未真正将类型展示给模型作为特征。 它无法读取标题,并看到《哈利波特和魔法石》和《哈利波特和密室》属于同一系列。 它设法获取这些潜在的模式并将它们合并到嵌入空间中,只需看到数据点,例如“用户 5299 给电影 806 评分为 4.5”。 非常好!
|
||||
|
||||
这是另一个稍微复杂的类型实验:可视化所有电影,其类型是`{喜剧,戏剧,浪漫}`的一部分(即喜剧,戏剧,浪漫,戏剧,浪漫剧,romcoms 和......我猜是“dromcoms”?)
|
||||
|
||||

|
||||
|
||||
这是最大规模的结构的一个很棒的例子。 戏剧主要在上半部分,而喜剧主要在另一半(浪漫片的分布更加分散)。
|
||||
|
||||
### 你的回合
|
||||
|
||||
前往[练习笔记本](https://www.kaggle.com/kernels/fork/1599029)进行一些实践练习,使用 t-SNE 可视化嵌入。
|
||||
|
||||
## 扩展阅读
|
||||
|
||||
我们使用 t-SNE 模型的开箱即用的默认参数取得了良好的效果,但根据你的数据特征,你可能不会那么幸运。
|
||||
|
||||
t-SNE 不是简单的闭式数学运算。 你正在训练模型,使用随机梯度下降来最小化一些非凸损失函数。 可能需要一段时间,需要一点折腾。 你甚至可以在使用相同参数训练的两个 t-SNE 模型之间看到非常不同的结果(如果你想要可重复性,则设置固定的`random_state`)。
|
||||
|
||||
如果你在尝试训练 t-SNE 模型时得到的结果不令人满意,或者你只是想了解更多数学基础和实现,那么下面的链接会提供一些你可能会觉得有用的信息。
|
||||
|
||||
+ 如果你对 t-SNE 的更深入的数学细节感兴趣,我强烈建议你查看 [Laurens van der Maaten 和 Geoff Hinton 向世界介绍 t-SNE 的原始论文](http://www.jmlr.org/papers/volume9/vandermaaten08a/vandermaaten08a.pdf)。
|
||||
+ [如何使用 t-SNE](https://distill.pub/2016/misread-tsne/) 有效地展示了一些令人难以置信的实时交互式示例,允许你将 t-SNE 应用于各种合成数据集并实时观察训练,并看到改变参数的效果,例如 perplexity。
|
||||
+ [sklearn TSNE 文档](http://scikit-learn.org/stable/modules/generated/sklearn.manifold.TSNE.html)提供了每个参数含义的详细信息,以及一些设置它们的提示。
|
||||
+ 另请参阅:[scikit-learn 的 t-SNE 用户指南](http://scikit-learn.org/stable/modules/manifold.html#t-sne)
|
||||
+ [t-SNE FAQ](https://lvdmaaten.github.io/tsne/#faq) 由 Laurens van der Maaten 撰写
|
After Width: | Height: | Size: 324 KiB |
After Width: | Height: | Size: 231 KiB |
After Width: | Height: | Size: 483 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 110 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 41 KiB |
After Width: | Height: | Size: 52 KiB |
After Width: | Height: | Size: 256 KiB |
After Width: | Height: | Size: 284 KiB |
After Width: | Height: | Size: 34 KiB |