師傅領進門之6步教你跑通一個AI程式!

騰訊雲加社群發表於2018-08-28

歡迎大家前往騰訊雲+社群,獲取更多騰訊海量技術實踐乾貨哦~

本文由雲端計算基礎發表於雲+社群專欄

原始碼下載地址請點選原文檢視。

初學機器學習,寫篇文章mark一下,希望能為將入坑者解點惑。本文介紹一些機器學習的入門知識,從安裝環境到跑通機器學習入門程式MNIST demo。

內容提綱:

  1. 環境搭建
  2. 瞭解Tensorflow執行機制
  3. MNIST(手寫數字識別 ) softmax性線迴歸
  4. MNIST 深度卷積神經網路(CNN)
  5. tools 工具類
  6. CPU & GPU & multi GPU
  7. 學習資料

1 環境搭建 (Windows)

  • 安裝虛擬環境 Anaconda,方便python包管理和環境隔離。

Anaconda3 4.2 www.anaconda.com/downloads,自帶python 3.5。

  • 建立tensorflow隔離環境。開啟Anaconda安裝後的終端Anaconda Prompt,執行下面命令
conda create -n tensorflow python=3.5 #建立名為tensorflow,python版本為3.5的虛擬環境
activate tensorflow #啟用這個環境  
deactivate #退出當前虛擬環境。這個不用執行
複製程式碼

CPU 版本

pip install tensorflow #通過包管理來安裝
pip install whl-file #通過下載 whl 檔案安裝,tensorflow-cpu安裝包:http://mirrors.oa.com/tensorflow/windows/cpu/tensorflow-1.2.1-cp35-cp35m-win_amd64.whl, cp35是指python3.5
複製程式碼

**GPU 版本。**我的筆記本是技持NVIDIA顯示卡的,可以安裝cuda,GPU比CPU快很多,不過筆記本的視訊記憶體不大,小模型還可以跑,大模型建議在本地用CPU跑通,到Tesla平臺上訓練。

img

注意點:選擇正確的 CUDA 和 cuDNN 版本搭配,不要只安裝最新版本,tensorflow可能不支援。
複製程式碼

目前Tensorflow已支援到CUDA 9 & cuDNN 7,之前本人安裝只支援CUDA 8 & cuDNN 6,所以用是的:

CUDA8.1 developer.nvidia.com/cuda-80-ga2…

cudnn 6 developer.nvidia.com/cudnn ,將cudnn包解壓,把檔案放到cuda安裝的對應目錄中,C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0,bin對應bin,include對應include,再新增bin目錄到環境變數path中。

pip install tensorflow-gpu #通過包管理來安裝
pip install whl-file #http://mirrors.oa.com/tensorflow/windows/gpu/tensorflow_gpu-1.2.1-cp35-cp35m-win_amd64.whl 
複製程式碼
  • 一些python工具包安裝。用到啥安啥就行, pip install,不行就找原始碼編譯安裝
(tensorflow) D:\> pip install opencv-python   #opencv, tensoflow 虛擬環境中
(tensorflow) D:\> pip install scipy    #圖片讀取寫入,scipy.misc.imread
(tensorflow) D:\> pip install Pillow   #PIL/Pillow,這裡有個坑,壓縮過的PNG圖,在1.x版本解析會出現透明通道質量下降,升級
複製程式碼

2 瞭解Tensorflow執行機制

  • 上程式碼。注意註釋說明
