第四章 TensorFlow 神经网络的功能扩展

注:学习资源源自PKU《人工智能实践-TensorFlow2.0》课程

4.1 整体介绍

这一章节在第三章Keras搭建神经网络框架的基础上实现功能的扩展 !

  1. 自制数据集,解决应用输入问题
  2. 数据增强,扩充数据集
  3. 断点续训,存取模型
  4. 参数提取,把参数存入文本
  5. acc/loss的可视化
  6. 应用程序,实现给图识物

4.2 自制数据集

4.2.1 介绍

因为我们需要的训练数据集不一定都已经生成好可以直接下载,因此需要我们自己生成!

根本其实是替换这一句等号后面的内容~

(x_train,y_train),(x_test,y_test) = mnist.load_data()

举例:

我们已经有的txt文件有两列,分别是文件名称和对应的标签,

因此我可以用value[0]索引到每张图片,用value[1]就是对应的标签~

4.2.2 关键代码

第一步:导入模块

# 需要加载的模块
from PIL import Image
import numpy as np
import os

第二步:写出通过文件路径的转化函数

def generateds(path,txt):
  f = open(txt,'r')
  # 读取所有行
  contents = f.readlines()
  f.close()
  x,y_ = [],[]
  for content in contents:
    # 以空格分开
    value = content.split()
    # 分开得到以图片名为value[0],输出标签为value[1]
    # path 和 图片名拼接成图片的路径
    img_path = path + value[0]
    img = Image.open(img_path)
    # 将图片变为8位宽度的灰度值
    img = np.array(img.convert('L'))
    # 归一化
    img = img / 255.0
    x.append(img)
    y_.append(value[1])
    print("loading : " + content + " 张图片 ")
  
  # 将数据集变为np.array的格式
  x = np.array(x)
  y_ = np.array(y_)
  y_ = y_.astye(np.int64)
  return x,y_

第三步:判断数据集是否存在,如果存在就直接读取,否则需要生成数据集!

其实就是判断是否需要执行上面那步 generateds(path,txt) 操作~

注意:在这之前需要先定义已有文件,和保存生成文件的路径参数!

if os.path.exists(x_train_savepath) and os.path.exists(y_train_savepath) and os.path.exists(x_test_savepath) and os.path.exists(y_test_savepath):
  print("-----------------已经存在数据集了-------------------")
  x_train_save = np.load(x_train_savepath)
  y_train = np.load(y_train_savepath)
  x_test_save = np.load(x_test_savepath)
  y_test = np.load(y_test_savepath)
  x_train = np.reshape(x_train_save,(len(x_train_save),28,28))
  x_text = np.reshape(x_test_save,(len(x_test_save),28,28))
else:
  print("-----------------没有数据集,需要先创建---------------------")
  x_train,y_train = generateds(train_path,train_txt)
  x_text,y_text = generateds(test_path,test_txt)

  print("-----------------已经创建好数据集,需要将数据存下来-----------------")
  x_train_save = np.reshape(x_train,(len(x_train),-1))
  x_test_save = np.reshape(x_test,(len(x_test),-1))
  np.save(x_train_savepath,x_train_save)
  np.save(y_train_savepath,y_train)
  np.save(x_test_savepath,x_test_save)
  np.save(y_test_savepath,y_test)

4.3 扩充数据集

4.3.1 介绍

扩充数据集也就是对数据集的强化。

通俗来讲,举个例子:对于图像来说,就是应对由于拍照角度的不同引起的图形形变。

Tf2给了数据增强函数 tf.keras.preprocessing.image.ImageDataGenerator

4.3.2 关键代码

这里要在参数后面添上对应的参数~

image_gen_train = tf.keras.preprocessing.image.ImageDataGenerator(
    rescale = # 所有数据乘以该数值
    rotation_range = # 随机旋转角度范围
    width_shift_range = # 随机宽度偏移量
    height_shift_range = # 高度偏转
    horizontal_flip = # 是否随机水平反转
    zoom_range = # 随机缩放的范围 [1-n,1+n]
)
# 因为需要四维数据,因此需要reshape
image_gen_train.fit(x_train)

将代码应用到之前识别手写字算法的框架代码中:

# 添加模块
from tensorflow.keras.preprocessing.image import ImageDataGenerator

# 导入MNIST数据集
.....
# 归一化
.....

image_gen_train = tf.keras.preprocessing.image.ImageDataGenerator(
    rescale = 1. /1. ,
    rotation_range=45,
    width_shift_range = .15,
    height_shift_range = .15,
    horizontal_flip = True,
    zoom_range = 0.5
)
# reshape
image_gen_train.fit(x_train)

# Sequential搭建网络模型
.....

# compile,fit,summary
.....

这里由于数据集没变,因此得到的准确率还是和之前基本一致,但是当用自制数据集或换成有拍照角度的数据就会有差距了~

4.4 断点续训

4.4.1 介绍

