深度学习
深度学习与机器学习的区别
特征提取
机器学习的特征工程步骤是要靠手动完成的,而且需要大量领域专业知识
深度学习通常由多个层组成,它们通常将更简单的模型组合在一起,通过将数据从一层传递到另一层来构建更复杂的模型。通过大量数据的训练自动得到模型,不需要人工设计特征提取环节。
数据量
机器学习需要的执行时间远少于深度学习,深度学习参数往往很庞大,需要通过大量数据的多次优化来训练参数
算法代表
机器学习
朴素贝叶斯、决策树等
深度学习
神经网络
深度学习的应用场景
图像识别
物体识别
场景识别
车型识别
人脸检测跟踪
人脸关键点定位
人脸身份认证
自然语言处理技术
机器翻译
文本识别
聊天对话
语音技术
语音识别
深度学习框架
常见的深度学习框架
Caffe
TensorFlow
Torch
Theano
deeplearning4j
CNTK
DSSTNE
TensorFlow
TensorFlow使用C++实现的,然后用Python封装
使用 tf.keras 构建、训练和验证您的模型,tf相关API用于损失计算修改等 tensorflow提供模型训练模型部署
TensorFlow的安装
安装 TensorFlow在64 位系统上测试这些系统支持 TensorFlow
Ubuntu 16.04 或更高版本
Windows 7 或更高版本
macOS 10.12.6 (Sierra) 或更高版本(不支持 GPU)
python 3.7.7
pip install tensorflow==1.14
poetry add tensorflow==1.14
TensorFlow的介绍使用
数据流图
TensorFlow是一个采用数据流图(data flow graphs),用于数值计算的开源框架。
TensorFlow 程序
构建图阶段
数据与操作的执行步骤被描述成一个图
执行图阶段
使用会话执行构建好的图中的操作
图
TensorFlow 将计算表示为指令之间的依赖关系的一种表示法
会话
TensorFlow 跨一个或多个本地或远程设备运行数据流图的机制
张量
TensorFlow 中的基本数据对象
表示在节点间相互联系的多维数据数组,先
节点
提供图当中执行的操作
在图中表示数学操作
TensorFlow实现一个加法运算
import tensorflow as tf
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' # 警告指出你的CPU支持AVX运算加速了线性代数计算,即点积,矩阵乘法,卷积等。可以从源代码安装TensorFlow来编译,选择关闭该警告
a = tf.constant(10)
b = tf.constant(20)
sum = tf.add(a, b)
print(a, b, sum)
with tf.compat.v1.Session() as sess:
sum_t = sess.run(sum)
print(sum_t)
图与TensorBoard
图相关操作
'''
默认图-TensorFlow会默认帮我们创建一张图
tf.get_default_graph()访问
op、sess都含有graph属性,默认都在一张图中
'''
import tensorflow as tf
a = tf.constant(10)
b = tf.constant(20)
num = tf.add(a, b)
with tf.compat.v1.Session() as sess:
num_t = sess.run(num)
print(num_t)
print(tf.get_default_graph())
print(a.graph)
print(b.graph)
print(num.graph)
print(sess.graph)
'''
创建图
new_g = tf.Graph()自定义创建图
图中创建OP,典型用法是使用new_g.as_default()上下文管理器
with new_g.as_default():
很少会同时开启不同的图,一般用默认的图就够了
'''
import tensorflow as tf
new_g = tf.Graph()
with new_g.as_default():
a1 = tf.constant(10)
b1 = tf.constant(20)
num1 = tf.add(a1, b1)
with tf.compat.v1.Session(graph=new_g) as sess:
num1_t = sess.run(num1)
print(num1_t)
print(tf.get_default_graph())
print(a1.graph)
print(b1.graph)
print(num1.graph)
print(sess.graph)
TensorBoard
TensorFlow提供了TensorBoard 可视化工具
可视化过程
生成一个序列化的 Summary protobuf 对象
tf.summary.FileWriter('./test/', graph=sess.graph)
在指定目录中生成一个 event 文件
格式-events.out.tfevents.{timestamp}.{hostname}
启动TensorBoard,读取 TensorFlow 的事件文件
tensorboard --host 127.0.0.1 --port=6006 --logdir="./test/"
常见OP
操作对象(Operation)是TensorFlow图中的节点, 接收0个或者多个输入Tensor, 并且可以输出0个或者多个Tensor,Operation对象是通过op构造函数(如tf.matmul())创建的
| 类型 | 实例 |
|---|---|
| 标量运算 | add, sub, mul, div, exp, log, greater, less, equal |
| 向量运算 | concat, slice, splot, constant, rank, shape, shuffle |
| 矩阵运算 | matmul, matrixinverse, matrixdateminant |
| 带状态的运算 | Variable, assgin, assginadd |
| 神经网络组件 | softmax, sigmoid, relu,convolution,max_pool |
| 存储, 恢复 | Save, Restroe |
| 队列及同步运算 | Enqueue, Dequeue, MutexAcquire, MutexRelease |
| 控制流 | Merge, Switch, Enter, Leave, NextIteration |
import tensorflow as tf
a = tf.constant(10)
b = tf.constant(20)
num = tf.add(a, b)
print(a, b, num)
'''
输出:
"Const:0", shape=(), dtype=int32) Tensor("Const_1:0", shape=(), dtype=int32) Tensor("Add:0", shape=(), dtype=int32)
用户可以指定指令名称,Tensorboard显示的名字也会被修改
a = tf.constant(10, name='a')
b = tf.constant(20, name='b')
num = tf.add(a, b, name='sum')
张量名称的形式为 "<OP_NAME>:<i>"
"<OP_NAME>" 是生成该张量的指令的名称
"<i>" 是一个整数,它表示该张量在指令的输出中的索引
'''
张量,可以理解成OP当中包含了这个值。每一个OP指令都对应一个唯一的名称,如上面的Const:0,在TensorBoard上面也可以显示
会话
TensorFlow 使用 tf.Session 类来表示客户端程序
会话两种开启方式
tf.Session:用于完整的程序当中
tf.InteractiveSession:用于交互式上下文中的TensorFlow ,例如shell
Session类
__init__(target='', graph=None, config=None)
target
默认空 会话将仅使用本地计算机中的设备,指定 grpc:// 网址,以便指定 TensorFlow 服务器的地址,这使得会话可以访问该服务器控制的计算机上的所有设备
graph
默认 默认图
config
指定tf.ConfigProto控制会话的行为
ConfigProto协议用于打印设备使用信息
sess = tf.Session(config=tf.ConfigProto(allow_soft_placement=True,
log_device_placement=True))
如/job:localhost/replica:0/task:0/device:CPU:0 device_type:类型设备(例如CPU,GPU,TPU)
close()
释放会话可能拥有的资源,如 tf.Variable,tf.QueueBase和tf.ReaderBase(将会话用作上下文管理器会自动释放)
run(fetches,feed_dict=None, options=None, run_metadata=None)
通过使用sess.run()来运行operation
fetches:单一的operation,或者列表、元组(其它不属于tensorflow的类型不行)
feed_dict:参数允许调用者覆盖图中张量的值,运行时赋值与tf.placeholder搭配使用
使用tf.operation.eval()也可运行operation,但需要在会话中运行
tensor.eval()
feed操作
'''
placeholder提供占位符
tf.placeholder(tf.float32) # 定义占位符
run时候通过feed_dict指定参数
sess.run(sum_ab, feed_dict={a: 3.0, b: 4.0})
'''
import tensorflow as tf
a = tf.placeholder(tf.int32, name='a')
b = tf.placeholder(tf.int32, name='b')
num = tf.add(a, b, name='sum')
with tf.compat.v1.Session(config=tf.ConfigProto(allow_soft_placement=True,
log_device_placement=True)) as sess:
num_t = sess.run(num, feed_dict={a:10, b:20})
print(num_t)
张量
TensorFlow 的张量就是一个 n 维数组, 类型为tf.Tensor
Tensor具有两个重要的属性
type:数据类型
tf.float32 32位浮点型
tf.float64 64位浮点型
tf.int64 64位有符号整型
tf.int32 32位有符号整型
tf.int16 16位有符号整型
tf.int8 8位有符号整型
tf.uint8 8位无符号整型
tf.string 可变长度的字节数组
tf.bool 布尔型
tf.complex64 有两个32位浮点数组成的复数,实数和虚数
tf.qint32 用于量化Ops的32位有符号整型
tf.qint8 用于量化Ops的8位有符号整型
tf.quint8 用于量化Ops的8位无符号整型
shape:形状(阶)
0 纯量
1 向量
2 矩阵
3 3阶张量
n n阶
创建张量指令
创建张量指令
固定值张量
tf.zeros(shape, dtype=tf.float32,name=None) 创建元素设置为0的张量
tf.zeros_like(tensor, dtype=None,name=None) 创建元素设置为0的张量,和tensor相同类型和形状
tf.ones(shape, dtype=tf.float32,name=None) 创建元素设置为1的张量
tf.ones_like(tensor, dtype=None,name=None) 创建元素设置为1的张量,和tensor相同类型和形状
tf.fill(dimes,value,name=None) 创建元素设置为value的张量
tf.constant(value, dtype=None,shape=None, name='Const') 创建一个常数张量
随机值张量
tr.truncated_normal(shape,mean=0.0,stddev=1.0,dtype=tf.float32,seed=None,name=None) 从截断的正态分布中输出随机值,和tr.random_normal一样,但是所有数字都不超过两个标准差
tr.random_normal(shape,mean=0.0,stddev=1.0,dtype=tf.float32,seed=None,name=None)
从正态分布中输出随机值,由随机正态分布的数字组成的矩阵
特殊的创建张量的op
tf.Variable
tf.placeholder
代码:
'''
创建张量指令
'''
import tensorflow as tf
a = tf.ones(shape=(1,2),dtype=tf.int32, name='as')
b = tf.ones_like(a,dtype=tf.float32, name='as')
c = tf.zeros(shape=(1,2),dtype=tf.int32, name='as')
d = tf.zeros_like(c,dtype=tf.float32, name='as')
e = tf.fill(dims=(1,2), value='123')
f = tf.constant(value='123')
g = tf.truncated_normal(shape=(10,10), dtype=tf.float32)
h = tf.random_normal(shape=(10,10), dtype=tf.float32)
print(a, b, c, d, e, f, g, h)
张量的变换
张量的变换
类型改变 (注意,字符串不能直接转为数字,需使用string_to_number)
tf.string_to_number(x, out_type=tf.int32) 将字符串转为数字
tf.to_double(x) = tf.cast(x,dtype=tf.double)
tf.to_float(x) = tf.cast(x,dtype=tf.float32)
tf.to_bfloat16(x) = tf.cast(x,dtype=tf.bfloat16)
tf.to_int32(x) = tf.cast(x,dtype=tf.int32)
tf.to_int64(x) = tf.cast(x,dtype=tf.int64)
tf.cast(x,dtype,name=None)
形状改变
tensor.reshape(shape=[1,2,3]) 张量的元素个数一致,直接操作元素
tf.set_shape(shape=[1,2,3]) 设置静态形状,已经有固定形状的的张量无法设置 [None,None] 可以设置为None的部分,返回新张量
类型改变代码:
'''
类型改变
'''
import tensorflow as tf
a = tf.constant(value='123')
b = tf.string_to_number(a, out_type=tf.int32)
c = tf.to_double(b)
d = tf.to_float(b)
e = tf.to_bfloat16(b)
f = tf.to_int32(b)
g = tf.to_int64(b)
h = tf.cast(b,dtype=tf.int32)
print(a,b,c,d,e,f,g,h)
形状改变代码:
'''
形状改变
'''
import tensorflow as tf
a = tf.constant(value=[[1,2]])
b = tf.placeholder(dtype=tf.int32, shape=[2,3])
c = tf.placeholder(dtype=tf.int32, shape=[2,None])
# a.set_shape((2,1)) # 报错
a_n = tf.reshape(a, shape=[2,1]) # 数量一致,可以转换
# b.set_shape((2,2)) # 报错
b_n = tf.reshape(b, shape=[1,2,3]) # 数量一致,可以转换
# c.set_shape((3,5)) # 报错 不能改变已有形状
c.set_shape((2,5)) # 部分未固定或者都为固定可以设置形状
c_n = tf.reshape(c, shape=[5,2]) # 数量一致,可以转换
张量的方法和属性
张量的方法和属性
get_shape 获取静态形状
graph
name
dtype
shape
op
张量的数学运算
张量的数学运算
算术运算符
基本数学函数
矩阵运算
reduce操作
序列索引操作
变量OP
TensorFlow变量是表示程序处理的共享持久状态的最佳方法
变量特点
存储持久化,
可修改值
可指定被训练
迁移学习(指定某些网络结构不被训练)
创建变量
tf.Variable(initial_value=None,trainable=True,collections=None,name=None)
init = tf.global_variables_initializer() # 初始化变量
sess.run(init)
修改变量的命名空间
with tf.variable_scope("name")语句中创建变量即可
代码:
'''
变量op
输出:<tf.Variable 'user/a:0' shape=() dtype=int32_ref> <tf.Variable 'user/b:0' shape=() dtype=int32_ref> Tensor("user/mul:0", shape=(), dtype=int32)
200
'''
import tensorflow as tf
with tf.variable_scope('user'):
a = tf.Variable(initial_value=10, name='a')
b = tf.Variable(initial_value=20, name='b')
c = tf.multiply(a, b, name='mul')
print(a,b,c)
init = tf.global_variables_initializer()
with tf.compat.v1.Session() as sess:
sess.run(init)
c_n = sess.run(c)
print(c_n)
实现线性回归
样本:特征值、目标值
随机生成100个点,特征只有一个
y=0.8*x+0.7,目标值(100,1)
w,b x[w]+[b]=y_predict(预期值)
步骤
准备好数据集:y = 0.8x + 0.7 100个样本
建立线性模型
随机初始化W1和b1(必须要可被训练的,要使用GradientDescentOptimizer训练)
y = W·X + b,目标:求出权重W和偏置b
确定损失函数(预测值与真实值之间的误差)-均方误差
梯度下降优化损失:需要指定学习率(超参数) w_n = w - 学习率 b_n = b - 学习率
API
运算
tf.matmul(x,w) 矩阵运算
tf.square(error) 平方
tf.reduce_mean(error) 不指定轴,计算全局均值
梯度下降优化
tf.train.GradientDescentOptimizer(learning_rate)
learning_rate:学习率,一般为0~1之间比较小的值
方法
minimize(loss)
返回梯度下降op
增加命名空间
with tf.variable_scope("original_data")
with tf.variable_scope("linear_model")
with tf.variable_scope("loss")
with tf.variable_scope("optimizer")
观察训练情况
先收集变量(标量使用scalar,多维使用histogram,第一个参数为命名)
tf.summary.scalar("error", error)
tf.summary.histogram("weights", weights)
tf.summary.histogram("bias", bias)
合并变量
merge = tf.summary.merge_all()
返回op
运行合并变量(每次迭代都要运行)
summary = sess.run(merge)
添加到输出文件
file_writer.add_summary(summary, i)
file_writer为tf.summary.FileWriter()返回值
i 为标识迭代次数
观察
使用tensorboard观察
模型的保存与加载
tf.train.Saver(var_list=None,max_to_keep=5)
保存和加载模型 保存文件格式:checkpoint文件
var_list 指定将要保存和还原的变量。它可以作为一个dict或一个列表传递
max_to_keep 指示要保留的最近检查点文件的最大数量
方法
save(sess, './ckpt/myregression.ckpt') 迭代之后保存即可,需要sess
restore(sess, '/ckpt/myregression.ckpt') 需要sess,restore之后,可以使用原训练到的变量
tf.train.latest_checkpoint('./ckpt')
The full path to the latest checkpoint or `None` if no checkpoint was found.
用于判断模型是否存在,需指定目录
命令行参数使用
定义命令行参数
tf.flags.DEFINE_integer("max_step", 0, "训练模型的步数")
tf.flags.DEFINE_string("model_dir", " ", "模型保存的路径+模型名字")
tf.flags.DEFINE_boolean()
tf.flags.DEFINE_float()
获取命令行参数
FLAGS = tf.flags.FLAGS
FLAGS.max_step
完整代码
import tensorflow as tf
tf.flags.DEFINE_integer("max_step", 10000, "模型训练的步数")
tf.flags.DEFINE_string("model_dir", './ckpt/my.ckpt', "模型保存的路径")
tf.flags.DEFINE_string("event_dir", './event', "事件保存的路径目录")
Flags = tf.flags.FLAGS
max_step = Flags.max_step
model_dir = Flags.model_dir
event_dir = Flags.event_dir
with tf.variable_scope("original_data"):
# 100个样本,每个样本一个特征值
x = tf.random_normal(shape=(100,1), mean=0, stddev=1.0,name='original_data_x')
# 根据 y = 0.8x + 0.7 得出目标值
y_true = tf.matmul(x,[[0.8]],name='original_data_y') + 0.7
with tf.variable_scope("linear_model"):
# 随机初始化w和b
w = tf.Variable(initial_value=tf.random_normal(shape=(1,1)),trainable=True, name='weights')
b = tf.Variable(initial_value=tf.random_normal(shape=(1,1)),trainable=True, name='bias')
# 计算预期值
y_predict = tf.matmul(x, w, name='original_data_y') + b
with tf.variable_scope('loss'):
loss = tf.reduce_mean(tf.square(y_predict-y_true), name='error')
with tf.variable_scope("optimizer"):
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.001).minimize(loss)
init = tf.global_variables_initializer()
# 收集变量(标量使用scalar,多维使用histogram,第一个参数为命名)
tf.summary.scalar("loss", loss)
tf.summary.histogram("weights", w)
tf.summary.histogram("bias", b)
# 合并变量
merge = tf.summary.merge_all()
saver = tf.train.Saver()
with tf.compat.v1.Session() as sess:
sess.run(init)
# 检查是否已经有保存的模型了
if tf.train.latest_checkpoint('./ckpt'):
saver.restore(sess, model_dir)
print(f"获取保存的模型,误差为{loss.eval()},权重为{w.eval()},偏置为{b.eval()}")
# 可以直接进行预测 可以改成其他操作
exit()
filewriter = tf.summary.FileWriter(logdir=event_dir, graph=sess.graph)
for i in range(max_step):
sess.run(optimizer)
print(f"第{i+1}次,误差为{loss.eval()},权重为{w.eval()},偏置为{b.eval()}")
# 运行合并变量(每次迭代都要运行)
summary = sess.run(merge)
# 添加到输出文件
filewriter.add_summary(summary, i)
saver.save(sess, model_dir)
逻辑回归(神经网络基础)
逻辑回归是一个主要用于二分分类类的算法,输出一个该样本属于1对应类别的预测概率y^=P(y=1∣x)
逻辑回归损失函数
损失函数(loss function)用于衡量预测结果与真实值之间的误差。最简单的损失函数定义方式为平方差损失
衡量了在单个训练样本上的表现
逻辑回归损失函数一般使用 L(a,y) = −(yloga)−(1−y)log(1−a)
a为预期值
y为目标值
理解:
y=1,损失为-loga,a值越大越接近1,损失越小
y=0,损失为-log(1−a),a值越小越接近0,损失越小
代价函数
代价函数(cost function)(J(w,b))衡量的是在全体训练样本上的表现,即衡量参数 w 和 b 的效果,所有训练样本的损失平均值
将 m 个样本的损失函数求和然后除以 m
损失函数只适用于单个训练样本,而代价函数针对的是训练集
梯度下降算法
逻辑回归的代价函数图像是一个凸函数,像个大碗一样,凸函数的特征之一是具有全局最小值
目的:使损失函数的值找到最小值
方式:梯度下降
逻辑回归的代价函数图像,横轴表示空间参数 w 和 参数 b ,参数 w 通常是多维的,但为了绘图方便,将 w 和 b 都定义为单维度实数,代价函数 J(w,b) 是关于水平轴 w 和 b 的曲面,曲面的高度是代价函数 J(w,b) 在某一点的值。我们训练神经网络的目标是找到代价函数J(w,b)取得最小值时,对应的参数 w 和 b
参数w和b的更新公式 := 表示更新参数, α为学习速率
w:= w-α*dJ(w,b)/dw
b:= b-α*dJ(w,b)/db
α 表示学习速率,每次更新的 w 的步伐长度。当 w 大于最优解 w′ 时,导数大于 0,那么 w 就会向更小的方向更新。反之当 w 小于最优解 w′ 时,导数小于 0,那么 w 就会向更大的方向更新。迭代直到收敛。
常见函数导数
| 函数 | 导数 |
|---|---|
| f(a)=a^2 | 2a |
| f(a)=a^3 | 3a^2 |
| f(a)=ln(a) | 1/a |
| f(a)=e^a | e^a |
| σ(z)=1/(1+e^-z) | σ(z)(1−σ(z)) |
逻辑回归的梯度下降
逻辑回归的梯度下降从前往后的计算
z = (w^T)x+b
a = σ(z) 预期值
L(a,y) = −(yloga)−(1−y)log(1−a)
J(w,b)代价函数关于z得导数
dJ/da = -y/a + (1-y)/(1-a)
da/dz = a(1-a)
dz = dJ/da * da/dz = a - y
J(w,b)代价函数关于w得导数
dw1 = dz * x1
dw2 = dz * x2
...
J(w,b)代价函数关于b得导数
db = dz
计算损失函数的某个点相对于w1,w2,b的导数之后,就可以更新这次优化后的结果
w1:= w1-α*dJ(w1,b)/dw1
w2:= w2-α*dJ(w2,b)/dw2
b:= b-α*dJ(w,b)/db
向量化并行计算更加快速
'''
输出
计算所用时间0.32294511795043945
向量计算所用时间0.0009639263153076172
'''
import numpy as np
import time
num = 1000000
a = np.random.rand(num)
b = np.random.rand(num)
c = 0
d = 0
start = time.time()
for i in range(num):
c = c + a[i]*b[i]
end = time.time()
d = np.dot(a,b)
end1 = time.time()
print(f'计算所用时间{end-start}')
print(f'向量计算所用时间{end1-end}')
向量化编程伪代码
实现多个样本向量化计算的伪代码(大写字母为向量)
n个特征,m个样本(n,m)
W=np.zeros([n,1]),b=0
Z=np.dot(W^T, X) + b -------- (1,m)
A=σ(z)
每个样本梯度计算过程
# 计算损失 L(a,y) = −(yloga)−(1−y)log(1−a)
cost = 1 / m * np.sum(-Y * np.log(A) - (1 - Y) * np.log(1 - A))
dZ=A-Y
dW=1/m*X*(dZ^T)
db=1/m*np.sum(dZ)
更新
W:=W-αdW
b:=b-αdb
实现逻辑回归
安装scikit-learn
poetry add scikit-learn==0.24.1
构造分类数据
X,Y = make_classification(n_samples=5000, n_features=5, n_classes=2)
500个样本,5个特征,2个目标值
x_train, x_test, y_train, y_test = train_test_split(X, Y, test_size=0.3)
0.3部分作为测试集,其余做训练集
代码
import numpy as np
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
def basic_sigmoid(x):
'''
计算sigmoid函数
'''
s = 1 / (1 + np.exp(-x))
return s
def initialize_with_zeros(shape):
'''
创建一个形状为(shape,1)的w参数和b=0
'''
w = np.zeros(shape=(shape,1))
b = 0
return w,b
def propagate(w, b, X, Y):
# 从前往后计算损失(前向传播)
# 从后往前求出梯度(反向传播)
'''
参数:w,b,X,Y:网络参数和数据
Return:
损失cost、参数W的梯度dw、参数b的梯度db
'''
m = X.shape[1]
# (n, 1)^T * (n, m) = (1, m)
Z = np.dot(w.T, X) + b
A = basic_sigmoid(Z)
# 计算损失
cost = 1 / m * np.sum(-Y * np.log(A) - (1 - Y) * np.log(1 - A))
dz = A - Y
# (n,m) * (1,m) ^ T = (n, 1)
dw = 1/m * np.dot(X, dz.T)
db = 1/m * np.sum(dz)
# 返回一个新的数组,其形状是输入数组的形状,去除所有单维维度
cost = np.squeeze(cost)
grads = {
'dw': dw,
'db': db
}
return grads, cost
def optimize(w, b, X, Y, num_iterations, learning_rate):
"""
参数:
w:权重,b:偏置,X特征,Y目标值,num_iterations总迭代次数,learning_rate学习率
Returns:
params:更新后的参数字典
grads:梯度
costs:损失结果
"""
costs = []
for i in range(num_iterations):
grads, cost = propagate(w, b, X, Y)
# 取出梯度
dw = grads['dw']
db = grads['db']
w = w - learning_rate * dw
b = b - learning_rate * db
if i % 100 == 0:
costs.append(cost)
print(f"损失结果 {i}: {cost}")
params = {"w": w,
"b": b}
grads = {"dw": dw,
"db": db}
return params, grads, costs
def model(x_train, x_test, y_train, y_test, num_iterations=200000, learning_rate=0.001):
# 修改数据形状
x_train = x_train.T
x_test = x_test.T
y_train = y_train.reshape(1, y_train.shape[0])
y_test = y_test.reshape(1, y_test.shape[0])
# 初始化参数
w, b = initialize_with_zeros(x_train.shape[0])
# 梯度下降
params, grads, costs = optimize(w, b, x_train, y_train, num_iterations, learning_rate)
# 获取训练的参数
# 预测结果
w = params['w']
b = params['b']
y_prediction_train = predict(w, b, x_train)
y_prediction_test = predict(w, b, x_test)
# 打印准确率
print("训练集准确率: {} ".format(100 - np.mean(np.abs(y_prediction_train - y_train)) * 100))
print("测试集准确率: {} ".format(100 - np.mean(np.abs(y_prediction_test - y_test)) * 100))
return None
def predict(w, b, X):
'''
利用训练好的参数预测
return:预测结果
'''
m = X.shape[1]
y_prediction = np.zeros(shape=(1, m), dtype=np.int32)
# 计算结果
A = basic_sigmoid(np.dot(w.T, X) + b)
for i in range(A.shape[1]):
if A[0, i] <= 0.5:
y_prediction[0, i] = 0
else:
y_prediction[0, i] = 1
return y_prediction
X,Y = make_classification(n_samples=5000, n_features=5, n_classes=2)
x_train, x_test, y_train, y_test = train_test_split(X, Y, test_size=0.3)
model(x_train, x_test, y_train, y_test)
神经网络与tf.keras
图片基础与tf.keras介绍
将图片文件转换成能够处理的数据
图片类型
灰度图
彩色图
组成图片的最基本单位是像素
图片三要素
长度
宽度
通道数
灰度图单通道
彩色图RGB三通道
在tensorflow中用张量表示图片
单个图片 [height,width,channel]
多个图片 [batch,height,width,channel] batch表示一个批次的张量数量
tf.keras
python编写的开源神经网络库,运行在tensorflow上
提供简单的API,减少认知困难的最佳实践
拥有强大的多GPU和分布式训练支持
常用模块
applications模块 - Keras应用程序是具有预训练权重的固定框架
callbacks模块 - 回调在模型训练期间在某些点调用的实用程序
datasets模块 - Keras内置数据集
initializers模块 - Keras初始化器序列化/反序列化
layers模块 - Keras层API 构建网络
losses模块 - 内置损失功能 现成的损失函数
metrics模块 - 内置指标
models模块 - 模型克隆代码,以及与模型相关的API
optimizers模块 - 内置优化器类
preprocessing模块 - Keras数据预处理工具
regularizers模块 - 内置正规化器
utils模块 - Keras实用程序
图片读取处理
tensorflow.python.keras.preprocessing.image下
要使用该模块需要下载Pillow库
poetry add Pillow==8.3.2
pip install Pillow==8.3.2
API
load_img(path=filepath, target_size,color_mode='grayscale')
path 图片路径
target_size 目标大小。用于调整图片长宽。不改变通道和图片
如:
image = load_img(path='X.png', target_size=(25,25))
print(image)
输出
<PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=384x256 at 0x10E51D6D8>
返回一个PIL.JpeglmagePlugin.JpeglmageFile对象。
color_mode rgb,grayscale
img_to_array(img, data_format, dtype) 读取值类型转换用于tensorflow使用
img PIL.JpeglmagePlugin.JpeglmageFile对象
data_format 可为channels_last和channels_first
dtype 类型
如:
image1 = img_to_array(image, data_format="channels_first")
image2 = img_to_array(image, data_format="channels_last")
image1.shape
image2.shape
图片特征值处理-图片大小都要求一致
NHWC和NCHW
读取设置图片形状两种格式
"channels_first" - NCHW 排列顺序为[batch, channels, height, width]
"channels_last" - NHWC 排列顺序为[batch, height, width, channels]
其中N表示这批图像有几张,H表示图像在竖直方向有多少像素,W表示水平方向像素数,C表示通道
数
Tensorflow默认的[height, width, channel] NHWC
理解记忆
假设1,2,3,4-红色;5,6,7,8-绿色;9,10,11,12-蓝色
img = tf.constant([1,2,3,4,5,6,7,8,9,10,11,12])
转换为"channels_first" [3,2,2], 可以发现在第一个通道找到有1,2,3,4即红色,所以转换为"channels_first"不会有问题
[[[ 1 2],
[ 3 4]],
[[ 5 6],
[ 7 8]],
[[ 9 10],
[11 12]]]
转换为"channels_last" [2,2,3], 可以发现在第一个通道找到有1,4,7,10有各种颜色,所以转换为"channels_last"有问题
[[[ 1 2 3],
[ 4 5 6]],
[[ 7 8 9],
[10 11 12]]]
解决转换为"channels_last"[2,2,3]
先转换为"channels_first"
img = tf.transpose(img, (1, 2, 0))进行维度交换,即将原来为1位置的维度放在最前面,以此类推
如:
img = tf.constant([1,2,3,4,5,6,7,8,9,10,11,12])
img = tf.reshape(img, (3, 2, 2))
img = tf.transpose(img, (1, 2, 0))
tf.keras数据集
CIFAR100小图片分类数据
50000张32x32大小的训练数据和10000张测试数据,总共100个类别。
cifar100.load_data()
返回值-两个元组
(x_train, y_train) uint8表示的RGB图像数据 尺寸(num_sample,32,32,3)
(x_test, y_test) uint8表示的类别标签 尺寸(num_sample,1)
如:
from tensorflow.python.keras.datasets import cifar100
(x_train, y_train), (x_test, y_test) = cifar100.load_data()
时装分类Mnist数据集
60,000张28x28总共10个类别的灰色图片,10,000张用于测试。
fashion_mnist.load_data()
返回值-两个元组
(x_train, y_train) uint8表示的RGB图像数据 尺寸(num_sample,28,28)
(x_test, y_test) uint8表示的类别标签 尺寸(num_sample)
如:
from tensorflow.python.keras.datasets import fashion_mnist
(x_train, y_train), (x_test, y_test) = fashion_mnist.load_data()
类别
0 T-shirt/top
1 Trouser
2 Pullover
3 Dress
4 Coat
5 Sandal
6 Shirt
7 Sneaker
8 Bag
9 Ankle boot
神经网络基础
人工神经网络( Artificial Neural Network, 简写为ANN)也简称为神经网络(NN)。
经典的神经网络结构包含三个层次的神经网络。分别输入层,输出层(全连接层)以及隐藏层。
神经网络的特点
- 每个连接都有个权值
- 同一层神经元之间没有连接
- 最后的输出结果对应的层也称之为全连接层
感知机(PLA: Perceptron Learning Algorithm))
感知机就是模拟这样的大脑神经网络处理数据的过程
感知机是一种最基础的分类模型,感知机最基础是这样的函数,逻辑回归用的sigmoid。这个感知机具有连接的权重和偏置
x1 w1
x2 w2 ->感知机(有激活函数)-> 0/1
x3 w3
u = 求和(wi * xi) + b
y = sign(u)
当u>0 y=1
当u<=0 y=0
playground使用
网址:http://playground.tensorflow.org
playground简单两类分类结果
一个感知机即可完成
感知机结构,能够很好去解决与、或等问题,但是并不能很好的解决异或等问题
(0,1)(1,1)
(0,0)(1,0)
该异或问题至少需要2个神经元可以解决
playground单神经元复杂的两类
一个感知机无法完成
playground多神经元复杂的两类
多个神经元可完成
神经网络原理
神经网络的主要用途在于分类,神经网络解决多分类问题最常用的方法是设置n个输出节点,其中n为类别的个数,n个输出节点概率和为1
训练过程中的计算机会尝试一点点增大或减小每个参数,看其能如何减少相比于训练数据集的误差,以望能找到最优的权重、偏置参数组合。
softmax回归(结果进行概率分布)
Softmax回归将神经网络输出转换成概率结果
softmax(y)i = e^yi / 求和(e^yj)
假设输出结果为:2.3, 4.1, 5.6
softmax的计算输出结果为:
y1_p = e^2.3/(e^2.3+e^4.1+e^5.6)
y2_p = e^4.1/(e^2.3+e^4.1+e^5.6)
y3_p = e^5.6/(e^2.3+e^4.1+e^5.6)
交叉熵损失
衡量神经网络预测的概率分布和真实答案的概率分布之间的距离
前提
目标值需要进行one-hot编码(即为0或者1),能与概率值一一对应
Hy(a) = -求和yi*(logai)
ai 为输出的概率
yi 为真实one-hot编码结果
值越大,损失越大,受符号影响即-1比-2损失大
神经网络最后的损失为平均每个样本的损失大小。对所有样本的损失求和取其平均值
梯度下降算法(同逻辑回归)
Tensorflow实现神经网络
tf.keras构建模型步骤
获取相关现有数据集
获取相关现有数据集
tensorflow.python.keras.datasets
mnist:手写数字
fashion_mnist:时尚分类
cifar10(100):10个类别分类
构建模型
构建模型
通过keras.Sequential构建
model = keras.Sequential([])
通过组合层来构建模型,层的堆叠,keras.layers中就有很多模型来实现层
keras.layers
from tensorflow.python.keras.layers import Dense
Dense(units,activation=None,**kwargs) 添加一层神经元
units 神经元个数
activation 激活函数,可以为tf.nn.relu,tf.nn.softmax(用于最后一层概率输出,配合units个数作为结果分类数目),tf.nn.sigmoid,tf.nn.tanh
**kwargs 输入上层输入的形状,input_shape=()
from tensorflow.python.keras.layers import DepthwiseConv2D
from tensorflow.python.keras.layers import Dot
from tensorflow.python.keras.layers import Dropout
from tensorflow.python.keras.layers import ELU
from tensorflow.python.keras.layers import Embedding
from tensorflow.python.keras.layers import Flatten
Flatten(input_shape=(28, 28)) 将输入数据进行形状改变展开-用于输入层
from tensorflow.python.keras.layers import GRU
from tensorflow.python.keras.layers import GRUCell
from tensorflow.python.keras.layers import LSTMCell
如:
import tensorflow as tf
from tensorflow.python import keras
model = keras.Sequential([
keras.layers.Flatten(input_shape=(28,28)),
keras.layers.Dense(units=128, activation=tf.nn.relu),
keras.layers.Dense(units=10, activation=tf.nn.softmax)
])
print(model)
print(model.layers)
print(model.inputs)
print(model.outputs)
print(model.summary())
通过keras.Model构建
Model(inputs=a,outputs=b)
inputs 定义模型的输入
使用tensorflow.python.keras.layers.Input构建
outputs 定义模型的输出
可使用tensorflow.python.keras.layers.Dense构建
def call(self,inputs) 可以接受上层的输入的方法实现
Model属性方法(keras.Sequential一样具有)
model.layers 模型的层级
model.inputs 模型的输入
model.outputs 模型的输出
model.summary() 打印模型的摘要表示
Model: "model"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_1 (InputLayer) [(None, 784)] 0
_________________________________________________________________
dense_2 (Dense) (None, 128) 100480
_________________________________________________________________
dense_3 (Dense) (None, 10) 1290
=================================================================
Total params: 101,770
Trainable params: 101,770
Non-trainable params: 0
___________________________
compile() 同训练与评估中的compile方法
fit() 训练
evaluate() 评估
predict() 预测
save_weights('./weights/my_model') 保存模型 可为ckpt格式和h5格式(h5格式结尾)
load_weights('./weights/my_model') 加载模型
如:
from tensorflow.python.keras.layers import Input,Dense
import tensorflow as tf
from tensorflow.python import keras
data = Input(shape=(784,))
output1 = Dense(128, activation=tf.nn.relu)(data)
output = Dense(10, activation=tf.nn.softmax)(output1)
model1 = keras.Model(inputs=data, outputs = output)
print(model1)
print(model1.layers)
print(model1.inputs)
print(model1.outputs)
print(model1.summary())
训练与评估-模型配置
模型配置
调用keras.Sequential对象或keras.Model对象的 compile 方法去配置该模型所需要的训练参数以及评估方法
compile(optimizer,loss=None,metrics=None)
optimizer 梯度下降优化器(在keras.optimizers)
loss=None 损失类型,类型可以是字符串或者该function名字
metrics=None, metrics=['accuracy'] 准确率评估
梯度下降优化器keras.optimizers
from tensorflow.python.keras.optimizers import Adadelta
from tensorflow.python.keras.optimizers import Adagrad
from tensorflow.python.keras.optimizers import Adam
from tensorflow.python.keras.optimizers import Adamax
from tensorflow.python.keras.optimizers import Nadam
from tensorflow.python.keras.optimizers import Optimizer
from tensorflow.python.keras.optimizers import RMSprop
from tensorflow.python.keras.optimizers import SGD
随机梯度下降
from tensorflow.python.keras.optimizers import deserialize
from tensorflow.python.keras.optimizers import get
from tensorflow.python.keras.optimizers import serialize
from tensorflow.python.keras.optimizers import AdamOptimizer()
损失类型计算keras.losses
from tensorflow.python.keras.losses import KLD
kullback_leibler_divergence
from tensorflow.python.keras.losses import MAE
mean_absolute_error
from tensorflow.python.keras.losses import MAPE
mean_absolute_percentage_error
from tensorflow.python.keras.losses import MSE
mean_squared_error
均方误差
from tensorflow.python.keras.losses import MSLE
mean_squared_logarithmic_error
from tensorflow.python.keras.losses import binary_crossentropy
类似交叉熵损失
from tensorflow.python.keras.losses import categorical_crossentropy
交叉熵损失(对于目标值是one-hot编码的)
from tensorflow.python.keras.losses import categorical_hinge
from tensorflow.python.keras.losses import cosine
cosine_proximity
from tensorflow.python.keras.losses import deserialize
from tensorflow.python.keras.losses import get
from tensorflow.python.keras.losses import hinge
from tensorflow.python.keras.losses import logcosh
from tensorflow.python.keras.losses import poisson
from tensorflow.python.keras.losses import serialize
from tensorflow.python.keras.losses import sparse_categorical_crossentropy
类似交叉熵损失(对于目标值是整型的)
from tensorflow.python.keras.losses import squared_hinge
训练与评估-模型训练
模型训练
调用keras.Sequential对象或者keras.Model对象的 fit 方法进行训练
fit(x=None,y=None,batch_size=None,epochs=1,callbacks=None)
X 特征值
y 目标值
batch_size 每次迭代中每次训练训练的样本数量
epochs 训练迭代次数,每次迭代所有样本训练一遍
callbacks 添加回调列表(用于如tensorboard显示)
可以使用回调来获取培训期间内部状态和模型统计信息的视图
定制化保存模型
keras.callbacks.ModelCheckpoint(filepath, monitor='val_loss', save_best_only=False,save_weights_only=False, mode='auto', period=1)
filepath 保存模型字符串
weights.{epoch:02d}-{loss:.2f}.h5格式,每隔epoch number数量并且将验证集的损失保存在该位置
weights.{epoch:02d}-{acc:.2f}.h5格式,每隔epoch number数量并且将验证集的损失保存在该位置
monitor
设置为'acc'或者'loss'
save_best_only
True, 与monitor配合,只保留比上次模型更好的结果
save_weights_only
True, 为(model.save_weights(filepath))只保存模型的权重和偏置, False 为 (model.save(filepath))保存整个模型.
mode
{auto, min, max} 如果save_best_only=True,val_acc要设置max,val_loss要设置min,设置为auto即可
period 迭代保存checkpoints的间隔
保存events文件
keras.callbacks.TensorBoard(log_dir='./logs',
write_graph=True,write_images=False,update_freq="epoch")
log_dir 保存事件文件目录
write_graph 是否显示图结构
write_image 是否显示图片
update_freq 默认'epoch' 即生成 epoch_acc和epoch_loss图,X轴为迭代次数,填其他如2则生成batch_acc,batch_loss图,X轴为样本训练数量
如:
# 每次迭代当loss比之前小的时候记录checkpoint,只保存权重和偏置。保存的h5文件可以用来恢复模型
mck = keras.callbacks.ModelCheckpoint(filepath="./ckpt/loss.{epoch:02d}-{loss:.2f}.h5",
monitor="loss",
save_best_only=True,
save_weights_only=True,
mode='auto',
period=1)
# 设置TensorBoard显示 epoch_acc和epoch_loss图,batch_acc,batch_loss图
tb = keras.callbacks.TensorBoard(log_dir='./event', write_graph=True, write_images=True)
# 取样本中的128个训练,取完之后一次迭代结束,迭代5次
SingleNN.model.fit(self.x_train, self.y_train, epochs=5, batch_size=128, callbacks=[mck,tb])
训练与评估-模型评估
模型评估
调用keras.Sequential对象或者keras.Model对象的evaluate方法进行训练
test_loss, test_acc = evaluate(test_images,test_labels)
test_images 测试样本特征值
test_labels 测试样本目标值
test_loss 测试样本平均损失值
test_acc 测试样本准确率
训练与评估-模型预测
模型预测
调用keras.Sequential对象或者keras.Model对象的predict方法进行训练
prediction = predict(test)
test 测试样本特征值
prediction 维度 (样本数, 类别数【值为每个类别的概率】)
使用np.argmax(prediction, axis=1) 按第二个维度获取最大值的索引,得到 维度 (样本数【值为类别】, )
如:
prediction = model.predict(self.x_test)
print(np.argmax(prediction, axis=1))
保存和恢复模型
可保存为ckpt格式和h5格式(h5格式结尾)
保存模型 可为ckpt格式和h5格式(h5格式结尾)
save_weights('./weights/my_model')
加载模型
if os.path.exists('./ckpt/snn.h5'):
load_weights('./weights/my_model')
h5保存报错
original_keras_version = f.attrs['keras_version'].decode('utf8')
AttributeError: 'str' object has no attribute 'decode'
原因:h5py版本过高
解决: poetry add h5py==2.10.0 或 pip install h5py==2.10.0
案例-多层神经网络进行时装分类
70000 张灰度图像,涵盖 10 个类别。分辨率(28x28 像素)
标签 类别
0 T 恤衫/上衣
1 裤子
2 套衫
3 裙子
4 外套
5 凉鞋
6 衬衫
7 运动鞋
8 包包
import os
from os import path
import shutil
import numpy as np
from tensorflow.python import keras
import tensorflow as tf
class SingleNN(object):
# 建立神经网络模型
model = keras.Sequential([
keras.layers.Flatten(input_shape=(28,28)),
keras.layers.Dense(units=128, activation=tf.nn.relu),
keras.layers.Dense(units=10, activation=tf.nn.softmax)
])
def __init__(self):
self.ckpt = './ckpt'
self.eventdir = './graph'
# 返回两个元祖
(self.x_train,self.y_train),(self.x_test, self.y_test) = keras.datasets.fashion_mnist.load_data()
self.x_train = self.x_train / 255.0
self.x_test = self.x_test / 255.0
def singlenn_compile(self):
'''
编译模型优化器,损失,准确率
:return:
'''
SingleNN.model.compile(optimizer=keras.optimizers.Adam(),
loss=keras.losses.sparse_categorical_crossentropy,
metrics=["accuracy"])
return None
def singlenn_fit(self):
'''
进行fit训练
:return:
'''
# 训练前删除重建ckpt文件夹
if os.path.exists(self.ckpt):
shutil.rmtree(self.ckpt)
os.mkdir(self.ckpt)
# 训练前删除重建event文件夹
if os.path.exists(self.eventdir):
shutil.rmtree(self.eventdir)
os.mkdir(self.eventdir)
mck = keras.callbacks.ModelCheckpoint(filepath=os.path.join(self.ckpt ,"loss.{epoch:02d}-{loss:.2f}.h5"),
monitor="loss",
save_best_only=True,
save_weights_only=True,
mode='auto',
period=1)
tb = keras.callbacks.TensorBoard(log_dir=self.eventdir, write_graph=True)
SingleNN.model.fit(self.x_train, self.y_train, epochs=200, batch_size=256, callbacks=[mck,tb])
return None
def singlenn_evaluate(self):
test_loss, test_acc = SingleNN.model.evaluate(self.x_test, self.y_test)
print(test_loss, test_acc)
return None
def singlenn_predict(self):
# 模型存在则加载保存的最新检查点
if os.path.exists(self.ckpt):
# 获取文件夹中所有文件和文件夹的名称
files_and_folders = os.listdir(self.ckpt)
# 过滤出文件,排除文件夹
files = [file for file in files_and_folders if os.path.isfile(os.path.join(self.ckpt, file))]
# 按字母顺序排序
sorted_files = sorted(files)
SingleNN.model.load_weights(os.path.join(self.ckpt, sorted_files[-1]))
prediction = SingleNN.model.predict(self.x_test)
print(np.argmax(prediction, axis=1))
return None
if __name__ == '__main__':
model_exist = False
snn = SingleNN()
if not model_exist:
snn.singlenn_compile()
snn.singlenn_fit()
snn.singlenn_evaluate()
else:
snn.singlenn_predict()
深层神经网络
随着神经网络的深度加深,模型能学习到更加复杂的问题,功能也更加强大
使用浅层网络的时候很多分类等问题得不到很好的解决,所以需要深层的网络
前向传播
从前往后计算损失
反向传播
从后往前求出梯度,计算每一层权重和偏置每次迭代的变化梯度
网络优化
参数与超参数
参数
模型学习到的信息(模型自己能计算出来的)
W[l],b[l]
超参数(hyper parameters)
控制参数的输出值的一些网络信息(需要人经验判断)
学习速率 α
迭代次数 N
隐藏层的层数 L
每一层的神经元个数:n[1],n[2],...
激活函数 g(z) 的选择
深度学习领域是一个很大程度基于经验的过程
优化遇到问题及解决
梯度消失
在梯度函数上出现的以指数级递增或者递减的情况分别称为梯度爆炸或者梯度消失。
深度学习中的梯度消失(Vanishing Gradient Problem)是指在训练深度神经网络时,当网络层数较多时,反向传播过程中的梯度会乘以多个层的梯度,如果这些梯度的值都小于1(如Sigmoid或Tanh函数的梯度在输入值较大或较小时),那么乘积将迅速减小,导致网络中较早层的梯度几乎为零。以至于网络中较早层的权重更新非常缓慢或几乎不更新,导致训练过程难以进行。
原因: 在鞍点附近
局部最优
鞍点(saddle)是函数上的导数为零,但不是轴上局部极值的点。
减少损失的难度也来自误差曲面中的鞍点,而不是局部最低点。
在训练较大的神经网络、存在大量参数,并且成本函数被定义在较高的维度空间时,困在极差的局部最优基本
不会发生
‘鞍点附近的平稳段会使得学习非常缓慢,而这也是需要后面的动量梯度下降法、RMSProp 以及 Adam 优化算法能够加速学习的原因,它们能帮助尽早走出平稳段。
解决办法
解决办法
初始化参数策略
要随机初始化权重
在初始化的时候,W 参数要进行随机初始化,不可以设置为 0且都相同。b 因为不存在上述问题,可以设置为 0。
初始化权重的值选择
w 以标准正态分布初始化
如Dense已包含自己初始化均为 1。
梯度下降算法
Batch梯度下降算法(不采用)
每次迭代中持续同时处理所有的数据集
Mini-Batch梯度下降算法(采用)
每次迭代中持续同时处理Mini-Batch大小(2^n)的数据集
随机梯度下降算法(不采用)
每次迭代中持续同时处理Mini-Batch大小为1的数据集
如:
SingleNN.model.fit(self.x_train, self.y_train, epochs=5, batch_size=128)
动量梯度下降法
动量梯度下降(Gradient Descent with Momentum)是计算梯度的指数加权平均数,并利用该值来更新参数值,达到平滑的过程。
帮助梯度下降在相关方向上加速,在不相关方向上抑制振荡。
在梯度下降的每一步中,不仅考虑当前梯度,还考虑过去梯度的累加影响。这样做可以减少梯度下降过程中的震荡,并加快收敛速度。
RMSProp算法
RMSProp (Root Mean Square Prop)算法是在对梯度进行指数加权平均的基础上,引入平方和平方根。
RMSProp 算法的核心思想是调整每个参数的学习率,以便更好地控制梯度下降的步长。它通过计算参数梯度的平方的指数移动平均值来实现这一点,然后使用这个平均值来调整每个参数的学习率。
Adam算法
它结合了动量(Momentum)和 RMSProp 的概念,Adam 算法在很多情况下都表现出了很好的性能,尤其是在训练大型神经网络时。
API
keras.optimizers.Adam(lr=0.01,beta_1=0.9,beta_2=0.999, epsilon=None,decay=0.,amsgrad=False,)
建议使用默认值
学习率衰减
学习率不固定可以自己调整,算法中自带指数衰减学习率如Adam
标准化输入
对网络输入的特征进行标准化,能够缓解地图小时或者梯度爆炸
损失函数变得碗装结构
self.x_train = self.x_train / 255.0
self.x_test = self.x_test / 255.0
卷积神经网络
多层的线性网络和单层的线性网络没有区别,而且线性模型的能够解决的问题也是有限的
激活函数选择
涉及到网络的优化时候,会有不同的激活函数选择有一个问题是神经网络的隐藏层和输出单元用什么激活函 数。之前我们都是选用sigmoid函数,但有时其他函数的效果会好得多,大多数通过实践得来,没有很好的 解释性。
线性激活函数
非线性激活函数
使用线性激活函数和不使用激活函数、直接使用 Logistic 回归没有区别,那么无论神经网络有多少层,输出 都是输入的线性组合,与没有隐藏层效果相当,就成了最原始的感知器了。
tanh函数,双曲正切函数