import tensorflow as tf
hello_world = tf.constant('Hello World!', dtype=tf.string) #常量tensor
print(hello_world) #這時hello_world是一個tensor,代表一個運算的輸出
#out: Tensor("Const:0", shape=(), dtype=string)
hello = tf.placeholder(dtype=tf.string, shape=[None])#佔位符tensor,在sess.run時賦值
world = tf.placeholder(dtype=tf.string, shape=[None])
hello_world2 = hello+world #加法運算tensor
print(hello_world2)
#out: Tensor("add:0", shape=(?,), dtype=string)
#math
x = tf.Variable([1.0, 2.0]) #變數tensor,可變。
y = tf.constant([3.0, 3.0])
mul = tf.multiply(x, y) #點乘運算tensor
#logical
rgb = tf.constant([[[255], [0], [126]]], dtype=tf.float32)
logical = tf.logical_or(tf.greater(rgb,250.), tf.less(rgb, 5.))#邏輯運算,rgb中>250 or <5的位置被標為True,其它False
where = tf.where(logical, tf.fill(tf.shape(rgb),1.), tf.fill(tf.shape(rgb),5.))#True的位置賦值1,False位置賦值5
# 啟動預設圖.
# sess = tf.Session()
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())#變數初始化
    result = sess.run(hello_world) #Fetch, 獲取tensor運算結果
    print(result, result.decode(), hello_world.eval())#`t.eval()` is a shortcut for calling  `tf.get_default_session().run(t)`.
    #out: b'Hello World!' Hello World! b'Hello World!' #前輟'b'表示bytestring格式,decode解成string格式
    print(sess.run(hello, feed_dict={hello: ['Hello']}))
    #out: ['Hello']
    print(sess.run(hello_world2, feed_dict={hello: ['Hello'], world: [' World!']}))#Feed,佔位符賦值
    #out: [b'Hello World!']
    print(sess.run(mul))
    #out: [ 3.  6.]
    print(sess.run(logical))
    #out: [[[ True] [ True] [False]]] #rgb中>250 or <5的位置被標為True,其它False
    print(sess.run(where))
    #out: [[[ 1.] [ 1.] [ 5.]]] #True的位置賦值1,False位置賦值5
#sess.close()#sess如果不是用with方式定義,需要close
複製程式碼
  • Tensor。是一個控制程式碼,代表一個運算的輸出,但並沒有儲存運算輸出的結果,需要通過tf.Session.run(Tensor)或者Tensor.eval()執行運算過程後,才能得到輸出結果。A Tensor is a symbolic handle to one of the outputs of an Operation,It does not hold the values of that operation's output, but instead provides a means of computing those values in a TensorFlow.
  • Tensorflow執行過程:定義計算邏輯,構建圖(Graph) => 通過會話(Session),獲取結果資料。基本用法參見連結。

3 MNIST(手寫數字識別 ) softmax性線迴歸

  • 分析

MNIST是一個入門級的計算機視覺資料集,它包含各種手寫數字圖片:

img

它也包含每一張圖片對應的標籤,告訴我們這個是數字幾。比如,上面這四張圖片的標籤分別是5,0,4,1。

資料集圖片大小28x28,單通道灰度圖。儲存樣式如下:

img

MNIST手寫數字識別的目的是輸入這樣的包含手寫數字的28x28的圖片,預測出圖片中包含的數字。

softmax線性迴歸認為圖片中數字是N可能性由影像中每個畫素點用

img

表示是 數字 i 的可能性,計算出所有數字(0-9)的可能性,也就是所有數字置信度,然後把可能性最高的數字作為預測值。

evidence的計算方式如下:

img

其中

img

代表權重,

img

代表數字 i 類的偏置量,j 代表給定圖片 x 的畫素索引(0~28x28=784),用於畫素求和。即圖片每個畫素值x權重之和,再加上一個偏置b,得到可能性值。

引入softmax的目的是對可能性值做歸一化normalize,讓所有可能性之和為1。這樣可以把這些可能性轉換成概率 y

img

img

img

  • 開始實現

資料

X樣本 size 28x28 = 784

img

Y樣本 ,樣式如

img

img

讀取

from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets(FLAGS.data_dir, one_hot=True) #total 55000,one_hot方式,圖片x格式為1維陣列,大小784
batch_xs, batch_ys = mnist.train.next_batch(batch_size) #分batch讀取
複製程式碼

構建圖(Graph)

