NLP(Natural Language Processing)指的是自然语言处理,就是研究计算机理解人类语言的一项技术。
计算机无论如何都无法理解人类语言,它只会计算,不过就是通过计算,它让你感觉它理解了人类语言。
你看,它面临文字的时候,都是要通过数字去理解的。
所以,如何把文本转成数字,这是NLP中最基础的一步。
很幸运,TensorFlow框架中,提供了一个很好用的类Tokenizer, 它就是为此而生的。
假设我们有一批文本,我们想投到分词器中,让他变为数字。
文本格式如下:
corpus = ["I love cat" , "I love dog" , "I love you too"]
想要对象,首先要先谈一个。想要使用分词器,首先得构建一个。
from tensorflow.keras.preprocessing.text import Tokenizer
tokenizer = Tokenizer()
可以调用分词器的fit_on_texts
方法来适配文本。
tokenizer.fit_on_texts(corpus)
经过tokenizer吃了文本数据并适配之后,tokenizer已经从小白变为鸿儒了,它对这些文本可以说是了如指掌。
["I love cat" , "I love dog" , "I love you too"]
tokenizer.document_count
记录了它处理过几段文本,此时值是3,表示处理了3段。tokenizer.word_index
将所有单词上了户口,每一个单词都指定了一个身份证编号,此时值是{'cat': 3, 'dog': 4, 'i': 1, 'love': 2, 'too': 6, 'you': 5}
。比如cat编号是3,3就代表cat。tokenizer.index_word
和word_index
相对,数字在前{1: 'i', 2: 'love', 3: 'cat', 4: 'dog', 5: 'you', 6: 'too'}
,编号为1的是i,i用1表示。tokenizer.word_docs
则统计的是每一个词出现的次数,此时值是{'cat': 1, 'dog': 1, 'i': 3, 'love': 3, 'too': 1, 'you': 1}
。比如“i”出现了3次。"I love cat"、"i love Cat"、"I love cat!",经过fit_on_texts
后,结果是一样的。
这说明它的处理是忽略英文字母大小写和英文标点符号的。
虽然上面对文本进行了适配,但也只是对词语做了编号和统计,文本并没有全部变为数字。
此时,可以调用分词器的texts_to_sequences
方法来将文本序列化为数字。
input_sequences = tokenizer.texts_to_sequences(corpus)
["I love cat" , "I love dog" , "I love you too"]
通过序列化后, 文本列表变为数字列表 [[1, 2, 3], [1, 2, 4], [1, 2, 5, 6]]
。
文本之所以能被序列化,其基础就是每一个词汇都有一个编号。
1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|
i | love | cat | dog | you | too |
I love you -> 1 2 5
当我们遇到没有见过的词汇时,比如 I do not love cat。 这时候该怎么办?
而这种情况出现的概率还是挺高的。
举个例子,你做了一个电影情感分析,用20000条电影的好评和差评训练了一个神经网络模型。当你想试验效果的时候,这时候你随便输入一条评论文本,这条新评论文本里面的词汇很有可能之前没出现过,这时候也是要能序列化并给出预测结果的。
我们先来看会发生什么?
corpus = ["I love cat","I love dog","I love you too"]
tokenizer = Tokenizer()
tokenizer.fit_on_texts(corpus)
# tokenizer.index_word: {1: 'i', 2: 'love', 3: 'cat', 4: 'dog', 5: 'you', 6: 'too'}
input_sequences = tokenizer.texts_to_sequences(["I do not love cat"])
# input_sequences: [[1, 2, 3]]
从结果看,它给忽略了。"I do not love cat"和"I love cat"最终结果一样,只因为"do"和"not"没有备案。
但是,有时候我们并不想忽略它,这时候该怎么办?
很简单,只需要构建Tokenizer的时候传入一个参数oov_token='<OOV>'
。
OOV是什么意思?在自然语言文本处理的时候,我们通常会有一个字词库(vocabulary),它来源于训练数据集。当然,这个词库是有限的。当以后你有新的数据集时,这个数据集中有一些词并不在你现有的vocabulary里,我们就说这些词汇是out-of-vocabulary,简称OOV。
因此只要是通过Tokenizer(oov_token='<OOV>')
方式构建的,分词器会给预留一个编号,专门用于标记超纲的词汇。
我们再来看看会发生什么?
corpus = ["I love cat","I love dog","I love you too"]
tokenizer = Tokenizer(oov_token='<OOV>')
tokenizer.fit_on_texts(corpus)
# tokenizer.index_word: {1:'<OOV>',2:'i',3:'love',4:'cat',5:'dog',6:'you',7:'too'}
input_sequences = tokenizer.texts_to_sequences(["I do not love cat"])
# input_sequences: [[2, 1, 1, 3, 4]]
从结果可见,给超纲词一个编号1,最终"I do not love cat"序列化为2, 1, 1, 3, 4。
["I love cat" , "I love dog" , "I love you too"]
已经通过序列化变为 [[1, 2, 3], [1, 2, 4], [1, 2, 5, 6]]
。
文本变数字这一步,看似大功告成了,实际上还差一步。
你看下面这张图,是两捆铅笔,你觉得不管是收纳还是运输,哪一个更方便处理呢?
从生活的常识来看,肯定是B序列更方便处理。因为它有统一的长度,可以不用考虑差异性,50个或者50000个,只是单纯的倍数关系。
是的,计算机也和你一样,[[1, 2, 3], [1, 2, 4], [1, 2, 5, 6]]
这些数字也是有长有短的,它也希望你能统一成一个长度。
TensorFlow早已经考虑到了,它提供了一个pad_sequences
方法,专门干这个事情。
from tensorflow.keras.preprocessing.sequence import pad_sequences
sequences = [[1, 2, 3], [1, 2, 4], [1, 2, 5, 6]]
sequences = pad_sequences(sequences)
# sequences:[[0, 1, 2, 3],[0, 1, 2, 4],[1, 2, 5, 6]]
将序列数据传进去,它会以序列中最长的那一条为标准长度,其他短的数据会在前面补0,这样就让序列长度统一了。
统一长度的序列,就是NLP要的素材。
划重点:我们会在各种场合看到这样一句代码vocab_size = len(tokenizer.word_index) + 1
,词汇总量=文本中所有词汇+1,这个1其实就是用来填充的0,数据集中没有这个词,其目的就是凑长度用的。要注意<OOV>
这个是真实存在的一个词汇,它表示超纲的词汇。
数据的填充处理,存在不同的场景。
上面说了前面补0, 其实有时候也希望后面补0。
sequences=[[1,2,3],[1,2,4],[1,2,5,6]]
sequences=pad_sequences(sequences, padding="post")
# sequences:[[1, 2, 3, 0],[1, 2, 4, 0],[1, 2, 5, 6]]
参数padding是填充类型,padding="post"
是后面补0。前面补0是padding="pre"
,pre是默认的。
还有一种场景,就是裁剪成固定的长度,这类也很常用。
举个例子,看下面几个序列:
[[2,3],[1,2],[3,2,1],[1,2,5,6,7,8,9,9,9,1]]
上面有4组数据,他们的长度分别是2,2,3,10。这时候如果给序列进行填充,所有数据都会填充到10的长度。
其实,这是没有必要的。因为不能为了个别数据,导致整体数据产生过多冗余。
因此,填充需要进行裁剪。
我们更愿意填充成长度为5的序列,这样可以兼顾各方利益。
代码如下:
sequences=[[2,3],[1,2],[3,2,1],[1,2,5,6,7,8,9,9,9,1]]
sequences=pad_sequences(sequences, maxlen = 5, padding='post', truncating='post')
# [[2,3,0,0,0],[1,2,0,0,0],[3,2,1,0,0],[1,2,5,6,7]]
新增了2个参数,一个是maxlen = 5
,是序列允许的最大长度。另一个参数是truncating='post'
,表示采用从后部截断(前部是pre)的方式。
这段代码意思是,不管来了什么序列,我都要搞成长度为5的数据,不够的后面补0,超过的后面扔掉。
其实这种方式是实战中最常用到的。因为训练数据的输入序列格式我们可以控制,我们也用它训练了一个模型。但是当预测时,输入格式是千奇百怪的。比如我们用了一个100长度的数据训练了一个模型。当用这个模型做预测时,用户输入了一个10000长度的数据,这时模型就识别不了了。所以,不管用户输了10000长度,还是1个长度,都要转成训练时的长度。
英文有空格区分出来各个词汇。
I love cat.
里面有3个词语:I、love、cat。
但是,中文却没有一个标志来区分词汇。
我喜欢猫。
里面有几个词语?
这就很尴尬了。
做NLP是必须要以词汇为基本单位的。
中文的词汇拆分很庞大,一般采用第三方的服务。
举例采用结巴分词实现词汇拆分。
代码对 Python 2/3 均兼容
easy_install jieba
或者 pip install jieba
/ pip3 install jieba
python setup.py install
import jieba
来引用关注下面的“北京大学”:
import jieba
sentence = " ".join(jieba.cut("欢迎来北京大学餐厅"))
print(sentence) # 欢迎 来 北京大学 餐厅
sentence2 = " ".join(jieba.cut("欢迎来北京大学生志愿者中心"))
print(sentence2) # 欢迎 来 北京 大学生 志愿者 中心
中文的自然语言处理首先要将词汇拆分出来,这是唯一区别。
阅读量:2020
点赞量:0
收藏量:0