PowerSensorAI 3 從現有模型遷移訓練 - 五花分類

效果展示

要什麼openMV,來試試Powersensor吧 - 深度學習 - 五花分類

圖文教程

  • 案例資料包:
    鏈接:https://pan.baidu.com/s/1bqxA2homcCQ1durTJDH2TQ
    提取碼:knhd

  • Anaconda安裝教程地址
    https://blog.csdn.net/linxiaobo110/article/details/106420769

  • 虛擬機下載地址:
    鏈接:https://pan.baidu.com/s/1mdZV9jn74RCxKp_pUh4mew
    提取碼:623k

  • powersensor鏡像下載地址:
    鏈接:https://pan.baidu.com/s/18CFK2aXonxuFF6-L_ittdw
    提取碼:8ve4

對於一些簡單的分類問題,如PowerSensor AI 1中的ministFashion,隨手設計一個簡單的深度網絡就可以得到較好的分類效果。然而,這樣的手法對於複雜一些的數據集很難保證效果。對於複雜的分類問題,使用已發佈的精心設計的神經網絡作爲基礎網絡是更明智的選擇。同時,從頭開始訓練深度的神經網絡是一個繁重的工作,使用別人在imagenet上預訓練好的權值來初始化我們的網絡可以起到事半功倍的效果。本節我們將以五花分類問題(雖然這個問題不見得多複雜)爲例,給大家介紹如何把tensorflow2.0官方訓練好的InceptionV3,通過簡單的幾步操作,遷移到我們自己的數據集上使用,並在powersensor上驗證運行。

經過上一講的學習,我們已經知道DPU的應用開發流程分爲PC模型訓練、DNNDK模型編譯、EDGE模型部署,三個步驟,本章剩下的內容將逐個介紹。

PC訓練模型

首先我們需要包含一些重要的頭文件和定義重要的參數

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
# 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 = 128
# 訓練的batch大小
batch_size = 32
# 數據庫路徑
dataset_path = '../dataset/'
# 各個花的路徑
flower_pathes = ['flowers/daisy', 'flowers/dandelion', 'flowers/rose', 'flowers/sunflower', 'flowers/tulip']
wordlist = ['雛菊', '蒲公英', '玫瑰', '向日葵', '鬱金香']
# 存放過程和結構的路徑
run_path = './run/'
if not os.path.exists(run_path):
    os.mkdir(run_path)
# 存放轉換後的tf數據集的路徑
dataset_tf_path = run_path + 'flowersTf.tfrecords'
dataset_nums = 4300

數據集準備與預處理

本節使用數據集是五花(玫瑰、向日葵、蒲公英、鬱金香、雛菊)數據集,可以在我們的百度網盤數據集文件夾裏下載到。下載完解壓到flower_classification的dataset目錄下,如下圖所示:
在這裏插入圖片描述

在數據集預處理上,我們使用了上一章“石頭剪刀布”識別的方案,先把原圖像處理後轉換爲tf-recorder,然後再使用tfrecoder完成樣本訓練。

tick_begin = time.time()
img_cnt = int(0)
label_cnt = int(0)
with tf.io.TFRecordWriter(dataset_tf_path) as writer:
    for sort_path in flower_pathes:    
        flower_list = os.listdir(dataset_path + sort_path)
        for img_name in flower_list:
            img_path = dataset_path + sort_path + "/" + img_name
            img = cv2.imread(img_path)  
            img_scale = cv2.resize(img,(img_size_net, img_size_net), interpolation = cv2.INTER_CUBIC)
            if not img is None:
                feature = {
                    'img1':tf.train.Feature(bytes_list=tf.train.BytesList(value=[img_scale.tostring()])),
                    'label':tf.train.Feature(int64_list=tf.train.Int64List(value=[label_cnt]))
#                     'label':tf.train.Feature(int64_list=tf.train.Int64List(value=[label_cnt]))
                }
                example = tf.train.Example(features=tf.train.Features(feature=feature))
                writer.write(example.SerializeToString())
                # 每隔50張打印一張圖片
                if img_cnt % 100 == 0:
                    print('The ', str(img_cnt), ' image')
                    plt.imshow(cv2.cvtColor(img_scale, cv2.COLOR_BGR2RGB))
                    plt.show()
                img_cnt += 1
        label_cnt = label_cnt + 1
    writer.close()   
tick_end = time.time()
print('Generate the dataset complete! Experied ', str(tick_end - tick_begin))
print('The dataset is ', dataset_tf_path)

