GitVP开源文摘
全部文章/人工智能

NLP基础知识

兜哥出品 <一本开源的NLP入门书籍>

作者duoergun0729 仓库duoergun0729/nlp ↗ 星标★ 2,496 字数67,349 阅读1
GitHub 原文 ↗
摘要图像、文字和声音是人类接触、理解外部世界最常见的三种形式,其中文字又是最容易保存和进行交换的形式。大量的人类文明,最终都是以文字的形式保留下来的;大量的信息交换,都是是文字的形式进行。如何能让机器可以与人交流,理解人类的思想,最终能像人类一样理解文字以及文字背后的各种想法、意图呢?自然语言处理,即所谓的NLP是重要的支撑技术。

NLP基础知识

NLP应用案例

让机器理解文字

图像、文字和声音是人类接触、理解外部世界最常见的三种形式,其中文字又是最容易保存和进行交换的形式。大量的人类文明,最终都是以文字的形式保留下来的;大量的信息交换,都是是文字的形式进行。如何能让机器可以与人交流,理解人类的思想,最终能像人类一样理解文字以及文字背后的各种想法、意图呢?自然语言处理,即所谓的NLP是重要的支撑技术。

人机对话

NLP与安全

在传统的web攻防中,大家与http协议结下了不解之缘。但是在安全领域,web攻防只是很小一个分支。许多明显的与工作和生活无关的垃圾邮件,人一样就可以看出来,但是基于规则的垃圾邮件网关处理起来却总是差强人意。越来越多的电商、论坛甚至是视频网站的弹幕,总是可以看到明显的人身攻击或者违法违规信息,但是基于规则的过滤机制总是被绕过。人类可以很轻松的理解二十四口交换机,知道苹果是水果还是手机,但是机器如何做到呢?答案就是NLP。

一本开源的NLP入门书籍

这可能是第一本用开源的思想写的NLP入门书籍,整个写作过程都在我的Github上。

https://github.com/duoergun0729/nlp

之所以想用开源的思路去写,主要是因为NLP技术,尤其是基于机器学习的NLP技术发展非常快,比如目前已经广泛使用的fasttext技术,2016年发布论文,2017年已经进入大量生产领域,但是许多自然语言处理书籍还停留在大学课程的范围,甚至连词向量都很少涉及。相对周期繁琐的纸质书籍编写,在Github上我可以很方便的进行编写和更新,有勘误也可以很快修改。目前我已经完成了其中的三篇,后面我将不断更新内容,大家可以订阅我的Github,或者关注我的微信公众号《兜哥带你学安全》

公众号

License

© 2018~2025 兜哥.

本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。没有我许可的任何使用该书进行的商业行为都是违法。


常用数据集简介

概述

数据和算法在NLP中都非常重要,使用公开的数据集可以帮助我们快速学习NLP相关知识并实践,下面我们将介绍常用的几个数据集。

搜狗实验室数据

常用数据集简介-图1

搜狗实验室(Sogo Labs)是搜狗搜索核心研发团队对外交流的窗口,包含数据资源、数据挖掘云、研究合作等几个栏目。数据资源包括评测集合、语料数据、新闻数据、图片数据和自然语言处理相关数据,网址为:

http://www.sogou.com/labs/resource/list_pingce.php

互联网语料库(SogouT)

SogouT来自互联网各种类型的1.3亿个原始网页, 压缩前的大小超过了5TB,格式如下:

页面ID

页面URL

页面原始内容

为了满足不同需求,SogouT分为了不同的版本,差别体现在数据量上:

  • 迷你版(样例数据, 61KB):tar.gz格式,zip格式
  • 完整版(1TB):(硬盘拷贝)
  • 历史版本(130GB):V2.0(硬盘拷贝)

全网新闻数据(SogouCA)

SogouCA来自若干新闻站点2012年6月—7月期间国内,国际,体育,社会,娱乐等18个频道的新闻数据,提供URL和正文信息,格式如下:

页面URL

页面ID

页面标题

页面内容

为了满足不同需求,SogouCA分为了不同的版本,差别体现在数据量上:

  • 迷你版(样例数据, 101KB):tar.gz格式,zip格式
  • 完整版(711MB):tar.gz格式,zip格式
  • 历史版本:
- 完整版(同时提供硬盘拷贝,1.02GB):tar.gz格式
- 迷你版(样例数据, 3KB):tar.gz格式
- 精简版(一个月数据, 437MB):tar.gz格式

搜狐新闻数据(SogouCS)

SogouCS来自搜狐新闻2012年6月—7月期间国内,国际,体育,社会,娱乐等18个频道的新闻数据,提供URL和正文信息,格式如下:

页面URL

页面ID

页面标题

页面内容

为了满足不同需求,SogouCS分为了不同的版本,差别体现在数据量上:

  • 迷你版(样例数据, 110KB):tar.gz格式,zip格式
  • 完整版(648MB):tar.gz格式,zip格式
  • 历史版本:
- 完整版(同时提供硬盘拷贝,65GB):tar.gz格式
- 迷你版(样例数据, 1KB):tar.gz格式
- 精简版(一个月数据, 347MB):tar.gz格式
- 特别版(王灿辉WWW08论文数据, 647KB):tar.gz格式

文本分类评价(SogouTCE)

SogouTCE用以评估文本分类结果的正确性,语料来自搜狐等多个新闻网站近20个频道,格式如下:

URL前缀\t对应类别标记

SogouTCE只包含URL前缀和对应类别标记的数据,原始的文本数据可以使用SogouCA和SogouCS。

互联网词库(SogouW)

SogouW来自于对SOGOU搜索引擎所索引到的中文互联网语料的统计分析,统计所进行的时间是2006年10月,涉及到的互联网语料规模在1亿页面以上。统计出的词条数约为15万条高频词,除标出这部分词条的词频信息之外,还标出了常用的词性信息,格式如下:

词A 词频 词性1 词性2 … 词性N

词B 词频 词性1 词性2 … 词性N

词C 词频 词性1 词性2 … 词性N

IMDB Reviews

互联网电影资料库(Internet Movie Database,简称IMDB)是一个关于电影演员、电影、电视节目、电视明星和电影制作的在线数据库。IMDB Reviews是记录了观众对IMDB中作品的评价。除了训练和测试评估示例之外,还有更多未标记的数据可供使用,包括文本和预处理的词袋格式。IMDB Reviews包含25,000个高度差异化的电影评论用于训练,25,000个测试,通常用于英文的情感理解。

Sentiment140

Sentiment140是一个可用于情感分析的数据集,包含160,000条推文。一个流行的数据集,非常适合开始你的NLP旅程。情绪已经从数据中预先移除。最终的数据集具有以下6个特征:

  • 推文的极性
  • 推文的ID
  • 推文的日期
  • 问题
  • 推文的用户名
  • 推文的文本

Yelp Reviews

Yelp Reviews是Yelp为了学习目的而发布的一个开源数据集。它包含了由数百万用户评论,商业属性和来自多个大都市地区的超过20万张照片。这是一个常用的全球NLP挑战数据集,包含5,200,000条评论,174,000条商业属性。 数据集下载地址为:

https://www.yelp.com/dataset/download

常用数据集简介-图5

数据集格式分为JSON和SQL两种,以JSON格式为例,其中最重要的review.json,包含评论数据,格式如下:

	{
    // string, 22 character unique review id
    "review_id": "zdSx_SD6obEhz9VrW9uAWA",

    // string, 22 character unique user id, maps to the user in user.json
    "user_id": "Ha3iJu77CxlrFm-vQRs_8g",

    // string, 22 character business id, maps to business in business.json
    "business_id": "tnhfDv5Il8EaGSXZGiuQGg",

    // integer, star rating
    "stars": 4,

    // string, date formatted YYYY-MM-DD
    "date": "2016-03-09",

    // string, the review itself
    "text": "Great place to hang out after work: the prices are decent, and the ambience is fun. It's a bit loud, but very lively. The staff is friendly, and the food is good. They have a good selection of drinks.",

    // integer, number of useful votes received
    "useful": 0,

    // integer, number of funny votes received
    "funny": 0,

    // integer, number of cool votes received
    "cool": 0}

另外tip.json文件记录了短评数据,格式如下:

{
    // string, text of the tip
    "text": "Secret menu - fried chicken sando is da bombbbbbb Their zapatos are good too.",

    // string, when the tip was written, formatted like YYYY-MM-DD
    "date": "2013-09-20",

    // integer, how many likes it has
    "likes": 172,

    // string, 22 character business id, maps to business in business.json
    "business_id": "tnhfDv5Il8EaGSXZGiuQGg",

    // string, 22 character unique user id, maps to the user in user.json
    "user_id": "49JhAJh8vSQ-vM4Aourl0g"
}

专门有个开源项目用于解析该JSON文件:

https://github.com/Yelp/dataset-examples

Enron-Spam

Enron-Spam数据集是目前在电子邮件相关研究中使用最多的公开数据集,其邮件数据是安然公司(Enron Corporation, 原是世界上最大的综合性天然气和电力公司之一,在北美地区是头号天然气和电力批发销售商)150位高级管理人员的往来邮件。这些邮件在安然公司接受美国联邦能源监管委员会调查时被其公布到网上。机器学习领域使用Enron-Spam数据集来研究文档分类、词性标注、垃圾邮件识别等,由于Enron-Spam数据集都是真实环境下的真实邮件,非常具有实际意义。 Enron-Spam数据集合如下图所示,使用不同文件夹区分正常邮件和垃圾邮件。

常用数据集简介-图3.png

正常邮件内容举例如下:

Subject: christmas baskets the christmas baskets have been ordered . we have ordered several baskets . individual earth - sat freeze - notis smith barney group baskets rodney keys matt rodgers charlie notis jon davis move team phillip randle chris hyde harvey freese faclities

垃圾邮件内容举例如下:

Subject: fw : this is the solution i mentioned lscoo thank you , your email address was obtained from a purchased list ,reference # 2020 mid = 3300 . if you wish to unsubscribe from this list , please click here and enter your name into the remove box . if you have previously unsubscribed and are still receiving this message , you may email our abuse control center , or call 1 - 888 - 763 - 2497 , or write us at : nospam , 6484 coral way , miami , fl , 33155 " . 2002 web credit inc . all rights reserved .

Enron-Spam数据集对应的网址为:

http://www2.aueb.gr/users/ion/data/enron-spam/

babi阅读理解数据集

babi是来自FAIR(Facebook AI Research)的合成式阅读理解与问答数据集,是个入门级的阅读理解数据集,其训练集以对话集合[2] + 问题[1] + 回答[1]的形式组成:

1 Mary moved to the bathroom.
2 John went to the hallway.
3 Where is Mary?     bathroom    1
4 Daniel went back to the hallway.
5 Sandra moved to the garden.
6 Where is Daniel?     hallway    4
7 John moved to the office.
8 Sandra journeyed to the bathroom.
9 Where is Daniel?     hallway    4
10 Mary moved to the hallway.
11 Daniel travelled to the office.
12 Where is Daniel?     office    11
13 John went back to the garden.
14 John moved to the bedroom.
15 Where is Sandra?     bathroom    8
1 Sandra travelled to the office.
2 Sandra went to the bathroom.
3 Where is Sandra?     bathroom    2
4 Mary went to the bedroom.
5 Daniel moved to the hallway.
6 Where is Sandra?     bathroom    2
7 John went to the garden.
8 John travelled to the office.
9 Where is Sandra?     bathroom    2
10 Daniel journeyed to the bedroom.
11 Daniel travelled to the hallway.
12 Where is John?     office    8

抽象表示格式为:

ID text
ID text
ID text
ID question[tab]answer[tab]supporting fact IDS.

项目主页地址为:

https://research.fb.com/downloads/babi/

数据下载地址为:

http://www.thespermwhale.com/jaseweston/babi/tasks_1-20_v1.tar.gz

下载压缩包,解压后,全部文件保存在en文件下,文件夹下包含以下文件:

qa10_indefinite-knowledge_test.txt	
qa1_single-supporting-fact_test.txt
qa10_indefinite-knowledge_train.txt	
qa1_single-supporting-fact_train.txt
qa11_basic-coreference_test.txt		
qa20_agents-motivations_test.txt
qa11_basic-coreference_train.txt	
qa20_agents-motivations_train.txt
qa12_conjunction_test.txt		
qa2_two-supporting-facts_test.txt
qa12_conjunction_train.txt		
qa2_two-supporting-facts_train.txt
qa13_compound-coreference_test.txt	
qa3_three-supporting-facts_test.txt
qa13_compound-coreference_train.txt	
qa3_three-supporting-facts_train.txt
qa14_time-reasoning_test.txt		
qa4_two-arg-relations_test.txt
qa14_time-reasoning_train.txt		
qa4_two-arg-relations_train.txt
qa15_basic-deduction_test.txt		
qa5_three-arg-relations_test.txt
qa15_basic-deduction_train.txt		
qa5_three-arg-relations_train.txt
qa16_basic-induction_test.txt		
qa6_yes-no-questions_test.txt
qa16_basic-induction_train.txt		
qa6_yes-no-questions_train.txt
qa17_positional-reasoning_test.txt
qa7_counting_test.txt
qa17_positional-reasoning_train.txt
qa7_counting_train.txt
qa18_size-reasoning_test.txt		
qa8_lists-sets_test.txt
qa18_size-reasoning_train.txt		
qa8_lists-sets_train.txt
qa19_path-finding_test.txt		
qa9_simple-negation_test.txt
qa19_path-finding_train.txt		
qa9_simple-negation_train.txt

SMS Spam Collection

SMS Spam Collection是用于骚扰短信识别的经典数据集,完全来自真实短信内容,包括4831条正常短信和747条骚扰短信。从官网下载数据集压缩包,解压,正常短信和骚扰短信保存在一个文本文件中。 每行完整记录一条短信内容,每行开头通过ham和spam标识正常短信和骚扰短信,数据集文件内容举例如下:

ham What you doing?how are you? ham Ok lar... Joking wif u oni... ham dun say so early hor... U c already then say... ham MY NO. IN LUTON 0125698789 RING ME IF UR AROUND! H* ham Siva is in hostel aha:-. ham Cos i was out shopping wif darren jus now n i called him 2 ask wat present he wan lor. Then he started guessing who i was wif n he finally guessed darren lor. spam FreeMsg: Txt: CALL to No: 86888 & claim your reward of 3 hours talk time to use from your phone now! ubscribe6GBP/ mnth inc 3hrs 16 stop?txtStop spam Sunshine Quiz! Win a super Sony DVD recorder if you canname the capital of Australia? Text MQUIZ to 82277. B spam URGENT! Your Mobile No 07808726822 was awarded a L2,000 Bonus Caller Prize on 02/09/03! This is our 2nd attempt to contact YOU! Call 0871-872-9758 BOX95QU

SMS Spam Collection数据集主页地址为:

http://archive.ics.uci.edu/ml/datasets/SMS+Spam+Collection

常用数据集

UBUNTU DIALOG CORPUS

UBUNTU DIALOG CORPUS(UDC)是可用的最大的公共对话数据集之一,下载地址为:

cs.mcgill.ca/~jpineau/datasets/ubuntu-corpus-1.0/ubuntu_dialogs.tgz

