TensorFlow分類教程
本篇文章有2個topic剂桥,簡單的分類器和TensorFlow凯旭。首先,我們會編寫函數(shù)生成三種類別的模擬數(shù)據(jù)芥喇。第一組數(shù)據(jù)是線性可分的西采,第二種是數(shù)據(jù)是月牙形數(shù)據(jù)咬合在一起,第三種是土星環(huán)形數(shù)據(jù)继控。每組數(shù)據(jù)有兩個類型械馆,我們將分別建立模型,對每組數(shù)據(jù)分類武通。
本文的所有代碼在ML-tutorial.
線性可分的數(shù)據(jù)如下:
月牙形的數(shù)據(jù)如下:
環(huán)形數(shù)據(jù)如下:
很明顯霹崎,第一組數(shù)據(jù)只需要一條直線(高維數(shù)據(jù)為超平面)即可滿足分類需求,所以后面我們會建立一個SoftMax回歸分類模型冶忱。第二組數(shù)據(jù)不可能被一條直線或一個超平面劃分尾菇,同樣,如果用直線劃分第三組數(shù)據(jù)囚枪,最好可以獲得50%的正確率派诬。針對第二組數(shù)據(jù)和第三組數(shù)據(jù)我們使用簡單的神經(jīng)網(wǎng)絡(luò)模型學(xué)習(xí)超曲面來劃分不同的數(shù)據(jù)類別。TensorFlow的使用方法會在建立三個模型的過程中引入链沼,減小學(xué)習(xí)的阻力默赂。
生成模擬數(shù)據(jù)
生成模擬數(shù)據(jù)集的方法很簡單,利用正弦括勺、圓等方程產(chǎn)生有規(guī)律的數(shù)據(jù)缆八,然后加入一些隨機(jī)擾動模擬噪音。所有函數(shù)只用到了Numpy疾捍。例如奈辰,generate_Saturn_data()
方法,首先使用np.linspace()
方法產(chǎn)生角度變化拾氓,然后確定圓心冯挎,之后生成兩個范圍的半徑底哥,分別用于生成內(nèi)核數(shù)據(jù)和外部的環(huán)形數(shù)據(jù)咙鞍,形狀就像土星和他的衛(wèi)星帶房官。代碼如下:
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構(gòu)成了第一種類別的樣本续滋,x2翰守、y2是第二種類別的樣本。所以疲酌,把(x1, y1)的標(biāo)簽標(biāo)注為0蜡峰,(x2, y2)的標(biāo)簽標(biāo)注為1,即id為0的類別和id為1的類別朗恳。0
和1
只是現(xiàn)實(shí)世界中某兩個關(guān)聯(lián)的類別的代表湿颅,例如車
和行
人。注意粥诫,參數(shù)N是每個類別的樣本數(shù)油航。
gen_data()
方法負(fù)責(zé)把上面生成的模擬數(shù)據(jù)組裝成訓(xùn)練數(shù)據(jù)集和測試數(shù)據(jù)集,每個樣本的標(biāo)注采用了TensorFlow支持的One-Hot編碼格式怀浆。例如谊囚,第一種類別的數(shù)據(jù)樣本(x1, y1)的標(biāo)注應(yīng)該是(1, 0),而第二種類別的數(shù)據(jù)樣本(x2, y2)的標(biāo)注應(yīng)該是(0, 1)执赡。為了滿足隨機(jī)梯度下降的特點(diǎn)镰踏,在gen_data()
方法內(nèi)部還對數(shù)據(jù)進(jìn)行了重新隨機(jī)排列,同時也考慮到了每個類別的樣本數(shù)量沙合。
線性模型
對于線性可分的數(shù)據(jù)奠伪,只需要SoftMax回歸模型就可以應(yīng)付。
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進(jìn)行了重置首懈,防止環(huán)境中有沖突的graph芳来。也可以實(shí)例化新的graph,然后綁定到后面的session上猜拾,但我們這里堅(jiān)持使用默認(rèn)的graph即舌。
模型的輸入是x
和y
,均為tf.placeholder
類型挎袜,相當(dāng)于占位符顽聂,只在訓(xùn)練或推理(只需要x
)的時候,真正綁定具體的輸入盯仪。x
表示輸入的樣本紊搪,注意每個樣本有兩個數(shù)值,因此x
的shape
是[None, 2]
全景。同理耀石,樣本只有兩種類型,因此one-hot編碼后的標(biāo)注y也是shape
為[None, 2]
的Tensor爸黄。
接下來滞伟,W
和b
是模型的參數(shù)揭鳞,經(jīng)過訓(xùn)練,不斷修正梆奈。這類型的數(shù)據(jù)野崇,在TensorFlow中為tf.Variable
類型,同時也給定了W
和b
的初始化方法亩钟,即全部初始化為0.0乓梨。這里,需要特別注意清酥,使用0.0初始化權(quán)重是有風(fēng)險的扶镀,很容易使模型陷于某個非最優(yōu)的鞍點(diǎn),導(dǎo)致無法優(yōu)化模型焰轻。典型的特點(diǎn)是狈惫,不管訓(xùn)練多少輪,loss只在最初有下降鹦马,訓(xùn)練一定輪數(shù)后就無法繼續(xù)下降胧谈。正確率很低,但卻不再提升荸频,一種過擬合的狀態(tài)菱肖。如下圖:
正確率曲線:
loss曲線:
通常,這個時候最有效但很容易被忽略的的方法可能就是改變初始化方法旭从,例如使用隨機(jī)正態(tài)初始化
tf.random_normal()
稳强。當(dāng)然,改變激活函數(shù)和悦、loss函數(shù)退疫、權(quán)重更新策略、學(xué)習(xí)率等都可能會產(chǎn)生作用鸽素。本文建立的所有模型褒繁,你都可以自己嘗試修改一些地方,看看能不能得到更高的正確率馍忽、更快的學(xué)習(xí)速度棒坏。不要害怕出錯,盡管試驗(yàn)遭笋,你會學(xué)到一些無法傳授的知識坝冕,這些知識只能通過實(shí)踐產(chǎn)生,所謂實(shí)踐出真知吧瓦呼。下面的accuracy曲線和loss曲線就是修改了初始化方法后的效果喂窟。
設(shè)定好模型結(jié)構(gòu)之后,還需要設(shè)計(jì)損失函數(shù)和優(yōu)化方法。損失函數(shù)直接定義了模型的學(xué)習(xí)目標(biāo)磨澡,設(shè)置的恰當(dāng)合理碗啄,有助于提升學(xué)習(xí)速度和正確率,這很大一部分取決于我們對整個問題的理解和學(xué)習(xí)過程的把握钱贯。鑒于這只是一組簡單的線性數(shù)據(jù),使用均方差和隨機(jī)梯度下降就可以了侦另。注意秩命,我們的第一個問題全局只有一個最優(yōu)解,所以只要學(xué)習(xí)時間夠長褒傅,總是可以得到很高(甚至100%)的正確率弃锐。有兩個概念要解釋一下,下面代碼中的epoch
和step
殿托。因?yàn)橐M(jìn)行隨機(jī)梯度下降霹菊,我們需要不斷迭代,把整個訓(xùn)練集的樣本分成多個step支竹,逐個送入模型計(jì)算誤差和梯度旋廷、更新權(quán)重。每個epoch完畢礼搁,正好訓(xùn)練集的所有樣本被迭代一遍饶碘。
# 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可視化學(xué)習(xí)過程,例如監(jiān)控Accuracy的變化馒吴、loss的變化扎运。TensorFlow和TensorBoard為開發(fā)者提供了很多功能。使用方法是
- 實(shí)例化
tf.summary.FileWriter()
- 把需要監(jiān)控的參數(shù)加入到隊(duì)列中饮戳,標(biāo)量用
tf.summary.scalar
豪治,張量用tf.summary.histogram
- 合并所有監(jiān)控的結(jié)點(diǎn)到graph上,建立依賴關(guān)系
merged = tf.summary.merge_all()
- 調(diào)用filewriter的
add_summary()
- 在terminal中啟動tensorboard
tensorboard --logdir=...
最終扯罐,可以在瀏覽器中看到上面的曲線了负拟。
訓(xùn)練之后,可視化模型的決策結(jié)果
參數(shù)
W
和b
的值如下:(你如果動手做一下歹河,結(jié)果會有些變動齿椅,因?yàn)閿?shù)據(jù)是隨機(jī)的,初始化也是隨機(jī)的启泣,但樣本的整體面貌不會有大的改變涣脚,所以最后的參數(shù)也會相近)可以看到,我們最后學(xué)到的其實(shí)是一條直線寥茫。
類多項(xiàng)式模型
一個簡單的兩個隱藏層的神經(jīng)網(wǎng)絡(luò)分類器模型遣蚀,第一層隱層有32個神經(jīng)元,激活函數(shù)為tanh
,第二個隱層有8個神經(jīng)元芭梯,激活函數(shù)同樣為tanh
险耀。
接下來的損失函數(shù)采用了交叉熵函數(shù)。
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))
最后玖喘,模型的分類效果大致如下:
類圓模型
第三組數(shù)據(jù)是環(huán)形數(shù)據(jù)甩牺,為了得到一個類圓的分類邊界,我們需要增加神經(jīng)網(wǎng)絡(luò)的隱藏層數(shù)量累奈,一個有四個隱藏層的神經(jīng)網(wǎng)絡(luò)分類器贬派。
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))
最后,分類結(jié)果如下:
當(dāng)然澎媒,建模過程肯定不是一帆風(fēng)順的搞乏,往往需要我們根據(jù)學(xué)習(xí)過程不斷調(diào)試模型的結(jié)構(gòu)和超參數(shù)的設(shè)定。例如戒努,一不小心就學(xué)習(xí)到了下面的結(jié)果请敦。。储玫。
不要灰心侍筛,保持前進(jìn)的勇氣,這就是深度學(xué)習(xí)撒穷。
參考:
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