【火爐煉AI】深度學習005-簡單幾行Keras代碼解決二分類問題

【火爐煉AI】深度學習005-簡單幾行Keras代碼解決二分類問題

(本文所使用的Python庫和版本號: Python 3.6, Numpy 1.14, scikit-learn 0.19, matplotlib 2.2, Keras 2.1.6, Tensorflow 1.9.0)html

不少文章和教材都是用MNIST數據集做爲深度學習屆的「Hello World」程序,可是這個數據集有一個很大的特色:它是一個典型的多分類問題(一共有10個分類),在咱們剛剛開始接觸深度學習時,我卻是以爲應該從最簡單的二分類問題着手。git

在深度學習框架方面,目前比較流行的是Tensorflow,Keras,PyTorch,Theano等,可是我建議新手入門,能夠從Keras入手,而後進階時轉移到Tensorflow上,實際上,Keras的後端是能夠支持Tensorflow和Theano,能夠說,Keras是在Tensorflow和Theano的基礎上進一步封裝,更加的簡單實用,更容易入門,一般幾行簡單的代碼就能夠解決一個小型的項目問題。github

我這篇博文主要參考了:keras系列︱圖像多分類訓練與利用bottleneck features進行微調(三),這篇博文也是參考的Building powerful image classification models using very little data,但我發現這兩篇博文有不少地方的代碼跑不起來,主要緣由多是Keras或Tensorflow升級形成的,因此我作了一些必要的修改。後端


1. 準備數據集

最經典的二分類數據集就是Kaggle競賽中的「貓狗大戰」數據集(train set有25K張圖片,test set: 12.5K),此處按照原始博文的作法,我從train_set中選取1000張Dog的照片+1000張Cat照片做爲咱們新的train set,選取400張Dog+400張Cat照片做爲新的test set。因此train和test兩個文件夾下都有兩個子文件夾(cats和dogs子文件夾)。固然,選取是隨機的,也是用代碼來實現的,準備小數據集的代碼以下:網絡

def dataset_prepare(raw_set_folder,dst_folder,train_num_per_class=1000,test_num_per_class=400):
    ''' 準備小數據集,從原始的raw_set_folder數據集中提取train_num_per_class(每一個類別)的照片放入train中, 提取val_num_per_class(每一個類別)放入到validation文件夾中 :param raw_set_folder: 含有貓狗的照片,這些照片的名稱必須爲cat.101.jpg或dog.102.jpg形式 :param dst_folder: 將選取以後的圖片放置到這個文件夾中 :param train_num_per_class: :param test_num_per_class: :return: '''
    all_imgs=glob(os.path.join(raw_set_folder,'*.jpg'))
    img_len = len(all_imgs)
    assert img_len > 0, '{} has no jpg image file'.format(raw_set_folder)

    cat_imgs=[]
    dog_imgs=[]
    for img_path in all_imgs:
        img_name=os.path.split(img_path)[1]
        if img_name.startswith('cat'):
            cat_imgs.append(img_path)
        elif img_name.startswith('dog'):
            dog_imgs.append(img_path)
    random.shuffle(cat_imgs)
    random.shuffle(dog_imgs)
    [ensure_folder_exists(os.path.join(dst_folder,type_folder,class_folder)) for type_folder in ['train','test']
        for class_folder in ['dogs','cats']]
    # 下面的代碼能夠進一步優化。。。。
    for cat_img_path in cat_imgs[:train_num_per_class]: # 最開始的N個圖片做爲train
        _, fname = os.path.split(cat_img_path)  # 獲取文件名和路徑
        shutil.copyfile(cat_img_path, os.path.join(dst_folder, 'train', 'cats',fname))
    print('imgs saved to train/cats folder')
    for dog_img_path in dog_imgs[:train_num_per_class]:
        _, fname = os.path.split(dog_img_path)  # 獲取文件名和路徑
        shutil.copyfile(dog_img_path, os.path.join(dst_folder, 'train', 'dogs',fname))
    print('imgs saved to train/dogs folder')
    for cat_img_path in cat_imgs[-test_num_per_class:]: # 最末的M個圖片做爲test
        _, fname = os.path.split(cat_img_path)  # 獲取文件名和路徑
        shutil.copyfile(cat_img_path, os.path.join(dst_folder, 'test', 'cats',fname))
    print('imgs saved to test/cats folder')
    for dog_img_path in dog_imgs[-test_num_per_class:]: # 最末的M個圖片做爲test
        _, fname = os.path.split(dog_img_path)  # 獲取文件名和路徑
        shutil.copyfile(dog_img_path, os.path.join(dst_folder, 'test', 'dogs',fname))
    print('imgs saved to test/dogs folder')
    print('finished...')
