1 常用 API 介紹
1.1 rknn初始化及釋放
初始化函數(shù):
rknn = RKNN(verbose,verbose_file)
初始化RKNN對象時,可以設(shè)置verbose和verbose_file參數(shù),以打印詳細(xì)的日志信息。
參數(shù) | 解析 |
---|---|
verbose | 指定是否要在屏幕上打印詳細(xì)日志信息 |
verbose_file | 如果verbose參數(shù)為True,日志信息將寫到該參數(shù)指定的文件中,一般將verbose設(shè)置為True,verbose_file不設(shè)置,將日志顯示到終端上。 |
1.2 rknn模型配置
轉(zhuǎn)化模型之前需要先配置RKNN-Toolkit。使用到的API是config,使用示例如下:
ret = rknn.config(
reorder_channel='2 1 0',
mean_values=[123.675, 116.28, 103.53],
std_values=[58.395, 57.12, 57.375],
optimization_level=3,
target_platform='rk1808,
quantize_input_node=False
output_optimize=1,
force_builtin_perm=False,
)
參數(shù) | 解析 |
---|---|
reorder_channel | 對輸入圖像RGB通道的調(diào)整,’ 0 1 2 ’ 表示不做調(diào)整,此處若設(shè) ‘2 1 0’,那么在前處理時就不用調(diào)整通道,否則就重復(fù)調(diào)整了 |
mean_values | 輸入的均值,參數(shù)是一個列表。表示輸入圖像的三個通道值分別減去[123.675, 116.28, 103.53] |
std_values | 輸入的歸一化值,參數(shù)是一個列表。表示輸入圖像的三個通道值分別減去[123.675, 116.28, 103.53]再分別除以[58.395, 57.12, 57.375] |
optimization_level | 設(shè)置代碼的優(yōu)化等級,0:不優(yōu)化;1:Fast;2:Faster; 3:Fastest |
target_platform | 運行平臺,這里用到的是rk1808 |
quantize_input_node | *** |
output_optimize | *** |
force_builtin_perm | *** |
1.3 PT模型加載
將Pytorch進(jìn)行處理,API是load_pytorch,示例:
ret = rknn.load_pytorch(model=pt_model, inputs=['Preprocessor/sub'], outputs=['concat_1', 'concat_2'], input_size_list=[[3,416, 416]])
參數(shù) | 解析 |
---|---|
pt_model | 是yolox算法(Pytorch開發(fā))模型的路徑 |
inputs | 模型的輸入節(jié)點(操作數(shù)名),按照官方例子寫[‘Preprocessor/sub’] |
outputs | 模型的輸出節(jié)點(操作數(shù)名),按照官方例子寫[‘concat’, ‘concat_1’] |
input_size_list | 每個輸入節(jié)點對應(yīng)的圖片的尺寸和通道數(shù) |
1.4 rknn模型轉(zhuǎn)化
?? 將Pytorch模型轉(zhuǎn)化為rknn模型,需要使用的API是build,示例:
ret = rknn.build(do_quantization, dataset, pre_compile)
這個就是將Pytorch模型轉(zhuǎn)化成rknn。
參數(shù) | 解析 |
---|---|
do_quantization | 是否對模型進(jìn)行量化,值為True 或False |
dataset | 量化校正數(shù)據(jù)的數(shù)據(jù)集??梢岳斫鉃橛脕磉M(jìn)行測試的圖像路徑名的集合。每一個圖像路徑放一行。我這里就用一個圖像進(jìn)行測試。所以dataset.txt內(nèi)如為 road.bmp |
pre_compile | 預(yù)編譯開關(guān),如果設(shè)置成 True,可以減小模型大小,及模型在硬件設(shè)備上的首次啟動速度。但是打開這個開關(guān)后,構(gòu)建出來的模型就只能在硬件平臺上運行,無法通過模擬器進(jìn)行推理或性能評估。如果硬件有更新,則對應(yīng)的模型要重新構(gòu)建 |
1.5 模型導(dǎo)出
?? 模型的導(dǎo)出需要使用export_rknn函數(shù),參數(shù)為導(dǎo)出模型所在的路徑,后綴名為‘.rknn’。
ret = rknn.export_rknn('./rknn_model.rknn')
1.6 rknn運行環(huán)境初始化
ret = rknn.init_runtime(target='rk1808', device_id=DEVICE_ID, perf_debug=True,eval_mem=True)
參數(shù) | 解析 |
---|---|
target | 目標(biāo)平臺,這里是rk1808 |
device_id | 設(shè)備的ID號 |
perf_debug | 評估模型使用時間,默認(rèn)為False |
eval_mem | 評估模型使用的內(nèi)存,這兩個配置為True后,才可以使用模型評估相關(guān)的API |
1.7rknn模型推理
outputs = rknn.inference(inputs=[img],data_type,data_format,inputs_pass_through)
參數(shù) | 解析 |
---|---|
img: | cv2讀取、處理好的圖像 |
inputs | 待推理的輸入,如經(jīng)過 cv2 處理的圖片。格式是 ndarray list |
data_type | 輸入數(shù)據(jù)的類型,可填以下值: ’float32’, ‘float16’, ‘int8’, ‘uint8’, ‘int16’。默認(rèn)值為’uint8’ |
data_format | 數(shù)據(jù)模式,可以填以下值: “nchw”, “nhwc”。默認(rèn)值為’nhwc’。這兩個的不同之處在于 channel 放置的位置 |
inputs_pass_through | 將輸入透傳給 NPU 驅(qū)動。非透傳模式下,在將輸入傳給 NPU 驅(qū)動之前,工具會對輸入進(jìn)行減均值、除方差等操作;而透傳模式下,不會做這些操作。這個參數(shù)的值是一個數(shù)組,比如要透傳 input0,不透徹 input1,則這個參數(shù)的值為[1,0]。默認(rèn)值為 None,即對所有輸入都不透傳 |
1.8 rknn性能評估
ret = rknn.eval_perf(inputs=[img], is_print=True)
memory_detail = rknn.eval_memory()
2 pth2pt
?? Pytorch訓(xùn)練的模型轉(zhuǎn)換成RKNN,只能通過 .pt 轉(zhuǎn)成 .rknn ,且需通過torch.jit.trace()函數(shù)保存的 .pt 。
目前只支持 torch.jit.trace 導(dǎo)出的模型。torch.save 接口僅保存權(quán)重參數(shù)字典,缺乏網(wǎng)絡(luò)結(jié)構(gòu)信息,故無法被正常導(dǎo)入并轉(zhuǎn)成 RKNN 模型。
import torch
import torchvision
from nets.yolo2rknn import YoloBody
model_path = 'model_data/7150_nano.pth' # 訓(xùn)練后保存的模型文件
num_classes = 3 # 檢測類別數(shù)
phi = 'nano' # 模型類型
model = YoloBody(num_classes, phi) #導(dǎo)入模型
#device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
#model.load_state_dict(torch.load(model_path, map_location=device)) #初始化權(quán)重
model.load_state_dict(torch.load(model_path))
model.eval()
example = torch.rand(1, 3, 416, 416)
traced_script_module = torch.jit.trace(model, example)
traced_script_module.save("model_data/MySelf_Nano.pt")
3 pt2rknn
3.1 界面轉(zhuǎn)換
?? 在終端中輸入:python3 -m rknn.bin.visualization ,然后出現(xiàn)如下界面:
?? 根據(jù)自己原始模型格式選擇,這里的原始模型是onnx模型,所以選擇onnx,進(jìn)去后各個選項的意思自己翻譯過來對照一下不難懂(rknn toolkit 1.7.1版本界面好像是中文),需要注意的是預(yù)編譯選項(Whether To Enable Pre-Compile),預(yù)編譯 RKNN 模型可以減少模型初始化時間,但是無法通過模擬器進(jìn)行推理或性能評估。
3.2 代碼轉(zhuǎn)換
模型轉(zhuǎn)換時API調(diào)用流程如下:
?? 在Ubuntu的虛擬環(huán)境RKNN中進(jìn)行轉(zhuǎn)換,其中特別需要注意的是rknn.config()中參數(shù)的設(shè)置,轉(zhuǎn)換代碼如下:
import os
import numpy as np
from rknn.api import RKNN
pt_model = '/home/liu/RKNN/model/myself_nano.pt'
rknn_model = '/home/liu/RKNN/model/myself_nano.rknn'
DATASET = '/home/liu/RKNN/model/JPEGImages.txt'
QUANTIZE_ON = False # 是否對模型進(jìn)行量化
if __name__ == '__main__':
# Create RKNN object
rknn = RKNN(verbose=False)
if not os.path.exists(pt_model):
print('model not exist')
exit(-1)
_force_builtin_perm = False
# pre-process config
print('--> Config model')
rknn.config(
reorder_channel='2 1 0',
mean_values=[[123.675, 116.28, 103.53]],
std_values=[[58.395, 57.12, 57.375]],
optimization_level=3,
target_platform = 'rk1808',
# target_platform='rv1109',
quantize_input_node= QUANTIZE_ON,
batch_size = 200,
output_optimize=1,
force_builtin_perm=_force_builtin_perm
)
print('done')
# Load PT model
print('--> Loading model')
ret = rknn.load_pytorch(model=pt_model, input_size_list=[[3,416, 416]])
if ret != 0:
print('Load model failed!')
exit(ret)
print('done')
# Build model
print('--> Building model')
ret = rknn.build(do_quantization=QUANTIZE_ON, dataset=DATASET, pre_compile=False)
if ret != 0:
print('Build model failed!')
exit(ret)
print('done')
# Export RKNN model
print('--> Export RKNN model')
ret = rknn.export_rknn(rknn_model)
if ret != 0:
print('Export rknn failed!')
exit(ret)
print('done')
exit(0)
#rknn.release()
轉(zhuǎn)換后得到MySelf_Nano.rknn模型。
4 測試
4.1 模型推理
模型推理時API調(diào)用流程如下:
?? RKNN-Toolkit 通過 PC 的 USB 連接到開發(fā)板硬件,將構(gòu)建或?qū)氲?RKNN 模型傳到 RK1808 上運行,并從 RK1808 上獲取推理結(jié)果、性能信息。使用 RKNN 模型時請先將設(shè)備的 NPU 驅(qū)動更新至最新的 release 版本。
請執(zhí)行以下步驟:
1、確保開發(fā)板的 USB OTG 連接到 PC,并且 ADB 能夠正確識別到設(shè)備,即在 PC 上執(zhí)行adb devices -l命令能看到目標(biāo)設(shè)備。
2、調(diào)用init_runtime 接口初始化運行環(huán)境時需要指定 target 參數(shù)和 device_id 參數(shù)。其中 target 參數(shù)表明硬件類型, 選值為 rk1808, 當(dāng) PC 連接多個設(shè)備時,還需要指定 device_id 參數(shù),即設(shè)備編號,可以通過adb devics命令查看,舉例如下:
$ adb devices
List of devices attached
0123456789ABCDEF device
即可以改為:
ret = rknn.init_runtime(target='rk1808', device_id='0123456789ABCDEF')
3、運行測試代碼
?? 該測試代碼包括前處理、后處理,前處理與后處理都與訓(xùn)練時的處理盡量保持一致,需要注意的是,torch中有些函數(shù)與numpy的函數(shù)略有不同,若檢測的Bbox存在不準(zhǔn)確的情況,可能是其中一些函數(shù)造成的。
例如:
- torch.stack((grid_x, grid_y), 2) 改成 np.transpose(np.stack((grid_x, grid_y), 2), (1, 0, 2))
- prediction.new(prediction.shape) 改成 np.copy(prediction)
- class_conf, class_pred = torch.max(image_pred[:, 5:5 + num_classes], 1, keepdim=True) 改成
class_conf = np.max(image_pred[:, 5:5 + num_classes], axis=1, keepdims=True)
class_pred = np.expand_dims(np.argmax(image_pred[:, 5:5 + num_classes], axis=1), axis=1) - boxes.batched_nms() 改成 nms_boxes()
import os
from re import T
import numpy as np
import cv2
import time
from rknn.api import RKNN
mode = 'image' # 模式:image、video,分別為用圖片測試和用攝像頭測試
#pt_model = '/home/liu/RKNN/model/myself_nano.pt'
rknn_model = '/home/liu/RKNN/model/myself_nano.rknn'
img_path = '/home/liu/RKNN/model/JPEGImages/04245.jpg'
#DATASET = '/home/liu/RKNN/model/JPEGImages.txt'
#QUANTIZE_ON = False # 是否對模型進(jìn)行量化
box_thresh = 0.7 # 置信度 閾值
nms_thresh = 0.3 # nms 閾值
input_shape = [416, 416]
letterbox_image = True # resize是否保持原長寬比例
num_classes = 3
class_names = ("iris", "pipul", "shut-eye")
#===================================================================
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def nms_boxes(boxes, scores):
x = boxes[:, 0]
y = boxes[:, 1]
w = boxes[:, 2] - boxes[:, 0]
h = boxes[:, 3] - boxes[:, 1]
areas = w * h
order = scores.argsort()[::-1]
keep = []
while order.size > 0:
i = order[0]
keep.append(i)
xx1 = np.maximum(x[i], x[order[1:]])
yy1 = np.maximum(y[i], y[order[1:]])
xx2 = np.minimum(x[i] + w[i], x[order[1:]] + w[order[1:]])
yy2 = np.minimum(y[i] + h[i], y[order[1:]] + h[order[1:]])
w1 = np.maximum(0.0, xx2 - xx1 + 0.00001)
h1 = np.maximum(0.0, yy2 - yy1 + 0.00001)
inter = w1 * h1
ovr = inter / (areas[i] + areas[order[1:]] - inter)
inds = np.where(ovr <= nms_thresh)[0]
order = order[inds + 1]
keep = np.array(keep)
return keep
def yolo_correct_boxes(box_xy, box_wh, input_shape, image_shape, letterbox_image):
box_yx = box_xy[..., ::-1]
box_hw = box_wh[..., ::-1]
input_shape = np.array(input_shape)
image_shape = np.array(image_shape)
if letterbox_image:
new_shape = np.round(image_shape * np.min(input_shape/image_shape))
offset = (input_shape - new_shape)/2./input_shape
scale = input_shape/new_shape
box_yx = (box_yx - offset) * scale
box_hw *= scale
box_mins = box_yx - (box_hw / 2.)
box_maxes = box_yx + (box_hw / 2.)
boxes = np.concatenate([box_mins[..., 0:1], box_mins[..., 1:2], box_maxes[..., 0:1], box_maxes[..., 1:2]], axis=-1)
boxes *= np.concatenate([image_shape, image_shape], axis=-1)
return boxes
def decode_outputs(outputs, input_shape):
# [1, 8, 52, 52]
# [1, 8, 26, 26]
# [1, 8, 13, 13]
grids = []
strides = []
hw = [x.shape[-2:] for x in outputs] # [[52,52] ,[26,26], [13,13]]
outputs = np.concatenate([x.reshape(1, 8,-1) for x in outputs],axis=2)
outputs = np.transpose(outputs,(0,2,1))
outputs[:, :, 4:] = sigmoid(outputs[:, :, 4:])
for h, w in hw:
# [[52,52] ,[26,26], [13,13]]
grid_y, grid_x = np.meshgrid([np.arange(0, h, 1.)], [np.arange(0, w, 1.)])
grid = np.transpose(np.stack((grid_x, grid_y), 2), (1, 0, 2)).reshape(1, -1, 2)
shape = grid.shape[:2]
grids.append(grid)
strides.append(np.full((shape[0], shape[1], 1), input_shape[0] / h))
grids = np.concatenate(grids, axis=1)
strides = np.concatenate(strides, axis=1)
outputs[..., :2] = (outputs[..., :2] + grids) * strides
outputs[..., 2:4] = np.exp(outputs[..., 2:4]) * strides
outputs[..., [0,2]] = outputs[..., [0,2]] / input_shape[1]
outputs[..., [1,3]] = outputs[..., [1,3]] / input_shape[0]
return outputs
def non_max_suppression(prediction, num_classes, input_shape, image_shape, letterbox_image, conf_thres=0.5, nms_thres=0.4):
#box_corner = prediction.copy()
box_corner = np.copy(prediction) # [xc,yc,w,h]
box_corner[:, :, 0] = prediction[:, :, 0] - prediction[:, :, 2] / 2
box_corner[:, :, 1] = prediction[:, :, 1] - prediction[:, :, 3] / 2
box_corner[:, :, 2] = prediction[:, :, 0] + prediction[:, :, 2] / 2
box_corner[:, :, 3] = prediction[:, :, 1] + prediction[:, :, 3] / 2
prediction[:, :, :4] = box_corner[:, :, :4] # [left,top,right,botton]
output = [None for _ in range(len(prediction))]
for i, image_pred in enumerate(prediction):
class_conf = np.max(image_pred[:, 5:5 + num_classes], axis=1, keepdims=True)
class_pred = np.expand_dims(np.argmax(image_pred[:, 5:5 + num_classes], axis=1), axis=1)
conf_mask = (image_pred[:, 4] * class_conf[:, 0] >= conf_thres).squeeze()
if not image_pred.shape[0]:
continue
detections = np.concatenate((image_pred[:, :5], class_conf, class_pred), axis=1)
detections = detections[conf_mask]
nms_out_index = nms_boxes(detections[:, :4], detections[:, 4] * detections[:, 5])
if not nms_out_index.shape[0]:
continue
output[i] = detections[nms_out_index]
if output[i] is not None: # [left,top,right,botton]
box_xy, box_wh = (output[i][:, 0:2] + output[i][:, 2:4])/2, output[i][:, 2:4] - output[i][:, 0:2]
output[i][:, :4] = yolo_correct_boxes(box_xy, box_wh, input_shape, image_shape, letterbox_image)
return output
def detect(inputs, image_shape):
# input: [1, 3 * h*w, 8]
outputs = decode_outputs(inputs, input_shape)
results = non_max_suppression(outputs, num_classes, input_shape,
image_shape, letterbox_image, conf_thres = box_thresh, nms_thres = nms_thresh)
if results[0] is None:
return None, None, None
label = np.array(results[0][:, 6], dtype = 'int32')
conf = results[0][:, 4] * results[0][:, 5]
boxes = np.array(results[0][:, :4], dtype = 'int32')
return label, conf, boxes
def draw(image, image_shape, label, conf, boxes):
for i, c in list(enumerate(label)):
predicted_class = class_names[int(c)]
box = boxes[i]
score = conf[i]
top, left, bottom, right = box
top = max(0, np.floor(top).astype('int32'))
left = max(0, np.floor(left).astype('int32'))
bottom = min(image_shape[1], np.floor(bottom).astype('int32'))
right = min(image_shape[0], np.floor(right).astype('int32'))
label = '{} {:.2f}'.format(predicted_class, score)
for box, score, cl in zip(boxes, scores, classes):
top, left, bottom, right = box
top = int(top)
left = int(left)
right = int(right)
bottom = int(bottom)
center_coordinates = ((right+left)//2, (bottom+top)//2) # 橢圓中心
axesLength = ((right-left)//2, (bottom-top)//2) #(長軸長度,短軸長度)
cv2.ellipse(image,center_coordinates, axesLength, 0, 0, 360, (0,0,255), 2)
#cv2.rectangle(image, (left, top), (right, bottom), (255, 0, 0), 2)
cv2.putText(image, '{0} {1:.2f}'.format(class_names[cl], score),
(left, top - 6),
cv2.FONT_HERSHEY_SIMPLEX,
0.6, (255, 0, 0), 2)
def resize_image(image, letterbox_image, size = input_shape):
ih, iw = image.shape[0:2] # 實際尺寸
w, h = size # [416, 416]
if letterbox_image:
scale = min(w/iw, h/ih)
nw = int(iw * scale) # resize后的尺寸
nh = int(ih * scale)
image = cv2.resize(image,(nw,nh), interpolation=cv2.INTER_LINEAR)
top, bottom = (h-nh)//2 , (h-nh)//2
left, right = (w-nw)//2 , (w-nw)//2
new_image = cv2.copyMakeBorder(image, top, bottom, left, right, cv2.BORDER_CONSTANT, value=(128,128,128))
else:
new_image = cv2.resize(image,(w,h), interpolation=cv2.INTER_LINEAR)
return new_image
if __name__ == '__main__':
# Create RKNN object
rknn = RKNN(verbose=False)
_force_builtin_perm = False
ret = rknn.load_rknn(path=rknn_model)
if ret!=0:
print('Load RKNN model failed !')
exit(ret)
# init runtime environment
print('--> Init runtime environment')
ret = rknn.init_runtime()
# ret = rknn.init_runtime('rv1109', device_id='1109')
# ret = rknn.init_runtime('rk1808', device_id='1808')
if ret != 0:
print('Init runtime environmentfailed')
exit(ret)
print('done')
if mode == 'video':
# input video
capture = cv2.VideoCapture(0 + cv2.CAP_DSHOW)
fps = 0.0
while(True):
t1 = time.time()
# 讀取某一幀
ref,frame=capture.read()
image_shape = np.array(np.shape(frame)[0:2])
# 格式轉(zhuǎn)變,BGRtoRGB
#frame = cv2.cvtColor(frame,cv2.COLOR_BGR2RGB)
# Inference
print('--> Running model')
outputs = rknn.inference(inputs=[frame], inputs_pass_through=[0 if not _force_builtin_perm else 1])
classes, scores, boxes = detect(outputs, image_shape)
#frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
if boxes is not None:
draw(frame, image_shape, classes, scores, boxes)
fps = ( fps + (1./(time.time()-t1)) ) / 2
print("fps= %.2f"%(fps))
frame = cv2.putText(frame, "fps= %.2f"%(fps), (0, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
cv2.imshow("video",frame)
c= cv2.waitKey(1) & 0xff
if c==27:
capture.release()
break
capture.release()
cv2.destroyAllWindows()
elif mode == 'image':
# input images
img = cv2.imread(img_path)
image_shape = np.array(np.shape(img)[0:2])
img_1 = img
#img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img_data = resize_image(img, letterbox_image, input_shape)
# Inference
print('--> Running model')
outputs = rknn.inference(inputs=[img_data], inputs_pass_through=[0 if not _force_builtin_perm else 1])
# type : ndarray
# print('outputs[0]:', outputs[0])
# [1, 8, 52, 52]
# [1, 8, 26, 26]
# [1, 8, 13, 13]
classes, scores, boxes = detect(outputs, image_shape)
print('classes:', classes)
print('scores:' , scores)
print('boxes:' , boxes)
#img_1 = cv2.cvtColor(img,cv2.COLOR_RGB2BGR)
if boxes is not None:
draw(img_1, image_shape, classes, scores, boxes)
cv2.imshow("post process result", img_1)
cv2.waitKeyEx(0)
exit(0)
#rknn.release()
4.2 量化后檢測不出目標(biāo)或精度大幅下降
4.2.1 模型訓(xùn)練注意事項
1、卷積核設(shè)置
?? 推薦在設(shè)計的時候盡量使用 3x3 的卷積核,這樣可以實現(xiàn)最高的乘加運算單元(MAC)利用率,使得 NPU 的性能最佳。
NPU 也可以支持大尺寸的卷積核。支持的最小卷積核為[1],最大值為[11 * stride - 1]。同時 NPU 也支持非對稱卷積核,不過會增加一些額外的計算開銷。
2、結(jié)構(gòu)融合設(shè)計
?? NPU 會對卷積后面的 ReLU 和 MAX Pooling 進(jìn)行融合的優(yōu)化操作,能在運行中減少計算和帶寬開銷。所以在搭建網(wǎng)絡(luò)時,能針對這一特性,進(jìn)行設(shè)計。
?? 模型量化后,卷積算子與下一層的 ReLU 算子可以被合并。另外為了確保 Max Pooling算子也能被合并加速,請在設(shè)計網(wǎng)絡(luò)時參考以下幾點:
- pool size 必須是 2x2 或者 3x3,而步長 stride=2;
- 2x2 池化的輸入圖片尺寸必須是偶數(shù),而且不能有填充;
- 3x3 池化的輸入圖片尺寸必須是非 1 的奇數(shù),而且不能有填充;
- 如果是 3x3 的池化,則水平輸入大小必須小于 64(8-bit 模型)或 32(16-bit 模型)。
3、2D卷積和Depthwise卷積
?? NPU 支持常規(guī) 2D 卷積和 Depthwise 卷積加速。由于 Depthiwise 卷積特定的結(jié)構(gòu),使得它對于量化(int8)模型不太友好,而 2D 卷積的優(yōu)化效果更好。所以設(shè)計網(wǎng)絡(luò)時建議盡量使用2D 卷積。如果必須使用 Depthwise 卷積,建議按照下面的規(guī)則進(jìn)行修改,能提高量化后模型的精度:
- 如果網(wǎng)絡(luò)中的激活函數(shù)使用的是 Silu,建議將其都改為 ReLU;
- 在 Depthwise 卷積層的 BN 層和激活層,建議去除;
- 在訓(xùn)練時,針對 Depthwise 卷積層,對它的權(quán)重進(jìn)行 L2 正則化。
4.2.2 RKNN量化過程使用的Dataset
?? RKNN Toolkit 量化過程中,需要根據(jù)數(shù)據(jù)的最大值、最小值,找到合適的量化參數(shù)。
此時需要使用 dataset 里的輸入進(jìn)行推理,獲取每一層的輸入、輸出數(shù)據(jù),再根據(jù)這些數(shù)據(jù)計算每一層輸入、輸出的量化參數(shù)。
?? 基于這個原因,校準(zhǔn)數(shù)據(jù)集里的數(shù)據(jù)最好是從訓(xùn)練集或驗證集中取一個有代表性的子集,建議數(shù)量在 100~500 張之間。文章來源:http://www.zghlxwxcb.cn/news/detail-421282.html
4.2.3 RKNN量化過程的參數(shù)設(shè)置
1、使用 RKNN Toolkit 導(dǎo)入量化后的模型時使 rknn.build(do_quantization=False);
2、設(shè)置 mean_values/std_values 參數(shù),確保其和訓(xùn)練模型時使用的參數(shù)相同;
3、務(wù)必確保測試時輸入圖像通道順序為 R,G,B(不論訓(xùn)練時使用的圖像通道順序如何,使用 RKNN 做測試時都按 R,G,B 輸入);
4、在rknn.config 函數(shù)里面設(shè)置 reorder_channel 參數(shù),’0 1 2’代表 RGB, ’2 1 0’代表 BGR,務(wù)必和訓(xùn)練時候圖像通道順序一致;
5、使用多張圖進(jìn)行量化校準(zhǔn),確保量化精度穩(wěn)定;
6、在 rknn.config 中設(shè)置 batch_size 參數(shù) (建議設(shè)置 batch_size = 200) 并且在 dataset.txt 中給出大于 200 張圖像路徑用于量化;如果內(nèi)存不夠,可以設(shè)置 batch_size =10, epochs=20 代替 batch_size = 200 進(jìn)行量化。文章來源地址http://www.zghlxwxcb.cn/news/detail-421282.html
到了這里,關(guān)于RKNN模型部署(3)—— 模型轉(zhuǎn)換與測試的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!