Inference推理,由輸入 x 到輸出預測值 y 的推理過程

img

img

x = tf.placeholder(tf.float32, [None, 784], name="input")#None表示batch size待定
with tf.variable_scope("inference"):#定義作用域,名子inference
    W = tf.Variable(tf.zeros([784, 10])) #初值為0,size 784x10
    b = tf.Variable(tf.zeros([10])) #初值為0 size 10
    y = tf.matmul(x, W) + b #矩陣相乘
複製程式碼

Loss 損失函式,分類一般採用交叉熵,這裡用的是softmax交交叉熵。交叉熵是用來度量兩個概率分佈間的差異性資訊,交叉熵公式如下:

img

with tf.variable_scope("loss"):
    loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y), name="loss")
    #softmax交叉熵公式: z * -log(softmax(x)) + (1 - z) * -log(1 - softmax (x)) # x: logits, z: label
複製程式碼

計算loss的方法有很多種,常見的還有L1 loss 、L2 loss、sigmoid 交叉熵、聯合loss、自定義loss...

Accuracy 準確率,預測值與真實值相同的概率。矩陣相乘輸出y值是一個陣列,tf.argmax函式可能從資料中找出最大元素下標,預測值的最大值下標和真值的最大值下標一致即為正確。

with tf.variable_scope("accuracy"):
    accuracy = tf.reduce_mean(tf.cast(tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1)), tf.float32), name="accuracy")
複製程式碼

Training 訓練,訓練的目的是讓Loss接近最小化,預測值接近真值,Tensorflow通過優化器Optimizers來實現。**在y = Wx+b中,W、b在訓練之初會賦初值(隨機 or 0),經過Optimizer不短優化,Loss逼近最小值,使W、b不斷接近理想值。**W、b一起共784x10+10個引數。

train_step = tf.train.GradientDescentOptimizer(FLAGS.learning_rate).minimize(loss)
複製程式碼

minimize函式:更新引數,讓Loss最小化,包含兩個步驟:計算梯度;更新引數。

grad_var = compute_gradients(loss) # return (gradient, variable) pairs
apply_gradients(grad_var) #沿著引數的梯度反方向更新引數,讓Loss變小
複製程式碼

GradientDescentOptimizer:梯度下降演算法優化器, Tensorflow實現的是SGD(隨機梯度下降)。其缺點是依賴當前batch,波動較大。

其它一些增強版Optimizers:參考連結。 MomentumOptimizer、AdadeltaOptimizer、AdamOptimizer、RMSPropOptimizer、AdadeltaOptimizer ...

Session:Tensorflow需要通過Session(會話)來執行推理運算,有兩種建立方式,兩者差別在於InteractiveSession會將自己設定為預設session,有了預設session,tensor.eval()才能執行。

sess = tf.Session()
sess = tf.InteractiveSession()
複製程式碼

也可以通過下設定預設session:

with sess.as_default(): xx.eval()
with tf.Session() as sess: xx.eval()
複製程式碼

配置gpu相關session引數:

sess_config = tf.ConfigProto(allow_soft_placement=True, log_device_placement=False)#允許沒有gpu或者gpu不足時用軟體模擬
sess_config.gpu_options.allow_growth = True #動態申請視訊記憶體。不加會申請全部,導致其他訓練程式不能執行
#sess_config.gpu_options.per_process_gpu_memory_fraction = 0.8 #按比例申請視訊記憶體
sess = tf.InteractiveSession(config=sess_config)
複製程式碼

一個網路的訓練過程是一個不斷迭代(前向+反向)的過程。前向演算法由前向後計算網路各層輸出,反向演算法由後向前計算引數梯度,優化引數,減小Loss。流程如圖:

img

注意:每隔一段時間輸出一下網路Loss和Accuracy,檢視效果。每隔一段時間快取一下網路引數,防止被突然中斷,可再恢復。

模型引數的儲存與恢復:

​ check point:預設儲存方式。

​ pb:mobile使用。

