CNN-簡單圖片分類

peterwell發表於2019-08-24

原文連結:

假期的時候跟著專知的一個深度學習課程學習了一些深度學習的內容,也是愈發覺得神經網路十分神奇,最近看了一份簡單的圖片分類的CNN網路,記錄學習一下,從簡單學起~

大部分神經網路的基礎就不再寫了,網上也有很多介紹,這裡就照著程式碼,順一遍基本的使用方法~

簡略介紹

訓練樣本50張,分為3類:

  • 0 => 飛機
  • 1 => 汽車
  • 2 => 鳥
    圖片都放在data文件夾中,按照label_id.jpg進行命名,例如2_111.jpg代表圖片類別為2(鳥),id為111。

匯入相應庫

1

2
3
4
5
6
7
8



import os

from PIL import Image
#矩陣運算庫
import numpy as np
import tensorflow as tf

讀取資料

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

# 資料文件夾

data_dir = "data"
test_data_dir = "test_data"
# 訓練還是測試,true為訓練
train = False
# 模型文件路徑
model_path = "model/image_model"


# 從文件夾讀取圖片和標籤到numpy陣列中
# 標籤資訊在文件名中,例如1_40.jpg表示該圖片的標籤為1
def (data_dir,test_data_dir):
   datas = []
   test_datas = []
   labels = []
   test_labels = []
   fpaths = []
   test_fpaths = []
    # 讀入圖片資訊,並且從圖片名中得到類別
    for fname in os.listdir(data_dir):
       fpath = os.path.join(data_dir, fname)
       fpaths.append(fpath)     # 向尾部新增
       image = Image.open(fpath)
       data = np.array(image) / 255.0
       label = int(fname.split( "_")[ 0])
       datas.append(data)
       labels.append(label)

    for fname in os.listdir(test_data_dir):
       fpath = os.path.join(test_data_dir, fname)
       test_fpaths.append(fpath)   # 向尾部新增
       image = Image.open(fpath)
       data = np.array(image) / 255.0
       label = int(fname.split( "_")[ 0])
       test_datas.append(data)
       test_labels.append(label)

   datas = np.array(datas)
   test_datas = np.array(test_datas)
   labels = np.array(labels)
   test_labels = np.array(test_labels)
    # 輸出矩陣維度
   print( "shape of datas: {}tshape of labels: {}".format(datas.shape, labels.shape))
    return fpaths, test_fpaths, datas, labels, test_labels, test_datas


fpaths, test_fpaths, datas, labels, test_labels, test_datas = read_data(data_dir,test_data_dir)

# 計算有多少類圖片
num_classes = len(set(labels))

這一部分程式碼主要就是實現將文件夾中的訓練樣本讀取出來,儲存在numpy陣列中。

定義placeholder(佔位符)

1

2
3
4
5
6

# 定義Placeholder,存放輸入和標籤

datas_placeholder = tf.placeholder(tf.float32, [ None, 32, 32, 3])
labels_placeholder = tf.placeholder(tf.int32, [ None])

# 存放DropOut引數的容器,訓練時為0.25,測試時為0
dropout_placeholdr = tf.placeholder(tf.float32)

palceholder

可以將placeholder理解為一種形參吧,然後不會被直接執行,只有在呼叫tf.run方法的時候才會被呼叫,這個時候需要向placeholder傳遞引數。
函式形式:

1

2
3
4
5
tf.placeholder(

   dtype, # 資料型別。常用的是tf.float32,tf.float64等數值型別
   shape= None, #資料形狀。預設是None,就是一維值,也可以是多維(比如[2,3], [None, 3]表示列是3,行不定)
   name= None   #名稱
)

dropout

dropout主要是為了解決過擬合的情況,過擬合就是將訓練集中的一些不是通用特徵的特徵當作了通用特徵,這樣可能會在訓練集上的損失函式很小,不過在測試的時候,就會導致損失函式很大。舉個例子吧,就像用一個網路來判斷這是不是一隻貓,而訓練集中的貓都是白色的,這樣就可能會導致整個網路將白色當作判斷貓的一個重要特徵,而測試集中的貓可能什麼顏色都有,這樣就會導致損失函式很大。

  • 而dropout的解決方法就是在訓練的時候,以一定的機率讓部分神經元停止工作,這樣就可以減少特徵檢測器(隱層節點)間的相互作用,避免過擬合情況的發生。
  • 測試時整合所有神經元

定義卷積神經網路(卷積層和pooling層)

1

2
3
4
5
6
7
8
9

# 定義卷積層, 20個卷積核, 卷積核大小為5,用Relu啟用

conv0 = tf.layers.conv2d(datas_placeholder, 20, 5, activation=tf.nn.relu)
# 定義max-pooling層,pooling視窗為2x2,步長為2x2
pool0 = tf.layers.max_pooling2d(conv0, [ 2, 2], [ 2, 2])

