TensorFlow学习系列(四):利用神经网络实现泛逼近器(universal approximator)
TensorFlow学习系列(四):利用神经网络实现泛逼近器(universal approximator)
AllenOR灵感 发表于7个月前
TensorFlow学习系列(四):利用神经网络实现泛逼近器(universal approximator)
  • 发表于 7个月前
  • 阅读 5
  • 收藏 0
  • 点赞 0
  • 评论 0

【腾讯云】新注册用户域名抢购1元起>>>   

这篇教程是翻译Morgan写的TensorFlow教程,作者已经授权翻译,这是原文


目录


TensorFlow学习系列(一):初识TensorFlow

TensorFlow学习系列(二):形状和动态维度

TensorFlow学习系列(三):保存/恢复和混合多个模型

TensorFlow学习系列(四):利用神经网络实现泛逼近器(universal approximator)

TensorFlow学习系列(五):如何使用队列和多线程优化输入管道


今天,我们先不用TensorFlow去实现具体的东西,而是去学习一点新的东西。

你听说过泛逼近定理 “Universal approximation theorem” 吗?

简单地,这个定理可以描述成如下三步(没有所有细节):

  • 对于任何定义域为 R^n 的连续函数 f ...
  • 你能找到一个拥有足够宽的一层隐藏层的神经网络 ...
  • 在一个闭区间上,这个神经网络就会逼近函数 f ...

听起来是不是很简单。

那让我们直接利用 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 必须是秩为2的TensorFlow张量,也就是说 x 的维度是 [None, 1] 。其中,None表示批处理大小,你可以把它看做是在单个神经元上面计算所需要的容量。
  • 在这里,input_dimoutput_dim参数我们都是采取硬编码方式,当然如果你想要处理更复杂的函数,你可以修改这两个参数。在我们的例子中,我们取一个神经元是为了使问题尽量的简单。
  • 最后,我们使用了 Relu 激活函数。当然我们可以使用很多不同的激活函数来代替 Relu 函数,但是对于这个理论是无所谓的,因为我们只需要一个递增的函数就行了,选择不同的激活函数只是和学习的速度有关。

接下来,让我们编写一个很简单的脚本来评估这个函数:

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 来近似。
  • 你可以手动建立一个神经网络,通过添加必要的神经元来构造这个分段函数。

接下来,我们可以构造一个脚本来做三件事:

  • 在正弦函数上训练我们的UA。
  • 用图来对比我们神经网络的结果和原始的正弦函数。
  • 从命令行中可以导入 hidden_dim 参数的值,以便能够更加轻松的修改它。

我将直接在这里发布整个脚本文件,包含说明注释。

我相信发布一个完整的代码对你的学习是非常有利的(不要害怕文件太长,里面包含了很多的注释和空行)。

# 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')

现在你可以在电脑上打开两个终端,并在主目录下启动以下命令来看看能发生什么:

  • python myfile.py --nb_neurons 50
  • tensorboard --logdir results --reload_interval 5 (默认的 reload_interval 是120秒,以避免在计算机上面太快统计,但是在我们的情况下,我们可以安全地加速一点)

现在,你可以实时的查看 UA 的训练过程了,观察它是怎么学习正弦函数的。

请记住,如果我们增加隐藏层的神经元个数,那么对于函数的近似效果会更加的好。

让我给你展示一下 4 种不同的隐藏层神经元个数 [20, 50, 100,500],所带来的函数近似效果吧。


Different graph showing the effect of the number of neurons in the UA

正如所预期的,如果我们增加神经元的数量,那么我们的近似函数 UA 将更好的近似我们的正弦函数。事实上,我们可以让神经网络模拟的数值尽可能的近似目标函数。这个工作是不是很漂亮 :)

然而,我们的 UA 模型有一个巨大的缺点,如果 input_dim 开始改变,那么我们不能对它进行重用。

我有一个疯狂的想法,如果我们能设计一个 UA,使得它能逼近一个复杂神经网络的激活函数!难道这不是一个很酷的设想吗?

我认为这是一个很好的练习例子,你怎么做能够欺骗 TensorFlow 去实现处理一个动态的输入维度。(具体可以参考我的 Github,但我建议你自己先写一下。)

在文章的最后,送大家一个小礼物:在MNIST数据集上,我已经使用了第二种方法去训练了一个神经网络,也就是说我们在一个神经网络中,使用另一个神经网络来代替激活函数。

以下图是激活函数近似的图形,是不是看起来很激动!


提示:在第二种 UA 中,我使用 ELU 函数作为了激活函数,所以看起来是一个凸的。所以,这些近似结果发生了多次。
我在 MNIST 测试集上面取得了 0.98 的正确率,这个结果给我一个启发,有可能激活函数对于一个任务的学习可能不是很重要。


Reference:

Universal approximation theorem

Universal Approximation Theorem — Neural Networks

  • 打赏
  • 点赞
  • 收藏
  • 分享
共有 人打赏支持
粉丝 8
博文 2139
码字总数 82983
×
AllenOR灵感
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: