用TensorFlow训练神经网络解决Fizz Buzz问题
在西方国家,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上,请点击查看。