它基于公共IRC网络上的Ubuntu频道的聊天记录。训练数据包括100万个样例,50%的正样例(标签1)和50%的负样例(标签0)。每个样例都包含一个上下文,即直到这一点的谈话记录,以及一个话语,即对上下文的回应。一个正标签意味着话语是对当前语境上下文的实际响应,一个负标签意味着这个话语不是真实的响应 ,它是从语料库的某个地方随机挑选出来的。这是一些示例数据:

常用数据集简介-图8

UDC相关的经典论文地址为:

https://arxiv.org/abs/1506.08909

UDC相关的Github地址为:

https://github.com/rkadlec/ubuntu-ranking-dataset-creator

Hate speech identification

Hate speech identification由ICWSM 2017论文“自动仇恨语音检测和无礼语言问题”的作者提供。包含3类短文本:

  • 包含仇恨言论;
  • 是冒犯性的,但没有仇恨言论;
  • 根本没有冒犯性。

由15,000行文本构成,每个字符串都经过3人判断。

下载链接为:

https://github.com/t-davidson/hate-speech-and-offensive-language

Twitter Progressive issues sentiment analysis

Twitter Progressive issues sentiment analysis是关于诸如堕胎合法化、女权主义、希拉里·克林顿等各种左倾问题的推文,分为赞成、反对或保持中立的三种类别。

下载链接为:

https://www.figure-eight.com/data-for-everyone/

今日头条新闻文本分类数据集

今日头条新闻文本分类数据集共382688条,分布于15个分类中,分类code与名称:

  • 100 民生 故事 news_story
  • 101 文化 文化 news_culture
  • 102 娱乐 娱乐 news_entertainment
  • 103 体育 体育 news_sports
  • 104 财经 财经 news_finance
  • 106 房产 房产 news_house
  • 107 汽车 汽车 news_car
  • 108 教育 教育 news_edu
  • 109 科技 科技 news_tech
  • 110 军事 军事 news_military
  • 112 旅游 旅游 news_travel
  • 113 国际 国际 news_world
  • 114 证券 股票 stock
  • 115 农业 三农 news_agriculture
  • 116 电竞 游戏 news_game

数据格式为:

6552431613437805063_!_102_!_news_entertainment_!_谢娜为李浩菲澄清网络谣言,

之后她的两个行为给自己加分_!_佟丽娅,网络谣言,快乐大本营,李浩菲,谢娜,观众们

每行为一条数据,以_\!\_分割的个字段,从前往后分别是 新闻ID,分类code,分类名称,新闻字符串(仅含标题),新闻关键词.

项目主页在github上,运行get_data.py即可获取实时获取对应的数据。

https://github.com/fateleak/toutiao-text-classfication-dataset

也可以直接使用github上的历史数据进行分析。

https://github.com/fateleak/toutiao-text-classfication-dataset/raw/master/toutiao_cat_data.txt.zip

打造NLP工具箱

Scikit-learn

Scikit-learn是广受欢迎的入门级机器学习库,包含大量的机器学习算法和特征提取实现,使用非常简便。Scikit-learn实现的是浅层学习算法,神经网络仅实现了多层感知机。 Scikit-learn的安装方式如下:

pip install scikit-learn

TensorFlow

TensorFlow是谷歌基于DistBelief进行研发的第二代人工智能学习系统,可被用于语音识别或图像识别等多项机器学习和深度学习领域.它可在小到一部智能手机、大到数千台数据中心服务器的各种设备上运行。TensorFlow的安装方式如下:

pip install tensorflow

Keras

Keras是一个高级别的Python神经网络框架,能在TensorFlow或者 Theano 上运行。Keras的作者、谷歌AI研究员Francois Chollet宣布了一条激动人心的消息,Keras将会成为第一个被添加到TensorFlow核心中的高级别框架,这将会让Keras变成Tensorflow的默认API。 Keras的安装非常简便,使用pip工具即可。

pip install keras

如果需要使用源码安装,可以直接从GitHub上下载对应源码。

https://github.com/fchollet/keras

然后进入Keras目录安装即可。

python setup.py install

Anaconda

Anaconda是一个用于科学计算的Python开发平台,支持 Linux,Mac和Windows系统,提供了包管理与环境管理的功能,可以很方便地解决多版本Python并存、切换以及各种第三方包安装问题。Anaconda利用conda命令来进行package和environment的管理,并且已经包含了Python和相关的配套工具。Anaconda集成了大量的机器学习库以及数据处理必不可少的第三方库,比如NumPy,SciPy,Scikit-Learn以及TensorFlow等。 Anaconda的安装非常方便,从其官网的下载页面选择对应的安装包即可。 以我的Mac本为例,安装对应Anaconda安装包后,使用如下命令查看当前用户的profile文件的内容。

cat ~/.bash_profile

我们可以发现在当前用户的profile文件的最后增加了如下内容,表明已经将Anaconda的bin目录下的命令添加到了PATH变量中,可以像使用系统命令一样直接使用Anaconda的命令行工具了。

# added by Anaconda2 5.0.0 installer
export PATH="/anaconda2/bin:$PATH"

Anaconda强大的包管理以及多种Python环境并存使用主要以来于conda命令,常用的conda命令列举如下。

# 创建一个名为python27的环境,指定Python版本是2.7
conda create --name python27 python=2.7
# 查看当前环境下已安装的包
conda list
# 查看某个指定环境的已安装包
conda list -n python27
# 查找package信息
conda search numpy
# 安装package
conda install -n python27 numpy
# 更新package
conda update -n python27 numpy
# 删除package
conda remove -n python27 numpy

假设我们已经创建一个名为python27的环境,指定Python版本是2.7,激活该环境的方法如下。

source activate python27

如果要退出该环境,命令如下所示。


source deactivate

在python27的环境下查看Python版本,果然是2.7版本。