​ npz:字典儲存方式,{name: value}, numpy的一種儲存方式。對引數按名儲存,按名恢復。save和restore方法自己控制,可以選擇性儲存和恢復。參見附近程式碼中【tools.py】save_npz_dict & load_and_assign_npz_dict方法。

saver = tf.train.Saver(max_to_keep = 3, write_version = 2)
save_path = saver.save(sess, FLAGS.out_model_dir+'/model.ckpt')# check point方式
output_graph_def = tf.graph_util.convert_variables_to_constants(sess, sess.graph_def, output_node_names=['output'])#指定輸出節點名稱,這個需要在網路中定義
with tf.gfile.FastGFile(FLAGS.out_model_dir+'/mobile-model.pb', mode='wb') as f:
    f.write(output_graph_def.SerializeToString()) #pb方式
tools.save_npz_dict(save_list=tf.global_variables(), name=FLAGS.out_model_dir+'/model.npz', sess=sess) #pnz方式
複製程式碼

​ 恢復:

#check point
saver = tf.train.Saver(max_to_keep = 3, write_version = 2)
model_file=tf.train.latest_checkpoint(FLAGS.log_dir)
if model_file:
     saver.restore(sess, model_file)
#npz
tools.load_and_assign_npz_dict(name=FLAGS.log_dir+'/model.npz', sess=sess))列印網路中各引數資訊:方便檢視網路引數是否正確。
複製程式碼
def print_all_variables(train_only=False):
    if train_only:
        t_vars = tf.trainable_variables()
        print("  [*] printing trainable variables")
    else:
        t_vars = tf.global_variables()
        print("  [*] printing global variables")
    for idx, v in enumerate(t_vars):
        print("  var {:3}: {:15}   {}".format(idx, str(v.get_shape()), v.name))
複製程式碼
  • 視覺化。Tensorflow提供tensorboard視覺化工具,通過命令開啟web服務,由瀏覽器檢視,輸入網址http://localhost:6006
tensorboard --logdir=your-log-path #path中不要出現中文
# 需要在訓練過程指定相應log路徑,寫入相關資訊
# 參考附件【sample.py】中summary、writer相關關鍵字程式碼。
複製程式碼

Graph視覺化:

img

訓練過程視覺化:

img

batch size = 128, 訓練集,驗證集。可以看到loss在收斂,accuracy在提高。由於訓練集曲線反應的是當前batch的loss和accuracy,batch size相對不高,抖動較大。而驗證集是全部圖片進行測試,曲線較平滑。

4 MNIST深度卷積神經網路(CNN)

Softmax性線迴歸網路中,輸出y是輸入x的線性組合,即y = Wx+b,這是線性關係。在很多問題中其解法並非線性關係能完成的,在深度學習,能過能多層卷積神經網路組合非線性啟用函式來模擬更復雜的非線性關係,效果往往比單一的線性關係更好。先看深度卷積神經網路(CNN,Convolutional Neural Network)構建的MNIST預測模型,再逐一介紹各網路層。

  • MNIST CNN Inference推理圖。從輸入到輸出中間包含多個網路層:reshape、conv卷積、pool池化、fc全連結、dropout。自底向上輸入原始圖片資料x經過各層序列處理,得到各數字分類概率預測輸出y。Inference的結果轉給loss用作迭代訓練,圖中的

img

可以看出用的是AdamOptimizer優化器。

img

  • reshape 變形,對資料的邏輯結構進行改變,如二維變四維:[1, 784] => [1, 28, 28, 1],資料儲存內容未發生改變。這裡由於輸入資料儲存的手寫圖片是一維資料,轉成[batch_size, height, width, channels]格式
    with tf.name_scope('reshape'): #scope
        inputs = tf.reshape(inputs, [-1, 28, 28, 1])
        #[batch_size, height, width, channels], batch size=-1表示由inputs決定,
        #batch_size=inputs_size/(28x28x1)
