用TensorFlow训练神经网络解决Fizz Buzz问题

在 2017-09-29 发布于 人工智能 下以来已有590人读过本文 | 0条评论 发表评论

在西方国家,Fizz Buzz是一个经典的游戏,经常用来帮助孩子们锻炼学习除法。其从1开始数数,遇到能被3整除的,将数字替换为fizz,遇到能被5整除的,将其替换为buzz,而遇到能同时被3和5整除的数字,则将其替换为fizzbuzz,对其余数字,则保持不变将其直接输出。

当然,这是一个很简单的问题,尤其是编码实现,原本不需要动用TensorFlow这样的牛刀。而本篇博客的来源却是有个牛人在Google面试的时候,面试官出了这个问题给他,他独辟蹊径尝试用TensorFlow训练模型对问题进行了解决。考虑到使用TensorFlow来解决这个问题时,无需外部数据(直接通过程序生成训练数据和测试数据),可以将所有的焦点集中到TensorFlow的使用上,因此,这个问题是一个很好的锻炼TensorFlow的机会,所以我也尝试着用TensorFlow来写一下。

0. import

与前类似,首先是import各模块。

import numpy as np
import tensorflow as tf

1.  生成数据

仿面试中的这个问题,我们用1到100之间的数字作为测试集数据;而使用101到10000之间的数字作为训练集数据。

此外,考虑到神经网络的输入一般为多维数据,而本例的输入为一个数字,是单维数据,所以可以将其转化为多维数据。一个很简单的方法,可以通过将输入数字编码为固定长度的二进制数。

首先定义编码函数,将十进制数转化为二进制数:

DIGITS = 20
def binary_encode(num, digits=DIGITS):
  return [num >> i & 1 for i in range(digits)][::-1]

这里,我们定义编码的二进制数为20位,高位补0即可。

输出我们采用one-hot编码,定义得到一个输入数字的label的函数如下:

def label_encode(num):
  if num % 15 == 0:
    return [1, 0, 0, 0]
  elif num % 3 == 0:
    return [0, 1, 0, 0]
  elif num % 5 == 0:
    return [0, 0, 1, 0]
  else:
    return [0, 0, 0, 1]

然后,根据以上定义的编码函数,定义生成数据的函数如下:

def get_data(num, low=101, high=10000):
  binary_num_list = []
  label_list = []
  for i in range(num):
    n = np.random.randint(low, high, 1)[0]
    binary_num_list.append(np.array(binary_encode(n)))
    label_list.append(np.array(label_encode(n)))
  return np.array(binary_num_list), np.array(label_list)

参数num表示生成数据的数量,随机产生一批数字,并将其编码返回;返回值中的第一个是数据,第二个是label。

2. 定义模型

我们将模型的定义放到一个函数里,以便管理。

def model(data):
  with tf.variable_scope('layer1') as scope:
    weight = tf.get_variable('weight', shape=(DIGITS, 256))
    bias = tf.get_variable('bias', shape=(256,))
    x = tf.nn.relu(tf.matmul(data, weight) + bias)

  with tf.variable_scope('layer2') as scope:
    weight = tf.get_variable('weight', shape=(256, 4))
    bias = tf.get_variable('bias', shape=(4,))
    x = tf.matmul(x, weight) + bias

  return x

此处,我们定义了一个隐含层的神经网络,隐含层节点用了256个,激活函数使用了relu,而模型的定义时输出层没有过激活函数,后续视具体情况再过激活函数。

3.定义数据Tensor、loss和Optimizer等

data = tf.placeholder(tf.float32, shape=(None, DIGITS))
label = tf.placeholder(tf.float32, shape=(None, 4))

x = model(data)
preds = tf.argmax(tf.nn.softmax(x), 1)
acc = tf.reduce_mean(tf.cast(tf.equal(preds, tf.argmax(label, 1)), tf.float32))
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=label, logits=x))
optimizer = tf.train.AdamOptimizer(0.01).minimize(loss)

首先定义两个placeholder用以装入数据和label,然后将输入数据过模型得到输出,并使用这个模型的输出来得到预测输出、精度和损失函数,并根据损失函数定义Optimizer。为了训练速度的考虑,直接使用了Adam优化器。

4.训练

with tf.Session() as sess:
  sess.run(tf.global_variables_initializer())
  for step in range(3000):
    train_data, train_label = get_data(128)
    _, a = sess.run([optimizer, acc],
                    feed_dict={data: train_data, label: train_label})
    if step % 300 == 0:
      print('Step: {} -> Accuracy: {:.3f}'.format(step, a))

接着就是定义Session开始训练过程,训练3000个step,每一个step随机取出128个数据作为一个batch。训练中对精度进行了监视,每300个step会输出当前batch的精度。

5.测试、输出

test_data = np.array([binary_encode(i) for i in range(1, 101)])
pred = sess.run(preds, feed_dict={data: test_data})
results = []
for i in range(1, 101):
  results.append('{}'.format(['fizzbuzz', 'fizz', 'buzz', i][pred[i - 1]]))
print(', '.join(results))

训练完成后,直接对训练得到的模型进行测试,输出1-100的fizzbuzz结果。测试数据和训练数据的生成过程类似,只是少了一个shuffle的过程,生成的测试数据需要保持最初的顺序。

此外,需要注意的是,这里的测试程序需要在上面的sess下运行。

6.完整代码

此处不再将完整代码列出来,而将其放到了GitHub上,请点击查看

发表评论

您的昵称 *

您的邮箱 *

您的网站