maidou:3book liu.yan$ source activate python27
(python27) maidou:3book liu.yan$ 
(python27) maidou:3book liu.yan$ python 
Python 2.7.14 |Anaconda, Inc.| (default, Oct  5 2017, 02:28:52) 
[GCC 4.2.1 Compatible Clang 4.0.1 (tags/RELEASE_401/final)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 

查看python27环境下默认安装了哪些包,为了避免显示内容过多,过滤前6行查看。

conda list | head -6
# packages in environment at /anaconda2/envs/python27:
#
ca-certificates           2017.08.26           ha1e5d58_0  
certifi                   2017.7.27.1      py27h482ffc0_0  
libcxx                    4.0.1                h579ed51_0  
libcxxabi                 4.0.1                hebd6815_0 

统计包的个数,除去2行的无关内容,当前环境下有16个包。

conda list | wc -l
  18
  

查看目前一共具有几个环境,发现除了系统默认的root环境,又多出了我们创建的python27环境。

conda info --envs
# conda environments:
#
python27                 /anaconda2/envs/python27
root                  *  /anaconda2

在python27环境下安装Anaconda默认的全部安装包,整个安装过程会比较漫长,速度取决于你的网速。

conda install anaconda
Fetching package metadata ...........
Solving package specifications: .
Package plan for installation in environment /anaconda2/envs/python27:

继续统计包的个数,除去2行的无关内容,当前环境下已经有238个包了。

conda list | wc -l
  240

Anaconda默认安装的第三方包里没有包含TensorFlow和Keras,需要使用命令手工安装,以TensorFlow为例,可以使用conda命令直接安装。

conda install tensorflow

同时也可以使用pip命令直接安装。


pip install tensorflow

本书一共创建了两个环境,分别是python27和python36,顾名思义对应的Python版本分别为2.7和3.6,用于满足不同案例对python版本的不同要求。

Gensim

Gensim是一款开源的第三方Python工具包,用于从原始的非结构化的文本中,无监督地学习到文本隐层的主题向量表达。它支持包括TF-IDF,LSA,LDA,和word2vec在内的多种主题模型算法,支持流式训练,并提供了诸如相似度计算,信息检索等一些常用任务的API接口。 Gensim的安装方式如下:

pip install gensim

NTLK

NLTK由Steven Bird和Edward Loper在宾夕法尼亚大学计算机和信息科学系开发,在NLP领域中,最常使用的一个Python库。 NTLK的安装方式如下:

pip install ntlk

NTLK分为模型和数据两部分,其中数据需要单独下载。

>>>import nltk
>>>nltk.download()

推荐选择all,设置好下载路径,然后点击Download,系统就开始下载。NLTK的数据包了,下载的时间比较漫长,大家要耐心等待。如果有个别数据包无法下载,可以切换到All Packages标签页,双击指定的包来进行下载。

Jieba

Jieba,经常被人昵称为结巴,是最受欢迎的中文分词工具,安装方式如下:

pip install jieba

Jupyter notebook

Jupyter notebook中使用Anaconda中的环境需要单独配置,默认情况下使用的是系统默认的Python环境,以使用advbox环境为例。 首先在默认系统环境下执行以下命令,安装ipykernel。

conda install ipykernel
conda install -n advbox ipykernel

在advbox环境下激活,这样启动后就可以在界面上看到advbox了。

python -m ipykernel install --user --name advbox --display-name advbox 

远程访问jupyter notebook ipython notebook是一个基于浏览器的python数据分析工具,使用起来非常方便,具有极强的交互方式和富文本的展示效果。jupyter是它的升级版,它的安装也非常方便,一般Anaconda安装包中会自带。安装好以后直接输入jupyter notebook便可以在浏览器中使用。但是它默认只能在本地访问,如果想把它安装在服务器上,然后在本地远程访问,则需要进行如下配置:

  1. 登陆远程服务器
  2. 生成配置文件
$jupyter notebook --generate-config
  1. 生成密码

打开ipython,创建一个密文的密码:

In [1]: from notebook.auth import passwd
In [2]: passwd()
Enter password: 
Verify password: 
Out[2]: 'sha1:ce23d945972f:34769685a7ccd3d08c84a18c63968a41f1140274'

把生成的密文‘sha:ce…’复制下来

  1. 修改默认配置文件
$vim ~/.jupyter/jupyter_notebook_config.py 

进行如下修改:

c.NotebookApp.ip='*'
c.NotebookApp.password = u'sha:ce...刚才复制的那个密文'
c.NotebookApp.open_browser = False
c.NotebookApp.port =8888 #随便指定一个端口
  1. 启动jupyter notebook:
$jupyter notebook

GPU服务器

当数据量大或者计算量大时,GPU几乎成为必选,尤其是使用CNN和RNN时,几乎就是CPU杀手。目前主流的云上都提供了GPU服务器。以百度云为例,默认支持的tensorflow的GPU版本是1.4。 当你习惯使用python2.*时,推荐使用的组合为:


- tensorflow-gpu==1.4
- keras==2.1.5
- python==2.7

当你习惯使用python5.*时,推荐使用的组合为:


- tensorflow-gpu==1.4
- keras==2.1.5
- python==3.5
手工安装深度学习库

有时候需要根据软硬件环境自己选择安装对应的深度学习库。其中最重要的是看cuDNN和CUDA的版本,查看服务器的cuDNN和CUDA版本的方法为:

#cuda 版本 
cat /usr/local/cuda/version.txt
#cudnn 版本  
cat /usr/local/cuda/include/cudnn.h | grep CUDNN_MAJOR -A 2 
#或者 
cat /usr/include/cudnn.h | grep CUDNN_MAJOR -A 2

如何衡量机器学习分类模型

概述

在NLP中我们经常需要使用机器学习的分类器。如何衡量一个分类器的好坏呢?最常见的指标包括准确率与召回率,准确度与F1-Score以及ROC与AUC。

测试数据

我们以Scikit-Learn环境介绍常见的性能衡量指标。为了演示方便,我们创建测试数据,测试数据一共1000条记录,每条记录100个特征,内容随机生成。

x, y = datasets.make_classification(n_samples=1000, n_features=100,
					n_redundant=0, random_state = 1)
把数据集随机划分成训练集和测试集,其中测试集占40%。
train_X, test_X, train_y, test_y = train_test_split(x,
                                                    y,
                                                    test_size=0.2,
                                                    random_state=66)

使用KNN算法进行训练和预测。

knn = KNeighborsClassifier(n_neighbors=5)
knn.fit(train_X, train_Y)
pred_Y = knn.predict(test_X)

混淆矩阵

混淆矩阵,即Confusion Matrix,是将分类问题按照真实情况与判别情况两个维度进行归类的一个矩阵,在二分类问题中,可以用一个2乘以2的矩阵表示。如图1-1 所示,TP表示实际为真预测为真,TN表示实际为假预测为假,FN表示实际为真预测为假,通俗讲就是漏报了,FP表示实际为假预测为真,通俗讲就是误报了。

如何衡量机器学习分类器模型

在Scikit-Learn中,使用metrics.confusion_matrix输出混淆矩阵。

print "confusion_matrix:"
print metrics.confusion_matrix(test_Y, pred_Y)

输出结果如下,其中漏报 36个,误报了25个。

confusion_matrix:
[[70 25]
 [36 69]]
 

准确率与召回率

机器学习中最基本指标是召回率(Recall Rate)和准确率(Precision Rate),召回率也叫查全率,准确率也叫查准率。

召回率=TP/(TP+FN)
准确率=TP/(TP+FP)

用一个吃货都可以理解的例子来解释这两个枯燥的概念。一个池塘有10条鱼和20只小龙虾,渔夫撒网打鱼,结果捞上来8条鱼12只小龙虾,那么准确率为8/(8+12)=40%,召回率为8/10=80%。 在Scikit-Learn中,可以如下获得准确率和召回率。

print "recall_score:"
print metrics.recall_score(test_Y, pred_Y)
print "precision_score:"
print metrics.precision_score(test_Y, pred_Y)

输出结果如下,其中召回率为65.71%,准确率为73.40%。

recall_score:
0.657142857143
precision_score:
0.734042553191

准确度与F1-Score

准确度(Accuracy)是对检测结果一个均衡的评价,表现的是全体预测正确占全部样本的比例。F1-Score也是对准确率和召回率的一个均衡评价,国内外不少数据挖掘比赛都是重点关注F1-Score的值。在Scikit-Learn中,可以如下获得准确度和F1-Score。

print "accuracy_score:"
print metrics.accuracy_score(test_Y, pred_Y)
print "f1_score:"
print metrics.f1_score(test_Y, pred_Y)

输出结果如下,其中准确度为69.5%和F1-Score为69.34%。

accuracy_score:
0.695
f1_score:
0.693467336683

ROC与AUC

ROC(Receiver Operating Characteristic Curve)受试者工作特征曲线,以真阳性率为纵坐标,假阳性率为横坐标绘制的曲线,是反映灵敏性和特效性连续变量的综合指标。一般认为ROC越光滑说明分类算法过拟合的概率越低,越接近左上角说明分类性能越好。AUC(Area Under the Receiver Operating Characteristic Curve)就是量化衡量ROC分类性能的指标,如图1-2 所示,物理含义是ROC曲线的面积,AUC越大越好。

如何衡量机器学习分类器模型-图2

绘制ROC曲线的方法如下:

f_pos, t_pos, thresh = metrics.roc_curve(test_Y, pred_Y)
auc_area = metrics.auc(f_pos, t_pos)
	plt.plot(f_pos, t_pos, 'darkorange', lw=2, label='AUC = %.2f' % auc_area)
plt.legend(loc='lower right')
plt.plot([0, 1], [0, 1], color='navy', linestyle='--')
plt.title('ROC')
plt.ylabel('True Pos Rate')
plt.xlabel('False Pos Rate')
plt.show()

在Scikit-Learn中,可以如下获得AUC值。

print "AUC:"
print metrics.roc_auc_score(test_Y, pred_Y)

计算获得的AUC值为0.70。

AUC:
0.696992481203

词袋模型和TFIDF模型

词袋模型

文本特征提取有两个非常重要的模型:

  • 词集模型:单词构成的集合,集合自然每个元素都只有一个,也即词集中的每个单词都只有一个。
  • 词袋模型:在词集的基础上如果一个单词在文档中出现不止一次,统计其出现的次数(频数)。

两者本质上的区别,词袋是在词集的基础上增加了频率的维度,词集只关注有和没有,词袋还要关注有几个。 假设我们要对一篇文章进行特征化,最常见的方式就是词袋。 导入相关的函数库:

>>> from sklearn.feature_extraction.text import CountVectorizer

实例化分词对象:

>>> vectorizer = CountVectorizer(min_df=1)
>>> vectorizer                    
CountVectorizer(analyzer=...'word', binary=False, decode_error=...'strict',
        dtype=<... 'numpy.int64'>, encoding=...'utf-8', input=...'content',
        lowercase=True, max_df=1.0, max_features=None, min_df=1,
        ngram_range=(1, 1), preprocessor=None, stop_words=None,
        strip_accents=None, token_pattern=...'(?u)\\b\\w\\w+\\b',
        tokenizer=None, vocabulary=None)
        

将文本进行词袋处理:

>>> corpus = [
...     'This is the first document.',
...     'This is the second second document.',
...     'And the third one.',
...     'Is this the first document?',
... ]
>>> X = vectorizer.fit_transform(corpus)
>>> X                             
<4x9 sparse matrix of type '<... 'numpy.int64'>'
    with 19 stored elements in Compressed Sparse ... format>
    

获取对应的特征名称:

>>> vectorizer.get_feature_names() == (
...     ['and', 'document', 'first', 'is', 'one',
...      'second', 'the', 'third', 'this'])
True

获取词袋数据,至此我们已经完成了词袋化:

>>> X.toarray()          
array([[0, 1, 1, 1, 0, 0, 1, 0, 1],
       [0, 1, 0, 1, 0, 2, 1, 0, 1],
       [1, 0, 0, 0, 1, 0, 1, 1, 0],
       [0, 1, 1, 1, 0, 0, 1, 0, 1]]...)

但是如何可以使用现有的词袋的特征,对其他文本进行特征提取呢?我们定义词袋的特征空间叫做词汇表vocabulary:

vocabulary=vectorizer.vocabulary_

针对其他文本进行词袋处理时,可以直接使用现有的词汇表:

>>> new_vectorizer = CountVectorizer(min_df=1, vocabulary=vocabulary)

CountVectorize函数比较重要的几个参数为:

  • decode_error,处理解码失败的方式,分为‘strict’、‘ignore’、‘replace’三种方式。
  • strip_accents,在预处理步骤中移除重音的方式。
  • max_features,词袋特征个数的最大值。
  • stop_words,判断word结束的方式。
  • max_df,df最大值。
  • min_df,df最小值 。
  • binary,默认为False,当与TF-IDF结合使用时需要设置为True。

本例中处理的数据集均为英文,所以针对解码失败直接忽略,使用ignore方式,stop_words的方式使用english,strip_accents方式为ascii方式。

TF-IDF模型

文本处理领域还有一种特征提取方法,叫做TF-IDF模型(term frequency–inverse document frequency,词频与逆向文件频率)。TF-IDF是一种统计方法,用以评估某一字词对于一个文件集或一个语料库的重要程度。字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。TF-IDF加权的各种形式常被搜索引擎应用,作为文件与用户查询之间相关程度的度量或评级。 TF-IDF的主要思想是,如果某个词或短语在一篇文章中出现的频率TF(Term Frequency,词频),词频高,并且在其他文章中很少出现,则认为此词或者短语具有很好的类别区分能力,适合用来分类。TF-IDF实际上是:TF * IDF。TF表示词条在文档d中出现的频率。IDF(inverse document frequency,逆向文件频率)的主要思想是:如果包含词条t的文档越少,也就是n越小,IDF越大,则说明词条t具有很好的类别区分能力。如果某一类文档C中包含词条t的文档数为m,而其他类包含t的文档总数为k,显然所有包含t的文档数n=m+k,当m大的时候,n也大,按照IDF公式得到的IDF的值会小,就说明该词条t类别区分能力不强。但是实际上,如果一个词条在一个类的文档中频繁出现,则说明该词条能够很好代表这个类的文本的特征,这样的词条应该给它们赋予较高的权重,并选来作为该类文本的特征词以区别与其他类文档。 在Scikit-Learn中实现了TF-IDF算法,实例化TfidfTransformer即可:

>>> from sklearn.feature_extraction.text import TfidfTransformer
>>> transformer = TfidfTransformer(smooth_idf=False)
>>> transformer    
TfidfTransformer(norm=...'l2', smooth_idf=False, sublinear_tf=False, use_idf=True)

TF-IDF模型通常和词袋模型配合使用,对词袋模型生成的数组进一步处理:

>>> counts = [[3, 0, 1],
...           [2, 0, 0],
...           [3, 0, 0],
...           [4, 0, 0],
...           [3, 2, 0],
...           [3, 0, 2]]
...
 >>> tfidf = transformer.fit_transform(counts)
>>> tfidf                         
<6x3 sparse matrix of type '<... 'numpy.float64'>'     with 9 stored elements in Compressed Sparse ... format> 
>>> tfidf.toarray()                         
array([[ 0.81940995,  0.        ,  0.57320793],       
[ 1.        ,  0.        ,  0.        ],      
[ 1.        ,  0.        ,  0.        ],  
[ 1.        ,  0.        ,  0.        ],       
[ 0.47330339,  0.88089948,  0.        ],       
[ 0.58149261,  0.        ,  0.81355169]])

词汇表模型

词袋模型可以很好的表现文本由哪些单词组成,但是却无法表达出单词之间的前后关系,于是人们借鉴了词袋模型的思想,使用生成的词汇表对原有句子按照单词逐个进行编码。TensorFlow默认支持了这种模型:

tf.contrib.learn.preprocessing.VocabularyProcessor (
                                          max_document_length,    
                                          min_frequency=0,
                                          vocabulary=None,
                                          tokenizer_fn=None)

其中各个参数的含义为:

  • max_document_length:,文档的最大长度。如果文本的长度大于最大长度,那么它会被剪切,反之则用0填充。
  • min_frequency,词频的最小值,出现次数小于最小词频则不会被收录到词表中。
  • vocabulary,CategoricalVocabulary 对象。
  • tokenizer_fn,分词函数。

假设有如下句子需要处理:

x_text =[
    'i love you',
    'me too'
]

基于以上句子生成词汇表,并对'i me too'这句话进行编码:

vocab_processor = learn.preprocessing.VocabularyProcessor(max_document_length)
vocab_processor.fit(x_text)
print next(vocab_processor.transform(['i me too'])).tolist()
x = np.array(list(vocab_processor.fit_transform(x_text)))
print x

运行程序,x_text使用词汇表编码后的数据为: [[1 2 3 0] [4 5 0 0]] 'i me too'这句话编码的结果为: [1, 4, 5, 0] 整个过程如下图所示。

image


Word2Vec模型和Doc2Vec模型

Word2Vec模型

Word2Vec是Google在2013年开源的一款将词表征为实数值向量的高效工具,采用的模型有CBOW(Continuous Bag-Of-Words,即连续的词袋模型)和Skip-Gram 两种。Word2Vec通过训练,可以把对文本内容的处理简化为K维向量空间中的向量运算,而向量空间上的相似度可以用来表示文本语义上的相似度。因此,Word2Vec 输出的词向量可以被用来做很多NLP相关的工作,比如聚类、找同义词、词性分析等等。 CBOW和Skip-gram原理图

CBOW模型能够根据输入周围n-1个词来预测出这个词本身,而Skip-gram模型能够根据词本身来预测周围有哪些词。也就是说,CBOW模型的输入是某个词A周围的n个单词的词向量之和,输出是词A本身的词向量,而Skip-gram模型的输入是词A本身,输出是词A周围的n个单词的词向量。 Word2Vec最常用的开源实现之一就是gensim,网址为:

http://radimrehurek.com/gensim/

gensim的安装非常简单:

pip install --upgrade gensim

gensim的使用非常简洁,加载数据和训练数据可以合并,训练好模型后就可以按照单词获取对应的向量表示:

sentences = [['first', 'sentence'], ['second', 'sentence']]
model = gensim.models.Word2Vec(sentences, min_count=1)
print model['first'] 

其中Word2Vec有很多可以影响训练速度和质量的参数。第一个参数可以对字典做截断,少于min_count次数的单词会被丢弃掉, 默认值为5:

model = Word2Vec(sentences, min_count=10)

另外一个是神经网络的隐藏层的单元数,推荐值为几十到几百。事实上Word2Vec参数的个数也与神经网络的隐藏层的单元数相同,比如size=200,那么训练得到的Word2Vec参数个数也是200: model = Word2Vec(sentences, size=200) 以处理IMDB数据集为例,初始化Word2Vec对象,设置神经网络的隐藏层的单元数为200,生成的词向量的维度也与神经网络的隐藏层的单元数相同。设置处理的窗口大小为8个单词,出现少于10次数的单词会被丢弃掉,迭代计算次数为10次,同时并发线程数与当前计算机的cpu个数相同:

model=gensim.models.Word2Vec(size=200, window=8, min_count=10, iter=10, workers=cores)

其中当前计算机的cpu个数可以使用multiprocessing获取:

cores=multiprocessing.cpu_count()

创建字典并开始训练获取Word2Vec。gensim的官方文档中强调增加训练次数可以提高生成的Word2Vec的质量,可以通过设置epochs参数来提高训练次数,默认的训练次数为5:

x=x_train+x_test
model.build_vocab(x)
model.train(x, total_examples=model.corpus_count, epochs=model.iter)

经过训练后,Word2Vec会以字典的形式保存在model对象中,可以使用类似字典的方式直接访问获取,比如获取单词“love”的Word2Vec就可以使用如下形式:

model[“love”]

Word2Vec的维度与之前设置的神经网络的隐藏层的单元数相同为200,也就是说是一个长度为200的一维向量。通过遍历一段英文,逐次获取每个单词对应的Word2Vec,连接起来就可以获得该英文段落对应的Word2Vec:

def getVecsByWord2Vec(model, corpus, size):
    x=[]
    for text in corpus:
        xx = []
        for i, vv in enumerate(text):
            try:
                xx.append(model[vv].reshape((1,size)))
            except KeyError:
                continue
        x = np.concatenate(xx)
    x=np.array(x, dtype='float')
    return x

需要注意的是,出于性能的考虑,我们将出现少于10次数的单词会被丢弃掉,所以存在这种情况,就是一部分单词找不到对应的Word2Vec,所以需要捕捉这个异常,通常使用python的KeyError异常捕捉即可。 基于上述的Word2Vec的方法,Quoc Le 和Tomas Mikolov又给出了Doc2Vec的训练方法。如下图所示,其原理与Word2Vec相同,分为Distributed Memory (DM) 和Distributed Bag of Words (DBOW)。

DM和DBOW原理图

以处理IMDB数据集为例,初始化Doc2Vec对象,设置神经网络的隐藏层的单元数为200,生成的词向量的维度也与神经网络的隐藏层的单元数相同。设置处理的窗口大小为8个单词,出现少于10次数的单词会被丢弃掉,迭代计算次数为10次,同时并发线程数与当前计算机的cpu个数相同:

model=Doc2Vec(dm=0, dbow_words=1, size=max_features, window=8, min_count=10, iter=10, workers=cores)

其中需要强调的是,dm为使用的算法,默认为1,表明使用DM算法,设置为0表明使用DBOW算法,通常使用默认配置即可,比如:

model = gensim.models.Doc2Vec.Doc2Vec(size=50, min_count=2, iter=10)

Doc2Vec

与Word2Vec不同的地方是,Doc2Vec处理的每个英文段落,需要使用一个唯一的标识标记,并且使用一种特殊定义的数据格式保存需要处理的英文段落,这种数据格式定义如下:

SentimentDocument = namedtuple('SentimentDocument', 'words tags')

其中SentimentDocument可以理解为这种格式的名称,也可以理解为这种对象的名称,words会保存英文段落,并且是以单词和符合列表的形式保存,tags就是我们说的保存的唯一标识。最简单的一种实现就是依次给每个英文段落编号,训练数据集的标记为“TRAIN_数字”,训练数据集的标记为“TEST_数字”:

def labelizeReviews(reviews, label_type):
    labelized = []
    for i, v in enumerate(reviews):
        label = '%s_%s' % (label_type, i)
        labelized.append(SentimentDocument(v, [label]))
    return labelized

创建字典并开始训练获取Doc2Vec。与Word2Vec的情况一样,gensim的官方文档中强调增加训练次数可以提高生成的Doc2Vec的质量,可以通过设置epochs参数来提高训练次数,默认的训练次数为5:

x=x_train+x_test
model.build_vocab(x)
model.train(x, total_examples=model.corpus_count, epochs=model.iter)

经过训练后,Doc2Vec会以字典的形式保存在model对象中,可以使用类似字典的方式直接访问获取,比如获取段落“I love tensorflow”的Doc2Vec就可以使用如下形式:

model.docvecs[”I love tensorflow”]

一个典型的doc2ver展开为向量形式,内容如下所示,为了显示方便只展示了其中一部分维度的数据:

array([ 0.02664499,  0.00475204, -0.03981256,  0.03796276, -0.03206162,
        0.10963056, -0.04897128,  0.00151982, -0.03258783,  0.04711508,
       -0.00667155, -0.08523653, -0.02975186,  0.00166316,  0.01915652,
       -0.03415785, -0.05794788,  0.05110953,  0.01623618, -0.00512495,
       -0.06385455, -0.0151557 ,  0.00365376,  0.03015811,  0.0229462 ,
        0.03176891,  0.01117626, -0.00743352,  0.02030453, -0.05072152,
       -0.00498496,  0.00151227,  0.06122205, -0.01811385, -0.01715777,
        0.04883198,  0.03925886, -0.03568915,  0.00805744,  0.01654406,
       -0.05160677,  0.0119908 , -0.01527433,  0.02209963, -0.10316766,
       -0.01069367, -0.02432527,  0.00761799,  0.02763799, -0.04288232], dtype=float32)
       

Doc2Vec的维度与之前设置的神经网络的隐藏层的单元数相同为200,也就是说是一个长度为200的一维向量。以英文段落为单位,通过遍历训练数据集和测试数据集,逐次获取每个英文段落对应的Doc2Vec,这里的英文段落就可以理解为数据集中针对电影的一段评价:

def getVecs(model, corpus, size):
    vecs = [np.array(model.docvecs[z.tags[0]]).reshape((1, size)) for z in corpus]
    return np.array(np.concatenate(vecs),dtype='float')

训练Word2Vec和Doc2Vec是非常费时费力的过程,调试阶段会频繁更换分类算法以及修改分类算法参数调优,为了提高效率,可以把之前训练得到的Word2Vec和Doc2Vec模型保存成文件形式,以Doc2Vec为例,使用model.save函数把训练后的结果保存在本地硬盘上,运行程序时,在初始化Doc2Vec对象之前,可以先判断本地硬盘是否存在模型文件,如果存在就直接读取模型文件初始化Doc2Vec对象,反之则需要训练数据:

if os.path.exists(doc2ver_bin):
    print "Find cache file %s" % doc2ver_bin
    model=Doc2Vec.load(doc2ver_bin)
else:
    model=Doc2Vec(size=max_features, window=5, min_count=2, workers=cores,iter=40)
    model.build_vocab(x))
    model.train(x, total_examples=model.corpus_count, epochs=model.iter)
    model.save(doc2ver_bin)

