项目
Recurrent Neural Networks with Word Embeddings
教程地址:http://deeplearning.net/tutorial/rnnslu.html
Task
The Slot-Filling (Spoken Language Understanding)给句子中每个word分配标签,是一个分类问题。
Dataset
数据集是DARPA的一个小型数据集:ATIS (Airline Travel Information System),使用Inside Outside Beginning (IOB)表示
数据集中训练集句子4978个,word 56590个;测试集句子893,word 9198个;平均句长 15;The number of classes (different slots) is 128 including the O label (NULL).
注:B- prefix 实体的开始, I- prefix 实体内部,O tag 不属于任何实体
评价指标
Precision,Recall,F1 score
教程中使用conlleval PERL script(是一个计算上述指标的脚本代码)来评价性能
词向量
这里使用的词向量是context window word embeddings,即定义一个窗口大小,把句子中的每个word及其前后的word index提取出来,再把把 index 转换成 embeddings作为对应每个 word 的实数向量。
不同于传统的FNNs(Feed-forward Neural Networks,前向反馈神经网络),RNNs引入了定向循环,能够处理那些输入之间前后关联的问题。教程中使用的是(Elman) recurrent neural network (E-RNN),把当前时刻(t)的输入以及前一时刻( t-1)的隐藏层状态作为输入。
参数
E-RNN中需要学习的参数如下:
the word embeddings
the initial hidden state (real-value vector)
two matrices for the linear projection of the input t and the previous hidden layer state t-1(神经网络中的权重W)
(optional) bias. (偏置项,此处不用)
softmax classification layer on top
超参数如下:
dimension of the word embedding(de,50)
size of the vocabulary
number of hidden units(隐藏单元数量)
number of classes(类别数)
random seed + way to initialize the model(初始化模型种子和方式)
更新
使用随机梯度下降,一个句子为一个minibatch,每一个句子更新一次参数。每次更新后,都要对 word embeddings 归一化。
停止条件
Early-stoppong,按给定数量的epochs训练,每次epoch后都在验证集上测试 F1 score,保留最佳模型。
超参选择
使用 KISS random search (没太看懂,是暴搜策略吗?)
教程附的超参如下:
learning rate : uniform([0.05,0.01])
window size : random value from {3,…,19}
number of hidden units : random value from {100,200}
embedding dimension : random value from {50,100}
主要代码及注释
#coding=utf-8
import theano
import numpy
import os
from theano import tensor as T
from collections import OrderedDict
class RNNSLU(object):
''' elman neural net model '''
def __init__(self, nh, nc, ne, de, cs):
''' nh :: dimension of the hidden layer nc :: number of classes ne :: number of word embeddings in the vocabulary de :: dimension of the word embeddings cs :: word window context size '''
# parameters of the model
self.emb = theano.shared(0.2 * numpy.random.uniform(-1.0, 1.0,\
(ne+1, de)).astype(theano.config.floatX)) # add one for PADDING at the end 首尾处的单词窗口记为-1
self.Wx = theano.shared(0.2 * numpy.random.uniform(-1.0, 1.0,\
(de * cs, nh)).astype(theano.config.floatX)) #连接nh隐藏单元与de个输入词(每个维度cs)
self.Wh = theano.shared(0.2 * numpy.random.uniform(-1.0, 1.0,\
(nh, nh)).astype(theano.config.floatX)) #隐藏层连接的也是隐藏层,nh*nh
self.W = theano.shared(0.2 * numpy.random.uniform(-1.0, 1.0,\
(nh, nc)).astype(theano.config.floatX)) #输出层 nh个隐藏层连接nc个输出单元
self.bh = theano.shared(numpy.zeros(nh, dtype=theano.config.floatX))#每个隐藏单元一个bia 共nh个
self.b = theano.shared(numpy.zeros(nc, dtype=theano.config.floatX))#每个输出单元一个bia 共nc类即nc个输出单元
self.h0 = theano.shared(numpy.zeros(nh, dtype=theano.config.floatX))#定义一个t0时刻的输出,每个隐藏单元一个
# bundle
self.params = [self.emb, self.Wx, self.Wh, self.W, self.bh, self.b, self.h0 ]
self.names = ['embeddings', 'Wx', 'Wh', 'W', 'bh', 'b', 'h0']
idxs = T.imatrix() # as many columns as context window size/lines as words in the sentence
x = self.emb[idxs].reshape((idxs.shape[0], de*cs)) #共n个输入,每个输入de个词(每个维度cs)
y = T.iscalar('y') # label
def recurrence(x_t, h_tm1):
h_t = T.nnet.sigmoid(T.dot(x_t, self.Wx) + T.dot(h_tm1, self.Wh) + self.bh)#隐藏层t时刻输出=f(输入*Wx+h_t-1*Wh+b)
s_t = T.nnet.softmax(T.dot(h_t, self.W) + self.b)#输出层t时刻的输出
return [h_t, s_t]
#将某个函数作用于输入序列上,得到每一步输出的结果。
#和Reduction和map不同之处在于,scan在计算的时候,可以访问以前n步的输出结果,比较适合RNN
[h, s], = theano.scan(fn=recurrence, \
sequences=x, outputs_info=[self.h0, None], \
n_steps=x.shape[0])
p_y_given_x_lastword = s[-1,0,:]
p_y_given_x_sentence = s[:,0,:]
y_pred = T.argmax(p_y_given_x_sentence, axis=1)
#learning rate
lr = T.scalar('lr')
#cost and gradients
nll = -T.log(p_y_given_x_lastword)[y]
gradients = T.grad(nll, self.params)
updates = OrderedDict((p, p-lr*g) for p, g in zip(self.params, gradients))
# theano functions
self.classify = theano.function(inputs=[idxs], outputs=y_pred)
#训练
self.train = theano.function( inputs = [idxs, y, lr],
outputs = nll,
updates = updates )
#归一化
self.normalize = theano.function( inputs = [],
updates = {self.emb:\
self.emb/T.sqrt((self.emb**2).sum(axis=1)).dimshuffle(0,'x')})
#保存参数
def save(self, folder):
for param, name in zip(self.params, self.names):
numpy.save(os.path.join(folder, name + '.npy'), param.get_value())