在這裏插入圖片描述

讀取和測試數據集,生成數據集只需要運行一次就會自動保存到相應的目錄。讀取數據集在每次重啓jupyter的時候都需要加載一次。

def read_and_decode(example_proto):
    '''
    從TFrecord格式文件中讀取數據
    
    '''
    image_feature_description  = {
        'img1':tf.io.FixedLenFeature([],tf.string),
        'label':tf.io.FixedLenFeature([1], tf.int64),
    }
    feature_dict = tf.io.parse_single_example(example_proto, image_feature_description)
    img1 = tf.io.decode_raw(feature_dict['img1'], tf.uint8)
    label = feature_dict['label']
    return img1, label

# 1. 讀取數據集
dataset = tf.data.TFRecordDataset(dataset_tf_path)
dataset = dataset.map(read_and_decode)

# 2. 隨機打印8個測試圖像
dataset = dataset.shuffle(buffer_size=dataset_nums)
dataSet = np.array([x1 for x1 in dataset.take(10)])
dataSet_img = np.array([x1[0].numpy() for x1 in dataSet])
dataSet_img = dataSet_img.reshape((-1,img_size_net,img_size_net, 3)) / ((np.float32)(255.))
dataSet_label = np.array([x1[1].numpy()[0] for x1 in dataSet]) 
fig, ax = plt.subplots(5, 2)
fig.set_size_inches(15,15)
l = 0
for i in range(5):
    for j in range(2):
        ax[i, j].imshow(cv2.cvtColor(dataSet_img[l], cv2.COLOR_BGR2RGB))
        ax[i, j].set_title(wordlist[dataSet_label[l]])
        l += 1
plt.tight_layout()

在這裏插入圖片描述

網絡設計與預訓練

  1. 訓練集和測試集的打亂和重排,打亂和重排數據集可以打打減小訓練不穩定的風險。
# 1. 打亂數據集
dataset = dataset.shuffle(buffer_size=dataset_nums)
# 2. 抓取數據集
dataSet = np.array([x1 for x1 in dataset.take(dataset_nums)])
dataSet_img = np.array([x1[0].numpy() for x1 in dataSet])
dataSet_img = dataSet_img.reshape((-1,img_size_net,img_size_net, 3)) / ((np.float32)(255.))
dataSet_label = np.array([x1[1].numpy()[0] for x1 in dataSet]) 
# 3. 分離訓練集和測試集
trainSet_num = int(0.75 * dataset_nums)
trainSet_img = dataSet_img[0 : trainSet_num, :, :, :]
testSet_img = dataSet_img[trainSet_num : , :, :, :]
trainSet_label = dataSet_label[0 : trainSet_num]
testSet_label = dataSet_label[trainSet_num : ]

# 3. 統計各種訓練集中各種樣本的數量
print('數據集中各個樣本的數量:')
l = []
for x in dataSet_label:
    l.append(wordlist[x])
plt.hist(l, rwidth=0.5)
plt.show()
  1. 網絡設計,對於minist哪種簡單的數據集,我們可以隨手搭一個自己的網絡就可以有不俗的表現,這對於複雜一些的數據集很難保證效果。對於複雜的分類問題,使用已發佈的精心設計的神經網絡作爲基礎網絡是更明智的選擇。同時,從頭開始訓練深度的神經網絡是一個繁重的工作,使用別人在imagenet上預訓練好的權值來初始化我們的網絡可以起到事半功倍的效果。
input_tensor = tf.keras.layers.Input(shape=(img_size_net,img_size_net,3), name="x_input")
base_model = tf.keras.applications.InceptionV3(weights='imagenet', 
                                               include_top=False, 
                                               input_tensor=input_tensor, 
                                               input_shape=(img_size_net,img_size_net,3))

base_model.summary()    #打印網絡結構

在這裏插入圖片描述

  1. 一般來說,爲了更高的效率,我們不需要整個網絡,我們從輸入開始,截取到輸出爲6x6的最後一個激活層。接上我們自己的全連接層和輸出層,生成我們自己的網絡:
base_model = tf.keras.Model(inputs=input_tensor, outputs=base_model.get_layer('activation_74').output)

# 添加全局平均池化層
x = base_model.output
x = tf.keras.layers.GlobalAveragePooling2D()(x)

# 添加一個全連接層
x = tf.keras.layers.Dense(64, activation='relu')(x)
x = tf.keras.layers.Dropout(0.5)(x)

