|
| 1 | +import random |
| 2 | +# import numpy as np |
| 3 | +from sklearn.model_selection import train_test_split |
| 4 | +from keras.preprocessing.image import ImageDataGenerator |
| 5 | +from keras.models import Sequential |
| 6 | +from keras.layers import Dense, Dropout, Activation, Flatten |
| 7 | +from keras.layers import Convolution2D, MaxPooling2D |
| 8 | +from keras.optimizers import SGD |
| 9 | +from keras.utils import np_utils |
| 10 | +from keras.models import load_model |
| 11 | +from keras import backend as K |
| 12 | +from load_data import load_dataset, resize_image, IMAGE_SIZE |
| 13 | + |
| 14 | + |
| 15 | +class Dataset: |
| 16 | + def __init__(self, path_name): |
| 17 | + # 训练集 |
| 18 | + self.train_images = None |
| 19 | + self.train_labels = None |
| 20 | + # 验证集 |
| 21 | + self.valid_images = None |
| 22 | + self.valid_labels = None |
| 23 | + # 测试集 |
| 24 | + self.test_images = None |
| 25 | + self.test_labels = None |
| 26 | + # 数据集加载路径 |
| 27 | + self.path_name = path_name |
| 28 | + # 当前库采用的维度顺序 |
| 29 | + self.input_shape = None |
| 30 | + |
| 31 | + # 加载数据集并按照交叉验证的原则划分数据集并进行相关预处理工作 |
| 32 | + def load(self, img_rows=IMAGE_SIZE, img_cols=IMAGE_SIZE, |
| 33 | + img_channels=3, nb_classes=2): |
| 34 | + # 加载数据集到内存 |
| 35 | + images, labels = load_dataset(self.path_name) |
| 36 | + train_images, valid_images, train_labels, valid_labels = train_test_split(images, labels, test_size=0.3, random_state=random.randint(0, 100)) |
| 37 | + _, test_images, _, test_labels = train_test_split( |
| 38 | + images, labels, test_size=0.5, random_state=random.randint(0, 100) |
| 39 | + ) |
| 40 | + # 当前的维度顺序如果为'channels_first',则输入图片数据时的顺序为:channels,rows,cols,否则:rows,cols,channels |
| 41 | + # 这部分代码就是根据keras库要求的维度顺序重组训练数据集 |
| 42 | + if K.image_data_format() == 'channels_first': |
| 43 | + train_images = train_images.reshape( |
| 44 | + train_images.shape[0], img_channels, img_rows, img_cols |
| 45 | + ) |
| 46 | + valid_images = valid_images.reshape( |
| 47 | + valid_images.shape[0], img_channels, img_rows, img_cols |
| 48 | + ) |
| 49 | + test_images = test_images.reshape( |
| 50 | + test_images.shape[0], img_channels, img_rows, img_cols |
| 51 | + ) |
| 52 | + self.input_shape = (img_channels, img_rows, img_cols) |
| 53 | + else: |
| 54 | + train_images = train_images.reshape( |
| 55 | + train_images.shape[0], img_rows, img_cols, img_channels |
| 56 | + ) |
| 57 | + valid_images = valid_images.reshape( |
| 58 | + valid_images.shape[0], img_rows, img_cols, img_channels |
| 59 | + ) |
| 60 | + test_images = test_images.reshape( |
| 61 | + test_images.shape[0], img_rows, img_cols, img_channels |
| 62 | + ) |
| 63 | + self.input_shape = (img_rows, img_cols, img_channels) |
| 64 | + # 输出训练集、验证集、测试集的数量 |
| 65 | + print(train_images.shape[0], 'train samples') |
| 66 | + print(valid_images.shape[0], 'valid samples') |
| 67 | + print(test_images.shape[0], 'test samples') |
| 68 | + # 我们的模型使用categorical_crossentropy作为损失函数,因此需要根据类别数量nb_classes将 |
| 69 | + # 类别标签进行one-hot编码使其向量化,在这里我们的类别只有两种,经过转化后标签数据变为二维 |
| 70 | + train_labels = np_utils.to_categorical(train_labels, nb_classes) |
| 71 | + valid_labels = np_utils.to_categorical(valid_labels, nb_classes) |
| 72 | + test_labels = np_utils.to_categorical(test_labels, nb_classes) |
| 73 | + # 像素数据浮点化以便归一化 |
| 74 | + train_images = train_images.astype('float32') |
| 75 | + valid_images = valid_images.astype('float32') |
| 76 | + test_images = test_images.astype('float32') |
| 77 | + # 将其归一化,图像的各像素值归一化到0~1区间 |
| 78 | + train_images /= 255 |
| 79 | + valid_images /= 255 |
| 80 | + test_images /= 255 |
| 81 | + self.train_images = train_images |
| 82 | + self.valid_images = valid_images |
| 83 | + self.test_images = test_images |
| 84 | + self.train_labels = train_labels |
| 85 | + self.valid_labels = valid_labels |
| 86 | + self.test_labels = test_labels |
| 87 | + |
| 88 | + |
| 89 | +# CNN网络模型类 |
| 90 | +class Model: |
| 91 | + def __init__(self): |
| 92 | + self.model = None |
| 93 | + |
| 94 | + # 建立模型 |
| 95 | + def build_model(self, dataset, nb_classes=2): |
| 96 | + # 构建一个空的网络模型,它是一个线性堆叠模型,各神经网络层会被顺序添加,专业名称为序贯模型或线性堆叠模型 |
| 97 | + self.model = Sequential() |
| 98 | + # 以下代码将顺序添加CNN网络需要的各层,一个add就是一个网络层 |
| 99 | + self.model.add(Convolution2D( |
| 100 | + 32, 3, 3, border_mode='same', input_shape=dataset.input_shape |
| 101 | + )) # 1 2维卷积层 |
| 102 | + self.model.add(Activation('relu')) # 2 激活函数层 |
| 103 | + self.model.add(Convolution2D(32, 3, 3)) # 3 2维卷积层 |
| 104 | + self.model.add(Activation('relu')) # 4 激活函数层 |
| 105 | + self.model.add(MaxPooling2D(pool_size=(2, 2))) # 5 池化层 |
| 106 | + self.model.add(Dropout(0.25)) # 6 Dropout层 |
| 107 | + self.model.add(Convolution2D(64, 3, 3, border_mode='same')) # 7 2维卷积层 |
| 108 | + self.model.add(Activation('relu')) # 8 激活函数层 |
| 109 | + self.model.add(Convolution2D(64, 3, 3)) # 9 2维卷积层 |
| 110 | + self.model.add(Activation('relu')) # 10 激活函数层 |
| 111 | + self.model.add(MaxPooling2D(pool_size=(2, 2))) # 11 池化层 |
| 112 | + self.model.add(Dropout(0.25)) # 12 Dropout层 |
| 113 | + self.model.add(Flatten()) # 13 Flatten层 |
| 114 | + self.model.add(Dense(512)) # 14 Dense层,又被称作全连接层 |
| 115 | + self.model.add(Activation('relu')) # 15 激活函数层 |
| 116 | + self.model.add(Dropout(0.5)) # 16 Dropout层 |
| 117 | + self.model.add(Dense(nb_classes)) # 17 Dense层 |
| 118 | + self.model.add(Activation('softmax')) # 18 分类层,输出最终结果 |
| 119 | + # 输出模型概况 |
| 120 | + self.model.summary() |
| 121 | + |
| 122 | + # 训练模型 |
| 123 | + def train( |
| 124 | + self, dataset, batch_size=20, nb_epoch=10, data_augmentation=True |
| 125 | + ): |
| 126 | + sgd = SGD(lr=0.01, decay=1e-6, |
| 127 | + momentum=0.9, nesterov=True) |
| 128 | + # 采用SGD+momentum的优化器进行训练,首先生成一个优化器对象 |
| 129 | + self.model.compile(loss='categorical_crossentropy', |
| 130 | + optimizer=sgd, |
| 131 | + metrics=['accuracy']) # 完成实际的模型配置工作 |
| 132 | + # 不使用数据提升,所谓的提升就是从我们提供的训练数据中利用旋转、翻转、加噪声等方法创造新的 |
| 133 | + # 训练数据,有意识的提升训练数据规模,增加模型训练量 |
| 134 | + if not data_augmentation: |
| 135 | + self.model.fit(dataset.train_images, |
| 136 | + dataset.train_labels, |
| 137 | + batch_size=batch_size, |
| 138 | + nb_epoch=nb_epoch, |
| 139 | + validation_data=( |
| 140 | + dataset.valid_images, dataset.valid_labels |
| 141 | + ), |
| 142 | + shuffle=True) |
| 143 | + # 使用实时数据提升 |
| 144 | + else: |
| 145 | + # 定义数据生成器用于数据提升,其返回一个生成器对象datagen,datagen每被调用一 |
| 146 | + # 次其生成一组数据(顺序生成),节省内存,其实就是python的数据生成器 |
| 147 | + datagen = ImageDataGenerator( |
| 148 | + featurewise_center=False, # 是否使输入数据去中心化(均值为0), |
| 149 | + samplewise_center=False, # 是否使输入数据的每个样本均值为0 |
| 150 | + featurewise_std_normalization=False, # 是否数据标准化(输入数据除以数据集的标准差) |
| 151 | + samplewise_std_normalization=False, # 是否将每个样本数据除以自身的标准差 |
| 152 | + zca_whitening=False, # 是否对输入数据施以ZCA白化 |
| 153 | + rotation_range=20, # 数据提升时图片随机转动的角度(范围为0~180) |
| 154 | + width_shift_range=0.2, # 数据提升时图片水平偏移的幅度(单位为图片宽度的占比,0~1之间的浮点数) |
| 155 | + height_shift_range=0.2, # 同上,只不过这里是垂直 |
| 156 | + horizontal_flip=True, # 是否进行随机水平翻转 |
| 157 | + vertical_flip=False) # 是否进行随机垂直翻转 |
| 158 | + # 计算整个训练样本集的数量以用于特征值归一化、ZCA白化等处理 |
| 159 | + datagen.fit(dataset.train_images) |
| 160 | + # 利用生成器开始训练模型 |
| 161 | + self.model.fit_generator(datagen.flow( |
| 162 | + dataset.train_images, dataset.train_labels, |
| 163 | + batch_size=batch_size |
| 164 | + ), |
| 165 | + samples_per_epoch=dataset.train_images.shape[0], |
| 166 | + nb_epoch=nb_epoch, validation_data=( |
| 167 | + dataset.valid_images, dataset.valid_labels |
| 168 | + ) |
| 169 | + ) |
| 170 | + MODEL_PATH = './my.face.model.h5' |
| 171 | + |
| 172 | + def save_model(self, file_path=MODEL_PATH): |
| 173 | + self.model.save(file_path) |
| 174 | + |
| 175 | + def load_model(self, file_path=MODEL_PATH): |
| 176 | + self.model = load_model(file_path) |
| 177 | + |
| 178 | + def evaluate(self, dataset): |
| 179 | + score = self.model.evaluate( |
| 180 | + dataset.test_images, dataset.test_labels, verbose=1 |
| 181 | + ) |
| 182 | + print("%s: %.2f%%" % (self.model.metrics_names[1], score[1] * 100)) |
| 183 | + |
| 184 | + # 识别人脸 |
| 185 | + def face_predict(self, image): |
| 186 | + # 依然是根据后端系统确定维度顺序 |
| 187 | + if K.image_data_format() == 'channels_first' and image.shape != ( |
| 188 | + 1, 3, IMAGE_SIZE, IMAGE_SIZE |
| 189 | + ): |
| 190 | + image = resize_image(image) |
| 191 | + # 尺寸必须与训练集一致都应该是IMAGE_SIZE x IMAGE_SIZE |
| 192 | + image = image.reshape((1, 3, IMAGE_SIZE, IMAGE_SIZE)) |
| 193 | + # 与模型训练不同,这次只是针对1张图片进行预测 |
| 194 | + elif K.image_data_format() == 'channels_last' and image.shape != ( |
| 195 | + 1, IMAGE_SIZE, IMAGE_SIZE, 3 |
| 196 | + ): |
| 197 | + image = resize_image(image) |
| 198 | + image = image.reshape((1, IMAGE_SIZE, IMAGE_SIZE, 3)) |
| 199 | + |
| 200 | + # 浮点并归一化 |
| 201 | + image = image.astype('float32') |
| 202 | + image /= 255 |
| 203 | + # 给出输入属于各个类别的概率,我们是二值类别,则该函数会给出输入图像属于0和1的概率各为多少 |
| 204 | + result = self.model.predict_proba(image) |
| 205 | + # 给出类别预测:0或者1 |
| 206 | + result = self.model.predict_classes(image) |
| 207 | + # 返回类别预测结果 |
| 208 | + return result[0] |
| 209 | + |
| 210 | + |
| 211 | +def faceTrainMain(myName): |
| 212 | + dataset = Dataset('./faceData/') |
| 213 | + dataset.load() |
| 214 | + model = Model() |
| 215 | + model.build_model(dataset) |
| 216 | + model.train(dataset) |
| 217 | + model.save_model(file_path='./faceData/' + myName + '.face.model.h5') |
| 218 | + model.evaluate(dataset) |
| 219 | + |
| 220 | + |
| 221 | +if __name__ == '__main__': |
| 222 | + dataset = Dataset('./faceData/') |
| 223 | + dataset.load() |
| 224 | + model = Model() |
| 225 | + model.build_model(dataset) |
| 226 | + # 先前添加的测试build_model()函数的代码 |
| 227 | + model.build_model(dataset) |
| 228 | + # 测试训练函数的代码 |
| 229 | + model.train(dataset) |
| 230 | +if __name__ == '__main__': |
| 231 | + dataset = Dataset('./faceData/') |
| 232 | + dataset.load() |
| 233 | + model = Model() |
| 234 | + model.build_model(dataset) |
| 235 | + model.train(dataset) |
| 236 | + model.save_model(file_path='./faceData/my.face.model.h5') |
| 237 | + |
| 238 | +if __name__ == '__main__': |
| 239 | + dataset = Dataset('./faceData/') |
| 240 | + dataset.load() |
| 241 | + # 评估模型 |
| 242 | + model = Model() |
| 243 | + model.load_model(file_path='./faceData/my.face.model.h5') |
| 244 | + model.evaluate(dataset) |
0 commit comments