系列文章目录
初识CNN01——认识CNN
初识CNN02——认识CNN2
文章目录
一、数据集获取
1.1 数据集分类
计算机视觉数据集主要分为两类,其差异体现在标注形式与任务类型上:
1. 图像分类数据集 (Image Classification Datasets)
- 标注形式:
以目录层级结构隐式存储标签,每个子目录名即为类别标签(如 train/dog/xxx.jpg, train/cat/yyy.jpg)。 - 标注特点:
标注信息轻量化(仅需类别名称),无需额外标注文件,目录结构即标签映射。
典型代表:ImageNet、CIFAR-10、MNIST
2.密集标注数据集 (Dense Annotation Datasets)
- 标注形式:
需独立的标注文件(如JSON/XML)存储目标位置与语义信息,常见格式包括:- 目标检测:边界框坐标 + 类别(COCO格式的 .json)
- 图像分割:像素级掩码图(PNG)或多边形坐标(VOC格式的 .xml)
- 标注特点:
标注信息复杂,需专用工具制作。
典型代表:COCO(检测/分割)、PASCAL VOC(检测/分割)、Cityscapes(语义分割)
1.2 数据集来源
- 开源数据集
pytorch内置数据集,kaggle数据集下载网址,Hugging Face数据集,robotflow,cvmart,开源数据集分类汇总。 - 外包平台
外包平台(Amazon Mechanical Turk,阿里众包,百度数据众包,京东微工等) - 自己采集
使用labelimg、labelme等工具对数据进行标注,以生成带标签的训练数据集。 - 爬虫获取
1.3 图片本地化
使用公开数据集时,会自动保存到本地。如果已下载,就不会重复下载。但所下载的数据集并非图片形式,如果需要以图片的形式保存到本地以方便观察和重新处理,可以按照如下方式处理。
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("所有图片保存成功")
二、训练过程可视化
这里使用Tensor Board进行讲解。(附官方文档)
2.1 准备工作
安装:安装的是执行指令,是一个本地化的服务器
pip install tensorboard
导入tensorboard操作模块
from torch.utils.tensorboard import SummaryWriter
指定tensorboard日志保存路径:可以指定多个实例对象
dir = os.path.dirname(__file__)
tbpath = os.path.join(dir, "tensorboard")
# 指定tensorboard日志保存路径
writer = SummaryWriter(log_dir=tbpath)
2.2 训练过程曲线
记录训练数据
# 记录训练数据到可视化面板
writer.add_scalar("Loss/train", loss, epoch)
writer.add_scalar("Accuracy/train", acc, epoch)
训练完后记得关闭
writer.close()
在训练完成后,查看训练结果,在当前目录下,打开控制台窗口:
tensorboard --logdir=runs
控制台会提示一个访问地址,通常为http://localhost:6006/,用浏览器直接访问即可。
2.3 模型可视化
保存网络结构到tensorboard
# 保存模型结构到tensorboard
writer.add_graph(net, input_to_model=torch.randn(1, 1, 28, 28))
writer.close()
启动tensorboard,在graphs菜单即可看到模型结构
# 获取模型参数并循环记录
params = net.named_parameters()
for name, param in params:
writer.add_histogram(f"{name}_{i}", param.clone().cpu().data.numpy(), epoch)
2.4 记录训练数据
tensorboard
中的add_image
函数用于将图像数据记录到TensorBoard,以便可视化和分析。这对于查看训练过程中生成的图像、调试和理解模型的行为非常有用,如帮助检查预处理是否生效。
#查看预处理的旋转是否生效
for i, data in enumerate(trainloader, 0):
inputs, labels = data
if i % 100 == 0:
img_grid = torchvision.utils.make_grid(inputs)
writer.add_image(f"r_m_{epoch}_{i * 100}", img_grid, epoch * len(trainloader) + i)

