目录
三.判断深度学习效果好坏(通过loss和val_loss比较)
五.model.evaluate和model.predict的区别
正文:
比如一个人马分类的例子
1.数据集准备
首先我们需要先下载人马数据集(如果是windows的话,先安装wget下载工具)
(注意wget下载后要放到项目工作路径下,例:)
#测试数据集的下载命令(在pycharm中的Terminal命令行下输入)
wget --no-check-certificate \ https://storage.googleapis.com/laurencemoroney-blog.appspot.com/validation-horse-or-human.zip
#训练数据集的下载命令
wget --no-check-certificate \ https://storage.googleapis.com/laurencemoroney-blog.appspot.com/horse-or-human.zip
然后把下载的数据移动到刚空文件夹tmp里,然后解压
在这里我们会遇到一个组件叫做imageDataGenerator
真实数据的特点
- 图片尺寸大小不一,需裁剪成一样大小
- 数据量比较大,不能一下子装入内存
- 经常需要修改参数,例如输出的尺寸,增补图像拉伸等
所有这些事情手工编程都可以做到,但是用imageDataGenerator比较省事。
2.模型建立
模型训练代码:
import tensorflow as tf
from tensorflow.keras.preprocessing.image import ImageDataGenerator
#创建两个数据生成器,指定scaling范围0~1
train_datagen = ImageDataGenerator(rescale=1/255) #数据生成器介绍详见下面注释一,
validataion_datagen = ImageDataGenerator(rescale=1/255)#其实在这里就是归一标准化
#指向训练数据文件夹
train_generator = train_datagen.flow_from_directory(#flow_from_directory参考注释二
'F:/deeplearning/tmp/horse-or-human',#训练数据所在文件夹
target_size=(150,150),#指定输出尺寸
batch_size=32,
class_mode='binary') #指定二分类
#指向测试数据文件夹
validation_generator = train_datagen.flow_from_directory(
'F:/deeplearning/tmp/validation-horse-or-human',
target_size=(150,150),
batch_size=32,
class_mode='binary')
#构建模型
model = tf.keras.models.Sequential([
tf.keras.layers.Conv2D(16,(3,3), activation='relu',input_shape=(150,150,3)),#16个3*3的过滤器,这是一个卷积层,详细参考https://keras.io/zh/layers/convolutional/
tf.keras.layers.MaxPooling2D(2,2),#最大池,对于空间数据的最大池化,详细参考https://keras.io/zh/layers/pooling/
tf.keras.layers.Conv2D(32,(3,3),activation='relu'),
tf.keras.layers.MaxPooling2D(2,2),
tf.keras.layers.Conv2D(64,(3,3),activation='relu'),
tf.keras.layers.MaxPooling2D(2,2),
tf.keras.layers.Flatten(),#这里不用写input_shape的原因是在第一个卷积层中就已经有过对特征图输入约束的声明了
tf.keras.layers.Dense(512,activation='relu'),
tf.keras.layers.Dense(1,activation='sigmoid')#因为是二分类,所以用sigmoid
])
#编译模型
from tensorflow.keras.optimizers import RMSprop
'''
常见的优化方法有SGD,Momentum,AdaGrad,RMSProp,Adam
RMSprop是一种自适应学习率方法。RMSprop仅仅是计算对应的平均值,算法学习率下降相对AdaGrad较为缓慢吧
'''
model.compile(loss='binary_crossentropy',
optimizer=RMSprop(lr=0.001),
metrics=['accuracy'])
'''
损失函数(loss)是用来估量模型的预测值f(x)与真实性Y的不一致程度,损失函数越小,一般就代表模型的鲁棒性越好,正是损失函数指导了模型的学习。损失函数还是比较多,这里就不赘述了可以参考https://www.cnblogs.com/smuxiaolei/p/8662177.html
'''
#训练模型
history = model.fit( #在CV-1已经介绍过这个函数
train_generator,
epochs=15,
verbose=1,
validation_data = validation_generator,
validation_steps=8)
#模型保存
model.save('E:/my_model.h5')
#训练后的模型精度
import matplotlib.image as mping
import matplotlib.pyplot as plt
'''
loss是训练损失,val_loss是测试损失,
accuracy是训练数据的精度,val_accuracy是测试数据的精度
深度学习效果好坏如何通过这两个参数发现,请看下面注释三
'''
accuracy = history.history['accuracy']
var_accuracy = history.history['val_accuracy']
loss = history.history['loss']
val_loss=history.history['val_loss']
'''
举例:range(5)能产生一组0到5的list
这里的accuracy是一个数组,len能拿到这个数组的长度
range(数组)能拿到要画的误差图的横坐标
'''
epochs=range(len(accuracy))
plt.plot(epochs, loss,'r',"Training loss")
plt.plot(epochs,val_loss,'b',"Validation Loss")
plt.figure()
plt.show()
这是跑完之后模型训练的结果。
这个图将成为我们分析模型是否合理的重要指标(可能陷入局部最优,过拟合等等),所以这个图应该好好分析。
3.模型调用
从图中我们看出训练的效果还是可以接受的。但是也不是很理想,因为我们建立的神经网络模型参数还没有进行优化,在CV-3文章中将对参数进行优化。
接下来我们再对模型进行调用(在之前代码中已经进行了模型的保存)
#模型预测
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.keras.preprocessing import image
from tensorflow import keras
import os
#调用模型
#重新创建完全相同的模型,包括其权重和优化程序
model = keras.models.load_model('E:/my_model.h5')
#显示网络结构
model.summary()
#验证模型
path1 = 'F:/deeplearning/tmp/validation-horse-or-human/horses/horse1-000.png'
path2 = 'F:/deeplearning/tmp/validation-horse-or-human/humans/valhuman01-00.png'
path3 = 'E:/289821395323715657.jpg'
path4 = 'E:/data.jpg'
img = image.load_img(path4,target_size=(150,150)) #加载图像
print(type(img)) #这里打印的结果是<class 'PIL.Image.Image'>
x = image.img_to_array(img) #对于x的变换请详看注释四
x = np.expand_dims(x,axis=0)
pclass = model.predict(x) #对于predict和evaluate的区别请详看注释五
print(pclass)#查看打印出来的预测结果
if pclass >= 1:
print('我们识别出来的是个'+'人')
else:
print('我们识别出来的是个' + '马')
plt.imshow(img)
plt.show()
在这里准备了四张图片,经过验证,我们的模型还是能够达到理想效果的。
梯度下降优化方法比较的论文:
An overview of gradient descent optimization algorithms
注释:
一.ImageDataGenerator介绍
keras.preprocessing.image.ImageDataGenerator(featurewise_center=False,samplewise_center=False,featurewise_std_normalizition=False,samplewise_std_normalization=False,zca_whitening=False,zca_epsilon=1e-6,rotation_range=0.,width_shift_range=0.,height_shift_range=0.,shear_range=0.,zoom_range=0.,channel_shift_range=0.,fill_mode='nearest',cval=0.,horizontal_filp=False,rescale=None,preprocessing_function=None,data_format=K.image_data_format())
作用:用以生成一个batch的图像数据,支持实时数据提升。训练时该函数会无限生成数据,直到达到规定的epoch次数为止。
参数:
- featurewise_center:布尔值,使输入数据集中去中心化(均值为0),按feature执行
- samplewise_center:布尔值,使输入数据的每个样本均值为0
- featurewise_std_normalization:布尔值,将输入除以数据集的标准差以完成标准化,按feature执行
- samplewist_std_normalization:布尔值,讲输入的每个样本除以其自身的标准差
- zca_whitening:布尔值,对输入数据施加ZCA白化
- zca_epsilon:ZCA使用的eposilon,默认1e-6
- rotation_range:整数,数据提升时图片随机转动的角度
- width_shift_range:浮点数,图片宽度的某个比例,数据提升时图片水平偏移的幅度
- height_shift_range:浮点数,图片高度的某个比例,数据提升时图片竖直偏移的幅度
- shear_range:浮点数,剪切强度(逆时针方向的剪切变换角度),是用来进行剪切变换的程度
- zoom_range:浮点数或形如[lower,upper]的列表,随机缩放的幅度,若为浮点数,则相当于[lower,upper]=[1-zoom_range,1+zoom_range],用来进行随机的放大
- channel_shift_range:浮点数,随机通道偏移的幅度
- fill_mode: 'constant','nearest','reflect'或'wrap'之一,当进行变换时超出边界的点将根据本参数给定的方法进行处理
- cval:浮点数或整数,当fill_mode=constant时,指定要向超出边界的点填充的值。
- horizontal_filp:布尔值,进行随机水平翻转。随机的对图片进行水平翻转,这个参数适用于水平翻转不影响图片语义的时候
- vertical_filp:布尔值,进行随机竖直翻转。
- rescale:值将在执行其他处理前乘到整个图像上,我们的图像在RGB通道都是0~255的整数,这样的操作可能使图像的值过高或过低,所以我们将这个值定为0~1之间的数。
- preprocessing_funtion:将被应用于每个输入的函数。该函数将在任何其他修改之前运行。该函数接受一个参数,为一张图片(秩为3的numpy array),并且输出一个具有相同shape的numpy array
- date_format:字符串,"channel_first"或“channel_last"之一,代表图像的通道维的位置。该参数是Keras 1.x中的image_dim_ordering,”channel_last"对应原本的”tf","channel_first"对应原本的”th"。以128*128的RGB图像为例,“channel_first"应将数据组织为(3,128,128),而”channel_last"应将数据组织为(128,128,3)。该参数的默认值是~/.keras/keras.json中设置的值;若从未设置过,则为"channel_last"。
例子:
dategen = ImageDateGenerator( rotation_range=10,#图片随机翻转的角度 width_shift_range=0.2,#图片随机水平偏移的幅度 height_shift_range=0.2,#图片随机竖直偏移的幅度 rescale=1./255,#执行其他处理前乘到整个图象上 shear_range=0.2,#剪切强度 zoom_range=0.2,#随机放大 horizontal_filp=True,#随机水平翻转 fill_mode='nearest')
二.flow_from_directory介绍
flow_from_directory(directory)
作用:以文件夹路径为参数,生成经过数据提升/归一化后的数据,在一个无限循环中无限产生batch数据
参数:
- directory:目标文件夹路径,对于每一个类该文件夹都要包含一个子文件夹,子文件夹中任何PNG、BNP、PPM的图片都会被生成器使用
- target_size:整数tuple,默认为(256,256),图像将被resize成该尺寸
- color_mode:颜色模式,为"grayscale","rgb"之一,默认为"rgb",代表这些图片是否会被转换为单通道或三通道的图片
- classes:可选参数,为子文件夹的列表,如['dogs','cats']默认为None,若未提供,则该类别列表将从directory下的子文件夹名称/结构自动推断。每一个子文件夹都会被认为是一个新的类。(类别顺序将按照字母表顺序映射到标签值)。通过属性class_indices可获得文件夹名与类的序号的对应字典
- class_mode:"categorical","binary","sparse"或None之一,默认为”categorical",该参数决定了返回的标签数组的形式,"categorical"会返回2D的one-hot编码标签(one hot编码进行数据的分类更准确,许多机器学习算法无法直接用于数据分类。数据的类别必须转换成数字,对于分类的输入和输出变量都是一样的,当输出变量使用one-hot编码时,它可以提供比单个标签更准确的一组预测),"binary"返回1D的二值标签,"sparse"返回1D的整数标签,如果为None则不返回任何标签,生成器将仅仅生成batch数据,这种情况在使用model.predict_generator()和model.evaluate_generator()等函数时会用到。
- batch_size:batch数据的大小,默认32
- shuffle:是否打乱数据,默认为True
- seed:可选参数,打乱数据和进行变换时的随机数种子
- save_to_dir:None或字符串,该参数能让你将提升后的图片保存起来,用以可视化
- save_prefix:字符串,保存提升后图片时使用的前缀,仅当设置了save_to_dir时生效
- save_format:"png"或"jpeg"之一,指定保存图片的数据格式,默认"jpeg"
- flollow_links:是否访问子文件夹中的软链接
例子:
from tensorflow.keras.preprocessing.image import ImageDataGenerator datagen = ImageDataGenerator( rotation_range=40, width_shift_range=0.2, height_shift_range=0.2, shear_range=0.2, zoom_range=0.2, horizontal_flip=True, fill_mode='constant' ) gener = datagen.flow_from_directory(r'F:\deeplearning\tmp\horse-or-human', batch_size=3, shuffle=False, save_to_dir=r'F:\deeplearning\tmp\train_result', save_prefix='trans_', save_format='jpg' ) for i in range(3): gener.next()
结果:
结果解析:
之前
与之前的相比,这个数据生成器确实把图形做了一些随机变换(注意我们没打乱顺序,方便比较变换前和变换后),因为batch_size是3,range是3,所以结果也应该是9张,然后我们发现变换后的结果确实是9张。
注意:
directory不需要你写那种directory='你的数据目录',直接写数据目录即可。
三.判断深度学习效果好坏(通过loss和val_loss比较)
train loss比test loss还高的话,一般是训练集的样本不足够,使得我们无法获得充分的图像特征,而测试集样本比较少,准确度容易高。
train loss不断下降,test loss不断下降,说明网络仍在学习(最好的)
train loss不断下降,test loss趋于不变,说明网络过拟合;(max pool或者正则化)
train loss趋于不变,test loss不断下降。说明数据集100%有问题(检查正则化)
train loss趋于不变,test loss趋于不变,说明学习遇到瓶颈,需要减小学习率或批量数目(减小学习率)
train loss不断上升,test loss不断上升,说明网络结构设计不当,训练超参数设置不当,数据集经过清洗等问题(最不好的情况)
四.测试图片格式转换问题
在这里我们通过load_img方法(指定图像路径读取图像)读取后的图像是PIL Image的实例,并不是Numpy数据(我们可以predict的输入数据需要是Numpy)这时候我们就需要通过img_to_array()方法转化为Numpy,读取完图像是三维向量,而模型输入要求是四维,所以我们需要通过expand_dims()方法扩展维度。
1.img_to_array()
作用:把numpy矩阵中的整数转换成浮点数
举例:
转换前: [[ 3 3 3 ... 5 5 5] [ 3 3 3 ... 5 5 5] [ 3 3 3 ... 5 5 5] ... [ 3 3 3 ... 12 11 10] [ 3 3 3 ... 12 11 10] [ 3 3 3 ... 12 11 10]] 转换后: [[ 3. 3. 3. ... 5. 5. 5.] [ 3. 3. 3. ... 5. 5. 5.] [ 3. 3. 3. ... 5. 5. 5.] ... [ 3. 3. 3. ... 12. 11. 10.] [ 3. 3. 3. ... 12. 11. 10.] [ 3. 3. 3. ... 12. 11. 10.]]
接下来的两个方法是对数组的维度进行增删操作,以使数组维度相匹配。
2.expand_dims()
作用: 在指定轴axis上增加数组a的一个维度,即在第"axis"维,加一个维度出来,原先在"axis"左边的维度保持位置不变,在“axis"右边的维度整体右移。
注意:该函数不改变输入数组a,而是产生一个新数组,新数组中的元素与原数组完全相同
举例:
例子1 代码: import numpy as np a = np.array([[[1,2,3],[4,5,6]]]) print(a.shape)#返回矩阵的规模,在这里是1个二维数组,二维数组里有两个一维数组,一维数组中有三列,所以是 1 2 3 print('------------------------------------------------') # np.expand_dims(a, axis=0)表示在0位置添加数据 b = np.expand_dims(a,axis=0) print(b.shape) print('------------------------------------------------') # np.expand_dims(a, axis=1)表示在1位置添加数据 c = np.expand_dims(a,axis=1) print(c.shape) print('------------------------------------------------') # np.expand_dims(a, axis=2)表示在2位置添加数据 d = np.expand_dims(a,axis=2) print(d.shape) print('------------------------------------------------') # np.expand_dims(a, axis=3)表示在3位置添加数据 e = np.expand_dims(a,axis=3) print(e.shape) print('------------------------------------------------') 运行结果: (1, 2, 3) ------------------------------------------------ (1, 1, 2, 3) ------------------------------------------------ (1, 1, 2, 3) ------------------------------------------------ (1, 2, 1, 3) ------------------------------------------------ (1, 2, 3, 1) ------------------------------------------------ 例子2 假设三维数组a的shape是(m, n, c),则 np.expand_dims(a, axis=0)表示在a的第一个维度上增加一个新的维度,而其他维度整体往右移,最终得到shape为(1, m, n, c)的新数组,新数组中的元素与原数组完全相同。 代码 import numpy as np a = np.reshape(list(range(24)), (2, 3, 4)) a_new = np.expand_dims(a, axis=0) print('a =', a) print('a_new =', a_new) print('a.shape = ', a.shape) print('a_new.shape = ', a_new.shape) 输出结果: a = [[[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11]] [[12 13 14 15] [16 17 18 19] [20 21 22 23]]] a_new = [[[[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11]] [[12 13 14 15] [16 17 18 19] [20 21 22 23]]]] a.shape = (2, 3, 4) a_new.shape = (1, 2, 3, 4)
3.np.squeeze()
squee(a,axis=None)
作用:删除输入数组a中维度为1的维度,并返回新的数组,新数组的元素与原数组a完全相同
举例:
>>> a = np.array([[[0], [1], [2]]]) >>> a.shape (1, 3, 1) # 未指定axis,则删除所有维度为1的维度 >>> np.squeeze(a) [0, 1, 2] >>> np.squeeze(a).shape (3,) # 指定axis=0,则删除该维度 >>> np.squeeze(a, axis=0) [[0] [1] [2]] >>> np.squeeze(a, axis=0).shape (3, 1) # 指定axis=2,则删除该维度 >>> np.squeeze(a, axis=2) [[0 1 2]] >>> np.squeeze(a, axis=2).shape (3, 1) # 同时指定axis=0和axis=2,则删除这两个维度 >>> np.squeeze(a, axis=(0, 2)) [0 1 2] >>> np.squeeze(a, axis=(0, 2)).shape (3,) # 对于指定的axis,其维度必定为1,否则会报错 >>> np.squeeze(a, axis=1).shape Traceback (most recent call last): ... ValueError: cannot select an axis to squeeze out which has size not equal to one
五.model.evaluate和model.predict的区别
1.model.evaluate
Model.evaluate( x=None, y=None, batch_size=None, verbose=1, sample_weight=None, steps=None, callbacks=None, max_queue_size=10, workers=1, use_multiprocessing=False, return_dict=False, )
参数:
- x:输入数据
- y:输入标签
- batch_size:批次大小
- verbose:0不显示进度条,1为显示进度条
- sample_weight:测试样本的可选Numpy权重数组,用于对损失函数加权
- steps:样本批次
- callbacks:评估期间需要应用的回调列表
- max_queue_size:生成器队列的最大大小
- workers:执行期间使用的进程数
- use_multiprocessing:如果为True,则使用基于进程的线程
- return_dict:如果为True,loss和metric结果将会作为词典(dict)返回,如果为False,他们 将会被返回为一个list。
返回值:
- 损失值:网络在训练数据上的损失(预测值和实际值之间的差距),该值和编译模型时选择的损失有关
- 精度:准确率(成功数量与总数据量的比值)
- 返回格式:['loss','accuracy']
2.model.predict
Model.predict( x, batch_size=None, verbose=0, steps=None, callbacks=None, max_queue_size=10, workers=1, use_multiprocessing=False, )
参数:
- x:输入数据
- 其他参数同上
返回值:输出输入数据的预测结果,需要自己手动比较