《机器学习系统设计》之应用scikit-learn做文本分类(上)
前言:
本系列是在作者学习《机器学习系统设计》([美] WilliRichert)过程中的思考与实践,全书通过Python从数据处理。到特征project,再到模型选择,把机器学习解决这个问题的过程一一呈现。
书中设计的源码和数据集已上传到我的资源:http://download.csdn.net/detail/solomon1558/8971649
第3章通过词袋模型+K均值聚类实现相关文本的匹配。本文主要解说文本预处理部分内容。涉及切分文本、数据清洗、计算TF-IDF值等内容。
相关链接:《机器学习系统设计》之应用scikit-learn做文本分类(下)
1. 统计词语
使用一个简单的数据集进行实验,它包含5个文档:
01. txt This is a toy post about machine learning.Actually, it contains not much interesting stuff.
02. txt Imaging databases provide storagecapabilities.
03. txt Most imaging databases safe imagespermanently.
04. txt Imaging databases store data.
05. txt Imaging databases store data. Imagingdatabases store data. Imaging databases store data.
在这个文档数据集中。我们想要找到和文档”imaging database”最相近的文档。为了将原始文本转换成聚类算法能够使用的特征数据,首先须要使用词袋(bag-of-word)方法来衡量文本间相似性。终于生成每一个文本的特征向量。
词袋方法基于简单的词频统计。统计每个帖子中的词频,表示成一个向量,即向量化。Scikit-learn的CountVectorizer能够高效地完毕统计词语的工作,Scikit的函数和类能够通过sklearn包引入进来:
posts = [open(os.path.join(DIR, f)).read() for f in os.listdir(DIR)]
vectorizer = CountVectorizer(min_df=1, stop_words="english")
X_train = vectorizer.fit_transform(posts)
如果待训练的文本存放在文件夹DIR下,我们将数据集传给CountVectorizer。
參数min_df决定了CounterVectorizer怎样处理那些不常常使用的词语(最小文档词频)。当min_df为一个整数时,全部出现次数小于这个值的词语都将被扔掉;当它是一个比例时,将整个数据集中出现比例小于这个值的词语都将被丢弃。
我们须要告诉这个想量化处理器整个数据集的信息,使它能够预先知道都有哪些词语:
X_train = vectorizer.fit_transform(posts)
num_samples, num_features = X_train.shape
print ("#sample: %d, #feature: %d" % (num_samples, num_features))
print(vectorizer.get_feature_names())
程序的输出例如以下。5个文档中包括了25个词语
#sample: 5, #feature: 25
[u'about', u'actually', u'capabilities', u'contains',u'data', u'databases', u'images', u'imaging', u'interesting',
u'is', u'it',u'learning', u'machine', u'most', u'much', u'not', u'permanently', u'post',u'provide', u'safe', u'storage', u'store', u'stuff', u'this', u'toy']
对新文档进行向量化:
#a new post
new_post = "imaging databases"
new_post_vec = vectorizer.transform([new_post])
把每一个样本的词频数组当做向量进行相似度计算,须要使用数组的所有元素[使用成员函数toarray()]。
通过norm()函数计算新文档与所有训练文档向量的欧几里得范数(最小距离),从而衡量它们之间的相似度。
#------- calculate raw distances betwee new and old posts and record the shortest one-------------------------
def dist_raw(v1, v2):
delta = v1 - v2
return sp.linalg.norm(delta.toarray())
best_doc = None
best_dist = sys.maxint
best_i = None
for i in range(0, num_samples):
post = posts[i]
if post == new_post:
continue
post_vec = X_train.getrow(i)
d = dist_raw(post_vec, new_post_vec)
print "=== Post %i with dist = %.2f: %s" % (i, d, post)
if d<best_dist:
best_dist = d
best_i = i
print("Best post is %i with dist=%.2f" % (best_i, best_dist))
=== Post 0 with dist = 4.00:This is a toy post about machine learning. Actually, it contains not muchinteresting
stuff.
=== Post 1 with dist =1.73:Imaging databases provide storage capabilities.
=== Post 2 with dist =2.00:Most imaging databases
safe images permanently.
=== Post 3 with dist =1.41:Imaging databases
store data.
=== Post 4 with dist =5.10:Imaging databases
store data. Imaging databases store data. Imaging databasesstore data.
Best post is 3 with dist=1.41
结果显示文档3与新文档最为相似。然而文档4和文档3的内容一样,但反复了3遍。所以,它和新文档的相似度应该与文档3是一样的。
#-------case study: why post 4 and post 5 different ?-----------
print(X_train.getrow(3).toarray())
print(X_train.getrow(4).toarray())
[[0 0 0 0 1 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0]]
[[0 0 0 0 3 3 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 3 0 0 0]]
2. 文本处理
2.1 词频向量归一化
对第2节中的dist_raw函数进行扩展,在归一化的向量上(向量各分量除以其模长)计算向量间的距离。
def dist_norm(v1, v2):
v1_normalized = v1 / sp.linalg.norm(v1.toarray())
v2_normalized = v2 / sp.linalg.norm(v2.toarray())
delta = v1_normalized - v2_normalized
return sp.linalg.norm(delta.toarray())
=== Post 0 with dist = 1.41: This is a toy post aboutmachine learning. Actually, it contains not much interesting stuff.
=== Post 1 with dist = 0.86: Imaging databases providestorage capabilities.
=== Post 2 with dist = 0.92: Most imaging databasessafe images permanently.
=== Post 3 with dist = 0.77:Imagingdatabases store data.
=== Post 4 with dist = 0.77:Imaging databases store data. Imaging databases
store data. Imaging databasesstore data.
Best post is 3 with dist=0.77
词频向量归一化之后,文档3和文档4与新文档具有了同样的相似度。从词频统计的角度来说,这样处理更为正确。
2.2 排除停用词
文本中类似”the”、”of”这些单词常常出如今各种不同的文本中,被称为停用词。因为停用词对于区分文本没有多大帮助,因此删除停用词是文本处理中的一个常见步骤。
CountVectorizer中有一个简单的參数stop_words()能够完毕该任务:
vectorizer = CountVectorizer(min_df=1, stop_words='english')
2.3词干处理
为了将语义类似但形式不同的词语放在一起统计,我们须要一个函数将词语归约到特定的词干形式。自然语言处理工具包(NLTK)提供了一个很easy嵌入到CountVectorizer的词干处理器。
把文档传入CountVectorizer之前,我们须要对它们进行词干处理。
该类提供了几种钩子。能够用它们定制预处理和词语切分阶段的操作。
预处理器和词语切分器能够当作參数传入构造函数。
我们并不想把词干处理器放入它们不论什么一个其中,由于那样的话,之后还须要亲自对词语进行切分和归一化。相反。我们能够通过改写build_analyzer方法来实现:
import nltk.stem
english_stemmer = nltk.stem.SnowballStemmer('english')
class StemmedCountVectorizer(CountVectorizer):
def build_analyzer(self):
analyzer = super(StemmedCountVectorizer, self).build_analyzer()
return lambda doc: (english_stemmer.stem(w) for w in analyzer(doc))
vectorizer = StemmedCountVectorizer(min_df=1, stop_words='english')
依照例如以下步骤对每一个帖子进行处理:
(1) 在预处理阶段将原始文档变成小写字母形式(这在父类中完毕);
(2) 在词语切分阶段提取全部单词;
(3) 将每一个词语转换成词干形式。
3. 计算TF-IDF
至此,我们採用统计词语的方式,从充满噪声的文本中提取了紧凑的特征向量。这些特征的值就是对应词语在全部训练文本中出现的次数,我们默认较大的特征值意味着合格词语对文本更为重要。
可是在训练文本中,不同的词语对文本的可区分性贡献更大。
这须要通过统计每一个文本的词频,而且对出如今多个文本中的词语在权重上打折来解决。即当某个词语常常出如今一些特定的文本中,而在其它地方非常少出现时。应该赋予该词语更大的权值。
这正是词频-反转文档频率(TF-IDF)所要做的:TF代表统计部分,而IDF把权重折扣考虑了进去。一个简单的实现例如以下:
import scipy as sp
def tfidf(t, d, D):
tf = float(d.count(t)) / sum(d.count(w) for w in set(d))
idf = sp.log(float(len(D)) / (len([doc for doc in D if t in doc])))
return tf * idf
在实际应用过程中,scikit-learn已经将该算法封装进了TfidfVectorizer(继承自CountVectorizer)中。进行这些操作后,我们得到的文档向量不会再包括词语拥挤值,而是每一个词语的TF-IDF值。
代码清单:
import os
import sys
import scipy as sp
from sklearn.feature_extraction.text import CountVectorizer DIR = r"../data/toy"
posts = [open(os.path.join(DIR, f)).read() for f in os.listdir(DIR)]
new_post = "imaging databases" import nltk.stem
english_stemmer = nltk.stem.SnowballStemmer('english') class StemmedCountVectorizer(CountVectorizer):
def build_analyzer(self):
analyzer = super(StemmedCountVectorizer, self).build_analyzer()
return lambda doc: (english_stemmer.stem(w) for w in analyzer(doc))
#vectorizer = StemmedCountVectorizer(min_df=1, stop_words='english') from sklearn.feature_extraction.text import TfidfVectorizer class StemmedTfidfVectorizer(TfidfVectorizer):
def build_analyzer(self):
analyzer = super(StemmedTfidfVectorizer, self).build_analyzer()
return lambda doc: (english_stemmer.stem(w) for w in analyzer(doc)) vectorizer = StemmedTfidfVectorizer(min_df=1, stop_words='english')
print(vectorizer)
X_train = vectorizer.fit_transform(posts) num_samples, num_features = X_train.shape
print("#samples: %d, #features: %d" % (num_samples, num_features)) new_post_vec = vectorizer.transform([new_post])
print(new_post_vec, type(new_post_vec))
print(new_post_vec.toarray())
print(vectorizer.get_feature_names()) def dist_raw(v1, v2):
delta = v1 - v2
return sp.linalg.norm(delta.toarray())
def dist_norm(v1, v2):
v1_normalized = v1 / sp.linalg.norm(v1.toarray())
v2_normalized = v2 / sp.linalg.norm(v2.toarray())
delta = v1_normalized - v2_normalized
return sp.linalg.norm(delta.toarray()) dist = dist_norm
best_dist = sys.maxsize
best_i = None for i in range(0, num_samples):
post = posts[i]
if post == new_post:
continue
post_vec = X_train.getrow(i)
d = dist(post_vec, new_post_vec)
print("=== Post %i with dist=%.2f: %s" % (i, d, post))
if d < best_dist:
best_dist = d
best_i = i
print("Best post is %i with dist=%.2f" % (best_i, best_dist))
4. 总结
文本预处理过程包括的步骤总结例如以下:
(1) 切分文本;
(2) 扔掉出现过于频繁。而又对匹配相关文档没有帮助的词语;
(3) 扔掉出现频率非常低。仅仅有非常小可能出如今未来帖子中的词语;
(4) 统计剩余的词语。
(5) 考虑整个预料集合,从词频统计中计算TF-IDF值。
通过这一过程。我们将一堆充满噪声的文本转换成了一个简明的特征表示。
然而,尽管词袋模型及其扩展简单有效,但仍然有一些缺点须要注意:
(1) 它并不涵盖词语之间的关联关系。採用之前的向量化方法,文本”Car hits wall”和”Wall hits car”会有同样的特征向量。
(2) 它没法捕捉否定关系。
比如”I will eat ice cream”和”I will not eat ice cream”。虽然它们意思截然相反。但从特征向量来看它们非常相似。这个问题事实上非常easy解决,仅仅须要既统计单个词语(又叫unigrams)。又考虑成队的词语(bigrams)或者trigrams(一行中的三个词语)就可以。
(3) 对于拼写错误的词语会处理失败。
《机器学习系统设计》之应用scikit-learn做文本分类(上)的更多相关文章
- 使用CNN做文本分类——将图像2维卷积换成1维
使用CNN做文本分类 from __future__ import division, print_function, absolute_import import tensorflow as tf ...
- Spark ML下实现的多分类adaboost+naivebayes算法在文本分类上的应用
1. Naive Bayes算法 朴素贝叶斯算法算是生成模型中一个最经典的分类算法之一了,常用的有Bernoulli和Multinomial两种.在文本分类上经常会用到这两种方法.在词袋模型中,对于一 ...
- NLP系列(2)_用朴素贝叶斯进行文本分类(上)
作者:龙心尘 && 寒小阳 时间:2016年1月. 出处: http://blog.csdn.net/longxinchen_ml/article/details/50597149 h ...
- 如何使用scikit—learn处理文本数据
答案在这里:http://www.tuicool.com/articles/U3uiiu http://scikit-learn.org/stable/modules/feature_extracti ...
- 应用scikit-learn做文本分类
文本挖掘的paper没找到统一的benchmark,只好自己跑程序,走过路过的前辈如果知道20newsgroups或者其它好用的公共数据集的分类(最好要所有类分类结果,全部或取部分特征无所谓)麻烦留言 ...
- 应用scikit-learn做文本分类(转)
文本挖掘的paper没找到统一的benchmark,只好自己跑程序,走过路过的前辈如果知道20newsgroups或者其它好用的公共数据集的分类(最好要所有类分类结果,全部或取部分特征无所谓)麻烦留言 ...
- 基于SVMLight的文本分类
支持向量机(Support Vector Machine)是Cortes和Vapnik于1995年首先提出的,它在解决小样本 .非线性及高维模式识别 中表现出许多特有的优势,并能够推广应用到函数拟合等 ...
- 基于Naive Bayes算法的文本分类
理论 什么是朴素贝叶斯算法? 朴素贝叶斯分类器是一种基于贝叶斯定理的弱分类器,所有朴素贝叶斯分类器都假定样本每个特征与其他特征都不相关.举个例子,如果一种水果其具有红,圆,直径大概3英寸等特征,该水果 ...
- 文本分类实战(十)—— BERT 预训练模型
1 大纲概述 文本分类这个系列将会有十篇左右,包括基于word2vec预训练的文本分类,与及基于最新的预训练模型(ELMo,BERT等)的文本分类.总共有以下系列: word2vec预训练词向量 te ...
随机推荐
- idea运行提示Error:java:无效的源发行版:1.9
如果你是jdk1.8 改到8即可,如图:
- Sublime Text编辑器配置Python解释器简易教程
前天在微信上遇到一个小伙伴问我一个关于Sublime text配置Python解释器的问题,可能是初学者,对这方面还不是很懂,想使用快捷键但是徒劳一场,因为缺少Python解释器,直接按下快捷键Ctr ...
- 【BZOJ4444】国旗计划 - 决策单调性
Description A国正在开展一项伟大的计划——国旗计划.这项计划的内容是边防战士手举国旗环绕边境线奔袭一圈.这项计划需要多名边防战士以接力的形式共同完成,为此,国土安全局已经挑选了N名优秀的边 ...
- 小程序canvas生成二维码图片踩的坑
1:生成临时图片,保证画布被加载以及渲染(即本身不可以 hidden 或是 上级元素不可以 hidden 或是 wx:if 隐藏等) == > 建议:因为 canvas 的组件层级(z-inde ...
- LightOJ-1336 Sigma Function 唯一分解定理 巧妙使用sqrt()等算数目
题目链接:https://cn.vjudge.net/problem/LightOJ-1336 题意 给出一个区间[1, n],求区间内所有数中因数之和为偶数的数目 思路 第二次写这个题 首先想到唯一 ...
- 永远不要在MySQL中使用“utf8”
最近我遇到了一个 bug,我试着通过 Rails 在以“utf8”编码的 MariaDB 中保存一个 UTF-8 字符串,然后出现了一个离奇的错误: Incorrect string value: ‘ ...
- 1113: [视频]树形动态规划(TreeDP)8:树(tree)(树形dp状态设计总结)
根据最近做的几道树形dp题总结一下规律.(从这篇往前到洛谷 P1352 ) 这几道题都是在一颗树上,然后要让整棵树的节点或边 满足一种状态.然后点可以影响到相邻点的这种状态 然后求最小次数 那么要从两 ...
- 洛谷—— P2663 越越的组队
https://www.luogu.org/problem/show?pid=2663 题目描述 班级要组织一场综合能力竞赛,全班同学(N个,N是偶数)分成两队互相竞争.老师找到了越越并给了越越一张全 ...
- 9种常见的Android开发错误及解决方案
整理总结了9个Android开发中的错误以及解决方案,共同探讨进步! 1. 如果你的项目的R文件不见的话,可以试下改版本号在保存,R文件不见一般都是布局文本出错导致. 2. 布局文件不可以有大写字母 ...
- SpringMVC+Jquery -页面异步载入数据
背景: 做项目时涉及到页面.当我打算在controller中传一个list到页面,然后通过<c:foreach>循环遍历出来时,同事说:你这样每次都要刷新.这都是几百年前使用的技术了.你用 ...