GoogLeNet网络模型

诞生背景

在2014年的ImageNet图像识别挑战赛中,一个GoogLeNet的网络架构大放异彩,与VGG不同的是,VGG用的是3*3的卷积,而GoogLeNet从1*1到7*7的卷积核都用,也就是使用不同大小的卷积核组合。

网络模型架构

在GoogLeNet中,基本的卷积块被称为Inception块。

Inception模块

输入
1x1卷积
1x1卷积
3x3卷积
1x1卷积
5x5卷积
3x3最大池化
1x1卷积
通道合并

Inception块参数详解

输入为224×224×3三通道的图像。

路径1:

​ (1)输入为224×224×3,卷积核数量为64个;卷 积核的尺寸大小为1×1×3;步幅为1(stride=1), 填充为0(padding=0);卷积后得到shape为 224×224×64的特征图输出。

路径2:

​ (1)输入为224×224×3,卷积核数量为96个;卷 积核的尺寸大小为1×1×3;步幅为1(stride = 1), 填充为0(padding=0);卷积后得到shape为 224×224×64的特征图输出。

​ (2)输入为224×224×64,卷积核数量为128个; 卷积核的尺寸大小为3×3×64;步幅为1(stride = 1),填充为1(padding=1);卷积后得到shape 为224×224×128的特征图输出。

路径3:
(1)输入为224×224×3,卷积核数量为16个;卷积核 的尺寸大小为1×1×3;步幅为1(stride = 1),填充为 0(padding=0);卷积后得到shape为224×224×16的 特征图输出。

​ (2)输入为224×224×16,卷积核数量为32个;卷积 核的尺寸大小为5×5×16;步幅为1(stride = 1),填 充 为 2 ( padding=2 ) ; 卷 积 后 得 到 shape 为 224×224×32的特征图输出。

路径4:

​ (1)输入为224×224×3,池化核的尺寸大小为3×3; 步幅为1(stride = 1),填充为1(padding=1);池 化后得到shape为224×224×3的特征图输出。

​ (2)输入为224×224×3,卷积核数量为32个;卷积核 的尺寸大小为1×1×3;步幅为1(stride = 1),填充为 0(padding=0);卷积后得到shape为224×224×32的 特征图输出。

通道合并:

路径1的到输出为:224×224×64

路径2的到输出为:224×224×128

路径3的到输出为:224×224×32

路径4的到输出为:224×224×32

最终通道合并为64+128+32+32=256,最终的输出为: 224×224×256

整个网络模型架构

阶段层号操作参数 / 配置输出尺寸(H×W×C)对应论文描述
输入层-图像输入224×224 RGB,均值预处理224×224×3摘要 / 引言部分
初始卷积Conv17×7 卷积,步长 2,填充 364 个滤波器112×112×64引言中 “7×7 卷积层”
初始池化Pool13×3 最大池化,步长 2,填充 1-56×56×64引言中 “最大池化层”
降维卷积Conv2并行分支: ① 1×1 卷积 ② 1×1 卷积→3×3 卷积分支①:64 通道 分支②:64→192 通道56×56×256引言中 “3×3 卷积(含 1×1 降维)”
Inception 组 1Inception 2a4 分支并行: ① 1×1 卷积 ② 1×1→3×3 卷积 ③ 1×1→5×5 卷积 ④ 池化→1×1①64 ②96→128 ③16→32 ④32 通道56×56×256论文第 4 节 “第一组 Inception 模块”
Inception 2b4 分支并行(通道数调整)①128 ②128→192 ③32→96 ④64 通道56×56×480同上
降采样Pool23×3 最大池化,步长 2,填充 1-28×28×480组间池化,降低尺寸
Inception 组 2Inception 3a4 分支并行(通道数调整)①192 ②96→208 ③16→48 ④64 通道28×28×512论文第 4 节 “第二组 Inception 模块”
Inception 3b4 分支并行(通道数调整)①160 ②112→224 ③24→64 ④64 通道28×28×512同上
降采样Pool33×3 最大池化,步长 2,填充 1-14×14×512组间池化,降低尺寸
Inception 组 3Inception 4a4 分支并行(通道数调整) 后接辅助分类器 1①128 ②128→256 ③24→64 ④64 通道 辅助分类器:平均池化 + 1×1 卷积 + FC14×14×512论文第 4 节 “第三组 Inception 模块”
Inception 4b4 分支并行(通道数调整) 后接辅助分类器 2①112 ②144→288 ③32→64 ④64 通道 辅助分类器:同上14×14×528同上,辅助分类器缓解梯度消失
Inception 4c4 分支并行(通道数调整)①256 ②256→320 ③32→128 ④128 通道14×14×832同上
降采样Pool43×3 最大池化,步长 2,填充 1-7×7×832组间池化,降低尺寸
Inception 组 4Inception 5a4 分支并行(通道数调整)①256 ②320→384 ③32→128 ④128 通道7×7×896论文第 4 节 “第四组 Inception 模块”
Inception 5b4 分支并行(通道数调整)①384 ②384→448 ③48→128 ④128 通道7×7×1088同上
全局池化GlobalPool全局平均池化-1×1×1024引言中 “全局平均池化替代全连接层”
分类器Dropout随机丢弃 70% 神经元-1×1×1024论文第 5 节 “Dropout 层”
FC全连接层 + Softmax1024→1000 通道(ImageNet 类别数)1×1×1000论文第 5 节 “线性层 + Softmax”

环境准备

首先在本地中的某个盘符新建一个文件夹,就叫GoogLeNet吧,作为项目的根目录。

然后新建一个python文件,就叫plot.py吧,往里面写入以下代码,用于下载数据集:

# FashionMNIST里面包含了许多数据集
from click.core import batch
from spacy.cli.train import train
from torchvision.datasets import FashionMNIST
from torchvision import transforms # 处理数据集,归一化
import torch.utils.data as Data
import numpy as np
import matplotlib.pyplot as plt# 下载FashionMMIST数据集
train_data = FashionMNIST(root="./data", # 指定数据集要下载的路径train=True,# 要训练集# 将数据进行归一化操作transform=transforms.Compose([transforms.Resize(size=224), # 调整数据的大小transforms.ToTensor() # 将数据转换为tensor]),download=True # 开启下载
)# 加载数据集集
train_loader = Data.DataLoader(dataset=train_data,# 要加载的数据集batch_size=64 ,# 批量数据大小shuffle=True, # 打乱数据顺序num_workers=0, # 加载数据线程数量
)# 绘制出训练集
for step,(b_x,b_y) in enumerate(train_loader):if step > 0:breakbatch_x = b_x.squeeze().numpy() # 将四维张量移除第一维,将数据转换为numpy格式batch_y = b_y.numpy() # 将张量数据转成numpy格式class_label = train_data.classes # 训练集标签
print("class_label,",class_label)# 绘图
plt.figure(figsize=(12,5))
for ii in np.arange(len(batch_y)):plt.subplot(4,16,ii+1)plt.imshow(batch_x[ii, : , :],cmap=plt.cm.gray)plt.title(class_label[batch_y[ii]],size=10)plt.axis('off')plt.subplots_adjust(wspace=0.05)plt.show()