这样来记录模型,使得不需要每次都重复训练,直接从上次训练开始即可~

4.4.2 关键代码

  1. 我们需要读取模型
  2. 判断是否模型已经存在(这里可以通过是否有生成的index索引判断)
  3. 保存模型,生成历史记录
# 首先读取模型
load_weights('文件路径名')

# 判断是否已经有保存的模型参数,如果已经有了,直接在已有基础上训练
# 只用检测有无索引index即可!
checkpoint_save_path = "./checkpoint/mnist.ckpt"
if os.path.exists(checkpoint_save_path + '.index'):
  print('-----------加载已经保存的模型-----------')
  model.load_weights(checkpoint_save_path)

# 保存模型
cp_callback = tf.keras.callbacks.ModelCheckpoint(
    filepath = 路径文件名,
    save_weights_only = True/False,
    save_best_only = True / False
)
history = model.fit(callbacks=[cp_callback])

保存模型的地方常习惯于这样写

cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_save_path,
                          save_weights_only=True,
                          save_best_only = True)
history = model.fit(x_train,y_train,batch_size = 32,epochs=5,validation_data=(x_test,y_test),validation_freq=1,callbacks=[cp_callback])

添加至原代码中(在compile后面):

# 以MNIST的作为baseline
# 用Sequential实现数字识别训练
import tensorflow as tf
import os

# 导入MNIST数据集
mnist = tf.keras.datasets.mnist
(x_train,y_train),(x_test,y_test) = mnist.load_data()
x_train,x_test = x_train/255.0,x_test/255.0
# 归一化

model = tf.keras.models.Sequential([
  tf.keras.layers.Flatten(),
  tf.keras.layers.Dense(128,activation='relu'),
  tf.keras.layers.Dense(10,activation='softmax'),        
])

model.compile(optimizer='adam',
       loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
       metrics = ['sparse_categorical_accuracy'])

# diff!!!
checkpoint_save_path = "./checkpoint/mnist.ckpt"
if os.path.exists(checkpoint_save_path + '.index'):
  print('-----------加载已经保存的模型-----------')
  model.load_weights(checkpoint_save_path)

cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_save_path,
                          save_weights_only=True,
                          save_best_only = True)
history = model.fit(x_train,y_train,batch_size = 32,epochs=5,validation_data=(x_test,y_test),validation_freq=1,callbacks=[cp_callback])

model.summary()

在第一次训练后,当前文件夹就会存在模型参数文件

文件夹结构

再重新开始训练,则会从上次的基础上进行~

模型已保存

4.5 参数提取

4.5.1 介绍

其实训练的本质就是参数的优化,这里我们需要将参数存下来便于应用,保存至txt文本中~

如果直接print,中间会有很多被省略号替换掉,所以需要设置输出格式~

同时,由于函数model.trainable_variables 已经返回了模型的参数,所以可以直接调用!

4.5.2 关键代码

(可以直接加在summary的末尾)

# 设置print的输出格式
# 设置打印所有内容
np.set_printoptions(threshold = np.inf) # np.inf 表示无限放大

print(model.trainable_variables)
file = open('./weights.txt','w')
for v in model.trainable_variables:
  file.write(str(v.name) + '\n')
  file.write(str(v.shape) + '\n')
  file.write(str(v.numpy()) + '\n')

flie.close()

这样最后就会把优化后的参数写入当前文件下的weights.txt中啦~

4.6 acc / loss 可视化

4.6.1 介绍

其实在model.fit时同时记录了训练集的loss&acc,还有测试集的loss&acc ~

之前是自己用一个空数组记录每次的准确率和loss,但是现在可以直接提取出来啦!

4.6.2 关键代码

提取出记录的数据

acc = history.history['sparse_categorical_accuracy']
val_acc = history.history['val_sparse_categorical_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']

画图!分成一行两列,分别画图~

plt.subplot(1,2,1) # 这里分割成一行两列,将图像画在第一列中
plt.plot(acc,label='Training Accuracy')
plt.plot(val_acc,label='Validation Accuracy')
plt.title('Training and Validation Accuracy')
plt.legend()

plt.subplot(1,2,2)
plt.plot(loss,label='Training Loss')
plt.plot(val_loss,label='Validation Loss')
plt.legend()
plt.show()

到这里,全部的代码,将上面的功能扩展都整合一下就是这样!

# 以MNIST的作为baseline
# 用Sequential实现数字识别训练
import tensorflow as tf
import os
import numpy as np
from matplotlib import pyplot as plt

np.set_printoptions(threshold = np.inf)

# 导入MNIST数据集
mnist = tf.keras.datasets.mnist
(x_train,y_train),(x_test,y_test) = mnist.load_data()
x_train,x_test = x_train/255.0,x_test/255.0
# 归一化

model = tf.keras.models.Sequential([
  tf.keras.layers.Flatten(),
  # 定义两层神经网络的神经元和激活函数
  tf.keras.layers.Dense(128,activation='relu'),
  tf.keras.layers.Dense(10,activation='softmax'),        
])