效果比 sigmoid函数好,因为函数输出介于-1和1之间。 注:tanh函数存在和 sigmoid函数一样的缺点:当z趋紧无穷大(或无穷小),导数的梯度(即函数的斜 率)就趋紧于0,这使得梯度算法的速度会减慢。
ReLU函数(the rectified linear unit,修正线性单元)

当z>0时,梯度始终为1, 从而提高神经网络基于梯度算法的运算速度,收敛速度远大于sigmoid 和 tanh。然而当z<0时,梯度一直为0,但是实际的运用中,该缺陷的影响不是很大。
Leaky ReLU (带泄漏的ReLU)

Leaky ReLU保证在z<0的时候,梯度仍然不为0。理论上来说,Leaky ReLU有ReLU的所有优点,但在 实际操作中没有证明总是好于ReLU,因此不常用。
神经网络的种类
基础神经网络
线性神经网络,BP神经网络,Hopfield神经网络等
进阶神经网络
玻尔兹曼机,受限玻尔兹曼机,递归神经网络等
深度神经网络
深度置信网络,卷积神经网络(图像),循环神经网络,LSTM网络等
为什么需要卷积神经网络
目的:
减少网络参数数量,达到更好效果
解释:
图像特征数量对神经网络效果压力 假设一图片大小为28*28的黑白图片时候,每一个像素点只有一个值(单通道)。那么总的数值个数 为784个特征。
如果这张图片是彩色的,那么彩色围片由RGB三通道组成,也就意味着总的数值有28 28 3 = 2352个值。
一张图片的输入是2352个特征值,即神经网路当中与若干个神经元连接,假设第一个隐层是10个神经元,那么也就是23520个权重参数。
图片再大一些,假设图片为1000x1000x3,那么总共有3百万数值同样接入1个神经元了那么就是3千万个权重参数。
这样的参数大小,神经网络参数更新需要大量的计算不说,也很难达到更好的效果,就不倾向于使用多层神经网络了。所以就有了卷积神经网络的流行
发展
1989年,Yann LeCun提出了一种用反向传导进行更新的看积神经网络,称为LeNet。 1998年,Yann LeCun改进了原来的卷积网络,LeNet-5。
感受野
1962年Hubel和Wiesel通过对猫视觉皮展细胞的研究,提出了感受野(receptive field)的概念
,Fukushima基于感受野概念提出的神经认知机(neocognitron)可以看作是卷积神经网络的第一个实现网络
单个感受器与许多感觉神经纤维相联系,感觉信息是通过许多感受神经纤维发放总和性的空间与时间类型不同的冲动,相当于经过编码来传递。
边缘检测
为了能够用更少的参数,检测出更多的信息,基于上面的感受野思想。通常神经网络需要检测出物体最明显的垂直和水平边缘来区分物体。
目的
过滤器过滤图片得到水平边缘和垂直边缘一些物体形状结论
解释
6x6的图像卷积与3x3的过滤器(Filter or kenel)进行卷积运算
相当于将 Filter放在Image 上,从左到右、从上到下地(默认一个像素)移动过整个lmage,分别计算Imagelmage被Filter盖住的部分与Filter的逐元素乘积的和

