NLP知识点:Tokenizer分词器

您所在的位置:网站首页 一个风加三个火读什么字 NLP知识点:Tokenizer分词器

NLP知识点:Tokenizer分词器

2023-09-20 23:19| 来源: 网络整理| 查看: 265

「这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战」。

1. 为什么要有分词器?

NLP(Natural Language Processing)指的是自然语言处理,就是研究计算机理解人类语言的一项技术。

计算机无论如何都无法理解人类语言,它只会计算,不过就是通过计算,它让你感觉它理解了人类语言。

举个例子:单=1,双=2,计算机面临“单”和“双”的时候,它所理解的就是2倍关系。 再举一个例子:赞美=1,诋毁=0, 当计算机遇到0.5的时候,它知道这是“毁誉参半”。 再再举一个例子:女王={1,1},女人={1,0},国王={0,1},它能明白“女人”+“国王”=“女王”。

你看,它面临文字的时候,都是要通过数字去理解的。

所以,如何把文本转成数字,这是NLP中最基础的一步。

很幸运,TensorFlow框架中,提供了一个很好用的类Tokenizer, 它就是为此而生的。

2. 分词工具 Tokenizer

假设我们有一批文本,我们想投到分词器中,让他变为数字。

文本格式如下:

corpus = ["I love cat" , "I love dog" , "I love you too"] 2.1 构建分词器

想要对象,首先要先谈一个。想要使用分词器,首先得构建一个。

from tensorflow.keras.preprocessing.text import Tokenizer tokenizer = Tokenizer() 2.2 适配文本 fit_on_texts

可以调用分词器的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后,结果是一样的。

这说明它的处理是忽略英文字母大小写和英文标点符号的。

2.3 文本序列化 texts_to_sequences

虽然上面对文本进行了适配,但也只是对词语做了编号和统计,文本并没有全部变为数字。

此时,可以调用分词器的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]]。

延伸知识:超出语料库 OOV

文本之所以能被序列化,其基础就是每一个词汇都有一个编号。

123456ilovecatdogyoutoo

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是什么意思?在自然语言文本处理的时候,我们通常会有一个字词库(vocabulary),它来源于训练数据集。当然,这个词库是有限的。当以后你有新的数据集时,这个数据集中有一些词并不在你现有的vocabulary里,我们就说这些词汇是out-of-vocabulary,简称OOV。

因此只要是通过Tokenizer(oov_token='')方式构建的,分词器会给预留一个编号,专门用于标记超纲的词汇。

我们再来看看会发生什么?

corpus = ["I love cat","I love dog","I love you too"] tokenizer = Tokenizer(oov_token='') tokenizer.fit_on_texts(corpus) # tokenizer.index_word: {1:'',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。

2.4 序列填充 pad_sequences

["I love cat" , "I love dog" , "I love you too"]已经通过序列化变为 [[1, 2, 3], [1, 2, 4], [1, 2, 5, 6]]。

文本变数字这一步,看似大功告成了,实际上还差一步。

你看下面这张图,是两捆铅笔,你觉得不管是收纳还是运输,哪一个更方便处理呢?

铅笔.jpg

从生活的常识来看,肯定是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,数据集中没有这个词,其目的就是凑长度用的。要注意这个是真实存在的一个词汇,它表示超纲的词汇。

延伸知识:更多的定制填充

数据的填充处理,存在不同的场景。

上面说了前面补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的长度。

[2, 3, 0, 0, 0, 0, 0, 0, 0, 0] [1, 2, 0, 0, 0, 0, 0, 0, 0, 0] [3, 2, 1, 0, 0, 0, 0, 0, 0, 0] [1, 2, 5, 6, 7, 8, 9, 9, 9, 1]

其实,这是没有必要的。因为不能为了个别数据,导致整体数据产生过多冗余。

因此,填充需要进行裁剪。

我们更愿意填充成长度为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个长度,都要转成训练时的长度。

3. 中文分词

英文有空格区分出来各个词汇。

I love cat.

里面有3个词语:I、love、cat。

但是,中文却没有一个标志来区分词汇。

我喜欢猫。

里面有几个词语?

这就很尴尬了。

做NLP是必须要以词汇为基本单位的。

中文的词汇拆分很庞大,一般采用第三方的服务。

举例采用结巴分词实现词汇拆分。

3.1 jieba的安装和使用方法

代码对 Python 2/3 均兼容

全自动安装:easy_install jieba 或者 pip install jieba / pip3 install jieba 半自动安装:先下载 pypi.python.org/pypi/jieba/ ,解压后运行 python setup.py install 手动安装:下载代码文件将 jieba 目录放置于当前目录或者 site-packages 目录 通过 import jieba 来引用

关注下面的“北京大学”:

import jieba sentence = " ".join(jieba.cut("欢迎来北京大学餐厅"))  print(sentence) # 欢迎 来 北京大学 餐厅 sentence2 = " ".join(jieba.cut("欢迎来北京大学生志愿者中心"))  print(sentence2) # 欢迎 来 北京 大学生 志愿者 中心

中文的自然语言处理首先要将词汇拆分出来,这是唯一区别。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3