複製代碼

運行該函數便可完成小數據集的構建,下面爲Keras建立圖片數據流,爲模型的構建作準備。app

# 2,準備訓練集,keras有不少Generator能夠直接處理圖片的加載,加強等操做,封裝的很是好
from keras.preprocessing.image import ImageDataGenerator
train_datagen = ImageDataGenerator( # 單張圖片的處理方式,train時通常都會進行圖片加強
        rescale=1. / 255, # 圖片像素值爲0-255,此處都乘以1/255,調整到0-1之間
        shear_range=0.2, # 斜切
        zoom_range=0.2, # 放大縮小範圍
        horizontal_flip=True) # 水平翻轉

train_generator = train_datagen.flow_from_directory(# 從文件夾中產生數據流
    train_data_dir, # 訓練集圖片的文件夾
    target_size=(IMG_W, IMG_H), # 調整後每張圖片的大小
    batch_size=batch_size,
    class_mode='binary') # 此處是二分類問題,故而mode是binary

# 3,一樣的方式準備測試集
val_datagen = ImageDataGenerator(rescale=1. / 255) # 只須要和trainset一樣的scale便可,不需加強
val_generator = val_datagen.flow_from_directory(
        val_data_dir,
        target_size=(IMG_W, IMG_H),
        batch_size=batch_size,
        class_mode='binary')
複製代碼

上面構建的generator就是keras須要的數據流,該數據流使用flow_from_directory首先從圖片文件夾(好比train_data_dir)中加載圖片到內存中,而後使用train_datagen來對圖片進行預處理和加強,最終獲得處理完成以後的batch size大小的數據流,這個數據流會無限循環的產生,直到達到必定的訓練epoch數量爲止。框架

上面用到了ImageDataGenerator來進行圖片加強,裏面的參數說明爲:(能夠參考Keras的官方文檔dom

rotation_range是一個0~180的度數,用來指定隨機選擇圖片的角度。函數

width_shift和height_shift用來指定水平和豎直方向隨機移動的程度,這是兩個0~1之間的比例。學習

rescale值將在執行其餘處理前乘到整個圖像上,咱們的圖像在RGB通道都是0~255的整數,這樣的操做可能使圖像的值太高或太低,因此咱們將這個值定爲0~1之間的數。

shear_range是用來進行剪切變換的程度

zoom_range用來進行隨機的放大

horizontal_flip隨機的對圖片進行水平翻轉,這個參數適用於水平翻轉不影響圖片語義的時候

fill_mode用來指定當須要進行像素填充,如旋轉,水平和豎直位移時,如何填充新出現的像素


2. 構建並訓練Keras模型

因爲Keras已經封裝了不少Tensorflow的函數,因此在使用上更加簡單容易,固然,若是想調整裏面的結構和參數等,也比較麻煩一些,因此對於高手,想要調整模型的結構和自定義一些函數,能夠直接用Tensorflow.

2.1 Keras模型的構建

不論是Keras模型仍是Tensorflow模型,我我的認爲其構建都包括兩個部分:模型的搭建和模型的配置,因此能夠從這兩個方面來創建一個小型的模型。代碼以下:

# 4,創建Keras模型:模型的創建主要包括模型的搭建,模型的配置
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import Activation, Dropout, Flatten, Dense
from keras import optimizers
def build_model(input_shape):
    # 模型的搭建:此處構建三個CNN層+2個全鏈接層的結構
    model = Sequential()
    model.add(Conv2D(32, (3, 3), input_shape=input_shape))
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))

    model.add(Conv2D(32, (3, 3)))
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))

    model.add(Conv2D(64, (3, 3)))
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))

    model.add(Flatten())
    model.add(Dense(64))
    model.add(Activation('relu'))
    model.add(Dropout(0.5)) # Dropout防止過擬合
    model.add(Dense(1)) # 此處雖然是二分類,可是不能用Dense(2),由於後面的activation是sigmoid,這個函數只能輸出一個值,即class_0的機率
    model.add(Activation('sigmoid')) #二分類問題用sigmoid做爲activation function
    
    # 模型的配置
    model.compile(loss='binary_crossentropy', # 定義模型的loss func,optimizer,
                  optimizer=optimizers.RMSprop(lr=0.0001),
                  metrics=['accuracy'])# 主要優化accuracy
    # 二分類問題的loss function使用binary_crossentropy,此處使用準確率做爲優化目標
    return model # 返回構建好的模型
