0%

深度学习记录-简单

记录深度学习AI过程中的一些问题。涉及到 tensorflowncnnonnxkeras的训练以及转换模型

关于 tensorflow

目前 tensorflow 发展到了 2.9.+
的版本,大部分的功能都完善,且使用简单,同时教程文档也非常完善:https://www.tensorflow.org/tutorials?hl=zh-cn

例如进行分类训练:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# vim:fenc=utf-8
# https://www.tensorflow.org/tutorials/images/classification

"""
训练文件
"""
# TensorFlow and tf.keras
import glob
import os

import tensorflow as tf
from keras import layers
from tensorflow import keras
# Helper libraries
import numpy as np

print(tf.__version__)
gpus = tf.config.list_physical_devices(device_type='GPU')
if len(gpus) > 0:
tf.config.experimental.set_memory_growth(gpus[0], True)

# 图像文件路径
Image_dir = 'image'
img_height = 180
img_width = 180
batch_size = 32
epochs = 15
model_path = "model.h5"

if __name__ == '__main__':
train_ds = tf.keras.utils.image_dataset_from_directory(
Image_dir + "/train",
validation_split=0.2,
subset="training",
seed=123,
image_size=(img_height, img_width),
batch_size=batch_size)
val_ds = tf.keras.utils.image_dataset_from_directory(
Image_dir + "/test",
validation_split=0.2,
subset="validation",
seed=123,
image_size=(img_height, img_width),
batch_size=batch_size)
class_names = train_ds.class_names
print(class_names)
AUTOTUNE = tf.data.AUTOTUNE
train_ds = train_ds.cache().shuffle(1000).prefetch(buffer_size=AUTOTUNE)
val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE)

normalization_layer = layers.Rescaling(1. / 255)

normalized_ds = train_ds.map(lambda x, y: (normalization_layer(x), y))
image_batch, labels_batch = next(iter(normalized_ds))
first_image = image_batch[0]
# Notice the pixel values are now in `[0,1]`.
print(np.min(first_image), np.max(first_image))
num_classes = len(class_names)
data_augmentation = keras.Sequential(
[
keras.layers.RandomFlip("horizontal", input_shape=(img_height, img_width, 3)),
keras.layers.RandomRotation(0.1),
keras.layers.RandomZoom(0.1),
]
)
model = None
if os.path.exists(model_path):
# 如果模型存在,则直接加载模型继续训练
model = tf.keras.models.load_model(model_path)
model.summary()
else:
model = keras.Sequential([
data_augmentation,
keras.layers.Rescaling(1. / 255),
keras.layers.Conv2D(16, 3, padding='same', activation='relu'),
keras.layers.MaxPooling2D(),
keras.layers.Conv2D(32, 3, padding='same', activation='relu'),
keras.layers.MaxPooling2D(),
keras.layers.Conv2D(64, 3, padding='same', activation='relu'),
keras.layers.MaxPooling2D(),
keras.layers.Conv2D(128, 3, padding='same', activation='relu'),
keras.layers.MaxPooling2D(),
keras.layers.Dropout(0.2),
keras.layers.Flatten(),
keras.layers.Dense(128, activation='relu'),
keras.layers.Dense(num_classes)
])
try:
model.compile(optimizer='adam',
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=['accuracy'])
# 执行训练
history = model.fit(
train_ds,
validation_data=val_ds,
epochs=epochs
)
except KeyboardInterrupt:
pass

print('\n\n\n')
# 验证结果
test_loss, test_acc = model.evaluate(val_ds, verbose=2)
print('\nTest accuracy:', test_acc)

test_loss, test_acc = model.evaluate(train_ds, verbose=2)
# 验证结果
print('\nTest accuracy:', test_acc)

# 保存模型
model.save(model_path)

exit()

该代码会自动找到 image/train 里面的文件夹,并将文件夹作为类别,加载各自类别(文件夹)里面的图像;然后对其进行训练。可以快速得到一个分类模型。
完整教程 : https://www.tensorflow.org/tutorials/images/classification

关于 keras 、 hdf5 模型

前面的分类训练保存的模型为 keras/hdf5 模型,好处是可以单文件存储,但是使用的时候需要依赖keras/tensorflow,同时目前 opencv dnn 模块还不支持加载keras/hdf5
模型,所以我们可以选择将其转换为其他格式的模型:

例如 tensorflowpb模型:
https://stackoverflow.com/questions/69633595/load-onnx-model-in-opencv-dnn

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# vim:fenc=utf-8
#

import tensorflow as tf
from tensorflow import keras
from tensorflow.python.framework.convert_to_constants import convert_variables_to_constants_v2
from tensorflow.keras.models import load_model
import numpy as np

# path of the directory where you want to save your model
frozen_out_path = '输出模型路径' # name of the .pb file
frozen_graph_filename = "输出模型文件名" # “frozen_graph”