6x6的图像中,左边一半像素的值全是10,右边一半像素的值全是0,中间是一条非常明显的垂直边缘。这个图像与过滤器卷积的结果中,中间两列的值都是30,两边两列的值都是0,即检测到了原 6x6图像中的垂直边缘。
组成
卷积神经网络由一个或多个卷积层、池化层以及全连接层等组成。
与其他深度学习结构相比,卷积神经网络在图像等方面能够给出更好的结果。
这一模型也可以使用反向传播算法进行训练。相比较其他浅层或深度神经网络,卷积神经网络需要考量的参数更少,使之成为一种颇真吸引力的深度学习结构。
卷积层(Convolutions)
目的
卷积运算的目的是提取输入的不同特征,某些卷积层可能只能提取一些低级的特征如边缘、线条和角等层级,更多层的网路能从低级特征中迭代提取更复杂的特征。
参数
size: 卷积核/过滤器大小,选择有1x1,3x3,5x5(奇数个)
padding: 零填充,Valid 与Same
stride: 步长,通常默认为1
运算过程
移动步长,观察结果
N为图片大小,F为卷积核大小
生成的大小 N-F+1
可能的缺点
图像变小
边缘信息丢失
如果我们换一个卷积核大小或者加入很多层卷积之后,图像可能最后就变成了1 X 1 大小
padding-零填充
零填充:在图片像素的最外层加上若干层0值,若一层,记做p =1
0在权重乘积和运算中对最终结果不造成影响,也就避免了图片增加了额外的干扰信息。
N为图片大小,F为卷积核大小,P为零填充层数
生成的大小 N+2 *P-F+1
填充方式
valid 不填充
same输出大小与原图一致
N+2 *P-F+1 = N
P = (F-1)/2
因此要使用奇数维度的过滤器(奇数维度的过滤器有中心,便于指出过滤器的位置)
stride-步长
对于输入图片大小为N,过滤器大小为F,步长为S,零填充为P(如果相除不是整数的时候,向下取整)
(N+2 *P-F)/S+1
多通道卷积
当输入有多个通道(channel)时(例如图片可以有 RGB 三个通道),卷积核需要拥有相同的channel数,每个卷积核 channel 与输入层的对应 channel 进行卷积,将每个 channel 的卷积结果按位相加得到最终的 Feature Map。
多卷积核(多个Filter)
当有多个卷积核时,可以学习到多种不同的特征,对应产生包含多个 channel 的 Feature Map
有n个 filter,所以 output 有n个 channel
这里的多少个卷积核也可理解为多少个神经元
总结
一般使用same的填充方式
所以卷积层充当特征提取的角色,但是并没有减少图片的特征数量,在最后的全连接层依然面临大量的参数,所以需要池化层进行特征数量的减少
池化层(Subsamping,Pooling)
池化层主要对卷积层学习到的特征图进行亚采样(subsampling)处理,主要由两种
- 最大池化:Max Pooling,取窗口内的最大值作为输出
- 平均池化:Avg Pooling,取窗口内的所有值的均值作为输出
意义在于:
- 降低了后续网络层的输入维度,缩减模型大小,提高计算速度
- 提高了Feature Map 的鲁棒性,防止过拟合
比如:
对于一个输入的图片,我们使用一个区域大小为2 *2,步长为2的参数进行求最大值操作
全连接层(Full connection)
卷积层+激活层+池化层可以看成是CNN的特征学习/特征提取层,而学习到的特征(Feature Map)最终应用于模型任务(分类、回归):
- 先对所有 Feature Map 进行扁平化(flatten, 即 reshape 成 1 x N 向量)
- 再接一个或多个全连接层,进行模型学习
CIFAR100类别分类
这个数据集就像CIFAR-10,除了它有100个类,每个类包含600个图像。,每类各有500个训练图像和100个测试图像。CIFAR-100中的100个类被分成20个超类。每个图像都带有一个“精细”标签(它所属的类)和一个“粗糙”标签(它所属的超类)