# 添加一個分類器,假設我們有200個類
predictions = tf.keras.layers.Dense(5, activation='softmax', name='y_out')(x)

# 構建我們需要訓練的完整模型
model = tf.keras.Model(inputs=input_tensor, outputs=predictions)

# 首先,我們只訓練頂部的幾層(隨機初始化的層)
# 鎖住所有 InceptionV3 的卷積層
for layer in model.layers[:195]:
    layer.trainable = False
for layer in model.layers[195:]:
    layer.trainable = True

# 編譯模型(一定要在鎖層以後操作)
model.compile(optimizer='rmsprop', loss=tf.keras.losses.SparseCategoricalCrossentropy(), metrics=['accuracy'])
model.summary()    #打印網絡結構
  1. 訓練,首先用較大的學習率訓練:
history = model.fit(trainSet_img, trainSet_label, 
                    batch_size=batch_size, 
                    epochs=20, 
                    validation_data=(testSet_img, testSet_label)
                    )
  1. 然後,放開更多的層,用較小的學習率訓練:
# 我們鎖住前面135層,然後放開之後的層。
for layer in model.layers[:135]:
    layer.trainable = False
for layer in model.layers[135:]:
    layer.trainable = True
# 我們需要重新編譯模型,才能使上面的修改生效
# 讓我們設置一個很低的學習率,使用 SGD 來微調    
from tensorflow.keras.optimizers import SGD
model.compile(optimizer=SGD(lr=0.0001, momentum=0.9), loss=tf.keras.losses.SparseCategoricalCrossentropy(), metrics=['accuracy'])

history = model.fit(trainSet_img, trainSet_label, 
                    batch_size=batch_size, 
                    epochs=20, 
                    validation_data=(testSet_img, testSet_label)
                    )
  1. 保存模型
model.save_weights(run_path + "model_weight.h5")
json_config = model.to_json()
with open(run_path + 'model_config.json', 'w') as json_file:
    json_file.write(json_config)
  1. 模型驗證部分詳情見源代碼

  2. 使用記事本查看生成的model_config.json,如果在輸入裏出現"ragged": false,字眼的字段,請手動把它刪掉,因爲dnndk裏的tensorflow沒有這個參數。沒有就不用處理。
    ragged

DNNDK 編譯

同minisFashiont的例子,我們將在DNNDK裏將模型編譯成DPU能夠運行的模型。

  1. 啓動虛擬機
  2. 在虛擬機的/home/xiaobo/powersensor目錄下新建flowerFive文件夾,並把案例目錄下的dnndk文件夾和dataset_valid文件夾複製到新建的文件夾下面。如果要使用自己新訓練的模型,需要把自己的模型(在案例目錄/pc/run下面的2個模型文件)替換掉我們準備好的文件。
  3. 進入dnndk目錄,右擊啓動shell,使用下面指令固化模型
./1_vitisAI_keras2frozon.sh
  1. 第二步,量化,注意把2_vitisAI_tf_quantize.sh中的input_nodesinput_shapesoutput_nodes改成與第一步打印的節點名稱一致。
./2_vitisAI_tf_quantize.sh
  1. 第三步,編譯模型
./3_vitisAI_tf_compile.sh 

編譯的結果在compile_result文件夾裏,裏面的.elf文件就是編譯好的模型。

EDGE調用

  1. 進入powersenosr的jupyter文件管理頁面,在/powersensor_workspace/powersensor_ai下面新建flowerFive目錄(隨教程發佈的案例已經準備好文件了)。在新建的flowerFive目錄下新建dataset_validedge文件夾
    edge_dir
  2. 通過jupyter的上傳功能,把案例目錄下的edge文件夾下的兩個文件上傳到powersensor的edge目錄;驗證集dataset_valid下的文件也同理上傳到相應目錄。
  • 如果要使用自己新訓練的模型,可以把edge下的elf換成虛擬機裏的compileResult下的elf文件。
  • 注意虛擬機裏的文件不能直接上傳(找不到),要先拷貝到自己的電腦裏才能上傳。
  1. 打開powersensor的edge下的powersensor_ministNumer.ipynb文件,按照notebook裏面的指導逐個運行程序。

  2. 首先也是加載頭文件和重要的參數,其中DPU網絡參數應該與DPU的編譯結果輸出保持一致,否則會導致DPU崩潰。

