使用python DyNet包
DyNet包计划用于训练和使用神经网络,尤其适合于动态变化的神经网络结构的应用。这是DyNet C++包的python包装器。
在一个神经网络包中通常有两种运作方式:
∙
∙
静态网络,其构建了一个网络并fed不同的输入/输出。大多数神经网络(Neural Network)包以这种方式工作。
∙
∙
动态网路,其为每一个训练样本构建了新的网络(和其他训练样本的网络共享参数)。这种方法使得DyNet是独一无二的,并且这也是它的强大之处。
我们将会描述这两者的模式。
包的基础知识
DyNet的主要部分是计算图(ComputationGraph),其本质是一个神经网络的定义。计算图由与网络的输入和输出相关的表达式(Expression)以及网络的参数(Parameter)组成。参数就是随时间优化的网络中的东西,所有的参数放在参数集合(ParameterCollection)中。训练器(Trainer)(比如SimpleSGDTrainer)负责设置参数值。
我们将不会直接使用计算图,但是其作为一个单独的对象存在于后台。导入DyNet时,就创建了一个新的计算图。我们可以通过调用dy.renew_cg()重置计算图到一个新的状态。
静态网络
一个DyNet程序的生命周期是: 1. 创建一个计算图,并使用参数来填充该计算图。2. 更新计算图,并且创建表征该网络(该网络将会包含定义在参数集合中的参数的表达式)的表达式。3. 用网络的目标函数优化该模型。
作为示例,考虑用于解决异或(xor)问题的模型。该模型有两个输入,要么是0要么是1,以及两个输入的疑惑(xor)后的单个输出。我们将会对此建模成有一个隐层的多层感知机。
令
x=x1,x2
x
=
x
1
,
x
2
是我们的输入,我们将会有8个节点的一个隐层,以及只有一个节点的输出层。隐层的激活函数是
tanh
t
a
n
h
。我们的网络会是:
其中 W W 是一个的矩阵, V V 是一个的矩阵, b b 是一个-维的向量。
我们想让输出要么是0要么是1,因此我们把输出层送进logistic-sigmoid函数, σ(x) σ ( x ) , 其以 −∞ − ∞ 和+ ∞ ∞ 之间的数作为输出,并返回 [0,1] [ 0 , 1 ] 之间的数。
我们将会开始定义该模型和计算图。
#导入dynet
import dynet as dy
#创建参数集合并添加参数
m = dy.ParameterCollection()
w = m.add_parameters((8, 2))
v = m.add_parameters((1, 8))
b = m.add_parameters((8))
#新的计算图。这不是严格要求的,但这是比较好做法
dy.renew_cg()
b.value()
第一块创建了一个参数集合并且填充了参数。第二块创建了计算图。
在计算图中,模型参数将会用作表达式。为了为该网络创建表达式,我们现在使用
V,W
V
,
W
和
b
b
<script type="math/tex" id="MathJax-Element-1315">b</script>。
#规模为2一个输入向量, 也是一个表达式。
x = dy.vecInput(2)
output = dy.logistic(v * (dy.tanh(w * x + b)))
#我们现在可以查询到我们的网络
x.set([0, 0])
output.value()
#我们想要能够定义一个损失,因此我们需要一个输入对应的表达式
y = dy.scalarInput(0)#用来保留正确的答案
loss = dy.binary_log_loss(output, y)
x.set([1, 0])
y.set(0)
print(loss.value())
y.set(1)
print(loss.value())
训练
我们现在想要设置参数权重,以便最小化损失。
鉴于此,我们将会使用trainer对象。构建一个trainer是与一个给定模型参数相关的。
trainer = dy.SimpleSGDTrainer(m)
要使用该训练器,我们需要调用计算图的forward_scalar方法。这将会通过网络运行一个前向传递, 计算所有的中间值直到最后一个(在我们的案例中是损失),然后将该值转换为一个标量。我们网络的最终输出必须是一个单一的标量值。然而,如果我们不关心该值,那么只需使用cg.forward()来代替cg.forward_scalar()。调用计算图的backward方法。这会从最后一个节点运行一个反向传递,计算关于最小化表达式(在我们 的案例中想要最小化的是损失)的梯度。该梯度将会存储在参数集合中,现在我们可以用trainer来关注最优化步。调用trainer_update()会优化关于最新梯度的值。
x.set([1, 0])
y.set(1)
loss_value = loss.value()#通过网路执行前向传递
print("the loss before step is: ", loss_value)
#现在做一个优化步
loss.backward()
trainer.update()
#查看其如何影响loss
loss_value = loss.value(recalculate=True)#recalculate=True意味着不使用预计算的值。
print("the loss after step is: ", loss_value)
该优化步的确使得损失减小。我们现在需要在一个循环中运行该优化步。做完这一步,我们将会创建一个训练集,并在这上面迭代。
对于异或(xor)问题,训练实例是容易创建的。
def create_xor_instances(num_rounds=2000):
questions = []
answers = []
for round in range(num_rounds):
for x1 in 0, 1:
for x2 in 0, 1:
answer = 0 if x1 == x2 else 1
questions.append((x1, x2))
answers.append(answer)
return questions, answers
questions, answers = create_xor_instances()
我们现在要feed每对question/answer进网络,并且尝试最小化该损失。
total_loss = 0
seen_instances = 0
for question, answer in zip(questions, answers):
x.set(question)
y.set(answer)
seen_instances += 1
total_loss += loss.value()
trainer.update()
if seen_instances > 1 and seen_instances % 100 == 0:
print('average loss is', total_loss / seen_instances)
我们的网络已经被训练了,现在来验证的确是学到了xor函数。
x.set([0, 1])
print('0, 1', output.value())
x.set([1, 0])
print('1, 0', output.value())
x.set([0, 0])
print('0, 0', output.value())
x.set([1, 1])
print('1, 1', output.value())
如果我们对参数感到好奇,我们可以查询它们:
w.value()
v.value()
b.value()
总结
这是完整的程序
import dynet as dy
m = dy.ParameterCollection()
w = m.add_parameters((8, 2))
v = m.add_parameters((1, 8))
b = m.add_parameters((8))
dy.renew_cg()
b.value()
x = dy.vecInput(2)
output = dy.logistic(v * (dy.tanh(w * x + b)))
x.set([0, 0])
output.value()
y = dy.scalarInput(0)
loss = dy.binary_log_loss(output, y)
x.set([1, 0])
y.set(0)
print(loss.value())
y.set(1)
print(loss.value())
trainer = dy.SimpleSGDTrainer(m)
x.set([1, 0])
y.set(1)
loss_value = loss.value()
print("the loss before step is: ", loss_value)
loss.backward()
trainer.update()
loss_value = loss.value(recalculate=True)
print("the loss after step is: ", loss_value)
def create_xor_instances(num_rounds=2000):
questions = []
answers = []
for round in range(num_rounds):
for x1 in 0, 1:
for x2 in 0, 1:
answer = 0 if x1 == x2 else 1
questions.append((x1, x2))
answers.append(answer)
return questions, answers
questions, answers = create_xor_instances()
total_loss = 0
seen_instances = 0
for question, answer in zip(questions, answers):
x.set(question)
y.set(answer)
seen_instances += 1
total_loss += loss.value()
trainer.update()
if seen_instances > 1 and seen_instances % 100 == 0:
print('average loss is', total_loss / seen_instances)
动态网络
动态网络和静态的网络非常相似,但不是创建网络一次,而是在每个训练示例中调用“set”来改变输入,我们职位每个训练示例创建一个新网络。
我们在下面给出一个例子。虽然在xor示例中这个值可能不明确,但动态方法对于结构固定的网络非常方便,例如循环网络或递归网络。
import dynet as dy
#如此前一样,创建训练实例
def create_xor_instances(num_rounds=2000):
questions = []
answers = []
for round in range(num_rounds):
for x1 in 0,1:
for x2 in 0,1:
answer = 0 if x1==x2 else 1
questions.append((x1,x2))
answers.append(answer)
return questions, answers
questions, answers = create_xor_instances()
# 创建一个给定输入或输出的xor问题网络
def create_xor_network(W, V, b, inputs, expected_answer):
dy.renew_cg() # new computation graph
x = dy.vecInput(len(inputs))
x.set(inputs)
y = dy.scalarInput(expected_answer)
output = dy.logistic(V*(dy.tanh((W*x)+b)))
loss = dy.binary_log_loss(output, y)
return loss
m2 = dy.ParameterCollection()
W = m2.add_parameters((8,2))
V = m2.add_parameters((1,8))
b = m2.add_parameters((8))
trainer = dy.SimpleSGDTrainer(m2)
seen_instances = 0
total_loss = 0
for question, answer in zip(questions, answers):
loss = create_xor_network(W, V, b, question, answer)
seen_instances += 1
total_loss += loss.value()
loss.backward()
trainer.update()
if (seen_instances > 1 and seen_instances % 100 == 0):
print("average loss is:",total_loss / seen_instances)