API 使用
构建CNN模型的API
keras.layers.Conv2D
实现卷积(2维)
keras.layers.Conv2D(32, kernel_size=5, strides=1,
padding='same', data_format='channels_last', activation=tf.nn.relu)
keras.layers.MaxPool2D
池化操作(2维)
keras.layers.MaxPool2D(pool_size=2, strides=2, padding='same')
步骤
模型规划
第一层
卷积:32个filter、大小5*5、strides=1、padding="SAME"
激活:Relu
池化:大小2x2、strides2
第一层
卷积:64个filter、大小5*5、strides=1、padding="SAME"
激活:Relu
池化:大小2x2、strides2
全连接层
1024神经元
100神经元
第一层
卷积:[None, 32, 32, 3]———>[None, 32, 32, 32]
权重数量:[5, 5, 3 ,32]
偏置数量:[32]
激活:[None, 32, 32, 32]———>[None, 32, 32, 32]
池化:[None, 32, 32, 32]———>[None, 16, 16, 32]
第二层
卷积:[None, 16, 16, 32]———>[None, 16, 16, 64]
权重数量:[5, 5, 32 ,64]
偏置数量:[64]
激活:[None, 16, 16, 64]———>[None, 16, 16, 64]
池化:[None, 16, 16, 64]———>[None, 8, 8, 64]
全连接层
[None, 8, 8, 64]——>[None, 8, 8, 64]
[None, 8 8 64] x [8, 8, 64, 1024] = [None, 1024]
[None,1024] x [1024, 100]——>[None, 100]
权重数量:[8, 8, 64, 1024] + [1024, 100],由分类别数而定
偏置数量:[1024] + [100],由分类别数而定
读取数据集
from tensorflow.python.keras.datasets import cifar100
class CNN(object):
def __init__(self):
(self.x_train, self.y_train), (self.x_test, self.y_test) = cifar100.load_data()
print(self.x_train.shape)
print(self.x_test.shape)
self.x_train = self.x_train / 255.0
self.x_test = self.x_test / 255.0
if __name__ == '__main__':
CNN()
进行模型编写
from tensorflow.python.keras.datasets import cifar100
from tensorflow.python import keras
import tensorflow as tf
class CNN(object):
model = keras.models.Sequential(
[
keras.layers.Conv2D(32, kernel_size=5, strides=1, padding='same', data_format='channels_last',
activation=tf.nn.relu),
keras.layers.MaxPool2D(pool_size=2, strides=2, padding='same'),
keras.layers.Conv2D(64, kernel_size=5, strides=1, padding='same', data_format='channels_last',
activation=tf.nn.relu),
keras.layers.MaxPool2D(pool_size=2, strides=2, padding='same'),
keras.layers.Flatten(),
keras.layers.Dense(1024, activation=tf.nn.relu),
keras.layers.Dense(100, activation=tf.nn.softmax),
]
)
其它完整代码
from tensorflow.python.keras.datasets import cifar100
from tensorflow.python import keras
import tensorflow as tf
class CNN(object):
model = keras.models.Sequential(
[
keras.layers.Conv2D(32, kernel_size=5, strides=1, padding='same', data_format='channels_last',
activation=tf.nn.relu),
keras.layers.MaxPool2D(pool_size=2, strides=2, padding='same'),
keras.layers.Conv2D(64, kernel_size=5, strides=1, padding='same', data_format='channels_last',
activation=tf.nn.relu),
keras.layers.MaxPool2D(pool_size=2, strides=2, padding='same'),
keras.layers.Flatten(),
keras.layers.Dense(1024, activation=tf.nn.relu),
keras.layers.Dense(100, activation=tf.nn.softmax),
]
)
def __init__(self):
(self.x_train, self.y_train), (self.x_test, self.y_test) = cifar100.load_data()
print(self.x_train.shape)
print(self.x_test.shape)
self.x_train = self.x_train / 255.0
self.x_test = self.x_test / 255.0
def cnn_compile(self):
self.model.compile(optimizer=keras.optimizers.Adam(), loss=keras.losses.sparse_categorical_crossentropy, metrics=['accuracy'])
def cnn_fit(self):
self.model.fit(x = self.x_train, y = self.y_train, epochs=1, batch_size=32)
def cnn_evaluate(self):
test_loss, test_acc = self.model.evaluate(self.x_test, self.y_test)
print(test_loss, test_acc)
if __name__ == '__main__':
cnn = CNN()
cnn.cnn_compile()
cnn.cnn_fit()
cnn.model.summary()
cnn.cnn_evaluate()
深度学习正则化
偏差与方差
数据集划分
训练集(train set)
用训练集对算法或模型进行训练过程
验证集(development set)
利用验证集(又称为简单交叉验证集,hold-out cross validation set)进行交叉验证,选择出最好的模型
测试集(test set)
最后利用测试集对模型进行测试,对学习方法进行评估。
在小数据量的时代,如100、1000、10000的数据量大小,可以将数据集按照以下比例进行划分
无验证集的情况:70% / 30%
有验证集的情况:60% / 20% / 20%
而在如今的大数据时代,拥有的数据集的规模可能是百万级别的,所以验证集和测试集所占的比重会趋向于变得更小。
100万数据量:98% / 1% /1%
超百万数据量:99.5% / 0.25% / 0.25%
以上这些比例可以根据数据集情况选择。
偏差与方差的意义
“偏差-方差分解”(bias-variance decomposition)是解释学习算法泛化性能的一种重要工具。
泛化误差可分解为偏差、方差与噪声,泛化性能是由学习算法的能力、数据的充分性以及学习任务本身的难度所共同决定的。
偏差(算法的差异)
度量了学习算法的期望预测与真实结果的偏离程度,即刻画了学习算法本身的拟合能力
方差(数据集的差异)
度量了同样大小的训练集的变动所导致的学习性能的变化,即刻画了数据扰动所造成的影响
噪声(学习任务本身的难度)
表达了在当前任务上任何学习算法所能够达到的期望泛化误差的下界,即刻画了学习问题本身的难度。
偏差、方差与我们的数据集划分的关系
1、训练集的错误率较小,而验证集/测试集的错误率较大,说明模型存在较大方差,可能出现了过拟合
2、训练集和测试集的错误率都较大,且两者相近,说明模型存在较大偏差,可能出现了欠拟合
3、训练集和测试集的错误率都较小,且两者相近,说明方差和偏差都较小,这个模型效果比较好。
解决办法
对于高方差,有以下几种方式:
获取更多的数据,使得训练能够包含所有可能出现的情况
正则化(Regularization)
寻找更合适的网络结构
对于高偏差,有以下几种方式:
扩大网络规模,如添加隐藏层或者神经元数量
寻找合适的网络架构,使用更大的网络结构,如AlexNet
训练时间更长一些
不断尝试,直到找到低偏差、低方差的框架。
正则化
简单的模型比复杂的泛化能力好。
对于训练来说,加入正则化。显示了两个网络的训练集损失值随epoch的变化情况。更大的网络很快就会把训练集损失训练到接近零。网络越大,训练数据学习的速度就越快(使训练损失很快降低),但是也更容易过拟合(导致训练和验证损失之间的巨大差异).

对于验证集合来讲。显示了L2正则化惩罚的影响。两个模型具有相同数量的参数,具有L2正则化的模型(点)也比参考模型(十字)更能减少过拟合。

正则化,即在成本函数中加入一个正则化项(惩罚项),惩罚模型的复杂度,防止网络过拟合。
L1与L2
由于L1正则化最后得到w向量中将存在大量的0,使模型变得稀疏化,因此L2正则化更加常用。
L2结果W趋近于0
为什么达到效果
因为加入L2正则化,W权重在更新时候,减小更快,减小的效果就是求Z=wx+b,Z就变小,求激活函数导数的时候,梯度就不会非常小,模型变得更加简单,不会发生过拟合
API
正则化器允许在优化过程中对层的参数或层的激活情况进行惩罚。
正则化器网络结构权重关键字参数:
kernel_regularizer : keras.regularizers.Regularizer的实例
from tensorflow.python.keras import regularizers
可用的正则化器
keras.regularizers.l1(0.01)
keras.regularizers.l2(0.01)
keras.regularizers.l1_l2(l1=0.01,l2=0.01)
目的不是为了让训练更好,而是在训练很好的条件下97%,在测试集表现不好89%有可能模型出现了过拟合,需要在原始网络当中增加正则化参数(L2正则化)
Droupout正则化
随机的对神经网络每一层进行丢弃部分神经元操作
对于网络的每一层会进行设置保留概率(不同层丢弃比率不一样),即keep_prob。假设keep_prob为0.8,那么也就是在每一层所有神经元有20%的概率直接失效,可以理解为0.
验证集上面,训练迭代5次之后,在验证集合上面效果,dropout拟合效果要好

Inverted droupout
这种方式会对每层进行如下代码操作
# 假设设置神经元保留概率
keep_prob = 0.8
# 随机建立一个标记1or0的矩阵,表示随机失活的单元,占比20%
dl = np.random.rand(al.shape[0], al.shape[1]) < keep_prob
# 让a1对应d1的为0地方结果为0
al = np.multiply(al, dl)
droupout为什么有效
加入了 dropout 后,输入的特征都存在被随机清除的可能,所以该神经元不会再特别依赖于任何一个输入特征,也就是不会给任何一个输入特征设置太大的权重。
通过传播过程,dropout 将产生和L2正则化相同的收缩权重的效果。
对于不同的层,设置的keep_prob大小也不一致,神经元较少的层,会设keep_prob为1.0,而神经元多的层则会设置比较小的keep_prob通常被使用在计算机视觉领域,图像拥有更多的特征,场景容易过拟合,效果被实验人员证明是很不错的。
调试时候使用技巧
dropout 的缺点是成本函数无法被明确定义,保证损失函数是单调下降的,确定网络没有问题,再次打开droupout才会有效。
API
制定丢弃率
keras.layers.Dropout(rate)
早停止法
通常我们在训练验证的时候,发现过拟合。可以得到下面这张损失图

通常不断训练之后,损失越来越小。但是到了一定之后,模型学到的过于复杂(过于拟合训练集上的数据的特征)造成测试集开始损失较小,后来又变大。模型的w参数会越来越大,那么可以在测试集损失减小一定程度之后停止训练。
但是这种方法治标不治本,得从根本上解决数据或者网络的问题
数据增强
数据增强指通过剪切、旋转/反射/翻转变换、缩放变换、平移变换、尺度变换、对比度变换、噪声扰动、颜色变换等一种或多种组合数据增强变换的方式来增加数据集的大小。
即使卷积神经网络被放在不同方向上,卷积神经网络对平移、视角、尺寸或照度(或以上组合)保持不变性,都会认为是一个物体。
那么TensorFlow 官方源码都是基于vgg与inception论文的图像增强介绍,全部通过tf.image相关API来预处理图像。并且提供了各种封装过tf.image之后的API。那么TensorFlow官网也给我们提供了一些模型的数据增强过程。
类别
离线增强
预先进行所有必要的变换,从根本上增加数据集的规模(例如,通过翻转所有图像,保存后数据集数量会增加2倍)
在线增强或称为动态增强
可通过对即将输入模型的小批量数据的执行相应的变化,这样同一张图片每次训练被随机执行一些变化操作,相当于不同的数据集了。
技术(在线增强)
翻转
tf.image.random_flip_left_right
你可以水平或垂直翻转图像
旋转
tf.image.rot90
将图像逆时针旋转90度的倍数
剪裁
tf.image.random_crop
随机从原始图像中采样一部分,然后将这部分图像调整为原始图像大小。这个方法更流行的叫法是随机裁剪。
平移、缩放等等
经典分类网络结构
卷积发展

LeNet-5解析
网络结构

- 激活层默认不画网络图当中,这个网络结构当时使用的是sigmoid和Tanh函数,还没有出现Relu函数
- 将卷积、激活、池化视作一层,即使池化没有参数
参数形状
| shape | size | parameters | |
|---|---|---|---|
| Input | (32,32,3) | 3072 | 0 |
| Conv1(f=5,s=1) | (28,28,6) | 4704 | 450+6 |
| Pool1 | (14,14,6) | 1176 | 0 |
| Conv2(f=5,s=1) | (10,10,16) | 1600 | 2400+16 |
| Pool2 | (5,5,16) | 400 | 0 |
| FC3 | (120,1) | 120 | 48000+120 |
| FC4 | (84,1) | 84 | 10080+84 |
| Ouput:softmax | (10,1) | 10 | 840+10 |
将近6w参数
AlexNet

- 输入:227x227x3
- 总参数量:60M=6000万,5层卷积+3层全连接
- 使用了非线性激活函数:ReLU
- 防止过拟合的方法:Dropout
- 批标准化层的使用
卷积网络结构的优化
整个过程:AlexNet—NIN—(VGG—GoogLeNet)—ResNet
- NIN:引入1 * 1卷积
- VGG,斩获2014年分类第二(第一是GoogLeNet),定位任务第一。
- 参数量巨大,140M = 1.4亿
- 19layers
- VGG 版本
- VGG16
- VGG19

输入: 224 x 224 x 3
- GoogleNet,2014年比赛冠军的model,这个model证明了一件事:用更多的卷积,更深的层次可以得到更好的结构。(当然,它并没有证明浅的层次不能达到这样的效果)
- 500万的参数量
- 22layers
- 引入了Inception模块
- Inception V1
- Inception V2
- Inception V3
- Inception V4

1MLP卷积(1 x 1卷积)
目的
提出了一种新的深度网络结构,称为“网络中的网络”(NIN),增强接受域内局部贴片的模型判别能力。
做法
- 对于传统线性卷积核:采用线性滤波器,然后采用非线性激活。
- 提出MLP卷积取代传统线性卷积核
作用或优点
- 重要作用:1x1的卷积核操作还可以实现卷积核通道数的降维和升维,实现参数的减小化。
- 多个1x1的卷积核,提高特征抽象能力 (Multilayer Perceptron,缩写MLP,就是一个多层神经网络)
介绍
假设输入是56x56x3,1 x 1卷积的过程,那么这里先假设只有3个1x1Filter,那么最终结果还是56x56x3。看作是对三个通道进行了线性组合
通常在卷积之后会加入非线性激活函数,在这里之后加入激活函数,就可以理解成一个简单的MLP网络了
他可以
- 保持通道数不变
- 提升通道数
- 减少通道数
- 减少参数量
Inception层
盗梦空间结构
目的
- 代替人手工去确定到底使用1x1,3x3,5x5还是是否需要max_pooling层,由网络自动去寻找适合的结构。并且节省计算。

- 特点
- 使每一个卷积/池化最终结果的长、宽大小一致
- 特殊的池化层,需要增加padding,步长为1来使得输出大小一致,并且选择32的通道数
- 最终结果28 x 28 x 256
- 使用更少的参数,达到跟AlexNet或者VGG同样类似的输出结果
Inception改进
改进目的:减少计算,如5 x 5卷积那的运算量

- 上面的参数:5 x 5 x 32 x 192 =153600
- 下面的参数:192 x 16 + 5 x 5 x 16 x 32 = 3072 + 12800 = 15872
GoogleNet结构