from dnndk import n2cube
import numpy as np
from numpy import float32
import os
import cv2
import matplotlib.pyplot as plt
import random
import time
import matplotlib as mpl
from matplotlib import font_manager
import PowerSensor as ps
from IPython.display import clear_output

mpl.rcParams['axes.unicode_minus']=False     # 正常顯示負號
font = font_manager.FontProperties(fname="/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf")

# 訓練用的圖像尺寸
img_size_net = 128
# 訓練的batch大小
batch_size = 32

wordlist = ['雛菊', '蒲公英', '玫瑰', '向日葵', '鬱金香']

# DPU網絡參數
# KERNEL_CONV="testModel"
ELF_NAME = "dpu_testModel_0.elf"
CONV_INPUT_NODE = "x_input_Conv2D"
CONV_OUTPUT_NODE = "y_out_MatMul"
  1. 讀取測試集
dataset_path = '../dataset_valid/'
(validSet_images, validSet_lables) = load_valid_data(dataset_path)

# 2. 圖像預處理
# test_images = test_images.reshape((-1,28,28,1)) / 255.
validSet_images = np.array(validSet_images, dtype='float32')

# 3. 隨機打印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(validSet_lables))
        ax[i, j].imshow(cv2.cvtColor(validSet_images[l], cv2.COLOR_BGR2RGB))
        title = wordlist[validSet_lables[l]]
        title_utf8 = title.decode('utf8')
        ax[i, j].set_title(title_utf8, fontproperties=font)
plt.tight_layout()
  1. 加載dpu
dpu1 = ps.DpuHelper()
dpu1.load_kernel(ELF_NAME, input_node_name=CONV_INPUT_NODE, output_node_name=CONV_OUTPUT_NODE)
  1. 測試集精度測試
tick_start = time.time()
test_num = len(validSet_lables)
right_eg_cnt = 0
for i in range(test_num):
    img1_scale = validSet_images[i]
    softmax = dpu1.predit_softmax(img1_scale)
    pdt= np.argmax(softmax, axis=0)
    if pdt == validSet_lables[i]:
        right_eg_cnt += 1
tick_end = time.time()
print('精度: ' + str((right_eg_cnt*1.) / test_num))
print('測試 ' + str(test_num) + ' 個樣本。耗時 ' + str(tick_end - tick_start) + '秒!')
#############################
# 精度: 1.0
# 測試 50 個樣本。耗時 9.1545650959秒!
  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)-1)
        img1_scale = validSet_images[l]
        softmax = dpu1.predit_softmax(img1_scale)
        pdt= np.argmax(softmax, axis=0)
        ax[i, j].imshow(cv2.cvtColor(validSet_images[l], cv2.COLOR_BGR2RGB))
#         title = "預測:" + str(wordlist[pdt]) + "\n" + "真實:" + str(wordlist[test_labels[l]])
        title = "預測:" + wordlist[pdt] + "\n" + "真實:" + wordlist[validSet_lables[l]]
        title_utf8 = title.decode('utf8')
        ax[i, j].set_title(title_utf8, fontproperties=font)
plt.tight_layout()

在這裏插入圖片描述

  1. 拍照測試
# 這個對象用於操作攝像頭
cam1 = ps.ImageSensor()
cv_font = cv2.freetype.createFreeType2()
cv_font.loadFontData(fontFileName='/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf', id=0)
for i in range(100):
    # 記錄時間
    start = time.time()
    # 清空顯示區
    clear_output(wait=True)
    # 讀取圖像
    imgMat = cam1.read_img_ori()
    imgShow = cv2.resize(imgMat, (320,240))
#     imgMat_cali = grey_world2(imgMat)
    # 圖像縮放,太大的圖像顯示非常浪費資源
    tempImg = cv2.resize(imgMat, (128,128))
    img_scale = tempImg / 255.
    img_scale = np.array(img_scale, dtype=np.float32)
    softmax = dpu1.predit_softmax(img_scale)
    pdt= np.argmax(softmax, axis=0)
    cv_font.putText(imgShow,wordlist[pdt], (10,10), fontHeight=30, color=(255,255,255), thickness=-1, line_type=cv2.LINE_4, bottomLeftOrigin=False)
#     tempImg = imgMat
    # 顯示圖像
    img = ps.CommonFunction.show_img_jupyter(imgShow)
    # 記錄運行時間
    end = time.time()
    # 打印運行時間
    print(end - start)
    # 因爲網絡傳輸的延時,需要稍息一下
    time.sleep(0.1)

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章