TensorFlow分类教程
本篇文章有2个topic,简单的分类器和TensorFlow。首先,我们会编写函数生成三种类别的模拟数据。第一组数据是线性可分的,第二种是数据是月牙形数据咬合在一起,第三种是土星环形数据。每组数据有两个类型,我们将分别建立模型,对每组数据分类。
本文的所有代码在ML-tutorial.
线性可分的数据如下:
月牙形的数据如下:
环形数据如下:
很明显,第一组数据只需要一条直线(高维数据为超平面)即可满足分类需求,所以后面我们会建立一个SoftMax回归分类模型。第二组数据不可能被一条直线或一个超平面划分,同样,如果用直线划分第三组数据,最好可以获得50%的正确率。针对第二组数据和第三组数据我们使用简单的神经网络模型学习超曲面来划分不同的数据类别。TensorFlow的使用方法会在建立三个模型的过程中引入,减小学习的阻力。
生成模拟数据
生成模拟数据集的方法很简单,利用正弦、圆等方程产生有规律的数据,然后加入一些随机扰动模拟噪音。所有函数只用到了Numpy。例如,generate_Saturn_data()
方法,首先使用np.linspace()
方法产生角度变化,然后确定圆心,之后生成两个范围的半径,分别用于生成内核数据和外部的环形数据,形状就像土星和他的卫星带。代码如下:
def generate_Saturn_data(N=100):
theta = np.linspace(0, 2*PI, N) + PI*(np.random.rand(N))/100
a = 0.5
b = 0.5
r1 = 0.4 + 2*(np.random.rand(N)-0.5)/10
x1 = a + r1*np.cos(theta) + (np.random.rand(N)-0.5)/50
y1 = b + r1*np.sin(theta) + (np.random.rand(N)-0.5)/50
r2 = 0.2*np.random.rand(N)
x2 = a + r2*np.cos(theta) + (np.random.rand(N)-0.5)/50
y2 = b + r2*np.sin(theta) + (np.random.rand(N)-0.5)/50
return x1, y1, x2, y2
代码中,x1和y1构成了第一种类别的样本,x2、y2是第二种类别的样本。所以,把(x1, y1)的标签标注为0,(x2, y2)的标签标注为1,即id为0的类别和id为1的类别。0
和1
只是现实世界中某两个关联的类别的代表,例如车
和行
人。注意,参数N是每个类别的样本数。
gen_data()
方法负责把上面生成的模拟数据组装成训练数据集和测试数据集,每个样本的标注采用了TensorFlow支持的One-Hot编码格式。例如,第一种类别的数据样本(x1, y1)的标注应该是(1, 0),而第二种类别的数据样本(x2, y2)的标注应该是(0, 1)。为了满足随机梯度下降的特点,在gen_data()
方法内部还对数据进行了重新随机排列,同时也考虑到了每个类别的样本数量。
线性模型
对于线性可分的数据,只需要SoftMax回归模型就可以应付。
tf.reset_default_graph()
x = tf.placeholder(dtype=tf.float32, shape=(None, 2), name='samples')
y = tf.placeholder(dtype=tf.float32, shape=(None, 2), name='labels')
W = tf.Variable(tf.zeros(shape=(2,2)), name='weight')
b = tf.Variable(tf.zeros(shape=(2)), name='bias')
pred = tf.nn.softmax(tf.matmul(x, W) + b, name='pred')
上面代码中首先对TensorFlow的graph进行了重置,防止环境中有冲突的graph。也可以实例化新的graph,然后绑定到后面的session上,但我们这里坚持使用默认的graph。
模型的输入是x
和y
,均为tf.placeholder
类型,相当于占位符,只在训练或推理(只需要x
)的时候,真正绑定具体的输入。x
表示输入的样本,注意每个样本有两个数值,因此x
的shape
是[None, 2]
。同理,样本只有两种类型,因此one-hot编码后的标注y也是shape
为[None, 2]
的Tensor。
接下来,W
和b
是模型的参数,经过训练,不断修正。这类型的数据,在TensorFlow中为tf.Variable
类型,同时也给定了W
和b
的初始化方法,即全部初始化为0.0。这里,需要特别注意,使用0.0初始化权重是有风险的,很容易使模型陷于某个非最优的鞍点,导致无法优化模型。典型的特点是,不管训练多少轮,loss只在最初有下降,训练一定轮数后就无法继续下降。正确率很低,但却不再提升,一种过拟合的状态。如下图:
正确率曲线:
loss曲线:
通常,这个时候最有效但很容易被忽略的的方法可能就是改变初始化方法,例如使用随机正态初始化
tf.random_normal()
。当然,改变激活函数、loss函数、权重更新策略、学习率等都可能会产生作用。本文建立的所有模型,你都可以自己尝试修改一些地方,看看能不能得到更高的正确率、更快的学习速度。不要害怕出错,尽管试验,你会学到一些无法传授的知识,这些知识只能通过实践产生,所谓实践出真知吧。下面的accuracy曲线和loss曲线就是修改了初始化方法后的效果。
设定好模型结构之后,还需要设计损失函数和优化方法。损失函数直接定义了模型的学习目标,设置的恰当合理,有助于提升学习速度和正确率,这很大一部分取决于我们对整个问题的理解和学习过程的把握。鉴于这只是一组简单的线性数据,使用均方差和随机梯度下降就可以了。注意,我们的第一个问题全局只有一个最优解,所以只要学习时间够长,总是可以得到很高(甚至100%)的正确率。有两个概念要解释一下,下面代码中的epoch
和step
。因为要进行随机梯度下降,我们需要不断迭代,把整个训练集的样本分成多个step,逐个送入模型计算误差和梯度、更新权重。每个epoch完毕,正好训练集的所有样本被迭代一遍。
# for train
cost = tf.reduce_mean(tf.square(y-pred))
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
train = optimizer.minimize(cost)
correct_pred = tf.equal(tf.argmax(pred, 1), tf.argmax(y, 1))
accurary = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
init_op = tf.global_variables_initializer()
saver = tf.train.Saver()
with tf.Session() as sess:
tf.summary.scalar('cost', cost)
tf.summary.histogram('weight', W)
tf.summary.scalar('accurary', accurary)
merged = tf.summary.merge_all()
train_writer = tf.summary.FileWriter('./log/linear_model/train', sess.graph)
test_writer = tf.summary.FileWriter('./log/linear_model/test', sess.graph)
sess.run(init_op)
x_train, y_train = data_linear['train_set']
x_test, y_test = data_linear['test_set']
num_samples = len(x_train)
for epoch in range(epochs):
steps = int(num_samples / batch_size)
indices = np.random.permutation(num_samples)
x_train_ = x_train[indices]
y_train_ = y_train[indices]
for step in range(steps):
start = step*batch_size
end = start + batch_size
x_ = x_train_[start:end,:]
y_ = y_train_[start:end,:]
summary, _, c = sess.run([merged, train, cost], feed_dict={x:x_, y:y_})
train_writer.add_summary(summary)
if epoch%100 == 99:
summary, acc = sess.run([merged, accurary], feed_dict={x:x_test, y:y_test})
test_writer.add_summary(summary, epoch)
print("Epoch:{:5d}, Accurary:{:.2f}".format(epoch, acc))
print('W:', W.eval())
print('b:', b.eval())
train_writer.close()
test_writer.close()
print("Training Finished!")
save_path = saver.save(sess, './log/linear_model/linear_model.ckpt')
print('model saved in path: ', save_path)
为了使用TensorBoard可视化学习过程,例如监控Accuracy的变化、loss的变化。TensorFlow和TensorBoard为开发者提供了很多功能。使用方法是
- 实例化
tf.summary.FileWriter()
- 把需要监控的参数加入到队列中,标量用
tf.summary.scalar
,张量用tf.summary.histogram
- 合并所有监控的结点到graph上,建立依赖关系
merged = tf.summary.merge_all()
- 调用filewriter的
add_summary()
- 在terminal中启动tensorboard
tensorboard --logdir=...
最终,可以在浏览器中看到上面的曲线了。
训练之后,可视化模型的决策结果
参数
W
和b
的值如下:(你如果动手做一下,结果会有些变动,因为数据是随机的,初始化也是随机的,但样本的整体面貌不会有大的改变,所以最后的参数也会相近)可以看到,我们最后学到的其实是一条直线。
类多项式模型
一个简单的两个隐藏层的神经网络分类器模型,第一层隐层有32个神经元,激活函数为tanh
,第二个隐层有8个神经元,激活函数同样为tanh
。
接下来的损失函数采用了交叉熵函数。
x = tf.placeholder(dtype=tf.float32, shape=(None, 2), name='samples')
y = tf.placeholder(dtype=tf.float32, shape=(None, 2), name='labels')
W1 = tf.Variable(tf.random_normal(shape=(2,32), mean=0.0, stddev=1), name='weight1')
b1 = tf.Variable(tf.zeros(shape=[32]), name='bias1')
W2 = tf.Variable(tf.random_normal(shape=(32,8)), name='weight2')
b2 = tf.Variable(tf.zeros(shape=[8]), name='bias2')
W3 = tf.Variable(tf.random_normal(shape=(8,2)), name='weight3')
b3 = tf.Variable(tf.zeros(shape=[2]), name='bias3')
z = tf.matmul(x, W1) + b1
layer1 = tf.tanh(z, name='layer1')
z = tf.matmul(layer1, W2) + b2
layer2 = tf.tanh(z, name='layer2')
out = tf.matmul(layer2, W3) + b3
pred = tf.nn.softmax(out, name='pred')
# for train
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(labels=y, logits=out))
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
train = optimizer.minimize(cost)
# for test
correct_pred = tf.equal(tf.argmax(pred, 1), tf.argmax(y, 1))
accurary = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
最后,模型的分类效果大致如下:
类圆模型
第三组数据是环形数据,为了得到一个类圆的分类边界,我们需要增加神经网络的隐藏层数量,一个有四个隐藏层的神经网络分类器。
x = tf.placeholder(dtype=tf.float32, shape=(None, 2), name='samples')
y = tf.placeholder(dtype=tf.float32, shape=(None, 2), name='labels')
W1 = tf.Variable(tf.random_normal(shape=(2,3), mean=0.0, stddev=1), name='weight1')
b1 = tf.Variable(tf.zeros(shape=(3)), name='bias1')
W2 = tf.Variable(tf.random_normal(shape=(3,6)), name='weight2')
b2 = tf.Variable(tf.zeros(shape=(6)), name='bias2')
W3 = tf.Variable(tf.random_normal(shape=(6,9)), name='weight3')
b3 = tf.Variable(tf.zeros(shape=(9)), name='bias3')
W4 = tf.Variable(tf.random_normal(shape=(9,2)), name='weight4')
b4 = tf.Variable(tf.zeros(shape=(2)), name='bias4')
z = tf.matmul(x, W1) + b1
# layer1 = tf.nn.relu(z, name='layer1')
# layer1 = tf.tanh(z, name='layer1')
layer1 = tf.tanh(z, name='layer1')
z = tf.matmul(layer1, W2) + b2
layer2 = tf.tanh(z, name='layer2')
z = tf.matmul(layer2, W3) + b3
layer3 = tf.tanh(z, name='layer3')
out = tf.matmul(layer3, W4) + b4
pred = tf.nn.softmax(out, name='pred')
# for train
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(labels=y, logits=out))
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
train = optimizer.minimize(cost)
# for test
correct_pred = tf.equal(tf.argmax(pred, 1), tf.argmax(y, 1))
accurary = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
最后,分类结果如下:
当然,建模过程肯定不是一帆风顺的,往往需要我们根据学习过程不断调试模型的结构和超参数的设定。例如,一不小心就学习到了下面的结果。。。
不要灰心,保持前进的勇气,这就是深度学习。
参考:
Simple end-to-end TensorFlow examples
Implementing a Neural Network from Scratch in Python – An Introduction
Implementing a Neural Network from Scratch in Python - Source Code