教程 | 理解和实现自然语言处理终极指南(附Python代码)

By 吴攀2017年2月16日 14:41

根据行情,只有21%的数据目前是结构化的。谈话、发推文、在 WhatsApp上发信息以及其他各种各样的活动,都在持续不断的产生数据。而大多数这种数据都是以非结构化的文本形式存在的。

最著名的例子有:社交媒体上的推文/帖子、用户到用户的聊天记录、新闻、博客、文章、产品或服务测评和医疗行业的病人记录。最近的例子有聊天机器人和其他声音驱动的机器人。

尽管我们有高维数据,但目前其中的信息并不能直接获得——除非被人工处理过或被一个自动化系统分析过。

为了从文本数据中产生具有重要意义和可实践的领悟,就需要了解自然语言处理的技巧和原理

所以,如果你今年打算打造一个聊天机器人,或者你想运用非结构化的文本数据的力量,那么你算看对了文章,这篇指南揭示了自然语言处理的概念以及它的技巧和实现方法。文章的主要目的是教导自然语言处理的概念以及让你了解把它运用到实际数据集上。

image-6.png

目录

1.自然语言处理简介

2.文本处理

3.文本到特征(文本数据上的特征工程)

4.自然语言处理面临的重要任务

5.重要的自然语言处理库


1.自然语言处理简介

自然语言处理是数据科学中以智能高效的方式对文本进行系统的分析、理解和信息提取的一个分支。通过利用自然语言处理及其成分,一个人能够组织起巨大数量的文本数据来执行许多自动化任务和解决例如自动摘要、机器翻译、命名实体识别、关系提取、情感分析、语音识别和主题分割等等非常广泛的问题。

开始之前,先解释一下这篇文章中用到的术语:

安装NTLK及其数据的步骤:

安装Pip:在终端运行:

    sudo easy_install pip

安装NTLK:在终端运行

    sudo pip install -U nltk

下载NTLK数据:终端python shell下输入如下代码:

``` import nltk nltk.download()```

遵循屏幕上的指令下载所需包或集。其他库可直接使用pip安装。


2.文本处理

现有数据中,文本是最非结构化的形式,里面有各种各样的噪声;如果没有预处理,文本数据都不能分析。清理和标准化文本的整个过程叫做文本预处理(text preprocessing),其作用是使文本数据没有噪声并且可以分析。

主要包括三个步骤:

下图展示了文本预处理流程的结构。


Image-1.png

2.1移除噪声

任何与数据上下文和最终输出无关的文本都可被判作噪声。

例如,语言停止词(stopword,语言中常用的词汇:系动词is,am,定冠词the,介词of,in)、URL 或链接、社交媒体实体(提及、标签)、标点符号和特定行业词汇。这一步移除了文本中所有类型的噪声。

移除噪声通用的做法是准备一个噪声实体的词典,在文本对象上逐个 token(或逐词)迭代,消除在噪声词典中出现的标签。

以下是实现这一步的 Python 代码:

```

# Sample code to remove noisy words from a text

noise_list = ["is", "a", "this", "..."] 
def _remove_noise(input_text):
    words = input_text.split() 
    noise_free_words = [word for word in words if word not in noise_list] 
    noise_free_text = " ".join(noise_free_words) 
    return noise_free_text

_remove_noise("this is a sample text")
>>> "sample text"
```

另外一种方法是使用正则表达式,尽管其只能解决特定模式的噪声。我们在之前的文章中详细介绍了正则表达式:https://www.analyticsvidhya.com/blog/2015/06/regular-expression-python/

以下是从输入文本中移除正则表达式的 Python 代码:

```

# Sample code to remove a regex pattern 
import re 

def _remove_regex(input_text, regex_pattern):
    urls = re.finditer(regex_pattern, input_text) 
    for i in urls: 
        input_text = re.sub(i.group().strip(), '', input_text)
    return input_text

regex_pattern = "#[A-Za-z0-9\w]*"

_remove_regex("remove this #hashtag from analytics vidhya", regex_pattern)
>>> "remove this  from analytics vidhya"

```

2.2词汇规范化

另外一种文本形式的噪声是由一个词汇所产生的多种表示形式。

例如,“play”,“player”,“played”,“plays”和“playing”,这些词汇都是由“play”变化而来的。虽然它们意义不一,但根据上下文都是相似的。词汇规范化这一步把一个词的不同展现形式转化为了他们规范化的形式(也叫做引理(lemma))。规范化是文本上的特征工程起中枢作用的一步,因为它把高维特征(N个不同的特征)转化为了对任何机器学习模型都很理想的低维空间(1个特征)。