自己动手训练word2vec模型

训练语料

word2vec的算法是公开的,word2vec模型的质量完全取决于训练语料的质量。目前免费开放的预料不多,中文语料更是凤毛麟角。

使用搜狗新闻预料生成word2vec-图1

这里推荐使用搜狗实验室的中文语料,对应的网址为:

http://www.sogou.com/labs/resource/cs.php

通常使用"搜狐新闻数据"即可,该数据来自搜狐新闻2012年6月—7月期间国内,国际,体育,社会,娱乐等18个频道的新闻数据,提供URL和正文信息。

数据格式

页面URL

页面ID

页面标题

页面内容

注意:content字段去除了HTML标签,保存的是新闻正文文本

数据文件

搜狐新闻数据区根据文件格式和数据规模细分为以下几种:

  • 迷你版(样例数据, 110KB):tar.gz格式,zip格式
  • 完整版(648MB):tar.gz格式,zip格式
  • 历史版本:2008版(6KB):完整版(同时提供硬盘拷贝,65GB):tar.gz格式

数据预处理

提取中文内容

原始数据中包含完整的html文件,所以需要提取其中的中文内容,通常提取其中<content>标签包含的内容即可。

tar -zxvf news_sohusite_xml.full.tar.gz
cat news_sohusite_xml.dat | iconv -f gb18030 -t utf-8 | grep "<content>" > news_sohusite.txt
sed -i "" 's/<content>//g' news_sohusite.txt
sed -i "" 's/<\/content>//g' news_sohusite.txt

其中iconv命令的格式为:

iconv -f encoding [-t encoding] [inputfile]... 

参数含义为:

  • -f encoding :把字符从encoding编码开始转换。
  • -t encoding :把字符转换到encoding编码。
  • -l :列出已知的编码字符集合
  • -o file :指定输出文件
  • -c :忽略输出的非法字符
  • -s :禁止警告信息,但不是错误信息
  • --verbose :显示进度信息
  • -f和-t所能指定的合法字符在-l选项的命令里面都列出来了

中文切词

与处理英文不同,中文没有切词,需要使用jieba进行切词处理。

python -m jieba -d ' ' news_sohusite.txt > news_sohusite_cutword.txt

训练word2vec

完成预处理后,即可以利用gensim库进行训练。

def train_word2vec(filename):
    #模型文件不存在才处理
    if not os.path.exists(word2vec_file):
        sentences = LineSentence(filename)
        #sg=0 使用cbow训练, sg=1对低频词较为敏感
        model = Word2Vec(sentences,
                         size=n_dim, window=5, min_count=2, sg=1, workers=2)
        model.save(word2vec_file)

Word2Vec函数常见的几个参数含义如下:

  • sentences表示需要处理的语料
  • size表示word2vec的维数,一般50-300
  • window表示处理word时的窗口长度
  • min_count表示处理分析的word出现的最小次数
  • sg为1表示使用skip-gram算法,为0为cbow
  • workers表示计算使用的线程数
  • iter表示迭代计算的次数

使用word2vec处理中文

把一个中文句子使用词向量表示的方法。对于类似短信、微博、标题这些长度较短的文字,可以使用各个word的word2vec相加取平均来表示。对训练数据集创建词向量,接着进行比例缩放(scale)。

def buildWordVector(imdb_w2v,text, size):
    vec = np.zeros(size).reshape((1, size))
    count = 0.
    #print text
    for word in text.split():
        #print word
        try:
            vec += imdb_w2v[word].reshape((1, size))
            count += 1.
        except KeyError:
            print word
            continue
    if count != 0:
        vec /= count
    return vec

当需要把中文数据集X转换成word2vec,可以使用如下方式。

#加载训练好的词向量模型
model = Word2Vec.load(word2vec_file)

x_vecs = np.concatenate([buildWordVector(model,z, n_dim) for z in x])
x_vecs = scale(x_vecs)

测试效果

下面我们测试生成的word2vec模型的质量。

寻找近义词

寻找近义词是word2vec的一个应用场景。

百度的近义词

print pd.Series(model.most_similar(u'百度'))
0      (网易, 0.844283640385)
1    (搜索引擎, 0.822018146515)
2      (腾讯, 0.774820387363)
3       (搜狗, 0.76777946949)
4      (新浪, 0.760137319565)
5      (奇虎, 0.745484173298)
6      (文库, 0.725166857243)
7    (手机软件, 0.717750906944)
8       (优酷, 0.70574760437)
9      (客户端, 0.70448333025)

微信的近义词

print pd.Series(model.most_similar(u'微信'))
0     (摇一摇, 0.768034994602)
1      (陌陌, 0.763847649097)
2    (网上聊天, 0.751431167126)
3    (聊天工具, 0.731707036495)
4      (盗号, 0.722806692123)
5      (飞聊, 0.715048789978)
6      (手机, 0.706719994545)
7     (发短信, 0.704942345619)
8      (聊天, 0.691777765751)
9    (账号密码, 0.679741084576)

单词运算

word2vec的一个神奇之处就是把文字转换成了数字,数字之间的加减运算,同样适用于word2vec。

足球+明星

print pd.Series(model.most_similar(positive=[u'足球'+u'明星']))
0      (巨星, 0.741350233555)
1    (光芒万丈, 0.727712750435)
2     (和亨利, 0.722848057747)
3      (球星, 0.722578346729)
4       (已贵, 0.71345859766)
5     (格米利, 0.694822609425)
6     (支斯篮, 0.690492749214)
7      (田坛, 0.689639627934)
8      (体坛, 0.689606904984)
9     (竞神锋, 0.684816122055)

球星-明星

print pd.Series(model.most_similar(positive=[u'球星'],negative=[u'明星']))
dtype: object
0    (国际米兰, 0.492849290371)
1      (中锋, 0.480526059866)
2      (球员, 0.479797780514)
3     (上赛季, 0.479528963566)
4      (主帅, 0.479275196791)
5      (球队, 0.477513790131)
6     (德里奇, 0.474446773529)
7     (热那亚, 0.472252100706)
8      (中场, 0.459134191275)
9       (巴萨, 0.45858669281)

比较单词的相似度

比较微信和陌陌

print model.wv.similarity(u'微信', u'陌陌')
0.763847656891

比较男人和坏人

print model.wv.similarity(u'男人', u'坏人')
0.617036796702

使用多层感知机进行文档分类

多层感知机概述

生活中的神经网络

使用多层感知机进行文档分类-图1

人的大脑是由无数的神经元组成的复杂网络。神经元是具有长突起的细胞,它由细胞体和轴突、树突组成。

使用多层感知机进行文档分类-图2

每个神经元可以有一或多个树突,可以接受刺激并将兴奋传入细胞体。每个神经元只有一个轴突,可以把兴奋从胞体传送到另一个神经元或其他组织,如肌肉或腺体。

使用多层感知机进行文档分类-图3

神经网络算法概述

神经网络算法就是模拟了人体神经元的工作原理,多个输入参数,分别具有各自的权重,经过激励函数的处理后,得到输出。输出可以再对接下一级的神经网络的输入,从而组成更加复杂的神经网络。

使用多层感知机进行文档分类-图4 大脑里的生物神经细胞和其他的神经细胞是相互连接在一起的。为了创建一个人工神经网络,人工神经细胞也要以同样方式相互连接在一起。为此可以有许多不同的连接方式,其中最容易理解并且也是最广泛地使用的,就是把神经细胞一层一层地连结在一起。这一种类型的神经网络就叫前馈网络。

使用多层感知机进行文档分类-图5

如果对预测错误的神经元施加惩罚,从输出层开始层层向上查找预测错误的神经元,微调这些神经元对应的权重,达到修复错误的目的,这样的算法就叫做反向传播算法。Scikit-learn中的神经网络实现都是使用反向传播算法。本文重点介绍算法在安全领域的应用,神经网络的公式推导请参考其他机器学习专业书籍。本书介绍的是神经网络中最简单的一种形式,即多层感知机。

数据集

数据集依然使用搜狗实验室提供的"搜狐新闻数据",该数据来自搜狐新闻2012年6月—7月期间国内,国际,体育,社会,娱乐等18个频道的新闻数据,提供URL和正文信息。 对应的网址为:

http://www.sogou.com/labs/resource/cs.php

我们选择其中数量最大的三个频道的数据进行分析,数据清洗过程请参考我之前的文章《使用fasttext进行文档分类》

特征提取

词袋&TFIDF

特征提取的方式采用词袋结合TFIDF的方式。

#切割词袋
vectorizer = CountVectorizer()
# 该类会统计每个词语的tf-idf权值
transformer = TfidfTransformer()
x = transformer.fit_transform(vectorizer.fit_transform(x))

n-gram&TFIDF

特征提取还可以使用词袋模型的加强版n-gram,比如最常见的2-gram,这样可以更好的提取单词前后之间的关系。

#切割词袋
vectorizer = CountVectorizer(ngram_range=(2,2))
# 该类会统计每个词语的tf-idf权值
transformer = TfidfTransformer()
x = transformer.fit_transform(vectorizer.fit_transform(x))

one-hot编码

由于这次需要区分的标签一共有三种,不能直接使用过去二分类问题的方式进行处理了。针对多分类问题,需要使用one-hot编码处理标签。所谓的one-hot编码,又称为一位有效编码,主要是采用位状态寄存器来对个状态进行编码,每个状态都由他独立的寄存器位,并且在任意时候只有一位有效。比如我们有三类标签,就可以编码为:

[1,0,0]
[0,1,0]
[0,0,1]

在keras中可以非常方便的进行转换。

#转换成one hot编码
y=to_categorical(t, num_classes=3)

训练与效果验证

在scikit-learn中构造多层感知机的分类器非常方便,通常只需要设计神经网络的结构即可。本例中设计的隐藏层有两层,结点数分为为5和3,通常最后一层的结点数与标签类型数相同。

#mlp
clf = MLPClassifier(solver='lbfgs',
                    alpha=1e-5,
                    hidden_layer_sizes=(5, 3),
                    random_state=1)
                    

其中比较重要的几个参数的含义为:

- hidden_layer_sizes,表示隐藏层的结构
- activation,激活函数,{‘identity’, ‘logistic’, ‘tanh’, ‘relu’}, 默认relu
- solver,优化方式,{‘lbfgs’, ‘sgd’, ‘adam’}, 默认adam。lbfgs使用quasi-Newton方法的优化器,sgd使用随机梯度下降,adam也是一种随机梯度的优化器
- alpha,可选的,默认0.0001,正则化项参数    

效果验证使用5折交叉验证,考核的指标是f1和accuracy。使用cross_val_score函数可以非常方便的实现交叉验证的功能,其中cv参数指定交叉验证的方式,比如5代表5折交叉验证。

 scores = cross_val_score(clf, x, y, cv = 5,scoring='f1_micro')
print("f1: %0.2f (+/- %0.2f)" % (scores.mean(), scores.std() * 2))
scores = cross_val_score(clf, x, y, cv = 5,scoring='accuracy')
print("accuracy: %0.2f (+/- %0.2f)" % (scores.mean(), scores.std() * 2))

这里需要特别说明的是,在二分类问题中,我们考核f1即可,但是在多分类问题中,f1以微平均和宏平均两个指标的形式存在,分别为f1\_micro和f1\_macro。f1\_micro对数据集中的每一个实例不分类别进行统计建立全局混淆矩阵,然后计算相应指标。f1\_macro是先对每一个类统计指标值,然后在对所有类求算术平均值。通常使用其中任何一个即可。 运行程序,当使用词袋&TFIDF模型时,效果如下:

f1: 0.98 (+/- 0.04)
accuracy: 0.97 (+/- 0.05)

当使用n-gram&TFIDF模型的效果,有兴趣的读者可以自行验证。


使用fasttext进行文档分类

fasttext原理

fasttext提供了一种有效且快速的方式生成词向量以及进行文档分类。 fasttext模型输入一个词的序列,输出这个词序列属于不同类别的概率。fasttext模型架构和Word2Vec中的CBOW模型很类似。不同之处在于,fasttext预测标签,而CBOW模型预测中间词。fasttext设计的初衷就是为了作为一个文档分类器,副产品是也生成了词向量。 使用fasttext进行文档分类-图2

fasttext特性

n-gram

在词袋模型中,把单词当做独立的个体,没有考虑词前后的关系。比如"我打你"和“你打我“,使用词袋模型的话,这两句话是完全一样的。 词袋的特征为:

["我",“打“,”你”]

"我打你"和“你打我“对应的特征向量均为:

[1,1,1]

n-gram是对词袋模型的一种改善,它会关注一个单词的前后关系,比如n-gram中最常见的2-gram,就关注单词的前一个词,比如"我打你",就可以拆分为"我打"和"打你"。这两句话一起建模的话,2-gram对应的特征为:

["我打","打你","你打","打我"]

"我打你"对应的特征向量为:

[1,1,0,0]

"你打我"对应的特征向量为:

[0,0,1,1]

与Word2Vec使用词袋模型不同,fasttext使用了n-gram模型,因此fasttext可以更有效的表达词前后的之间的关系。

高效率

fasttext在使用标准多核CPU的情况下10分钟内处理超过10亿个词汇,特别是与深度模型对比,fastText能将训练时间由数天缩短到几秒钟。使用一个标准多核CPU,得到了在10分钟内训练完超过10亿词汇量模型的结果。

安装fasttext

fasttext的安装非常简便,直接从github上同步最新的代码并进行安装即可。

$ git clone https://github.com/facebookresearch/fastText.git
$ cd fastText
$ pip install .

预训练模型

facebook已经基于其收集的海量语料,训练好了fasttext的词向量模型,目前已经支持了150多种语言。有需要的读者可以直接下载并使用,对应的链接为:

https://github.com/facebookresearch/fastText/blob/master/docs/crawl-vectors.md

使用fasttext进行文档分类

数据集

数据集依然使用搜狗实验室提供的"搜狐新闻数据",该数据来自搜狐新闻2012年6月—7月期间国内,国际,体育,社会,娱乐等18个频道的新闻数据,提供URL和正文信息。 对应的网址为:

http://www.sogou.com/labs/resource/cs.php

数据文件的格式为:

页面URL

页面ID

页面标题

页面内容

我们可以看到数据文件中并没有标记页面内容属于哪个频道,如果需要做文档分类,搜狗提供了页面URL和频道之间的映射关系。

使用fasttext进行文档分类-图3

下载SogouTCE文件,可以看到具体的映射关系举例如下:

