数据获取方法
- 三大要素:数据、算法(神经网络)、算力
数据集分类
- 分类数据:图像分类,一般以目录的形式分开
- 标注数据:目标检测和图像分割,是有标注数据的
开源数据集
免费,成本低
-
PyTorch: Datasets — Torchvision 0.20 documentation
-
开源数据集imagenet:ImageNet
-
Hugging Face数据集:https://huggingface.co/datasets
-
kaggle数据集下载网址:https://www.kaggle.com/datasets
-
各种网站:
Computer Vision DatasetsDownload free, open source datasets for computer vision machine learning models in a variety of formats.https://public.roboflow.com/
https://zhuanlan.zhihu.com/p/648720525https://zhuanlan.zhihu.com/p/648720525
极市开发者平台-计算机视觉算法开发落地平台-极市科技极市开发者平台(Extreme Mart)是极视角科技旗下AI开发者生态,为计算机视觉开发者提供一站式算法开发落地平台,同时提供大咖技术分享、社区交流、竞赛活动、数据集下载、CV课程等丰富的内容与服务。https://www.cvmart.net/dataSets
外包平台
- 效果好,成本高,外包平台(Amazon Mechanical Turk,阿里众包,百度数据众包,京东微工等)
自己采集和标注
- 质量高、效率低、成本高。
- labelimg、labelme工具的使用。
pip install -i https://mirrors.aliyun.com/pypi/simple/ labelimg
pip install -i https://mirrors.aliyun.com/pypi/simple/ label
- 在安装工具环境下的命令行打开
- 图片标注
- 标注保存记录
- labelme使用大致同理,但标注方式不一样(点框),保存格式不一样,以json格式进行保存
通过网络爬虫获取
爬虫工具或爬虫代码
数据本地化
- 使用公开数据集时,会自动保存到本地。如果已下载,就不会重复下载。如果需要以图片的形式保存到本地以方便观察和重新处理,可以按照如下方式处理。
图片本地化
使用一下代码保存图片到本地
dir = os.path.dirname(__file__)
def save2local():trainimgsdir = os.path.join(dir, "MNIST/trainimgs")testimgsdir = os.path.join(dir, "MNIST/testimgs")if not os.path.exists(trainimgsdir):os.makedirs(trainimgsdir)if not os.path.exists(testimgsdir):os.makedirs(testimgsdir)
trainset = torchvision.datasets.MNIST(root=datapath,train=True,download=True,transform=transforms.Compose([transforms.ToTensor()]),)for idx, (img, label) in enumerate(trainset):labdir = os.path.join(trainimgsdir, str(label))os.makedirs(labdir, exist_ok=True)pilimg = transforms.ToPILImage()(img)# 保存成单通道的灰度图pilimg = pilimg.convert("L")pilimg.save(os.path.join(labdir, f"{idx}.png"))
# 加载测试集testset = torchvision.datasets.MNIST(root=datapath,train=False,download=True,transform=transforms.Compose([transforms.ToTensor()]),)for idx, (img, label) in enumerate(testset):labdir = os.path.join(testimgsdir, str(label))os.makedirs(labdir, exist_ok=True)pilimg = transforms.ToPILImage()(img)# 保存成单通道的灰度图pilimg = pilimg.convert("L")# 保存中不能有中文路径pilimg.save(os.path.join(labdir, f"{idx}.png"))
print("所有图片保存成功~~")
加载图片数据集
直接下载的图片文件目录也可以直接使用
trainpath = os.path.join(dir, "MNIST/trainimgs")
trainset = torchvision.datasets.ImageFolder(root=trainpath, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=32, shuffle=True)
本地图片序列化
- 把本地图片存储为 pickle序列化格式,然后通过 tar格式的形式分发。
- Pickle 和 Tar 是两种不同的文件格式,分别用于不同的目的。
Pickle 格式
- Pickle 是 Python 内置的模块,它允许你序列化和反序列化 Python 对象结构。这意味着你可以把几乎任何 Python 对象(如列表、字典、类实例等)转换为一个字节流(序列化),然后可以将其保存到磁盘上或通过网络传输。之后,你可以从这个字节流中恢复出原始的对象(反序列化)。Pickle 文件通常以 `.pkl` 或 `.pickle` 扩展名保存。
字节流
1.字节流(Byte Stream)是一种数据传输和存储的概念,指的是将数据表示为一系列的字节(8位二进制数)。每个字节由八个连续的比特组成,是计算机中最基本的信息单位之一。字节流可以用于表示任何类型的数据,比如文本、图片、音频、视频等,并且可以在程序内部处理、通过网络发送或保存到磁盘上。
2.在计算机科学中,字节流是一个抽象的概念,它描述了数据从一个地方到另一个地方流动的过程。
- 文件读写:当你从文件读取数据时,操作系统通常会提供一个接口,允许你以字节流的形式读取文件内容。同样,当你向文件写入数据时,你也通常是以字节流的形式写入。
- 网络通信:在网络编程中,客户端和服务器之间交换的数据也是以字节流的形式进行传输的。无论是HTTP请求、FTP传输还是其他类型的网络协议,数据都被编码成字节流后在网络上传输。
- 序列化/反序列化:如之前提到的 Pickle 模块,它可以将 Python 对象转换成字节流(序列化),然后可以通过网络发送或保存到磁盘。接收方可以读取这些字节并将它们转换回原始对象(反序列化)。
字节流的一个重要特性是它是无结构的,即它不包含关于如何解释这些字节的信息。这意味着,为了正确地处理字节流,发送方和接收方必须就数据格式达成一致,或者在字节流中嵌入额外的信息来描述数据结构(如使用特定的文件格式或协议)。
例如,当处理图像文件时,不同的图像格式(如JPEG、PNG等)会有不同的方式来组织字节流中的信息。解码器需要知道正在处理的是哪种格式才能正确解析这些字节。
此外,在某些情况下,字节流可能会经过压缩或加密,以便更高效地传输或保护数据的安全性。在这种情况下,接收方需要先解压缩或解密字节流,然后再进行进一步的处理。
Tar 格式
- Tar(Tape Archive)是一种文件格式,最初设计用于磁带归档。它可以把多个文件和目录打包成一个单独的文件,但不包括压缩功能(虽然 Tar 文件经常与压缩算法结合使用,例如 gzip 或 bzip2,生成 `.tar.gz` 或 `.tar.bz2` 文件)。Tar 文件保留了原始文件的元信息,比如权限、时间戳和目录结构等。
结合使用
为了将本地图片存储为 Pickle 序列化格式并以 Tar 形式分发,你可以按照以下步骤操作:
- 读取图片:用 Python 读取图片文件为二进制数据。
- 序列化:使用 pickle 模块将包含图片二进制数据的对象序列化。
- 保存 Pickle 文件:将序列化的对象保存为 Pickle 文件。
- 创建 Tar 包:使用 `tarfile` 模块将 Pickle 文件以及可能的其他相关文件打包成一个 Tar 文件。
- 分发 Tar 文件:你现在可以将这个 Tar 文件分发给其他人。
接收方可以解包 Tar 文件,然后使用 pickle 模块反序列化 Pickle 文件来恢复原始的图片数据。
过拟合处理
数据增强
- 可以使用transform完成对图像的数据增强,防止过拟合发生
Transforming and augmenting images — Torchvision 0.20 documentationhttps://pytorch.org/vision/stable/transforms.html
数据增强的方法
1.随机旋转
2.镜像
3.缩放
4.图像模糊
5.裁剪
6.翻转
7.饱和度、亮度、灰度、色相
8.噪声、锐化、颜色反转
9.多样本增强
- SamplePairing操作:随机选择两张图片分别经过基础数据增强操作处理后,叠加合成一个新的样本,标签为原样本标签中的一种。
- 多样本线性插值:Mixup 标签更平滑
- 直接复制:CutMix, Cutout,直接复制粘贴样本
- Mosic:四张图片合并到一起进行训练
数据增强的好处
- 查出更多训练数据:大幅度降低数据采集和标注成本;
- 提升泛化能力:模型过拟合风险降低,提高模型泛化能力
标准化
- 使数据更加的平滑,利于数据集的训练
DROP-OUT
- 处理过拟合问题的,丢去一定比列的神经元
欠拟合注意事项
- 欠拟合: 如果模型在训练集和验证集上表现都不够好,考虑增加模型的层级或训练更多的周期。
训练过程可视化
wandb.ai
- 可在控制台看到训练进度。
- 详情参考官方文档:
https://wandb.ai/https://wandb.ai/
使用参考实例
安装
在使用的python虚拟环境中安装
pip install -i https://mirrors.aliyun.com/pypi/simple/ wandb
登录运行
wandb login
复制平台提供的 API key粘贴回车即可(粘贴之后看不到的)。
案列代码
import torch
import torchvision.transforms as transforms
import os
from torchvision.datasets import MNIST
# 使用的网络要导进来
from model import numberModel
from torch.utils.data import DataLoader
import torch.nn as nn
import torch.optim as optim
import time
# 导入训练过程可视化
import wandb
import random
# 初始化配置
# start a new wandb run to track this script
wandb.init(# set the wandb project where this run will be loggedproject="my-awesome-project",# track hyperparameters and run metadataconfig={"learning_rate": 0.001,"architecture": "CNN","dataset": "MNIST","epochs": 10,}
)
# 路径处理
current_path = os.path.dirname(__file__)
data_path = os.path.realpath(os.path.join(current_path,'datasets'))
print(data_path)
# 保存模型目录
# weight_path = os.path.realpath(os.path.join(current_path,'weights'))
# 设备更换
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')def train():# 三要素之数据集获取train_datasets = MNIST(# 训练数据集root=data_path, train=True, transform=transforms.Compose([transforms.ToTensor(),# 张量transforms.Resize((32,32))# 输入为32*32]),download=True)# 三要素之网路model = numberModel()model.to(device)# 三要素之模型训练epochs = 10batch_size = 64# 损失函数,自带Softmax分类功能激活函数,但如果net输出不用softmax,只是分类但未有概率计算cred = nn.CrossEntropyLoss(reduction='sum')# 优化器optimizer = optim.Adam(model.parameters(), lr=0.001)offset = random.random() / 5for epoch in range(epochs):start_time = time.time() #开始时间accuracy = 0tatol_loss = 0# count = 0train_dataloader = DataLoader(train_datasets, batch_size=batch_size, shuffle=True)for i, (x,y) in enumerate(train_dataloader):x,y = x.to(device),y.to(device)# 使用网络进行预测yhat = model(x)# 预测正确率accuracy += torch.sum(torch.argmax(yhat,dim=1) == y)# 计算损失loss = cred(yhat,y)# 总损损失率tatol_loss += loss.item()# 梯度清零optimizer.zero_grad()# 方向传播loss.backward()# 更新梯度optimizer.step()# log metrics to wandbtorch.save(model.state_dict(), './weights/mnist_net.pth')print(f"训练轮次{epoch}/{epochs},耗时:{time.time()-start_time}, accuracy:{accuracy/len(train_datasets)}, mean_loss:{tatol_loss/len(train_dataloader)}")wandb.log({"acc": accuracy/len(train_datasets), "loss": tatol_loss/len(train_dataloader)})# 关闭 wandbwandb.finish()if __name__ == '__main__':train()
查看
根据控制台提供的访问地址去查看训练过程数据即可。
Tensor Board
参考官方文档进行操作:
Visualizing Models, Data, and Training with TensorBoard — PyTorch Tutorials 2.5.0+cu124 documentationhttps://pytorch.org/tutorials/intermediate/tensorboard_tutorial.html
准备工作
- 导入tensorboard操作模块
from torch.utils.tensorboard import SummaryWriter
- 指定tensorboard日志保存路径:可以指定多个实例对象
安装
pip install -i https://mirrors.aliyun.com/pypi/simple/ tensorboard
案列代码
import torch
import torchvision
import torchvision.transforms as transforms
import os
from torchvision.datasets import MNIST
# 使用的网络要导进来
from model import numberModel
from torch.utils.data import DataLoader
import torch.nn as nn
import torch.optim as optim
import time
# 导入训练过程可视化# 路径处理
current_path = os.path.dirname(__file__)
data_path = os.path.realpath(os.path.join(current_path,'datasets'))
print(data_path)
# 保存模型目录
weight_path = os.path.realpath(os.path.join(current_path,'weights'))
# 设备更换
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')# 引入训练过程可视化工具tensorboard
from torch.utils.tensorboard import SummaryWriter
# 指定tensorboard日志保存路径
# 不存在的目录在运行时会自动去创建
writer = SummaryWriter(log_dir='./tblogs/train3')def train():# 三要素之数据集获取train_datasets = MNIST(# 训练数据集root=data_path, train=True, transform=transforms.Compose([transforms.ToTensor(),# 张量transforms.Resize((32,32)),# 输入为32*32# 旋转transforms.RandomHorizontalFlip(),transforms.RandomRotation(45),]),download=True)# 三要素之网路model = numberModel()# 保存网络结构到tensorboardinput_to_model = torch.randn(1,1,32,32)writer.add_graph(model, input_to_model=input_to_model)model.to(device)# 三要素之模型训练epochs = 10batch_size = 256# 损失函数,自带Softmax分类功能激活函数,但如果net输出不用softmax,只是分类但未有概率计算cred = nn.CrossEntropyLoss(reduction='sum')# 优化器optimizer = optim.Adam(model.parameters(), lr=0.001)for epoch in range(epochs):start_time = time.time() #开始时间accuracy = 0tatol_loss = 0# count = 0train_dataloader = DataLoader(train_datasets, batch_size=batch_size, shuffle=True)for i, (x,y) in enumerate(train_dataloader):# 没100次记录一下if i%100 == 0:grid = torchvision.utils.make_grid(x)writer.add_image(f'images_{epoch}_{i}', grid, 0)x,y = x.to(device),y.to(device)# 使用网络进行预测yhat = model(x)# 预测正确率accuracy += torch.sum(torch.argmax(yhat,dim=1) == y)# 计算损失loss = cred(yhat,y)# 总损损失率tatol_loss += loss.item()# 梯度清零optimizer.zero_grad()# 方向传播loss.backward()# 更新梯度optimizer.step()# count += 1# if count < 100:# print(accuracy, len(train_datasets))# else:# breakprint(f"训练轮次{epoch}/{epochs},耗时:{time.time()-start_time}, accuracy:{accuracy/len(train_datasets)}, mean_loss:{tatol_loss/len(train_dataloader)}")# 记录训练数据到可视化面板writer.add_scalar("Loss/train", loss, epoch)writer.add_scalar("Accuracy/train", accuracy, epoch)# torch.save(model.state_dict(), os.path.join(weight_path, 'mnist_net.pth'))# 关闭 wandbwriter.close()if __name__ == '__main__':train()
运行并启动服务器
曲线查看
更多操作参考官方文档
验证结果数据化
- 我们可以把预测结果全部记录下来,以观察其效果,用于分析和评估我们的模型情况。
数据结果Excel
- 导入数据分析模块pandas
import pandas as pd
- 把推理结果softmax化,将预测概率,预测结果,真实结果拼接为一个完整的DataFrame数据,并循环不断拼接批次预测数据,最终以pandas进行Excel表格的输出
- 代码实现
import torch
from torchvision.datasets import MNIST
import os
import torchvision.transforms as transforms
from model import numberModel
from torch.utils.data import DataLoader# 生成验证数据用到的包
import pandas as pd
import numpy as np
# 关=关闭科学计数法
np.set_printoptions(suppress=True)# 路径处理
current_path = os.path.dirname(__file__)
data_path = os.path.realpath(os.path.join(current_path,'datasets'))
path_xls = os.path.realpath(os.path.join(current_path,'metrics', 'validation_metrics.xlsx'))print(data_path)
def modelVal():# 我应该有验证的数据vaildation_data = MNIST(data_path, train=False, download=True,transform=transforms.Compose([transforms.ToTensor(),transforms.Resize((32,32))]))device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")# 模型:训练好的模型model = numberModel()state_dict = torch.load('./runs/weights/mnist_net.pth')# 初始化到模型model.load_state_dict(state_dict)# 移到相应的数据上去model.to(device)accraccy = 0tatol_excel_data = np.empty((0,12))# 使用训练好的模型对验证数据进行推理,看一下推理效果怎么样for input,label in DataLoader(vaildation_data, batch_size=256):input = input.to(device)label = label.to(device)# 使用模型对数据进行推理y_pred = model(input)# 统计推理正确的记录数accraccy += torch.sum(torch.argmax(y_pred,dim=1) == label)# 装换成numpypred_excel_data = torch.argmax(y_pred,dim=1).cpu().detach().numpy()# 升维pred_excel_data = np.expand_dims(pred_excel_data, axis=1)# 真实值label_excel_data = label.cpu().unsqueeze(dim=1).detach().numpy().astype(int)excel_data = y_pred.cpu().detach().numpy()# 把两个numpy拼接位一个数据excel_data = np.concatenate((excel_data, pred_excel_data,label_excel_data), axis=1)# print(excel_data)# 拼接而不是追加tatol_excel_data = np.concatenate((tatol_excel_data,excel_data), axis=0)print(tatol_excel_data.shape)# 写入excel表格columns = vaildation_data.classes + ['pred', 'label']# columns = [*vaildation_data.classes,'pred', 'label']df = pd.DataFrame(tatol_excel_data, columns=columns)df.to_excel(path_xls, index=False)print("写入excel成功......")print(f"验证数据集的正确率:{accraccy/len(vaildation_data)}")if __name__ == '__main__':modelVal()
模型指标矩阵化
混淆矩阵
1.混淆矩阵是一种特定的表格布局,用于可视化监督学习算法的性能,特别是分类算法。在这个矩阵中,每一行代表实际类别,每一列代表预测类别。矩阵的每个单元格则包含了在该实际类别和预测类别下的样本数量。通过混淆矩阵,我们不仅可以计算出诸如准确度、精确度和召回率等评估指标,还可以更全面地了解模型在不同类别上的性能。
2.混淆矩阵的四个基本组成部分是:
- True Positives(TP):当模型预测为正类,并且该预测是正确的,我们称之为真正(True Positive);
- True Negatives(TN):当模型预测为负类,并且该预测是正确的,我们称之为真负(True Negative);
- False Positives(FP):当模型预测为正类,但该预测是错误的,我们称之为假正(False Positive);
- False Negatives(FN):当模型预测为负类,但该预测是错误的,我们称之为假负(False Negative)
常见指标
理解对角线
- 对角线上的元素越大越好
模型指标计算及可视化
验证数据报表化
import os
from sklearn.metrics import *
import pandas as pd
import numpy as np
# 路径兼容问题
current_path = os.path.dirname(__file__)
path_xls = os.path.realpath(os.path.join(current_path,'metrics', 'validation_metrics.xlsx'))def report():# 读取数据excel_data = pd.read_excel(os.path.join(path_xls))label = excel_data['label'].valuespred = excel_data['pred'].values# 整体报表class_report = classification_report(label, pred, target_names=['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'])print(class_report)# 准确度accracy = accuracy_score(label, pred)print("准确度:%.4f" % accracy)# 精确度precision = precision_score(label, pred, average='macro')print("精确度:%.8f" % precision)# 召回率recall = recall_score(label, pred, average='macro')print("召回率:%.8f" % recall)# f1-scoref1 = f1_score(label, pred, average='macro')print("f1:%.8f" % f1)if __name__ == '__main__':report()"""precision recall f1-score support0 0.91 0.99 0.95 9801 0.99 0.98 0.98 11352 0.71 0.94 0.81 10323 0.66 0.89 0.76 10104 0.95 0.91 0.93 9825 0.00 0.00 0.00 8926 0.82 0.96 0.89 9587 0.90 0.87 0.88 10288 0.89 0.90 0.90 9749 0.90 0.93 0.91 1009accuracy 0.85 10000macro avg 0.77 0.84 0.80 10000
weighted avg 0.78 0.85 0.81 10000准确度:0.8472
精确度:0.77281523
召回率:0.83683025
f1:0.80074910
"""
混淆矩阵及可视化
import os
from sklearn.metrics import *
import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
# 路径兼容问题
current_path = os.path.dirname(__file__)
path_xls = os.path.realpath(os.path.join(current_path,'metrics', 'validation_metrics.xlsx'))# 解决显示中文乱码
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
def report():# 读取数据excel_data = pd.read_excel(os.path.join(path_xls))label = excel_data['label'].valuespred = excel_data['pred'].values# 整体报表labels=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]matrix = confusion_matrix(label, pred,labels=labels) print(matrix)# 下面的代码就是简单的plt绘制过程# 绘制混淆举证plt.matshow(matrix, cmap=plt.cm.Oranges)# 显示颜色条plt.colorbar()# 显示具体的数字过程for i in range(len(matrix)):for j in range(len(matrix)):plt.annotate(matrix[i,j],xy=(j, i),horizontalalignment="center",verticalalignment="center",)# 美化图表# plt.grid(True)plt.xlabel("True labels")plt.ylabel("pred labels")plt.xticks(range(len(labels)), labels, rotation=45)plt.yticks(range(len(labels)), labels)plt.title("训练结果混淆矩阵视图")plt.show()if __name__ == '__main__':report()
网络性能提升
使用更复杂的模型
参考官方文档:
Models and pre-trained weights — Torchvision 0.17 documentationhttps://pytorch.org/vision/0.17/models.html#classification
导入模型
# 导入模型:我这里依然使用了以前的模型名称,只是为了不改代码
from torchvision.models import resnet18 as ImageClassifier
使用模型
#num_classes参数很重要,是你要的分类数量,默认是1000
model = ImageClassifier(num_classes=10)
继续训练
-
训练完成后,在wandb中观察到,整体的准确率稳定上升,但是效果不是很好,此时应该在原训练好的权重参数基础下继续训练
-
不能每次都从0开始训练
- 代码实现
from torchvision.models import resnet18
from torchvision.datasets import CIFAR10
import os
import torchvision.transforms as transforms
import torch.nn as nn
import torch.optim as optim
import time
import torch
from torch.utils.data import DataLoadercurrent_path = os.path.dirname(__file__)
data_path = os.path.realpath(os.path.join(current_path,'datasets'))
# 设备更换
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
def train():# 加载训练数据集train_data = CIFAR10(root=data_path, # 数据集路径train=True, # 是否是训练集download=True, # 是否下载transform=transforms.Compose([transforms.ToTensor(),# 张量]),)# 网络 10分类net = resnet18(weights=None, num_classes=10)# 加载模型参数,它的是cuda中保存出来的数据, map_location=devic转到cpu# 导入的他人的权重参数模型state_dict = torch.load("./weights/resnet18.pth", map_location=device)# 更新模型参数net.load_state_dict(state_dict)net.to(device)# 三要素之模型训练epochs = 1batch_size = 64# 损失函数,自带Softmax分类功能激活函数,但如果net输出不用softmax,只是分类但未有概率计算cred = nn.CrossEntropyLoss(reduction='sum')# 优化器optimizer = optim.Adam(net.parameters(), lr=0.001)for epoch in range(epochs):start_time = time.time() #开始时间accuracy = 0tatol_loss = 0# count = 0train_dataloader = DataLoader(train_data, batch_size=batch_size, shuffle=True)for i, (x,y) in enumerate(train_dataloader):x,y = x.to(device),y.to(device)# 使用网络进行预测yhat = net(x)# 预测正确率accuracy += torch.sum(torch.argmax(yhat,dim=1) == y)# 计算损失loss = cred(yhat,y)# 总损损失率tatol_loss += loss.item()# 梯度清零optimizer.zero_grad()# 方向传播loss.backward()# 更新梯度optimizer.step()print(f"训练轮次{epoch}/{epochs},耗时:{time.time()-start_time}, accuracy:{accuracy/len(train_data)}, mean_loss:{tatol_loss/len(train_dataloader)}")torch.save(net.state_dict(),'./weight/resnet18_1.pth')if __name__ == '__main__':train()
预训练和迁移学习
1.在原始的已经学习了基本特征的权重参数基础之上,继续进行训练,而不是每次都从0开始。
2.原始权重参数:
- 官方经典网络模型的预训练参数:别人已经训练好了;
- 也可以是自己训练好的权重文件;
迁移学习步骤:
导入
from torchvision.models import resnet18, ResNet18_Weights
初始化
# 【预训练】我们要拿到预训练的权重参数,保存模型,进行迁移学习。# 拿到预训练的权重参数# 网络 10分类pretraind_model = resnet18(weights=ResNet18_Weights.IMAGENET1K_V1)
保存初始权重文件
# 保存预训练权重文件# weight_path = os.path.join(current_path,'weights', 'resnet18_imagenet.pth')torch.save(pretraind_model.state_dict(),"./weights/resnet18_imagenet.pth")
修改网络结构
- 重新加载resnet18模型并修改网络结构。
- ResNet18默认有1000个类别,和我们的需求不匹配需要修改网络结构
# 【迁移学习】# 构建自己的网络模型net = resnet18(weights=None)# 修改conv1in_channels = net.conv1.in_channelsout_channels = net.conv1.out_channelsnet.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=1, padding=0, bias=False)# 池化的操作不适合小图片net.maxpool = nn.MaxPool2d(kernel_size=1, stride=1)# 分类器# 获取全连接层的输入特征数in_features = net.fc.in_features# 主要改的是out_features=10# 修改开始的全连接层的输出值net.fc = nn.Linear(in_features,out_features=10, bias=True)
调整权重参数
以满足调整网络结构后的新模型,主要在全连接层,冻结不需要更新的权重参数(根据具体需求)
# 训练自己的网络模型# 冻结不需要更新的权重参数for name, value in net.named_parameters():value.requires_grad = True if 'fc' in name else False# 保留需要更新的权重参数grade_true_state_dict = filter(lambda p:p.requires_grad, net.parameters())
加载权重参数
# 加载预训练权重参数state_dict = torch.load("./weights/resnet18_imagenet.pth")# net模型的全连接层被修改了,保存的权重模型的权重参数如果要也net相匹配,就需要先删除保存模型权重参数的相关的权重和参数# 删除全连接层的权重参数和偏置参数# 删除 fc.weight 和 fc.bias 后的影响# 当你执行 state_dict.pop('fc.weight') 和 state_dict.pop('fc.bias') 后:# 预训练模型中全连接层的权重和偏置参数被移除,这样可以确保在后续更新过程中不会覆盖你自定义的全连接层。# 在 my_state_dict.update(state_dict) 时,只有那些与 state_dict 中存在的键相匹配的层会被更新,而 fc 层的权重和偏置由于已经被移除,所以不会受到影响。# 因此,最终 net 模型中的全连接层权重参数仍然是随机初始化的,而不是预训练模型中的值。state_dict.pop('fc.weight')state_dict.pop('fc.bias')# 更新权重参数# 获取net网络的相关信息, net 模型当前的状态字典(即所有可学习参数的名称和对应的张量)# 由于你在创建 net 模型时设置了 weights=None,因此这些权重是随机初始化的。my_state_dict = net.state_dict()# 更新net网络参数# 这里使用了 Python 字典的 update() 方法,用 state_dict 中的内容来更新 my_state_dict。需要注意的是,在这之前你已经通过 state_dict.pop('fc.weight') 和 state_dict.pop('fc.bias') 删除了全连接层的权重参数。这意味着:# state_dict 中不再包含原预训练模型中全连接层的权重和偏置。# my_state_dict.update(state_dict) 只会更新那些与 state_dict 中键名匹配且不是全连接层的部分。# 全连接层 (fc) 的权重和偏置不会被更新,保持为随机初始化状态。my_state_dict.update(state_dict)# 这段代码将更新后的状态字典 my_state_dict 加载回 net 模型中。# 此时,除了全连接层外的所有层都已经被预训练模型的权重所替换,而全连接层仍然保留着随机初始化的权重和偏置。net.load_state_dict(my_state_dict)
学习率的调整
StepLR
是 PyTorch 中的一种学习率调度器,用于以固定步长周期性地降低学习率。
#step_size:每经过多少个 epoch,学习率减少一次。
#gamma:学习率每次减少时的倍率因子。 0.01 ---> 30个ecposh之后,变成0.001---> 30个ecposh之后,变成0.0001
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.1)
# 一轮训练完更新学习率
scheduler.step()
代码实现
from torchvision.models import resnet18
from torchvision.datasets import CIFAR10
import os
import torchvision.transforms as transforms
import torch.nn as nn
import torch.optim as optim
import time
import torch
from torch.utils.data import DataLoader
from torchvision.models import ResNet18_Weightscurrent_path = os.path.dirname(__file__)
data_path = os.path.realpath(os.path.join(current_path,'datasets'))
# 设备更换
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
def pred_train():# 【预训练】我们要拿到预训练的权重参数,保存模型,进行迁移学习。# 拿到预训练的权重参数# 网络 10分类pretraind_model = resnet18(weights=ResNet18_Weights.IMAGENET1K_V1)# 保存预训练权重文件# weight_path = os.path.join(current_path,'weights', 'resnet18_imagenet.pth')torch.save(pretraind_model.state_dict(),"./weights/resnet18_imagenet.pth")# 【迁移学习】# 构建自己的网络模型net = resnet18(weights=None)# 修改conv1in_channels = net.conv1.in_channelsout_channels = net.conv1.out_channelsnet.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=1, padding=0, bias=False)# 池化的操作不适合小图片net.maxpool = nn.MaxPool2d(kernel_size=1, stride=1)# 分类器# 获取全连接层的输入特征数in_features = net.fc.in_features# 主要改的是out_features=10# 修改开始的全连接层的输出值net.fc = nn.Linear(in_features,out_features=10, bias=True)# 加载预训练权重参数state_dict = torch.load("./weights/resnet18_imagenet.pth")state_dict.pop('fc.weight')state_dict.pop('fc.bias')del state_dict['conv1.weight']# 更新权重参数my_state_dict = net.state_dict()# 更新net网络参数my_state_dict.update(state_dict)net.load_state_dict(my_state_dict)# 训练自己的网络模型# 冻结不需要更新的权重参数for name, value in net.named_parameters():value.requires_grad = True if ('fc' in name or 'conv1.weight' == name) else False# for name, value in net.named_parameters():# print(name, value.requires_grad)# 保留需要更新的权重参数# grade_true_state_dict = net.named_parameters()grade_true_state_dict = filter(lambda p:p.requires_grad, net.parameters())# 【数据集】train_dataset = CIFAR10(root=data_path, train=True, transform=transforms.Compose([transforms.ToTensor()]),download=True)# 【模型训练】# 移到所需设备上进行训练net.to(device)# 三要素之模型训练epochs = 1batch_size = 128# 损失函数,自带Softmax分类功能激活函数,但如果net输出不用softmax,只是分类但未有概率计算cred = nn.CrossEntropyLoss(reduction='sum')# 优化器optimizer = optim.Adam(grade_true_state_dict, lr=0.001)# 学习率更新的优化器# scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.01)for epoch in range(epochs):start_time = time.time() #开始时间accuracy = 0tatol_loss = 0# count = 0train_dataloader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)for i, (x,y) in enumerate(train_dataloader):x,y = x.to(device),y.to(device)# 使用网络进行预测yhat = net(x)# 预测正确率accuracy += torch.sum(torch.argmax(yhat,dim=1) == y)# 计算损失loss = cred(yhat,y)print(loss)# 总损损失率tatol_loss += loss.item()# 梯度清零optimizer.zero_grad()# 方向传播loss.backward()# 更新梯度optimizer.step()# 学习率更新# count += 1# if count < 100:# print(accuracy, len(train_datasets))# else:# breaktorch.save(net.state_dict(),'./weights/first.pth')print(f"训练轮次{epoch}/{epochs},耗时:{time.time()-start_time}, accuracy:{accuracy/len(train_dataset)}, mean_loss:{tatol_loss/len(train_dataloader)}")# scheduler.step()# savepath = os.path.join(weight_path, 'mnist_net.pth')# print(savepath)torch.save(net.state_dict(),'./weights/last.pth')if __name__ == '__main__':pred_train()
整体流程梳理
引入使用的包
用到什么包,临时引入就可以,不用太担心。
import time
import os
import numpy as np
import pandas as pd
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
from torchvision.datasets import CIFAR10
from torchvision.models import resnet18, ResNet18_Weights
import wandb
from torch.utils.tensorboard import SummaryWriter
from sklearn.metrics import *
import matplotlib.pyplot as plt
数据
# 下面和以前就一样了
train_dataset = CIFAR10(root=datapath,train=True,download=True,transform=transform,
)
# 构建训练数据集
train_loader = DataLoader(#dataset=train_dataset,batch_size=batzh_size,shuffle=True,num_workers=2,
)
模型
所使用的模型(自己写的,引用经典神经网络、修改的经典神经网络等)
训练
数据增强
为了防止过拟合,增加模型的泛化能力,我们会数据增
# 损失函数和优化器loss_fn = nn.CrossEntropyLoss()optimizer = optim.Adam(model.parameters(), lr=lr)for epoch in range(epochs):# 开始时间start = time.time()# 总的损失值total_loss = 0.0# 样本数量:最后一次样本数量不是128samp_num = 0# 总的预测正确的分类correct = 0
model.train()for i, (x, y) in enumerate(train_loader):x, y = x.to(device), y.to(device)# 累加样本数量samp_num += len(y)out = model(x)# 预测正确的样本数量correct += out.argmax(dim=1).eq(y).sum().item()loss = loss_fn(out, y)# 损失率累加total_loss += loss.item() * len(y)optimizer.zero_grad()loss.backward()optimizer.step()if i % 100 == 0:img_grid = torchvision.utils.make_grid(x)write1.add_image(f"r_m_{epoch}_{i}", img_grid, epoch * len(train_loader) + i)
print("批次:%d 损失率:%.4f 准确率:%.4f 耗时:%.4f"% (epoch, total_loss / samp_num, correct / samp_num, time.time() - start))# log metrics to wandbwandb.log({"acc": correct / samp_num, "loss": total_loss / samp_num})
保存模型
torch.save(model.state_dict(), weightpath)
训练过程可视化
参考官网: torch.utils.tensorboard — PyTorch 2.5 documentationhttps://pytorch.org/docs/stable/tensorboard.html
验证阶段
数据验证
- 对训练的模型参数进行验证
验证结果可视化
- 验证数据保存到Excel
import torch
from torchvision.datasets import MNIST
import os
import torchvision.transforms as transforms
from model import numberModel
from torch.utils.data import DataLoader# 生成验证数据用到的包
import pandas as pd
import numpy as np
# 关=关闭科学计数法
np.set_printoptions(suppress=True)# 路径处理
current_path = os.path.dirname(__file__)
data_path = os.path.realpath(os.path.join(current_path,'datasets'))
path_xls = os.path.realpath(os.path.join(current_path,'metrics', 'validation_metrics.xlsx'))print(data_path)
def modelVal():# 我应该有验证的数据vaildation_data = MNIST(data_path, train=False, download=True,transform=transforms.Compose([transforms.ToTensor(),transforms.Resize((32,32))]))device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")# 模型:训练好的模型model = numberModel()state_dict = torch.load('./runs/weights/mnist_net.pth')# 初始化到模型model.load_state_dict(state_dict)# 移到相应的数据上去model.to(device)accraccy = 0tatol_excel_data = np.empty((0,12))# 使用训练好的模型对验证数据进行推理,看一下推理效果怎么样for input,label in DataLoader(vaildation_data, batch_size=256):input = input.to(device)label = label.to(device)# 使用模型对数据进行推理y_pred = model(input)# 统计推理正确的记录数accraccy += torch.sum(torch.argmax(y_pred,dim=1) == label)# 装换成numpypred_excel_data = torch.argmax(y_pred,dim=1).cpu().detach().numpy()# 升维pred_excel_data = np.expand_dims(pred_excel_data, axis=1)# 真实值label_excel_data = label.cpu().unsqueeze(dim=1).detach().numpy().astype(int)excel_data = y_pred.cpu().detach().numpy()# 把两个numpy拼接位一个数据excel_data = np.concatenate((excel_data, pred_excel_data,label_excel_data), axis=1)# print(excel_data)# 拼接而不是追加tatol_excel_data = np.concatenate((tatol_excel_data,excel_data), axis=0)print(tatol_excel_data.shape)# 写入excel表格columns = vaildation_data.classes + ['pred', 'label']# columns = [*vaildation_data.classes,'pred', 'label']df = pd.DataFrame(tatol_excel_data, columns=columns)df.to_excel(path_xls, index=False)print("写入excel成功......")print(f"验证数据集的正确率:{accraccy/len(vaildation_data)}")if __name__ == '__main__':modelVal()
- 指标分析:可视化
import os
from sklearn.metrics import *
import pandas as pd
import numpy as np
# 路径兼容问题
current_path = os.path.dirname(__file__)
path_xls = os.path.realpath(os.path.join(current_path,'metrics', 'validation_metrics.xlsx'))def report():# 读取数据excel_data = pd.read_excel(os.path.join(path_xls))label = excel_data['label'].valuespred = excel_data['pred'].values# 整体报表class_report = classification_report(label, pred, target_names=['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'])print(class_report)# 准确度accracy = accuracy_score(label, pred)print("准确度:%.4f" % accracy)# 精确度precision = precision_score(label, pred, average='macro')print("精确度:%.8f" % precision)# 召回率recall = recall_score(label, pred, average='macro')print("召回率:%.8f" % recall)# f1-scoref1 = f1_score(label, pred, average='macro')print("f1:%.8f" % f1)if __name__ == '__main__':report()
- 混淆矩阵
import os
from sklearn.metrics import *
import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
# 路径兼容问题
current_path = os.path.dirname(__file__)
path_xls = os.path.realpath(os.path.join(current_path,'metrics', 'validation_metrics.xlsx'))# 解决显示中文乱码
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
def report():# 读取数据excel_data = pd.read_excel(os.path.join(path_xls))label = excel_data['label'].valuespred = excel_data['pred'].values# 整体报表labels=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]matrix = confusion_matrix(label, pred,labels=labels) print(matrix)# 下面的代码就是简单的plt绘制过程# 绘制混淆举证plt.matshow(matrix, cmap=plt.cm.Oranges)# 显示颜色条plt.colorbar()# 显示具体的数字过程for i in range(len(matrix)):for j in range(len(matrix)):plt.annotate(matrix[i,j],xy=(j, i),horizontalalignment="center",verticalalignment="center",)# 美化图表# plt.grid(True)plt.xlabel("True labels")plt.ylabel("pred labels")plt.xticks(range(len(labels)), labels, rotation=45)plt.yticks(range(len(labels)), labels)plt.title("训练结果混淆矩阵视图")plt.show()if __name__ == '__main__':report()
使用
def app():dir = os.path.dirname(__file__)imgpath = os.path.join("./write", "6.png")# 读取图像文件 '8.png'img = cv2.imread(imgpath)# 将图像转换为灰度图img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# 对灰度图进行二值化处理,采用OTSU自适应阈值方法,并反转颜色ret, img = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)plt.imshow(img)plt.show()# img = cv2.resize(img, (32, 32))img = torch.Tensor(img).unsqueeze(0)transform = transforms.Compose([transforms.Resize((32, 32)), # 调整输入图像大小为32x32transforms.ToTensor(),transforms.Normalize((0.1307,), (0.3081,)),])img = transform(img).unsqueeze(0)# 加载我们的模型net = LeNet5()net.load_state_dict(torch.load(modelpath))# 预测outputs = net(img)print(outputs)print(outputs.argmax(axis=1))
模型移植
认识ONNX
ONNX | Home
Open Neural Network Exchange(ONNX,开放神经网络交换)格式,是一个用于表示深度学习模型的标准,可使模型在不同框架之间进行转移。
ONNX的规范及代码主要由微软,亚马逊 ,Face book 和 IBM等公司共同开发,以开放源代码的方式托管在Github上。目前官方支持加载ONNX模型并进行推理的深度学习框架有: Caffe2, PyTorch, PaddlePaddle, TensorFlow等。
导出ONNX
安装依赖包
pip install onnx
pip install onnxruntime
导出ONNX模型
import torch
from torchvision.models import resnet18
import torch.nn as nndef export_onnx():# 【迁移学习】# 构建自己的网络模型net = resnet18(weights=None)# 下面是对网络的小改变# 修改conv1in_channels = net.conv1.in_channelsout_channels = net.conv1.out_channelsnet.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=False)# 池化的操作不适合小图片net.maxpool = nn.MaxPool2d(kernel_size=1, stride=1)# 分类器# 获取全连接层的输入特征数in_features = net.fc.in_features# 主要改的是out_features=10# 修改开始的全连接层的输出值net.fc = nn.Linear(in_features,out_features=10, bias=True)# 权重参数读取state_dict = torch.load("./weights/resnet18_default_weight.pth", weights_only=True, map_location='cpu')net.load_state_dict(state_dict)# 导出为onnx格式imgdata = torch.randn(1,3,32,32)torch.onnx.export(net, imgdata, # 热身数据"./weights/resnet18.onnx",verbose=True,input_names=["input"],output_names=["output"],opset_version=11)print("export onnx success")if __name__ == '__main__':export_onnx()
ONNX结构可视化
- 可以直接在线查看:Netron
- 也可以下载桌面版:https://github.com/lutzroeder/netron
ONNX推理
- ONNX在做推理时不再需要导入网络,且适用于Python、JAVA、PyQT等各种语言,不再依赖于PyTorch框架;
简单推理(使用GPU推理)
pip install onnxruntime-gpu
import cv2
import onnx
import onnxruntime as ort
import torch
import numpy as np
import torchvision.transforms as transformstransformer = transforms.Compose([transforms.ToTensor(),transforms.Resize((32,32)),transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
def imgread(img_path):# try抛出异常try:imgdata = cv2.imread(img_path)except FileNotFoundError:print(f"Failed to load image from {img_path}")# 转换成RGBimgdata = cv2.cvtColor(imgdata, cv2.COLOR_BGR2RGB)imgdata = transformer(imgdata)# 改变图片类型imgdata = imgdata.unsqueeze(0).numpy()return imgdatadef inference():# 加载onnx模型model = ort.InferenceSession("./weights/resnet18.onnx", providers=['CPUExecutionProvider'])imgdata = imgread("./images/dog1.webp")out = model.run(None, {"input": imgdata})classlabel = ['飞机', '汽车·', '鸟', '猫', '鹿', '狗', '青蛙', '马', '船', '船车']print(out[0][0])print(classlabel[list(out[0][0]).index(max(out[0][0]))])if __name__ == '__main__':inference()