最常见的词汇规范化是:

下面是实现词形还原和词干提取的代码,使用了一个流行的 Python 库 NLTK:

```

from nltk.stem.wordnet import WordNetLemmatizer 
lem = WordNetLemmatizer()

from nltk.stem.porter import PorterStemmer 
stem = PorterStemmer()

word = "multiplying" 
lem.lemmatize(word, "v")
>> "multiply" 
stem.stem(word)
>> "multipli"

```

2.3对象标准化

文本数据经常包含不在任何标准词典里出现的词汇或短语。搜索引擎和模型都识别不了这些。

比如,首字母缩略词、词汇附加标签和通俗俚语。通过正则表达式和人工准备的数据词典,这种类型的噪声可以被修复。以下代码使用了词典查找方法来替代文本中的社交俚语。

``` 
lookup_dict = {'rt':'Retweet', 'dm':'direct message', "awsm" : "awesome", "luv" :"love", "..."}
def _lookup_words(input_text):
    words = input_text.split() 
    new_words = [] 
    for word in words:
        if word.lower() in lookup_dict:
            word = lookup_dict[word.lower()]
        new_words.append(word) new_text = " ".join(new_words) 
        return new_text

_lookup_words("RT this is a retweeted tweet by Shivam Bansal")
>> "Retweet this is a retweeted tweet by Shivam Bansal"

```

除了目前为止讨论过的三个步骤,其他类型的文本预处理有编码-解码噪声,语法检查器和拼写改正等。我之前的一篇文章给出了预处理及其方法的细节。


3.文本到特征(文本数据上的特征工程)

为了分析已经预处理过的数据,需要将数据转化成特征(feature)。取决于用途,文本特征可通过句法分析、实体/N元模型/基于词汇的特征、统计特征和词汇嵌入等方法来构建。下面来详细理解这些技巧。

3.1句法分析

句法分析涉及到对句中词的语法分析和位置与词汇的关系的分析。依存语法(Dependency Grammar)和词性标注(Part of Speech tags)是重要的文本句法属性。

依赖树(Dependency Trees)——由一些词汇共同组成的句子。句中词与词之间的联系是由基本的依存语法决定的。从属关系语法是一类解决(已标签)两个词汇项(字词)间二元不对称关系的句法文本分析。每一种关系都可用三元组(关系、支配成分、从属成分)来表示。例如:考虑下面这个句子:“Bills on ports and immigration were submitted by Senator Brownback, Republican of Kansas.”词汇间的关系可由如下所示的树的形式观察得到

image-2.png

观察树的形状可得:“submitted”是该句的根词(root word),由两颗子树所连接(主语和宾语子树)。每一颗子树本身又是一颗依存关系树(dependency tree ),其中的关系比如有 - (“Bills” <-> “ports” <by> “proposition” 关系),(“ports” <-> “immigration” <by> “conjugation” 关系)

