机器学习折腾记(3-1):自然语言处理(NLP)初探

彼得·德鲁克说,我们总是高估一年的变化,而低估了五年十年的变化,因为我们总是忘记“复利”的存在。(《卓有成效的管理者》)

机器学习虽然很受欢迎,但是真当要静下心来学习时,我们遇见的困难又是巨大的,面对很高的学习成本,与陡峭的学习曲线,有时并不只是单单说一两句坚持就能做好的。

比如,我们可能需要下面的相关的基础知识——

1、基本数学知识
2、线性代数
3、微积分
4、概率和信息论
5、编程语言(python、R、java、scala等)

别泄气,基础虽然需要打牢,但是我始终认为,先建立起一个连接(兴趣)更重要,因为学习机器学习不只是毅力足够好就行的。

继续拆书《机器学习系统设计》,作者在讲聚类的时候,先讲了一个基于NLTK库的自然语言处理的例子,而聚类放在了后面,我打算分两小节来说,先说自然语言处理。

将原始文本转化为词袋

词袋 (Bag of Words,简称BoW) 是一种统计某个词在一份文档中出现次数的算法。统计所得的词频数据可以用于比较文档并测量其相似性,具体应用包括搜索、文档分类、主题建模等。

我们使用Scikit包里的CountVectorizer来先统计词频,书中例子是按照英文单词的方式来统计的,先别急,我先上手一个简单的例子试试看,虽然到中文的统计还有很长的路要走。

这里就要提到一个概念——NLP(Natural Language Processing),中文叫自然语言处理,现在大家熟悉的机器翻译主要研究和实践的就是对自然语言的处理。

自然语言处理(英语:natural language processing,缩写作 NLP)是人工智能和语言学领域的分支学科。此领域探讨如何处理及运用自然语言;自然语言认知则是指让电脑“懂”人类的语言。自然语言生成系统把计算机数据转化为自然语言。自然语言理解系统把自然语言转化为计算机程序更易于处理的形式。(wiki)

先看一张图,大致了解一下NLP到底干了什么,我们输入一句话:持续践行,从每天完成一件事开始。
这里写图片描述
这张图通过在线NLP工具生成,包含了很多概念:词性标注、命名实体、句法分析、语义角色标注 语义依存……

是不是又有点晕了?没关系,我们先建立一个概念,知道有一个NLP就足够了。

继续书中的例子,词频统计完成后,我们需要进行归一化。因为我们需要衡量不同帖子之间的相似性,那就需要一个计算距离的函数,如果要把数组当做向量进行相似度计算,我们就需要使用数组的全部元素。通过相似度的衡量方法(朴素方法),我们计算新帖子和其他所有老帖子的词频向量之间的欧氏距离(先不讲,欧式距离在很多地方都会用到),如下所示:

import scipy as sp
def dist_raw(v1, v2):
    delta = v1-v2
    return sp.linalg.norm(delta.toarray())

norm() 函数用于计算欧几里得范数(最小距离)。只需要用dist_raw 遍历所有帖子,并记录最相近的一个就行了。到这里,已经有一个能够衡量相似性的方法了(具体代码看==代码实现==小节),然后就是词语频次向量的归一化,并删除不重要的词语。

这里插一句,有时我们认为只有主谓宾这样的主干结构才有用,但实际应用中,很多副词可能才是我们需要关注的,比如,情感分析力对情绪的识别等,而在初期需要的只是先找出一句话表达的最重要的含义,因为我们想要快速找到相似性。

词干处理

我们需要一个函数将词语归约到特定的词干形式。Scikit并没有默认的词干处理器。书中通过自然语言处理工具包 (NLTK)下载一个免费的软件工具包。它提供了一个很容易嵌入CountVectorizer 的词干处理器。

安装和使用NLTK

python -m pip install -U nltk

但现在已经都是深度学习时代了,根据清华大学刘知远老师的观点,这在使用SVM+BOW的时候(也就是书里倡导的时代)曾是个问题。进入到深度学习时代,已经是直接可以用基于字的神经网络模型了,因为中英文在词性标注、句法分析等任务上颇有差异。

但我们的目的是为了快速上手,从历史了解更有助于进入深度学习时代,关于中文和英文的差异,以后再讲。

我们继续按照如下步骤对每个帖子进行处理:
1、在预处理阶段将原始帖子变成小写字母形式(这在父类中完成);
2、在词语切分阶段提取所有单词(这在父类中完成);
3、将每个词语转换成词干形式。

然而,这只能通过统计每个帖子的词频,并且对出现在多个帖子中的词语在权重上打折扣来解决。换句话说,当某个词语经常出现在一些特定帖子中,而在其他地方很少出现的时候,我们会赋予该词语较高的权值。

所以我们想要用一种更高级一点的办法来找到相似性,这里就是用了一个叫词频-反转文档频率 (TF-IDF)的算法。什么叫TF-IDF?

tf-idf(英语:term frequency–inverse document frequency)是一种用于信息检索与文本挖掘的常用加权技术。tf-idf是一种统计方法,用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。tf-idf加权的各种形式常被搜索引擎应用,作为文件与用户查询之间相关程度的度量或评级。除了tf-idf以外,互联网上的搜索引擎还会使用基于链接分析的评级方法,以确定文件在搜索结果中出现的顺序。(wiki)

词频-反转文档频率 (TF-IDF)是另一种根据文章中包含的词来判断文章主题的方法。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

a, abb, abc = ["a"], ["a", "b", "b"], ["a", "b", "c"]
D = [a, abb, abc]

print(tfidf("a", a, D))
#0.0
print(tfidf("b", abb, D))
#0.270310072072
print(tfidf("a", abc, D))
#0.0
print(tfidf("b", abc, D))
#0.135155036036
print(tfidf("c", abc, D))
#0.366204096223

实现代码

注意本地保存的几个文件的路径,需要修改DIR。

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 = CountVectorizer(min_df=1, stop_words='english',
# preprocessor=stemmer)
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')
    #charset_error='ignore')
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))

小结

我们现在的文本预处理过程包含以下步骤:
1、切分文本;
2、扔掉出现过于频繁,而又对检测相关帖子没有帮助的词语;
3、扔掉出现频率很低,只有很小可能出现在未来帖子中的词语;
4、统计剩余的词语;
5、考虑整个语料集合,从词频统计中计算TF-IDF值。

其实现在我用的多的切词工具是Ansj中文分词(这是一个基于n-Gram+CRF+HMM的中文分词的java实现),是一个对汉语比较好的切词工具,不过切词只是第一步,到后面的泛化,意图识别可能才是更重要的,后续我会专门讲基于自然语言处理的机器学习,然后再去说深度学习的方法。

今天主要学习了NLP的基础,虽然基础远远比这里说的复杂得多。

机器学习中难免出现很多生僻的概念,如果看不懂可以先记住,跳过,但一定要抓住核心脉络去学,不要为了学而学,因为很容易就放弃了。

参考资源

1、《机器学习系统设计》
2、《深度学习》
3、scikit
4、https://deeplearning4j.org/cn/bagofwords-tf-idf
5、https://www.zhihu.com/question/59227800
6、https://www.ltp-cloud.com/demo/
7、https://zh.wikipedia.org/wiki/%E8%AF%8D%E8%A2%8B%E6%A8%A1%E5%9E%8B
8、https://www.csdn.net/article/2015-06-19/2825009
9、https://zh.wikipedia.org/wiki/%E8%87%AA%E7%84%B6%E8%AF%AD%E8%A8%80%E5%A4%84%E7%90%86

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值