作者:chen_h
微信号 & QQ:862251340
微信公众号:coderpai
这篇教程是翻译Morgan写的TensorFlow教程,作者已经授权翻译,这是原文。
TensorFlow学习系列(一):初识TensorFlow
TensorFlow学习系列(二):形状和动态维度
TensorFlow学习系列(三):保存/恢复和混合多个模型
TensorFlow学习系列(四):利用神经网络实现泛逼近器(universal approximator)
TensorFlow学习系列(五):如何使用队列和多线程优化输入管道
今天,我们先不用TensorFlow去实现具体的东西,而是去学习一点新的东西。
你听说过泛逼近定理 “Universal approximation theorem” 吗?
简单地,这个定理可以描述成如下三步(没有所有细节):
听起来是不是很简单。
那让我们直接利用 TensorFlow 实现一个简单的例子吧,比如将一个函数从 R 维度映射到 R 维度。简单地,我们构造一个只拥有一层隐藏层的神经网络,并且在输出层不加偏置项,具体代码如下:
import tensorflow as tf
def univAprox(x, hidden_dim=50):
# The simple case is f: R -> R
input_dim = 1
output_dim = 1
with tf.variable_scope('UniversalApproximator'):
ua_w = tf.get_variable('ua_w', shape=[input_dim, hidden_dim], initializer=tf.random_normal_initializer(stddev=.1))
ua_b = tf.get_variable('ua_b', shape=[hidden_dim], initializer=tf.constant_initializer(0.))
z = tf.matmul(x, ua_w) + ua_b
a = tf.nn.relu(z) # we now have our hidden_dim activations
ua_v = tf.get_variable('ua_v', shape=[hidden_dim, output_dim], initializer=tf.random_normal_initializer(stddev=.1))
z = tf.matmul(a, ua_v)
return z
一些注意事项:
接下来,让我们编写一个很简单的脚本来评估这个函数:
x = tf.placeholder(tf.float32, shape=[None, 1], name="x")
y = univAprox(x)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
y_res = sess.run(y, feed_dict={
x: [[0], [1], [2]] # We are batching 3 value at the same time
})
print(y_res) # -> [[ 0. ] [ 0.0373688 ] [ 0.07473759]]
# Those values will be different for you, since we initialize our variables randomly
至此,我们已经完成了泛逼近器(UA)的设计和开发。
接下来我们需要去训练这个泛逼近器,去逼近我们给定的闭区间内的任何函数。
让我们从正弦函数(the sine function)开始吧,我个人不是很相信神经网络可以很好的近似一个函数。
提示:如果你和我一样,想知道这种近似是怎么做到的,我可以给你一个数学提示:
* 在闭区间上的任何连续函数都可以通过分段常数函数 piecewise constant function 来近似。
* 你可以手动建立一个神经网络,通过添加必要的神经元来构造这个分段函数。
接下来,我们可以构造一个脚本来做三件事:
我将直接在这里发布整个脚本文件,包含说明注释。
我相信发布一个完整的代码对你的学习是非常有利的(不要害怕文件太长,里面包含了很多的注释和空行)。
# First let's import all the tools needed
# Some basic tools
import time, os, argparse, io
dir = os.path.dirname(os.path.realpath(__file__))
# Tensorflow and numpy!
import tensorflow as tf
import numpy as np
# Matplotlib, so we can graph our functions
# The Agg backend is here for those running this on a server without X sessions
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
# Our UA function
def univAprox(x, hidden_dim=50):
# The simple case is f: R -> R
input_dim = 1
output_dim = 1
with tf.variable_scope('UniversalApproximator'):
ua_w = tf.get_variable(
name='ua_w'
, shape=[input_dim, hidden_dim]
, initializer=tf.random_normal_initializer(stddev=.1)
)
ua_b = tf.get_variable(
name='ua_b'
, shape=[hidden_dim]
, initializer=tf.constant_initializer(0.)
)
z = tf.matmul(x, ua_w) + ua_b
a = tf.nn.relu(z) # we now have our hidden_dim activations
ua_v = tf.get_variable(
name='ua_v'
, shape=[hidden_dim, output_dim]
, initializer=tf.random_normal_initializer(stddev=.1)
)
z = tf.matmul(a, ua_v)
return z
# We define the function we want to approximate
def func_to_approx(x):
return tf.sin(x)
if __name__ == '__main__': # When we call the script directly ...
# ... we parse a potentiel --nb_neurons argument
parser = argparse.ArgumentParser()
parser.add_argument("--nb_neurons", default=50, type=int, help="Number of neurons or the UA")
args = parser.parse_args()
# We build the computation graph
with tf.variable_scope('Graph') as scope:
# Our inputs will be a batch of values taken by our functions
x = tf.placeholder(tf.float32, shape=[None, 1], name="x")
# We define the ground truth and our approximation
y_true = func_to_approx(x)
y = univAprox(x, args.nb_neurons)
# We define the resulting loss and graph it using tensorboard
with tf.variable_scope('Loss'):
loss = tf.reduce_mean(tf.square(y - y_true))
# (Note the "_t" suffix here. It is pretty handy to avoid mixing
# tensor summaries and their actual computed summaries)
loss_summary_t = tf.summary.scalar('loss', loss)
# We define our train operation using the Adam optimizer
adam = tf.train.AdamOptimizer(learning_rate=1e-2)
train_op = adam.minimize(loss)
# This is some tricks to push our matplotlib graph inside tensorboard
with tf.variable_scope('TensorboardMatplotlibInput') as scope:
# Matplotlib will give us the image as a string ...
img_strbuf_plh = tf.placeholder(tf.string, shape=[])
# ... encoded in the PNG format ...
my_img = tf.image.decode_png(img_strbuf_plh, 4)
# ... that we transform into an image summary
img_summary = tf.summary.image(
'matplotlib_graph'
, tf.expand_dims(my_img, 0)
)
# We create a Saver as we want to save our UA after training
saver = tf.train.Saver()
with tf.Session() as sess:
# We create a SummaryWriter to save data for TensorBoard
result_folder = dir + '/results/' + str(int(time.time()))
sw = tf.summary.FileWriter(result_folder, sess.graph)
print('Training our universal approximator')
sess.run(tf.global_variables_initializer())
for i in range(3000):
# We uniformly select a lot of points for a good approximation ...
x_in = np.random.uniform(-10, 10, [100000, 1])
# ... and train on it
current_loss, loss_summary, _ = sess.run([loss, loss_summary_t, train_op], feed_dict={
x: x_in
})
# We leverage tensorboard by keeping track of the loss in real time
sw.add_summary(loss_summary, i + 1)
if (i + 1) % 100 == 0:
print('batch: %d, loss: %f' % (i + 1, current_loss))
print('Plotting graphs')
# We compute a dense enough graph of our functions
inputs = np.array([ [(i - 1000) / 100] for i in range(2000) ])
y_true_res, y_res = sess.run([y_true, y], feed_dict={
x: inputs
})
# We plot it using matplotlib
# (This is some matplotlib wizardry to get an image as a string,
# read the matplotlib documentation for more information)
plt.figure(1)
plt.subplot(211)
plt.plot(inputs, y_true_res.flatten())
plt.subplot(212)
plt.plot(inputs, y_res)
imgdata = io.BytesIO()
plt.savefig(imgdata, format='png')
imgdata.seek(0)
# We push our graph into TensorBoard
plot_img_summary = sess.run(img_summary, feed_dict={
img_strbuf_plh: imgdata.getvalue()
})
sw.add_summary(plot_img_summary, i + 1)
plt.clf()
# Finally we save the graph to check that it looks like what we wanted
saver.save(sess, result_folder + '/data.chkp')
现在你可以在电脑上打开两个终端,并在主目录下启动以下命令来看看能发生什么:
现在,你可以实时的查看 UA 的训练过程了,观察它是怎么学习正弦函数的。
请记住,如果我们增加隐藏层的神经元个数,那么对于函数的近似效果会更加的好。
让我给你展示一下 4 种不同的隐藏层神经元个数 [20, 50, 100,500],所带来的函数近似效果吧。
正如所预期的,如果我们增加神经元的数量,那么我们的近似函数 UA 将更好的近似我们的正弦函数。事实上,我们可以让神经网络模拟的数值尽可能的近似目标函数。这个工作是不是很漂亮 :)
然而,我们的 UA 模型有一个巨大的缺点,如果 input_dim 开始改变,那么我们不能对它进行重用。
我有一个疯狂的想法,如果我们能设计一个 UA,使得它能逼近一个复杂神经网络的激活函数!难道这不是一个很酷的设想吗?
我认为这是一个很好的练习例子,你怎么做能够欺骗 TensorFlow 去实现处理一个动态的输入维度。(具体可以参考我的 Github,但我建议你自己先写一下。)
在文章的最后,送大家一个小礼物:在MNIST数据集上,我已经使用了第二种方法去训练了一个神经网络,也就是说我们在一个神经网络中,使用另一个神经网络来代替激活函数。
以下图是激活函数近似的图形,是不是看起来很激动!
提示:在第二种 UA 中,我使用 ELU 函数作为了激活函数,所以看起来是一个凸的。所以,这些近似结果发生了多次。
我在 MNIST 测试集上面取得了 0.98 的正确率,这个结果给我一个启发,有可能激活函数对于一个任务的学习可能不是很重要。
Universal approximation theorem
Universal Approximation Theorem — Neural Networks