玩玩Tensorflow-從logistic到神經網路

最近在研究類神經網路的過程中,一直有在留意目前很紅的Tensorflow
Tensorflow是google推出的一款open source的深度學習框架
他的優點有可以將運算過程圖形化,可以運用GPU的資源等
今天試著用Tensorflow做一些小實驗跟記錄

在研究Tensorflow的時候可以從官方提供的MNIST的數字辨識入門
https://www.tensorflow.org/versions/r0.11/tutorials/mnist/beginners/index.html#mnist-for-ml-beginners
這個介紹從演算法到程式碼都還蠻詳細的,有興趣請多參考上面的入門,看完對tensorflow可以有一定程度的了解

預備環境

  • python 2.7
  • tensorflow 0.11
  • numpy 1.11.2
  • matplotlib 1.4.3

安裝方法可以參考https://www.tensorflow.org/versions/r0.12/get_started/os_setup.html#pip-installation
因為我是Mac的環境基本上就是pip install把想要的把想要的套件全部裝起來

tensorflow基本程式開發

開發一個tensorflow的基本流程如下

#import所需套件

import tensorflow as tf

#設置運算邏輯

x = tf.constant(1)
y = tf.constant(2)
op_add = tf.add(x, y)

#開啓一個tensorflows session進行運算

sess = tf.Session()
sess.run(tf.initialize_all_variables())
print sess.run(op_add)
sess.close()

#output : 3

上面是一個最基本的加法運算的例子
啟動一個tensorflow的運算需要以下幾步

  1. import所需套件
  2. 設置運算邏輯
  3. 開啓一個tensorflows session進行運算

整個流程來說比較特別的在於第二步的部分,也就是設置個運算邏輯的部分
要啟動一個tensorflow的運算時用的各種變數以及運算均需透過tensorflow裡面定義的function去實現
這也是最複雜的一部份,舉凡我們要設計各種機器學習還是深度學習的演算法均會在這一階段完成

而第三步就比較簡單,開啓一個tensorflow的Session對前面定義的演算子進行實際的運算
在開始運算之前需要先透過session跑一變初始化的動作sess.run(tf.initialize_all_variables())
之後就可以開始進行程式運算了,這裡要注意的是初始化的動作從tensorflow 0.12之後的版本變成sess.run(tf.global_variables_initializer()) 我這邊因為是0.11的版本所以用舊的方法

而session的啟動方式也可以考慮搭配with一起使用

with tf.Session() as sess:
    sess.run(tf.initialize_all_variables())
    print sess.run(op_add)

這裡同時介紹tensorflow的圖形化工具tensorboard
tensorboard可以幫助tensoflow做出運算的圖形跟記錄變數的變化
這在機器學習裡面畫出cost function變化的時候很方便

最簡單的用法就是加入一行tf.train.SummaryWriter("logs/", sess.graph)就行了
其中logs是tensorflow記錄的資料夾,它會自己生成
考慮下面的程式

test.py
import tensorflow as tf

x = tf.constant(1, name='x')
y = tf.constant(2, name='y')
op_add = tf.add(x, y, name='add_x_y')


with tf.Session() as sess:
    writer = tf.train.SummaryWriter("logs/", sess.graph)
    sess.run(tf.initialize_all_variables())
    print sess.run(op_add)

tensorflow在宣告變數或是演算子的時候都可以指定一個名字用來顯示在tensorboard
上面我將個演算子還有變數指定好name執行,當前目錄下會產生logs目錄,之後可以用以下方法打開tensorboard