http://www.xinhuanet.com/auto/	汽车
http://www.xinhuanet.com/fortune	财经
http://www.xinhuanet.com/internet/	IT
http://www.xinhuanet.com/health/	健康
http://www.xinhuanet.com/sports	体育
http://www.xinhuanet.com/travel	旅游
http://www.xinhuanet.com/edu	教育
http://www.xinhuanet.com/employment	招聘
http://www.xinhuanet.com/life	文化
http://www.xinhuanet.com/mil	军事
http://www.xinhuanet.com/olympics/	奥运
http://www.xinhuanet.com/society	社会

数据清洗

搜狐新闻数据的文件默认编码格式为gb18030,因此解压缩后要线转换成utf-8格式。

tar -zxvf news_sohusite_xml.full.tar.gz
cat news_sohusite_xml.dat | iconv -f gb18030 -t utf-8 > news_sohusite_xml-utf8.txt

转换完格式后查看文件内容,文件以xml形式记录,举例如下:

http://gongyi.sohu.com/s2008/sourceoflife/ f2467af22cd2f0ea-34913306c0bb3300 中国西部是地球上主要干旱带之一,妇女是当地劳动力... 同心县地处宁夏中部干旱带的核心区, 冬寒长,春暖迟,夏热短,秋凉早,干旱少雨,蒸发强烈,风大沙多。主要自然灾害有沙尘暴、干热风、霜冻、冰雹等,其中以干旱危害最为严重。由于生态环境的极度恶劣,导致农村经济发展缓慢,人民群众生产、生活水平低下,靠天吃饭的被动局 面依然存在,同心,又是国家级老、少、边、穷县之一…[详细]

但是数据文件并不是标准的xml格式,如下所示,该文件相对标准的xml格式缺少了根元素。

所有的doc节点都直接是最顶层,没有根节点。因此要添加根节点使该文本文件符合xml文件的规范,最简单的一种形式就是在文件的开始和结尾添加根元素标签。

<?xml version="1.0" encoding="utf-8"?>
<docs>
	<doc>
	    <url></url>
	    <docno></docno>
	    <contenttitle></contenttitle>
	    <content></content>
	</doc>
	<doc>
	    <url></url>
	    <docno></docno>
	    <contenttitle></contenttitle>
	    <content></content>
	</doc>
</docs>

可以直接使用文本编辑工具在数据文件的开始和结尾进行修改,但是这有可能导致你的终端因为内存使用过大而崩溃。一种比较稳妥的做法是使用程序完成。

def make_xml():
	print "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
    print "<docs>"
    with open("data/news_sohusite_xml-utf8.txt") as F:
        for line in F:
            print line
        F.close()
    print "</docs>"
    

在终端执行该程序,并将标准输出的结果保存即可,剩下的操作只要解析xml文件即可。下面我们介绍另一种方法,观察可以发现,url和content是成对出现的,并且一一对应。我们可以过滤这两个字段的内容,分别保存成content文件和url文件。 首先过滤出url字段的内容,并且删除掉url标签。

cat news_sohusite_xml-utf8.txt | grep '<url>' | sed  's/<url>//g' | sed  's/<\/url>//g' > news_sohusite_url.txt

然后过滤出content字段的内容,并且删除掉content标签。

cat news_sohusite_xml-utf8.txt | grep '<content>' | sed  's/<content>//g' | sed  's/<\/content>//g' > news_sohusite_content.txt

content是中文内容,需要使用jieba进行切词,可以把切词的动作也放到上面的命令里面。

cat news_sohusite_xml-utf8.txt | grep '<content>' | sed  's/<content>//g' | sed  's/<\/content>//g' | python -m jieba -d ' '  > news_sohusite_content.txt

加载url和对应领域的映射关系的文件,以哈希的形式保存对应的映射关系。

def load_SogouTCE():
    SogouTCE=[]
    SogouTCE_kv = {}
    with open("../data/SogouTCE.txt") as F:
        for line in F:
            (url,channel)=line.split()
            SogouTCE.append(url)
        F.close()
    for index,url in enumerate(SogouTCE):
        #删除http前缀
        url=re.sub('http://','',url)
        print "k:%s v:%d" % (url,index)
        SogouTCE_kv[url]=index
    return  SogouTCE_kv
    

我们分析下各个领域的数据分布情况,把匹配上的url对应的标记打印出来。

def load_url(SogouTCE_kv):
    labels=[]
    with open("../data/news_sohusite_url.txt") as F:
        for line in F:
            for k,v in SogouTCE_kv.items():
                if re.search(k,line,re.IGNORECASE):
                    #print "x:%s y:%d" % (line,v)
                    print v
                    labels.append(v)
        F.close()
    return  labels
   

运行程序,分析各个领域对应的url数量。

python fasttext.py > v.txt
cat v.txt | sort -n | uniq -c

每行的第一个字段是数量,第二个字段是对应的领域的id,结果表明搜狐新闻数据集中在某几个领域,并且分布不均匀。为了避免样本不均衡导致的误判,我们选择数量上占前三的领域作为后继分析的数据,id分别为81,79和91。

138576 79
27489 80
199871 81
23409 82
44537 83
2179 84
13012 85
1924 87
3294 88
 842 89
50138 91
5882 92

反查对应的url为:

kit.sohu.com/ id:81
auto.sohu.com/ id:79
yule.sohu.com/ id:91

过滤我们关注的领域的内容,将content保存在x列表里,对应的领域的id保存在y列表里,作为标签使用,至此我们完成了数据清洗的工作。

def load_selecteddata(SogouTCE_kv):
    x=[]
    y=[]

    #加载content列表
    with open("../data/news_sohusite_content.txt") as F:
        content=F.readlines()
        F.close()

    # 加载url列表
    with open("../data/news_sohusite_url.txt") as F:
        url = F.readlines()
        F.close()

    for index,u in  enumerate(url):
        for k, v in SogouTCE_kv.items():
            # 只加载id为81,79和91的数据
            if re.search(k, u, re.IGNORECASE) and v in (81, 79, 91):
                #保存url对应的content内容
                x.append(content[index])
                y.append(v)

    return x,y
   

删除停用词

在处理中文语料时,需要删除停用词。所谓停用词就是对理解中文含义没有明显作用的哪些单词,常见的停用词举例如下:

一一  
一下  
一个  
一些  
一何  
一切  
一则  
一则通过  
一天  
一定  
一方面  
一旦  
一时 

另外所有的字母和数字还有标点符号也可以作为停用词。我们把停用词保存在一个文本文件里面便于配置使用。 定义加载停用词的函数。

def load_stopwords():
    with open("stopwords.txt") as F:
        stopwords=F.readlines()
        F.close()
    return [word.strip() for word in stopwords]
    

使用停用词过滤之前提取的文本内容。

stopwords=load_stopwords()
#切割token
x=[  [word for word in line.split() if word not in stopwords]   for line in x]

文档分类

数据文件格式

fasttext对训练和测试的数据格式有一定的要求,数据文件和标签文件要合并到一个文件里面。文件中的每一行代表一条记录,同时每条记录的最后标记对应的标签。默认情况下标签要以\_\_label\_\_开头,比如:

这是一条测试数据	__label__1

python下实现合并数据文件和标签文件的功能非常简单。

def dump_file(x,y,filename):
    with open(filename, 'w') as f:
        for i,v in enumerate(x):
            line="%s __label__%d\n" % (v,y[i])
            f.write(line)
        f.close()
        

加载数据清洗后的数据和标签,随机划分成训练数据和测试数据,其中测试数据占20%。

SogouTCE_kv=load_SogouTCE()
x,y=load_selecteddata(SogouTCE_kv)
# 分割训练集和测试集
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2)

按照fasttext的格式要求保存成训练数据和测试数据。

#按照fasttest的要求生成训练数据和测试数据
dump_file(x_train,y_train,"../data/sougou_train.txt")
dump_file(x_test, y_test, "../data/sougou_test.txt")

查看训练数据文件的内容,举例如下:

2 0 1 2 款 长安 标致 雪铁龙 D S 4 / D S 5 九寨沟 试驾 __label__79
 

训练模型

下面开始训练fasttext模型。

# train_supervised uses the same arguments and defaults as the fastText cli
model = train_supervised(
        input="../data/sougou_train.txt", epoch=25, lr=0.6, wordNgrams=2, verbose=2, minCount=1
    )

其中比较重要的几个参数的含义为:

  • input;表示训练数据文件的路径
  • epoch:表示训练的次数
  • lr:表示初始的学习速率
  • wordNgrams:表示n-gram的值,一般使用2,表示2-gram
  • minCount:表示参与计算的单词的最小出现次数。

验证效果

fasttext默认情况下会计算对应的准确率和召回率。

def print_results(N, p, r):
    print("N\t" + str(N))
    print("P@{}\t{:.3f}".format(1, p))
    print("R@{}\t{:.3f}".format(1, r))
    

使用测试数据文件进行校验。

print_results(*model.test("../data/sougou_test.txt"))

运行程序,显示加载了36M的单词,其中包含288770的单词组合,标记类型一共3种。

Read 36M words
Number of words:  288770
Number of labels: 3

验证效果如下所示,准确率为99.0%,召回率为99.0%,对应的F1计算为99.0%,效果非常不错。

Progress: 100.0% words/sec/thread:  626183 lr:  0.000000 loss:  0.005640 ETA:   0h 0m 
N	71107
P@1	0.990
R@1	0.990

使用LDA进行文档主题建模

LDA简介

LDA(Latent Dirichlet Allocation)是一种文档主题模型,包含词、主题和文档三层结构。

使用LDA进行文档主题建模-图1

LDA认为一篇文档由一些主题按照一定概率组成,一个主题又由一些词语按照一定概率组成。早期人们用词袋模型对一篇文章进行建模,把一篇文档表示为若干单词的计数。无论是中文还是英文,都由大量单词组成,这就造成词袋向量的维数巨大,少则几千多则上万,在使用分类模型进行训练时,非常容易造成训练缓慢以及过拟合。LDA本质上把词袋模型进行了降维,把一篇文档以主题的形式进行了表示。主题的个数通常为几百,这就把文档使用了维数为几百的向量进行了表示,大大加快了训练速度,并且相对不容易造成过拟合。从某种程度上来说,主题是对若干词语的抽象表示。

使用LDA进行文档主题建模-图3

以最近一部电视剧《南方有乔木》为例。假设一篇文章介绍了这部电视剧的主要内容。我们可以把这篇文章表示为:

0.30*"创业"+0.3*"三角恋"+0.2*"无人机"

然后我们可以把三角恋表示为:

0.4*"南乔"+0.3*"时樾"+0.3*"安宁"

需要指出的是,计算出文档、主题以及词语之间的表示关系,需要基于大量的文档,这样才具有普遍的意义。LDA正是提供了这种算法,自动从训练文档中计算出这种对应关系。

数据集

本文演示用的数据集,依然使用搜狗新闻数据集SogouCS。我们从SogouCS中提取正文内容,每个URL对应的正文当做一篇文档,并且使用jieba进行了分词。演示期间我们提取SogouCS的前10000条数据用于计算LDA。

def load_sougou_content():
    with open("../data/news_sohusite_content_10000.txt") as F:
        content=F.readlines()
        F.close()
    return content

计算LDA时,需要删除停用词,加载我们之前保存的停用词。

def load_stopwords():
    with open("stopwords.txt") as F:
        stopwords=F.readlines()
        F.close()
    return [word.strip() for word in stopwords]

计算主题

我们使用gensim提供的API进行LDA计算。首先从语料中提取字典,并用该字典把预料转换成词袋。

 # 得到文档-单词矩阵 (直接利用统计词频得到特征)
 dictionary = corpora.Dictionary(content)
 # 将dictionary转化为一个词袋,得到文档-单词矩阵
 texts = [dictionary.doc2bow(text) for text in content]
 

然后进行LDA计算,演示期间设置只计算5个主题,通常生产环境经验值为200。

num_topics=5
lda = models.ldamodel.LdaModel(corpus=texts, id2word=dictionary, num_topics=num_topics)

其中比较重要的几个参数含义如下:

  • corpus,计算LDA的语料
  • id2word,语料对应的字典
  • num_topics,计算的主题的数量

我们打印前5个主题。

for index,topic in lda.print_topics(5):
    print topic

主题内容如下所示。

0.007*"月" + 0.006*"中" + 0.005*"年" + 0.005*"日" + 0.004*"公司" + 0.004*"时间" + 0.003*"北京" + 0.003*"比赛" + 0.002*"中国" + 0.002*"记者"

0.006*"月" + 0.006*"发展" + 0.005*"年" + 0.005*"日" + 0.005*"中" + 0.005*"中国" + 0.004*"工作" + 0.004*"说" + 0.003*"记者" + 0.003*"建设"

0.008*"月" + 0.007*"市场" + 0.006*"经济" + 0.005*"增长" + 0.004*"元" + 0.004*"中国" + 0.004*"企业" + 0.004*"产品" + 0.004*"年" + 0.004*"记者"

0.011*"日" + 0.011*"月" + 0.007*"记者" + 0.005*"时" + 0.005*"年" + 0.004*"公司" + 0.004*"说" + 0.004*"中" + 0.003*"发现" + 0.002*"亿元"

0.006*"o" + 0.006*"i" + 0.006*"月" + 0.005*"日" + 0.005*"e" + 0.005*"说" + 0.005*"n" + 0.005*"中" + 0.004*"中国" + 0.004*"a"

如果需要设置每个话题对应的关键字的个数,可以通过参数num_words设置,默认为10,这里的num_topics参数容易导致误解,它的含义是显示排名前几个话题,类似topN参数。

print_topics(num_topics=20, num_words=10)

词袋处理后的结果,使用TFIDF算法处理后,可以进一步提升LDA的效果。

# 利用tf-idf来做为特征进行处理
texts_tf_idf = models.TfidfModel(texts)[texts]
lda = models.ldamodel.LdaModel(corpus=texts_tf_idf, id2word=dictionary, num_topics=num_topics)

运行的效果如下所示。

0.001*"比赛" + 0.001*"联赛" + 0.001*"意甲" + 0.001*"主场" + 0.001*"轮" + 0.001*"赛季" + 0.001*"时间" + 0.001*"孩子" + 0.001*"北京" + 0.000*"航天员"

0.001*"叙利亚" + 0.001*"奥运会" + 0.001*"伦敦" + 0.000*"选手" + 0.000*"中国" + 0.000*"说" + 0.000*"男子" + 0.000*"米" + 0.000*"北京" + 0.000*"日"

0.000*"梅西" + 0.000*"林书豪" + 0.000*"鲁尼" + 0.000*"稀土" + 0.000*"钓鱼岛" + 0.000*"巴萨" + 0.000*"试用" + 0.000*"常规赛" + 0.000*"蛋黄派" + 0.000*"尼克斯"

0.002*"体育" + 0.001*"搜狐" + 0.001*"北京" + 0.001*"o" + 0.001*"时间" + 0.001*"n" + 0.001*"i" + 0.001*"日" + 0.001*"C" + 0.001*"e"

0.001*"市场" + 0.001*"经济" + 0.001*"增长" + 0.001*"投资" + 0.001*"亿元" + 0.001*"基金" + 0.001*"公司" + 0.001*"银行" + 0.001*"企业" + 0.001*"同比"

使用LDA提取文档特征

通常LDA的结果可以作为进一步文档分类、文档相似度计算以及文档聚类的依据,可以把LDA当做一种特征提取方法。