卷积神经网络学习特征可视化
将网络学习过程中产生的特征图可视化出来
- layer1,layer2学习到的特征基本是颜色、边缘等低层特征
- layer3学习到的特征,一些纹理特征,如网格纹理
- layer4学习到的特征会稍微复杂些,比如狗的头部形状
- layer5学习到的是完整一些的,比如关键性的区分特征
使用pre_trained模型进行VGG预测
Google 在提供VGG行预测的时候效果会更好一些,所以选择VGG来进行测试
模型
在tensorflow.keras.applications中已经存在很多现有模型
from tensorflow._api.v1.keras.applications import densenet
from tensorflow._api.v1.keras.applications import inception_resnet_v2
from tensorflow._api.v1.keras.applications import inception_v3
from tensorflow._api.v1.keras.applications import mobilenet
from tensorflow._api.v1.keras.applications import mobilenet_v2
from tensorflow._api.v1.keras.applications import nasnet
from tensorflow._api.v1.keras.applications import resnet50
from tensorflow._api.v1.keras.applications import vgg16
from tensorflow._api.v1.keras.applications import vgg19
from tensorflow._api.v1.keras.applications import xception
from tensorflow.python.keras.applications import DenseNet121
from tensorflow.python.keras.applications import DenseNet169
from tensorflow.python.keras.applications import DenseNet201
from tensorflow.python.keras.applications import InceptionResNetV2
from tensorflow.python.keras.applications import InceptionV3
from tensorflow.python.keras.applications import MobileNet
from tensorflow.python.keras.applications import MobileNetV2
from tensorflow.python.keras.applications import NASNetLarge
from tensorflow.python.keras.applications import NASNetMobile
from tensorflow.python.keras.applications import ResNet50
from tensorflow.python.keras.applications import VGG16
from tensorflow.python.keras.applications import VGG19
from tensorflow.python.keras.applications import Xception
使用VGG16模型
from tensorflow.python.keras.applications import VGG16
from tensorflow.python.keras.applications.vgg16 import decode_predictions
from tensorflow.python.keras.applications.vgg16 import preprocess_input
- preprocess_input:处理输入图片
- decode_predictions:对预测结果进行处理
模型获取以及已训练好的参数加载
使用过程中会自动获取和下载模型,如何下载失败可手动下载然后放在C:\Users\Administrator.keras\models目录下
图片的输入以及格式转换
from tensorflow.python.keras.applications import vgg16
from tensorflow.python.keras.preprocessing import image
import tensorflow as tf
class VGG(object):
model = vgg16.VGG16()
def vgg_predict(self):
img = image.load_img('./img_3.png', target_size=(224, 224))
img = image.img_to_array(img)
img = img.reshape((1, img.shape[0], img.shape[1], img.shape[2]))
img = vgg16.preprocess_input(img)
y = VGG.model.predict(img)
result = vgg16.decode_predictions(y)
res = result[0][0]
print(res[1], res[2])
if __name__ == '__main__':
vgg = VGG()
vgg.vgg_predict()
使用模型对数据进行处理和预测
from tensorflow.python.keras.applications import vgg16
from tensorflow.python.keras.preprocessing import image
import tensorflow as tf
class VGG(object):
model = vgg16.VGG16()
def vgg_predict(self):
img = image.load_img('./img_3.png', target_size=(224, 224))
img = image.img_to_array(img)
img = img.reshape((1, img.shape[0], img.shape[1], img.shape[2]))
img = vgg16.preprocess_input(img)
y = VGG.model.predict(img)
result = vgg16.decode_predictions(y)
res = result[0][0]
print(res[1], res[2])
if __name__ == '__main__':
vgg = VGG()
vgg.vgg_predict()
预测的条件
Google在用VGG训练ImageNet比赛当中的1000个类别才能预测。特定场景的图像识别任务,必须训练自己的模型进行预测可以在VGG的基础之上做训练,节约训练时间,包括效果都会得到改善
神经网络调优
参数的调优,也称之为超参数调优。超参数有
- 算法层面:
- 学习率α
- β1,β2,ϵ: Adam 优化算法的超参数,常设为 0.9、0.999、10^−8
- λ:正则化网络参数,
- 网络层面:
- hidden units:各隐藏层神经元个数
- layers:神经网络层数
调参技巧
对于调参,通常采用跟机器学习中介绍的网格搜索一致,让所有参数的可能组合在一起,得到N组结果。然后去测试每一组的效果去选择。
- 合理的参数设置
- 学习率α:0.0001、0.001、0.01、0.1,跨度稍微大一些。
- 算法参数β, 0.999、0.9995、0.998等,尽可能的选择接近于1的值
注:而指数移动平均值参数:β 从 0.9 (相当于近10天的影响)增加到 0.9005 对结果(1/(1-β))几乎没有影响,而 β 从 0.999 到 0.9995 对结果的影响会较大,因为是指数级增加。
批标准化(Batch Normalization)
对于这么多的超参数组合,调优是一件复杂的事情,这么多的超参数范围,批标准化让工作效果能达到更好,训练变得更容易
根本上不是去优化模型,而是帮助我们能够更好的去训练简单,训练过程,节省时间(AlexNet就使用了BN层)
解决目的
解决内部协变量偏移(在网络当中数据的分布会随着不同数据集改变 )
消除对Dropout的需求
批标准化公式
对于深层网络一些层级输出,进行批标准化
1、对于每一层的输出,进行标准化操作
2、在标准化之后,增加一个状态分布参数(garma, beta),让标准数据进行修改分布
作用
1、BN减少不同数据分布状态带来的影响,模型鲁棒性强,测试准确率高,防止过拟合的作用
2、BN使得不同层学到不同的分布状态
3、减少了各层W和b之间的耦合性,让各层更加独立,实现自我训练学习的效果
比如:
学习率0.1或者使用0.0001,都能学习到很好的模型
没有BN可能有些模型只适用0.0001这个学习率
给大学习率,学习就快
CNN网络实战技巧
做一个具体场景的计算机视觉任务
迁移学习
概念
利用数据、任务或模型之间的相似性,将在旧的领域学习过或训练好的模型,应用于新的领域这样的一个过程
前提
两个任务的输入属于同一性质:要么同是图像、要么同是语音或其他
当数据资源不大,低训练成本的情况下考虑迁移学习
方法(fine tuning,即微调)
调整模型参数不需要过多调整
调整模型结构,做微调整
Pre-trained 预训练模型 models/research/slim at master · tensorflow/models · GitHub TensorFlow提供了相关模型
fine tuning 微调之后的模型
具体过程
比如
有两个任务A和B,任务 A(1000个类别模型) 拥有海量的数据资源且已训练好,但并不是我们的目标任务,任务 B 是我们的目标任务。
建立自己的网络,在A的基础上,修改最后输出结构,并加载A的模型参数
根据数据大小调整
- 如果B任务数据量小,可以选择将A模型的所有的层进行freeze(可以通过Tensorflow的trainable=False参数实现),只训练全连接层
- 如果B任务的数据量大,可以将A中一半或者大部分的层进行freeze, 部分的layer可以进行新任务数据基础上的微调
迁移学习案例
应用Keras基于VGG对五种图片类别识别的迁移学习
读取本地的图片数据以及类别
ImageDataGenerator图片生成器
定义图片处理以及数据增强相关
keras.preprocessing.image import ImageDataGenerator
train_generator = ImageDataGenerator(rescale=1.0 / 255)
"""
简单点就只给rescale值,其他用默认也可
rescale=1.0/255 标准化 坐乘法
zca_whitening=False 默认False,zca白化的作用是针对图片进行PCA降维操作,减少图片的冗余信息
rotation_range=20 默认0, 旋转角度,在这个角度范围随机生成一个值
width_shift_range=0.2, 默认0 水平平移
height_shift_range=0.2, 默认0 垂直平移
shear_range=0.2, 默认0
horizontal_flip=True, 默认False 水平翻转
"""
ImageDataGenerator方法
flow(x ,y, batch_size)
for x_batch, y_batch in datagen.flow(self.x_train, self.y_train, batch_size=32):
self.model.fit(x = x_batch, y = y_batch)
ImageDataGenerator方法
flow(x ,y, batch_size)
for x_batch, y_batch in datagen.flow(self.x_train, self.y_train, batch_size=32):
self.model.fit(x = x_batch, y = y_batch)
flow_from_directory *
"""
directory=path 读取目录
target_size=(h, w) 目标形状
batch_size=size 批数量大小
class_mode='binary', 默认 categorical 目标值格式 One of "categorical"(2D one-hot encoded labels), "binary"(1D binary labels), "sparse"
shuffle=True 默认 True 打乱顺序
要求读取固定目录格式
data/
train/
dogs/
dog001.jpg
dog002.jpg
...
cats/
cat001.jpg
cat002.jpg
...
test/
dogs/
dog001.jpg
dog002.jpg
...
cats/
cat001.jpg
cat002.jpg
...
"""
代码示例
from tensorflow.python.keras.applications import vgg16
from tensorflow.python.keras.preprocessing.image import ImageDataGenerator
class VGGTransfer(object):
def __init__(self):
self.train_generator = ImageDataGenerator(rescale=1.0/255)
self.test_generator = ImageDataGenerator(rescale=1.0/255)
self.train_dir = './data/train'
self.test_dir = './data/test'
self.image_size = (224, 224)
self.batch_size = 32
def get_local_data(self):
train_gen = self.train_generator.flow_from_directory(directory=self.train_dir, target_size=self.image_size, batch_size=self.batch_size, class_mode='binary', shuffle=True)
test_gen = self.train_generator.flow_from_directory(directory=self.test_dir, target_size=self.image_size, batch_size=self.batch_size, class_mode='binary', shuffle=True)
for data in train_gen:
print(data[0].shape, data[1].shape)
for data in test_gen:
print(data[0].shape, data[1].shape)
if __name__ == '__main__':
vgg = VGGTransfer()
vgg.get_local_data()
模型的结构修改
获取notop模型
VGG提供所有层参数训练好的模型和没有全连接层参数的模型notop模型,不包含最后的3个全连接层。用来做fine-tuning专用,专门开源了这类模型
self.base_model = VGG16(weights='imagenet', include_top=False)
'''
weights='imagenet' 默认'imagenet'
使用的是vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5 这个文件
'''
结构修改
修改为一个GlobalAveragePooling2D + 两个全连接层
- 在图像分类任务中,模型经过最后CNN层后的尺寸为[bath_size, img_width, img_height, channels],通常的做法是:接一个flatten layer,将尺寸变为[batch_size, w h channels],再至少接一个FC layer,这样做的最大问题是:模型参数多,且容易过拟合。利用pooling layer来替代最后的FC layer
def refine_vgg_model(self):
# x Tensor("block5_pool/MaxPool:0", shape=(?, ?, ?, 512), dtype=float32)
x = self.base_model.outputs[0]
# x Tensor("global_average_pooling2d/Mean:0", shape=(?, 512), dtype=float32)
x = keras.layers.GlobalAveragePooling2D()(x)
# 定义新的迁移模型
x = keras.layers.Dense(1024, activation=tf.nn.relu)(x)
y_predict = keras.layers.Dense(15, activation=tf.nn.softmax)(x)
# 定义model
transfer_model = keras.models.Model(inputs = self.base_model.inputs, outputs = y_predict)
return transfer_model
freeze掉原始VGG模型
def freeze_vgg_model(self):
for layer in self.base_model.layers:
layer.trainable = False
编译以及训练和保存模型方式
编译
def compile(self, model):
model.compile(optimizer=keras.optimizers.Adam(), loss=keras.losses.sparse_categorical_crossentropy, metrics=['accuracy'])
训练和保存
model.fit_generator(self.train_gen, epochs=3, validation_data=self.test_gen, callbacks=[mck])
def fit(self, model):
mck = keras.callbacks.ModelCheckpoint(filepath=os.path.join(self.ckpt, "acc.{epoch:02d}-{acc:.2f}.h5"),
monitor="acc",
save_best_only=True,
save_weights_only=True,
mode='auto',
period=1)
model.fit_generator(self.train_gen, epochs=3, validation_data=self.test_gen, callbacks=[mck])
输入数据进行预测
def predict(self, model):
model.load_weights("./ckpt/acc.03-0.61.h5")
image = load_img("./data/test/Bird/Bird_28_4.jpg", target_size=(224, 224))
# 转换成numpy array数组
image = img_to_array(image)
# 形状从3维度修改成4维
img = image.reshape((1, image.shape[0], image.shape[1], image.shape[2]))
# 3、处理图像内容,归一化处理等,进行预测
img = preprocess_input(img)
y_predict = model.predict(img)
index = np.argmax(y_predict, axis=1)
print(self.label_dict[index[0]])
示例代码:
import os
import numpy as np
from tensorflow.python.keras.applications import vgg16
from tensorflow.python.keras.applications.vgg16 import preprocess_input
from tensorflow.python.keras.preprocessing.image import ImageDataGenerator, load_img, img_to_array
from tensorflow.python import keras
import tensorflow as tf
class VGGTransfer(object):
def __init__(self):
self.train_generator = ImageDataGenerator(rescale=1.0/255)
self.test_generator = ImageDataGenerator(rescale=1.0/255)
self.train_dir = './data/train'
self.test_dir = './data/test'
self.image_size = (224, 224)
self.batch_size = 32
self.base_model = vgg16.VGG16(weights='imagenet', include_top=False)
self.ckpt = './ckpt'
self.label_dict = {
0: 'Bear',
1:'Bird',
2:'Cat',
3:'Cow',
4:'Deer',
5:'Dog',
6:'Dolphin',
7:'Elephant',
8:'Giraffe',
9:'Horse',
10:'Kangaroo',
11:'Lion',
12:'Panda',
13:'Tiger',
14:'Zebra'
}
def get_local_data(self):
self.train_gen = self.train_generator.flow_from_directory(directory=self.train_dir, target_size=self.image_size, batch_size=self.batch_size, class_mode='binary', shuffle=True)
self.test_gen = self.train_generator.flow_from_directory(directory=self.test_dir, target_size=self.image_size, batch_size=self.batch_size, class_mode='binary', shuffle=True)
def refine_vgg_model(self):
# x Tensor("block5_pool/MaxPool:0", shape=(?, ?, ?, 512), dtype=float32)
x = self.base_model.outputs[0]
# x Tensor("global_average_pooling2d/Mean:0", shape=(?, 512), dtype=float32)
x = keras.layers.GlobalAveragePooling2D()(x)
# 定义新的迁移模型
x = keras.layers.Dense(1024, activation=tf.nn.relu)(x)
y_predict = keras.layers.Dense(15, activation=tf.nn.softmax)(x)
# 定义model
transfer_model = keras.models.Model(inputs = self.base_model.inputs, outputs = y_predict)
return transfer_model
def freeze_vgg_model(self):
for layer in self.base_model.layers:
layer.trainable = False
def compile(self, model):
model.compile(optimizer=keras.optimizers.Adam(), loss=keras.losses.sparse_categorical_crossentropy, metrics=['accuracy'])
def fit(self, model):
mck = keras.callbacks.ModelCheckpoint(filepath=os.path.join(self.ckpt, "acc.{epoch:02d}-{acc:.2f}.h5"),
monitor="acc",
save_best_only=True,
save_weights_only=True,
mode='auto',
period=1)
model.fit_generator(self.train_gen, epochs=3, validation_data=self.test_gen, callbacks=[mck])
def predict(self, model):
model.load_weights("./ckpt/acc.03-0.61.h5")
image = load_img("./data/test/Bird/Bird_28_4.jpg", target_size=(224, 224))
# 转换成numpy array数组
image = img_to_array(image)
# 形状从3维度修改成4维
img = image.reshape((1, image.shape[0], image.shape[1], image.shape[2]))
# 3、处理图像内容,归一化处理等,进行预测
img = preprocess_input(img)
y_predict = model.predict(img)
index = np.argmax(y_predict, axis=1)
print(self.label_dict[index[0]])
if __name__ == '__main__':
vgg = VGGTransfer()
vgg.get_local_data()
model = vgg.refine_vgg_model()
# vgg.freeze_vgg_model()
# vgg.compile(model)
# vgg.fit(model)
vgg.predict(model)
商品物体检测
项目结构
数据采集层: 数据收集标注
深度模型层: YOLO SSD模型,模型导出,Serving部署
用户层:前端交互,(web后台)对接部署的模型
目标检测算法分类
两步走的目标检测
1、先找出候选的一些区域 2、对区域进行调整,分类
端到端的目标检测
采用一个网络一步到位 输入图片,输出有哪些物体,物体在什么位置
目标检测的任务
输入图片 输出物体类别(评估指标:Accuracy),物体的位置坐标(x,y,w,h)叫做bounding box(bbox)(主要评估指标:IOU) bounding box x,y代表物体中心点的位置,w,h代表中心点离两边物体的长宽
目标定位的简单实现思路
在分类的时候我们直接输出各个类别的概率,加上定位的话,考虑在网络的最后输出加上位 置信息。
增加一个全连接层,即为FC1、FC2 FC1:作为类别的输出 FC2:作为这个物体位置数值的输出
假设有10个类别,输出[p1,p2,p3,.,p10],然后输出这一个对象的四个位置信息[x,y,w,h]
对于分类的概率,使用交叉熵损失
位置信息具体的数值,使用MSE均方误差损失(L2损失)
两种Bounding box名称
Ground-truth bounding box: GT图片真实目标位置(标记结果)
Predicted bounding box: 预测的框位置
RCNN
对于多个目标的情况,就不能以固定个数输出物体的位置值
目标检测-Overfeat模型
滑动窗口
目标检测的暴力方法是从左到右、从上到下滑动窗口,利用分类识别目标
提前设定K个窗口,每个窗口滑动提取M个,总共K x M 个图片
Overfeat模型总结
暴力方式
计算消耗太大
提供了一种解决目标检测问题的思路
目标检测-R-CNN模型
完整R-CNN流程
1.找出图片中可能存在目标的侯选区域region proposals(默认2000个)
2.侯选区域图片大小调整为适应AlexNet网络的输入图像的大小227×227,CNN对候选区域提取特征向量,特征组合成2000×4096维矩阵
3.将2000×4096维特征与20个SVM组成的权值矩阵4096×20相乘(20种分类,SVM是二分类器,有20个SVM),获得2000×20维矩阵
4.对2000×20维矩阵中每一列即每一类进行非极大值抑制(NMS:non-maximum suppression)剔除重叠建议框,得到该列即该类中得分最高的一些建议框
5.修正bbox,对bbox做回归微调
候选区域(region proposals)
通过选择性搜索(SelectiveSearch,SS)将每个像素作为一组,然后,计算每一组的纹理,并将两个最接近的组结合起来
为了避免单个区域吞噬其他区域,首先对较小的组进行分组,继续合并区域,直到所有区域都结合在一起
选择性搜索在一张图片上提取出来约2000个侯选区域
候选区域的长宽不固定,需要对候选区域做尺寸上的修改,从而使用CNN提取候选区域的特征向量
CNN网络提取特征
在侯选区域的基础上提取出更高级、更抽象的特征
提取的这些特征将会保存在磁盘当中(这些提取的特征才是真正的要训练的数据)
特征向量训练分类器SVM
对2000 x 4096特征向量进行分类,R-CNN选用SVM进行二分类。
假设检测N个类别,那么会提供20个不同类别的SVM分类器,每个分类器都会对2000个候选区域的特征向量分别判断一次(判断2000个候选区域是某类别,还是背景),得出[2000, 20]的得分矩阵,如:
猫分类器: 2000个候选区域做判断,得到2000个属于猫的类别概率
狗分类器: 2000个候选区域做判断,得到2000个属于猫的类别概率
。。。
[2000,20]
非最大抑制(NMS)
筛选候选区域,目标是一个物体只保留一个最优的框,来抑制那些冗余的候选框
过程:
1、对于所有的2000个候选区域得分进行概率筛选,小于0.5的直接去除
2、 剩余的候选框都找到自己对应的Ground-truth bounding box(GT)
每个候选框和所有GT的IoU值(简单理解为覆盖值)的最高那个就是对应的GT
3、对某一GT,选择IoU值最高的框作为候选框,选择该GT其他框与候选框计算IOU,若IOU>0.5,则重复率较大,则剔除;否则,不变。由此循环。
修正候选区域
非最大抑制筛选出来的候选框不一定就非常准确,R-CNN建立一个bbox regressor用于修正筛选后的候选区域,使之回归于ground-truth
修正过程(线性回归)
候选区为(x, y, w ,h) ,通过损失计算学习参数(wx,wy, ww, wh) ,设为F,使得F*(x, y, w ,h) 接近于ground-truth
检测的评价指标
IoU(位置的考量)
两个区域的重叠程度,通常大于0.5可认为类别正确
IoU=Area of Overlap / Area of Union
平均准确率(mean average precision)(分类准确的考量)
一文让你彻底理解准确率,精准率,召回率,真正率,假正率,ROC/AUC - AIQ
通过阈值区分候选框的正负样本
比如
每个ground-truth box有着最高的IoU的标记为正样本 剩下的与任何ground-truth box的loU大于0.7记为正样本,loU小于0.3,记为负样本
map定义:
多个分类任务的AP的平均值 mAP =所有类别的AP之和/类别的总个数
方法步骤: 1、对于其中一个类别C,首先将算法输出的所有C类别的预测框,按预测的分数排序(RCNN中就是SVM的输出分数) 2、设定不同的k值,选择top k个预测框,计算FP和TP,计算Precision和AP 3、将得到的N个类别的AP取平均,即得到mAP; AP是针对单一类别的,mAP是将所有类别的AP求 和,再取平均
总结
优点:
在VOC2007数据集上的平均精确度达到66%
缺点:
1、训练阶段多:步骤繁琐: 微调网络+训练SVM+训练边框回归器。
2、训练耗时:占用磁盘空间大:5000张图像产生几百G的特征文件。
3、处理速度慢: 使用GPU, VGG16模型处理一张图像需要47s。
目标检测-SPPNet模型
R-CNN的速度慢在会对每个候选区域都进行了卷积操作提取特征

| R-CNN模型 | SPPNet模型 |
|---|---|
| 1、R-CNN让每个候选区域经过crop/wrap等操作变换成固定大小的图像 2、固定大小的图像给CNN 传给后面的层做训练回归分类操作 | 1、SPPNet把全图给CNN得到全图的feature map 2、候选区域与feature map直接映射,得到候选区域的映射特征向量 3、映射过来的特征向量给SPP层(空间金字塔变换层),SPP层输出固定大小的特征向量,再给FC层 |
映射
将基于原始图片的候选区域映射到feature map中的特征向量

- 左上角的点:
- x′=[x/S]+1
- 右下角的点:
- x′=[x/S]−1
S 就是CNN中所有的strides的乘积,包含了池化、卷积的stride
spatial pyramid pooling
通过spatial pyramid pooling 将任意大小的特征图转换成固定大小的特征向量

假设原图输入是224x224,对于conv出来后的输出是13x13x256的,可以理解成有256个这样的Filter,每个Filter对应一张13x13的feature map
特征图中找到每一个候选区域映射的区域,spp layer将每一个映射出来的区域分成1x1,2x2,4x4三张子图,对每个子图的每个区域作max pooling,得出的特征再连接到一起(平铺),就是(16+4+1)x256的特征向量, 接着给全连接层做进一步处理
总结
优点:
达到了CNN层的共享计算,减少了运算时间
缺点:
训练依然过慢、效率低,特征需要写入磁盘(因为SVM的存在)
分阶段训练网络:选取候选区域、训练CNN、训练SVM、训练bbox回归器
目标检测-Fast R-CNN模型
Fast R-CNN解决SPPNet网络之间不统一训练的问题,废弃了SVM和sppnet
提出RoI pooling,然后整合整个模型,把CNN、SPP变换层、分类器、bbox回归几个模块一起训练

具体步骤
1、图片输入到基础卷积网络,得到整张图的feature map
2、region proposal(RoI)映射到feature map中
3、RoI pooling layer提取固定长度特征向量,每个特征会输入到全连接层,得到一个RoI特征向量
(此步骤是对每一个候选区域映射的区域都会进行同样的操作)
- 一个是传统softmax层进行分类,输出类别有K个类别加上”背景”类
- 另一个是bounding box regressor
RoI pooling
RoI pooling是一个简单版本的SPP,目的是为了减少计算时间并且得出固定长度的向量
把1x1,2x2,4x4三张子图换成了只有4x4的子图

- single scale,直接将image定为某种scale,直接输入网络来训练(Fast R-CNN)
- multi scal,生成一个金字塔(SPPNet)
后者比前者更加准确些,也没多突出,但是第一种时间要省很多计算时间,所以实际采用的是第一个策略,因此Fast R-CNN要比SPPNet快。
多任务损失-Multi-task loss
分类loss,N+1路的softmax输出,其中的N是类别个数,1是背景,使用交叉熵损失
回归loss,4xN路输出的regressor,对于每个类别都会训练单独的regressor,使用平均绝对误差(MAE)损失即L1损失
效果对比
| 参数 | R-CNN | SPPNet | Fast R-CNN |
|---|---|---|---|
| 训练时间(h) | 84 | 25 | 9.5 |
| 测试时间/图片 | 47.0s | 2.3s | 0.32s |
| mAP | 66.0 | 63.1 | 66.9 |
总结
缺点:
使用Selective Search提取Region Proposals,没有实现真正意义上的端对端,操作也十分耗时
目标检测-Faster R-CNN模型
解决了Fast R-CNN存在的问题:Selective Search(选择性搜索)找出所有候选框非常耗时
找候选框的工作也交给神经网络,Faster R-CNN可以简单地看成是区域生成网络+Fast R-CNN的模型


具体步骤
1、向CNN网络(VGG-16)输入任意大小图片
2、Faster RCNN使用一组基础的conv+relu+pooling层提取feature map。该feature map被共享用于后续RPN层和全连接层。
3、RPN网络生成region proposals
该softmax判断anchors属于foreground或者background,再利用bounding box regression修正anchors获得精确的proposals,输出Top-N(默认为300)的区域给RoI pooling
4、第2步的高维特征图和第3步输出的区域合并输入RoI池化层
5、计算每个proposal的不同类别概率,同时bounding box regression获得检测框最终的精确位置
RPN原理
用nxn(默认3x3=9)的窗口去扫描特征图得到K个候选窗口,以窗口中心点作为anchor的中心点在原图中框出多尺度、多种长宽比的anchors,三种尺度{ 128,256,512 }, 三种长宽比{1:1,1:2,2:1}