model = load_model("keras模型路径.h5")
# model = ""# Your model# Convert Keras model to ConcreteFunction
full_model = tf.function(lambda x: model(x))
full_model = full_model.get_concrete_function(
tf.TensorSpec(model.inputs[0].shape, model.inputs[0].dtype)) # Get frozen ConcreteFunction
frozen_func = convert_variables_to_constants_v2(full_model)
frozen_func.graph.as_graph_def()
layers = [op.name for op in frozen_func.graph.get_operations()]
print("-" * 60)
print("Frozen model layers: ")
for layer in layers:
print(layer)
print("-" * 60)
print("Frozen model inputs: ")
print(frozen_func.inputs)
print("Frozen model outputs: ")
print(frozen_func.outputs) # Save frozen graph to disk
tf.io.write_graph(graph_or_graph_def=frozen_func.graph,
logdir=frozen_out_path,
name=f"{frozen_graph_filename}.pb",
as_text=False) # Save its text representation
tf.io.write_graph(graph_or_graph_def=frozen_func.graph,
logdir=frozen_out_path,
name=f"{frozen_graph_filename}.pbtxt",
as_text=True)

此时我们能够得到tensorflowpb以及pbtxt模型。

关于opencv加载pb模型失败或分类错误问题

如果 opencv dnn 加载pb模型失败,或者与tensorflow的结果差距较大,可以试着不加载 pbtxt 配置。输入的Mat使用方式:

1
cv::Mat blob = cv::dnn::blobFromImage(img, 1.0, Size(180, 180), Scalar(0,0,0),true);

基本可以得到正确的结果。

关于 onnx

如果 opencv dnn 加载 pb 模型一直失败,或者得到错误的分类结果,则可以考虑使用 onnx 模型。

需要安装tf2onnx

1
pip install -U tf2onnx

macos 下如果无法安装 onnxruntime,可以尝试使用测试仓库安装:

1
pip3 install -i https://test.pypi.org/simple/ onnxruntime

然后使用转换命令,进行转换:

1
python -m tf2onnx.convert --input 模型地址.pb  --inputs 模型输入 --outputs 模型输出 --output 输出模型名字.onnx

将可以得到模型onnx模型。

可以考虑简化一下onnx

1
python3 -m onnxsim model.onnx model-sim.onnx

关于 ncnn

如果不想使用opencv dnn 模块,或者opencv dnn 在arm下面速度比较慢,可以考虑使用ncnn 推导🐶:

由于ncnn还没有发布版本包,所以需要自行下载源码、编译。

ncnn 仓库地址:http://github.com/Tencent/ncnn

编译好之后在 tools 下面将有所需要的工具:

img.png

模型转换

使用以下命令,我们将得到 ncnn 的模型:

1
./onnx2ncnn onnx模型.onnx my_mobileface.param my_mobileface.bin

然后就可以使用 ncnn 进行推导。

参考:https://blog.csdn.net/litt1e/article/details/116000118

opencv dnn c++ 测试 demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//
// Created by caesar kekxv on 2021/7/26.
//

#include "opencv2/opencv.hpp"
#include <iostream>
#include "string"

using namespace std;
using namespace cv;

int main(int argc, char *argv[]) {
cout << "OpenCV version : " << CV_VERSION << endl;
cv::dnn::Net tf_net = cv::dnn::readNetFromTensorflow("freez.pb");
cout << !tf_net.empty() << endl;
cv::dnn::Net net = cv::dnn::readNetFromONNX("model.onnx");
std::vector<string> className{"success\t", "error\t"};
std::cout << "['success','error']" << std::endl;
std::vector<string> files{
"推导图像列表"
};
for (auto &file: files) {
cv::Mat img = imread(file);

if (!net.empty()) {
cv::Mat blob;
cv::resize(img, blob, Size(180, 180));
blob.convertTo(blob, CV_32F);
blob = blob.reshape(1, {1, 180, 180, 3});
net.setInput(blob);
cv::Mat output = net.forward();
double minValue, maxValue; // 最大值,最小值
cv::Point minIdx, maxIdx; // 最小值坐标,最大值坐标
cv::minMaxLoc(output, &minValue, &maxValue, &minIdx, &maxIdx);
std::cout << className[maxIdx.x] << "\t" << " o" << output << "[ " << file << std::endl;
}
if (!tf_net.empty()) {
cv::Mat blob;
blob = cv::dnn::blobFromImage(img, 1.0, Size(180, 180), Scalar(0,0,0),true);
tf_net.setInput(blob);
cv::Mat output = tf_net.forward();
double minValue, maxValue; // 最大值,最小值
cv::Point minIdx, maxIdx; // 最小值坐标,最大值坐标
cv::minMaxLoc(output, &minValue, &maxValue, &minIdx, &maxIdx);
std::cout << className[maxIdx.x] << "\t" << " t" << output << "[ " << file << std::endl;
}
}
return 0;
}