执行上述代码后,就会开始下载所需要的数据集文件,只不过下载的速度比较慢,然后下载完成,项目的根目录会多出data文件夹,以下是data的目录结构:

--data--FashionMNIST--raw # 该文件夹下就存放数据集文件

模型搭建

创建model.py文件,用于构建模型代码。

import torch
from torch import nn
from torchsummary import summary# 定义一个通用的Inception模块
class Inception(nn.Module):"""in_channels:输入通道数c1:路线1的卷积c2:元组,路线2中有两层卷积c3:元组,路线3中有两层卷积c4:路线4的卷积"""def __init__(self,in_channels,c1,c2,c3,c4):super(Inception,self).__init__()self.ReLU = nn.ReLU() # 定义激活函数 ReLU# 路线1 单1*1卷积层self.p1_1 = nn.Conv2d(in_channels=in_channels,out_channels=c1,kernel_size=1)# 路线2 单1*1卷积层,3*3的卷积self.p2_1 = nn.Conv2d(in_channels=in_channels,out_channels=c2[0],kernel_size=1)self.p2_2 = nn.Conv2d(in_channels=c2[0],out_channels=c2[1],kernel_size=3,padding=1)# 路线3 单1*1卷积层,5*5的卷积self.p3_1 = nn.Conv2d(in_channels=in_channels,out_channels=c3[0],kernel_size=1)self.p3_2 = nn.Conv2d(in_channels=c3[0],out_channels=c3[1],kernel_size=5,padding=2)# 路线4 3*3最大池化,单1*1卷积# stride的默认参数与kernel_size的值一致,而kernel_size的默认值是1self.p4_1 = nn.MaxPool2d(kernel_size=3,padding=1,stride=1)self.p4_2 = nn.Conv2d(in_channels=in_channels,out_channels=c4,kernel_size=1)def forward(self,x):# x为输入大小p1 = self.ReLU(self.p1_1(x)) # 路线1p2 = self.ReLU(self.p2_2(self.ReLU(self.p2_1(x)))) # 路线2p3 = self.ReLU(self.p3_2(self.ReLU(self.p3_1(x)))) # 路线3p4 = self.ReLU(self.p4_2(self.p4_1(x))) # 路线4# 最后进行通道融合 dim=1表示在通道的基础上进行融合return torch.cat((p1,p2,p3,p4),dim=1)# 定义GoogLeNet网络模型
class GoogLeNet(nn.Module):def __init__(self,Inception):super(GoogLeNet,self).__init__()# 定义第一块网络层,相当于封装了部分网络层self.b1 = nn.Sequential(nn.Conv2d(in_channels=3,out_channels=64,kernel_size=7,stride=2,padding=3),nn.ReLU(),nn.MaxPool2d(kernel_size=3,stride=2,padding=1))# 定义第二块网络层self.b2 = nn.Sequential(nn.Conv2d(in_channels=64,out_channels=64,kernel_size=1),nn.ReLU(),nn.Conv2d(in_channels=64,out_channels=192,kernel_size=3,padding=1),nn.ReLU(),nn.MaxPool2d(kernel_size=3,stride=2,padding=1))# 定义第三块网络层self.b3 = nn.Sequential(Inception(192,64,(96,128),(16,32),32),Inception(256,128,(128,192),(32,96),64),nn.MaxPool2d(kernel_size=3,stride=2,padding=1))# 定义第四块网络层self.b3 = nn.Sequential(Inception(480,192,(96,208),(16,48),64),Inception(512,160,(112,224),(24,64),64),Inception(512,128,(128,256),(24,64),64),Inception(512,112,(128,288),(32,64),64),Inception(528,256,(160,320),(32,128),128),nn.MaxPool2d(kernel_size=3,stride=2,padding=1))# 定义第五块网络层self.b5 = nn.Sequential(Inception(832,256,(160,320),(32,128),128),Inception(832,384,(192,384),(48,128),128),nn.AdaptiveAvgPool2d((1,1)), # 全局平均池化nn.Flatten(), # 平展层nn.Linear(1024,10) # 全连接层)# 参数初始化 避免模型不收敛for m in self.modules(): # 遍历模型的每一层if isinstance(m,nn.Conv2d): # 如果当前层是卷积层nn.init.kaiming_normal_(m.weight,mode="fan_out",nonlinearity='relu') # 使用凯明初始化方式if m.bias is not None: # 如果偏置b存在nn.init.constant_(m.bias,0) # 将b置为0elif isinstance(m,nn.Linear): # 如果当前层是全连接层nn.init.normal_(m.weight,0,0.01) # 使用正太分布初始化,将权重w置为0,标准差为0.01if m.bias is not None: # 如果参数b存在nn.init.constant_(m.bias,0) # 将b置为0# 定义前向传播def forward(self,x):x = self.b1(x)x = self.b2(x)x = self.b3(x)x = self.b4(x)x = self.b5(x)return x# 定义主函数,进行模型测试(仅测试)
if __name__ == "__main__":device = torch.device("cuda" if torch.cuda.is_available() else "cpu")model = GoogLeNet(Inception).to(device)print(summary(model,(1,224,224)))
  • 打个简单的比方,训练网络模型,就好比解方程,为了得到这个方程的极值点,训练的过程就好比是找准一个方向,不断的朝这个方向靠近,使得方程的值不断减小,最终达到极值点,而不收敛,就是,不论你怎么跑,方程的解都不减小。即达不到最后的极值点.在loss上就表现为稳定性的比较大。跟迭代不收敛或者系统不稳定差不多,上下波动不能趋近一个定值。

  • 收敛的意思是指某个值一直在往我们所期望的阈值靠,就拿深度学习中loss损失来做示例,如下一张图是loss在每轮训练时的一个曲线图,可以看到loss一直从一开始的1.8在往1.0降,1.0就是我们期望的阈值,而1.8是最开始loss最大损失值。

模型训练

创建一个model_train.py 文件