$ python test.py
$ tensorboard --logdir='logs/'
Starting TensorBoard 29 on port 6006
(You can navigate to http://xxx.xxx.xxx.xx:6006)

之後可以從本地6006 port打開tensorboard看到如下畫面

現階段只有最基本的GRAPHIC有資料,其他EVENT那些因為還沒設定什麼都看不到
當透過適當的設定,tensorboard會是我們觀察機器學習performance的好幫手

回頭看看tensorflow在宣告變數的部分,tensorflow主要有三類變數

  • tf.constant:一般常數
  • tf.Variable:可變變量,常用在機器學習演算法裡面的權重角色,可隨著演算法進行過程被改變
  • tf.placeholder:填充用函數,在tensorflow session執行過程可動態填入,用來填入train data, test data等

用下面一個例子看這三個變數的用法
我想完成一個算式

程式碼如下

test.py
import tensorflow as tf

x = tf.constant(1, name='x')
y = tf.Variable(2, name='y')
z = tf.placeholder(tf.int32, name='z')
op_add = tf.add(x, y, name='add_x_y')
op_mul = tf.mul(z, op_add, name="z_mul_x_plus_y")
update_y = tf.assign(y, op_mul)

with tf.Session() as sess:
    writer = tf.train.SummaryWriter("logs/", sess.graph)
    sess.run(tf.initialize_all_variables())
    print sess.run(y)   #output: 2

    sess.run(update_y, feed_dict={z:3})
    print sess.run(y)   #output: 9


#Create a new session, all variables will be initialized again

with tf.Session() as sess:
    sess.run(tf.initialize_all_variables())
    print sess.run(y)   #output: 2

宣告一個常數x,跟一個可變數y,跟一個動態填入的參數z
這裡特別看一下placeholder的部分

z = tf.placeholder(tf.int32, name='z')

placeholder在宣告的時候內容是不知道的,只能填data type還有構造等部分,他的內容會在session執行的時候被填入
填的方法是在有用到z的演算子使用feed_dict去帶入實際的值

sess.run(update_y, feed_dict={z:3})

最後將運算完的結果用tf.assign賦予給y
整個運算流程的頂點節點是update_y(參考下圖),我只需要run update_y就可以完成整個運算流程

這邊要注意的一點是當我想對某個tensorflow的變數或運算結果的時候一定要用Session的run函式去執行
同時一個變數的生命週期會依賴Session,一旦Session結束,就算在啟動一個新的Session的,之前跑的結果也不會留下來

用tensorflow實作logistic

對tensorflow有點概念了,用以前實作過的logistic當範例,用tensorflow重新實作一遍

首先先定義訓練資料

import matplotlib.pyplot as plt
import tensorflow as tf
import numpy as np

#Define training data

dataset = np.array([
((-0.4, 0.3), 0),
((-0.3, -0.1), 0),
((-0.2, 0.4), 0),
((-0.1, 0.1), 0),
((0.6, -0.5), 0), #non-linear point

((0.8, 0.7), 1),
((0.9, -0.5), 1),
((0.7, -0.9), 1),
((0.8, 0.2), 1),
((0.4, -0.6), 1)])

x_data = np.matrix([x for x,y in dataset])
y_data = np.matrix([y for x,y in dataset]).T

x_data是我們的輸入特徵的矩陣,y_data是期望的結果,這是一個不可線性分割的範例,請參考logistic

在來是第二步,定義logistic演算法流程

首先定義執行時帶入input跟output的變量,方便我們之後將上面定義的x_data跟y_data塞給xs, ys

xs = tf.placeholder(tf.float32, [None, 2])
ys = tf.placeholder(tf.float32, [None, 1])

這邊定義資料類型是float32,同時定義結構
[None, 2]代表輸入是個 ? x 2 的矩陣,因為我們不知道輸入資料有幾筆
輸出結果則是一個 ? x 1的矩陣

接下來定義logistic公式

W = tf.Variable(tf.random_normal([2, 1]))
b = tf.Variable(tf.zeros([1, 1]) + 1)
z = tf.matmul(xs, W) + b
o = tf.sigmoid(z)
cross_entropy = tf.reduce_mean(ys * -tf.log(o) + (1-ys) * -tf.log(1-o))
#cross_entropy = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(z, ys))

train = tf.train.GradientDescentOptimizer(0.05).minimize(cross_entropy)

首先先用tf.random_normal將權重初始化,在將bias的部分全部設定成1
之後可以算出資料的加權總和公式如下

有加權總和之後就可以搭配sigmoid function算出logistic的cost function


題外話,相關的cost function在tensorflow大部份都有提供了,像我用的是cross_entropy這個logistic專用的cost function,就可以直接呼叫tf.nn.sigmoid_cross_entropy_with_logits去得到
最後定義最佳化方法,這邊直接用梯度下降法tf.train.GradientDescentOptimizer

之後開始執行

optimal_W = None
optimal_b = None
with tf.Session() as sess:
    init = tf.initialize_all_variables()
    sess.run(init)
    for i in range(1000):
        sess.run(train, feed_dict={xs:x_data, ys:y_data})
        if i % 50 == 0:
            print sess.run(cross_entropy, feed_dict={xs:x_data, ys:y_data})
    optimal_W = sess.run(W)
    optimal_b = sess.run(b)

###output

0.83135
0.70686
0.626753
0.569379
0.525247
0.48999
0.461233
0.437457
0.417591
0.400838
0.386592
0.374384
0.363848
0.354693
0.34669
0.339653
0.333435
0.327912
0.322984
0.31857

設定訓練1000次,每50次印出一次當下的cross_entropy
可以看出我們的誤差隨著訓練次數越來越低,代表我們定義的演算法的確有在學習

最後來畫個結果看來效果如何吧

ps = [v[0] for v in dataset]
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax1.scatter([v[0] for v in ps[:5]], [v[1] for v in ps[:5]], s=10, c='b', marker="o", label='O')
ax1.scatter([v[0] for v in ps[5:]], [v[1] for v in ps[5:]], s=10, c='r', marker="x", label='X')
l = np.linspace(-2,2)
a,b = -optimal_W[0][0]/optimal_W[1][0], -optimal_b[0][0]/optimal_W[1][0]
ax1.plot(l, a*l + b, 'b-')
plt.legend(loc='upper left');
plt.show()

得到一個還不錯的線幫我們做分類

雖說用logist可以做到一個還不錯的分類,但是如果我強行想去fit所有的資料呢,也就是我想要有個overfitting的效果
接下來用之前提過的神經網路試試吧

用tensorflow實作神經網路

這邊參考網路上的一段source code
https://www.youtube.com/watch?v=S9wBMi2B4Ss&list=PLXO45tsB95cKI5AIlf5TxxFPzb-0zeVZ8&index=12

我這邊建立一個2x10x1的三層神經網路來解上面的分類問題
輸入資料一樣,我這邊從新定義運算過程

def add_layer(inputs, in_size, out_size, activation_function=None):
    # add one more layer and return the output of this layer

    Weights = tf.Variable(tf.random_normal([in_size, out_size]))
    biases = tf.Variable(tf.zeros([1, out_size]) + 0.1)
    Wx_plus_b = tf.matmul(inputs, Weights) + biases
    if activation_function is None:
        outputs = Wx_plus_b
    else:
        outputs = activation_function(Wx_plus_b)
    return outputs


def draw(plt, ax, sess, o):
    x_dataset =[]
    o_dataset = []
    line = np.linspace(-1,1)
    for x in line:
        for y in line:
            res = sess.run(o, feed_dict={xs:[[x, y]]})[0]
            if res > 0.5:
                o_dataset.append([x, y])
            else:
                x_dataset.append([x, y])
    ax.scatter([v[0] for v in o_dataset], [v[1] for v in o_dataset], s=4, c='b', marker="o", label='O')
    ax.scatter([v[0] for v in x_dataset], [v[1] for v in x_dataset], s=4, c='r', marker="x", label='X')
    plt.pause(0.1)

上面定義了兩個函數
add_layer:用來產生神經網路的一個layer
draw:用來畫出當下的分類範圍

之後從新定義訓練過程如下

xs = tf.placeholder(tf.float32, [None, 2])
ys = tf.placeholder(tf.float32, [None, 1])
l1 = add_layer(xs, 2, 10, activation_function=tf.sigmoid)
o = add_layer(l1, 10, 1, activation_function=tf.sigmoid)
cross_entropy = tf.reduce_mean(ys * -tf.log(o) + (1-ys) * -tf.log(1-o))
train = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)

