在西方国家,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上,请点击查看。