import copy
import timeimport torch
from torchvision.datasets import FashionMNIST
from torchvision import transforms
import torch.nn as nn
import torch.utils.data as Data
import numpy as np
import pandas as pd
import matplotlib.pyplot as pltfrom model import GoogLeNet# 处理训练集核数据集
def train_val_data_process():# 加载数据集train_data = FashionMNIST(root="./data",# 数据集所在路径train=True, # 要训练集# 将数据进行归一化操作transform=transforms.Compose([transforms.Resize(size=224), # 修改数据的大小transforms.ToTensor() # 将数据转成Tensor格式]),download=True # 开启加载)# 随机 划分训练集 和 验证集train_data,val_data = Data.random_split(train_data, # 要划分的数据集[round(0.8*len(train_data)), # 划分80%给训练集round(0.2*len(train_data)) # 划分20%给验证集])# 加载训练集数据train_dataloader = Data.DataLoader(dataset=train_data,# 要加载的训练集batch_size=32,# 每轮的训练批次数shuffle=True,# 打乱数据顺序num_workers=2,# 加载数据线程数量)# 加载验证集数据val_dataloader = Data.DataLoader(dataset=val_data,# 要加载的验证集batch_size=32,# 每轮的训练批数shuffle=True,# 打乱数据顺序num_workers=2,# 加载数据集的线程数量)return train_dataloader,val_dataloader# 模型训练
def train_model_process(model,train_dataloader,val_dataloader,num_epochs):# model:需要训练的模型,train_dataloader:训练集数据,val_dataloader:验证集数据,num_epochs:训练轮数# 指定设备device = torch.device("cuda" if torch.cuda.is_available() else "cpu")# 定义优化器optimizer = torch.optim.Adam(model.parameters(),lr=0.001)# 定义交叉熵损失函数criterion = nn.CrossEntropyLoss()# 将模型放入到设备中进行训练model.to(device)# 复制当前模型的参数best_model_wts = copy.deepcopy(model.state_dict())# 初始化参数,记录模型的的精确度和损失值best_acc = 0.0 # 最高精确度train_loss_all = [] # 训练集的总损失train_acc_all = [] # 训练集的总精度val_loss_all = [] # 验证集的总损失val_acc_all = [] # 验证集的总精度since = time.time() # 记录开始训练的时间# 开始训练模型 每轮参数for epoch in range(num_epochs):print("Epoch {}/{}".format(epoch,num_epochs-1))print("-"*10)# 初始化参数,记录本轮的模型的损失之和精度train_loss = 0.0 # 训练的损失train_corrects = 0  # 训练的准确度val_loss = 0.0 # 验证集的损失val_corrents = 0 # 验证集的准确度train_num = 0 # 本轮训练集的数量val_num = 0 # 本轮验证集的数量# 取出每轮中的数据集进行训练for step,(b_x,b_y) in enumerate(train_dataloader):b_x = b_x.to(device) # 将训练集数据放入到设备当中b_y = b_y.to(device) # 将标签数据放入到设备当中model.train() # 开启模型训练模式# 将每批次中的标签数据放入到模型中,进行前向传播output = model(b_x)# 查找每一行中最大值对应的行标,即预测值pre_lab = torch.argmax(output,dim=1)# 计算当前批次的损失值(模型的输出,标签)loss = criterion(output,b_y)# 每批次训练完后,将梯度初始化成0optimizer.zero_grad()# 反向传播计算loss.backward()# 更新参数optimizer.step()# 本批次损失值的累加train_loss += loss.item() * b_x.size(0)# 如果模型预测的结果正确,本批次的准确度+1train_corrects += torch.sum(pre_lab == b_y.data)# 本此次的训练数据累加train_num += b_y.size(0)# 取出每轮中的数据进行验证for step,(b_x,b_y) in enumerate(val_dataloader):# 将数据和标签分别放入到设备中b_x = b_x.to(device)b_y = b_y.to(device)model.eval() # 设置模型为评估模式# 前向传播,输入一个批次,输出该批次的对应的预测值output = model(b_x)# 查找每一行中最大值对应的行标,即预测值pre_lab = torch.argmax(output,dim=1)# 计算本此次的损失函数loss = criterion(output,b_y)# 本批次的损失函数累加val_loss += loss.item() * b_x.size(0)# 如果预测正确,那就本批次的精度度累加val_corrents += torch.sum(pre_lab==b_y.data)# 当前用于验证的样本数累加val_num += b_x.size(0)# 计算每轮次的损失值和准确率train_loss_all.append(train_loss / train_num) # 本轮训练集的loss值train_acc_all.append(train_corrects.double().item() / train_num) # 本轮训练集的准确率val_loss_all.append(val_loss / val_num) # 本轮验证集的loss值val_acc_all.append(val_corrents.double().item() / val_num) # 本轮验证集的准确率print("{} train loss:{:.4f} train acc: {:.4f}".format(epoch, train_loss_all[-1], train_acc_all[-1]))print("{} val loss:{:.4f} val acc: {:.4f}".format(epoch, val_loss_all[-1], val_acc_all[-1]))# 寻找最高准确度 和 模型的权重参数if val_acc_all[-1] > best_acc:best_acc = val_acc_all[-1] # 最高准确度best_model_wts = copy.deepcopy(model.state_dict()) # 最佳模型参数# 计算训练耗时time_use = time.time() - sinceprint("训练耗费的时间:{:.0f}m{:.0f}s".format(time_use//60,time_use%60))# 将最佳的模型参数保存torch.save(best_model_wts,"best_model.pth") # .pth是权重文件的后缀,如果相对路径不行,就改为绝对路径# 保存训练好的模型参数train_process = pd.DataFrame(data={"epoch":range(num_epochs),"train_loss_all":train_loss_all,"train_acc_all":train_acc_all,"val_loss_all":val_loss_all,"val_acc_all":val_acc_all})return train_process# 定义绘图的函数,绘制loss和准确度
def matplot_acc_loss(train_process):plt.figure(figsize=(12,4))# 绘制训练集和验证集的损失值图像plt.subplot(1,2,1) # 一行两列,第一列plt.plot(train_process['epoch'],train_process.train_loss_all,"ro-",label="train loss")plt.plot(train_process['epoch'],train_process.val_acc_all,"bs-",label="val loss")plt.legend() # 图例plt.xlabel("epoch") # x轴标签plt.ylabel("loss") # y轴标签# 绘制训练集和验证集的准确度图像plt.subplot(1,2,2) # 一行两列,第二列plt.plot(train_process['epoch'],train_process.val_loss_all,"ro-",label="train acc")plt.plot(train_process['epoch'],train_process.val_acc_all,"bs-",label="va acc")if __name__ == '__main__':# 实例化自定义模型类model = GoogLeNet()# 加载数据集train_dataloader,val_dataloader = train_val_data_process()# 训练模型train_process = train_model_process(model,train_dataloader,val_dataloader,20)# 绘制图像matplot_acc_loss(train_process)

