#-*- coding: utf-8 -*-
import random
import keras
import numpy as np
from sklearn.model_selection import train_test_split
from keras.preprocessing.image import ImageDataGenerator
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras.optimizers import SGD
from keras.utils import np_utils
from keras.models import load_model
from keras import backend as K
from face_dataset import load_dataset, resize_image, IMAGE_SIZE
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE'
class Dataset:
def __init__(self, path_name):
#訓練集
self.train_images = None
self.train_labels = None
#驗證集
self.valid_images = None
self.valid_labels = None
#測試集
self.test_images = None
self.test_labels = None
#數據集加載路徑
self.path_name = path_name
#當前庫採用的維度順序
self.input_shape = None
#加載數據集並按照交叉驗證的原則劃分數據集並進行相關預處理工作
def load(self, img_rows = IMAGE_SIZE, img_cols = IMAGE_SIZE,
img_channels = 3, nb_classes = 2):
#加載數據集到內存
images, labels = load_dataset(self.path_name)
print 'images,labels'
print images,labels
train_images, valid_images, train_labels, valid_labels = train_test_split(images, labels, test_size = 0.3, random_state = random.randint(0, 100))
_, test_images, _, test_labels = train_test_split(images, labels, test_size = 0.5, random_state = random.randint(0, 100))
#當前的維度順序如果爲'th',則輸入圖片數據時的順序爲:channels,rows,cols,否則:rows,cols,channels
#這部分代碼就是根據keras庫要求的維度順序重組訓練數據集
if K.image_dim_ordering() == 'th':
train_images = train_images.reshape(train_images.shape[0], img_channels, img_rows, img_cols)
valid_images = valid_images.reshape(valid_images.shape[0], img_channels, img_rows, img_cols)
test_images = test_images.reshape(test_images.shape[0], img_channels, img_rows, img_cols)
self.input_shape = (img_channels, img_rows, img_cols)
else:
train_images = train_images.reshape(train_images.shape[0], img_rows, img_cols, img_channels)
valid_images = valid_images.reshape(valid_images.shape[0], img_rows, img_cols, img_channels)
test_images = test_images.reshape(test_images.shape[0], img_rows, img_cols, img_channels)
self.input_shape = (img_rows, img_cols, img_channels)
#輸出訓練集、驗證集、測試集的數量
print(train_images.shape[0], 'train samples')
print(valid_images.shape[0], 'valid samples')
print(test_images.shape[0], 'test samples')
#我們的模型使用categorical_crossentropy作爲損失函數,因此需要根據類別數量nb_classes將
#類別標籤進行one-hot編碼使其向量化,在這裏我們的類別只有兩種,經過轉化後標籤數據變爲二維
train_labels = np_utils.to_categorical(train_labels, nb_classes)
valid_labels = np_utils.to_categorical(valid_labels, nb_classes)
test_labels = np_utils.to_categorical(test_labels, nb_classes)
#像素數據浮點化以便歸一化
train_images = train_images.astype('float32')
valid_images = valid_images.astype('float32')
test_images = test_images.astype('float32')
#將其歸一化,圖像的各像素值歸一化到0~1區間
train_images /= 255.0
valid_images /= 255.0
test_images /= 255.0
self.train_images = train_images
self.valid_images = valid_images
self.test_images = test_images
self.train_labels = train_labels
self.valid_labels = valid_labels
self.test_labels = test_labels
#CNN網絡模型類
class Model:
def __init__(self):
self.model = None
#建立模型
def build_model(self, dataset, nb_classes = 2):
#構建一個空的網絡模型,它是一個線性堆疊模型,各神經網絡層會被順序添加,專業名稱爲序貫模型或線性堆疊模型
self.model = Sequential()
#以下代碼將順序添加CNN網絡需要的各層,一個add就是一個網絡層
self.model.add(Conv2D(32, (3, 3), padding='same',
input_shape = dataset.input_shape)) #1 2維卷積層
self.model.add(Activation('relu')) #2 激活函數層
self.model.add(Conv2D(32, (3, 3))) #3 2維卷積層
self.model.add(Activation('relu')) #4 激活函數層
self.model.add(MaxPooling2D(pool_size=(2, 2))) #5 池化層
self.model.add(Dropout(0.25)) #6 Dropout層
self.model.add(Conv2D(64, (3, 3), padding='same')) #7 2維卷積層
self.model.add(Activation('relu')) #8 激活函數層
self.model.add(Conv2D(64, (3, 3))) #9 2維卷積層
self.model.add(Activation('relu')) #10 激活函數層
self.model.add(MaxPooling2D(pool_size=(2, 2))) #11 池化層
self.model.add(Dropout(0.25)) #12 Dropout層
self.model.add(Flatten()) #13 Flatten層
self.model.add(Dense(512)) #14 Dense層,又被稱作全連接層
self.model.add(Activation('relu')) #15 激活函數層
self.model.add(Dropout(0.25)) #16 Dropout層
self.model.add(Dense(nb_classes)) #17 Dense層
self.model.add(Activation('softmax')) #18 分類層,輸出最終結果
#輸出模型概況
self.model.summary()
#訓練模型
def train(self, dataset, batch_size = 20, nb_epoch = 7, data_augmentation = True):
#sgd = SGD(lr = 0.01, decay = 1e-6,
#momentum = 0.9, nesterov = True) #採用SGD+momentum的優化器進行訓練,首先生成一個優化器對象
self.model.compile(loss='categorical_crossentropy',
#optimizer=sgd,
optimizer= 'ADAM',
metrics=['accuracy']) #完成實際的模型配置工作
#不使用數據提升,所謂的提升就是從我們提供的訓練數據中利用旋轉、翻轉、加噪聲等方法創造新的
#訓練數據,有意識的提升訓練數據規模,增加模型訓練量
if not data_augmentation:
self.model.fit(dataset.train_images,
dataset.train_labels,
batch_size = batch_size,
epochs = nb_epoch,
validation_data = (dataset.valid_images, dataset.valid_labels),
shuffle = True)
#使用實時數據提升
else:
#定義數據生成器用於數據提升,其返回一個生成器對象datagen,datagen每被調用一
#次其生成一組數據(順序生成),節省內存,其實就是python的數據生成器
datagen = ImageDataGenerator(
featurewise_center = False, #是否使輸入數據去中心化(均值爲0),
samplewise_center = False, #是否使輸入數據的每個樣本均值爲0
featurewise_std_normalization = False, #是否數據標準化(輸入數據除以數據集的標準差)
samplewise_std_normalization = False, #是否將每個樣本數據除以自身的標準差
zca_whitening = False, #是否對輸入數據施以ZCA白化
rotation_range = 20, #數據提升時圖片隨機轉動的角度(範圍爲0~180)
width_shift_range = 0.2, #數據提升時圖片水平偏移的幅度(單位爲圖片寬度的佔比,0~1之間的浮點數)
height_shift_range = 0.2, #同上,只不過這裏是垂直
horizontal_flip = True, #是否進行隨機水平翻轉
vertical_flip = False) #是否進行隨機垂直翻轉
#計算整個訓練樣本集的數量以用於特徵值歸一化、ZCA白化等處理
datagen.fit(dataset.train_images)
#利用生成器開始訓練模型
self.model.fit_generator(datagen.flow(dataset.train_images, dataset.train_labels,
batch_size = batch_size),
samples_per_epoch = dataset.train_images.shape[0],
epochs = nb_epoch,
validation_data = (dataset.valid_images, dataset.valid_labels))
MODEL_PATH = './me.face.model.h5'
def save_model(self, file_path):
self.model.save(file_path)
def load_model(self, file_path):
self.model = load_model(file_path)
def evaluate(self, dataset):
score = self.model.evaluate(dataset.test_images, dataset.test_labels, verbose = 1)
print("%s: %.2f%%" % (self.model.metrics_names[1], score[1] * 100))
#識別人臉
def face_predict(self, image):
#依然是根據後端系統確定維度順序
if K.image_dim_ordering() == 'th' and image.shape != (1, 3, IMAGE_SIZE, IMAGE_SIZE):
image = resize_image(image) #尺寸必須與訓練集一致都應該是IMAGE_SIZE x IMAGE_SIZE
image = image.reshape((1, 3, IMAGE_SIZE, IMAGE_SIZE)) #與模型訓練不同,這次只是針對1張圖片進行預測
elif K.image_dim_ordering() == 'tf' and image.shape != (1, IMAGE_SIZE, IMAGE_SIZE, 3):
image = resize_image(image)
image = image.reshape((1, IMAGE_SIZE, IMAGE_SIZE, 3))
#浮點並歸一化
image = image.astype('float32')
image /= 255.0
#給出輸入屬於各個類別的概率,我們是二值類別,則該函數會給出輸入圖像屬於0和1的概率各爲多少
result = self.model.predict_proba(image)
print('result:', result)
#給出類別預測:0或者1
result = self.model.predict_classes(image)
#返回類別預測結果
return result[0]
if __name__ == '__main__':
dataset = Dataset('data')
dataset.load()
#訓練模型,這段代碼不用,註釋掉
model = Model()
model.build_model(dataset)
#測試訓練函數的代碼
model.train(dataset)
model.save_model(file_path = 'model/me.face.model.h5')
#評估模型
model = Model()
model.load_model(file_path = 'model/me.face.model.h5')
model.evaluate(dataset)