複製程式碼
  • conv2d 卷積, 卷積核(yellow)與Image元(green)素相乘,累加得到輸出元素值(red)。Image的每個Channel(通道)都對應一個不同的卷積核,Channel內卷積核引數共享。所有輸入channel與其kernel相乘累加多層得到輸出的一個channel值。輸出如有多個channel,則會重複多次,kernel也是不同的。所以會有input_channel_count * output_channel_count個卷積核在卷積層中訓練的是卷積核
def conv2d(x, W): #W: filter [kernel[0], kernel[1], in_channels, out_channels]
  return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')
複製程式碼

tf.nn.conv2d:

img

img

data_format: input和output資料的邏輯結構,NHWC : batch height width channel。NCHW: batch channel height width。常用的是NHWC格式;在一些輸入資料中各通道資料分開存放,這種更適合NCHW。

input:輸入,data_format=NHWC時,shape為batch, in_height, in_width, in_channels,Tensor。

filter:卷積核,shape為filter_height, filter_width, in_channels, out_channels,共有in_channels*out_channels個filter_height, filter_width的卷積核,輸入輸出channel越多,計算量越大。

strides: 步長,shape為1, stride_h, stride_w, 1,通常stride_h和stride_w相等,表示卷積核延縱橫方向上每次前進的步數。上gif圖stride為1。

padding:卷積計算時資料不對齊時填充方式,VALID:丟棄多餘;SAME:兩端補0,讓多餘部分可被計算。

img

output:輸出,shape為batch, out_height, out_width, out_channels

output[b, i, j, k] =
    sum_{di, dj, q} input[b, strides[1] * i + di, strides[2] * j + dj, q] *
                    filter[di, dj, q, k]
複製程式碼
  • 啟用函式,與卷積搭配使用。啟用函式不是真的要去啟用什麼,在神經網路中,啟用函式的作用是能夠給神經網路加入一些非線性因素,使得神經網路可以更好地解決較為複雜的問題。

img

tf.nn.relu即是啟用函式,對卷積輸出作非線性處理,其函式如下:

img

img

其它還有如sigmoid:

img

img

tanh:

img

img

  • Pool池化,有最大池化和平均值池化,計算與卷積計算類似,但無卷積核,求核所覆蓋範圍的最大值或平均值,輸入channel對應輸出channel,沒有多層累加情況。輸入與輸出 channel數相同,輸出height、width取決於strides。

img

img

if is_max_pool:
    x = tf.nn.max_pool(x, [1,kernel[0],kernel[1],1], strides=[1,stride[0],stride[1],1], padding=padding, name='pool')
else:
    x = tf.nn.avg_pool(x, [1,kernel[0],kernel[1],1], strides=[1,stride[0],stride[1],1], padding=padding, name='pool')
複製程式碼
  • Dropout,隨機刪除一些資料,讓網路在這些刪除的資料上也能訓練出準確的結果,讓網路有更強的適應性,減少過擬合
x = tf.nn.dropout(x, keep_prob) #keep_prob 保留比例,keep_prob=1.0表示無dropout
複製程式碼
  • BN(batch normalize),批規範化。Inference中未標出,demo中未使用,但也是網路中很常用的一層。BN常作用在非線性對映前,即對Conv結果做規範化。一般的順序是 卷積-> BN -> 啟用函式。

BN好處:提升訓練速度,加快loss收斂,增加網路適應性,一定程式的解決反向傳播過程中的梯度消失和爆炸問題。詳細請戳。

  • FC(Full Connection)全連線,核心是矩陣相乘

img

,softmax性線迴歸就是一個FC。在CNN中全連線常出現在最後幾層,用於對前面設計的特徵做加權和。Tensorflow提供了相應函式tf.layers.dense。

  • 日誌,下圖列印了模型中需要訓練的引數的shape 和 各層輸出資料的shape(batch_size=1時),附件【tool.py】中有相關程式碼。目的是方便觀自己搭的網路結構是否符合預期。 資料由**[1x784]** -reshape-> [1x28x28x1](batch_size, height, width, channels) -conv-> [1x28x28x32] -pool-> [1x14x14x32] -conv-> [1x14x14x64] -pool-> [1x7x7x64] -fc-> [1x1024] -fc-> [1x10](每類數字的概率)

