PowerSensor AI教程1.1 – 数字识别 – tf模型训练

写在前面

  1. 需要懂一点机器学习
  2. 需要懂一点常用的shell指令
  3. 最好电脑配有显卡,训练过程会快很多
  4. 模型训练不要使用虚拟机,会非常慢
  5. windows和linux都可以用来训练模型,不要歧视windows
  6. 本章的例程在下面的百度网盘可以下载到:
    链接:https://pan.baidu.com/s/1-ThiQVzCazyY-5ZnqMYfXw
    提取码:bg7c

背景知识

  1. 人工神经网络
    人工神经网络(Artificial Neural Network,即ANN ),是20世纪80 年代以来人工智能领域兴起的研究热点。它从信息处理角度对人脑神经元网络进行抽象, 建立某种简单模型,按不同的连接方式组成不同的网络。在工程与学术界也常直接简称为神经网络或类神经网络。神经网络是一种运算模型,由大量的节点(或称神经元)之间相互联接构成。每个节点代表一种特定的输出函数,称为激励函数(activation function)。每两个节点间的连接都代表一个对于通过该连接信号的加权值,称之为权重,这相当于人工神经网络的记忆。网络的输出则依网络的连接方式,权重值和激励函数的不同而不同。而网络自身通常都是对自然界某种算法或者函数的逼近,也可能是对一种逻辑策略的表达。

  2. 深度学习
    深度学习是机器学习中一种基于对数据进行表征学习的算法。目前常说的深度学习技术主要指利用深层(5层或以上)的神经网络对一个模型(比如手写数字识别)进行逼近的技术。深度学习可以看成一个万能逼近器,只要提供足够多的样本和标签,通过训练,深度学习就可以代替人类,完成生活中的分类、跟踪等任务。

  3. 深度学习的模型、结构、权值
    我们常说的深度学习的模型主要包含神经网络的结构和权值两个部分,为什么要把这两个参数分开呢?因为一般网络结构具有较好的泛化能力,就是说识别水果和识别数字可以使用同一个网络结构,但是权值往往具有针对性,同一个结构的神经网络通过载入不同的权值来实现不同的识别任务。下文所说的模型编译指:当网络训练好要对模型进行部署时,把网络权值固化到网络结构里,并编译成powersensor能够运行的机器码。

  4. tensorflow
    tensorflow是google开发的一个机器学习库,支持python等多种编程语言。虽然深度学习本身的结构复杂,很难从零开始编程实现,但是通过tensorflow,哪怕一个是中学生也可以通过几十行代码完成简单的深度学习网络的实现和训练。

  5. dpu和dnndk
    DPU是xilinx推出的一个适用于FPGA的用于深度学习加速的IP核,为了方便用户将自己训练的模型部署到DPU中,xilinx推出了dnndk来完成模型编译(将用户模型编译成DPU能运行的模型)。可以说,DPU是硬件IP核,而dnndk是软件管理的函数库。由于dnndk只能在linux平台下运行, 为了方便大家,我们准备了一个按照好dnndk的vmware虚拟机,这样用户就可以在windows下通过虚拟机完成模型编译。

  6. Anaconda
    Anaconda是一个免费开源[5]PythonR语言的发行版本,用于计算科学数据科学机器学习大数据处理预测分析),Anaconda致力于简化包管理和部署。Anaconda的包使用软件包管理系统Conda[6]进行管理。(来自wiki百科)。
    简单的说,Anaconda是一个python的包提供平台,软件仓库,同时配有conda管理工具。

  7. conda
    conda是一个开源跨平台的包管理和环境管理系统,常用于python虚拟环境的搭建与管理。conda允许用户从不同的源下载和升级软件包。conda使得用户可以同时安装多个python虚拟环境,通过激活的方式,在不同的任务中使用不同的虚拟环境。

powersensor ai案例的开发流程

在了解了以上的基础知识后,如果要把一个深度学习模型部署到powersensor上运行需要进行以下流程。
– 首先,PC环节,使用tensorflow(本文使用的是tensorflow2.0)完成深度学习模型的设计和训练,并将结构和权值分开存储。
– 然后,dnndk环节,在虚拟机下完成模型权值固化,并使用dnndk将tensorflow模型编译成dpu能够运行的模型(elf模型)。
– 最后,edge环节,在powersensor下编写调度程序,完成最终的深度学习任务。
本章主要介绍第一步,pc环节。

软件安装与启动

软件安装(只需要进行一次)

  1. 安装anaconda,下载地址(去我们的百度网盘下载):https://www.anaconda.com/products/individual

  2. 启动anaconda prompt,这个是终端,大部分包的安装等操作会在终端进行

  3. 新建一个虚拟的Anaconda环境。命令过程需要输入y确认,这里都把这个步骤略过。

# 新建一个虚拟环境,名为ps
conda create -n ps
# 激活ps环境,每次启动prompt都需要激活,激活后左侧的标识会变成ps
(base) C:\Users\xxx>conda activate ps

