天然语言是很是复杂多变的,计算机也不认识我们的语言,那么我们如何让我们的计算机学习我们的语言呢?首先确定得对我们的全部文字进行编码吧,那我们不少小伙伴确定立马就想出了这还不简单嘛,我们的计算机不都是ASCII编码的嘛,咱直接拿来用不就好啦?我只能说too young too simple。我们的计算机只是对我们的“字母”进行ASCII编码,并无对我们的“Word”编码。world应该是我们处理天然语言的最基本的元素,而不是字母。那么世界上有千千万万的Word,我们具体怎么表示呢?就算找出了一种方式来表示每个Word,那么这些Word之间的关系如何来表示,毕竟有些词汇在某种维度上是比较类似的,有些词汇在某些维度上的距离则是比较远的,那么我们如何还找到他们的关系呢?还有一个问题就是我们如何经过我们的text corpus(training dataset)来最终来学习我们的模型呢?等等这些问题都是我们NLP的内容的最基础也是最根本的问题。这一节主要就是解决这些问题的。node
首先我们来看我们如何在机器学习系统中表示我们的词汇,这里呢先说答案,那就是有两种方式,一种就是One-hot encoding, 另一种就是 Featured representation (word embedding)。首先我们来看一下One-hot encoding,它是我们定义的长度固定的vocabulary中赋予每个Word一个index,例如vocabulary的长度是10000,其中“cat”的index是3202,那么我们对于cat的表示方式就是[0,0,0,0,..........1.........,0], 其中只在3202的位置是1,其余的9999个位置都是0. 我们也能够结合下面的图片来更加直观的展现一下Word的one-hot encoding。算法
上面的图片是否是一目了然了什么是Word的one-hot encoding呢?那么问题来了?这种方式的representation有没有什么缺点是避免不了的以致于我们必须得用Word Embedding呢?固然有啦,其实one-hot encoding的主要的缺点主要有2个:第一个你们主要到没有,one-hot encoding表示的方式是很是sparse的,每个Word都必需要有更vocabulary长度同样多的元素element,并且这些element绝大可能是都是0,这是很是浪费资源的一种方式;第二个问题就更加严重了,那就是用one-hot encoding 的方式表示Word的话,那么所用的Word之间都是没有任何联系的,任何两个Word之间的cosin值cos(word1,word2)都是0,这显然是很不符合我们的实际状况的,例如实际中cat和dog之间关系老是要比cat和book之间的关系要更加紧密才对啊,但是在one-hot encoding中cos(cat, dog) == cos(cat, book) ==0,表示它们之间的关系都是同样的,这显然不符合我们的实际状况。网络
Word embedding 应该是我们整个NLP中最最经常使用的一个技术点了,它的核心思想就是每个Word都有一些维度,具体有多少维度我们用户本身定义,可是这些维度能保证类似的词汇在这个多维的空间上的值是类似的,例如我们用5维的空间来表示一个词,那么cat多是[1.0, 1.5, 0.0, 6.4, 0.0], dog多是[0.95, 1.2, 0.11, 5.5, 0.0], book可能的值是[9.5, 0.0, 3.4, 0.3, 6.2]。从前面的几个值我们能够很显然的看出cat和dog在这个五维空间是比较接近的,而他们跟book则在这个五维空间上的距离要远的多,这也更加符合我们的实际状况的认知,那么具体这个五维空间的每个feature是什么,我们不须要知道,在后面的部分我会介绍2红算法来计算embedding的。那么下面就接着上面的例子,我画一个图片更加方便你们的理解app
上面的这个图片就是对我们上面的例子的一个简单的embedding的展现,你们如今没必要纠结上面这些数据是怎么来的,你们只须要知道embedding的展示形式和意思就好了。还有一个你们也不须要被多维空间这个词给吓到了,其实就把它当作是多个features的就行,超过三维的数据我们肉眼是没法经过形象的图像展现的,因此你也不用费神在脑子里面想象多维数据是啥样子了,你就把它当作多个features多个特征就行,至少每个物体都有不少特性的,例如人有身高,体重,肤色,性别等等等不少的特性。而后我们还有一个算法就是T-SNE算法能够将我们多维的数据转化成2维数据给展现出来,你们稍微知道有这么个东西就行,无需深究。机器学习
首先我们介绍第一个计算embedding的算法,那就是经过传统的Neural Network来学习训练我们的embedding和neural network自己的参数的。那么我们来看一下它的具体流程,在正式介绍以前我们展现一下它的流程图,而后在来解释,毕竟这种方式要容易理解的多,我们的大脑处理图片的速度仍是要比文字快的多滴。。。。。函数
上面的图片我不但展现了用DNN的方式来求Embedding的流程而且应用这个embedding来训练model的流程,同时我还配了一个例子来解释这个流程。这个过程当中,大家必定要注意的一点就是这里的Embedding 即做为这个模型的Input data,同时也是做为这个模型的parameters来不断学习更新的。 那么我们如今就来解释这个模型学习的流程吧,首先第一步将我们的语言sentence经过encoding的方式转成sequences, 具体这个过程是如何实现的呢?实际上是想根据我们的语义集(text corpus)生成tokenizer, 而后用这个tokenizer来作encoding的工做,具体的代码我会在最后的应用部分展现。而后第二步我们来到我们的embedding中找到前面sequences相对应的数据,而且将这些数据提出来,这里的embedding我们根据用户自定义它有多少个words多少个维度,而后这个embedding会随机初始化一些数据出来;第三步我们将我们前面从embedding中提取出来的数据进行flatten处理,以便于我们将数据feed到后面的DNN中;最后一步就是我们的DNN训练的过程,在这个训练的过程当中,我们不但会训练DNN本身的paramets,它同时会训练而且更新前面从embedding中提取的数据。那么这个流程就是我们用DNN的方式来计算embedding的方式。上面的I LOVE YOU在这里叫作context words, 用上面的方式来计算而且训练embedding的时候,我们的context words的数量必定得是固定的,不然我们没办法flatten我们的数据feed到同一个neural network(由于同一个neural network的input layer的units是固定的)。同时具体要选择几个context words也是随便用户本身定义的,可是一旦选定了context words,则后面的context words必需要遵循前面的规则,规则必须一致。例如我们便可以选择target的前4个words做为context words, 也能够选择target前面的2个words做为context words,甚至能够选择target的后2个数据做为context words。可是一旦选择了,后面就必须一致。固然了,若是我们data中的context words的长度不够,我们能够经过padded的方式补齐。post
还有一种经常使用的embedding的算法,那就是应用Skip-Gram算法来计算我们的embedding和模型。那么到底它是如何工做的呢?首先我们仍是先看一下这个算法的流程图,而后我们详细解释一下流程哈学习
我们的skip-gram的算法,首先第一步是我们在training data(text corpus)中的的sentence中任意选择一个Word做为context Word;其次在我们初始化的embedding中找到这个context Word对应的值ec,而后将我们的ec 值带入到我们的softmax model中,softmax会计算我们vocabulary全部的词汇的几率,而后选择几率最大的那个Word就是我们根据这个模型选择出来的target Word。上面是我们应用skip-Grams的步骤,实际中的softmax model中的参数还有embedding是我们经过上图中的Loss函数的gradient descent来不断的学习出来的。在这个算法中一样的embedding即做为我们softmax模型的input data同时也做为我们softmax模型的参数parameter。这里面的关系容易混淆啊,上图的softmax模型P(t|c)是我们定义的模型,这个模型里面的参数和embedding是经过上图中的Loss函数不断的gradient descent得来的,我们的training data是在我们的text corpus中的每一行sentence中随机选择出来的一个context Word和在这个context Word先后必定范围内随机选择的一个target Word。这就是这个Skip-Grams算法的核心,固然啦,若是要实践这个算法,里面还有不少的细节须要处理的,可是这里面最核心的思想就是上面提到的步骤了。可是这个算法也有一个致命的弱点,那就是,这个算法须要大量的运算,我们每走一个gradient descent,我们就得计算出我们全部的10000个words的值,实际中我们word可能还远远不止10000个,多是百万都有可能。优化
上面说了半天的理论内容,着实有点无聊,如今我们来看看如何用TensorFlow来用Embedding吧,虽然上面的理论内容很枯燥无聊,但其实仍是很重要的,一来我们偶尔能够装B用,二来若是你不理解上面的理论,我们在应用的时候你连参数都不知道怎么调,你怎么去优化你的模型啊??因此啊,这一块仍是有必要理解一下滴。好了,那我们就来看一个用TensorFlow应用embedding的例子,假设我们的需求是判断你们对一部电影的review是好仍是坏。谷歌自带的dataset中给了我们这些数据,我们能够直接下载下来直接用的。这一个例子其实也是我们的NLP中经常使用的一个场景叫作text sentiment analysis。好吧, 那我们开始吧。编码
第一步:加载我们的数据
import tensorflow as tf import tensorflow_datasets as tfds import numpy as np imdb,info = tfds.load("imdb_reviews",with_info=True,as_supervised=True) train_data = imdb["train"] test_data = imdb["test"]
这一步很简单,就是直接从我们的谷歌的tfds中加载movie reviews的数据。
第二步:将dataset从TensorFlow中的数据对象转成Python list而且分割出features 和 labels。
training_sentences = [] training_labels = [] test_sentences = [] test_labels = [] for s,l in train_data: training_sentences.append(str(s.numpy())) training_labels.append(l.numpy()) for s,l in test_data: test_sentences.append(str(s.numpy())) test_labels.append(l.numpy())
上面的代码是遍历了我们training dataset和test dataset中的数据,而且把它们加载到Python中的list,同时将我们数据中的features和labels分开。这一步算是数据准备阶段, 接下来就是我们来配置我们的tokenizer参数的时候了
第三部:配置tokenizer信息
from tensorflow.keras.preprocessing.text import Tokenizer from tensorflow.keras.preprocessing.sequence import pad_sequences max_length = 120 trunc_type="post" oov_tok = "<OOV>" #initialize a tokenizer tokenizer = Tokenizer(num_words = vocab_size,oov_token = oov_tok) #fit the text to create word index, result a dict of the word:index key-values tokenizer.fit_on_texts(training_sentences) word_index = tokenizer.word_index #create a sequences of the training sentences based on the dictionary above(word-index) training_sequences = tokenizer.texts_to_sequences(training_sentences) #pad the sequences training_padded_sequences = pad_sequences(training_sequences,maxlen=max_length,truncating=trunc_type) #create a sequences of the test sentences based on the dictionary above(word-index) test_sequences = tokenizer.texts_to_sequences(test_sentences) #pad the sequences test_padded_sequences = pad_sequences(test_sequences,maxlen=max_length,truncating=trunc_type)
这一步的主要功能是讲我们上面获得的原始的data(text)转化成我们的计算机熟悉的数字格式,从而每个句子都是一个数字的sequence。其实就是encoding的过程,首先把我们上面的training_sentences中全部出现的Word都赋予一个数字,这一步是经过fit_on_texts()函数来实现的,这一步我们生成了一个dict对象word_index, 这个dict将training_sentences中出现的每个Word(不重复)做为key, 每个Word都对应一个value(不重复)。接下来第二步就是经过texts_to_sequences()这个函数,将我们的全部的sentence都根据上面的word_index来一一对应从text转化成数字的sequence。上面的代码主要就是这两个功能,整个过程,我们称之为encoding。这里有一个小细节,那就是我们在配置tokenizer的时候设置了一个oov_tok参数,这个参数是干什么的呢?我们虽然根据traininig dataset编码了不少的word, 可是实际中总有一些词是我们training dataset中没有出现过的,那么这种状况下,我们就须要一个out-of-value (oov)来表示这些未见过的word啦,因此这里我们就配置了oov_tok = "<oov>", 就是一旦未来遇到了生词,我们一致用“<oov>”这个词表示,而且它在我们的word_index中也有键值对,通常状况下,它个value是1。还有一个细节就是pad_sequences()函数,由于我们的text的长度是不同的,为了保证未来能正确的feed到我们的DNN中,它们的长度必须同样长,这时候我们就得用到pad技术了,他会将我们所用的text同补充到max_length那么长。
第四步:配置神经网络和embedding
这一步就是我们的核心部分了,那就是配置我们的embedding信息和我们的DNN网络
vocab_size = 10000 embedding_dim = 16 #define model structure model = tf.keras.Sequential([ tf.keras.layers.Embedding(vocab_size,embedding_dim,input_length=max_length), tf.keras.layers.Flatten(), tf.keras.layers.Dense(64,activation="relu"), tf.keras.layers.Dense(1,activation="sigmoid") ]) model.compile(loss="binary_crossentropy",optimizer="Adam",metrics=["accuracy"]) model.summary()
上面的embedding我们配置的是一个10000长度,16个维度的embedding,这个embedding里面每个Word都有16个维度。由于我们的是一个binary classification的问题,output layer只有一个node,而且activation是sigmoid。经过summary()函数我们看一下我们的整个的网络结构
上面能够完整的看到我们定义的网络结构和一些参数。
第五步:train model
我们既然已经有了复合格式的数据,也定义了我们的模型,那么接下来我们就用我们的数据来训练我们的模型了
#training the model model.fit( training_padded_sequences, training_labels_final, epochs=10, validation_data=(test_padded_sequences,test_labels_final) )
这一步跟前面章节讲个训练过程如出一辙,也很简单这里就不细讲了。上面五个步骤以后,我们已经训练好了我们模型了,也是我们在遇到text sentiment analysis这一类问题的主要流程,就是从数据加载,encoding,模型定义和训练这几个步骤。