img

  • 訓練效果,詳細程式碼參考附件【cnn.py

img

  • 一個網上的視覺化手寫識別DEMOscs.ryerson.ca/~aharley/vi…
  • CNN家族經典網路,如LeNet,AlexNet,VGG-Net,GoogLeNet,ResNet、U-Net、FPN。它們也都是由基本網路層元素(上述介紹)堆疊而成,像搭積木一樣。

VGG,如下圖,非常有名的特徵提取和分類網路。由多層卷積池化層組成,最後用FC做特徵融合實現分類,很多網路基於其前幾層卷積池化層做特徵提取,再發展自己的業務。

img

5 tool工具類

tool.py】是一個自己基於tensorflow二次封裝的工具類,位於附件中。好處是以後程式設計更方便,程式碼結構更好看。網上也有現成的開源庫,如TensorLayer、Keras、Tflearn,自己封裝的目的是更好的理解tensorflow API,自己造可控性也更強一些,如果控制是引數是否被訓練、log列印。

下圖是MNIST CNN網路的Inference推理程式碼:

img

6 CPU & GPU & multi GPU

  • CPU, Tensorflow預設所有cpu都是/cpu:0,預設佔所有cpu,可以通過程式碼指定佔用數。
sess_config = tf.ConfigProto(device_count={"CPU": 14}, allow_soft_placement=True, log_device_placement=False)
sess_config.intra_op_parallelism_threads = 56
sess_config.inter_op_parallelism_threads = 56
sess = tf.InteractiveSession(config=sess_config)
複製程式碼
  • GPU,Tensorflow預設佔用/gpu:0, 可通過指定device來確定程式碼執行在哪個gpu。下面
with tf.device('/device:GPU:2'):
   a = tf.constant([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], shape=[2, 3], name='a')
   b = tf.constant([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], shape=[3, 2], name='b')
   c = tf.matmul(a, b)
#下面的程式碼配置可以避免GPU被佔滿,用多少記憶體佔多少
sess_config = tf.ConfigProto(allow_soft_placement=True, log_device_placement=False)
sess_config.gpu_options.allow_growth = True
sess_config.gpu_options.per_process_gpu_memory_fraction = 0.8
sess = tf.InteractiveSession(config=sess_config)
複製程式碼

多塊GPU時,可以通過在終端執行下面指令來設定CUDA可見GPU塊來控制程式使用哪些GPU。

export CUDA_VISIBLE_DEVICES=2,3
複製程式碼
  • 多GPU使用,在Tensorflow中多GPU程式設計比較尷尬,資料較好,程式碼寫起比較複雜,這一點不如Caffe。

在Tensorflow中你需要自己寫程式碼控制多GPU的loss和gradient的合併,這裡有個官方例子請戳。自己也寫過多GPU的工程,附件程式碼【tmp-main-gpus-不可用.py】可做參考,但此處不可用,來自它工程。

img

7 學習資料

收藏了一些機器學習相關資料,分享一下。自己也只看過很小一部分,仍在學習中....

問答

如何使用人工智慧合成人聲?

相關閱讀

多個場景中的AI落地實踐

AI學院 | 人工智慧基本知識概覽

人工智慧,天使還是魔鬼

雲學院 · 課程推薦 | 騰訊專項技術測試組長,結合8年經驗為你細說冷熱分離法則

此文已由作者授權騰訊雲+社群釋出,更多原文請點選

搜尋關注公眾號「雲加社群」,第一時間獲取技術乾貨,關注後回覆1024 送你一份技術課程大禮包!

海量技術實踐經驗,盡在雲加社群

相關文章