模型训练完毕后,会生成最佳模型参数文件:best_model.pth

模型测试

创建一个model_test.py文件,用于模型的测试

import torch
import torch.utils.data as Data
from numpy.random import shuffle
from torchvision import transforms
from torchvision.datasets import FashionMNISTfrom model import GoogLeNet,Inception# 加载要训练的数据
def test_data_process():# 加载测试集数据test_data = FashionMNIST(root="./data",# 指定数据集要下载的路径train=False,# 不要训练集数据# 数据归一化操作transform=transforms.Compose([transforms.Resize(size=224), # 将数据转成224*224大小transforms.ToTensor(),# 将数据转成Tensor格式]),download=True # 加载数据)# 通过DataLoader加载器 来加载数据test_dataloader = Data.DataLoader(dataset=test_data,# 要加载的数据batch_size=1, # 每轮训练的批次数shuffle=True, # 打乱数据集num_workers=0, # 加载数据集的线程数量)return test_dataloader# 测试模型
def test_model_process(model,test_dataloader):# 指定设备device = torch.device("cuda" if torch.cuda.is_available() else "cpu")# 将模型放入设备中model.to(device)# 初始化模型训练的每轮参数test_correct = 0.0 # 准确度test_num = 0 # 测试样本数量# 只进行前向传播with torch.no_grad(): # 将梯度设置为0for test_data_x,test_data_y in enumerate(test_dataloader): # 遍历每轮次# 由于上面设置批次为1,所以这里就不需要循环批次了test_data_x = test_data_x.to(device) # 将测试数据放入到设备中test_data_y = test_data_y.to(device) # 将标签数据放入到设备中# 模型切换成评估模式model.eval()# 前向传播 将测试数据放入到模型中output = model(test_data_x)# 查找每一行中最大值的行标pre_lab = torch.argmax(output,dim=1)# 模型预测的结果 将pre_lab 与 标签数据 进行比较# 如果预测正确,则加1test_correct += torch.sum(pre_lab==test_data_y.data)# 测试样本数量累加test_num += test_data_y.size(0)# 计算最终测试的准确率 每轮的准确度 / 总样本数量test_acc = test_correct.double().item() / test_numprint("测试模型的准确率为:",test_acc)if __name__ == '__main__':# 加载模型model = GoogLeNet(Inception)# 模型具体的训练过程device = torch.device("cuda" if torch.cuda.is_available() else "cpu")# 加载训练好的模型最佳参数model.load_state_dict(torch.load('best_model.pth',map_location=device))# 加载测试的数据集test_dataloader = test_data_process()# 开始测试# test_model_process(model, test_dataloader) # 简略测试# 模型具体的训练过程# device = torch.device("cuda" if torch.cuda.is_available() else "cpu")# 将模型放入到设备当中model = model.to(device)# 数据的类别classes = ('T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat','Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot')# 梯度设置为0with torch.no_grad():# 遍历测试集中的 测试数据 和 标签for b_x,b_y in test_dataloader:# 将数据和标签移动到与模型相同的设备b_x = b_x.to(device)b_y = b_y.to(device)# 将模型设置为评估模式model.eval()# 将数据放入到模型中,得出预测结果output = model(b_x)# 获取最大值的行标pre_lab = torch.argmax(output,dim=1)# 取出张量中的下标result = pre_lab.item()label = b_y.item()print("预测结果为:",classes[result],"标签为:",classes[label])

猫狗分类

利用自己的训练集来进行模型训练,实现猫狗分类

环境搭建

将上面刚刚搭建好的GoogLeNet项目,复制一份,就叫GoogLeNet-1吧。

数据集准备和划分

本地已经准备好了data_cat_dog的自定义数据集文件夹,里面均为图片。执行以下脚本,可以实现训练集的数据划分。

data_cat_dog的自定义数据集获取地址

禁止私自搬运和商用,后果自负,仅限学习使用

新建一个data_partitioning.py文件,并写入以下代码:

import os
from shutil import copy
import randomdef mkfile(file):if not os.path.exists(file):os.makedirs(file)# 获取data文件夹下所有文件夹名(即需要分类的类名)
file_path = 'data_cat_dog'
flower_class = [cla for cla in os.listdir(file_path)]# 创建 训练集train 文件夹,并由类名在其目录下创建5个子目录
mkfile('data/train')
for cla in flower_class:mkfile('data/train/' + cla)# 创建 验证集val 文件夹,并由类名在其目录下创建子目录
mkfile('data/test')
for cla in flower_class:mkfile('data/test/' + cla)# 划分比例,训练集 : 测试集 = 9 : 1
split_rate = 0.1# 遍历所有类别的全部图像并按比例分成训练集和验证集
for cla in flower_class:cla_path = file_path + '/' + cla + '/'  # 某一类别的子目录images = os.listdir(cla_path)  # iamges 列表存储了该目录下所有图像的名称num = len(images)eval_index = random.sample(images, k=int(num * split_rate))  # 从images列表中随机抽取 k 个图像名称for index, image in enumerate(images):# eval_index 中保存验证集val的图像名称if image in eval_index:image_path = cla_path + imagenew_path = 'data/test/' + clacopy(image_path, new_path)  # 将选中的图像复制到新路径# 其余的图像保存在训练集train中else:image_path = cla_path + imagenew_path = 'data/train/' + clacopy(image_path, new_path)print("\r[{}] processing [{}/{}]".format(cla, index + 1, num), end="")  # processing barprint()print("processing done!")

执行以上代码,就可以划分猫和狗的训练集和测试集,然后本地项目中会多个以下文件:

--data--test--cat--dog--train--cat--dog

数据集加载

重写model_train.py文件中的部分代码