#获取语料对应的LDA特征
corpus_lda = lda[texts_tf_idf]
#打印0号文档对应的LDA值
print corpus_lda[0]

输出0号文档对应的LDA值如下,即把0号文档以5个话题形式表示。

[(0, 0.019423252), (1, 0.019521076), (2, 0.92217809), (3, 0.01954053), (4, 0.019337002)]

这里需要解释的是,无论是词袋模型还是LDA生成的结果,都可能存在大量的0,这会占用大量的内存空间。因此默认情况下,词袋以及LDA计算的结果都以稀疏矩阵的形式保存。稀疏矩阵的最小单元定义为:

(元素所在的位置,元素的值)

比如一个稀疏矩阵只有0号和2号元素不为0,分别为1和5,那么它的表示方法如下:

[(0,1),(2,5)]

使用多核计算

LDA在生产环境中运行遇到的最大问题就是默认只能使用单核资源,运行速度过慢。gensim针对这一情况也提供了多核版本。

lda = models.ldamulticore.LdaMulticore(corpus=texts_tf_idf, id2word=dictionary, num_topics=num_topics)

该版本默认情况默认使用cpu_count()-1 即使用几乎全部CPU,仅保留一个CPU不参与LDA计算,也可以通过参数workers指定使用的CPU个数,这里的CPU指的物理CPU,不是超线程的CPU数。一般认为:

CPU总核数 = 物理CPU个数 * 每颗物理CPU的核数 
总逻辑CPU数 = 物理CPU个数 * 每颗物理CPU的核数 * 超线程数

可以尝试使用如下命令查看服务器的CPU信息。

# 查看CPU信息(型号)
cat /proc/cpuinfo | grep name | cut -f2 -d: | uniq -c
24         Intel(R) Xeon(R) CPU E5-2630 0 @ 2.30GHz

# 查看物理CPU个数
cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l
2

# 查看每个物理CPU中core的个数(即核数)
cat /proc/cpuinfo| grep "cpu cores"| uniq
cpu cores    : 6

# 查看逻辑CPU的个数
cat /proc/cpuinfo| grep "processor"| wc -l
24

gensim官网上公布了一组测试数据,数据集为Wikipedia英文版数据集,该数据集有350万个文档,10万个特征,话题数设置为100的情况下运行LDA算法。硬件环境为一台拥有4个物理i7 CPU的服务器。使用单核接口需要使用3小时44分,当使用多核接口且使用3个物理CPU仅需要1小时6分钟。我们延续上面的例子,继续使用搜狗的数据集,为了要效果更加明显,我们使用SogouCS的前50000的数据进行LDA运算,话题数设置为100,并使用time.clock()获取当前时间,便于计算各个环节消耗的时间,比如计算LDA消耗的时间的方法如下所示。

#获取当前时间
start = time.clock()
#workers指定使用的CPU个数 默认使用cpu_count()-1 即使用几乎全部CPU 仅保留一个CPU不参与LDA计算
lda = models.ldamulticore.LdaMulticore(corpus=texts_tf_idf, id2word=dictionary, num_topics=num_topics)
#计算耗时
end = time.clock()
print('[lda]Running time: %s Seconds' % (end - start))

分别计算使用1,2,4等不同CPU的情况,统计了预处理环节、LDA环节的耗时,其中预处理环节主要进行了词袋处理和TFIDF处理,测试用的服务器一共有12个物理CPU。

CPU数 预处理环节耗时(单位:秒) LDA环节耗时(单位:秒)
默认 24 169
1 25 437
2 24 296
4 25 215
6 23 189
8 25 186
10 23 171
12 23 160

测试结果表明,使用多CPU资源可以提升LDA的计算效率,尤其对于CPU使用个数较少时,几乎成线性下降,当使用CPU个数较多时,性能改善不明显。在本例中默认参数将使用11个CPU,因此默认参数下计算速率略低于使用12个CPU。

使用LDA进行文档主题建模-图4.png

在线学习

LDA可以进行在线学习,动态更新模型。

lda = LdaMulticore(corpus, num_topics=10)
lda.update(other_corpus)

参考文档


使用Jieba进行中文词性标注

词性

词性指以词的特点作为划分词类的根据。现代汉语的词可以分为两类14种词性。

常见词性分类

词性分类又叫词性标注(Part-Of-Speech tag, POS-tag),常见的词性标准类型如下:

  1. 名词
- n 名词
- nr 人名
- nr1 汉语姓氏
- nr2 汉语名字
- nrj 日语人名
- nrf 音译人名
- ns 地名
- nsf 音译地名
- nt 机构团体名
- nz 其它专名
- nl 名词性惯用语
- ng 名词性语素
  1. 时间词
- t 时间词
- tg 时间词性语素
  1. 处所词
- s 处所词 (在公司,在学校)
  1. 方位词
- f 方位词
  1. 动词
- v 动词
- vd 副动词
- vn 名动词
- vshi 动词“是”
- vyou 动词“有”
- vf 趋向动词
- vx 形式动词
- vi 不及物动词(内动词)
- vl 动词性惯用语
- vg 动词性语素
  1. 形容词
- a 形容词
- ad 副形词
- an 名形词
- ag 形容词性语素
- al 形容词性惯用语
  1. 区别词
- b 区别词 
- bl 区别词性惯用语
  1. 状态词
- z 状态词
  1. 代词
- r 代词
- rr 人称代词
- rz 指示代词
- rzt 时间指示代词
- rzs 处所指示代词
- rzv 谓词性指示代词
- ry 疑问代词
- ryt 时间疑问代词
- rys 处所疑问代词
- ryv 谓词性疑问代词
- rg 代词性语素
  1. 数词
- m 数词
- mq 数量词
  1. 量词
- q 量词
- qv 动量词
- qt 时量词
  1. 副词
- d 副词
  1. 介词
- p 介词
- pba 介词“把”
- pbei 介词“被”
  1. 连词
- c 连词
- cc 并列连词

使用Jieba词性分类

Jieba下进行词性分类非常简便。

seg_lig = jieba.posseg.cut(text)
for w,tag in seg_lig:
    print "%s /%s" % (w,tag)

以经典句子为例,“我爱北京天安门“,词性分类的结果为:

我 /r
爱 /v
北京 /ns
天安门 /ns

使用一个稍微复杂的例子。

据半岛电视台援引叙利亚国家电视台称,叙利亚已经对美国、英国、法国的空袭进行了反击。据介绍,在叙军武器库中,对西方最具威慑力的当属各型战术地对地弹道导弹。尽管美英法是利用巡航导弹等武器发动远程空袭,但叙军要对等还击却几乎是“不可能完成的任务”。目前叙军仍能作战的战机仍是老旧的苏制米格-29、米格-23、米格-21战斗机和苏-22、苏-24轰炸机

由于文字较多,使用分行显示会十分乱,我们稍微修改代码,让分词后的词性标注结果紧跟着原单词。

seg_lig = jieba.posseg.cut(text)
print " ".join(["%s /%s" % (w,tag) for w,tag in seg_lig])

分词的结果如下所示。

据 /p 半岛 /n 电视台 /n 援引 /vn 叙利亚 /ns 国家 /n 电视台 /n 称 /v , /x 叙利亚 /ns 已经 /d 对 /p 美国 /ns 、 /x 英国 /ns 、 /x 法国 /ns 的 /uj 空袭 /v 进行 /v 了 /ul 反击 /v 。 /x 据介绍 /n , /x 在 /p 叙军 /n 武器库 /n 中 /f , /x 对 /p 西方 /s 最 /d 具 /v 威慑力 /n 的 /uj 当属 /n 各型 /r 战术 /n 地对地 /n 弹道导弹 /n 。
 尽管 /c 美英 /nz 法 /j 是 /v 利用 /n 巡航导弹 /n 等 /u 武器 /n 发动 /vn 远程 /n 空袭 /v , /x 但 /c 叙军 /n 要 /v 对 /p 等 /u 还击 /v 却 /d 几乎 /d 是 /v “ /x 不 /d 可能 /v 完成 /v 的 /uj 任务 /n ” /x 。 /x 目前 /t 叙军 /n 仍 /d 能 /v 作战 /v 的 /uj 战机 /n 仍 /d 是 /v 老 /a 旧 /a 的 /uj 苏制 /n 米格 /nrt - /x 29 /m 、 /x 米格 /nrt - /x 23 /m 、 /x 米格 /nrt - /x 21 /m 战斗机 /n 和 /c 苏 /ns - /x 22 /m 、 /x 苏 /j - /x 24 /m 轰炸机 /n , /x 它们 /r 在 /p 现代化 /vn 的 /uj 西方 /s 空军 /n 面前 /f 难 /a 有 /v 自保 /vn 之 /u 力 /n , /x 因此 /c 叙军 /n 的 /uj 远程 /n 反击 /v 只能 /v 依靠 /v 另 /r 一个 /m 撒手锏 /n — /x — /x 地对地 /n 战术 /n 弹道导弹 /n 。

参考文献


使用TextRank和TFIDF进行关键字自动提取

文档关键字

早期的信息检索平台并没有如今这么发达,大量的类似论坛、wiki的信息发布网站都依赖文档的关键字进行文档检索。即使是在如今的互联网世界,大量的信息检索还要依赖关键字。依靠人工归纳文章的关键字虽然相对准确,但是面对信息爆炸的互联网,这几乎成为了不可能的任务。本章将介绍如何使用TextRank和TFIDF算法自动提取文档的关键字。

TextRank

TextRank的思路来自于PageRank。PageRank最开始用来计算网页的重要性。整个互联网可以看作一张有向图,节点是网页。如果网页A存在到网页B的链接,那么有一条从网页A指向网页B的有向边,指向同一个网页的链接越多,该网页的重要性或者说PageRank值更大。综合考虑Title和Keywords等其它因素之后,Google通过PageRank来调整结果,使那些更重要的网页在搜索结果排名更靠前。

使用TextRank和TFIDF进行文档关键字自动提取-图2

TextRank的原理和PageRank类似,一篇文档去掉停用词以后,每个单词相当于一个网页,有时候也会指定某些词性的单词才参与计算TextRank,比如名词和动词。网页有明显的指向关系,但是文档的单词之间只有前后关系,所以要指定一个滑动的窗口大小,比如前后5个单词。在滑动窗口内的单词之间的前后关系当做网页之间的指向关系,参与TextRank的计算。 使用TextRank和TFIDF进行文档关键字自动提取-图1

TFIDF

TFIDF在本书中多次提到了,它同样可以用于提取关键字。TFIDF的一个基本假设是,一个单词的重要性由词频决定,如果一个单词在一句话里出现频率高,同时在其他句子里出现频率低,那么这个单词对这句话就非常重要,对于一个文档也是如此。

测试数据

测试数据挑选了写本章时的一条新闻,新闻内容如下:

据半岛电视台援引叙利亚国家电视台称,叙利亚已经对美国、英国、法国的空袭进行了反击。据介绍,在叙军武器库中,对西方最具威慑力的当属各型战术地对地弹道导弹。尽管美英法是利用巡航导弹等武器发动远程空袭,但叙军要对等还击却几乎是“不可能完成的任务”。目前叙军仍能作战的战机仍是老旧的苏制米格-29、米格-23、米格-21战斗机和苏-22、苏-24轰炸机,它们在现代化的西方空军面前难有自保之力,因此叙军的远程反击只能依靠另一个撒手锏——地对地战术弹道导弹.

提取关键字

使用TextRank提取关键字

Jieba提供了TextRank实现,直接使用默认参数就可以完成关键字的提取。

# 引入TextRank关键词抽取接口
textrank = analyse.textrank
# 基于TextRank算法进行关键词抽取
keywords = textrank(text)
# 输出抽取出的关键词
for keyword in keywords:
    print keyword + "/"

提取的结果如下所示,自动化提取关键字的结果差强人意,其中"只能"、"据介绍"这些完全可以省略,另外通常关键字个数需要控制在10个以内。

叙军/
远程/
空袭/
电视台/
战术/
反击/
空军/
现代化/
叙利亚/
地对地/
只能/
武器/
发动/
弹道导弹/
任务/
国家/
据介绍/
法国/
进行/
当属/

Jieba提供了接口,设置关键字的个数以及提取的关键字的词性,比如:

  • topK,指定关键字的个数
  • allowPOS,指定关键字的词性,常见的词性包括:
- n 名词
- nr 人名
- ns 地名
- nz 其它专名
- t 时间词
- v 动词
- vd 副动词
- vn 名动词

我们只提取10个关键字,且只关注名词和动词以及名动词。

# 基于TextRank算法进行关键词抽取
keywords = textrank(text,topK = 10, withWeight = False, allowPOS = ('n','ns','vn','v'))

生成的结果如下所示,基本满足我们的需求了。

叙军/
远程/
空袭/
电视台/
战术/
反击/
空军/
现代化/
叙利亚/
地对地/

使用TFIDF提取关键字

使用TFIDF提取关键字的方法和TextRank类似。

# TFIDF
keywords_tfidf = analyse.extract_tags(text,topK = 10, withWeight = False, allowPOS = ('n','ns','vn','v','nz'))
# 输出抽取出的关键词
for keyword in keywords_tfidf:
    print keyword + "/"

生成的结果如下所示,与TextRank相比差别不大。

叙军/
地对地/
空袭/
弹道导弹/
远程/
叙利亚/
电视台/
反击/
战术/
撒手锏/

参考资料

  • Rada Mihalcea and Paul Tarau,TextRank: Bringing Order into Texts
  • The PageRank Citation Ranking:Bringing Order to the Web

文档相似度

概述

每年毕业季,各位学子都会使用一个叫做论文查重系统,来检测是否毕业论文存在抄袭嫌疑。如何衡量两篇论文之间的相似度呢?面对类似的问题,人们提出了文档相似度的概念。文档相似度指的是两篇文档之间的相似程度,也被称为文档距离。文档相似度通常是文本聚类、信息检索等NLP任务的基础,常见的计算文档距离的方法包括simhash和余弦距离。

simhash算法

simhash是由Charikar在2002年提出来的,论文名为《Similarity estimation techniques from rounding algorithms》。Google基于simhash在海量网页中进行相似度计算并去重。通常对比两个文档是否相同时,会计算对应的hash值,常见的算法包括md5和sha256。实际使用中,对于检测文档是否被篡改时,使用hash值具有不错的表现。但是当文档内容因为修改少许文字,插入广告甚至只是修改了标点符合和错别字,都会导致hash值改变,可是文档的核心内容并未发生改变。如何使用数学的方法表征这种文档相似性呢?simhash的设计初衷就是使用一种所谓局部hash的方法,可以既可以敏感的识别文档的少许修改又可以识别出文档的大多数内容相同。

simhash的一种典型实现就是将一个文档最后转换成一个64位的字节的特征字或者说simhash值,然后判断重复只需要判断他们的特征字的距离是不是小于3,就可以判断两个文档是否相似。这个距离使用海明距离,即两个simhash值取异或后二进制中1的个数。大家可以结合自身业务特点修改simhash值的位数以及判断文档相似性的海明距离的值。

文档相似度-图1