每个特征图中像素对应的9个窗口
每一个候选框进行如下操作:
分类:判断是否是背景
与自己对应目标值GT做回归,修正位置
得到更好的候选区域提供给ROI pooling使用
Faster R-CNN的训练

RPN的训练 分类:是否有物体,二分类,softmax, logisticregression 候选框的调整:均方误差做修正
Fast R-CNN的训练 预测类别训练:softamx 预测位置的训练:均方误差损失

效果对比
| R-CNN | Fast R-CNN | Faster R-CNN | |
|---|---|---|---|
| Test time/image | 50.0s | 2.0s | 0.2s |
| mAP(VOC2007) | 66.0 | 66.9 | 66.9 |
开源keras Faster RCNN模型
GitHub - lucasjinreal/keras_frcnn: Keras Implementation of faster-rcnn
需要下载keras,必须是2.0.3版本 keras==2.0.3 源码读取图片以及处理图片标记图片工具使用opencv需要安装 opencv-python
总结
优点:
提出RPN网络
端到端网络模型
缺点:
训练参数过大
对于真实训练使用来说还是依然过于耗时
目标检测-YOLO模型(You only look once)
一个网络搞定一切,GoogleNet + 4个卷积+2个全连接层


- S:栅格数,可以为7
- B:Bounding boxes个数,可以2为
- C:检测类别数,可以为20
具体步骤:
1、输入图像的大小为 448 x 448,YOLO将输入的图像分成S*S的栅格,每个栅格负责检测中心落在该栅格中的物体
2、使用卷积网络执行预测,输出SxSx(5*B + C)的结构,得到B个bounding box以及bounding box的confidence scores(置信度)和C个conditional class probability(条件类别概率) confidence scores:反映了模型对于这个栅格的预测:该栅格是否含有物体,以及这个box的坐标预测的准确度。
confidence scores=Pr(Object) * IoU,Pr(Object) 即为物体存在的概率 没有物体则为为0有物体则为1, IoU即预测框和GT的IoU值
3、进行非极大值抑制,筛选Boxes
非最大抑制(NMS)
1、设置confidence scores阈值,小于该阈值的数值归零
2、进行降序排列
3、针对某一类别,选择得分最大的bboxes作为候选框,选择该类别其他bboxes与候选框计算IOU,若IOU>0.5,则重复率较大,设为0;否则,不变。由此循环。
训练

三部分损失 bbox损失+confidence损失+classfication损失
总结
优点:
速度快
缺点:
准确率会打折扣
YOLO对相互靠的很近的物体(中点都落在同一个格子上的情况),以及很小的群体检测效果不好
目标检测-SSD模型(Single Shot MultiBox Detector)
论文网址:https://arxiv.org/abs/1512.02325
SSD的核心是在不同尺度的特征图上采用卷积核来预测一系列Default Bounding Boxes的类别、坐标偏移
结构
以VGG-16为基础,使用VGG的前五个卷积,后面增加从CONV6开始的5个卷积结构,输入图片要求300*300

特征图卷积流程

SSD中引入Defalut Box,与Faster R-CNN的anchor box机制类似,就是预设一些目标预选框,不同的是在不同尺度feature map所有特征点上使用PriorBox层(default boxes)
Detector & classifier结构
SSD的核心是在不同尺度的特征图上来进行Detector & classifier 容易使得SSD观察到更小的物体

包含三个部分:
1、PriorBox层:生成default boxes,默认候选框
default boxes相当于faster rcnn里的anchors,预设box,网络根据box,通过分类和回归给出被检测到物体的类别和位置。每个都会被分类,并回归到一个更准的位置和尺寸上
各个feature map层经过priorBox层生成prior box

除此之外还有一些其它参数,比如min_size, max_size, aspect ratio, step,offset,variance参数,这些共同确定一个anchor