import copy
import timeimport torch
from torchvision.datasets import ImageFolder # 加载自己的数据集
from torchvision import transforms
import torch.nn as nn
import torch.utils.data as Data
import numpy as np
import pandas as pd
import matplotlib.pyplot as pltfrom model import GoogLeNet# 处理训练集核数据集
def train_val_data_process():# ======(修改的部分开始)=============================ROOT_TRAIN = r'data\train' # 数据集的路径# 定义归一化方法 计算均值和方差 其中的数值来自于下面的数据集归一化部分的mean和variance两个值normalize = transforms.Normalize([0.162,0.151,0.138],[0.058,0.052,0.048])# 定义数据集处理方法变量train_transform = transforms.Compose( # 对数据集进行操作[transforms.Resize((224,224)),# 修改数据集的大小为 224*224transforms.ToTensor(), # 将数据修改为tensor格式normalize # 数据归一化]  )# 加载数据集 ImageFolder()为第三方库导入的包train_data = ImageFolder(ROOT_TRAIN, # 读取数据集的路径transform = train_transform # 处理数据的方法)# ======(修改的部分结束)=============================# 随机 划分训练集 和 验证集train_data,val_data = Data.random_split(train_data, # 要划分的数据集[round(0.8*len(train_data)), # 划分80%给训练集round(0.2*len(train_data)) # 划分20%给验证集])# 加载训练集数据train_dataloader = Data.DataLoader(dataset=train_data,# 要加载的训练集batch_size=32,# 每轮的训练批次数shuffle=True,# 打乱数据顺序num_workers=2,# 加载数据线程数量)# 加载验证集数据val_dataloader = Data.DataLoader(dataset=val_data,# 要加载的验证集batch_size=32,# 每轮的训练批数shuffle=True,# 打乱数据顺序num_workers=2,# 加载数据集的线程数量)return train_dataloader,val_dataloader# 模型训练
def train_model_process(model,train_dataloader,val_dataloader,num_epochs):# model:需要训练的模型,train_dataloader:训练集数据,val_dataloader:验证集数据,num_epochs:训练轮数# 指定设备device = torch.device("cuda" if torch.cuda.is_available() else "cpu")# 定义优化器optimizer = torch.optim.Adam(model.parameters(),lr=0.001)# 定义交叉熵损失函数criterion = nn.CrossEntropyLoss()# 将模型放入到设备中进行训练model.to(device)# 复制当前模型的参数best_model_wts = copy.deepcopy(model.state_dict())# 初始化参数,记录模型的的精确度和损失值best_acc = 0.0 # 最高精确度train_loss_all = [] # 训练集的总损失train_acc_all = [] # 训练集的总精度val_loss_all = [] # 验证集的总损失val_acc_all = [] # 验证集的总精度since = time.time() # 记录开始训练的时间# 开始训练模型 每轮参数for epoch in range(num_epochs):print("Epoch {}/{}".format(epoch,num_epochs-1))print("-"*10)# 初始化参数,记录本轮的模型的损失之和精度train_loss = 0.0 # 训练的损失train_corrects = 0  # 训练的准确度val_loss = 0.0 # 验证集的损失val_corrents = 0 # 验证集的准确度train_num = 0 # 本轮训练集的数量val_num = 0 # 本轮验证集的数量# 取出每轮中的数据集进行训练for step,(b_x,b_y) in enumerate(train_dataloader):b_x = b_x.to(device) # 将训练集数据放入到设备当中b_y = b_y.to(device) # 将标签数据放入到设备当中model.train() # 开启模型训练模式# 将每批次中的标签数据放入到模型中,进行前向传播output = model(b_x)# 查找每一行中最大值对应的行标,即预测值pre_lab = torch.argmax(output,dim=1)# 计算当前批次的损失值(模型的输出,标签)loss = criterion(output,b_y)# 每批次训练完后,将梯度初始化成0optimizer.zero_grad()# 反向传播计算loss.backward()# 更新参数optimizer.step()# 本批次损失值的累加train_loss += loss.item() * b_x.size(0)# 如果模型预测的结果正确,本批次的准确度+1train_corrects += torch.sum(pre_lab == b_y.data)# 本此次的训练数据累加train_num += b_y.size(0)# 取出每轮中的数据进行验证for step,(b_x,b_y) in enumerate(val_dataloader):# 将数据和标签分别放入到设备中b_x = b_x.to(device)b_y = b_y.to(device)model.eval() # 设置模型为评估模式# 前向传播,输入一个批次,输出该批次的对应的预测值output = model(b_x)# 查找每一行中最大值对应的行标,即预测值pre_lab = torch.argmax(output,dim=1)# 计算本此次的损失函数loss = criterion(output,b_y)# 本批次的损失函数累加val_loss += loss.item() * b_x.size(0)# 如果预测正确,那就本批次的精度度累加val_corrents += torch.sum(pre_lab==b_y.data)# 当前用于验证的样本数累加val_num += b_x.size(0)# 计算每轮次的损失值和准确率train_loss_all.append(train_loss / train_num) # 本轮训练集的loss值train_acc_all.append(train_corrects.double().item() / train_num) # 本轮训练集的准确率val_loss_all.append(val_loss / val_num) # 本轮验证集的loss值val_acc_all.append(val_corrents.double().item() / val_num) # 本轮验证集的准确率print("{} train loss:{:.4f} train acc: {:.4f}".format(epoch, train_loss_all[-1], train_acc_all[-1]))print("{} val loss:{:.4f} val acc: {:.4f}".format(epoch, val_loss_all[-1], val_acc_all[-1]))# 寻找最高准确度 和 模型的权重参数if val_acc_all[-1] > best_acc:best_acc = val_acc_all[-1] # 最高准确度best_model_wts = copy.deepcopy(model.state_dict()) # 最佳模型参数# 计算训练耗时time_use = time.time() - sinceprint("训练耗费的时间:{:.0f}m{:.0f}s".format(time_use//60,time_use%60))# 将最佳的模型参数保存torch.save(best_model_wts,"best_model.pth") # .pth是权重文件的后缀,如果相对路径不行,就改为绝对路径# 保存训练好的模型参数train_process = pd.DataFrame(data={"epoch":range(num_epochs),"train_loss_all":train_loss_all,"train_acc_all":train_acc_all,"val_loss_all":val_loss_all,"val_acc_all":val_acc_all})return train_process# 定义绘图的函数,绘制loss和准确度
def matplot_acc_loss(train_process):plt.figure(figsize=(12,4))# 绘制训练集和验证集的损失值图像plt.subplot(1,2,1) # 一行两列,第一列plt.plot(train_process['epoch'],train_process.train_loss_all,"ro-",label="train loss")plt.plot(train_process['epoch'],train_process.val_acc_all,"bs-",label="val loss")plt.legend() # 图例plt.xlabel("epoch") # x轴标签plt.ylabel("loss") # y轴标签# 绘制训练集和验证集的准确度图像plt.subplot(1,2,2) # 一行两列,第二列plt.plot(train_process['epoch'],train_process.val_loss_all,"ro-",label="train acc")plt.plot(train_process['epoch'],train_process.val_acc_all,"bs-",label="va acc")if __name__ == '__main__':# 实例化自定义模型类model = GoogLeNet()# 加载数据集train_dataloader,val_dataloader = train_val_data_process()# 训练模型train_process = train_model_process(model,train_dataloader,val_dataloader,20)# 绘制图像matplot_acc_loss(train_process)