如图所示,计算6位simhash值典型的实现算法为:

  • 将Doc分词和计算权重,抽取出n个(关键词,权重)对,即图中的(feature, weight)
  • 计算关键词的hash,生成图中的(hash,weight),并将hash和weight相乘,这一过程是对hash值加权
  • 将hash和weight相乘的值相加,比如图中的[13, 108, -22, -5, -32, 55],并最终转换成simhash值110001,转换的规则为正数为1负数为0

simhash库

simhash具有多种实现,常用的一种已经部署在pip源上了,直接安装即可。

pip install simhash

有兴趣的读者也可以使用源码安装。

git clone https://github.com/leonsim/simhash
cd simhash/
python setup.py 
python setup.py install

数据集

数据集依然使用搜狗实验室提供的"搜狐新闻数据",该数据来自搜狐新闻2012年6月—7月期间国内,国际,体育,社会,娱乐等18个频道的新闻数据,提供URL和正文信息。 对应的网址为:

http://www.sogou.com/labs/resource/cs.php

为了处理方便,我们提取其中前1万条的正文信息,保存到如下文件中:

../data/news_sohusite_content_10000.txt

数据清洗

为了避免开发环境的编码方式对结果的影响,设置环境的默认编码方式为utf8.

reload(sys)
sys.setdefaultencoding('utf8')

加载加载搜狐新闻语料.

def load_sougou_content():
    #with open("../data/news_sohusite_content.txt") as F:
    # 测试阶段仅加载前1w条记录
    with open("../data/news_sohusite_content_10000.txt") as F:
        content=F.readlines()
        F.close()
    return content

选择其第88篇文章为测试文章,在剩下的语料中寻找与其相似的文档。

#加载搜狐新闻语料
content=load_sougou_content()
#设置测试文章
print "select test data:"
test_news=content[88]
print test_news

测试文档的内容如下:

南方日报 讯   ( 记者 / 黄少宏   实习生 / 朱子 煜   通讯员 / 成广伟 ) “ 学习 台北 垃圾 分类 模式 ” 要 全面铺开 , 部分 小区 试行 垃圾 费 “ 按袋 计量 征收 ” , 厨余 垃圾 专袋 收运 . . . . . . 这是 记者 昨日 从 广州市 城 管委 庆祝 建党 9 1 周年 暨 争先 创优 表彰大会 上 获悉 的 。 据 广州市 城 管委 主任 李廷贵 透露 , 7 月 1 0 日 将 召开 垃圾 分类 全市 动员大会 , 并 推出 一系列 政策 。 在 昨日 的 大会 上 , 针对 垃圾 围城 难题 , 李廷贵 透露 , 广州市委 、 市政府 已经 形成 决议 , 初步 决定 在 7 月 1 0 日 , 在 中山纪念堂 召开 动员大会 , 进行 全面 部署 , 全面 动员 , 正式 启动 垃圾 分类 , 并 将 形成 一整套 法律法规 和 规范 文件 、 配套 实施方案 。 李廷贵 提到 , 要 采取 5 种 垃圾 分类 技术 路线 , 其中 包括 按袋 计量 。 据介绍 , 广州 将 于 今年年底 前 选择 1 - 2 个 生活 小区 试点 “ 垃圾 费 按袋 计量 收费 ” 模式 , 厨余 垃圾 排放 免费 , 其他 垃圾 排放 计量 收费 。 至于 “ 垃圾 费 按袋 计量 收费 ” 政策 , 将 以 专用 垃圾袋 作为 收费 的 工具 , 市民 丢弃 垃圾 , 必须 购买 政府 制作 、 在 指定 地点 发售 的 专用 垃圾袋 盛装 , 再交 垃圾车 收运 。 产生 多少 垃圾 付 多少 钱 , 垃圾 越 少 , 缴费 就 越少 。 除 台北 “ 垃圾 不 落地 ” 模式 将 在 广州 进一步 推广 外 , “ 垃圾袋 实名制 、 垃圾 费 随袋 征收 ” 政策 也 将 在 广州 试点 运行 。 那么 , 垃圾袋 实名制 将 如何 推行 呢 ? 据介绍 , 广州 将 在 政府 特制 的 分类 垃圾袋 上 , 印 上 居民 的 住宅 地段 号 和 房号 , 以此 作为 识别 垃圾袋 出自 那家 哪户 的 标志 , 根据 该 标志 , 可以 追查 垃圾 投放 的 源头 。 如果 居民 没有 按照 规定 对 垃圾 分类 , 将 根据 标记 信息 追查 到 居民 个人 。

数据清洗过程,加载我们积累的中文停用词。

def load_stopwords():
    with open("stopwords.txt") as F:
        stopwords=F.readlines()
        F.close()
    return [word.strip() for word in stopwords]

为了避免停用词的影响,清洗阶段我们从数据集中删除停用词。

# 加载积累的stopwords
stopwords = load_stopwords()
# 切割token并清除stopwords
x = [[word for word in line.split() if word not in stopwords] for line in content]
# 切割token并清除stopwords
test_news = [word for word in test_news.split() if word not in stopwords]
# 计算simhash
test_news_hash = Simhash(test_news)

计算simhash值

依次计算语料库中每条记录的simhash,并记录下与测试数据的距离。

sim=[]
# 遍历语料计算simhash值
for news in x:
    hash = Simhash(news)
    score=test_news_hash.distance(hash)
    sim.append( score)
    

选择距离最短的6的文档和序号并打印,因为需要按照score正序排列,需要设置key参数。

for index, score in sorted(enumerate(sim), key=lambda item: item[1])[:6]:
    print   "index:%d similarities:%f content:%s" % (index, score, content[index])

排名第一的是距离为0的第88号文档,正好就是原文,排名第二的是序号为5644的文档,距离为4,可以发现讲的也是垃圾回收的内容。

垃圾袋 印 房号 可追溯 源头 , 按袋 计量 收费 广州 将 试点 垃圾袋 实名制 垃圾袋 印 房号 可追溯 源头 , 按袋 计量 收费 “ 学习 台北 垃圾 分类 模式 ” 要 全面铺开 , 部分 小区 试行 垃圾 费 “ 按袋 计量 征收 ” , 厨余 垃圾 专袋 收运 … … 这是 记者 近日 从 广州市 城 管委 庆祝 建党 9 1 周年 暨 争先 创优 表彰大会 上 获悉 的 。 据 广州市 城 管委 主任 李廷贵 透露 , 7 月 1 0 日 将 召开 垃圾 分类 全市 动员大会 , 并 推出 一系列 政策 。 针对 垃圾 围城 难题 , 李廷贵 透露 , 广州市委 、 市政府 已经 形成 决议 , 初步 决定 在 7 月 1 0 日 , 在 中山纪念堂 召开 动员大会 , 进行 全面 部署 , 全面 动员 , 正式 启动 垃圾 分类 , 并 将 形成 一整套 法律法规 和 规范 文件 、 配套 实施方案 。 李廷贵 提到 , 要 采取 5 种 垃圾 分类 技术 路线 , 其中 包括 按袋 计量 。 据介绍 , 广州 将 于 今年年底 前 选择 1 - 2 个 生活 小区 试点 “ 垃圾 费 按袋 计量 收费 ” 模式 , 厨余 垃圾 排放 免费 , 其他 垃圾 排放 计量 收费 。 至于 “ 垃圾 费 按袋 计量 收费 ” 政策 , 将 以 专用 垃圾袋 作为 收费 的 工具 , 市民 丢弃 垃圾 , 必须 购买 政府 制作 、 在 指定 地点 发售 的 专用 垃圾袋 盛装 , 再交 垃圾车 收运 。 产生 多少 垃圾 付 多少 钱 , 垃圾 越 少 , 缴费 就 越少 。

余弦距离

余弦距离,又称为余弦相似性,是通过计算两个向量的夹角余弦值来评估他们的相似度。余弦相似度将向量根据坐标值,绘制到向量空间中,如最常见的二维空间。假设向量a、b的坐标分别为(x1,y1)、(x2,y2) ,则对应的余弦距离为:

文档相似度-图2

设向量 A = (A1,A2,...,An),B = (B1,B2,...,Bn) 。推广到多维:

文档相似度-图3

夹角越小,余弦值越接近于1,它们的方向更加吻合,则越相似。可见余弦距离在0和1之间且约接近1说明越两者越相似。

数据集

数据集也和simhash使用相同的数据集。

数据清洗

数据清洗方式与simhash类似,只不过多了一个TFIDF处理。

# 加载积累的stopwords
stopwords = load_stopwords()
# 切割token并清除stopwords
x = [[word for word in line.split() if word not in stopwords] for line in content]
# 获取词袋
dictionary = corpora.Dictionary(x)
# 制作语料
corpus = [dictionary.doc2bow(doc) for doc in x]
# 进行TFIDF处理
tfidf = models.TfidfModel(corpus)

# 把测试文章转换成tfidf
test_news_vec = [word for word in test_news.split() if word not in stopwords]
test_news_vec = tfidf[dictionary.doc2bow(test_news_vec)]

词袋提取使用的是gensim的库,生成的矩阵为稀疏矩阵。

计算余弦距离

gensim库封装实现了针对稀疏矩阵计算余弦距离的类,直接调用即可。由于需要按照score的倒序排列,所以使用key参数。

index = similarities.SparseMatrixSimilarity(tfidf[corpus], num_features=len(dictionary.keys()))
sim = index[tfidf[test_news_vec]]

for index, score in sorted(enumerate(sim), key=lambda item: -item[1])[:6]:
 	print   "index:%d similarities:%f content:%s" % (index, score, content[index])

排名第一的是距离为0.965616的第88号文档,正好就是原文,排名第二的是序号为5644的文档,距离为40.907202,可以发现讲的也是垃圾回收的内容。

参考文献


预测Yelp美食评分

Yelp简介

Yelp是美国著名商户点评网站,创立于2004年,囊括各地餐馆、购物中心、酒店、旅游等领域的商户,用户可以在Yelp网站中给商户打分,提交评论,交流购物体验等。在Yelp中搜索一个餐厅或者旅馆,能看到它的简要介绍以及网友的点论,点评者还会给出多少星级的评价,通常点评者都是亲身体验过该商户服务的消费者,评论大多形象细致。

Yelp Reviews

Yelp Reviews是Yelp为了学习目的而发布的一个开源数据集。它包含了由数百万用户评论,商业属性和来自多个大都市地区的超过20万张照片。这是一个常用的全球NLP挑战数据集,包含5,200,000条评论,174,000条商业属性。 数据集下载地址为:

https://www.yelp.com/dataset/download

Yelp Reviews格式分为JSON和SQL两种,以JSON格式为例,其中最重要的review.json,包含评论数据,格式如下:

{
// string, 22 character unique review id
"review_id": "zdSx_SD6obEhz9VrW9uAWA",
// string, 22 character unique user id, maps to the user in user.json
"user_id": "Ha3iJu77CxlrFm-vQRs_8g",
// string, 22 character business id, maps to business in business.json
"business_id": "tnhfDv5Il8EaGSXZGiuQGg",
// integer, star rating
"stars": 4,
// string, date formatted YYYY-MM-DD
"date": "2016-03-09",
// string, the review itself
"text": "Great place to hang out after work: the prices are decent, and the ambience is fun. It's a bit loud, but very lively. The staff is friendly, and the food is good. They have a good selection of drinks.",
// integer, number of useful votes received
"useful": 0,
// integer, number of funny votes received
"funny": 0,
// integer, number of cool votes received
"cool": 0}

数据清洗

Yelp Reviews文件格式为JSON和SQL,使用起来并不是十分方便。专门有个开源项目用于解析该JSON文件:

https://github.com/Yelp/dataset-examples

该项目可以将Yelp Reviews的Yelp Reviews转换成CSV格式,便于进一步处理,该项目的安装非常简便,同步完项目后直接安装即可。

git clone https://github.com/Yelp/dataset-examples
python setup.py install

假如需要把review.json转换成CSV格式,命令如下:

python json_to_csv_converter.py /dataset/yelp/dataset/review.json

命令执行完以后,就会在review.json相同目录下生成对应的CSV文件review.csv。查看该CSV文件的表头,内容如下,其中最重要的两个字段就是text和stars,分别代表评语和打分。

#CSV格式表头内容:
#funny,user_id,review_id,text,business_id,stars,date,useful,cool
	

使用pandas读取该CSV文件,开发阶段可以指定仅读取前10000行。


#开发阶段读取前10000行
df = pd.read_csv(filename,sep=',',header=0,nrows=10000)

pandas的可以配置的参数非常多,其中比较重要的几个含义如下:

  • sep : str, default ‘,’。指定分隔符。
  • header: int or list of ints, default ‘infer’。指定行数用来作为列名,数据开始行数。如果文件中没有列名,设置为None。设置为0则认为第0行是列名
  • nrows : int, default None 需要读取的行数(从文件头开始算起)
  • skiprows : list-like or integer, default None。需要忽略的行数(从文件开始处算起),或需要跳过的行号列表(从0开始)。
  • skip\_blank\_lines : boolean, default True。如果为True,则跳过空行;否则记为NaN

按照列名直接获取数据,读取评论内容和打分结果,使用list转换成list对象。


text=list(df['text'])
stars=list(df['stars'])

查看打分结果的分布。

#显示各个评分的个数
print df.describe()

分布结果如下,一共有10000个评分,最高分5分,最低1分,平均得分为3.74。

              funny         stars        useful          cool
count  10000.000000  10000.000000  10000.000000  10000.000000
mean       0.649800      3.743800      1.669500      0.777800
std        1.840679      1.266381      3.059511      1.958625
min        0.000000      1.000000      0.000000      0.000000
25%        0.000000      3.000000      0.000000      0.000000
50%        0.000000      4.000000      1.000000      0.000000
75%        1.000000      5.000000      2.000000      1.000000
max       46.000000      5.000000     95.000000     43.000000

pandas下面分析数据的分布非常方便,而且可以支持可视化。以分析stars评分的分布为例,首先按照stars评分统计各个评分的个数。


#绘图
plt.figure()
count_classes=pd.value_counts(df['stars'],sort=True)

然后使用pandas的内置函数进行绘图,横轴是stars评分,纵轴是对应的计数。

		
print "各个star的总数:"
print count_classes
count_classes.plot(kind='bar',rot=0)
plt.xlabel('stars')
plt.ylabel('stars counts')
plt.savefig("yelp_stars.png")

在Mac系统下运行可能会有如下报错。

RuntimeError: Python is not installed as a framework. The Mac OS X backend will not be able to function correctly if Python is not installed as a framework. See the Python documentation for more information on installing Python as a framework on Mac OS X. Please either reinstall Python as a framework, or try one of the other backends. If you are using (Ana)Conda please install python.app and replace the use of ‘python‘ with ‘pythonw‘. See ‘Working with Matplotlib on OSX‘ in the Matplotlib FAQ for more information.

处理方式为:

  • 打开终端,输入cd ~/.matplotlib
  • 新建文件vi matplotlibrc
  • 文件中添加内容 backend: TkAgg

再次运行程序,得到可视化的图表,可以发现大多数人倾向打4-5分。

预测Yelp美食评分-图1.png

各个评分的具体计数分别为:

各个star的总数:
5    3555
4    2965
3    1716
2     891
1     873

在进行情感分析的时候,通常可以把3分及其以上的认为是积极,标记为1,1-2分认为是消极,标记为0。

stars=[ 0 if star < 3 else 1 for star in stars ]

