我们之所以使用卷积运算,是因为图像数据在空间上具有关联性,当然数据不仅在空间上会具备一定的关联性,在时间上亦是如此。最典型的旧日人类的语言。
我们一般把“词”作为自然语音处理当中的基本单位。我们可能会有一些简单的给词编码的方式,比如依据词典中的序号,对整个词典做归一化处理后以数值表示一个词。但这样的缺点便是:往往会出现一些读音极为相近,但是意义差距甚远的词语,比如开心和开除,但是归一化后两者的数值却可能十分相近。此外,或许可以尝试One-Hot编码,但这样的缺点便是:通过one-hot编码后每个词都完全不一样,又丧失了某种联系性。此外,one-hot编码会让输入数据变得十分庞大。参考之前我们用来描述一个“豆豆”的特征而使用的输入“特征向量”,我们是否也可以基于这种特征向量的思路来描述一个词。
比如上图中所示的,同意或近义词相距很近,而一些词义相反或是完全不相干的词语则相距很远。基于这样的思想,如果特征提取地恰当,那么在这样的多维描述下,猫的词向量减去老鼠的词向量,结果与警察的词向量减去小偷的词向量的结果十分相近。这就可以说明在某种意义上两对的关系具有相似性。这样的技术,在NLP中称之为词嵌入。即把词嵌入到一个特征向量空间中。
例如,一个词典中有四个词,并拥有10个特征维度,就可以用一个4×10的词向量矩阵来描述这一“词典”。如果要组成一句话,仅将每个词的one-hot编码作为列向量,按顺序依次组成一个矩阵,与词向量矩阵点乘,就可以达到这句话的矩阵表达。
如果我们将这两个矩阵相乘的结果矩阵铺开,并输入一个全连接神经网络,在上图中的嵌入层线性计算部分,就可以通过反向传播来更新和修改词向量矩阵,具体比如使用卷积等方法。经过不断地训练和学习,最终得到最合适的词向量描述。当然,越复杂的维度分类下,每一个维度对应的意义也会变得越来越抽象,也越来越难以用实际意义去具体描述。常见的词向量训练算法为:word2vec和Glove
import shopping_data
from keras_preprocessing import sequence
# 用来将传入神经网络的向量进行数据对齐
from keras.models import Sequential
from keras.layers import Dense,Embedding
from keras.layers import Flatten
x_train,y_train,x_test,y_test = shopping_data.load_data()
print('The X shape',x_train.shape)
print(x_train[10])
vocalen,word_index = shopping_data.createWordIndex(x_train,x_test)
#vocalen:词典的词汇量,word_index:训练集和测试集全部语料的词典
print(word_index)
x_train_index = shopping_data.word2Index(x_train,word_index)
x_test_index = shopping_data.word2Index(x_test,word_index)
maxlen = 25
x_train_index = sequence.pad_sequences(x_train_index,maxlen = maxlen)
x_test_index = sequence.pad_sequences(x_test_index,maxlen = maxlen)
model = Sequential()
model.add( Embedding(trainable = True,input_dim = vocalen,output_dim = 300,input_length=maxlen) )
# trainable:是否让这一层可以被训练,即决定词嵌入矩阵是否会被更新
# 输入维度,输出维度,序列长度
model.add(Flatten())
model.add(Dense(256,activation = 'relu'))
model.add(Dense(256,activation = 'relu'))
model.add(Dense(256,activation = 'relu'))
model.add(Dense(1,activation = 'sigmoid'))
model.compile(loss = 'binary_crossentropy',
optimizer = 'adam', #使用动量的自适应优化器
metrics = ['accuracy'])
model.fit(x_train_index,y_train,
batch_size=512,
epochs=200)
score,acc = model.evaluate(x_test_index,y_test)
print(score)
print(acc)
现在,我们来改造一下传统的全连接神经网络。如果将词向量矩阵中的四个记为x1,x2,x3,x4,将x1输入后经第一层神经元的输出记为a1。那么,在第二次输入x2时,将a1与x2一起作为新的输入。
此后,又将这次的输出a2与x3一起作为第三次运行的输入。这样,每一个词对最后预测输出的影响就在每一次的保存并和下一步的数据的共同作用中,持续到了最后。把这样改造的神经网络就称为循环神经网络RNN(Rerrent Neural Network)
首先看标准的RNN结构的细节的数据流转和计算的结构图:
再来看LSTM的主要数据流转与运算的过程:
LSTM定义了一个细胞状态参数,即上图中的Ct与Ct-1,细胞状态将使得神经元具有记忆与遗忘功能
如上图所示,将输入位置安置一个遗忘门,也即一个Sigmoid激活函数,这个函数的输入变量即为上一次的输出at-1与这一次的输入xt,并将结果与上一次的细胞状态相乘。显然,如果Sigmoid输出为0,则细胞状态就被更新为0,也即永久遗忘。反之,如果Sigmoid输出为1,则是选择继续记忆,为0~1之间,则可以理解为部分遗忘。
同样的道理,我们再补充一个更新门,用来决定是否需要更新这次的细胞状态值,这样,网络就会选择重要的词汇更新细胞状态。
而除了记忆与遗忘之外,最后的输出还有一个输出门,这样就可以在遇到重要词汇时产生强输出,在不重要时产生弱输出,下图就是最终完整的LSTM网络层结构。
当然,后人对LSTM又做了简化与改进,但仍然保留了相近的效果,比如常用的GRU网络结构。
具体可以查阅博客Understanding LSTM Networks
代码中用到的预训练词向量文件下载
核心代码:
import shopping_data
from keras_preprocessing import sequence
# 用来将传入神经网络的向量进行数据对齐
from keras.models import Sequential
from keras.layers import Dense,Embedding
from keras.layers import Flatten
from keras.layers import LSTM
import numpy as np
import chinese_vec
x_train,y_train,x_test,y_test = shopping_data.load_data()
print('The X shape',x_train.shape)
print(x_train[10])
vocalen,word_index = shopping_data.createWordIndex(x_train,x_test)
#vocalen:词典的词汇量,word_index:训练集和测试集全部语料的词典
print(word_index)
x_train_index = shopping_data.word2Index(x_train,word_index)
x_test_index = shopping_data.word2Index(x_test,word_index)
maxlen = 25
x_train_index = sequence.pad_sequences(x_train_index,maxlen = maxlen)
x_test_index = sequence.pad_sequences(x_test_index,maxlen = maxlen)
# 自行构造词嵌入矩阵
word_vecs = chinese_vec.load_word_vecs()
embedding_matrix = np.zeros((vocalen,300))
for word,i in word_index.items():
embedding_vector = word_vecs.get(word)
if embedding_vector is not None:
embedding_matrix[i] = embedding_vector
model = Sequential()
model.add( Embedding(trainable = False,weights = [embedding_matrix],input_dim = vocalen,output_dim = 300,input_length=maxlen) )
# trainable:是否让这一层可以被训练,即决定词嵌入矩阵是否会被更新
# 输入维度,输出维度,序列长度
model.add(LSTM(128,return_sequences=True)) #输出数据维度
model.add(LSTM(128))
model.add(Dense(1,activation = 'sigmoid'))
model.compile(loss = 'binary_crossentropy',
optimizer = 'adam', #使用动量的自适应优化器
metrics = ['accuracy'])
model.fit(x_train_index,y_train,
batch_size=512,
epochs=200)
score,acc = model.evaluate(x_test_index,y_test)
print(score)
print(acc)
代码中的辅助文件:chinese_vec.py
import os
import numpy as np
def load_word_vecs():
embeddings_index = {}
f = open(os.path.dirname(os.path.abspath(__file__))+'/sgns.target.word-word.dynwin5.thr10.neg5.dim300.iter5',encoding='utf8')
f.readline()#escape first line
for line in f:
values = line.split()
word = values[0]
coefs = np.asarray(values[1:], dtype='float32')
embeddings_index[word] = coefs
f.close()
print('Found %s word vectors.' % len(embeddings_index))
return embeddings_index