(ps) C:\Users\xxx>
  1. 逐条输入以下指令完成python软件包的安装

熟悉的用户可以通过ananconda换源来提升在国内的下载速度,教程见https://mirror.tuna.tsinghua.edu.cn/help/anaconda/

conda install opencv
conda install matplotlib
conda install jupyter

# 没有显卡的安装这个tf
conda install tensorflow
# 有显卡的安装这个tf
conda install tensorflow-gpu

启动jupyter(每次启动需要进行一次)

# 首先激活环境
conda activate ps
# 启动jupyter,路径填powersensor_flowerclassfication所在盘的盘符(如e:)
jupyter-notebook --notebook-dir 路径

模型训练与保存

模型训练

  1. 解压下载到的ministNumber_classificaiton压缩包(或者从Github上获取,从Github上可以获取到最新的程序,但是数据集需要额外下载),可以看到以下目录结构

dir
其中,dataset中存放的是用于训练和测试的数据集,dataset_valid存放的是用于验证的数据集。pc存放的是tensorflow相关的训练文件,用于训练模型。dnndk存放的是需要拷贝到虚拟机编译配置文件。edge存放的是在powersensor运行的调度文件。

  1. 启动anaconda,激活上面建好的环境,启动jupyter。进入pc文件夹,打开modelTrain.ipython,按照记事本的提示逐个运行。

  2. 首先是包含头文件和重要的参数初始化,如果没有GPU,请把GPU下面的4行注释掉

import cv2
import numpy as np
import os
import tensorflow as tf
from tensorflow import keras
import random
import time
import matplotlib.pyplot as plt

# 有GPU才能打印
gpus = tf.config.experimental.list_physical_devices(device_type='GPU')
cpus = tf.config.experimental.list_physical_devices(device_type='CPU')
print(gpus, cpus)
for gpu in gpus:
    tf.config.experimental.set_memory_growth(gpu, True)

# 让Matplotlib正确显示中文
import matplotlib as mpl
mpl.style.use('seaborn')
mpl.rcParams['font.sans-serif']=['SimHei']   # 用黑体显示中文
mpl.rcParams['axes.unicode_minus']=False     # 正常显示负号

# 训练用的图像尺寸
img_size_net = 28
# 训练的batch大小
batch_size = 32
# 数据库路径
dataset_path = '../dataset/minist-number/'
# 存放过程和结果的路径
run_path = './run/'
if not os.path.exists(run_path):
    os.mkdir(run_path)
wordlist = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']

训练用的图像尺寸指输入给神经网络的图像的尺寸,决定模型的入口大小,这一讲用的数据集是预处理好的,因此原图像尺寸与训练用的图像尺寸是一致的,但在其他问题里面可能不一样,需要进行预处理。神经网络的训练一次往往使用多个样本,训练的batch大小描述的就是这个大小,一般去2的幂次。数据库的路径,请确认在dataset\minist-number下有4个训练数据。run路径会临时生成,存放的是训练完的结果(需要拷贝到虚拟机里)。wordlist是样本的标签,比如数字识别就是0-9,水果识别就是苹果、梨。。。

  1. 数据集读取与测试,在训练前,我们需要加载数据集,并把数据集分成训练集和测试集,训练集用于训练神经网络,测试集用于测试训练效果(不参与训练)。这里会随机打印几张样本,用来验证读取是否正确。
# 1. 读取数据集
(train_images, train_labels) = load_mnist(dataset_path, 'train')
(test_images, test_labels) = load_mnist(dataset_path, 't10k')

# 2. 图像预处理
train_images = train_images.reshape((-1,28,28,1)) / 255.
test_images = test_images.reshape((-1,28,28,1)) / 255.

# 3. 统计各种训练集中各种样本的数量
print('各个样本的数量:')
l = []
for x in train_labels:
    l.append(wordlist[x])
plt.hist(l, rwidth=0.5)
plt.show()

# 4. 随机打印8个测试图像
fig, ax = plt.subplots(5, 2)
fig.set_size_inches(15,15)
for i in range(5):
    for j in range(2):
        l = random.randint(0, len(test_labels))
        ax[i, j].imshow(test_images[l, :, :, 0])
        ax[i, j].set_title(wordlist[test_labels[l]])
plt.tight_layout()
  1. 网络结构设计,这里设计的是一个较为简单的6层神经网络,包含2个卷积层(用于提取特征),2个池化层(用于抽象特征),2个全连接层,全连接层用于特征的合并和类别的预测。因为我们有0-9共10个类别需要预测,所以输出层的神经元数量是10。compile是将设计的神经网络实现处理,这里我们使用了Adam作为优化器,这种优化器具有动量的特性,能够跃出一些常见的极小值点。lr是学习率。