# 定義卷積層, 40個卷積核, 卷積核大小為4,用Relu啟用
conv1 = tf.layers.conv2d(pool0, 40, 4, activation=tf.nn.relu)
# 定義max-pooling層,pooling視窗為2x2,步長為2x2
pool1 = tf.layers.max_pooling2d(conv1, [ 2, 2], [ 2, 2])

sigmoid與relu


relu函式解決了梯度消失問題。

  • 在BP演算法中,反向傳播的過程是一個鏈式求導的過程(誤差透過梯度傳播),不過使用sigmoid函式,其中可能某一項的導數存在極小值,這樣就會導致整個偏導的導數值較小,導致誤差無法向前傳播,這樣的話,前幾層的引數無法得到更新。
  • 不過relu函式就解決了這個問題啦

max-pooling

池化層有mean-pooling、max-pooling啥的。
pooling層主要是保留特徵,減少下一層的引數量和計算量,同時也能防止過擬合。
例如:max-pooling層的大小為2x2,那麼就會對一個2x2的畫素快進行取樣,得到這個小區域的最大值,並將這個值來代表這個區域塊傳遞到下一層中。
透過這樣的方式就可以來減少引數量和計算量,並且還保持某種不變性,包括translation(平移),rotation(旋轉),scale(尺度)

定義全連線部分

1

2
3
4
5
6
7
8
9
10
11
12
13

# 將3維特徵轉換為1維向量

flatten = tf.layers.flatten(pool1)

# 全連線層,轉換為長度為100的特徵向量
fc = tf.layers.dense(flatten, 400, activation=tf.nn.relu)

# 加上DropOut,防止過擬合
dropout_fc = tf.layers.dropout(fc, dropout_placeholdr)

# 未啟用的輸出層
logits = tf.layers.dense(dropout_fc, num_classes)
# 預測輸出
predicted_labels = tf.arg_max(logits, 1)

全連線層,加上dropout,然後最後定義輸出層。

1

2
3
4
arg_max(a, axis=
None, out=
None)

# a 表示array
# axis 表示指定的軸,預設是None,表示把array平鋪,
# out 預設為None,如果指定,那麼返回的結果會插入其中

返回沿軸axis最大值的索引值,也就是最可能的標籤值。

定義損失函式和最佳化器

1

2
3
4
5
6
7
8
9
10

# 利用交叉熵定義損失

losses = tf.nn.softmax_cross_entropy_with_logits(
   labels=tf.one_hot(labels_placeholder, num_classes),
   logits=logits
)
# 平均損失
mean_loss = tf.reduce_mean(losses)

# 定義最佳化器,指定要最佳化的損失函式
optimizer = tf.train.AdamOptimizer(learning_rate= 1e-2).minimize(losses)

程式碼中的one_hot()函式的作用是將一個值化為一個機率分佈的向量,一般用於分類問題。然後再用reduce_mean()得到平均值。

執行階段

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

# 用於儲存和載入模型

saver = tf.train.Saver()

with tf.Session() as sess:

    if train:
       print( "訓練模式")
        # 如果是訓練,初始化引數
       sess.run(tf.global_variables_initializer())
        # 定義輸入和Label以填充容器,訓練時dropout為0.25
       train_feed_dict = {
           datas_placeholder: datas,
           labels_placeholder: labels,
           dropout_placeholdr: 0.25
       }
        for step in range( 136):
           _, mean_loss_val = sess.run([optimizer, mean_loss], feed_dict=train_feed_dict)

            if step % 10 == 0:
               print( "step = {}tmean loss = {}".format(step, mean_loss_val))
       saver.save(sess, model_path)
       print( "訓練結束,儲存模型到{}".format(model_path))
    else:
       print( "測試模式")
        # 如果是測試,載入引數
       saver.restore(sess, model_path)
       print( "從{}載入模型".format(model_path))
        # label和名稱的對照關係
       label_name_dict = {
            0: "飛機",
            1: "汽車",
            2: "鳥"
       }
        # 定義輸入和Label以填充容器,測試時dropout為0
       test_feed_dict = {
           datas_placeholder: test_datas,
           labels_placeholder: labels,
           dropout_placeholdr: 0
       }
       predicted_labels_val = sess.run(predicted_labels, feed_dict=test_feed_dict)
        # 真實label與模型預測label
        for fpath, real_label, predicted_label in zip(test_fpaths, test_labels, predicted_labels_val):
            # 將label id轉換為label名
           real_label_name = label_name_dict[real_label]
           predicted_label_name = label_name_dict[predicted_label]
           print( "{}t{} => {}".format(fpath, real_label_name, predicted_label_name))

這一部分程式碼感覺沒啥特別好寫的,大部分都寫在註釋裡了,而且一些寫法應該也是比較固定的,,,,

總結

可以看一下執行結果

感覺效果還是挺好的,真的挺神奇的。在這個過程中也是學習到了很多知識,這個整理總結歸納的過程也是收穫頗多,以後繼續加油~


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69946120/viewspace-2654834/,如需轉載,請註明出處,否則將追究法律責任。

相關文章