# 输入概率分布-》false,输出概率-》s_c_a
model.compile(optimizer='adam',
       loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
       metrics = ['sparse_categorical_accuracy'])

checkpoint_save_path = "./class4/checkpoint/mnist.ckpt"
if os.path.exists(checkpoint_save_path + '.index'):
  print('-----------加载已经保存的模型-----------')
  model.load_weights(checkpoint_save_path)

cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_save_path,
                          save_weights_only=True,
                          save_best_only = True)
history = model.fit(x_train,y_train,batch_size = 32,epochs=5,validation_data=(x_test,y_test),validation_freq=1,callbacks=[cp_callback])

model.summary()

print(model.trainable_variables)
file = open('./class4/weights.txt','w')
for v in model.trainable_variables:
  file.write(str(v.name) + '\n')
  file.write(str(v.shape) + '\n')
  file.write(str(v.numpy()) + '\n')

file.close()

# diff!! 新添加的画图~
acc = history.history['sparse_categorical_accuracy']
val_acc = history.history['val_sparse_categorical_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']

plt.subplot(1,2,1) # 这里分割成一行两列,将图像画在第一列中
plt.plot(acc,label='Training Accuracy')
plt.plot(val_acc,label='Validation Accuracy')
plt.title('Training and Validation Accuracy')
plt.legend()

plt.subplot(1,2,2)
plt.plot(loss,label='Training Loss')
plt.plot(val_loss,label='Validation Loss')
plt.legend()
plt.show()

结果:可以直观地显示Acc和Loss的变化率

结果图

到这里八股模型已经完善了,一般简单的模型都可以用这种代码框架搭建训练~

4.7 应用程序,给图识物

4.7.1 介绍

这里我们希望利用模型做真正的识别~

这里想用自己的手写图片作为输入啦!

我的手写数字

首先需要复现模型:

model = tf.keras.models.Sequential{[
  tf.keras.layers.Flatten(),
  tf.keras.layers.Dense(128,activation='relu'),
  tf.keras.layers.Dense(10,activation='softmax')
]}

然后从保存的模型优化后的参数文件中加载参数~

model.load_weights(model_save_path)

最后利用model.predict函数得到预测结果

result = model.predict(x_predict)

4.7.2 预处理

要注意的是,这里除了利用模型的部分,我们还需要做预处理,也就是将图片变成数组形式,且和训练的形式一样,黑底白字的样子~

(第一种方法)因为我们自己的图片是白底黑字的所有需要灰度值取反!

img = img.resize((28,28),Image.ANTIALIAS)
  img_arr = np.array(img.convert('L'))
  img_arr = 255 - img_arr

(另一种方法)可以用遍历二维数组的方法,选取一个合适的阈值,最终生成只有纯黑和纯白的对比度很高的图片~

 for i in range(28):
     for j in range(28):
       if img_arr[i][j] < 200:
         img_arr[i][j] = 255
       else:
         img_arr[i][j] = 0

4.7.3 完整代码

这里我已经用之前的训练生成优化数据啦,就不用重新训练了,换言之,套上下面的代码再改一改图片参数和路径,你也可以用我训练的模型来识别啦~

注:这里因为我不太会选择阈值,就用第一种预处理方法了~

from PIL import Image
import numpy as np
import tensorflow as tf

# 这是我原来训练好的模型参数存放的地方
model_save_path = '/content/class4/checkpoint/mnist.ckpt'

# 复现模型
model = tf.keras.models.Sequential([
  tf.keras.layers.Flatten(),
  tf.keras.layers.Dense(128,activation='relu'),
  tf.keras.layers.Dense(10,activation='softmax')
])

# 加载参数
model.load_weights(model_save_path)

# 一共需要判断的图片数量
preNum = int(input("input the total number of the pics : "))

for i in range(preNum):
  rootpath = '/content/drive/My Drive/Tensorflow/MNIST_FC (1)/'
  image_path = input("the name of the text pic : ")
  image_path = rootpath + image_path
  img = Image.open(image_path)
  img = img.resize((28,28),Image.ANTIALIAS)
  img_arr = np.array(img.convert('L'))

  # # 预处理
  img_arr = 255 - img_arr
        
  img_arr = img_arr / 255.0
  x_predict = img_arr[tf.newaxis,...]
  result = model.predict(x_predict)
  pred = tf.argmax(result,axis = 1)
  print('\n')
  tf.print("littlefisher!我识别的数字是 : ",pred)

结果:

判断的答案

就是这准确率着实有点低...

不管怎么说,这东西还是挺有意思的。

至此,已经将从数据采集到具体实现应用的简单操作学会啦~

原发布日期:2020-07-28 22:30
更改为:2020-11-04

Last modification:November 4th, 2020 at 09:00 pm
请赏我杯奶茶,让我快乐长肉