这种类型的树,当从上至下迭代分析时可以得到语法关系三元组。对于很多自然语言处理问题,比如实体性情感分析,执行者(actor)与实体识别和文本分类等,语法关系三元组都可以用作特征。Python wrapper 的StanfordCoreNLP( http://stanfordnlp.github.io/CoreNLP/ 来自斯坦福自然语言处理组,只允许商业许可证)和NTLK从属关系语法可以用来生成依赖树。

词性标注(PoS/Part of speech tagging)——除了语法关系外,句中每个词都与词性(名词、动词、形容词、副词等等)联系起来。词性标注标签决定了句中该词的用法和作用。这里有宾夕法尼亚大学定义的所有可能的词性标签表。以下代码使用了NTLK包对输入文本执行词性标签注释。(NTLK提供了不同的实现方式,默认是感知器标签)

```
from nltk import word_tokenize, pos_tag
text = "I am learning Natural Language Processing on Analytics Vidhya"
tokens = word_tokenize(text)
print pos_tag(tokens)
>>> [('I', 'PRP'), ('am', 'VBP'), ('learning', 'VBG'), ('Natural', 'NNP'),('Language', 'NNP'),
('Processing', 'NNP'), ('on', 'IN'), ('Analytics', 'NNP'),

词性标注被用在许多重要的自然语言处理目的上:

A. 词义消歧:一些词汇根据用法有很多种意思。例如,下面的两个句子:

“Book”在不同的上下文中出现,然而这两种情况的词性标签却不一样。在第一句中,“book”被用作动词,而在第二句中,它被用作名词。(Lesk算法也可被用于相同的目的)

B. 提高基于词汇的特征:当词汇作为特征时,一个学习模型可以学习到不同的词汇上下文,然而特征与词性连接起来,上下文就被保留了,因此得到了很强的特征。例如:

C. 规范化和词形归并(Lemmatizatio):词性标签是将词转化为其基本形式(引理)的基础

D. 高效移除停止词:词性标签在移除停止词方面也非常有用。

例如,有一些标签总是定义低频/较低重要性的词汇。
例如:(IN – “within”, “upon”, “except”), (CD – “one”,”two”, “hundred”), (MD – “may”, “must” 等)

3.2 实体提取(实体作为特征)

实体(entity)被定义为句中最重要的部分——名词短语、动词短语或两者都有。实体检测算法通常是由基于规则的解析、词典查询、词性标签和依存分析组合起来的模型。实体检测的适用性很广泛,在自动聊天机器人、内容分析器和消费者见解中都有应用。

image-3.png

主题建模和命名实体识别是自然语言处理领域中两种关键的实体检测方法。

A. 命名实体识别(NER/Named Entity Recognition)

从文本中检测命名实体比如人名、位置、公司名称等的过程叫做命名实体识别(NER)。例如:

句 - Sergey Brin, the manager of Google Inc. is walking in the streets of New York.命名实体 - ( “人” : “Sergey Brin” ), (“公司名” : “Google Inc.”), (“位置” : “New York”)典型NER模型包含三个模块:

B.主题建模

主题建模是自动识别文本集中主题的过程,它以无监督的方式从语料库中的词汇里提取隐藏的模式。主题(topic)被定义为“文本集中共同出现术语的重复模式”。一个好的主题模型能对“健康”、“医生”、“病人”、“医院”建模为“健康保健”,“农场”、“作物”、“小麦”建模为“耕作”。

隐含狄利克雷分布(LDA)是最流行的主题建模技术,以下是在Python环境下使用LDA技术实现主题建模的代码。若想查看更详细的细节,请参看:https://www.analyticsvidhya.com/blog/2016/08/beginners-guide-to-topic-modeling-in-python/

```
doc1 = "Sugar is bad to consume. My sister likes to have sugar, but not my father." 
doc2 = "My father spends a lot of time driving my sister around to dance practice."
doc3 = "Doctors suggest that driving may cause increased stress and blood pressure."
doc_complete = [doc1, doc2, doc3]
doc_clean = [doc.split() for doc in doc_complete]

import gensim from gensim
import corpora

# Creating the term dictionary of our corpus, where every unique term is assigned an index.  
dictionary = corpora.Dictionary(doc_clean)

# Converting list of documents (corpus) into Document Term Matrix using dictionary prepared above. 
doc_term_matrix = [dictionary.doc2bow(doc) for doc in doc_clean]

# Creating the object for LDA model using gensim library
Lda = gensim.models.ldamodel.LdaModel

# Running and Training LDA model on the document term matrix
ldamodel = Lda(doc_term_matrix, num_topics=3, id2word = dictionary, passes=50)

# Results 
print(ldamodel.print_topics())

```

C.N-Grams 特征

N-Grams是指N个词汇的结合体。N-Grams(N>1)作为特征与词汇(Unigrams)作为特征相比,通常会更加富含信息。同时,bigrams(N=2)被认为是最重要的特征。以下代码生成了文本的 bigrams。

``` 
def generate_ngrams(text, n):
    words = text.split()
    output = []  
    for i in range(len(words)-n+1):
        output.append(words[i:i+n])
    return output

>>> generate_ngrams('this is a sample text', 2)
# [['this', 'is'], ['is', 'a'], ['a', 'sample'], , ['sample', 'text']] 
```

3.3 统计特征

文本数据使用该节所讲的几种技术可直接量化成数字。

A. 术语频率 - 逆文献频率(TF – IDF)

TF-IDF 是经常被使用在信息检索问题上的权重模型。TF-IDF在不考虑文献中词的具体位置情况下,基于文献中出现的词汇将文本文献转化成向量模型。例如,假设有一个N 个文本文献的数据集,在任何一个文献“D”中,TF和IDF会被定义为 - 术语频率(TF) - 术语“t”的术语频率被定义为“t”在文献“D”中的数量。

逆文献频率(IDF)- 术语的逆文献频率被定义为文本集中可用文献的数量与包含术语“t”的文献的数量的比例的对数。

TF-IDF公式给出了文本集中术语的相对重要性,以下为TF-IDF公式和使用Python的scikit学习包将文本转换为tf-idf向量。

image-4.png

```
from sklearn.feature_extraction.text import TfidfVectorizer
obj = TfidfVectorizer()
corpus = ['This is sample document.', 'another random document.', 'third sample document text']
X = obj.fit_transform(corpus)
print X
>>>
(0, 1) 0.345205016865
(0, 4) ... 0.444514311537
(2, 1) 0.345205016865
(2, 4) 0.444514311537
```


模型创建了一个词典并给每一个词汇赋了一个索引。输出的每一行包含了一个元组(i,j)和在第i篇文献索引j处词汇的tf-idf值。

B. 数量/密度/可读性特征

基于数量或密度的特征同样也能被用于模型和分析中。这些特征可能看起来比较繁琐但是对学习模型有非常大的影响。一些特征有:词数、句数、标点符号数和特定行业词汇的数量。其他类型的测量还包括可读性测量(比如音节数量、smog index 和易读性指数)。参考 Textstat 库创建这样的特征:https://github.com/shivam5992/textstat

3.4词嵌入(文本向量)

词嵌入是将词表示为向量的方法,在尽量保存文本相似性的基础上将高维的词特征向量映射为低维特征向量。词嵌入广泛用于深度学习领域,例如卷积神经网络和循环神经网络。Word2Vec和GloVe是目前非常流行的两种做词嵌入的开源工具包,都是将文本转化为对应的向量。

Word2Vec是由预处理模块和两个浅层神经网络(CBOW/Continuous Bag of Words和Skip-gram)组成,这些模型广泛用于自然语言处理问题。Word2Vec首先从训练语料库中组织词汇,然后将词汇做词嵌入,得到对应的文本向量。下面的代码是利用gensim包实现词嵌入表示。

```
from gensim.models import Word2Vec
sentences = [['data', 'science'], ['vidhya', 'science', 'data', 'analytics'],['machine', 'learning'], ['deep', 'learning']]

# train the model on your corpus  
model = Word2Vec(sentences, min_count = 1)

print model.similarity('data', 'science')
>>> 0.11222489293

print model['learning']  
>>> array([ 0.00459356  0.00303564 -0.00467622  0.00209638, ...])

```

这些向量作为机器学习的特征向量,然后利用余弦相似性、单词聚类、文本分类等方法来衡量文本的相似性。


4.自然语言处理(NLP)的重要任务

本节讨论NLP的不同案例和问题。

4.1文本分类

文本分类是NLP的经典问题之一。例如垃圾邮件识别、新闻主题分类、搜索引擎的网页组织和情感分类。

通俗来讲,文本分类就是系统地将文本对象(文件和句子)按照一定的分类体系或标准进行自动分类标记。尤其是当数据量太大时,文本分类对于组织、信息过滤、储存非常有帮助。典型的自然语言分类包括两部分:a)训练(b)预测。首先,文本输入是创建特征过程,机器学习从这些特征中学习,然后对新文本进行预测。


image-5.png


下面的代码利用了Python的TextBlob文本处理库中的朴素贝叶斯模型。

```
from textblob.classifiers import NaiveBayesClassifier as NBC
from textblob import TextBlob
training_corpus = [
                   ('I am exhausted of this work.', 'Class_B'),
                   ("I can't cooperate with this", 'Class_B'),
                   ('He is my badest enemy!', 'Class_B'),
                   ('My management is poor.', 'Class_B'),
                   ('I love this burger.', 'Class_A'),
                   ('This is an brilliant place!', 'Class_A'),
                   ('I feel very good about these dates.', 'Class_A'),
                   ('This is my best work.', 'Class_A'),
                   ("What an awesome view", 'Class_A'),
                   ('I do not like this dish', 'Class_B')]
test_corpus = [
                ("I am not feeling well today.", 'Class_B'), 
                ("I feel brilliant!", 'Class_A'), 
                ('Gary is a friend of mine.', 'Class_A'), 
                ("I can't believe I'm doing this.", 'Class_B'), 
                ('The date was good.', 'Class_A'), ('I do not enjoy my job', 'Class_B')]

model = NBC(training_corpus) 
print(model.classify("Their codes are amazing."))
>>> "Class_A" 
print(model.classify("I don't like their computer."))
>>> "Class_B"
print(model.accuracy(test_corpus))
>>> 0.83 
```

Scikit.Learn为文本分类提供了另一种途径:

``` 
from sklearn.feature_extraction.text
import TfidfVectorizer from sklearn.metrics
import classification_report
from sklearn import svm 

# preparing data for SVM model (using the same training_corpus, test_corpus from naive bayes example)
train_data = []
train_labels = []
for row in training_corpus:
    train_data.append(row[0])
    train_labels.append(row[1])

test_data = [] 
test_labels = [] 
for row in test_corpus:
    test_data.append(row[0]) 
    test_labels.append(row[1])

# Create feature vectors 
vectorizer = TfidfVectorizer(min_df=4, max_df=0.9)
# Train the feature vectors
train_vectors = vectorizer.fit_transform(train_data)
# Apply model on test data 
test_vectors = vectorizer.transform(test_data)

# Perform classification with SVM, kernel=linear 
model = svm.SVC(kernel='linear') 
model.fit(train_vectors, train_labels) 
prediction = model.predict(test_vectors)
>>> ['Class_A' 'Class_A' 'Class_B' 'Class_B' 'Class_A' 'Class_A']

print (classification_report(test_labels, prediction))
```

文本分类的效果在很大程度上依赖于特征的选择,在机器学习中,使用越来越多的训练数据总是一个好的选择。

4.2 文本匹配/相似性

NLP的一个重要应用是对文本对象进行匹配以找到相似性。文本匹配的重要应用包括自动拼写校正、删除重复数据和基因组分析等。目前有许多文本分类方法,本节对一些重要的分类方法进行详细介绍。

A. Levenshtein距离 —— 两个字符串之间的Levenshtein距离被定义为将一个字符串转换为另一个字符串所需的最小编辑次数,允许的编辑操作包括单个字符的插入、删除或替换。以下代码是高效内存计算的具体实现。

``` 
def levenshtein(s1,s2): 
    if len(s1) > len(s2):
        s1,s2 = s2,s1 
    distances = range(len(s1) + 1) 
    for index2,char2 in enumerate(s2):
        newDistances = [index2+1]
        for index1,char1 in enumerate(s1):
            if char1 == char2:
                newDistances.append(distances[index1]) 
            else:
                 newDistances.append(1 + min((distances[index1], distances[index1+1], newDistances[-1]))) 
        distances = newDistances 
    return distances[-1]

print(levenshtein("analyze","analyse"))
```

B.音素匹配 ——音素匹配算法以关键词作为输入(人的姓名、位置名称等),然后产生一个字符串来标识一组音素相似的单词。音素匹配对于搜索大文本语料库、更正拼写错误和匹配相关名称非常有用。 Soundex和Metaphone是目前两种主要音素匹配算法。 Python的Fuzzy模块用来计算不同单词的soundex字符串,例如:

``` 
import fuzzy 
soundex = fuzzy.Soundex(4) 
print soundex('ankit')
>>> “A523”
print soundex('aunkit')
>>> “A523” 
```

C.灵活的字符串匹配 —— 一个完整的文本匹配系统包括不同的算法,这些算法计算各种文本差异。正则表达式对字符串匹配非常有帮助。另一些常见的字符串匹配技术有精确串匹配,lemmatized匹配和紧凑匹配(考虑空格、标点符号、俚语等)。

D.余弦相似性 —— 当文本以向量表示时,也可以应用余弦相似性来表征矢量的相似性。下面的代码将文本转换为向量(使用术语频率),并应用余弦相似性来衡量文本之间的相近性。

``` 
import math
from collections import Counter
def get_cosine(vec1, vec2):
    common = set(vec1.keys()) & set(vec2.keys())
    numerator = sum([vec1[x] * vec2[x] for x in common])

    sum1 = sum([vec1[x]**2 for x in vec1.keys()]) 
    sum2 = sum([vec2[x]**2 for x in vec2.keys()]) 
    denominator = math.sqrt(sum1) * math.sqrt(sum2)
   
    if not denominator:
        return 0.0 
    else:
        return float(numerator) / denominator

def text_to_vector(text): 
    words = text.split() 
    return Counter(words)

text1 = 'This is an article on analytics vidhya' 
text2 = 'article on analytics vidhya is about natural language processing'

vector1 = text_to_vector(text1) 
vector2 = text_to_vector(text2) 
cosine = get_cosine(vector1, vector2)
>>> 0.62 
```

4.3指代消解

指代消解是指找出句子中相对应的词或短语。考虑如下例子“Donald went to John’s office to see the new table. He looked at it for an hour.”,人类很容易识别出“He”表示的是Donald而不是John’s office,“it”是指new table而不是John’s office。指代消解是自然语言处理的重要内容,它被用于自动摘要、问答系统和信息采集等方面。 Stanford CoreNLP提供了一个商用Python wrapper:

https://github.com/Wordseer/stanford-corenlp-python

4.4其它NLP问题/任务


5.重要的自然语言处理库(Python)


声明:本文由机器之心编译出品,原文来自analyticsvidhya,作者Shivam Bansal,转载请查看要求,机器之心对于违规侵权者保有法律追诉权。