prior_box:打印出来的形状为:
Tensor("concat_2:0", shape=(?, 7308, 8), dtype=float32)
layer {
name: "conv6_2_mbox_priorbox"
type: "PriorBox"
bottom: "conv6_2"
bottom: "data"
top: "conv6_2_mbox_priorbox"
prior_box_param {
min_size: 111.0
max_size: 162.0
aspect_ratio: 2.0
aspect_ratio: 3.0
flip: true
clip: false
variance: 0.10000000149
variance: 0.10000000149
variance: 0.20000000298
variance: 0.20000000298
step: 32.0
offset: 0.5
}
2、Conv3 x 3:生成localization, 4个位置偏移
Tensor("Reshape_42:0", shape=(?, 7308, 4), dtype=float32)
3、Conv3 x 3:confidence,21个类别置信度(要区分出背景)
Tensor("truediv:0", shape=(?, 7308, 21), dtype=float32)
训练与测试流程
输入->输出->结果与ground truth标记样本回归损失计算->反向传播, 更新权值
样本标记
prior box与ground truth box做匹配进行标记正负样本, 先进行置信度筛选,训练指定的正样本和负样本, 如下规则
- 正样本
- 1.与GT重合最高的boxes, 其输出对应label为对应物体
- 2.物体GT与anchor iou满足大于0.5
- 负样本:其它的样本标记为负样本
在训练时, default boxes按照正负样本控制positive:negative=1:3
损失
网络输出预测的predict box与ground truth回归变换之间的损失计算, 置信度采用 Softmax Loss(Faster R-CNN是log loss),位置回归采用 Smooth L1 loss (与Faster R-CNN一样)
预测流程
输入->输出->nms->输出
SSD300网络接口介绍-keras
ckpt
weights_SSD300.hdf5
images
nets
ssd.py input 300x300
网络结构
Block 1 [150x150x64]
Block 2 [75x75x128]
Block 3 [38x38x256]
Block 4 [19x19x512] (2, 2), strides=(2, 2) 输出尺寸减半
Block 5 [19x19x512] (3, 3), strides=(1, 1) 输出尺寸不变
FC6 [19x19x1024]
FC7 [19x19x1024]
Block 6 [10x10x1024]
Block 7 [5x5x256]
Block 8 [3x3x256]
Last Pool [1x1x256]
对以下特征进行Detector & classifier
Block 4 池化前的,即[38x38x512]
num_priors = 3
localization卷积 num_priors * 4, 3, 3 第一个参数是filters的数量,需为num_priors * 4
confidence卷积 num_priors * num_classes, 3, 3
FC7 [19x19x1024]
num_priors = 6
localization卷积 num_priors * 4, 3, 3 第一个参数是filters的数量,需为num_priors * 4
confidence卷积 num_priors * num_classes, 3, 3
Block 6 [10x10x1024]
num_priors = 6
localization卷积 num_priors * 4, 3, 3 第一个参数是filters的数量,需为num_priors * 4
confidence卷积 num_priors * num_classes, 3, 3
Block 7 [5x5x256]
num_priors = 6
localization卷积 num_priors * 4, 3, 3 第一个参数是filters的数量,需为num_priors * 4
confidence卷积 num_priors * num_classes, 3, 3
Block 8 [3x3x256]
num_priors = 6
localization卷积 num_priors * 4, 3, 3 第一个参数是filters的数量,需为num_priors * 4
confidence卷积 num_priors * num_classes, 3, 3
Last Pool [1x1x256]
num_priors = 6
localization卷积 num_priors * 4 第一个参数是filters的数量,需为num_priors * 4
confidence卷积 num_priors * num_classes
收集所有预测并定义模型
net['predictions'] = merge([net['mbox_loc'],
net['mbox_conf'],
net['mbox_priorbox']],
mode='concat', concat_axis=2,
name='predictions')
model = Model(net['input'], net['predictions'])
utils
ssd_layers.py
class Normalize(Layer): 标准化
class PriorBox(Layer): 生成指定尺寸和纵横比的先前方框
ssd_utils.py
class BBoxUtility(object):提供编解码工具以及NMS工具
def iou(self, box): 计算iou
def detection_out(self, predictions, background_label_id=0, keep_top_k=200,
confidence_threshold=0.01): 对预测结果进行非最大抑制(nms)
train_ssd.py
SSD300网络输出结构
Tensor("Reshape_42:0", shape=(?, 7308, 4), dtype=float32)
Tensor("truediv:0", shape=(?, 7308, 21), dtype=float32)
Tensor("concat_2:0", shape=(?, 7308, 8), dtype=float32)
[<tf.Tensor 'concat_3:0' shape=(?, 7308, 33) dtype=float32>]
目标检测算法效果对比

SSD300物体检测
前提条件
keras==1.2.2
imageio==2.0.0
from scipy.misc import imread 提示ImportError: cannot import name imread
使用imageio替代 可下载2.0.0版本
# from scipy.misc import imread
改为
from imageio import imread
#from scipy.misc import imresize
#image = imresize(image , (224,224))
改为
from PIL import Image
image = np.array(Image.fromarray(image).resize((224,224)))
也就是generator.py文件中更改如下:
# img = imresize(img, self.image_size).astype('float32')
img = np.array(Image.fromarray(img.astype('uint8')).resize(self.image_size)).astype('float32')
搭建框架
SSD300 keras版本源代码
GitHub - wikke/SSD_Keras: Single Shot MultiBox Detector(SSD)目标检测算法
模型权重weights_SSD300.hdf5在下面地址下载
需要以下文件
ssd.py
ssd_layers.py
ssd_utils.py
组成如下结构
ckpt
weights_SSD300.hdf5
images
nets
ssd.py
utils
ssd_layers.py
ssd_utils.py
ssd_losses.py 对应仓库里的ssd_training.py
generator.py
train_ssd.py
定义类别数量以及输入
import numpy as np
from keras.applications.imagenet_utils import preprocess_input
from keras.preprocessing.image import load_img, img_to_array
from imageio import imread
import os
from ssd300.nets.ssd import SSD300
from utils.ssd_utils import BBoxUtility
import matplotlib.pyplot as plt
class SSDPredict(object):
def __init__(self):
# 模型权重文件已经训练了如下类型
self.classes_name = ['Aeroplane', 'Bicycle', 'Bird', 'Boat', 'Bottle',
'Bus', 'Car', 'Cat', 'Chair', 'Cow', 'Diningtable',
'Dog', 'Horse', 'Motorbike', 'Person', 'Pottedplant',
'Sheep', 'Sofa', 'Train', 'Tvmonitor']
self.classes_nums = len(self.classes_name) + 1
self.input_shape = (300, 300, 3)
模型预测流程
1、SSD300模型输入、加载参数
2、读取本地路径下所有图片,preprocess_input以及保存图像像素值
3、模型预测结果,得到priorbox
4、进行非最大抑制算法处理
def test(self):
model = SSD300(self.input_shape, num_classes=self.classes_nums)
# by_name=True 用于微调或迁移学习模型
model.load_weights('./ckpt/weights_SSD300.hdf5', by_name=True)
# 循环读取图片进行多个图片输出检测
feature = []
images_data = []
for pic_name in os.listdir("./images/"):
img_path = os.path.join("./images/", pic_name)
# PIL格式读取图片
img = load_img(img_path, target_size=(self.input_shape[0], self.input_shape[1]))
# PIL格式转换成numpy数组
img = img_to_array(img)
# 保存numpy数组
feature.append(img)
# 保存读取图像返回的numpy数组
images_data.append(imread(img_path))
# 处理numpy数组图片数据,对图像数据进行标准化,使其具有与训练数据相同的均值和方差
inputs = preprocess_input(np.array(feature))
# 预测 (n, 7308, 33) n为图片数量
pred = model.predict(inputs, batch_size=1, verbose=1)
# 定义BBox工具
bbox_util = BBoxUtility(self.classes_nums)
# 使用非最大抑制算法过滤
# 返回每张图片的预测列表。每个预测是:[标签、置信度、xmin、ymin、xmax、ymax]
res = bbox_util.detection_out(pred)
return res, images_data
结果显示
1、解析输出结果, 每张图片的标签,置信度和位置
2、过滤置信度低的结果
3、Matplotlib绘图
def tag_picture(self, res, images_data, acc):
for i, img in enumerate(images_data):
print(img)
# 解析输出结果, 每张图片的标签,置信度和位置
pre_label = res[i][:, 0]
pre_conf = res[i][:, 1]
pre_xmin = res[i][:, 2]
pre_ymin = res[i][:, 3]
pre_xmax = res[i][:, 4]
pre_ymax = res[i][:, 5]
# print("label:{}, probability:{}, xmin:{}, ymin:{}, xmax:{}, ymax:{}".
# format(pre_label, pre_conf, pre_xmin, pre_ymin, pre_xmax, pre_ymax))
# 过滤置信度低的结果
top_indices = [i for i, conf in enumerate(pre_conf) if conf >= acc]
top_conf = pre_conf[top_indices]
top_label_indices = pre_label[top_indices].tolist()
top_xmin = pre_xmin[top_indices]
top_ymin = pre_ymin[top_indices]
top_xmax = pre_xmax[top_indices]
top_ymax = pre_ymax[top_indices]
# 定义21中颜色,显示图片
# hsv 是 Matplotlib 中的一个颜色映射(colormap), HSV 颜色模型特别适合于可视化那些具有周期性变化的数据
colors = plt.cm.hsv(np.linspace(0, 1, 21)).tolist()
plt.imshow(img)
# currentAxis增加图中文本显示和标记显示,获取当前轴
currentAxis = plt.gca()
for i in range(top_conf.shape[0]):
xmin = round(top_xmin[i] * img.shape[1])
ymin = round(top_ymin[i] * img.shape[0])
xmax = round(top_xmax[i] * img.shape[1])
ymax = round(top_ymax[i] * img.shape[0])
# 获取该图片预测概率,名称,定义显示颜色
score = top_conf[i]
label = int(top_label_indices[i])
label_name = self.classes_name[label - 1]
display_txt = '{:0.2f}, {}'.format(score, label_name)
rect = (xmin, ymin), xmax - xmin + 1, ymax - ymin + 1
color = colors[label]
# 显示方框 fill是否填充 edgecolor指定边线颜色,linewidth指定边线宽度
currentAxis.add_patch(plt.Rectangle(*rect, fill=False, edgecolor=color, linewidth=2))
# 左上角显示概率以及名称
currentAxis.text(xmin, ymin, display_txt, bbox={'facecolor': color, 'alpha': 0.5})
plt.show()
代码
import numpy as np
from keras.applications.imagenet_utils import preprocess_input
from keras.preprocessing.image import load_img, img_to_array
from imageio import imread
import os
from nets.ssd import SSD300
from utils.ssd_utils import BBoxUtility
import matplotlib.pyplot as plt
class SSDPredict(object):
def __init__(self):
self.classes_name = ['Aeroplane', 'Bicycle', 'Bird', 'Boat', 'Bottle',
'Bus', 'Car', 'Cat', 'Chair', 'Cow', 'Diningtable',
'Dog', 'Horse', 'Motorbike', 'Person', 'Pottedplant',
'Sheep', 'Sofa', 'Train', 'Tvmonitor']
self.classes_nums = len(self.classes_name) + 1
self.input_shape = (300, 300, 3)
def test(self):
model = SSD300(self.input_shape, num_classes=self.classes_nums)
# by_name=True 用于微调或迁移学习模型
model.load_weights('./ckpt/weights_SSD300.hdf5', by_name=True)
# 循环读取图片进行多个图片输出检测
feature = []
images_data = []
for pic_name in os.listdir("./images/"):
img_path = os.path.join("./images/", pic_name)
# PIL格式读取图片
img = load_img(img_path, target_size=(self.input_shape[0], self.input_shape[1]))
# PIL格式转换成numpy数组
img = img_to_array(img)
# 保存numpy数组
feature.append(img)
# 保存读取图像返回的numpy数组
images_data.append(imread(img_path))
# 处理numpy数组图片数据,对图像数据进行标准化,使其具有与训练数据相同的均值和方差
inputs = preprocess_input(np.array(feature))
# 预测 (n, 7308, 33) n为图片数量
pred = model.predict(inputs, batch_size=1, verbose=1)
# 定义BBox工具
bbox_util = BBoxUtility(self.classes_nums)
# 使用非最大抑制算法过滤
# 返回每张图片的预测列表。每个预测是:[标签、置信度、xmin、ymin、xmax、ymax]
res = bbox_util.detection_out(pred)
print(res[0].shape)
return res, images_data
def tag_picture(self, res, images_data, acc):
for i, img in enumerate(images_data):
print(img)
# 解析输出结果, 每张图片的标签,置信度和位置
pre_label = res[i][:, 0]
pre_conf = res[i][:, 1]
pre_xmin = res[i][:, 2]
pre_ymin = res[i][:, 3]
pre_xmax = res[i][:, 4]
pre_ymax = res[i][:, 5]
# print("label:{}, probability:{}, xmin:{}, ymin:{}, xmax:{}, ymax:{}".
# format(pre_label, pre_conf, pre_xmin, pre_ymin, pre_xmax, pre_ymax))
# 过滤置信度低的结果
top_indices = [i for i, conf in enumerate(pre_conf) if conf >= acc]
top_conf = pre_conf[top_indices]
top_label_indices = pre_label[top_indices].tolist()
top_xmin = pre_xmin[top_indices]
top_ymin = pre_ymin[top_indices]
top_xmax = pre_xmax[top_indices]
top_ymax = pre_ymax[top_indices]
# 定义21中颜色,显示图片
# hsv 是 Matplotlib 中的一个颜色映射(colormap), HSV 颜色模型特别适合于可视化那些具有周期性变化的数据
colors = plt.cm.hsv(np.linspace(0, 1, 21)).tolist()
plt.imshow(img)
# currentAxis增加图中文本显示和标记显示,获取当前轴
currentAxis = plt.gca()
for i in range(top_conf.shape[0]):
xmin = round(top_xmin[i] * img.shape[1])
ymin = round(top_ymin[i] * img.shape[0])
xmax = round(top_xmax[i] * img.shape[1])
ymax = round(top_ymax[i] * img.shape[0])
# 获取该图片预测概率,名称,定义显示颜色
score = top_conf[i]
label = int(top_label_indices[i])
label_name = self.classes_name[label - 1]
display_txt = '{:0.2f}, {}'.format(score, label_name)
rect = (xmin, ymin), xmax - xmin + 1, ymax - ymin + 1
color = colors[label]
# 显示方框 fill是否填充 edgecolor指定边线颜色,linewidth指定边线宽度
currentAxis.add_patch(plt.Rectangle(*rect, fill=False, edgecolor=color, linewidth=2))
# 左上角显示概率以及名称
currentAxis.text(xmin, ymin, display_txt, bbox={'facecolor': color, 'alpha': 0.5})
plt.show()
if __name__ == '__main__':
ssd = SSDPredict()
res, images_data = ssd.test()
ssd.tag_picture(res, images_data, 0.6)
数据集训练
常用目标检测数据集
pascal Visual Object Classes
VOC数据集是目标检测经常用的一个数据集,主要由VOC2007和VOC2012两个数据集
官网地址:http://host.robots.ox.ac.uk/pascal/VOC/
下载地址:https://pjreddie.com/projects/pascal-voc-dataset-mirror/
VOC2007数据集
VOC2007数据集(VOCtrainval_06-Nov-2007.tar和VOCtest_06-Nov-2007.tar),总共9963张图片,需要判定的总物体类别数量为20个对象类别是:
- 人:人
- 动物:鸟,猫,牛,狗,马,羊
- 车辆:飞机,自行车,船,公共汽车,汽车,摩托车,火车
- 室内:瓶子,椅子,餐桌,盆栽,沙发,电视/显示器
文件结构和文件内容
VOC2007
Annotations 图像中的目标标注信息xml格式
*.XML 标准的物体检测标记结果格式,这就是用于训练的物体标记结果
需要关注字段
size:
图片尺寸大小,宽、高、通道数
object:
name:被标记物体的名称
bndbox:标记物体的框大小
xmin
ymin
xmax>
ymax
ImageSets
JPEGImages 图像
*.jpg
SegmentationClass 图像分割相关
SegmentationObject 图像分割相关
<annotation>
<folder>VOC2007</folder>
<filename>000001.jpg</filename># 文件名
<source># 文件来源
<database>The VOC2007 Database</database>
<annotation>PASCAL VOC2007</annotation>
<image>flickr</image>
<flickrid>341012865</flickrid>
</source>
<owner>
<flickrid>Fried Camels</flickrid>
<name>Jinky the Fruit Bat</name>
</owner>
<size># 文件尺寸,包括宽、高、通道数
<width>353</width>
<height>500</height>
<depth>3</depth>
</size>
<segmented>0</segmented># 是否用于目标分割
<object># 真实标记的物体
<name>dog</name># 目标类别名称
<pose>Left</pose>
<truncated>1</truncated># 是否截断,这个目标因为各种原因没有被框完整(被截断了),比如说一辆车有一部分在画外面
<difficult>0</difficult># 表明这个待检测目标很难识别,有可能是虽然视觉上很清楚,但是没有上下文的话还是很难确认它属于哪个分类,标为difficult的目标在测试评估中一般会被忽略
<bndbox># bounding-box
<xmin>48</xmin>
<ymin>240</ymin>
<xmax>195</xmax>
<ymax>371</ymax>
</bndbox>
</object>
<object># 真实标记的第二个物体
<name>person</name>
<pose>Left</pose>
<truncated>1</truncated>
<difficult>0</difficult>
<bndbox>
<xmin>8</xmin>
<ymin>12</ymin>
<xmax>352</xmax>
<ymax>498</ymax>
</bndbox>
</object>
</annotation>
Open Images Dataset V4
2018年发布了包含在 190 万张图片上针对 600 个类别的 1540 万个边框盒,现有最大的具有对象位置注释的数据集。大部分都是由专业注释人员手动绘制的,确保了它们的准确性和一致性
谷歌的数据集类目较多涵盖范围广,文件过多,处理起来比较麻烦
目标数据标记
数据集标记工具LabelImg
官网:https://github.com/tzutalin/labelImg 下载Binary v1.8.1版本 windows_v1.8.1.zip 请直接在压缩包打开
LabelImg是一个图形图像注释工具。用Python编写的,使用Qt作为其图形界面。注释以PASCAL VOC格式保存为XML文件,是ImageNet使用的格式。
检测项目-需求
确定需求,以商品数据为例,需要明确的有
1、商品图片
2、需要被标记物体有哪些
我们确定了8种类别的商品(如需更细致,可将类别商品扩大)
类别数 8
服饰
衣服 clothes
裤子 gants
鞋子 shoes
科技产品
手表 watch
手机 phone
音箱 audio
电脑 computer
学习
书籍 books
检测项目-标记
1、运行labelimg
2、打开图片文件
3、选择标注保存格式(有YOLO格式和pascal VOC格式),选择pascal VOC格式即可
4、创建框进行标注,并设置object-name
5、ctrl+s键保存,保存为默认XML文件格式,默认和源文件在一个目录,文件名也一样
目标训练结构
ckpt
fine_tuning 微调模型
pre_trained 预训练模型
weights_SSD300.hdf5
datasets 训练原始数据以及存储数据、读取数据代码以及模型priorbox
objects
Annotations 图像中的目标标注信息xml格式
Images 图像
objects_gt.pkl 自己生成的pkl文件
classes.py 保存种类列表
get_label_data.py
prior_boxes_ssd300.pkl 是定义好的这些先验框的参数,包括每个先验框的中心坐标 (x_min, y_min, x_max, y_max) 以及与之相关的方差 (var[0], var[1], var[2], var[3]),这些方差用于数据增强和训练过程中的坐标回归
images 存放验证的图片文件
video 存放验证的视频文件
nets
ssd.py
serving_model 模型部署使用的模型位置
utils
ssd_layers.py
ssd_utils.py
ssd_losses.py 对应仓库里的ssd_training.py
generator.py
train_ssd.py 训练模型代码逻辑
test_ssd.py 预测图片的代码
xml读取本地标记数据文件存储到pkl
ElementTree解析xml结构
需要的字段width,height,xmin,ymin,xmax,ymax,name,filename
1、XML解析根路径
2、每个图片标记的对象进行坐标获取,将类别进行one_hot编码
3、 每张图片以图片名为key,value为该图片存储标记的对象的四个坐标和类别的one_hot编码(numpy事数组)
4、pickle.dump系列化字典对象
get_label_data.py代码如下:
import os
import pickle
from xml.etree import ElementTree
from classes import classes_name
import numpy as np
class XmlProcess:
def __init__(self, data_path):
self.data_path = data_path
self.data = dict()
# 类别自定义
self.classes_name = classes_name
self.num_classes = len(self.classes_name)
def process_xml(self):
for filename in os.listdir(self.data_path):
print(filename)
bboxes = []
one_hot_classes = []
tree = ElementTree.parse(self.data_path + filename)
root = tree.getroot()
image_name = root.find('filename').text
size_tree = root.find('size')
width = float(size_tree.find('width').text)
height = float(size_tree.find('height').text)
# 遍历每一个对象
for object_tree in root.findall('object'):
bodbox = object_tree.find('bndbox')
# 归一化
xmin = float(bodbox.find('xmin').text) / width
ymin = float(bodbox.find('ymin').text) / height
xmax = float(bodbox.find('xmax').text) / width
ymax = float(bodbox.find('ymax').text) / height
bbox = [xmin, ymin, xmax, ymax]
class_name = object_tree.find('name').text
one_hot_class = self.one_hot(class_name)
bboxes.append(bbox)
one_hot_classes.append(one_hot_class)
bboxes = np.array(bboxes)
one_hot_classes = np.array(one_hot_classes)
print(bboxes)
print(one_hot_classes)
# 同维度进行堆叠
image_data = np.hstack((bboxes, one_hot_classes))
print(image_data)
if len(image_data) > 0:
self.data[image_name] = image_data
def one_hot(self, name):
one_hot_v = [0] * self.num_classes
try:
index = self.classes_name.index(name)
one_hot_v[index] = 1
except:
print('unknown label:', name)
return one_hot_v
if __name__ == '__main__':
xp = XmlProcess('./objects/Annotations/')
xp.process_xml()
pickle.dump(xp.data, open('objects_gt.pkl', 'wb'))
classes.py代码如下:
classes_name = ['red', 'yellow', 'green', 'off', 'wait_on']
模型训练
train_ssd.py
1、定义类,进行初始化网络基础参数
2、获取标记数据,分成训练集与测试集
3、进行模型参数加载以及模型的结构freeze
4、设置训练参数以及fit
import os
import pickle
import keras.optimizers
from datasets.classes import classes_name
from utils.ssd_utils import BBoxUtility
from utils.generator import Generator
from utils.ssd_losses import MultiboxLoss
from nets.ssd import SSD300
import tensorflow as tf
class SSDTrain:
def __init__(self, num_classes, input_shape=(300, 300, 3), epoch=30, batch_size=16):
self.num_classes = num_classes
self.input_shape = input_shape
self.epoch = epoch
self.batch_size = batch_size
self.prior_boxes_file = 'datasets/prior_boxes_ssd300.pkl'
self.gt_pkl_file = 'datasets/objects_gt.pkl'
self.weight_file = 'ckpt/pre_trained/weights_SSD300.hdf5'
# self.weight_file = 'ckpt/fine_tuning/loss.16-1.01.h5'
self.path_prefix = 'datasets/objects/JPEGImages/'
self.save_weight_dir = 'ckpt/fine_tuning/'
# 读取定义好的先验框的参数
priors = pickle.load(open(self.prior_boxes_file, 'rb'))
# 获取先验框对象
self.bbox_util = BBoxUtility(self.num_classes , priors=priors)
# 定义模型
self.model = SSD300(input_shape = self.input_shape, num_classes = self.num_classes)
# 图片生成器
def image_generator(self, train=0.8):
# 获取标记数据
gt = pickle.load(open(self.gt_pkl_file, 'rb'))
keys = sorted(gt.keys())
num_train = round(train * len(keys))
train_keys = keys[:num_train]
val_keys = keys[num_train:]
# Generator获取数据, 不做裁剪
gen = Generator(gt, self.bbox_util,
self.batch_size, self.path_prefix,
train_keys, val_keys, (self.input_shape[0], self.input_shape[1]), do_crop=False)
return gen
# 初始化参数,可冻结部分网络层
def init_model_param(self):
self.model.load_weights(self.weight_file, by_name=True)
# 选择freeze部分结构
freeze = ['input_1', 'conv1_1', 'conv1_2', 'pool1',
'conv2_1', 'conv2_2', 'pool2',
'conv3_1', 'conv3_2', 'conv3_3', 'pool3']
for L in self.model.layers:
if L.name in freeze:
L.trainable = False
# 编译
def compile(self):
# 使用keras库的Adam
optimizer = keras.optimizers.Adam()
distribution = tf.contrib.distribute.MirroredStrategy()
# , metrics=['accuracy'], 准确率没有参考价值对于预测框,衡量loss即可
self.model.compile(optimizer=optimizer, loss=MultiboxLoss(self.num_classes).compute_loss, distribution=distribution)
# 迭代器训练
def fit_generator(self, gen):
callbacks = [
keras.callbacks.ModelCheckpoint(filepath=os.path.join(self.save_weight_dir, "loss.{epoch:02d}-{loss:.2f}.h5"),
monitor="loss",
save_best_only=True,
save_weights_only=True,
mode='auto',
period=1),
keras.callbacks.TensorBoard(log_dir='./graph',
write_graph=True, write_images=False, histogram_freq="1")
]
# Generator().generate(True)代表训练集
# Generator().generate(False)代表验证集
# Generator().train_batches代表train_keys的长度
# Generator().val_batches代表val_keys的长度
self.model.fit_generator(gen.generate(True), samples_per_epoch=gen.train_batches, nb_epoch=self.epoch,
verbose=1, callbacks=callbacks, validation_data=gen.generate(False), nb_val_samples=gen.val_batches)
if __name__ == '__main__':
ssdTrain = SSDTrain(num_classes=len(classes_name) + 1, input_shape=(300, 300, 3), epoch=20, batch_size=32)
gen = ssdTrain.image_generator(train=0.8)
ssdTrain.init_model_param()
ssdTrain.compile()
ssdTrain.fit_generator(gen)
本地预测图片
test_ssd.py
预测./images文件夹下的文件
import numpy as np
from keras.applications.imagenet_utils import preprocess_input
from keras.preprocessing.image import load_img, img_to_array
from imageio import imread
import os
from nets.ssd import SSD300
from utils.ssd_utils import BBoxUtility
import matplotlib.pyplot as plt
from datasets.classes import classes_name
class SSDImagePredict(object):
def __init__(self):
# self.classes_name = ['aeroplane', 'bicycle', 'bird', 'boat', 'bottle',
# 'bus', 'car', 'cat', 'chair', 'cow', 'diningtable',
# 'dog', 'horse', 'motorbike', 'person', 'pottedplant',
# 'sheep', 'sofa', 'train', 'tvmonitor']
self.classes_name = classes_name
self.classes_nums = len(self.classes_name) + 1
self.input_shape = (300, 300, 3)
def test(self):
model = SSD300(self.input_shape, num_classes=self.classes_nums)
# by_name=True 用于微调或迁移学习模型
model.load_weights('./ckpt/fine_tuning/loss.16-1.01.h5', by_name=True)
# model.load_weights('./ckpt/pre_trained/weights_SSD300.hdf5', by_name=True)
# 循环读取图片进行多个图片输出检测
feature = []
images_data = []
for pic_name in os.listdir("./images/"):
img_path = os.path.join("./images/", pic_name)
# PIL格式读取图片
img = load_img(img_path, target_size=(self.input_shape[0], self.input_shape[1]))
# PIL格式转换成numpy数组
img = img_to_array(img)
# 保存numpy数组
feature.append(img)
# 保存读取图像返回的numpy数组
images_data.append(imread(img_path))
# 处理numpy数组图片数据,对图像数据进行标准化,使其具有与训练数据相同的均值和方差
inputs = preprocess_input(np.array(feature))
# 预测 (n, 7308, 33) n为图片数量
pred = model.predict(inputs, batch_size=1, verbose=1)
# 定义BBox工具
bbox_util = BBoxUtility(self.classes_nums)
# 使用非最大抑制算法过滤
# 返回每张图片的预测列表。每个预测是:[标签、置信度、xmin、ymin、xmax、ymax]
res = bbox_util.detection_out(pred)
print(res)
return res, images_data
def tag_picture(self, res, images_data, acc):
for i, img in enumerate(images_data):
# 解析输出结果, 每张图片的标签,置信度和位置
pre_label = res[i][:, 0]
pre_conf = res[i][:, 1]
pre_xmin = res[i][:, 2]
pre_ymin = res[i][:, 3]
pre_xmax = res[i][:, 4]
pre_ymax = res[i][:, 5]
# print("label:{}, probability:{}, xmin:{}, ymin:{}, xmax:{}, ymax:{}".
# format(pre_label, pre_conf, pre_xmin, pre_ymin, pre_xmax, pre_ymax))
# 过滤置信度低的结果
top_indices = [i for i, conf in enumerate(pre_conf) if conf >= acc]
top_conf = pre_conf[top_indices]
top_label_indices = pre_label[top_indices].tolist()
top_xmin = pre_xmin[top_indices]
top_ymin = pre_ymin[top_indices]
top_xmax = pre_xmax[top_indices]
top_ymax = pre_ymax[top_indices]
# 定义21中颜色,显示图片
# hsv 是 Matplotlib 中的一个颜色映射(colormap), HSV 颜色模型特别适合于可视化那些具有周期性变化的数据
colors = plt.cm.hsv(np.linspace(0, 1, 21)).tolist()
plt.imshow(img)
# currentAxis增加图中文本显示和标记显示,获取当前轴
currentAxis = plt.gca()
for i in range(top_conf.shape[0]):
xmin = round(top_xmin[i] * img.shape[1])
ymin = round(top_ymin[i] * img.shape[0])
xmax = round(top_xmax[i] * img.shape[1])
ymax = round(top_ymax[i] * img.shape[0])
# 获取该图片预测概率,名称,定义显示颜色
score = top_conf[i]
label = int(top_label_indices[i])
label_name = self.classes_name[label - 1]
display_txt = '{:0.2f}, {}'.format(score, label_name)
rect = (xmin, ymin), xmax - xmin + 1, ymax - ymin + 1
color = colors[label]
# 显示方框 fill是否填充 edgecolor指定边线颜色,linewidth指定边线宽度
currentAxis.add_patch(plt.Rectangle(*rect, fill=False, edgecolor=color, linewidth=2))
# 左上角显示概率以及名称
currentAxis.text(xmin, ymin, display_txt, bbox={'facecolor': color, 'alpha': 0.5})
plt.show()
if __name__ == '__main__':
ssd = SSDImagePredict()
res, images_data = ssd.test()
ssd.tag_picture(res, images_data, 0.6)
本地预测摄像头视频
test_ssd_video.py
预测摄像头捕获的视频
步骤:
1、SSD300模型输入、加载参数
2、获取摄像头每帧数据,进行格式处理BGR->RGB,大小处理为300x300,,preprocess_input以及保存图像像素值
3、模型预测结果,得到priorbox
4、进行非最大抑制算法处理
5、结果显示,画图,显示物体位置和FPS值
import numpy as np
from keras.applications.imagenet_utils import preprocess_input
from keras.preprocessing.image import load_img, img_to_array
import cv2
from nets.ssd import SSD300
from utils.ssd_utils import BBoxUtility
from datasets.classes import classes_name
import colorsys
class SSDVideoPredict(object):
def __init__(self):
# self.classes_name = ['aeroplane', 'bicycle', 'bird', 'boat', 'bottle',
# 'bus', 'car', 'cat', 'chair', 'cow', 'diningtable',
# 'dog', 'horse', 'motorbike', 'person', 'pottedplant',
# 'sheep', 'sofa', 'train', 'tvmonitor']
self.classes_name = classes_name
self.classes_nums = len(self.classes_name) + 1
self.input_shape = (300, 300, 3)
self.model = SSD300(self.input_shape, num_classes=self.classes_nums)
# by_name=True 用于微调或迁移学习模型
self.model.load_weights('./ckpt/fine_tuning/01-2.54.h5', by_name=True)
# self.model.load_weights('./ckpt/pre_trained/weights_SSD300.hdf5', by_name=True)
# 定义BBox工具
self.bbox_util = BBoxUtility(self.classes_nums)
# 定义颜色
self.class_colors = []
for i in range(classes_nums):
# HSV转RGB
hue = 255 * i / classes_nums
saturation = 1 # 饱和度
value = 1 # 亮度
r, g, b = colorsys.hsv_to_rgb(hue, saturation, value)
# 将RGB值从0-1范围转换为0-255范围
col = [int(r * 255), int(g * 255), int(b * 255)]
class_colors.append(col)
def run(self, acc=0.8):
cap = cv2.VideoCapture('video/25772425798-1-192.mp4')
while cap.isOpened():
# 一帧一帧读取摄像头内容
res, frame = cap.read()
if not res:
print('无法读取帧')
break
frame = self.tag_frame(frame, acc)
fps = "FPS: " + str(cap.get(cv2.CAP_PROP_FPS))
cv2.putText(frame, fps, (3, 10), cv2.FONT_HERSHEY_SIMPLEX, 0.35, (0, 0, 0), 1, cv2.LINE_AA)
# 显示转换后的颜色
cv2.imshow('frame', frame)
# 按下'q'键退出循环,这个必须要不然没画面一直无响应
if cv2.waitKey(1) == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
def tag_frame(self, frame, acc=0.8):
# 更改为输入尺寸
resized = cv2.resize(frame, (self.input_shape[0], self.input_shape[1]))
# BGR -> RGB
rgb = cv2.cvtColor(resized, cv2.COLOR_BGR2RGB)
# 处理numpy数组图片数据,对图像数据进行标准化,使其具有与训练数据相同的均值和方差, 需要四维
inputs = [img_to_array(rgb)]
x = preprocess_input(np.array(inputs))
# 预测 (n, 7308, 33) n为图片数量q
y = self.model.predict(x)
# 使用非最大抑制算法过滤
# 返回每张图片的预测列表。每个预测是:[标签、置信度、xmin、ymin、xmax、ymax]
res = self.bbox_util.detection_out(y)
print(res[0][0])
if len(res) > 0:
# 解析输出结果, 每张图片的标签,置信度和位置
pre_label = res[0][:, 0]
pre_conf = res[0][:, 1]
pre_xmin = res[0][:, 2]
pre_ymin = res[0][:, 3]
pre_xmax = res[0][:, 4]
pre_ymax = res[0][:, 5]
# 过滤置信度低的结果
top_indices = [i for i, conf in enumerate(pre_conf) if conf >= acc]
top_conf = pre_conf[top_indices]
top_label_indices = pre_label[top_indices].tolist()
top_xmin = pre_xmin[top_indices]
top_ymin = pre_ymin[top_indices]
top_xmax = pre_xmax[top_indices]
top_ymax = pre_ymax[top_indices]
# 定义21中颜色,显示图片
# hsv 是 Matplotlib 中的一个颜色映射(colormap), HSV 颜色模型特别适合于可视化那些具有周期性变化的数据
# currentAxis增加图中文本显示和标记显示,获取当前轴
for i in range(top_conf.shape[0]):
xmin = round(top_xmin[i] * frame.shape[1])
ymin = round(top_ymin[i] * frame.shape[0])
xmax = round(top_xmax[i] * frame.shape[1])
ymax = round(top_ymax[i] * frame.shape[0])
# 获取该图片预测概率,名称,定义显示颜色
score = top_conf[i]
label = int(top_label_indices[i])
label_name = self.classes_name[label - 1]
display_txt = '{:0.2f}, {}'.format(score, label_name)
color = self.class_colors[label]
cv2.rectangle(frame, (xmin, ymin), (xmax, ymax), color, 1)
cv2.putText(frame, display_txt, (xmin, ymin), cv2.FONT_HERSHEY_SIMPLEX, 0.35, color, 1, cv2.LINE_AA)
return frame
if __name__ == '__main__':
ssd = SSDVideoPredict()
ssd.run(acc=0.6)
多GPU训练
使用distribution = tf.contrib.distribute.MirroredStrategy()
compile中添加 distribution=distribution
# 编译
def compile(self):
# 使用keras库的Adam
optimizer = keras.optimizers.Adam()
distribution = tf.contrib.distribute.MirroredStrategy()
self.model.compile(optimizer=optimizer, loss=MultiboxLoss(self.num_classes).compute_loss, distribution=distribution)
tensorflow GPU
poetry add tensorflow_gpu==1.14
安装cuda10.0
CUDA Toolkit Archive | NVIDIA Developer
只勾选CUDA中的全部,但不包括visual studio integration
环境变量会自动加好
安装cuDNN7.4
cuDNN Archive | NVIDIA Developer
压缩包解压
将其中’bin‘中的’cudnn64_7.dll‘拷贝到cuda10.0安装路径下的bin文件夹中
检查是否安装成功
D:\CUDAROOT\CUDA Development\extras\demo_suite\bandwidthTest.exe
D:\CUDAROOT\CUDA Development\extras\demo_suite\deviceQuery.exe
均返回Result=PASS
验证
import tensorflow as tf
print(tf.test.is_gpu_available())
import tensorflow as tf
device = tf.test.gpu_device_name()
if device:
print('Default GPU Device' , device)
else:
print('Please install GPU version of TF')
版本对应关系

OpenCV-python
介绍
OpenCV是Intel@开源计算机视觉库 OpenCV包含如下几个部分: Cxcore:一些基本函数(各种数据类型的基本运算等) Cv:图像处理和计算机视觉功能(图像处理,结构分析,运动分析,物体跟踪,模式识别,摄像机定 MI:机器学习模块,目前内容主要为分类器 Cvaux:一些实验性的函数(ViewMorphing,三维跟踪,PCA, HMM) Highgui:用户交互部分,(GUI,图象视频1/0,系统调用函数)
import cv2
安装
poetry add opencv-python
HelloWorld
摄像头捕获视频处理成灰色
import numpy as np
import cv2
cap = cv2.VideoCapture(0)
while cap.isOpened():
# 一帧一帧读取摄像头内容
res, frame = cap.read()
if not res:
print('无法读取帧')
break
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 显示转换后的颜色
cv2.imshow('frame', frame)
# 按下'q'键退出循环,这个必须要不然没画面一直无响应
if cv2.waitKey(1) == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
摄像头捕获视频
cap = cv2.VideoCapture(0)
参数
0:默认摄像头
file_path:视频文件
返回值
VideoCapture对象
cap.read() 读取捕获内容
返回值:
res, frame 读取状态和帧数据
cap.isOpened()
返回值
res 检查摄像头是否被成功打开
cap.get(cv2.CAP_PROP_FPS)获取fps
cap.release() 释放摄像头
cv2.imshow(‘frame’, frame) 显示图片
cv2.destroyAllWindows() 销毁所有窗口
cv2.waitKey(1) 用于等待特定的毫秒数(在这种情况下是1毫秒),以便从键盘捕获按键事件
cv2颜色空间变换
OpenCV默认颜色顺序是BGR
cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) 颜色空间变换
参数
frame 帧数据
cv2.COLOR_BGR2GRAY 可为
cv2.COLOR_BGR2GRAY BGR -> GRAY
cv2.COLOR_BGR2HSV BGR -> HSV
cv2.COLOR_BGR2RGB BGR -> RGB
返回值
新frame
cv2画图函数
画矩形
cv2.rectangle(frame, (xmin, ymin), (xmax, ymax), color, thickness)
参数
frame 被画的帧
(xmin, ymin) 左上角顶点
(xmax, ymax) 右下角顶点
color 画框颜色 RGB 如(0, 255, 0)
thickness 线粗。如果为-1则为填充
cv2.rectangle(frame, (20, 20), (100, 100), (0, 255, 0), 1)
画圆
cv2.circle(frame, (x, y), r, color, thickness)
参数
frame 被画的帧
(x, y) 圆心
r 半径
color 画框颜色 RGB 如(0, 255, 0)
thickness 线粗,如果为-1则为填充
cv2.circle(frame, (20, 20), 20, (0, 255, 0), 1)
画文字
cv2.putText(frame, text, (x, y), font, fontScale, (0, 255, 0), thickness, lineType)
参数
frame 被画的帧
text 文字
(x, y) 绘制位置
font 字体类型
cv2.FONT_HERSHEY_SIMPLEX
cv2.FONT_HERSHEY_PLAIN
cv2.FONT_HERSHEY_DUPLEX
cv2.FONT_HERSHEY_COMPLEX
cv2.FONT_HERSHEY_TRIPLEX
cv2.FONT_HERSHEY_COMPLEX_SMALL
cv2.FONT_HERSHEY_SCRIPT_SIMPLEX
cv2.FONT_HERSHEY_SCRIPT_COMPLEX
fontScale 字体缩放因子
(0, 255, 0) color 画框颜色 RGB
thickness 线粗
lineType 画线类型
cv2.LINE_4
cv2.LINE_8
cv2.LINE_AA(抗锯齿线)
cv2.putText(frame, 'python', (20, 20), font, fontsize, (0, 255, 0), 1, cv2.LINE_AA)
画fps
fps = "FPS: " + str(cap.get(cv2.CAP_PROP_FPS))
cv2.putText(frame, fps, (3, 10), cv2.FONT_HERSHEY_SIMPLEX, 0.35, (0, 0, 0), 1, cv2.LINE_AA)
YOLO
使用python3.9
创建虚拟环境
这里只使用poetry创建虚拟环境
D:\Python39\python -m pip install poetry 安装1poetry
D:\Python39\python -m poetry init 初始化
D:\Python39\python -m poetry env use D:\Python39\python.exe 创建虚拟环境
安装cuda11.8
(用tensorflow旧版本的也可以,感觉不必要)
CUDA Toolkit Archive | NVIDIA Developer
只勾选CUDA中的全部,但不包括visual studio integration,也不包括Hsight VSE
环境变量会自动加好
安装cuDNN8.9.7
(用tensorflow旧版本的也可以,感觉不必要)
cuDNN Archive | NVIDIA Developer
压缩包解压
将其中’bin‘中的’cudnn64_7.dll‘拷贝到cuda10.0安装路径下的bin文件夹中
检查是否安装成功
D:\CUDAROOT\CUDA Development\extras\demo_suite\bandwidthTest.exe
D:\CUDAROOT\CUDA Development\extras\demo_suite\deviceQuery.exe
均返回Result=PASS
安装pytorch
poetry 虚拟环境中
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
Looking in indexes: https://download.pytorch.org/whl/cu118
Collecting torch
Downloading https://download.pytorch.org/whl/cu118/torch-2.5.1%2Bcu118-cp39-cp39-win_amd64.whl
下载慢,可以直接把网址拿去用其他工具下载
然后安装
pip install torch-2.5.1+2Bcu118-cp39-cp39-win_amd64.whl
下载yolov源代码
github官网下载
yolov11
GitHub - ultralytics/ultralytics: Ultralytics YOLO11 🚀
yolo环境安装
pip install ultralytics
下载所需要的预训练模型
源码地址往下翻,找到权重文件,点击下载即可
| Model | size (pixels) | mAPval 50-95 | Speed CPU ONNX (ms) | Speed T4 TensorRT10 (ms) | params (M) | FLOPs (B) |
|---|---|---|---|---|---|---|
| YOLO11n | 640 | 39.5 | 56.1 ± 0.8 | 1.5 ± 0.0 | 2.6 | 6.5 |
| YOLO11s | 640 | 47.0 | 90.0 ± 1.2 | 2.5 ± 0.0 | 9.4 | 21.5 |
| YOLO11m | 640 | 51.5 | 183.2 ± 2.0 | 4.7 ± 0.1 | 20.1 | 68.0 |
| YOLO11l | 640 | 53.4 | 238.6 ± 1.4 | 6.2 ± 0.1 | 25.3 | 86.9 |
| YOLO11x | 640 | 54.7 | 462.8 ± 6.7 | 11.3 ± 0.2 | 56.9 | 194.9 |
将下载的预训练权重拷贝到解压的工程目录下
验证能否运行
yolo predict model=yolo11n.pt source=ultralytics/assets/bus.jpg
runs/detect/predict目录则会生成预测后的文件
准备数据集
工程目录下添加如下
datasets
images
test
train
labels
test
train
Ultralytics版本的YOLO所需格式的数据集标签为txt格式的文本文件,文本文件中保存的标签信息分别为:类别序号、中心点x/y坐标、长宽的归一化信息,每一行对应一个对象
0 0.064453 0.287109 0.042969 0.032552
0 0.098633 0.362630 0.042969 0.029948
工程目录下创建 data.yaml文件保存数据集的相关信息
# dataset path
train: ./images/train
val: ./images/test
test: ./images/test
# number of classes
nc: 15
# class names
names: ['car', 'Truck', 'Van', 'Long Vehicle','Bus', 'Airliner', 'Propeller Aircraft', 'Trainer Aircraft', 'Chartered Aircraft', 'Fighter Aircraft',\
'Others', 'Stair Truck', 'Pushback Truck', 'Helicopter', 'Boat']
模型训练
训练之后会默认在runs/detect/下生成train*目录
runs
detect
train
weights
best.pt 训练过程中性能最好的模型的权重文件
last.pl 训练结束时的最后一次模型的权重文件(若需继续训练可选)
一些参考量图片
args.yaml(配置文件)
confusion_matrix.png(原始混淆矩阵) 用于评估目标检测模型的分类性能
confusion_matrix_normalized.png(归一化混淆矩阵)
labels.jpg(数据集信息)用于展示训练数据集中的标签分布情况,帮助理解数据集的类别分布和标签的几何特性。
labels_correlogram.jpg(标签相关图)中心点横纵坐标以及框的高宽间的关系,通过分析这类图,可以更好地理解数据集的特征,为模型训练和优化提供依据。
results.csv(训练指标结果)记录了每个训练 epoch 的详细信息,包括每轮训练的指标和结果
results.png(结果指标曲线)显示了训练过程中关键指标(如损失、mAP)的变化趋势
train_batchX.jpg(X 表示 batch 序号)显示了在训练过程中对某些批次(batch)的图像进行检测后的结果,通常用于展示训练中预测的效果。
val_batchX_labels.jpg 显示了验证集中某些图像的真实标签,主要用于与预测结果进行对比,以观察模型在验证集上的表现
val_batchX_pred.jpg 显示了验证集中某些图像的预测标签
命令行
yolo detect train data=data.yaml model=yolo11n.pt epochs=5 batch=16 imgsz=640 device=0
device=0,1 多GPU训练
更多参数见Train - Ultralytics YOLO Docs
代码
工程目录下创建train.py文件
from ultralytics.models import YOLO
if __name__ == '__main__':
model = YOLO(model='yolo11n.pt')
model.train(data='./data.yaml', epochs=5, batch=16, device=[0], imgsz=640)
模型验证
验证之后会默认在runs/detect/下生成val*目录
runs
detect
val
confusion_matrix.png(原始混淆矩阵) 用于评估目标检测模型的分类性能
confusion_matrix_normalized.png(归一化混淆矩阵)
val_batchX_labels.jpg 显示了验证集中某些图像的真实标签,主要用于与预测结果进行对比,以观察模型在验证集上的表现
val_batchX_pred.jpg 显示了验证集中某些图像的预测标签
命令行
yolo detect val model=./runs/detect/train/weights/best.pt data=data.yaml imgsz=640 batch=16 device=0
更多参数见Val - Ultralytics YOLO Docs
代码
工程目录下创建val.py文件
from ultralytics.models import YOLO
if __name__ == '__main__':
model = YOLO(model='./runs/detect/train/weights/best.pt')
model.val(data='./data.yaml', split='val', imgsz=640, batch=16, device=[0])
模型预测
命令行模式预测之后会默认在runs/detect/下生成predict目录,是预测后标注的图片
文件形式需要自己去处理
命令行
yolo detect predict model=yolo11n.pt source='ultralytics/assets/bus.jpg' conf=0.6 device=0
conf默认0.2
代码
工程目录下创建detect.py文件
from ultralytics.models import YOLO
if __name__ == '__main__':
model = YOLO(model='yolo11n.pt')
results = model.predict(source='ultralytics/assets/bus.jpg', device=[0], imgsz=640, conf=0.6)
# 可处理results
处理文本
import colorsys
from ultralytics.models import YOLO
import cv2
if __name__ == '__main__':
model = YOLO(model='yolo11n.pt')
# 定义颜色
class_colors = []
classes_nums = len(model.names)
for i in range(classes_nums):
# HSV转RGB
hue = 255 * i / classes_nums
saturation = 1 # 饱和度
value = 1 # 亮度
r, g, b = colorsys.hsv_to_rgb(hue, saturation, value)
# 将RGB值从0-1范围转换为0-255范围
col = [int(r * 255), int(g * 255), int(b * 255)]
class_colors.append(col)
results = model.predict(source='ultralytics/assets/bus.jpg', device=[0], imgsz=640, conf=0.6)
# 可处理results
for res in results:
frame = res.orig_img
# # 将预测结果绘制到帧上
L = len(res.boxes.cls)
W = res.orig_shape[1]
H = res.orig_shape[0]
for box in res.boxes:
cls = int(box.cls.cpu().numpy()[0])
print(cls)
conf = box.conf.cpu().numpy()[0]
label = f'{model.names[cls]} {conf:.2f}'
xminn, yminn, xmaxn, ymaxn = box.xyxyn.cpu().numpy()[0]
xmin = round(xminn * W)
ymin = round(yminn * H)
xmax = round(xmaxn * W)
ymax = round(ymaxn * H)
color = class_colors[cls]
cv2.rectangle(frame, (xmin, ymin), (xmax, ymax), color, 1)
cv2.putText(frame, label, (xmin, ymin), cv2.FONT_HERSHEY_SIMPLEX, 0.35, color, 1, cv2.LINE_AA)
cv2.imshow('frame', frame)
cv2.waitKey(0)
处理视频
yolo detect predict model=yolo11n.pt source='ultralytics/assets/25772425798-1-192.mp4' device=0
predict目录会生成标注的avi文件,不会播放出来
使用代码可自己任意定义
import colorsys
from ultralytics.models import YOLO
import cv2
if __name__ == '__main__':
model = YOLO(model='yolo11n.pt')
# 定义颜色
class_colors = []
classes_nums = len(model.names)
for i in range(classes_nums):
# HSV转RGB
hue = 255 * i / classes_nums
saturation = 1 # 饱和度
value = 1 # 亮度
r, g, b = colorsys.hsv_to_rgb(hue, saturation, value)
# 将RGB值从0-1范围转换为0-255范围
col = [int(r * 255), int(g * 255), int(b * 255)]
class_colors.append(col)
cap = cv2.VideoCapture(0)
while cap.isOpened():
# 一帧一帧读取摄像头内容
res, frame = cap.read()
if not res:
print('无法读取帧')
break
results = model.predict(source=frame, device=[0], imgsz=640, conf=0.6)
# 可处理results
for res in results:
frame = res.orig_img
# # 将预测结果绘制到帧上
L = len(res.boxes.cls)
W = res.orig_shape[1]
H = res.orig_shape[0]
for box in res.boxes:
cls = int(box.cls.cpu().numpy()[0])
conf = box.conf.cpu().numpy()[0]
label = f'{model.names[cls]} {conf:.2f}'
xminn, yminn, xmaxn, ymaxn = box.xyxyn.cpu().numpy()[0]
xmin = round(xminn * W)
ymin = round(yminn * H)
xmax = round(xmaxn * W)
ymax = round(ymaxn * H)
color = class_colors[cls]
cv2.rectangle(frame, (xmin, ymin), (xmax, ymax), color, 1)
cv2.putText(frame, label, (xmin, ymin), cv2.FONT_HERSHEY_SIMPLEX, 0.35, color, 1, cv2.LINE_AA)
fps = "FPS: " + str(cap.get(cv2.CAP_PROP_FPS))
cv2.putText(frame, fps, (3, 10), cv2.FONT_HERSHEY_SIMPLEX, 0.35, (0, 0, 0), 1, cv2.LINE_AA)
# 显示转换后的颜色
cv2.imshow('frame', frame)
# 按下'q'键退出循环,这个必须要不然没画面一直无响应
if cv2.waitKey(1) == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
不用自己画跟简单
使用res.plot()返回画好的帧
from ultralytics.models import YOLO
import cv2
if __name__ == '__main__':
model = YOLO(model='yolo11n.pt')
# 定义颜色
cap = cv2.VideoCapture(0)
while cap.isOpened():
# 一帧一帧读取摄像头内容
res, frame = cap.read()
if not res:
print('无法读取帧')
break
# results = model.track(source=frame, device=[0], imgsz=640, conf=0.6, persist=True)
results = model.predict(source=frame, device=[0], imgsz=640, conf=0.6)
# 可处理results
annotated_frame = results[0].plot()
fps = "FPS: " + str(cap.get(cv2.CAP_PROP_FPS))
cv2.putText(annotated_frame, fps, (3, 10), cv2.FONT_HERSHEY_SIMPLEX, 0.35, (0, 0, 0), 1, cv2.LINE_AA)
# 显示转换后的颜色
cv2.imshow('frame', annotated_frame)
# 按下'q'键退出循环,这个必须要不然没画面一直无响应
if cv2.waitKey(1) == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
模型导出
命令行
yolo export model=yolo11n.pt format=onnx
代码
为便于部署,将模型进行导出为onnx\torchscript\tensorflow等格式。
工程目录下创建export.py脚本,填入想要导出的模型路径。
from ultralytics import YOLO
# Load a model
model = YOLO("yolo11n.pt") # load an official model
# model = YOLO("path/to/best.pt") # load a custom trained model
# Export the model
model.export(format="onnx")
目标追踪
在视频或流媒体源上运行的所有 Detect、Segment 和 Pose 模型都可以进行跟踪
比如Detect
会加上id
命令行
yolo track model=yolo11n.pt source="https://youtu.be/LNwODJXcvt4" conf=0.3
代码
import colorsys
from ultralytics.models import YOLO
import cv2
if __name__ == '__main__':
model = YOLO(model='yolo11n.pt')
# 定义颜色
cap = cv2.VideoCapture(0)
while cap.isOpened():
# 一帧一帧读取摄像头内容
res, frame = cap.read()
if not res:
print('无法读取帧')
break
results = model.track(source=frame, device=[0], imgsz=640, conf=0.6, persist=True)
# 可处理results
annotated_frame = results[0].plot()
fps = "FPS: " + str(cap.get(cv2.CAP_PROP_FPS))
cv2.putText(annotated_frame, fps, (3, 10), cv2.FONT_HERSHEY_SIMPLEX, 0.35, (0, 0, 0), 1, cv2.LINE_AA)
# 显示转换后的颜色
cv2.imshow('frame', annotated_frame)
# 按下'q'键退出循环,这个必须要不然没画面一直无响应
if cv2.waitKey(1) == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
维持原视频速度只需要添加如下代码
# 获取视频的帧率
fps = cap.get(cv2.CAP_PROP_FPS)
wait_time = int(1000 / fps)
# 按下'q'键退出循环,这个必须要不然没画面一直无响应
if cv2.waitKey(wait_time) == ord('q'):
break