model = keras.Sequential([
    #keras.layers.Flatten(input_shape=(128, 128, 3)),
    keras.layers.Conv2D(32, (3,3), padding="same", activation=tf.nn.relu, input_shape=(img_size_net, img_size_net, 1), name='x_input'),
    keras.layers.MaxPooling2D(pool_size=(2,2)),
    keras.layers.Conv2D(64, (3,3), padding="same", activation=tf.nn.relu, input_shape=(img_size_net, img_size_net, 1)),
    keras.layers.MaxPooling2D(pool_size=(2,2)),
    keras.layers.Flatten(),
    keras.layers.Dense(128, activation=tf.nn.relu),
    keras.layers.Dropout(0.5),
    # 最后一个层决定输出类别的数量
    keras.layers.Dense(10, activation=tf.nn.softmax, name='y_out')
])
model.compile(optimizer=keras.optimizers.Adam(lr=0.001),
    loss='sparse_categorical_crossentropy',
    metrics=['accuracy'])
model.summary()
  1. 神经网络训练,对于这种简单的数据集,第一次训练的效果会非常好,一般地训练集精度会随着训练进行逐渐提高,而测试集会呈现一个先上升再下降的趋势(过拟合)。
tick_start = time.time()
history = model.fit(train_images, train_labels, batch_size=batch_size, epochs=15, validation_data=(test_images, test_labels))
tick_end = time.time()
print("Tring completed. Experied ", str(tick_end - tick_start))
  1. 训练完成,我们可以打印训练过程的精度变化
import matplotlib.pyplot as plt
fig, ax = plt.subplots(1, 1)
fig.set_size_inches(10,5)
ax.plot(history.history['accuracy'][1:])
ax.plot(history.history['val_accuracy'][1:])
ax.legend(['训练集精度', '验证集精度'], loc='upper left')
plt.show()

trainProcess

模型保存

使用以下python语句将模型结构与模型权值分开保存,会在run目录下生成model_weight.h5model_config.json,保存的参数在dnndk的编译中会使用到这两个文件。分开保存是为了更好地适应tensorflow的版本变化。

model_test.save_weights("model_weight.h5")
json_config = model_test.to_json()
with open('model_config.json', 'w') as json_file:
    json_file.write(json_config)

检查ragged-tensor
使用记事本打开modelconfig.json,如果在输入里出现"ragged": false,字眼的字段,请手动把它删掉,因为dnndk里的tensorflow没有这个参数。没有就不用处理。

模型验证

在训练好模型后,我们一般需要通过一些验证程序来测试保存的模型。
1. 加载模型,由于我们把结构和权值分开存储,所以我们要先解码模型再加载权值。

# 加载训练好的模型

with open(run_path + 'model_config.json') as json_file:
    json_config = json_file.read()
    model_test = tf.keras.models.model_from_json(json_config)
# Load weights
model_test.load_weights(run_path + 'model_weight.h5')
# model_test = tf.keras.models.load_model(run_path + "model.h5")
model_test.summary()

pcModelLoad

  1. 加载验证集,因为这个案例中测试集没有参与超参数的选择,所以直接使用了测试集当验证集。实际案例的训练集、测试集、验证集应该是独立的。此外,dnndk在进行量化和在powersensor上测试的时候也需要使用验证集,所以这里将验证集放在一个独立的文件夹dataset_valid里。
dataset_valid_path = '../dataset_valid//minist-number/'
# 1. 加载数据集
(validSet_images, validSet_lables) = load_mnist(dataset_valid_path, 't10k')

# 2. 图像预处理
validSet_images = validSet_images.reshape((-1,28,28,1)) / 255.
  1. 使用验证集评价模型精度
# 使用tensorflow的函数评估精度
res = model_test.evaluate(validSet_images, validSet_lables)
  1. 使用imblance库评价统计学特性(可选)。从统计学上说,要评价一个模型的可信赖程度,不仅要看它的精度,还有考虑它的召回率、F1参数等。
# 使用imblance库评估统计学特性
from imblearn.metrics import classification_report_imbalanced
pdt_label_fre = model_test.predict(validSet_images)
pdt_label = np.argmax(pdt_label_fre, axis=1)
print(classification_report_imbalanced(validSet_lables, pdt_label, target_names=wordlist))

imlearn

  1. 使用随机样本直观地观测预测结果:
fig, ax = plt.subplots(5, 2)
fig.set_size_inches(15,15)
for i in range(5):
    for j in range(2):
        l = random.randint(0, len(validSet_lables))
        pdt_label_fre = model_test.predict(validSet_images[l:l+1])
        pdt_label = np.argmax(pdt_label_fre, axis=1)
        ax[i, j].imshow(validSet_images[l, :, :, 0])
        title = "预测:" + wordlist[pdt_label[0]] + "\n" + "真实:" + wordlist[validSet_lables[l]]
        ax[i, j].set_title(title)
plt.tight_layout()

result1

  1. 到这里pc阶段的任务就结束了。这个阶段的主要任务是根据实际问题设计合适的深度网络结构,通过训练得到满足需求的网络权值,训练的结果在pc文件夹的run目录里:
    modelFile