不再單純用sigmoid,用了一個2x10x1的神經網路去算出cross_entropy
訓練方法一樣是梯度下降法

之後執行過程中試著把訓練的變化畫出來
訓練10000次,每500次更新一次現在的分類狀況
這邊用plt.ion這個函式去讓plt.show不會被卡住

fig = plt.figure()
ax = fig.add_subplot(1,1,1)
plt.ion()
plt.xlim(-1, 1)
plt.ylim(-1, 1)
ax.scatter([v.item(0) for v in x_data[:5]], [v.item(1) for v in x_data[:5]], s=500, c='r', marker="x", label='X')
ax.scatter([v.item(0) for v in x_data[5:]], [v.item(1) for v in x_data[5:]], s=500, c='b', marker="o", label='O')
plt.show()

with tf.Session() as sess:
    init = tf.initialize_all_variables()
    sess.run(init)
    for i in range(10000):
        sess.run(train, feed_dict={xs:x_data, ys:y_data})
        if i % 500 == 0:
            print sess.run(cross_entropy, feed_dict={xs:x_data, ys:y_data})
            draw(plt, ax, sess, o)

變化狀況大致如下,隨著時間增進,分類的分佈越來越複雜,已經不再是簡單的線性分類

今天先記錄到此

comments powered by Disqus