数据集归一化

在项目的根目录下创建一个py文件,就叫mean_std.py吧,用于计算均值和方差,使数据归一化,符合正太分布

# mean_std.py文件
from PIL import Image
import os
import numpy as np# 文件夹路径,包含所有图片文件
folder_path = 'data_cat_dog'# 初始化累积变量
total_pixels = 0
sum_normalized_pixel_values = np.zeros(3)  # 如果是RGB图像,需要三个通道的均值和方差# 遍历文件夹中的图片文件
for root, dirs, files in os.walk(folder_path):for filename in files:if filename.endswith(('.jpg', '.jpeg', '.png', '.bmp')):  # 可根据实际情况添加其他格式image_path = os.path.join(root, filename)image = Image.open(image_path)image_array = np.array(image)# 归一化像素值到0-1之间normalized_image_array = image_array / 255.0# print(image_path)# print(normalized_image_array.shape)# 累积归一化后的像素值和像素数量total_pixels += normalized_image_array.sizesum_normalized_pixel_values += np.sum(normalized_image_array, axis=(0, 1))# 计算均值和方差
mean = sum_normalized_pixel_values / total_pixelssum_squared_diff = np.zeros(3)
for root, dirs, files in os.walk(folder_path):for filename in files:if filename.endswith(('.jpg', '.jpeg', '.png', '.bmp')):image_path = os.path.join(root, filename)image = Image.open(image_path)image_array = np.array(image)# 归一化像素值到0-1之间normalized_image_array = image_array / 255.0# print(normalized_image_array.shape)# print(mean.shape)# print(image_path)try:diff = (normalized_image_array - mean) ** 2sum_squared_diff += np.sum(diff, axis=(0, 1))except:print(f"捕获到自定义异常")# diff = (normalized_image_array - mean) ** 2# sum_squared_diff += np.sum(diff, axis=(0, 1))variance = sum_squared_diff / total_pixelsprint("Mean:", mean)
print("Variance:", variance)

运行起来也是需要一定的时间。运行完毕后会得到mean和variance两个值。

模型测试

修改model_test.py文件中的部分代码

import torch
import torch.utils.data as Data
from numpy.random import shuffle
from torchvision import transforms
from torchvision.datasets import FashionMNIST
from torchvision.datasets import ImageFolderfrom model import GoogLeNet,Inception# 加载要训练的数据
def test_data_process():#======================修改的部分开始======================ROOT_TRAIN = r'data\train' # 数据集的路径# 定义归一化方法 计算均值和方差normalize = transforms.Normalize([0.162,0.151,0.138],[0.058,0.052,0.048])# 定义数据集处理方法变量test_transform = transforms.Compose( # 对数据集进行操作[transforms.Resize((224,224)),# 修改数据集的大小为 224*224transforms.ToTensor(), # 将数据修改为tensor格式normalize # 数据归一化]  )# 加载数据集 ImageFolder()为第三方库导入的包test_data = ImageFolder(ROOT_TRAIN, # 读取数据集的路径transform = test_transform # 处理数据的方法)#======================修改的部分结束======================# 通过DataLoader加载器 来加载数据test_dataloader = Data.DataLoader(dataset=test_data,# 要加载的数据batch_size=1, # 每轮训练的批次数shuffle=True, # 打乱数据集num_workers=0, # 加载数据集的线程数量)return test_dataloader# 测试模型
def test_model_process(model,test_dataloader):# 指定设备device = torch.device("cuda" if torch.cuda.is_available() else "cpu")# 将模型放入设备中model.to(device)# 初始化模型训练的每轮参数test_correct = 0.0 # 准确度test_num = 0 # 测试样本数量# 只进行前向传播with torch.no_grad(): # 将梯度设置为0for test_data_x,test_data_y in enumerate(test_dataloader): # 遍历每轮次# 由于上面设置批次为1,所以这里就不需要循环批次了test_data_x = test_data_x.to(device) # 将测试数据放入到设备中test_data_y = test_data_y.to(device) # 将标签数据放入到设备中# 模型切换成评估模式model.eval()# 前向传播 将测试数据放入到模型中output = model(test_data_x)# 查找每一行中最大值的行标pre_lab = torch.argmax(output,dim=1)# 模型预测的结果 将pre_lab 与 标签数据 进行比较# 如果预测正确,则加1test_correct += torch.sum(pre_lab==test_data_y.data)# 测试样本数量累加test_num += test_data_y.size(0)# 计算最终测试的准确率 每轮的准确度 / 总样本数量test_acc = test_correct.double().item() / test_numprint("测试模型的准确率为:",test_acc)if __name__ == '__main__':# 加载模型model = GoogLeNet(Inception)# 模型具体的训练过程device = torch.device("cuda" if torch.cuda.is_available() else "cpu")# 加载训练好的模型最佳参数model.load_state_dict(torch.load('best_model.pth',map_location=device))# 加载测试的数据集test_dataloader = test_data_process()# 开始测试# test_model_process(model, test_dataloader) # 简略测试# 模型具体的训练过程# device = torch.device("cuda" if torch.cuda.is_available() else "cpu")# 将模型放入到设备当中model = model.to(device)# 数据的类别classes = ['猫','狗']# 梯度设置为0with torch.no_grad():# 遍历测试集中的 测试数据 和 标签for b_x,b_y in test_dataloader:# 将数据和标签移动到与模型相同的设备b_x = b_x.to(device)b_y = b_y.to(device)# 将模型设置为评估模式model.eval()# 将数据放入到模型中,得出预测结果output = model(b_x)# 获取最大值的行标pre_lab = torch.argmax(output,dim=1)# 取出张量中的下标result = pre_lab.item()label = b_y.item()print("预测结果为:",classes[result],"标签为:",classes[label])

模型的推理

随便从网上下载猫或者狗的图像到项目的根目录下,这里就下载猫的图片吧:

图片名就设置为: 7dd98d1001e939010b1f0c1e537c22e836d19614.jpeg

然后增加和修改model_test.py文件中的代码:

