( ):包含的是元组
[ ]:包含的是列表
{ }:包含的是对象
开发前提:安装PyQt5,Numpy,OpenCV
http://39.98.127.39:3000/zhangyuhao2018302110032/WHU_AI_exercise
人工智能 > 机器学习 > 深度学习
经典编程输入数据和规则,得到答案
机器学习输入数据和答案,得到规则
机器学习和深度学习的区别
线性运算模型
y = h ( x ) = ω ∗ x + b y = h(x) = ω * x + b y=h(x)=ω∗x+b
x成为特征,为一维向量;函数h(x)称为假设;ω、b为参数;输出y为特征x1、x2、x3……的线性组合
y = h ( x ) = ω T x ( T 表 示 转 置 ) y = h(x) = ω^Tx(T表示转置) y=h(x)=ωTx(T表示转置)
机器学习就是利用训练数据x、y来找到最合适的ω和b,即规律
监督学习和无监督学习
监督学习:已知特征x,标记y(实际值),根据模型h(x)得到输出y¯即预测值,用公式计算误差,修改算法减小误差
样本误差用两者的差的平方的二分之一表示
e = 1 2 ( y − y ˉ ) 2 e = \frac{1}{2}(y-\bar{y})^2 e=21(y−yˉ)2
e为单个样本的误差
多个样本误差为
E ( ω ) = e ( 1 ) + e ( 2 ) + e ( 3 ) + … + e ( n ) = ∑ i = 1 n e ( i ) = 1 2 ∑ i = 1 n ( y ( i ) − y ˉ ( i ) ) 2 \begin{aligned} E(ω) &= e^{(1)}+e^{(2)}+e^{(3)}+…+e^{(n)}\\ &=\sum_{i=1}^n{e^{(i)}}\\ &=\frac{1}{2}\sum_{i=1}^n{(y^{(i)}-\bar{y}^{(i)})^2} \end{aligned} E(ω)=e(1)+e(2)+e(3)+…+e(n)=i=1∑ne(i)=21i=1∑n(y(i)−yˉ(i))2
想要得到最优算法,就是取E(ω)的最小值,就是找到合适的ω使E取最小值,利用梯度下降算法实现
梯度下降算法
所有测试数据产生的误差组成的向量,用梯度下降算法减小误差,优化AI算法
原理:自变量向梯度的相反方向迭代,找到极值点
方法:用xnew = xold - grad*η迭代找到最后的x
最后的实现
ω n e w = w o l d + η ∑ i = 1 n ( y ( i ) − y ˉ ( i ) ) x ( i ) ω_{new} = w_{old} + η\sum_{i=1}^n{(y^{(i)}-\bar{y}^{(i)})x^{(i)}} ωnew=wold+ηi=1∑n(y(i)−yˉ(i))x(i)
神经元
线性模型就是一个神经网络的一层的,也是一个线性模型的神经元
多层神经网络就是将上一层输入的y当做x输入到下一层进行计算,直到最后一层输入的y为预测值
技术框架选择:TensorFlow、Pytorch
UI实现:Qt
利用torch求导
import torch
x = torch.Tensor([5])
x.requires_grad=True
y = x**2
y.backward()
print(x.grad)
z = 2*x
z.backward()
print(x.grad)
输出:
tensor([10.])
tensor([12.])
不管对x进行了多少次计算,只要在每次计算后调用backward()方法,就会把梯度累加到.grad属性上
Numpy实现梯度下降
import numpy as np
x = 0
grad_fn = lambda x: 2*x-2
learning_rate = 0.01
epoch = 1000
x_list = []
for e in range(epoch):
x_grad = grad_fn(x)
x -= learning_rate * x_grad
x_list.append(x)
print(x)
实现图表的可视化工具
import matplotlib.pyplot as plt
plt.plot(range(epoch),x_list)
plt.show()
Pytorch实现梯度下降
import torch
x = torch.Tensor([1000.0])# 100
x.requires_grad = True
learning_rate = 0.01
opoch = 1000
x_list = []
for e in range(epoch):
#1. 损失函数
y = x ** 2 -2 * x + 1
#2. 求导
y.backward(retain_graph=True)
#废除导数跟踪环境
with torch.autograd.no_grad():
# 3更新
x -= learning_rate * x.grad
# 4 记录
x_list.append(x.detach().clone().numpy())
x.grad.zero_()
print(x.detach().clone().numpy())
损失函数:需要被求导的函数即为损失函数,y表示损失或者是误差。等价于E(ω)=f(ω)
废除跟踪环境:在求导后需要更新x,此时需要废除跟踪,并清除累积的grad
数据集分类(鸢尾花)学习实现
import sklearn
import sklearn.datasets
data,target = sklearn.datasets.load_iris(return_X_y=True)
print(data.shape)
print(target.shape)
data为x张量,即150朵花,每朵花的4个属性;target为y张量,即150朵花的分类
输出:
(150,4)
(150,)
x = torch.Tensor(data[0:100])
y = torch.Tensor(target[0:100]).view(100,1)
x的矩阵为100*4的形状,为了让x和y一一对应,将y转置成100*1的形状
w = torch.randn(1,4)
b = torch.randn(1)
#记录操作
w.requires_grad = True
b.requires_grad = True
#轮数10000
epoch = 10000
#学习率
learning_rate = 0.0001
w随机生成,符合正态分布;b随机生成;epoch一般来说越大越好;learning_rate一般为小数,根据测试情况改变大小
for e in range(epoch):
#算法模型(此处为线性模型)
y_ = torch.nn.functional.linear(input=x,weight=w,bias=b)
#激活函数:把y_变成能与y计算误差的值
sy_ = torch.sigmoid(y_)
#损失函数:计算误差
loss = torch.nn.functional.binary_cross_entropy(sy_,y,reduction="mean")
#求导
loss.backward()
with torch.autograd.no_grad():
#优化器:更新参数w = w - 导数*学习率
w -= learning_rate * w.grad
b -= learning_rate * b.grad
#清零
w.grad.zero_()
b.grad.zero_()
sy_[sy_>0.5] = 1
sy_[sy_<=0.5] = 0
#准确率
correct_rate = (sy_ == y).float().mean()
利用torch提供的线性函数来计算出y_;利用激活函数把y_变成能和目标值y计算误差的预测值;利用损失函数计算预测值y_和目标值y的误差;对loss求导得到w和b的梯度;在优化器中利用梯度下降算法更新参数;计算准确率实时观察学习效果,求平均值mean()只能在float类型上处理
由许多神经元组成,每一个神经元都是一个优化模型
神经元的学习:梯度下降算法优化学习参数
神经网络的学习:反向传播算法优化学习参数
深度学习
神经网络中的神经元被分为若干层,层间神经元有连接,层内的神经元无连接。最左边的叫输入层,负责接受输入数据。最右边的叫输出层,可以从该层获取输出数据。中间的部分叫做隐藏层。隐藏层大于2的神经网络叫做深度神经网络,深度学习就是使用深层架构(如深度神经网络)的机器学习方法
反向传播算法
旨在得到最优的全局参数矩阵
前向传递输入信号直至产生误差,反向传播误差信息更新权重
常见损失函数
binary_cross_entropy(input, target, weight=None, size_average=None, reduce=None, reduction):对数损失函数,没有做逻辑分布函数运算
binary_cross_entropy_with_logits(input, target, weight=None, size_average=None, reduce=None, reduction, pos_weight=None):自动做逻辑分布函数运算
poisson_nll_loss(input, target, log_input, full, size_average, eps, reduce, reduction):泊松负对数似然损失
……等
常见激活函数
threshold(input, threshold, value, inplace=False):线性函数的阈值,本质是relu的功能增强版
relu(input, inplace=False):线性整流函数,又称修正线性单元
……等
如果第n层的每个神经元与第n-1层的每个神经元全部相连;第n-1层的神经元的输出就是第n层神经元的输入;每个连接都有一个权值,那么该网络为全连接神经网络
多层全连接神经网络的实现
思路:神经网络分层为 4 -> 12 -> 6 -> 3;预测模型:采用默认求导实现,激活函数采用sigmoid;损失模型:采用交叉熵损失函数
演算:
第一层:150个元素,使用4个神经元表示每个元素4个特征。则用一个150*4的张量存储
第二层:使用12个神经元表示每个元素由4个特征计算得12个新特征,则用12个4*1的向量w即一个12*4的张量进行计算,每个4特征的元素与12个4*1的w计算得出该元素12个新特征。结果用一个150*12的张量存储
第三层:使用6个神经元表示每个元素由12个特征计算得6个新特征,则用6个12*1的向量w即一个6*12的张量进行计算,每个12特征的元素与6个12*1的w计算得出该元素的6个新特征。结果用一个150*6的张量存储
第四层:使用3个神经元表示每个元素由6个特征计算得3个新特征,则用3个6*1的向量w即一个3*6的张量进行计算,每个6特征的元素与3个6*1的w计算得出该元素的3个新特征。结果用一个150*3的张量存储
如果处理的是图片数据,使用线性网络会使图片像素的位置特征丢失,所以需要使用其他神经网络
图像的特征:两个像素点的差值/两个像素点的位置距离
卷积运算
用n*n的卷积核在m*m的图像中移动,使用卷积核对应的权重与图像中对应像素相乘并求和,得到卷积特征,然后卷积核右移(到边界后回到下一行的最左端)。得到所有卷积特征形成一个m-n+1*m-n+1的新图像。如果不想图片变小,可以用0补边
卷积核:卷积核本质是一个数值矩阵,由矩阵的大小和值来决定;卷积用来作为权重,卷积核就是一个权重矩阵
卷积操作:卷积操作就是加权求和操作,由步长和加权求和确定
卷积特征:卷积操作后得到的数值成为卷积特征,所有的卷积特征构成新的图像
补边
经过卷积操作的卷积特征图像与原始图像大小不同。为了使卷积特征图像大小与原始图像相同,采用补边方法。即在卷积操作前,对原始图像周边增加几圈0元素(也可以补1等其他值)
池化
也称向下采样,与卷积操作基本相同,不过向下采用只取对应位置的最大值、平均值等(最大池化、平均池化),采样的值成为池化特征
池化的步长与池化核的边长相同
主要使用2D卷积做图像处理
卷积函数
torch.nn.functional.conv2d(input, weight, bias=None, stride=1, padding=0, dilation=1, group)
:input为一个四维张量(批量数,深度,高度,宽度);weight为(输出数,深度,高度,宽度);bias偏移量为一维张量;stride步长;padding补边长度;dilation卷积核的间隔
tips:读取图像后,需要将其转化成灰度图像,即单通道的二维图片,然后利用torch.Tensor().view()转化成一个四维张量
池化函数
torch.nn.functional.avg_pool2d(input, kernel_size, stride=None, padding=0, ceil_mode=False, count_include_pad=True, divisor_override=None)
:平均池化函数。input同卷积函数;kernel_size为池化核大小;padding为补边大小;count_include_pad表示计算平均的时候是否考虑padding的0;ceil_mode表示当计算输出形状时,采用取最大还是最小
torch.nn.functional.max_pool2d(\*arg, \*\*kwargs)
:最大池化函数。同平均池化一样,就是运算方式不同
演算:
输入为N张深度为1层的32*32的图像
第一层:1层32*32的图像经过6个5*5的卷积核,每个卷积核分别和图像的每一层进行卷积运算,共得到6张28*28的卷积特征图像,即6层28*28的图像。再经过2*2的池化核进行池化得到6层14*14的池化特征图像,输出给下层
第二层:6层14*14的图像经过16个5*5的卷积核,每个卷积核分别和图像的每一层进行卷积运算,同一卷积核下的6张图像进行元素相加变成1张10*10的图像,共得到16层10*10的图像。再经过2*2的池化核进行池化得到16层5*5的池化特征图像,输出给下层
第三层:16层5*5的图像经过120个5*5的卷积核分别进行卷积运算,每个卷积核分别和图像的每一层进行卷积运算,同一卷积核下的16张图像进行元素相加变成1张1*1的图像,共得到120层1*1的图像,即一个大小为120的一维张量,输出给下层
第四层:由全连接的线性层将120个特征转换成84个特征,输出给下层
第五层:由全连接的线性层将84个特征转换成10个特征,输出10个特征
核心模块
基本模块:函数调用torch;数据结构管理torch.Tensor;数据存储管理torch.Stroage;数据类型管理torch.finfo与torch.iinfo
机器学习和深度学习运算模块:torch.nn.functional
自动求导模块:求导函数backward和grad;上下文管理enable_grad、no_grad、set_grad_enabled(mode)
前向传播深度学习模块:决策模型容器封装;决策模型的运算封装;训练参数封装;其他运算的封装
数据集管理模块:数据集管理,包含数据切分、随机洗牌、交叉验证
样本数据集模块:获取训练数据集
GPU运算模块:torch.cuda
分布式运算模块:torch.distributed
其他模块:持久化模型存储、模型评估、可视化仪表盘、调试诊断
完整的功能设计文档
技术文档(md格式)
学习笔记两篇
code
源代码、安装包、答辩包
类图
需要提交类图
包管理
QT包管理、AI包管理、项目脚本、工具脚本
模块发布
python setup.py sdist
命令打包python setup.py install
命令安装setup.py:
from distutils.core import setup
setup(
name="haha",
version="1.0",
description="haha",
author="ming"
package=[
"app",
"app.uis"
],
scripts=["haha.bat"]
)
工作场景使用Git服务器
组员操作
git init
git remote add origin ...git
git pull --rebase origin master
git branch ming 创建自己的分支
git checkout ming 切换到自己的分支
工作新建代码文件 coding...
git pull --rebase origin master 拉取远程主分支最新代码
git add .
git commit -m "提交注释"
git push -u origin ming
下班
上班
git pull --rebase origin master 拉取远程主分支最新代码
工作新建代码文件 coding
git pull --rebase origin master 拉取远程主分支最新代码
git add .
git commit -m "提交注释"
git push -u origin ming
下班
组长操作
git clone ......git
git checkout -b ming origin/ming b选项指定本地分支名
git checkout master
git merge ming 把ming分支的代码合并到主分支
git push --set-upstream origin master 把合并后的代码推送到主分支
读取图片、标签
import struct
import numpy as np
# 读取图片
def load_image_fromfile(filename):
with open(filename, 'br') as fd:
# 读取图像的信息
header_buf = fd.read(16) # 16字节,4个int整数
# 按照字节解析头信息(具体参考python SL的struct帮助) 解包
magic_, nums_, width_, height_ = struct.unpack('>iiii', header_buf) # 解析成四个整数:>表示大端字节序,i表示4字节整数
# 保存成ndarray对象
imgs_ = np.fromfile(fd, dtype=np.uint8)
imgs_ = imgs_.reshape(nums_, height_, width_)
return imgs_
# 读取标签
def load_label_fromfile(filename):
with open(filename, 'br') as fd:
header_buf = fd.read(8)
magic, nums = struct.unpack('>ii' ,header_buf)
labels_ = np.fromfile(fd, np.uint8)
return labels_
读取训练数据集
import loader
import torch
import torch.utils.data
from lenet import LeNet5
train_x = loader.load_image_fromfile('data/train-images.idx3-ubyte')
train_y = loader.load_label_fromfile('data/train-labels.idx1-ubyte')
读取测试数据集
test_x = loader.load_image_fromfile('data/t10k-images.idx3-ubyte')
test_t = loader.load_label_fromfile('data/t10k-labels.idx1-ubyte')
将数据转换成(N, C, H, W)格式的四维张量
import torch
x = torch.Tensor(train_x).view(train_x.shape[0],1,train_x.shape[1],train_x.shape[2])
y = torch.Tensor(train_y)
t_x = torch.Tensor(test_x).view(test_x.shape[0],1,test_x.shape[1],test_x.shape[2])
t_y = torch.Tensor(test_y)
使用torch封装数据
train_dataset = torch.utils.data.TensorDataSet(x,y)
test_dataset = torch.utils.data.TensorDataSet(t_x,t_y)
在之后取数据的时候可以直接用过utils的方法按批次来取
数据随机加载,按批切分
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,shuffle=True,batch_size=2000)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset,shuffle=True,batch_size=10000)
参数shuffle表示将数据随机打乱再取
训练模型:模型、优化器、损失函数、训练轮数、学习率
from lenet import LeNet5
model = LeNet5() #forward()的调动方法model(input)
params = model.paramaters()
cri = torch.nn.CrossEntropLoss() #损失函数
opt = torch.optim.Adam(model.parameters(), lr=0.001) #优化器、学习率
epoch = 2 #训练轮数
for e in range(epoch):
#1轮 整个训练数据集学习一次
for data,target in train_loader:
#批处理:批梯度下降,权重更新
opt.zero_grad()
out = model(data)
loss = cri(out, target)
loss.backward()
opt.step()
#计算准确率 建议一百轮打印一次
with torch.no_grad():
for data,target in test_loader:
y_ = model(data)
predict = torch.argmax(y_,dim=1)
c_rate = (predict == target).float().mean()
print(F"准确率:{c_rate*100:5.2f}%")
#保存模型:保存整个网络结构和参数 通过torch.load()使用
torch.save(model,"lenet.pth")
可用可视化在线工具查看模型结构
torch.save(model.state_dict, "lenet.pth")
只保存参数,不能用可视化工具查看
import torch
class LeNet5(torch.nn.Module):
#重写__init__, forward()
#定义层
def __init__(self):
super(LeNet5,self).__init__()
#层 torch.nn.层
#原始数据N=60000, C=1, H=28, W=28
#第一层卷积 1@28*28 卷积得(含padding)6@28*28 池化得6@14*14
self.layer1 = torch.nn.Conv2d(in_channels=1, out_channels=6, kernel_size=(5,5), padding=2)
#第二层卷积 6@14*14 卷积得16@10*10 池化得16@5*5
self.layer2 = torch.nn.Conv2d(in_channels=6, out_channels=16, kernel_size(5,5), padding=0)
#第三层卷积 16@5*5 卷积得120@1*1
self.layer3 = torch.nn.Conv2d(in_channels=16, out_channels=120, kernel_size=(5,5), padding=0)
#第四层全连接 120 线性运算得84
self.layer4 = torch.nn.Linear(120, 84)
#第五层全连接 84 线性运算得10
self.layer5 = torch.nn.Linear(84, 10)
#计算
def forward(self, input):
#第一层计算
t = self.later1(input)
#激活函数
t = torch.nn.functional.relu(t)
#池化
t = torch.nn.functional.max_pool2d(t, kernel_size=(2,2))
#第二层计算
t = self.layer2(t)
t = torch.nn.functional.relu(t)
t = torch.nn.functional.max_pool2d(t, kernel_size=(2,2))
#第三层计算
t = self.layer3(t)
t = torch.nn.functional.relu(t)
#降维操作
t = t.squeeze()
#第四层计算
t = self.layer4(t)
t = torch.nn.functional.relu(t)
#第五层操作
t = self.layer5(t)
t = torch.nn.functional.log_softmax(t, dim=1)
return t
线性层可以不作为层级结构,仅作为计算方式使用,方法为torch.nn.functional.linear()
同理池化可以作为层级结构使用,方法为torch.nn.MaxPool2d()
ImageFolder类
负责加载图像数据集
ImageFolder(root, transform=None, target_transform=None, loader=, is_valid_file=None):root为指定图像的目录;transform为对图像的转换;target_transform为对标签的转换;loader为指定加载函数;is_valid_file为对文件进行格式验证
通过OpenCV读取到的图片是NumPy类型,通过ImageFolder读取到的图片是PIL类型
ImageFolder返回的数组,每个元素包含一个图片和一个类别下标
包含的属性:classes,列表类型,包含所有类别的名字;class_to_idx,字典类型,包含类名和类别下标;imgs,列表类型,包含图片路径和类别下标
加载图像数据集和训练前的预处理
from torchvision.datasets import ImageFolder
from torchvision.transforms import Compose, ToTensor, Normalize, RandomHorizontalFlip, RandomVerticalFlip, RandomResizedCrop
# 随机切片的模块 数据加载器模块
from torch.utils.data import random_split, DataLoader
# 加载目录数据集的功能函数 进行预处理和训练数据 测试数据切分 返回数据加载器
# 数据目录 拆分比例 一批的图片个数
def load_data(img_dir, rate=0.8, batch_size=128):
#Compose 定义转换结构,是一个管道([,,,,,,])
transform = Compose(
[
#修改尺寸 224*224
RandomResizedCrop((224,224)),
RandomHorizontalFlip(),
RandomVerticalFlip(),
# 转换为张量 并且值 0-1
ToTensor(),
# 均值与方差(必须放在ToTensor之后)
#[0.5,0.8.0.9]
#(x1-mean1)/std1 序列化可以改变张量中元素的范围
Normalize(mean=[0.5,0.5293289,0.48351972], std=[0.5,0.21455203,0.22451781]) # 均值与方差 (必须Tensor图像)
]
)
# ImageFolder(地址,数据转换管道(一系列预处理操作))
ds = ImageFolder(img_dir,transform=transform)
#训练数据 测试数据切分
l = len(ds)
#训练数据个数l_train 测试数据个数l-l_train
l_train = int(l*rate)
# 数据 [l_train ,l-l_train]
train,test = random_split(ds,[l_train,l-l_train])
#print(train[0])# 图片tensor数据 类别下标
#print(train[0][1])
#print(ds.classes)
#print(ds.class_to_idx) 类别和类别下标的关系字典
#print(ds.imgs)
train_loader = DataLoader(dataset=train, shuffle=True, batch_size=batch_size)
test_loader = DataLoader(dataset=test, shuffle=True, batch_size=batch_size)
return train_loader, test_loader, ds.class_to_idx
# train_loader,test_loader,class_to_idx = load_data('./image')
# print(class_to_idx)
构建训练网络,封装训练模型
import torch
from torchvision.models import resnet18 #18层残差网络 神经网络
from model.loaddataset import load_data # 数据加载模块
import os, sys # 系统模块
# 训练模型 封装一个训练模型的过程
# 定义神经网络 加载数据集 是否使用GPU 训练(轮 损失 优化) 测试准确率
class TrainResNet:
# 构造函数 数据集路径ds_dir 轮数 学习率
def __init__(self, ds_dir = './data', epoch = 10, lr = 0.0001):
super(TrainResNet,self).__init__()
print("训练准备.......")#开始
# 二进制模型文件
self.model_file= "./gb.mod"
# self.CUDA true false
self.CUDA = torch.cuda.is_available()
# 1、数据集 self.tr, self.ts, self.cls_idx
self.tr, self.ts, self.cls_idx = load_data(ds_dir, batch_size = 128)
#模型是可以累加的 100 + 100
# 2、网络
if os.path.exists(self.model_file):
print("加载本地模型")
# 定义 self.net = 残差神经网络(pretrained=False)
self.net = resnet18(pretrained=False)
# 设置类别个数 1000-40
fc_features = self.net.fc.in_features
#resnet网络最后一层分类层fc是对1000种类型进行划分, layer
self.net.fc = torch.nn.Linear(fc_features,40) # 40分类
#torch中的所有数据和操作都可以加.cuda(),然后会使用GPU进行计算
if self.CUDA:
self.net.cuda()
# 加载本地模型
state = torch.load(self.model_file)
self.net.load_state_dict(state) #加载本地参数
else:
print("初始化预训练模型")
self.net = resnet18(pretrained=False)
fc_features = self.net.fc.in_features
self.net.fc = torch.nn.Linear(fc_features,40) # 40分类
if self.CUDA:
self.net.cuda()
# 3、参数
self.epoch = epoch
self.lr = lr
# 4、优化器 很多 选择 adam
self.optimizer = torch.optim.Adam(self.net.parameters(), lr = self.lr)
# 5、损失函数
self.loss_function = torch.nn.CrossEntropyLoss()
if self.CUDA:
self.loss_function = self.loss_function.cuda()
#执行训练
def execute(self):
print("训练开始.....")
# 轮数迭代
for e in range(self.epoch):
# self.net.train() 训练
self.net.train()
for samples,labels in self.tr:
self.optimizer.zero_grad()
if self.CUDA:
samples = samples.cuda()
labels = labels.cuda()
# 计算输出 -1表示原数组长度,避免原数组长度为1时结构出错
samples = samples.view(-1,3,224,224)
# 计算预测值
y = self.net(samples)
# 计算损失y labels
loss = self.loss_function(y,labels)
# 求导
loss.backward()
# 更新梯度
self.optimizer.step()
# 每轮 验证准确率 使用测试数据集
# 使用数据集验证
correct_rate = self.validate()
print(F"轮数:{e+1:03d}")
print(F"正确率:{correct_rate:5.4f}%")
print(F"损失值:{loss:8.6f}")
# 训练结束,保存模型
torch.save(self.net.state_dict(), self.model_file)
# torch.save(self.net, 'gb.pth')
# 验证
@torch.no_grad()
def validate(self):
num_samples = 0.0
num_correct = 0.0
self.net.eval()
for samples, labels in self.ts:
if self.CUDA:
samples = samples.cuda()
labels = labels.cuda()
num_samples += len(samples)
# 计算输出
out = self.net(samples.view(-1, 3, 224, 224))
# 激活 0--1
out = torch.nn.functional.softmax(out, dim=1)
# 概率最大的类别下标和概率值
y = torch.argmax(out, dim=1)
num_correct += (y == labels).float().sum()
return num_correct * 100.0 / num_samples
#使用系统模块属性,动态传参
#sys.argv[0]为系统当前文件的路径
if (len(sys.argv)) >=4:
trainer = TrainResNet(sys.argv[1],int(sys.argv[2]),float(sys.argv[3]))
else:
trainer = TrainResNet()
trainer.execute()
识别测试数据
import torch
from torchvision.models import resnet18
from torchvision.transforms import Resize, Compose, ToTensor, Normalize, RandomHorizontalFlip, RandomVerticalFlip, RandomResizedCrop
import cv2 as cv
from PIL import Image
from torchvision.datasets import ImageFolder
# 设置管道 类别和类别下标
transform = Compose([
Resize((224,224)),
RandomHorizontalFlip(),
RandomVerticalFlip(),
ToTensor(),
Normalize(mean=[0.56719673,0.5293289,0.48351972], std=[0.20874391,0.21455203,0.22451781]) # 均值与方差 (必须Tensor图像)
])
#类别和类别下标
ds = ImageFolder("./data",transform=transform)
#垃圾识别的类
class GarbageRecognizer:
# 参数:模型文件的路径
def __init__(self, model_file="./gb.mod"):
super(GarbageRecognizer, self).__init__()
#1 设置模型文件
self.model_file = model_file
# 判断cuda
self.CUDA = torch.cuda.is_available()
#2.模型定义
self.net = resnet18(pretrained=False, num_classes=40)
if self.CUDA:
self.net.cuda()
#3.加载模型
state = torch.load(self.model_file)
self.net.load_state_dict(state)
print("模型加载完毕!")
self.net.eval()
@torch.no_grad()
# 参数:要识别的图片的路径
def recognize(self,img):
with torch.no_grad():
# 返回固定格式的图片数据
img = self.trans_data(img)
if self.CUDA:
img = img.cuda()
# 1 3 224 224
img = img.view(-1,3,224,224)
# 预测
y = self.net(img)
# 激活 生成目标个数 概率
p_y = torch.nn.functional.softmax(y, dim=1)
# 概率最大的元素 概率值p和类别下标cls_idx
p, cls_idx = torch.max(p_y, dim=1)
return cls_idx.cpu(), p.cpu()
# 传入图片路径
def trans_data(self,img):
# 执行和训练数据一样的数据预处理
transform = Compose([
Resize((224,224)),
RandomHorizontalFlip(),
RandomVerticalFlip(),
ToTensor(),
Normalize(mean=[0.56719673,0.5293289,0.48351972], std=[0.20874391,0.21455203,0.22451781]) # 均值与方差 (必须Tensor图像)
])
# 读取图片
img = cv.imread(img)
# BGR(OpenCV)--->RGB(PIL)
img = cv.cvtColor(img,cv.COLOR_BGR2RGB)
# 转换为PIL Image
img = Image.fromarray(img)
# 执行 transform
img = transform(img)
return img
r = GarbageRecognizer()
cls, p = r.recognize('./data/13/fimg_1343.jpg')
# print(cls.numpy()[0])
# print(p.numpy()[0])
# 类别下标
cls = cls.numpy()[0]
print("类别下标",cls)
# print(ds.class_to_idx)
# 类别下标===>类别名称
cls = ds.classes[cls]
print("类别名称",cls)
# 把实际类别换成名称
from model.handle_by_dir import ClassifyRule
tool = ClassifyRule()
category = tool.read_rules()
print(category)
print(category[cls], ":", p.numpy()[0])
识别一张图片中的某一区域,需要手动在图片中进行标记
一张图片中可以包含多个类别
工具:精灵标注助手
YOLO实时检测
yolo的核心思想就是利用整张图像作为网络的输入,直接在输出层回归bounding box的位置和bounding box所属的类别
配置数据集
datasets中包含:coco128文件夹、coco.data、coco.names、train.txt
coco.names:类别名文件,其中类别名按照类别id进行索引
train.txt:训练使用的图像数据集文件。可以用脚本实现
coco.data:组织训练与测试的数据集工程文件。classes指定类别数;train指定训练数据集;valid指定验证测试数据集;names指定类别名
classes=80
train=datasets/train.txt
valid=datasets/train.txt
names=datasets/coco.names
tips:系统寻找类别id时,会将图像路径中的images字符自动替换成labels,来寻找图片对应的类别
模型文件准备
取自github上的cfg文件,常用yolov4-tiny.cfg文件
权重文件准备
取自github上的pt文件,本次使用的是yolov4-tiny.pt文件。使用的权重文件应该与模型文件相对应
训练调用
调用已有的train.py文件
python train.py
--epoch 3 ^
--batch-size 3 ^
--data datasets/coco.data ^
--cfg cfg/yolov4-tiny.cfg ^
--weights weights/yolov4-tiny.pt ^
--name yolov4-tiny
--img 640 640 640
epoch训练轮数;batch-size数据集批次大小;data datasets/coco.data数据集工程组织文件;cfg cfg/yolov4-tiny.cfg模型文件;weights weights/yolov4-tiny.pt预训练的权重文件;name yolov4-tiny保存训练过程产生的数据的文件名,会添加一个result前缀
tips:python中^
表示符号后的所有字符都看作普通字符。用^加回车可以表示不换行
tips:yolov4-tiny.pt为预训练中的初始权重,在预训练结束后会生成一个best_yolov4-tiny.pt最好权重和一个last_yolov4-tiny.pt最后权重文件。在下次进行预训练时,可以删除多余的权重文件,留下更新过后的权重文件,并更名为yolov4-tiny.pt
目标侦测调用
调用已有的detect.py文件
python detect.py ^
--cfg cfg/yolov4-tiny.cfg ^
--weights weights/yolov4-tiny.pt ^
--names datasets/coco.names ^
--source imgs/ ^
--img-size 640 ^
--iou-thres 0.2 ^
--conf-thres 0.1 ^
--device 0
source imgs/指定需要识别的图像所在目录;device 0识别使用GPU的编号;iou-thr目标重叠度阈值,根据重合度判断类别;conf-thres置信度阈值
tips:置信度阈值表示当识别出的事物概率大于某个值以后就认为识别出来了;目标重叠度阈值会影响识别后标记框在事物上的标记位置和准确度
实现json向YOLOlabels的转换
import os
import json
names = {
"dog":0,
"cat":1,
}
# 单个文件 格式转换
def format_label(json_file,out_path):
file_name = os.path.basename(json_file)
# '1.json' 1.txt
only_name = file_name.split(".")[0]
out_file = os.path.join(out_path,F"{only_name}.txt")
#json_file out_file
with open(json_file) as fd:
json_data = json.load(fd)
#print(json_data)
#解析
is_labeled = json_data["labeled"]
if is_labeled:
# 图像宽高
img_w = json_data["size"]["width"]
img_h = json_data["size"]["height"]
# 标注目标 数组
objects = json_data["outputs"]["object"]
# 打开新文件
out_fd = open(out_file,"w")
for obj in objects:
name = obj["name"]
xmin = obj["bndbox"]["xmin"]
xmax = obj["bndbox"]["xmax"]
ymin = obj["bndbox"]["ymin"]
ymax = obj["bndbox"]["ymax"]
#name对应的类别下标 中心点坐标 宽 高
name = names[name]
w = float(xmax-xmin)
h = float(ymax-ymin)
xcenter = xmin + w / 2
ycenter = ymin + h /2
# 归一化
w /= img_w
h /= img_h
xcenter /= img_w
ycenter /= img_h
#写入一个目标
out_fd.write(F"{name} {xcenter:.6f} {ycenter:.6f} {w:.6f} {h:.6f}")
out_fd.close()
print(F"完成{json_file}文件的转换")
#输入路径 输出路径
def to_yolo(in_path,out_path):
#判断输出路径是否存在
if not os.path.exists(out_path):
os.mkdir(out_path)
# 遍历in_path
all_json_files = os.listdir(in_path)
#print(all_json)
for json_file in all_json_files:
# 1.json 拼接路径
path_file = os.path.join(in_path,json_file)
format_label(path_file,out_path)
to_yolo("./outputs","labels")
目标侦测实现
from models import *
from utils.datasets import *
from utils.utils import *
import torch
import os
# 目标侦测
# 定义目标侦测类
# 步骤
# 1、init方法中 配置模型以及模型的参数
# 2、侦测图片目标 返回边界框 类别 概率
# 具体包括:
# 加载图片 opencv
# 对图片数据预处理
# 侦测
# 数据处理
# 类别下标--》具体类别名字
# 返回边界框 类别 概率
# 3、侦测图片目标 返回带有标记的图片和类别
# 获取当前路径
current_path= os.path.dirname(__file__)
class YOLOv4Detector:
def __init__(self,
img_size=640,
cfg_file="yolov4-tiny.cfg",
weights="yolov4-tiny.pt",
names="coco.names"):
#参数详细化
self.img_size = img_size
# 路径
self.cfg_file = os.path.join(current_path,F"cfg/{cfg_file}")
self.weights = os.path.join(current_path,F"weights/{weights}")
self.names = os.path.join(current_path,F"datasets/{names}")
#配置模型
self.model = Darknet(self.cfg_file, self.img_size)
# 加载训练好的模型
self.model.load_state_dict(torch.load(self.weights)['model'])
self.CUDA = torch.cuda.is_available()
if self.CUDA:
self.model.cuda()
# 不调用求导 权重更新的方法
self.model.eval()
#self.names 路径 ====》具体的类别的值
#load_classes utils.utils
self.names = load_classes(self.names)
def detect(self,img0):
"""
img0:opencv读取的原始图片
"""
#图片的预处理
img = self.format_img(img0)
# print("--------format----------------")
# print(img.shape)
if self.CUDA:
img = img.cuda()
# 计算侦测结果
pred = self.model(img, augment=False)[0]
pred = pred.cpu()
# 进行最大化抑制
pred = non_max_suppression(pred, 0.3, 0.2, merge=False, classes=None, agnostic=False)
# print("------pred最大化抑制------")
# print(pred)
# 解析识别结果
for det in pred:
if det is not None and len(det):
det[:, :4] = scale_coords(img.shape[2:], det[:, :4], img0.shape).round()
return pred # 总长6:目标位置与大小(0:3),目标概率(4),目标类别[5]
# 图片的预处理
def format_img(self, img0):
# utils.datasets的方法letterbox
img = letterbox(img0, new_shape=self.img_size)[0]
img = img[:, :, ::-1].transpose(2, 0, 1) # BGR to RGB
img = np.ascontiguousarray(img)
img = torch.from_numpy(img)
img = img.float()
img /= 255.0 # 0 - 255 to 0.0 - 1.0
if img.ndimension() == 3:
img = img.unsqueeze(0)
return img
def load_image(self,img_file):
img0 = cv2.imread(img_file)
return img0
def get_name(self,idx):
return self.names[idx]
detector = YOLOv4Detector()
img = detector.load_image("imgs/000000000034.jpg")
pred = detector.detect(img)
print(pred)
result = pred[0][0]
# 边界框
rect = result[0:4].detach().numpy()
#概率
p = result[4].detach().item()
# 类别
clss = int(result[5].detach().item())
clss = detector.get_name(clss)
print(rect,p,clss)
tips:Tensor对象调用detach(),作用为去掉Tensor中的求导函数
tips:移植python文件时,要注意在导入模块和代码中路径的使用。利用bat运行时,导入模块需要从项目根路径去导入模块,即需要使用完整了根路径