2.5 补充
尽可能使用新版本的pyTorch
pip install tensorboard
在创建实例对象时使用默认值
# 导入训练过程可视化工具tensorboard from torch.utils.tensorboard import SummaryWriter # 实例对象默认将结果写入 ./runs/ writer = SummaryWriter()
启动tensorboard:
tensorboard --logdir=runs
不要在IDE(如vsCode)里面安装tensorboard插件
三、验证结果数据化
3.1 数据结果Excel
训练模型后,可以将模型预测结果导出为结构化Excel文件,便于定量分析。首先需要导入数据分析模块pandas。
import pandas as pd
把推理结果softmax化,并放到全局的列表里面。
#定义全局的 pd_data :10分类,加了分类id 和 分类名称, 12个
pd_data = np.empty((0, 12))
#在训练的过程中把训练结果整理进来
out_softmax = torch.softmax(out, dim=1).cpu().detach().numpy()
target_y = y.cpu().detach().numpy()
#根据目标值找到分类名
target_name = np.array([vaild_dataset.classes[i] for i in target_y])
# 把真实所属分类追加到数据中
out_softmax = np.concatenate(
(out_softmax, target_y.reshape(-1, 1), target_name.reshape(-1, 1)), axis=1
)
#数据追加
pd_data = np.concatenate((pd_data, out_softmax), axis=0)
保存数据到CSV。
# 数据有了,找到列名:和前面追加的目标值及类名要呼应上
columnsn = np.concatenate(
(vaild_dataset.classes, np.array(["target", "真实值"])), axis=0
)
pd_data_df = pd.DataFrame(pd_data, columns=columnsn)
# 把数据保存到Excel:CSV
csvpath = os.path.join(dir, "vaild")
if not os.path.exists(csvpath):
os.makedirs(csvpath)
pd_data_df.to_csv(os.path.join(csvpath, "vaild.csv"))
3.2 模型指标矩阵化
3.2.1 混淆矩阵
混淆矩阵是一种特定的表格布局,用于可视化监督学习算法的性能,特别是分类算法。在这个矩阵中,每一行代表实际类别,每一列代表预测类别。矩阵的每个单元格则包含了在该实际类别和预测类别下的样本数量。通过混淆矩阵,我们不仅可以计算出诸如准确度、精确度和召回率等评估指标,还可以更全面地了解模型在不同类别上的性能。
混淆矩阵的四个基本组成部分是:
- True Positives(TP):当模型预测为正类,并且该预测是正确的,我们称之为真正(True Positive);
- True Negatives(TN):当模型预测为负类,并且该预测是正确的,我们称之为真负(True Negative);
- False Positives(FP):当模型预测为正类,但该预测是错误的,我们称之为假正(False Positive);
- False Negatives(FN):当模型预测为负类,但该预测是错误的,我们称之为假负(False Negative)
3.2.2 常见指标
实际上,宁肯错杀一千不肯放过一个,就是提高召回率,而降低精确度的做法
3.2.3 模型指标计算及可视化
csvpath = os.path.join(dir, "vaild")
# 读取CSV数据
csvdata = pd.read_csv(
os.path.join(csvpath, "vaild.csv"), encoding="GBK", index_col=0
)
# 拿到真实标签
true_label = csvdata["target"].values
print(true_label, type(true_label), len(true_label))
# 获取预测标签
predict_label = csvdata.iloc[:, :-2].values
print(predict_label, predict_label.shape)
# 预测分类及分数的提取
predict_label_ind = np.argmax(predict_label, axis=1)
predict_label_score = np.max(predict_label, axis=1)
print(predict_label_ind, predict_label_score)
# 根据预测值和真实值生成分类报告
report = classification_report(y_true=true_label, y_pred=predict_label_ind)
print(report)
四、预训练和迁移学习
预训练就是在原始的已经学习了基本特征的权重参数基础之上,继续进行训练,而不是每次都从0开始。
原始权重参数来源:
- 官方经典网络模型的预训练参数:别人已经训练好了;
- 也可以是自己训练好的权重文件;
4.1 迁移学习步骤
- 导入
from torchvision.models import resnet18, ResNet18_Weights
- 初始化
weight = ResNet18_Weights.DEFAULT
model = resnet18(weights=weight)
model.to(device)
- 保存初始权重文件
# 保存模型权重文件到本地
if not os.path.exists(os.path.join(mdelpath, f"model_res18.pth")):
torch.save(model.state_dict(), os.path.join(mdelpath, f"model_res18.pth"))
- 修改网络结构
ResNet18默认有1000个类别,和我们的需求不匹配需要修改网络结构。所以我们需要重新加载resnet18模型并修改网络结构。
# 重新加载网络模型:需要根据分类任务进行模型结构调整
pretrained_model = resnet18(weights=None)
# print(pretrained_model)
in_features_num = pretrained_model.fc.in_features
pretrained_model.fc = nn.Linear(in_features_num, 10)
- 调整权重参数
以满足调整网络结构后的新模型,主要在全连接层。
# 加载刚才下载的权重参数
weight18 = torch.load(os.path.join(mdelpath, f"model_res18.pth"))
print(weight18.keys())
# 全连接层被我们修改了,需要删除历史的全连接层参数
weight18.pop("fc.weight")
weight18.pop("fc.bias")
# 获取自己的模型的参数信息
my_resnet18_dict = pretrained_model.state_dict()
# 去除不必要的权重参数
weight18 = {k:v for k, v in weight18.items() if k in my_resnet18_dict}
#更新
my_resnet18_dict.update(weight18)
- 新参数+新模型
# 处理完后把最新的参数更新到模型中
pretrained_model.load_state_dict(my_resnet18_dict)
model = pretrained_model.to(device)
4.2 冻结层
对于不在需要训练的网络层,可以把梯度更新关闭:根据具体需求来,会影响训练效果。
# 冻结层:自己打印和调试,是完全可以的
for name, value in pretrained_model.named_parameters():
if name != "fc.weight" and name != "fc.bias":
value.requires_grad = False
# 开始筛选需要进行梯度更新的参数,而不是全部
params_grade_true = filter(lambda x: x.requires_grad, pretrained_model.parameters() )
# 创建优化器
# optimizer = optim.Adam(model.parameters(), lr=learning_rate)
optimizer = optim.Adam(params_grade_true, lr=learning_rate)
五、模型移植
Open Neural Network Exchange(ONNX,开放神经网络交换)格式,是一个用于表示深度学习模型的标准,可使模型在不同框架之间进行转移。ONNX的规范及代码主要由微软,亚马逊 ,Face book 和 IBM等公司共同开发,以开放源代码的方式托管在Github上。目前官方支持加载ONNX模型并进行推理的深度学习框架有: Caffe2, PyTorch, PaddlePaddle, TensorFlow等。
ONNX官网
5.1 导出ONNX
安装依赖包
pip install onnx
pip install onnxruntime
导出ONNX模型
import os
import torch
import torch.nn as nn
from torchvision.models import resnet18
if __name__ == "__main__":
dir = os.path.dirname(__file__)
weightpath = os.path.join(
os.path.dirname(__file__), "pth", "resnet18_default_weight.pth"
)
onnxpath = os.path.join(
os.path.dirname(__file__), "pth", "resnet18_default_weight.onnx"
)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = resnet18(pretrained=False)
model.conv1 = nn.Conv2d(
#
in_channels=3,
out_channels=64,
kernel_size=3,
stride=1,
padding=0,
bias=False,
)
# 删除池化层
model.maxpool = nn.MaxPool2d(kernel_size=1, stride=1, padding=0)
# 修改全连接层
in_feature = model.fc.in_features
model.fc = nn.Linear(in_feature, 10)
model.load_state_dict(torch.load(weightpath, map_location=device))
model.to(device)
# 创建一个实例输入
x = torch.randn(1, 3, 224, 224, device=device)
# 导出onnx
torch.onnx.export(
model,
x,
onnxpath,
#
verbose=True, # 输出转换过程
input_names=["input"],
output_names=["output"],
)
print("onnx导出成功")
5.2 ONNX可视化
在线查看,桌面版
ONNX在做推理时不再需要导入网络,且适用于Python、JAVA、PyQT等各种语言,不再依赖于PyTorch框架;
5.3 ONNX推理
ONNX在做推理时不再需要导入网络,且适用于Python、JAVA、PyQT等各种语言,不再依赖于PyTorch框架;
import onnxruntime
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
import requests
from io import BytesIO
import torchvision.transforms as transforms
# 1. 下载测试图像
def download_image(url):
response = requests.get(url)
img = Image.open(BytesIO(response.content)).convert('RGB')
return img
# 2. 图像预处理
def preprocess_image(img, input_size=(224, 224)):
# 创建预处理管道
preprocess = transforms.Compose([
transforms.Resize(input_size),
transforms.ToTensor(),
transforms.Normalize(
mean=[0.485, 0.456, 0.406], # ImageNet标准化参数
std=[0.229, 0.224, 0.225]
)
])
# 应用预处理并添加批次维度
input_tensor = preprocess(img)
input_batch = input_tensor.unsqueeze(0).numpy() # 转换为NumPy数组
return input_batch
# 3. 加载ONNX模型并进行推理
def onnx_inference(model_path, input_data):
# 创建推理会话
session = onnxruntime.InferenceSession(model_path)
# 获取输入名称
input_name = session.get_inputs()[0].name
# 执行推理
outputs = session.run(None, {input_name: input_data})
return outputs
# 4. 后处理:解析分类结果
def postprocess(output, topk=5):
# 获取ImageNet类别标签
classes_url = "https://raw.githubusercontent.com/anishathalye/imagenet-simple-labels/master/imagenet-simple-labels.json"
classes = requests.get(classes_url).json()
# 获取top-k预测结果
probs = output[0].squeeze() # 移除批次维度
topk_indices = np.argsort(probs)[-topk:][::-1]
# 返回结果列表
results = []
for idx in topk_indices:
results.append({
"class_id": idx,
"class_name": classes[idx],
"probability": float(probs[idx])
})
return results
# 5. 可视化结果
def visualize_results(img, results):
plt.figure(figsize=(10, 6))
plt.imshow(img)
plt.axis('off')
# 显示预测结果
text = "\n".join([f"{res['class_name']}: {res['probability']:.2%}"
for res in results])
plt.figtext(0.5, 0.01, text, wrap=True,
horizontalalignment='center', fontsize=12)
plt.show()
# 主函数
def main():
# 配置参数
MODEL_PATH = "resnet50.onnx" # 替换为你的ONNX模型路径
IMAGE_URL = "https://storage.googleapis.com/mledu-datasets/cats_and_dogs_filtered/validation/dogs/dog.2000.jpg"
try:
# 步骤1: 获取图像
print("下载图像...")
img = download_image(IMAGE_URL)
# 步骤2: 预处理
print("预处理图像...")
input_data = preprocess_image(img)
# 步骤3: ONNX推理
print("执行ONNX推理...")
outputs = onnx_inference(MODEL_PATH, input_data)
# 步骤4: 解析结果
print("解析预测结果...")
results = postprocess(outputs)
# 步骤5: 可视化
print("可视化结果...")
visualize_results(img, results)
# 打印详细结果
print("\nTop-5 预测结果:")
for res in results:
print(f"{res['class_name']}: {res['probability']:.2%}")
except Exception as e:
print(f"发生错误: {str(e)}")
if __name__ == "__main__":
main()
总结
本文简要介绍了数据集的获取,训练过程与训练结果的可视化,以及预训练与迁移学习,还有模型移植等方面的内容。