import torch
import torch.utils.data as Data
from numpy.random import shuffle
from torchvision import transforms
from torchvision.datasets import FashionMNIST
from torchvision.datasets import ImageFolder
from PIL import Image # 导入第三方库 增加代码from model import GoogLeNet,Inception# 加载要训练的数据
def test_data_process():#======================修改的部分开始======================ROOT_TRAIN = r'data\train' # 数据集的路径# 定义归一化方法 计算均值和方差normalize = transforms.Normalize([0.162,0.151,0.138],[0.058,0.052,0.048])# 定义数据集处理方法变量test_transform = transforms.Compose( # 对数据集进行操作[transforms.Resize((224,224)),# 修改数据集的大小为 224*224transforms.ToTensor(), # 将数据修改为tensor格式normalize # 数据归一化]  )# 加载数据集 ImageFolder()为第三方库导入的包test_data = ImageFolder(ROOT_TRAIN, # 读取数据集的路径transform = test_transform # 处理数据的方法)#======================修改的部分结束======================# 通过DataLoader加载器 来加载数据test_dataloader = Data.DataLoader(dataset=test_data,# 要加载的数据batch_size=1, # 每轮训练的批次数shuffle=True, # 打乱数据集num_workers=0, # 加载数据集的线程数量)return test_dataloader# 测试模型
def test_model_process(model,test_dataloader):# 指定设备device = torch.device("cuda" if torch.cuda.is_available() else "cpu")# 将模型放入设备中model.to(device)# 初始化模型训练的每轮参数test_correct = 0.0 # 准确度test_num = 0 # 测试样本数量# 只进行前向传播with torch.no_grad(): # 将梯度设置为0for test_data_x,test_data_y in enumerate(test_dataloader): # 遍历每轮次# 由于上面设置批次为1,所以这里就不需要循环批次了test_data_x = test_data_x.to(device) # 将测试数据放入到设备中test_data_y = test_data_y.to(device) # 将标签数据放入到设备中# 模型切换成评估模式model.eval()# 前向传播 将测试数据放入到模型中output = model(test_data_x)# 查找每一行中最大值的行标pre_lab = torch.argmax(output,dim=1)# 模型预测的结果 将pre_lab 与 标签数据 进行比较# 如果预测正确,则加1test_correct += torch.sum(pre_lab==test_data_y.data)# 测试样本数量累加test_num += test_data_y.size(0)# 计算最终测试的准确率 每轮的准确度 / 总样本数量test_acc = test_correct.double().item() / test_numprint("测试模型的准确率为:",test_acc)if __name__ == '__main__':# 加载模型model = GoogLeNet(Inception)# 指定设备device = torch.device("cuda" if torch.cuda.is_available() else "cpu")# 加载训练好的模型最佳参数model.load_state_dict(torch.load('best_model.pth',map_location=device))# 加载测试的数据集# test_dataloader = test_data_process()# 开始测试# test_model_process(model, test_dataloader) # 简略测试# 指定设备device = torch.device("cuda" if torch.cuda.is_available() else "cpu")# 将模型放入到设备当中model = model.to(device)# 数据的类别classes = ['猫','狗']# =================新增代码部分开始,到底直接结束=================# 读取刚刚下载到本地猫的图片cat_url = "7dd98d1001e939010b1f0c1e537c22e836d19614.jpeg"image = Image.open(url) # 要预测图片的路径# 定义归一化方法 计算均值和方差normalize = transforms.Normalize([0.162,0.151,0.138],[0.058,0.052,0.048])# 定义数据集处理方法变量test_transform = transforms.Compose( # 对数据集进行操作[transforms.Resize((224,224)),# 修改数据集的大小为 224*224transforms.ToTensor(), # 将数据修改为tensor格式normalize # 数据归一化]  )# 对下载的图片进行格式处理 转成torch类型数据image = test_transform(image)# print(image.shape) # torch.Size([3,224,224]) 打印处理后的图片的维度# 添加批次维度 1image = unsqueeze(0)# print(image.shape) # torch.Size([1,3,224,224]) 打印处理后的图片的维度,其中1表示批次,是由unsqueeze(0)得来的# 去除梯度下降with torch.no_grad():model.eval() # 开启验证模式image = image.to(device) # 将图片放入到设备当中output = model(image) # 将图片放入到模型当中pre_lab = torch.argmax(out_put,dim=1) # 标签result = pre_lab.item()# print(pre_lab) # tensor([0],device='cude:0')# print(result) # 0 表示猫print("预测值: ",classes[result])

device = torch.device(“cuda” if torch.cuda.is_available() else “cpu”)

# 将模型放入到设备当中
model = model.to(device)# 数据的类别
classes = ['猫','狗']# =================新增代码部分开始,到底直接结束=================
# 读取刚刚下载到本地猫的图片
cat_url = "7dd98d1001e939010b1f0c1e537c22e836d19614.jpeg"
image = Image.open(url) # 要预测图片的路径# 定义归一化方法 计算均值和方差
normalize = transforms.Normalize([0.162,0.151,0.138],[0.058,0.052,0.048])# 定义数据集处理方法变量
test_transform = transforms.Compose( # 对数据集进行操作[transforms.Resize((224,224)),# 修改数据集的大小为 224*224transforms.ToTensor(), # 将数据修改为tensor格式normalize # 数据归一化]  
)# 对下载的图片进行格式处理 转成torch类型数据
image = test_transform(image)
# print(image.shape) # torch.Size([3,224,224]) 打印处理后的图片的维度# 添加批次维度 1
image = unsqueeze(0)
# print(image.shape) # torch.Size([1,3,224,224]) 打印处理后的图片的维度,其中1表示批次,是由unsqueeze(0)得来的# 去除梯度下降
with torch.no_grad():model.eval() # 开启验证模式image = image.to(device) # 将图片放入到设备当中output = model(image) # 将图片放入到模型当中pre_lab = torch.argmax(out_put,dim=1) # 标签result = pre_lab.item()
# print(pre_lab) # tensor([0],device='cude:0')
# print(result) # 0 表示猫
print("预测值: ",classes[result])

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/83146.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/83146.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/83146.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Free2AI:企业智能化转型的加速器

随着数字化与智能化的深度交融,企业的竞争舞台已悄然转变为数据处理能力和智能服务水平的竞技场。Free2AI以其三大核心功能——智能数据采集、多格式文档解析、智能FAQ构建,为企业铺设了一条从数据洞察到智能服务的全链路升级之路,成为推动企…

Vue 核心技术与实战day07

1. vuex概述 2. 构建 vuex [多组件数据共享] 环境 <template><div id"app"><h1>根组件- {{ title }}- {{ count }}</h1><input :value"count" input"handleInput" type"text"><Son1></Son1>…

【原神 × 插入排序】刷圣遗物也讲算法:圣遗物评分系统背后的排序逻辑你真的懂吗?

📘 改编自:王争《数据结构与算法之美》 🎮 游戏演绎:米哈游《原神》 🧠 核心关键词:插入排序、排序算法、评分系统、属性评价、强化圣遗物、冒泡排序对比 🧭 引言:原神刷本=刷排序? 玩《原神》的玩家每天日常是啥?体力用来刷圣遗物、精通头、暴击头、攻充沙………