複製代碼

這個函數就搭建了模型的結構,對模型進行了配置,主要配置了loss function, optimzer, 優化目標等,固然能夠作更多其餘配置。

此處,爲了簡單說明,只是創建了三層卷積層+兩層全鏈接層的小型網絡結構,固然,對於一些比較簡單的圖像問題,這個小型模型也能解決。若是須要構建更爲複雜的模型,只須要自定義這個函數,修改裏面的模型構建和配置方法便可。

2.2 模型的訓練

因爲此處咱們使用generator來產生數據流,故而訓練時要使用fit_generator函數。代碼以下:

model=build_model(input_shape=(IMG_W,IMG_H,IMG_CH)) # 輸入的圖片維度
# 模型的訓練
model.fit_generator(train_generator, # 數據流
                    steps_per_epoch=train_samples_num // batch_size, 
                    epochs=epochs,
                    validation_data=val_generator,
                    validation_steps=val_samples_num // batch_size)
複製代碼

因爲我在本身的筆記本上訓練,沒有獨立顯卡,更沒有英偉達那麼NB的顯卡,故而速度很慢,可是的確能運行下去。運行的具體結果能夠去個人github上看。

-------------------------------------輸---------出--------------------------------

Epoch 1/20 62/62 [==============================] - 136s 2s/step - loss: 0.6976 - acc: 0.5015 - val_loss: 0.6937 - val_acc: 0.5000 Epoch 2/20 62/62 [==============================] - 137s 2s/step - loss: 0.6926 - acc: 0.5131 - val_loss: 0.6846 - val_acc: 0.5813 Epoch 3/20 62/62 [==============================] - 152s 2s/step - loss: 0.6821 - acc: 0.5544 - val_loss: 0.6735 - val_acc: 0.6100

。。。

Epoch 18/20 62/62 [==============================] - 140s 2s/step - loss: 0.5776 - acc: 0.6880 - val_loss: 0.5615 - val_acc: 0.7262 Epoch 19/20 62/62 [==============================] - 143s 2s/step - loss: 0.5766 - acc: 0.6971 - val_loss: 0.5852 - val_acc: 0.6800 Epoch 20/20 62/62 [==============================] - 140s 2s/step - loss: 0.5654 - acc: 0.7117 - val_loss: 0.5374 - val_acc: 0.7450

--------------------------------------------完-------------------------------------

從訓練後的loss和acc上能夠大體看出,loss在不斷減少,acc也不斷增大,趨勢比較平穩。

此處咱們能夠將訓練過程當中的loss和acc繪圖,看看他們的變化趨勢。

# 畫圖,將訓練時的acc和loss都繪製到圖上
import matplotlib.pyplot as plt
%matplotlib inline
def plot_training(history):
    plt.figure(12)
    
    plt.subplot(121)
    train_acc = history.history['acc']
    val_acc = history.history['val_acc']
    epochs = range(len(train_acc))
    plt.plot(epochs, train_acc, 'b',label='train_acc')
    plt.plot(epochs, val_acc, 'r',label='test_acc')
    plt.title('Train and Test accuracy')
    plt.legend()
    
    plt.subplot(122)
    train_loss = history.history['loss']
    val_loss = history.history['val_loss']
    epochs = range(len(train_loss))
    plt.plot(epochs, train_loss, 'b',label='train_loss')
    plt.plot(epochs, val_loss, 'r',label='test_loss')
    plt.title('Train and Test loss')
    plt.legend()
 
    plt.show()
複製代碼

很明顯,因爲epoch次數太少,acc和loss都沒有達到平臺期,後續能夠增大epoch次數來達到一個比較好的結果。在原始博文中,做者在50個epoch以後達到了約80%左右的準確率,此處我20個epoch後的準確率爲74%。

2.3 預測新樣本

單張圖片的預測

模型訓練好以後,就須要用來預測新的圖片,看看它能不能準確的給出結果。預測函數爲:

# 用訓練好的模型來預測新樣本
from PIL import Image
from keras.preprocessing import image
def predict(model, img_path, target_size):
    img=Image.open(img_path) # 加載圖片
    if img.size != target_size:
        img = img.resize(target_size)

    x = image.img_to_array(img) 
    x *=1./255 # 至關於ImageDataGenerator(rescale=1. / 255)
    x = np.expand_dims(x, axis=0) # 調整圖片維度
    preds = model.predict(x) # 預測
    return preds[0]
複製代碼

用這個函數能夠預測單張圖片:

predict(model,'E:\PyProjects\DataSet\FireAI\DeepLearning/FireAI005/cat11.jpg',(IMG_W,IMG_H))

predict(model,'E:\PyProjects\DataSet\FireAI\DeepLearning//FireAI005/dog4.jpg',(IMG_W,IMG_H))
複製代碼

-------------------------------------輸---------出--------------------------------

array([0.14361556], dtype=float32)

array([0.9942463], dtype=float32)

--------------------------------------------完-------------------------------------

能夠看出,對於單張圖片cat11.jpg獲得的機率爲0.14,而dog4.jpg的機率爲0.99,能夠看出第0個類別是dog,第1個類別是cat,模型可以很好的區分開來。

多張圖片的預測

若是想用這個模型來預測一個文件夾中的全部圖片,那麼該怎麼辦了?

# 預測一個文件夾中的全部圖片
new_sample_gen=ImageDataGenerator(rescale=1. / 255)
newsample_generator=new_sample_gen.flow_from_directory(
        'E:\PyProjects\DataSet\FireAI\DeepLearning',
        target_size=(IMG_W, IMG_H),
        batch_size=16,
        class_mode=None,
        shuffle=False)
predicted=model.predict_generator(newsample_generator)
print(predicted)
複製代碼

-------------------------------------輸---------出--------------------------------

Found 4 images belonging to 2 classes. [[0.14361556] [0.5149474 ] [0.71455824] [0.9942463 ]]

--------------------------------------------完-------------------------------------

上面的結果中第二個0.5149對應的應該是cat,應該小於0.5,這個預測是錯誤的,不過粗略估計正確率有3/4=75%。

2.4 模型的保存和加載

模型通常要及時保存到硬盤上,防止數據丟失,下面是保存的代碼:

# 模型保存
# model.save_weights('E:\PyProjects\DataSet\FireAI\DeepLearning//FireAI005/FireAI005_Model.h5') # 這個只保存weights,不保存模型的結構
model.save('E:\PyProjects\DataSet\FireAI\DeepLearning//FireAI005/FireAI005_Model2.h5') # 對於一個完整的模型,應該要保存這個
複製代碼
# 模型的加載,預測
from keras.models import load_model
saved_model=load_model('E:\PyProjects\DataSet\FireAI\DeepLearning//FireAI005/FireAI005_Model2.h5')

predicted=saved_model.predict_generator(newsample_generator)
print(predicted) # saved_model的結果和前面的model結果一致,表面模型正確保存和加載
複製代碼

此處獲得的結果和上面model預測的結果如出一轍,代表模型被正確保存和加載。

########################小**********結###############################

1,本篇文章講解了:準備一個簡單的小數據集,從數據集中創建數據流,將該數據流引入到Keras的模型中進行訓練,並使用訓練後的模型進行新圖片的預測,而後將模型進行保存,加載保存好的模型到內存中。

2,此處使用的模型是咱們本身搭建的,結構比較簡單,只有三層卷積層和兩層全鏈接層,故而模型的準確率不過高,並且此處因爲時間關係,我只訓練了20個epoch,訓練並無達到平臺期。

#################################################################


注:本部分代碼已經所有上傳到(個人github)上,歡迎下載。

相關文章
相關標籤/搜索