简单的TensorFlow分类教程

TensorFlow分类教程

本篇文章有2个topic,简单的分类器和TensorFlow。首先,我们会编写函数生成三种类别的模拟数据。第一组数据是线性可分的,第二种是数据是月牙形数据咬合在一起,第三种是土星环形数据。每组数据有两个类型,我们将分别建立模型,对每组数据分类。
本文的所有代码在ML-tutorial.

线性可分的数据如下:

linear data

月牙形的数据如下:

moon data

环形数据如下:

saturn data

很明显,第一组数据只需要一条直线(高维数据为超平面)即可满足分类需求,所以后面我们会建立一个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的类别。01只是现实世界中某两个关联的类别的代表,例如人。注意,参数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。
模型的输入是xy,均为tf.placeholder类型,相当于占位符,只在训练或推理(只需要x)的时候,真正绑定具体的输入。x表示输入的样本,注意每个样本有两个数值,因此xshape[None, 2]。同理,样本只有两种类型,因此one-hot编码后的标注y也是shape[None, 2]的Tensor。
接下来,Wb是模型的参数,经过训练,不断修正。这类型的数据,在TensorFlow中为tf.Variable类型,同时也给定了Wb的初始化方法,即全部初始化为0.0。这里,需要特别注意,使用0.0初始化权重是有风险的,很容易使模型陷于某个非最优的鞍点,导致无法优化模型。典型的特点是,不管训练多少轮,loss只在最初有下降,训练一定轮数后就无法继续下降。正确率很低,但却不再提升,一种过拟合的状态。如下图:
正确率曲线:

accuracy1

loss曲线:
cost1

通常,这个时候最有效但很容易被忽略的的方法可能就是改变初始化方法,例如使用随机正态初始化tf.random_normal()。当然,改变激活函数、loss函数、权重更新策略、学习率等都可能会产生作用。本文建立的所有模型,你都可以自己尝试修改一些地方,看看能不能得到更高的正确率、更快的学习速度。不要害怕出错,尽管试验,你会学到一些无法传授的知识,这些知识只能通过实践产生,所谓实践出真知吧。下面的accuracy曲线和loss曲线就是修改了初始化方法后的效果。

accuracy2
cost2

设定好模型结构之后,还需要设计损失函数和优化方法。损失函数直接定义了模型的学习目标,设置的恰当合理,有助于提升学习速度和正确率,这很大一部分取决于我们对整个问题的理解和学习过程的把握。鉴于这只是一组简单的线性数据,使用均方差和随机梯度下降就可以了。注意,我们的第一个问题全局只有一个最优解,所以只要学习时间够长,总是可以得到很高(甚至100%)的正确率。有两个概念要解释一下,下面代码中的epochstep。因为要进行随机梯度下降,我们需要不断迭代,把整个训练集的样本分成多个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为开发者提供了很多功能。使用方法是

  1. 实例化tf.summary.FileWriter()
  2. 把需要监控的参数加入到队列中,标量用tf.summary.scalar,张量用tf.summary.histogram
  3. 合并所有监控的结点到graph上,建立依赖关系merged = tf.summary.merge_all()
  4. 调用filewriter的add_summary()
  5. 在terminal中启动tensorboard tensorboard --logdir=...

最终,可以在浏览器中看到上面的曲线了。
训练之后,可视化模型的决策结果

linear

参数Wb的值如下:(你如果动手做一下,结果会有些变动,因为数据是随机的,初始化也是随机的,但样本的整体面貌不会有大的改变,所以最后的参数也会相近)
WB

可以看到,我们最后学到的其实是一条直线。

类多项式模型

一个简单的两个隐藏层的神经网络分类器模型,第一层隐层有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))

最后,模型的分类效果大致如下:

moon

类圆模型

第三组数据是环形数据,为了得到一个类圆的分类边界,我们需要增加神经网络的隐藏层数量,一个有四个隐藏层的神经网络分类器。

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

最后,分类结果如下:

saturn2

当然,建模过程肯定不是一帆风顺的,往往需要我们根据学习过程不断调试模型的结构和超参数的设定。例如,一不小心就学习到了下面的结果。。。


saturn1

不要灰心,保持前进的勇气,这就是深度学习。

参考:
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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,099评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,473评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,229评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,570评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,427评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,335评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,737评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,392评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,693评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,730评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,512评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,349评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,750评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,017评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,290评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,706评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,904评论 2 335

推荐阅读更多精彩内容