神经网络也是可微分的参数化函数,常用的训练方法就是基于梯度的优化方法。


前向计算就是计算图中每个节点的输出,
假设 f i f_i fi为结点 i i i的计算函数, π ( i ) \pi\left(i\right) π(i)为结点 i i i的父节点, π − 1 ( i ) \pi^{-1}\left(i\right) π−1(i)为结点 i i i的子节点, v i ( i ) v_i\left(i\right) vi(i)为结点 i i i的输出,则前向计算可以表示为:
for i = 1 i=1 i=1 to N N N do
令 a 1 , . . . , a m = π − 1 ( i ) a_1,...,a_m=\pi^{-1}\left(i\right) a1,...,am=π−1(i)
v ( i ) ← f i ( v ( a 1 ) , ⋯   , v ( a m ) ) v\left( i \right) \leftarrow {f_i}\left( {v\left( {{a_1}} \right), \cdots ,v\left( {{a_m}} \right)} \right) v(i)←fi(v(a1),⋯,v(am))
反向传播过程开始于损失结点 N N N,向前传播,指定 d ( i ) d\left(i\right) d(i)为 ∂ N ∂ i \frac{{\partial N}}{{\partial i}} ∂i∂N,可以表示为:
d ( N ) ← 1 d\left( N \right) \leftarrow 1 d(N)←1 ( ∂ N ∂ N = 1 ) (\frac{{\partial N}}{{\partial N}} = 1) (∂N∂N=1)
for i = N − 1 i=N-1 i=N−1 to 1 1 1 do
d ( i ) ← ∑ j ∈ π ( i ) d ( j ) ⋅ ∂ f j ∂ i d\left( i \right) \leftarrow \sum\nolimits_{j \in \pi \left( i \right)} {d\left( j \right) \cdot \frac{{\partial {f_j}}}{{\partial i}}} d(i)←∑j∈π(i)d(j)⋅∂i∂fj ( ∂ N ∂ i = ∑ j ∈ π ( i ) ∂ N ∂ j ∂ j ∂ i ) ( {\frac{{\partial N}}{{\partial i}} = \sum\limits_{j \in \pi \left( i \right)} {\frac{{\partial N}}{{\partial j}}\frac{{\partial j}}{{\partial i}}} }) (∂i∂N=j∈π(i)∑∂j∂N∂i∂j)
import dynet as dy
#模型初始化
model=dy.Model()
mW1=model.add_parameters((20,150)) #向模型添加权重参数
mb1=model.add_parameters(20)
mW2=model.add_parameters((17,20))
mb2=model.add_parameters(17)
lookup=model.add_lookup_parameters((100,50)) #向模型添加查找参数
trainer=dy.SimpleSGDTrainer(model) #定义训练器
def get_index(x):
pass
#将词映射为索引值
#构建图结构并执行
#更新模型参数
#只显示一个数据点,实践中应该运行一个数据填充循环
#建立计算图
dy.renew_cg() #创建一个新图
#将模型参数创建
W1=dy.parameter(mW1)
b1=dy.parameter(mb1)
W2=dy.parameter(mW2)
b2=dy.parameter(mb2)
#生成embeddings层
vthe=dy.lookup[get_index("the")]
vblack=dy.lookup[get_index("black")]
vdog=dy.lookup[get_index("dog")]
#将叶子结点连接成完整的图
x=dy.concatenate([vthe,vblack,vdog])
output=dy.softmax(W2*(dy.tanh(W1*x+b1))+b2)
loss=-dy.log(dy.pick(output,5))
loss_value=loss.forward()
loss.backward() #计算参数并存储
trainer.update() #通过梯度进行参数更新
#TensorFlow
import tensorflow as tf
W1=tf.get_variable("W1",[20,150])
b1=tf.get_variable("b1",[20])
W2=tf.get_variable("W2",[17,20])
b2=tf.get_variable("b2",[17])
def get_index(x):
pass
p1=tf.placeholder(tf.int32,[])
p2=tf.placeholder(tf.int32,[])
p3=tf.placeholder(tf.int32,[])
target=tf.placeholder(tf.int32,[])
v_w1=tf.nn.embedding_lookup(lookup,p1)
v_w2=tf.nn.embedding_lookup(lookup,p2)
v_w3=tf.nn.embedding_lookup(lookup,p3)
x=tf.concat([v_w1,v_w2.v_w3],0)
output=tf.nn.softmax(tf.einsum("ij,j->i",W2,tf.tanh(tf.einsum("ij,j->i",W1,x)+b1))+b2)
loss=-tf.log(output[target])
trainer=tf.train.GradientDescentOptimizer(0.1).minimize(losss)
#完成图的初始化工作,编译并赋予具体数据
#只显示一个数据点,实践中我们将使用一个数据输入环
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
feed_dict={
p1:get_index("the"),
p2:get_index("black"),
p3:get_index("dog"),
target:5
}
loss_value=sess.run(loss,feed_dict)
sess.run(trainer,feed_dict)
具有计算图概念的神经网络训练
for iteration=1 to T do
for 数据集中训练样本(x_i,y_i) do
loss_node<-build_computation_graph(x_i,y_i,parameters) #用户自定义函数,给定输入、输出和网络结构可自动生成计算图
loss_node.forward()
gradients<-loss_node().backward()
parameters<-update_parameters(parameters,gradients) #优化器特定更新规则
return parameters
虽然SGD算法效果很好,但是收敛速度慢,在训练大型网络时Adam算法非常有效。
网络读入训练样本的顺序是很重要的。
实验应该从 [ 0 , 1 ] \left[ {0,1} \right] [0,1]内尝试初始学习率,观察网络 l o s s loss loss值,一旦 l o s s loss loss值停止改进则降低学习率。
建议使用 η t = η 0 ( 1 + η 0 λ t ) − 1 {\eta _t} = {\eta _0}{\left( {1 + {\eta _0}\lambda t} \right)^{ - 1}} ηt=η0(1+η0λt)−1作为学习率的表达式, η 0 \eta_0 η0为初始学习率, η t \eta_t ηt为第 t t t个训练样例的学习率, λ \lambda λ为超参。
在每训练 1 1 1个训练样例( m i n i b a t c h = 1 minibatch=1 minibatch=1)或 k k k个训练样例( m i n i b a t c h = k minibatch=k minibatch=k)后更新参数。
大的 m i n i b a t c h minibatch minibatch对训练是有益的。
《基于深度学习的自然语言处理》