一个能帮助神经网络更好地模拟数值信息的新框架

电子说

1.3w人已加入

描述

编者按:2个月前,DeepMind发表了一篇名为“ 神经算术逻辑单元(NALU)”的新论文,提出了一个能帮助神经网络更好地模拟数值信息的新框架。这是一篇有趣的论文,解决的问题也很实际,所以今天论智想推荐一篇有关这个框架的文章,它也是被TensorFlow官博力荐的佳作。比起复杂的论文解读,它更简洁直观,也易于理解。

现如今,尽管深度学习已经在许多任务中取得了令人惊艳的成果,诸多AI产品也逐渐在医疗等领域发挥越来越重要的作用,但如何教导神经网络还是它的一个重要问题,说出来可能有人不信,神经网络在简单算术任务上还会出现问题。

在一个实验中,DeepMind的研究人员曾训练了一个精度接近完美的模型,它能从数据中找出范围在-5到5之间的数字,但当输入从未见过的新数据后,模型就无法概括了。

论文针对上述问题提出了两种方法,但这里我们不会搬运原文的详细内容,相反地,下文将简要介绍NAC的工作原理,以及它如何处理加减乘除等操作,相应代码也会在文章中列出,读者可以从中获得更直观的了解。

第一个神经网络(NAC)

论文介绍的第一个神经网络是神经累积器(简称NAC),它能对输入执行线性变换,而用于变换的矩阵是tanh(What)和sigmoid(Mhat)的元素乘积。简而言之,input(x)后,模型输入会乘以变换矩阵W,并产生输出a。

NAC的Python实现:

import tensorflow as tf

# NAC

W_hat = tf.Variable(tf.truncated_normal(shape, stddev=0.02))

M_hat = tf.Variable(tf.truncated_normal(shape, stddev=0.02))

W = tf.tanh(W_hat) * tf.sigmoid(M_hat)

# 前向传播

a = tf.matmul(in_dim, W)

第二个神经网络(NALU)

神经算术逻辑单元(NALU)由两个NAC构成,其中,第一个NAC g是sigmoid(Gx),第二个NAC在一个等于exp(W(log(|x| + epsilon)))的对数空间m中运行。

NALU的Python实现:

import tensorflow as tf

# NALU

G = tf.Variable(tf.truncated_normal(shape, stddev=0.02))

m = tf.exp(tf.matmul(tf.log(tf.abs(in_dim)+ epsilon), W))

g = tf.sigmoid(tf.matmul(in_dim, G))

y = g * a +(1- g)* m

通过加法理解NAC

现在我们来进行测试。首先,把NAC转成函数:

# NAC

def NAC(in_dim, out_dim):

in_features = in_dim.shape[1]

# 定义W_hat和M_hat

W_hat = tf.get_variable(name = 'W_hat', initializer=tf.initializers.random_uniform(minval=-2, maxval=2),shape=[in_features, out_dim],  trainable=True)

M_hat = tf.get_variable(name = 'M_hat', initializer=tf.initializers.random_uniform(minval=-2, maxval=2), shape=[in_features, out_dim], trainable=True)

W = tf.nn.tanh(W_hat) * tf.nn.sigmoid(M_hat)

a = tf.matmul(in_dim, W)

return a, W

其次,创建一些数据,把它们分成训练集和测试集。NumPy有一个较numpy.arrange的API,很适合用来创建数据集:

# 生成一系列输入数字X1和X2用于训练

x1 = np.arange(0,10000,5, dtype=np.float32)

x2 = np.arange(5,10005,5, dtype=np.float32)

y_train = x1 + x2

x_train = np.column_stack((x1,x2))

print(x_train.shape)

print(y_train.shape)

# 生成一系列输入数字X1和X2进行测试

x1 = np.arange(1000,2000,8, dtype=np.float32)

x2 = np.arange(1000,1500,4, dtype= np.float32)

x_test = np.column_stack((x1,x2))

y_test = x1 + x2

print()

print(x_test.shape)

print(y_test.shape)

接着,用这些准备好的东西训练模型。我们先定义占位符X和Y以在运行时提供数据,用tf.reduce_sum()计算损失,模型包含两个超参数:学习率alpha和训练几个epochs。在训练开始前,我们还要定义一个优化器,方便用tf.train.AdamOptimizer()降低损失。

# 定义占位符以在运行时提供输入

X = tf.placeholder(dtype=tf.float32, shape =[None , 2])    # Number of samples x Number of features (number of inputs to be added)

Y = tf.placeholder(dtype=tf.float32, shape=[None,])

#定义网络

#这里网络只包含一个NAC(用于测试)

y_pred, W = NAC(in_dim=X, out_dim=1)

y_pred = tf.squeeze(y_pred)             # Remove extra dimensions if any

# 均方误差 (MSE)

loss = tf.reduce_mean( (y_pred - Y) **2)

# 训练参数

alpha = 0.05    # learning rate

epochs = 22000

optimize = tf.train.AdamOptimizer(learning_rate=alpha).minimize(loss)

with tf.Session() as sess:

#init = tf.global_variables_initializer()

cost_history = []

sess.run(tf.global_variables_initializer())

# 训练前损失

print("Pre training MSE: ", sess.run (loss, feed_dict={X: x_test, Y:y_test}))

print()

for i in range(epochs):

_, cost = sess.run([optimize, loss ], feed_dict={X:x_train, Y: y_train})

print("epoch: {}, MSE: {}".format( i,cost) )

cost_history.append(cost)

# 列出每次迭代的均方误差

