自从开始使用Python做深度学习的相关项目时,大部分时候或者说基本都是在研究图像处理与分析方面,但是找工作反而碰到了很多关于自然语言处理(natural language processing: NLP)的问题,所以决定花点时间学习并且写下来,希望那些跟我一样同时在学习NLP的朋友能有一些帮助,学习过程中以英文为文本标准,后期会尝试用中文,并且将相关的信息补进来博客文章中。刚开始学习哪里讲得不好,多海涵并且欢迎指出。
NLP的首要任务就是将文本内容做Tokenization(标识化)处理,也就是说我们将文本分割成一个小块一个小块的例如以一个英文单词为单位或者一个汉字为单位,这样子的操作主要是方便我们可以更集中的去分析文本信息的内容和文本想表达的含义。当然分割是一个大范围,不仅仅是文本分成不同的词,也可以将整个文本分成段落,进而分成句子,句子在细分到词。当然,我们一般所说的标识化就是将整句分割为单个标识符(tokens)。
文本标识化主要是将没有结构的数据(unstructured data)或者自然语言文本分割成不同的分散的(discrete)块信息,从而可以被进行统计计算,这样子做的原因是我们可以通过计算每个标识符(tokens)在文件中出现的频次从而形成一个频次向量(vector)组来表示文件,因为计算机是无法直接处理我们人类所能辨识的文本信息,所以我们用统计的方式将纯文本信息转化为数字信息。完成了转换之后,我们就可以直接将这些频次向量作为特征输入到机器学习或者深度学习相关的算法模型中去进行训练。
当我们实际处理的时候一个比较简单的标记化处理就是利用空格作为分隔符将字符串分割,下面的代码是简单的将英文句子进行分割:
1 sentence_example = """I love deep learning, I love natural language processing.""" 2 token_seq1 = str.split(sentence_example) 3 token_seq2 = sentence_example.split(' ') 4 print("token_seq1: ", token_seq1) 5 print("token_seq2: ", token_seq2)
第二和第三行采用不同形式对句子进切分割,输出的结果是一样的:
token_seq1: ['I', 'love', 'deep', 'learning,', 'I', 'love', 'natural', 'language', 'processing.'] token_seq2: ['I', 'love', 'deep', 'learning,', 'I', 'love', 'natural', 'language', 'processing.']
但是我们可以看出上面的做的标识化的结果是有问题的,processing后面跟了句号,正常情况下,我们在一个句子的末尾是不需要跟着句号的在做自然语言处理分析的时候,当然这个情况分的比较多种,倘若我们句子的最后是一个数字13,那么‘13.’是表示13呢还是标识一个13之后有小数呢?这点需要我们进行一定的的解析。这里我们先不对句号等标点符号进行处理,随着学习的深入,我们也会遇到,到时再以相应的技巧进行处理。
在完成句子词结构分割之后,我们需要做的就是将其转化成计算机系统能识别和处理的数字,这个过程我们叫做one-hot-encoding:即我们可以创建一个数组(矩阵),数组由多个向量组成,每个向量中有一个数字为1,其余数字为0,1在向量所在的位置代表的是该英文单词出现的位置,one-hot也就因此而来,下面代码演示如何进行one-hot-encoding:
1 import numpy as np 2 3 token_seq = str.split(sentence_example) # 使用str.plit()函数分割句子 4 vocab = sorted(set(token_seq)) # set函数找出句子中所有独特唯一的单词,sorted函数则按照数字字母(先大写后小写)方式排序 5 ','.join(vocab) # 按照分割的单词组成新的语句 6 num_tokens = len(token_seq) # 让我们知道句子是由多少个标识符组成 7 print("The total tokens are: ", num_tokens) 8 vocab_size = len(vocab) # 句子中包含多少个单词(这里统计的是非重复的,区别于标识符) 9 print("the vocabulary size is: ", vocab_size) 10 one_hot_vector = np.zeros((num_tokens, vocab_size), int) # 创建一个空矩阵来初始化one-hot-vectors 11 for i, word in enumerate(token_seq): 12 one_hot_vector[i, vocab.index(word)] = 1 13 14 print(one_hot_vector)
输出结果为:
The total tokens are: 9 the vocabulary size is: 7 [[1 0 0 0 0 0 0] [0 0 0 0 1 0 0] [0 1 0 0 0 0 0] [0 0 0 1 0 0 0] [1 0 0 0 0 0 0] [0 0 0 0 1 0 0] [0 0 0 0 0 1 0] [0 0 1 0 0 0 0] [0 0 0 0 0 0 1]]
这是比较原始的方法,其实如果用这个直接作为特征输入到机器学习算法模型中的话,是非常不好的,主要有以下几点需要考虑:
- 我们可以看到它是由于我们目前句子较短,向量构成的矩阵是一个9 x 7的大小,但是我们平时处理的文本或者文集是非常庞大的,那么如果是这样子的话,也就意味着我们需要创建一个非常非常大的矩阵来表示左右出现过的单词,而这样子的一个结果是计算效率非常低,且计算资源开销(需要非常大的储存空间)很大;
- 这个样子的向量组是极度稀疏的,因为它只包含了一个非零数字,这样的形式对于计算及寻找特征是没有太大意义的。
一个比较简单的方法解决上述的问题就是我们用Python的字典来标识已有的字符,如下面的代码所示:
1 sent_bow = {} 2 for token in sentence_example.split(): 3 sent_bow[token] = 1 4 print(sorted(sent_bow.items()))
通过运行上述的代码,我们可以得到下列的结果,并且可以看出这样子要比矩阵的形式的要简单的多,对于计算的开销也就没那么大,导致这样子的原因是字典形式只需要储存1就行了
[('I', 1), ('deep', 1), ('language', 1), ('learning,', 1), ('love', 1), ('natural', 1), ('processing.', 1)]
综上,NLP的第一步就是tokenization然后将其转换成计算机可以处理的数字,因为我们主要是让计算机从数字中去寻找文字当中隐含的模式和对应的标记关系,这在NLP中是非常重要的一步,也将影像往后的每一步NLP流程。