1-2,图片数据建模流程范例
一,准备数据
cifar2数据集为cifar10数据集的子集,只包括前两种类别airplane和automobile。
训练集有airplane和automobile图片各5000张,测试集有airplane和automobile图片各1000张。
cifar2任务的目标是训练一个模型来对飞机airplane和机动车automobile两种图片进行分类。
在tensorflow中准备图片数据的常用方案有两种,第一种是使用tf.keras中的ImageDataGenerator工具构建图片数据生成器。这里给的链接失效了,我自己找了个
介绍链接https://blog.csdn.net/baixue0729/article/details/96154225
第二种是使用tf.data.Dataset搭配tf.image中的一些图片处理方法构建数据管道。教学项目里着重介绍的是第二种
首先是这个读取图片的函数
def load_image(img_path,size = (32,32)):
label = tf.constant(1,tf.int8) if tf.strings.regex_full_match(img_path,".*automobile.*") \
else tf.constant(0,tf.int8)
img = tf.io.read_file(img_path)
img = tf.image.decode_jpeg(img) #注意此处为jpeg格式
img = tf.image.resize(img,size)/255.0
return(img,label)
tf.constant是创建常量的方法,格式是constant(value, dtype=None, shape=None, name="Const"),其中value是必须的,dtype指定常量的格式,shape指定这个常量的维度,这里dtype为tf.int8是因为标签只需要是0或者1,所以如果用int32会有一些浪费
tf.strings.regex_full_match(img_path,".*automobile.*")这部分,regex_full_match这个方法用于匹配字符串,格式是regex_full_match (input, pattern, name=None),pattern是需要匹配的通配符,如果input符合那么会返回一个input的shape的布尔型张量。如果图片路径里包含automobile这个字段那么标签为1,否则为0
tf.io.read_file则是把文件读成二进制流
image.resize用于对图片数据进行调整,size参数指定转换后图片的大小。通过打印的结果可以看到图片是以像素点RGB的形式存储的,即用三个数值来表示一格的颜色,除255是为了将这些数据映射到0-1,因为之后用的激活函数sigmod,在输入为0-1的时候变化效果较好。不过我在查找这方面资料的时候发现resize函数似乎是一个大坑,因此需要调整大小的话最好用其他方法如cv2调整图片大小,待调整好之后再读进来。
参考链接:https://zhuanlan.zhihu.com/p/163668681
实验代码:
label = tf.constant(1,tf.int8) if tf.strings.regex_full_match(img_path,".*automobile.*") \
else tf.constant(0,tf.int8)
print(label)
img = tf.io.read_file(img_path)
print(img)
img = tf.image.decode_jpeg(img) #注意此处为jpeg格式
print("after decode",img)
img = tf.image.resize(img,size)/255.0
print("after resize",img)
结果:
之后是这部分
ds_train = tf.data.Dataset.list_files("./data/cifar2/train/*/*.jpg") \
.map(load_image, num_parallel_calls=tf.data.experimental.AUTOTUNE) \
.shuffle(buffer_size=1000).batch(BATCH_SIZE) \
.prefetch(tf.data.experimental.AUTOTUNE)
ds_test = tf.data.Dataset.list_files("./data/cifar2/test/*/*.jpg") \
.map(load_image, num_parallel_calls=tf.data.experimental.AUTOTUNE) \
.batch(BATCH_SIZE) \
.prefetch(tf.data.experimental.AUTOTUNE)
接下来是Dataset.list_files方法,首先通过ctrl+鼠标左键点进函数去看函数介绍,发现这个是方法通过传入一个含通配符的路径参数来读取文件,
实验代码:
print("dataset", tf.data.Dataset.list_files("./data/cifar2/train/*/*.jpg"))
for each in tf.data.Dataset.list_files("./data/cifar2/train/*/*.jpg"):
print(each)
结果:
通过实验结果可以发现其实这个方法只是读取了所有符合文件的路径名
.map方法接收一个函数,Dataset中的每个元素都会被当作这个函数的输入,并将函数返回值作为新的Dataset
Num_parallel_calls是使用多线程进行数据通道处理,从而实现并行提升速度。
tf.data.experimental.AUTOTUNE是让电脑根据可用的CPU动态设置并行调用的cpu数量
.shuffle方法为打乱dataset中的元素,它有一个参数buffersize,表示打乱时使用的buffer的大小,不设置会报错。Buffersize=1时不打乱,越大的时候越乱
.batch方法相当于对数据集进行切块,不切的话一大块是无法一下子放进神经网络里进行训练的
.prefetch是相当于预读数据并留存缓冲区,它的参数也是buffersize,但和前面那个意义不一样,prefetch的buffersize决定缓冲区大小,shuffle的决定混乱程度
参考:map,shuffle,repeat,batch的总结https://blog.csdn.net/anshuai_aw1/article/details/105094548
不同buffersize的含义
https://blog.csdn.net/eartha1995/article/details/84930492
接下来是数据查看部分
plt.figure(figsize=(8,8))
for i,(img,label) in enumerate(ds_train.unbatch().take(9)):
ax=plt.subplot(3,3,i+1)
ax.imshow(img.numpy())
ax.set_title("label = %d"%label)
ax.set_xticks([])
ax.set_yticks([])
plt.show()
for x,y in ds_train.take(1):
print(x.shape,y.shape)
plt.figure(figsize=(8,8)) 这段代码是对plt的画布进行大小调整
参考:plt.figure的使用 https://blog.csdn.net/m0_37362454/article/details/81511427
Unbtach这个方法是将打包好的数据解压,具体体现在元素会少维度。函数介绍里是会将dataset里的内容分成多个元素,即[ [1, 2, 3], [1, 2], [1, 2, 3, 4] ] -> [1, 2, 3, 1, 2, 1, 2, 3, 4]。注意返回的是一个可迭代的对象,想要把内容取出来还需要遍历一遍
.take方法就是取其中的一部分元素
实验代码:
print("unbatch前",ds_train)
print("unbatch后",ds_train.unbatch())
结果:
Plt.subplot是画图方法的一种,其中的三个参数分别是行,列,和索引值。可以理解为3,3,1就代表了3*3格子中位于左上方的第一幅图
Imshow即显示图片,img.numpy是将img转化为numpy数组,其实我试了下不转化也能正常打印出图片,对比下可以发现tensor和numpy的img其实都是同纬度的数组,只不过tensor类型额外规定了shape和dtype
Settitle方法就是制定图片的标题
实验代码
print("img",img)
print("img.numpy",img.numpy())
结果:
Set_xticks和set_yticks都是设置刻度,后面跟着的列表即刻度的指标
实验代码
ax.set_xticks([0, 1,2,3,4,5,6,7])
ax.set_yticks([10,20])
实验结果:
二,定义模型
使用Keras接口有以下3种方式构建模型:使用Sequential按层顺序构建模型,使用函数式API构建任意结构模型,继承Model基类构建自定义模型。
教学项目里选择使用函数式API构建模型。
tf.keras.backend.clear_session() #清空会话
inputs = layers.Input(shape=(32,32,3))
x = layers.Conv2D(32,kernel_size=(3,3))(inputs)
x = layers.MaxPool2D()(x)
x = layers.Conv2D(64,kernel_size=(5,5))(x)
x = layers.MaxPool2D()(x)
x = layers.Dropout(rate=0.1)(x)
x = layers.Flatten()(x)
x = layers.Dense(32,activation='relu')(x)
outputs = layers.Dense(1,activation = 'sigmoid')(x)
model = models.Model(inputs = inputs,outputs = outputs)
model.summary()
layers.Input用于生产一个输入的静态张量,其中shape或者输入的tensor必须给出一个
layers.Conv2D用于构建卷积层,其中参数说明:https://woj.app/6491.html
后面带上的(inputs)则是用于制定这层输入的shape,如果缺失会提示报错
layers.MaxPool2D则是构建池化层,提供2维最大池化(max pooling)操作(最大池化就是3*3的格子里取最大值)
dropout 是在深度学习网络的训练过程中,对于神经网络单元,按照一定的概率将其暂时从网络中丢弃,这里的dropout rate=0.1就是指10%的神经元会被丢弃,为了防止过拟合
Flatten层用来将输入“压平”,即把多维的输入一维化,常用在从卷积层到全连接层的过渡。Flatten不影响batch的大小。
Dense是全连接层,相当于初学时候的add layer方法
其中参数参考:https://blog.csdn.net/sazass/article/details/86935556
三,训练模型
训练模型通常有3种方法,内置fit方法,内置train_on_batch方法,以及自定义训练循环。教学项目里使用了最常用也最简单的内置fit方法。
import datetime
import os
stamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
logdir = os.path.join('data', 'autograph', stamp)
## 在 Python3 下建议使用 pathlib 修正各操作系统的路径
# from pathlib import Path
# stamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
# logdir = str(Path('./data/autograph/' + stamp))
tensorboard_callback = tf.keras.callbacks.TensorBoard(logdir, histogram_freq=1)
model.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
loss=tf.keras.losses.binary_crossentropy,
metrics=["accuracy"]
)
history = model.fit(ds_train,epochs= 10,validation_data=ds_test,
callbacks = [tensorboard_callback],workers = 4)
把stamp和logfir打印一下,会发现这个是系统时间,可以用这个来区分每次训练的数据
tf.keras.callbacks.TensorBoard 这个方法用来记录训练过程,其中参数解释参考https://blog.csdn.net/a906958671/article/details/107183238
fit方法里,validation_data=ds_test是用于评估损失的数据集,callbacks里是指定存放数据路径的函数名,workers用于指定最大线程数
四,评估模型
from tensorboard import notebook
print("notebook.list")
notebook.list()
#在tensorboard中查看模型
print("notebook.start")
notebook.start("--logdir ./data/keras_model")
这个tensorboard是tf自带的可视化训练管理工具,通过浏览器访问可以看到训练的过程(注意上述的python3路径那部分代码,实测如果注释掉会报错,找不到tensorboard实例)
import pandas as pd
dfhistory = pd.DataFrame(history.history)
dfhistory.index = range(1,len(dfhistory) + 1)
dfhistory.index.name = 'epoch'
print(dfhistory)
这段代码也可以直接打印随着训练次数变化的loss
import matplotlib.pyplot as plt
def plot_metric(history, metric):
train_metrics = history.history[metric]
val_metrics = history.history['val_'+metric]
epochs = range(1, len(train_metrics) + 1)
plt.plot(epochs, train_metrics, 'bo--')
plt.plot(epochs, val_metrics, 'ro-')
plt.title('Training and validation '+ metric)
plt.xlabel("Epochs")
plt.ylabel(metric)
plt.legend(["train_"+metric, 'val_'+metric])
plt.show()
plot_metric(history,"loss")
plot_metric(history,"accuracy")
这段代码是和之前一样的用plt画图
也可以使用evaluate对数据进行评估
val_loss,val_accuracy = model.evaluate(ds_test,workers=4)
print(val_loss,val_accuracy)
结果:
0.16139143370091916 0.9345
五,使用模型
可以使用model.predict(ds_test)进行预测。
也可以使用model.predict_on_batch(x_test)对一个批量进行预测
model.predict(ds_test)
结果:
array([[9.9996173e-01],
[9.5104784e-01],
[2.8648047e-04],
...,
[1.1484033e-03],
[3.5589080e-02],
[9.8537153e-01]], dtype=float32)
for x,y in ds_test.take(1):
print(model.predict_on_batch(x[0:20]))
结果:
tf.Tensor(
[[3.8065155e-05]
[8.8236779e-01]
[9.1433197e-01]
[9.9921846e-01]
[6.4052093e-01]
[4.9970779e-03]
[2.6735585e-04]
[9.9842811e-01]
[7.9198682e-01]
[7.4823302e-01]
[8.7208226e-03]
[9.3951421e-03]
[9.9790359e-01]
[9.9998581e-01]
[2.1642199e-05]
[1.7915063e-02]
[2.5839690e-02]
[9.7538447e-01]
[9.7393811e-01]
[9.7333014e-01]], shape=(20, 1), dtype=float32)
六,保存模型
model.save('./data/tf_model_savedmodel', save_format="tf")
model_loaded = tf.keras.models.load_model('./data/tf_model_savedmodel')
model_loaded.evaluate(ds_test)