plt.plot(np.arange(epochs),np.log(cost_history))  # Plot MSE on log scale

plt.xlabel("Epoch")

plt.ylabel("MSE")

plt.show()

print()

print(W.eval())

print()

# 训练后损失

print("Post training MSE: ", sess.run(loss, feed_dict={X: x_test, Y: y_test}))

print("Actual sum: ", y_test[0:10])

print()

print("Predicted sum: ", sess.run(y_pred[0:10], feed_dict={X: x_test, Y: y_test}))

训练完成后,我们可以得到这样一幅图损失曲线图:

Actual sum:  [2000.2012.2024.2036.2048.2060.2072.2084.2096.2108.]

Predicted sum:  [1999.90212011.90152023.90092035.90042047.89972059.89922071.8984

2083.8982095.89752107.8967]

如输出所示,NAC可以处理诸如加减法的操作,但它还做不到处理乘法和除法。为了解决这个问题,我们就要用到NALU。

通过乘法理解NALU

在上文基础上,首先我们再添加一个NAC,组成NALU:

如果说NAC只是对输入做线性变化,那么NALU就是把两个具有权重的NAC组合在一起,用来执行加减(较小的紫色单元)和乘除(较大的紫色单元),计算由门(橙色单元)控制。

# NALU

def NALU(in_dim, out_dim):

shape = (int(in_dim.shape[-1]), out_dim)

epsilon = 1e-7

# NAC

W_hat = tf.Variable(tf.truncated_normal(shape, stddev=0.02))

M_hat = tf.Variable(tf.truncated_normal(shape, stddev=0.02))

G = tf.Variable(tf.truncated_normal(shape, stddev=0.02))

W = tf.tanh(W_hat) * tf.sigmoid(M_hat)

# 前向传播

a = tf.matmul(in_dim, W)

# NALU  

m = tf.exp(tf.matmul(tf.log(tf.abs(in_dim) + epsilon), W))

g = tf.sigmoid(tf.matmul(in_dim, G))

y = g * a + (1 - g) * m

return y

这里我们再创建一些数据,但和上次相比,这次要做一些改动:在第8行和第20行,我们把运算符从加改成了乘。

# 通过学习乘法来测试网络

# 生成一系列输入数字X1和X2用于训练

x1 = np.arange(0,10000,5, dtype=np.float32)

x2 = np.arange(5,10005,5, dtype=np.float32)

y_train = x1 * x2

x_train = np.column_stack((x1,x2))

print(x_train.shape)

print(y_train.shape)

# 生成一系列输入数字X1和X2进行测试

x1 = np.arange(1000,2000,8, dtype=np.float32)

x2 = np.arange(1000,1500,4, dtype= np.float32)

x_test = np.column_stack((x1,x2))

y_test = x1 * x2

print()

print(x_test.shape)

print(y_test.shape)

之后是训练模型,需要注意的是,这里我们定义的还是NAC,而不是NALU:

# 定义占位符以在运行时提供值

X = tf.placeholder(dtype=tf.float32, shape =[None , 2])    # Number of samples x Number of features (number of inputs to be added)

Y = tf.placeholder(dtype=tf.float32, shape=[None,])

# 定义网络

# 这里网络只包含一个NAC(用于测试)

y_pred = NALU(in_dim=X, out_dim=1)

y_pred = tf.squeeze(y_pred)      # Remove extra dimensions if any

# 均方误差 (MSE)

loss = tf.reduce_mean( (y_pred - Y) **2)

# 训练参数

alpha = 0.05    # 学习率

epochs = 22000

optimize = tf.train.AdamOptimizer(learning_rate=alpha).minimize(loss)

with tf.Session() as sess:

#init = tf.global_variables_initializer()

cost_history = []

sess.run(tf.global_variables_initializer())

# 训练前损失

print("Pre training MSE: ", sess.run (loss, feed_dict={X: x_test, Y: y_test}))

print()

for i in range(epochs):

_, cost = sess.run([optimize, loss ], feed_dict={X: x_train, Y: y_train})

print("epoch: {}, MSE: {}".format( i,cost) )

cost_history.append(cost)

# 列出每次迭代的损失

plt.plot(np.arange(epochs),np.log(cost_history))  # Plot MSE on log scale

plt.xlabel("Epoch")

plt.ylabel("MSE")

plt.show()

# 训练后损失

print("Post training MSE: ", sess.run(loss, feed_dict={X: x_test, Y: y_test}))

print("Actual product: ", y_test[0:10])

print()

print("Predicted product: ", sess.run(y_pred[0:10], feed_dict={X: x_test, Y: y_test}))

Actual product:  [1000000.1012032.1024128.1036288.1048512.1060800.1073152.1085568.

1098048.1110592.]

Predicted product:  [1000000.21012032.   1024127.561036288.61048512.061060800.8

1073151.61085567.61098047.61110592.8 ]

如果想获取在TensorFlow中实现NALU的完整代码,可以去这个github:github.com/ahylton19/simpleNALU-tf

小结

以上只是NALU在加减乘除任务上具体表现,在论文中,研究人员还测试了平方运算和开根,NALU的表现都优于传统框架。简而言之,DeepMind的这个简单而实用的技术让神经网络掌握了数值推算,它类似传统处理器中的算术逻辑单元,能让网络真正“学会”加减乘除和基于加减乘除的近似估计,更好地把经验外推到其他数值任务上,而不再受训练数据限制。

通过这篇文章,我们希望现在你已经了解了这篇轰动学界的论文到底说了什么,以及它对深度学习的贡献和影响。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分