经过处理后,统计情感分类的结果。

print "情感分类的总数:"
count_classes = pd.value_counts(stars, sort=True)
print count_classes

结果显示,积极的占将近80%。

情感分类的总数:
1    8236
0    1764

可视化情感分类的数据,结果如下。

预测Yelp美食评分-图3.png

另外需要主要的是,如果数据清洗过程中出现编码错误,示例如下:

UnicodeDecodeError: 'utf8' codec can't decode byte 0xc3 in position 18: unexpected end of data

遇到类似问题的解决方法是,解析CSV文件时强制使用utf8格式。

df = pd.read_csv(filename,sep=',',header=0,encoding='utf-8',nrows=200000)

另外在代码文件的前几行中显示设置编码格式为utf8。


#coding=utf-8
import sys
#处理编码问题
reload(sys)
sys.setdefaultencoding('utf-8')

特征提取

词袋模型

最简单的一种特征提取方式就是词袋模型,scikit-learn下有完整的封装。


# 切割词袋 删除英文停用词
vectorizer = CountVectorizer(ngram_range=(1, 1), max_features=max_features,stop_words='english',lowercase=True)

词袋模型的一种变形就是ngram,提取的特征是相邻的若干单词,最常见的就是2-gram,表示前后两个单词,在scikit-learn的实现为:

# 切割词袋 删除英文停用词
vectorizer = CountVectorizer(ngram_range=(2, 2), max_features=max_features,stop_words='english',lowercase=True)

词袋模型结合TF-IDF模型

词袋模型通常可以和TF-IDF模型一起使用,用于提升分类效果。


# 该类会统计每个词语的tf-idf权值
transformer = TfidfTransformer()
# 使用2-gram和TFIDF处理
x = transformer.fit_transform(vectorizer.fit_transform(text))

词袋序列模型

词袋序列模型是在词袋模型的基础上发展而来的,相对于词袋模型,词袋序列模型可以反映出单词在句子中的前后关系。keras中通过Tokenizer类实现了词袋序列模型,这个类用来对文本中的词进行统计计数,生成文档词典,以支持基于词典位序生成文本的向量表示,创建该类时,需要设置词典的最大值。

tokenizer = Tokenizer(num_words=None)

Tokenizer类的成员函数为:

  • fit_on_text(texts) 使用一系列文档来生成token词典,texts为list类,每个元素为一个文档。
  • texts_to_sequences(texts) 将多个文档转换为word下标的向量形式,shape为[len(texts),len(text)] -- (文档数,每条文档的长度)
  • texts_to_matrix(texts) 将多个文档转换为矩阵表示,shape为[len(texts),num_words]

Tokenizer类的示例代码如下:

from keras.preprocessing.text import Tokenizer

text1='some thing to eat'
text2='some thing to drink'
texts=[text1,text2]

tokenizer = Tokenizer(num_words=None) 
#num_words:None或整数,处理的最大单词数量。少于此数的单词丢掉
tokenizer.fit_on_texts(texts)

# num_words=多少会影响下面的结果,行数=num_words
print( tokenizer.texts_to_sequences(texts)) 
#得到词索引[[1, 2, 3, 4], [1, 2, 3, 5]]
print( tokenizer.texts_to_matrix(texts))  
# 矩阵化=one_hot
[[ 0.,  1.,  1.,  1.,  1.,  0.,  0.,  0.,  0.,  0.],
 [ 0.,  1.,  1.,  1.,  0.,  1.,  0.,  0.,  0.,  0.]]

在处理Yelp数据集时,把每条评论看成一个词袋序列,且长度固定。超过固定长度的截断,不足的使用0补齐。

#转换成词袋序列,max_document_length为序列的最大长度
max_document_length=200
#设置分词最大个数 即词袋的单词个数
tokenizer = Tokenizer(num_words=max_features)
tokenizer.fit_on_texts(text)
sequences = tokenizer.texts_to_sequences(text)
 #截断补齐
x=pad_sequences(sequences, maxlen=max_document_length)

第一次使用nltk资源时,需要进行下载。

Python 2.7.14 |Anaconda, Inc.| (default, Mar 27 2018, 17:29:31) 
[GCC 7.2.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import nltk
>>> nltk.download('stopwords')
[nltk_data] Downloading package stopwords to /root/nltk_data...
[nltk_data]   Unzipping corpora/stopwords.zip.
True
>>> 

使用MLP进行情感分析

MLP是多层感知机的简写,是最简单的深度神经网络结构。我们构造一个双层的MLP,第一层隐藏层的结点数为5,第二层为2.


#构造神经网络
def baseline_model():
    model = Sequential()
    model.add(Dense(5, input_dim=max_features, activation='relu'))
    model.add(Dropout(0.2))
    model.add(Dense(2, activation='softmax'))
    # Compile model
    model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

可视化MLP模型,keras下将模型可视化非常容易,内置函数即可图片化展现。

from keras.utils import plot_model
plot_model(model, to_file='model.png')

plot_model接收两个可选参数:

  • show_shapes:指定是否显示输出数据的形状,默认为False
  • show\_layer\_names:指定是否显示层名称,默认为True

第一次运行,可能会报错。

ImportError: Failed to import pydot. You must install pydot and graphviz for `pydotprint` to work.

这是因为相关库没有及时安装,解决方法如下:

pip install pydot-ng 
brew install graphviz

再次运行程序,可视化结果如下。

预测Yelp美食评分-图2.png

keras也支持打印模型。

model.summary()

输出的结果如下所示,除了显示模型的结构,还可以显示需要训练的参数信息。

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_3 (Dense)              (None, 5)                 25005     
_________________________________________________________________
dropout_2 (Dropout)          (None, 5)                 0         
_________________________________________________________________
dense_4 (Dense)              (None, 2)                 12        
=================================================================

为了让验证的效果更加可信,我们使用5折交叉验证,考核分类器的F1值,训练的轮数为20。在 scikit-learn 中使用 Keras 的模型,我们必须使用 KerasClassifier 进行包装。这个类起到创建并返回我们的神经网络模型的作用。它需要传入调用 fit()所需要的参数,比如迭代次数和批处理大小。

# 最新接口指定训练的次数为epochs
clf = KerasClassifier(build_fn=baseline_model, epochs=20, batch_size=128, verbose=0)
#使用5折交叉验证
scores = cross_val_score(clf, x, encoded_y, cv=5, scoring='f1_micro')
# print scores
print("f1_micro: %0.2f (+/- %0.2f)" % (scores.mean(), scores.std() * 2))

在样本数为10000,特征数取5000的前提下,结果如下所示,可以看出2-gram在本数据集下没有对分类效果有改善。

特征提取方式 F1值
词袋 0.89
2-gram 0.84
词袋&TF-IDF 0.89
2-gram&TF-IDF 0.84

适当增加训练数据量,特征数取5000的前提下,结果如下所示,可见在该测试集合下,增加数据量对F1的影响有限。

样本总量 F1值
1w 0.89
10w 0.91
20w 0.91

使用LSTM进行情感分析

LSTM特别适合处理具有序列化数据,并且可以很好的自动化提炼序列前后的特征关系。当我们把Yelp数据集转换成词袋序列后,就可以尝试使用LSTM来进行处理。我们构造一个简单的LSTM结构,首先通过一个Embedding层进行降维成为128位的向量,然后使用一个核数为128的LSTM进行处理。为了防止过拟合,LSTM层和全连接层之间随机丢失20%的数据进行训练。

#构造神经网络
def baseline_model():
    model = Sequential()
    model.add(Embedding(max_features, 128))
    model.add(LSTM(128, dropout=0.2, recurrent_dropout=0.2))
    model.add(Dense(2, activation='softmax'))
    # try using different optimizers and different optimizer configs
    model.compile(loss='categorical_crossentropy',
                  optimizer='adam',
                  metrics=['accuracy'])
    #可视化
    plot_model(model, to_file='yelp-lstm-model.png',show_shapes=True)
    model.summary()
    return model

再次运行程序,可视化结果如下。

预测Yelp美食评分-图4.png

打印LSTM的结构。

model.summary()

输出的结果如下所示,除了显示模型的结构,还可以显示需要训练的参数信息。

=================================================================
Layer (type)                 Output Shape              Param #   
=================================================================
embedding_1 (Embedding)      (None, None, 128)         640000    
_________________________________________________________________
lstm_1 (LSTM)                (None, 128)               131584    
_________________________________________________________________
dense_1 (Dense)              (None, 2)                 258       
=================================================================
Total params: 771,842
Trainable params: 771,842
Non-trainable params: 0
_________________________________________________________________

为了让验证的效果更加可信,我们使用5折交叉验证,考核分类器的F1值,训练的轮数为20。在 scikit-learn 中使用 Keras 的模型,我们必须使用 KerasClassifier 进行包装。这个类起到创建并返回我们的神经网络模型的作用。它需要传入调用 fit()所需要的参数,比如迭代次数和批处理大小。

# 最新接口指定训练的次数为epochs
clf = KerasClassifier(build_fn=baseline_model, epochs=20, batch_size=128, verbose=0)
#使用5折交叉验证
scores = cross_val_score(clf, x, encoded_y, cv=5, scoring='f1_micro')
# print scores
print("f1_micro: %0.2f (+/- %0.2f)" % (scores.mean(), scores.std() * 2))

LSTM和CNN都是计算密集型的模型,在CPU上运行的速度几乎难以接受。强烈建议实用GPU服务器进行加速,下面为使用一块Tesla P4进行加速的显示内容。

name: Tesla P4
major: 6 minor: 1 memoryClockRate (GHz) 1.1135
pciBusID 0000:00:06.0
Total memory: 7.43GiB
Free memory: 7.32GiB
2018-05-05 11:20:53.108858: I tensorflow/core/common_runtime/gpu/gpu_device.cc:976] DMA: 0 
2018-05-05 11:20:53.108869: I tensorflow/core/common_runtime/gpu/gpu_device.cc:986] 0:   Y 
2018-05-05 11:20:53.108882: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1045] Creating TensorFlow device (/gpu:0) -> (device: 0, name: Tesla P4, pci bus id: 0000:00:06.0)

在样本数为10000,特征数取5000的前提下,结果如下所示。

特征提取方式 F1值
原始词袋序列 0.84
词袋序列+全部转换成小写 0.85

适当增加训练数据量,特征数取5000,词袋序列+全部转换成小写的前提下,结果如下所示,可见在该测试集合下,增加数据量可以提升F1性能,但是效果有限。

样本总量 F1值
1w 0.85
10w 0.90

使用CNN进行情感分析

近几年使用CNN处理文本分类问题也逐渐成为主流。我们尝试使用简单的CNN结构来处理Yelp的分类问题。首先通过一个Embedding层进行降维成为50位的向量,然后使用一个核数为250,步长为1的一维CNN层进行处理,接着连接一个池化层。为了防止过拟合,CNN层和全连接层之间随机丢失20%的数据进行训练。

    #CNN参数
    embedding_dims = 50
    filters = 250
    kernel_size = 3
    hidden_dims = 250
    model = Sequential()
    model.add(Embedding(max_features, embedding_dims))
    model.add(Conv1D(filters,
                     kernel_size,
                     padding='valid',
                     activation='relu',
                     strides=1))
    #池化
    model.add(GlobalMaxPooling1D())
    model.add(Dense(2, activation='softmax'))
    model.compile(loss='categorical_crossentropy',
                  optimizer='adam',
                  metrics=['accuracy'])

可视化结果如下。

预测Yelp美食评分-图6.png

打印CNN的结构。

model.summary()

输出的结果如下所示,除了显示模型的结构,还可以显示需要训练的参数信息。

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding_1 (Embedding)      (None, None, 50)          250000    
_________________________________________________________________
conv1d_1 (Conv1D)            (None, None, 250)         37750     
_________________________________________________________________
global_max_pooling1d_1 (Glob (None, 250)               0         
_________________________________________________________________
dense_1 (Dense)              (None, 2)                 502       
=================================================================
Total params: 288,252
Trainable params: 288,252
Non-trainable params: 0
_________________________________________________________________

在样本数为10000,特征数取5000的前提下,结果如下所示,。

特征提取方式 F1值
词袋序列 0.88
词袋序列+全部转换成小写+删除停用词 0.86

适当增加训练数据量,特征数取5000,词袋序列+全部转换成小写的前提下,结果如下所示,可见在该测试集合下,增加数据量对F1的影响有限。

样本总量 F1值
1w 0.86
10w 0.90
20w 0.91

使用fasttext进行情感分析

fasttext对训练和测试的数据格式有一定的要求,数据文件和标签文件要合并到一个文件里面。文件中的每一行代表一条记录,同时每条记录的最后标记对应的标签。默认情况下标签要以__label__开头,比如:

这是一条测试数据	__label__1

python下实现合并数据文件和标签文件的功能非常简单。

def dump_file(x,y,filename):
    with open(filename, 'w') as f:
        for i,v in enumerate(x):
            line="%s __label__%d\n" % (v,y[i])
            f.write(line)
        f.close()

加载数据清洗后的数据和标签,随机划分成训练数据和测试数据,其中测试数据占20%。

按照fasttest的要求生成训练数据和测试数据。

dump_file(x_train, y_train, "yelp_train.txt")
dump_file(x_test, y_test, "yelp_test.txt")

使用训练数据集对应的文件进行训练。

model = train_supervised(
    input="yelp_train.txt", epoch=20, lr=0.6, wordNgrams=2, verbose=2, minCount=1
)

在测试数据集对应的文件上进行预测。

def print_results(N, p, r):

    print("N\t" + str(N))
    print("P@{}\t{:.3f}".format(1, p))
    print("R@{}\t{:.3f}".format(1, r))
print_results(*model.test("yelp_test.txt"))

在样本数为10000的前提下,结果如下所示,F1为0.866。

Read 1M words
Number of words:  71445
Number of labels: 2
Progress: 100.0% words/sec/thread: 1744947 lr:  0.000000 loss:  0.066168 ETA:   0h 0m
N       2000
P@1     0.866
R@1     0.866

通过优化特征提取方式,可以进一步提升fasttext的分类性能,结果如下所示。

特征提取方式 F1值
2-gram 0.866
2-gram+删除停用词 0.902
2-gram+删除停用词+转小写 0.908

另外,通过增加训练数据量也可以提升分类效果,在使用2-gram+删除停用词+转小写的前提下,结果如下。

样本总量 F1值
1w 0.908
10w 0.921
20w 0.923

使用SVM进行情感分析

在深度学习出现之前,SVM和朴素贝叶斯经常用于文本分类领域,我们以SVM为例。实例化SVM分类器,并使用5折验证法,考核F1值。

clf = SVC()
# 使用5折交叉验证
scores = cross_val_score(clf, x, y, cv=5, scoring='f1_micro')
# print scores
print("f1_micro: %0.2f (+/- %0.2f)" % (scores.mean(), scores.std() * 2))

在样本数为10000,特征数取5000的前提下,结果如下所示,可见在本数据集下特征提取方式对结果影响不大。

特征提取方式 F1值
词袋 0.82
2-gram 0.82
词袋&TF-IDF 0.82
2-gram&TF-IDF 0.82

参考文献

本文由 GitVP 从 GitHub 收录并在站内全文呈现,版权归原作者所有。

← 回到全部文章

同分类还有