quasar electron mode如何打包无边框桌面应用程序

预览 开源项目Tokei Kun 一款简洁的周年纪念app&#xff0c;现已发布APK&#xff08;安卓&#xff09;和 EXE&#xff08;Windows&#xff09; 项目仓库地址&#xff1a;Github Repo 应用下载链接&#xff1a;Github Releases Preparation for Electron quasar dev -m elect…

微信小程序真机调试时如何实现与本地开发环境服务器交互

最近在开发微信小程序项目,真机调试时需要在手机上运行小程序,为了实现本地开发服务器与手机小程序的交互,需要以下步骤 1.将手机连到和本地一样的局域网 2.Visual Studio中将IIS Express服务器的localhost端口地址修改为本机的IP自定义的端口: 1&#xff09;找到web api项目…

Scratch节日 | 拯救屈原 | 端午节

端午节快乐&#xff01; 这款特别为端午节打造的Scratch游戏 《拯救屈原》&#xff0c;将带你走进古代中国&#xff0c;感受历史与文化的魅力&#xff01; &#x1f3ee; 游戏介绍 扮演勇敢的探险者&#xff0c;穿越时空回到古代&#xff0c;解锁谜题&#xff0c;完成任务&…

PHP下实现RSA的加密,解密,加签和验签

前言&#xff1a; RSA下加密&#xff0c;解密&#xff0c;加签和验签是四种不同的操作&#xff0c;有时候会搞错&#xff0c;记录一下。 1.公钥加密&#xff0c;私钥解密 发送方通过公钥将原数据加密成一个sign参数&#xff0c;相当于就是信息的载体&#xff0c;接收方能通过si…

Win10秘笈:两种方式修改网卡物理地址(MAC)

Win10秘笈&#xff1a;两种方式修改网卡物理地址&#xff08;MAC&#xff09; 在修改之前&#xff0c;可以先确定一下要修改的网卡MAC地址&#xff0c;查询方法有很多种&#xff0c;比如&#xff1a; 1、在设置→网络和Internet→WLAN/以太网&#xff0c;如下图所示。 2、在控…

C++中IO文件输入输出知识详解和注意事项

以下内容将从文件流类体系、打开模式、文本与二进制 I/O、随机访问、错误处理、性能优化等方面&#xff0c;详解 C 中文件输入输出的使用要点&#xff0c;并配以示例。 一、文件流类体系 C 标准库提供三种文件流类型&#xff0c;均定义在 <fstream> 中&#xff1a; std…

Unity3D仿星露谷物语开发56之保存角色位置到文件

1、目标 游戏中通过Save Game保存角色位置&#xff0c;当重启游戏后&#xff0c;通过Load Game可以恢复角色的位置。 2、Player对象操作 &#xff08;1&#xff09;组件添加 给Hierarchy下的Player组件添加Generate GUID组件。 &#xff08;2&#xff09;修改SceneSave.cs脚…

TKernel模块--杂项

TKernel模块–杂项 1.DEFINE_HARRAY1 #define DEFINE_HARRAY1(HClassName, _Array1Type_) \ class HClassName : public _Array1Type_, public Standard_Transient { \public: …

c++ typeid运算符

typeid运算符能获取类型信息。获取到的是type_info对象。type_info类型如下&#xff1a; 可以看到&#xff0c;这个类删除了拷贝构造函数以及等号操作符。有一些成员函数&#xff1a;hash_code、before、name、raw_name, 还重载了和!运算符。 测试&#xff1a; void testTyp…

第304个Vulnhub靶场演练攻略:digital world.local:FALL

digital world.local&#xff1a;FALL Vulnhub 演练 FALL (digitalworld.local: FALL) 是 Donavan 为 Vulnhub 打造的一款中型机器。这款实验室非常适合经验丰富的 CTF 玩家&#xff0c;他们希望在这类环境中检验自己的技能。那么&#xff0c;让我们开始吧&#xff0c;看看如何…

【数据库】数据库恢复技术

数据库恢复技术 实现恢复的核心是使用冗余&#xff0c;也就是根据冗余数据重建不正确数据。 事务 事务是一个数据库操作序列&#xff0c;是一个不可分割的工作单位&#xff0c;是恢复和并发的基本单位。 在关系数据库中&#xff0c;一个事务是一条或多条SQL语句&#xff0c…

switch-case判断

switch-case判断 #include <stdio.h> int main() {int type;printf("请输入你的选择&#xff1a;\n");scanf("%d",&type);getchar();switch (type){case 1:printf("你好&#xff01;");break;case 2:printf("早上好&#xff01;…

从监控到告警:Prometheus+Grafana+Alertmanager+告警通知服务全链路落地实践

文章目录 一、引言1.1 监控告警的必要性1.2 监控告警的基本原理1.2.1 指标采集与存储1.2.2 告警规则与触发机制1.2.3 多渠道通知与闭环 二、技术选型与架构设计2.1 为什么选择 Prometheus 及其生态2.1.1 Prometheus 优势分析2.1.2 Grafana 可视化能力2.1.3 Alertmanager 灵活告…

STM32 UART通信实战指南:从原理到项目落地

STM32串口通信实战指南&#xff1a;从零开始手把手教你 前言&#xff1a;为什么串口这么重要&#xff1f; 在嵌入式开发中&#xff0c;串口就像设备的"嘴巴"和"耳朵"。无论是给单片机下达指令、读取传感器数据&#xff0c;还是让两个模块"对话"…

Jmeter requests

1.Jemter元件和组件 1.1 元件和组件的概念 元件&#xff1a;多个功能相似的的组件的容器&#xff0c;类似于一个工具箱。 组件&#xff1a;实现某个特定功能的实例&#xff0c;类似于工具箱中的螺丝刀&#xff0c;十字扳手... 1.2 作用域和执行顺序 1.2.1 作用域 例子&#…

计算机视觉---GT(ground truth)

在计算机视觉&#xff08;Computer Vision, CV&#xff09;领域&#xff0c;Ground Truth&#xff08;GT&#xff0c;中文常译为“真值”或“ ground truth”&#xff09; 是指关于数据的真实标签或客观事实&#xff0c;是模型训练、评估和验证的基准。它是连接算法与现实世界的…

1-Wire 一线式总线:从原理到实战,玩转 DS18B20 温度采集

引言 在嵌入式系统中&#xff0c;通信总线是连接 CPU 与外设的桥梁。从 I2C、SPI 到 UART&#xff0c;每种总线都有其独特的应用场景。而本文要介绍的1-Wire 一线式总线&#xff0c;以其极简的硬件设计和独特的通信协议&#xff0c;在温度采集、身份识别等领域大放异彩。本文将…