深度学习每周学习总结P7(咖啡豆识别)

  • 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
  • 🍖 原作者:K同学啊 | 接辅导、项目定制

–来自百度网盘超级会员V5的分享

数据链接
提取码:7zt2

–来自百度网盘超级会员V5的分享

目录

    • 0. 总结
    • 1. 数据导入及处理部分
    • 2. 划分数据集
    • 3. 模型构建部分
      • 3.1 调用官方的VGG16模型
      • 3.2 自定义VGG16模型
      • 3.3 公式推导
    • 4. 设置超参数:定义损失函数,学习率,以及根据学习率定义优化器等
      • 4.1 设置设置初始学习率,动态学习率,梯度下降优化器,损失函数
      • 4.2 动态学习率代码说明
    • 5. 编写训练函数
    • 6. 编写测试函数
    • 7. 正式训练
    • 8. 结果可视化
    • 9. 模型的保存
    • 10. 使用训练好的模型进行预测
    • 11. 不同参数模型预测效果测试与记录-自定义模型(待完善)
      • 固定学习率
      • 动态学习率 + Adam
        • 1e-4 测试集准确率99.6%

0. 总结

数据导入及处理部分:本次数据导入没有使用torchvision自带的数据集,需要将原始数据进行处理包括数据导入,查看数据分类情况,定义transforms,进行数据类型转换等操作。

划分数据集:划定训练集测试集后,再使用torch.utils.data中的DataLoader()分别加载上一步处理好的训练及测试数据,查看批处理维度.

模型构建部分:有两个部分一个初始化部分(init())列出了网络结构的所有层,比如卷积层池化层等。第二个部分是前向传播部分,定义了数据在各层的处理过程。

设置超参数:在这之前需要定义损失函数,学习率(动态学习率),以及根据学习率定义优化器(例如SGD随机梯度下降),用来在训练中更新参数,最小化损失函数。

定义训练函数:函数的传入的参数有四个,分别是设置好的DataLoader(),定义好的模型,损失函数,优化器。函数内部初始化损失准确率为0,接着开始循环,使用DataLoader()获取一个批次的数据,对这个批次的数据带入模型得到预测值,然后使用损失函数计算得到损失值。接下来就是进行反向传播以及使用优化器优化参数,梯度清零放在反向传播之前或者是使用优化器优化之后都是可以的,一般是默认放在反向传播之前。

定义测试函数:函数传入的参数相比训练函数少了优化器,只需传入设置好的DataLoader(),定义好的模型,损失函数。此外除了处理批次数据时无需再设置梯度清零、返向传播以及优化器优化参数,其余部分均和训练函数保持一致。

训练过程:定义训练次数,有几次就使用整个数据集进行几次训练,初始化四个空list分别存储每次训练及测试的准确率及损失。使用model.train()开启训练模式,调用训练函数得到准确率及损失。使用model.eval()将模型设置为评估模式,调用测试函数得到准确率及损失。接着就是将得到的训练及测试的准确率及损失存储到相应list中并合并打印出来,得到每一次整体训练后的准确率及损失。学习优秀大佬的调试方案,达到优化目的。注意:之前有疏忽的一点是保存的是最后一次训练的模型,没有保存最好的模型训练参数,本次需要认真复习总结

结果可视化

模型的保存,调取及使用。在PyTorch中,通常使用 torch.save(model.state_dict(), ‘model.pth’) 保存模型的参数,使用 model.load_state_dict(torch.load(‘model.pth’)) 加载参数。

需要改进优化的地方:在保证整体流程没有问题的情况下,继续细化细节研究,比如一些函数的原理及作用,如何提升训练集准确率等问题。此外上次训练时发现同样的初始学习率,自定义的vgg16模型模型没有直接调用官方的模型测试准确率高的情形,本次需要重点关注此问题

1. 数据导入及处理部分

import torch
import torch.nn as nn
import torchvision
from torchvision import transforms,datasets

import PIL,random,os,pathlib
import matplotlib.pyplot as plt
import warnings

warnings.filterwarnings("ignore") # 忽略警告信息
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False   # 用来正常显示负号
plt.rcParams['figure.dpi'] = 100  # 分辨率

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
device
device(type='cuda')
# 获取数据集分类情况
path_dir = './data/coffee_bean_recognize/'
path_dir = pathlib.Path(path_dir)     # 使用pathlib.Path()函数将字符串类型的文件夹路径转换为pathlib.Path对象。

data_paths = list(path_dir.glob('*')) # 使用glob()方法获取data_dir路径下的所有文件路径,并以列表形式存储在data_paths中。
# classNames = [str(path).split('\\')[-1] for path in data_paths]
classNames = [path.parts[-1] for path in data_paths] # pathlib的.parts方法会返回路径各部分的一个元组
classNames
['Dark', 'Green', 'Light', 'Medium']
# 定义transforms 并处理数据
train_transforms = transforms.Compose([
    transforms.Resize([224,224]),      # 将输入图片resize成统一尺寸
    transforms.RandomHorizontalFlip(), # 随机水平翻转
    transforms.ToTensor(),             # 将PIL Image 或 numpy.ndarray 装换为tensor,并归一化到[0,1]之间
    transforms.Normalize(              # 标准化处理 --> 转换为标准正太分布(高斯分布),使模型更容易收敛
        mean = [0.485,0.456,0.406],    # 其中 mean=[0.485,0.456,0.406]与std=[0.229,0.224,0.225] 从数据集中随机抽样计算得到的。
        std = [0.229,0.224,0.225]
    )
])
test_transforms = transforms.Compose([
    transforms.Resize([224,224]),
    transforms.ToTensor(),
    transforms.Normalize(
        mean = [0.485,0.456,0.406],
        std = [0.229,0.224,0.225]
    )
])
total_data = datasets.ImageFolder('./data/coffee_bean_recognize/',transform = train_transforms)
total_data
Dataset ImageFolder
    Number of datapoints: 1200
    Root location: ./data/coffee_bean_recognize/
    StandardTransform
Transform: Compose(
               Resize(size=[224, 224], interpolation=bilinear, max_size=None, antialias=warn)
               RandomHorizontalFlip(p=0.5)
               ToTensor()
               Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
           )
total_data.class_to_idx
{'Dark': 0, 'Green': 1, 'Light': 2, 'Medium': 3}

2. 划分数据集

# 划分数据集
train_size = int(len(total_data) * 0.8)
test_size = len(total_data) - train_size

train_dataset,test_dataset = torch.utils.data.random_split(total_data,[train_size,test_size])
train_dataset,test_dataset
(<torch.utils.data.dataset.Subset at 0x16285db9160>,
 <torch.utils.data.dataset.Subset at 0x16285db99a0>)
# 定义DataLoader用于数据集的加载

batch_size = 32

train_dl = torch.utils.data.DataLoader(
    train_dataset,
    batch_size = batch_size,
    shuffle = True,
    num_workers = 1
)
test_dl = torch.utils.data.DataLoader(
    test_dataset,
    batch_size = batch_size,
    shuffle = True,
    num_workers = 1
)
# 观察数据维度
for X,y in test_dl:
    print("Shape of X [N,C,H,W]: ",X.shape)
    print("Shape of y: ", y.shape,y.dtype)
    break
Shape of X [N,C,H,W]:  torch.Size([32, 3, 224, 224])
Shape of y:  torch.Size([32]) torch.int64

3. 模型构建部分

3.1 调用官方的VGG16模型

# from torchvision.models import vgg16

# # 加载预训练的vgg16模型
# model = vgg16(pretrained = True).to(device)

# for param in model.parameters():
#     param.requires_grad = False  # 冻结模型参数,这样子在训练的时候只训练最后一层的参数
    
# # 修改classifier模块的第6层,改为实际需要的分类数目,即修改:(6): Linear(in_features=4096, out_features=2, bias=True)
# model.classifier._modules['6'] = nn.Linear(4096,len(classNames)) # 修改vgg16模型中最后一层全连接层,输出目标类别个数
# model.to(device)
# model
# # 查看模型详情
# import torchsummary as summary
# summary.summary(model,(3,224,224))

3.2 自定义VGG16模型

import torch.nn.functional as F

class vgg16(nn.Module):
    def __init__(self):
        super(vgg16,self).__init__()
        # 卷积块1
        self.block1 = nn.Sequential(
            nn.Conv2d(3,64,kernel_size=(3,3),stride = (1,1),padding = (1,1)),
            nn.ReLU(),
            nn.Conv2d(64,64,kernel_size=(3,3),stride = (1,1),padding = (1,1)),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=(2,2),stride=(2,2))
        )
        # 卷积块2
        self.block2 = nn.Sequential(
            nn.Conv2d(64,128,kernel_size=(3,3),stride=(1,1),padding=(1,1)),
            nn.ReLU(),
            nn.Conv2d(128,128,kernel_size=(3,3),stride=(1,1),padding=(1,1)),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=(2,2),stride=(2,2))
        )
        # 卷积块3
        self.block3 = nn.Sequential(
            nn.Conv2d(128,256,kernel_size=(3,3),stride=(1,1),padding=(1,1)),
            nn.ReLU(),
            nn.Conv2d(256,256,kernel_size=(3,3),stride=(1,1),padding=(1,1)),
            nn.ReLU(),
            nn.Conv2d(256,256,kernel_size=(3,3),stride=(1,1),padding=(1,1)),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=(2,2),stride=(2,2))
        )
        # 卷积块4
        self.block4 = nn.Sequential(
            nn.Conv2d(256,512,kernel_size=(3,3),stride=(1,1),padding=(1,1)),
            nn.ReLU(),
            nn.Conv2d(512,512,kernel_size=(3,3),stride=(1,1),padding=(1,1)),
            nn.ReLU(),
            nn.Conv2d(512,512,kernel_size=(3,3),stride=(1,1),padding=(1,1)),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=(2,2),stride=(2,2))
        )
        # 卷积块5
        self.block5 = nn.Sequential(
            nn.Conv2d(512,512,kernel_size=(3,3),stride=(1,1),padding=(1,1)),
            nn.ReLU(),
            nn.Conv2d(512,512,kernel_size=(3,3),stride=(1,1),padding=(1,1)),
            nn.ReLU(),
            nn.Conv2d(512,512,kernel_size=(3,3),stride=(1,1),padding=(1,1)),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=(2,2),stride=(2,2))
        )
        # 全连接层,用于分类
        self.classifier = nn.Sequential(
            nn.Linear(in_features = 512 * 7 *7,out_features = 4096),
            nn.ReLU(),
            nn.Linear(in_features = 4096,out_features = 4096),
            nn.ReLU(),
            nn.Linear(in_features = 4096,out_features = len(classNames))
        )
    def forward(self,x):
        x = self.block1(x)
        x = self.block2(x)
        x = self.block3(x)
        x = self.block4(x)
        x = self.block5(x)
        x = torch.flatten(x,start_dim = 1)
        x = self.classifier(x)
        
        return x

model = vgg16().to(device)
model
vgg16(
  (block1): Sequential(
    (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (1): ReLU()
    (2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (3): ReLU()
    (4): MaxPool2d(kernel_size=(2, 2), stride=(2, 2), padding=0, dilation=1, ceil_mode=False)
  )
  (block2): Sequential(
    (0): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (1): ReLU()
    (2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (3): ReLU()
    (4): MaxPool2d(kernel_size=(2, 2), stride=(2, 2), padding=0, dilation=1, ceil_mode=False)
  )
  (block3): Sequential(
    (0): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (1): ReLU()
    (2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (3): ReLU()
    (4): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (5): ReLU()
    (6): MaxPool2d(kernel_size=(2, 2), stride=(2, 2), padding=0, dilation=1, ceil_mode=False)
  )
  (block4): Sequential(
    (0): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (1): ReLU()
    (2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (3): ReLU()
    (4): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (5): ReLU()
    (6): MaxPool2d(kernel_size=(2, 2), stride=(2, 2), padding=0, dilation=1, ceil_mode=False)
  )
  (block5): Sequential(
    (0): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (1): ReLU()
    (2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (3): ReLU()
    (4): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (5): ReLU()
    (6): MaxPool2d(kernel_size=(2, 2), stride=(2, 2), padding=0, dilation=1, ceil_mode=False)
  )
  (classifier): Sequential(
    (0): Linear(in_features=25088, out_features=4096, bias=True)
    (1): ReLU()
    (2): Linear(in_features=4096, out_features=4096, bias=True)
    (3): ReLU()
    (4): Linear(in_features=4096, out_features=4, bias=True)
  )
)
import torchsummary as summary
summary.summary(model,(3,224,224))
----------------------------------------------------------------
        Layer (type)               Output Shape         Param #
================================================================
            Conv2d-1         [-1, 64, 224, 224]           1,792
              ReLU-2         [-1, 64, 224, 224]               0
            Conv2d-3         [-1, 64, 224, 224]          36,928
              ReLU-4         [-1, 64, 224, 224]               0
         MaxPool2d-5         [-1, 64, 112, 112]               0
            Conv2d-6        [-1, 128, 112, 112]          73,856
              ReLU-7        [-1, 128, 112, 112]               0
            Conv2d-8        [-1, 128, 112, 112]         147,584
              ReLU-9        [-1, 128, 112, 112]               0
        MaxPool2d-10          [-1, 128, 56, 56]               0
           Conv2d-11          [-1, 256, 56, 56]         295,168
             ReLU-12          [-1, 256, 56, 56]               0
           Conv2d-13          [-1, 256, 56, 56]         590,080
             ReLU-14          [-1, 256, 56, 56]               0
           Conv2d-15          [-1, 256, 56, 56]         590,080
             ReLU-16          [-1, 256, 56, 56]               0
        MaxPool2d-17          [-1, 256, 28, 28]               0
           Conv2d-18          [-1, 512, 28, 28]       1,180,160
             ReLU-19          [-1, 512, 28, 28]               0
           Conv2d-20          [-1, 512, 28, 28]       2,359,808
             ReLU-21          [-1, 512, 28, 28]               0
           Conv2d-22          [-1, 512, 28, 28]       2,359,808
             ReLU-23          [-1, 512, 28, 28]               0
        MaxPool2d-24          [-1, 512, 14, 14]               0
           Conv2d-25          [-1, 512, 14, 14]       2,359,808
             ReLU-26          [-1, 512, 14, 14]               0
           Conv2d-27          [-1, 512, 14, 14]       2,359,808
             ReLU-28          [-1, 512, 14, 14]               0
           Conv2d-29          [-1, 512, 14, 14]       2,359,808
             ReLU-30          [-1, 512, 14, 14]               0
        MaxPool2d-31            [-1, 512, 7, 7]               0
           Linear-32                 [-1, 4096]     102,764,544
             ReLU-33                 [-1, 4096]               0
           Linear-34                 [-1, 4096]      16,781,312
             ReLU-35                 [-1, 4096]               0
           Linear-36                    [-1, 4]          16,388
================================================================
Total params: 134,276,932
Trainable params: 134,276,932
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.57
Forward/backward pass size (MB): 218.52
Params size (MB): 512.23
Estimated Total Size (MB): 731.32
----------------------------------------------------------------

3.3 公式推导

涉及维度变化的层有卷积层,池化层,全连接层

3, 224, 224(输入数据)

-> 64, 224, 224(经过卷积层1)

-> 64, 224, 224(经过卷积层2)-> 64, 112, 112(经过池化层1)

-> 128, 112, 112(经过卷积层3)

-> 128, 112, 112(经过卷积层4)-> 128, 56, 56(经过池化层2)

-> 256, 56, 56(经过卷积层5)

-> 256, 56, 56(经过卷积层6)

-> 256, 56, 56(经过卷积层7)-> 256, 28, 28(经过池化层3)

-> 512, 28, 28(经过卷积层8)

-> 512, 28, 28(经过卷积层9)

-> 512, 28, 28(经过卷积层10)-> 512, 14, 14(经过池化层4)

-> 512, 14, 14(经过卷积层11)

-> 512, 14, 14(经过卷积层12)

-> 512, 14, 14(经过卷积层13)-> 512, 7, 7(经过池化层5)

-> 4096

-> 4096 -> num_classes(17)

计算公式:

卷积维度计算公式:

  • 高度方向: H o u t = ( H i n − K e r n e l _ s i z e + 2 × p a d d i n g ) s t r i d e + 1 H_{out} = \frac{\left(H_{in} - Kernel\_size + 2\times padding\right)}{stride} + 1 Hout=stride(HinKernel_size+2×padding)+1

  • 宽度方向: W o u t = ( W i n − K e r n e l _ s i z e + 2 × p a d d i n g ) s t r i d e + 1 W_{out} = \frac{\left(W_{in} - Kernel\_size + 2\times padding\right)}{stride} + 1 Wout=stride(WinKernel_size+2×padding)+1

  • 卷积层通道数变化:数据通道数为卷积层该卷积层定义的输出通道数,例如:self.conv1 = nn.Conv2d(3,64,kernel_size = 3)。在这个例子中,输出的通道数为64,这意味着卷积层使用了64个不同的卷积核,每个核都在输入数据上独立进行卷积运算,产生一个新的通道。需要注意,卷积操作不是在单独的通道上进行的,而是跨所有输入通道(本例中为3个通道)进行的,每个卷积核提供一个新的输出通道。

池化层计算公式:

  • 高度方向: H o u t = ( H i n + 2 × p a d d i n g H − d i l a t i o n H × ( k e r n e l _ s i z e H − 1 ) − 1 s t r i d e H + 1 ) H_{out} = \left(\frac{H_{in} + 2 \times padding_H - dilation_H \times (kernel\_size_H - 1) - 1}{stride_H} + 1 \right) Hout=(strideHHin+2×paddingHdilationH×(kernel_sizeH1)1+1)

  • 宽度方向: W o u t = ( W i n + 2 × p a d d i n g W − d i l a t i o n W × ( k e r n e l _ s i z e W − 1 ) − 1 s t r i d e W + 1 ) W_{out} = \left( \frac{W_{in} + 2 \times padding_W - dilation_W \times (kernel\_size_W - 1) - 1}{stride_W} + 1 \right) Wout=(strideWWin+2×paddingWdilationW×(kernel_sizeW1)1+1)

其中:

  • H i n H_{in} Hin W i n W_{in} Win 是输入的高度和宽度。
  • p a d d i n g H padding_H paddingH p a d d i n g W padding_W paddingW 是在高度和宽度方向上的填充量。
  • k e r n e l _ s i z e H kernel\_size_H kernel_sizeH k e r n e l _ s i z e W kernel\_size_W kernel_sizeW 是卷积核或池化核在高度和宽度方向上的大小。
  • s t r i d e H stride_H strideH s t r i d e W stride_W strideW 是在高度和宽度方向上的步长。
  • d i l a t i o n H dilation_H dilationH d i l a t i o n W dilation_W dilationW 是在高度和宽度方向上的膨胀系数。

请注意,这里的膨胀系数 $dilation \times (kernel_size - 1) $实际上表示核在膨胀后覆盖的区域大小。例如,一个 $3 \times 3 $ 的核,如果膨胀系数为2,则实际上它覆盖的区域大小为$ 5 \times 5 $(原始核大小加上膨胀引入的间隔)。

计算流程:(只考虑卷积层和池化层,只有这两层影响数据维度)

输入数据:( 3 ∗ 224 ∗ 224 3*224*224 3224224)

conv1计算:卷积核数64,输出的通道也为64 -> ( 64 ∗ 224 ∗ 224 ) (64*224*224) (64224224)
输出维度 = ( 224 − 3 + 2 × 1 ) 1 + 1 = 224 \text{输出维度} = \frac{(224 - 3 + 2 \times 1)}{1} + 1 = 224 输出维度=1(2243+2×1)+1=224

conv2计算:-> ( 64 ∗ 224 ∗ 224 ) (64*224*224) (64224224)
输出维度 = ( 224 − 3 + 2 × 1 ) 1 + 1 = 224 \text{输出维度} = \frac{(224 - 3 + 2 \times 1)}{1} + 1 = 224 输出维度=1(2243+2×1)+1=224

pool1计算:通道数不变,步长为2-> ( 64 ∗ 112 ∗ 112 ) (64*112*112) (64112112)
输出维度 = 224 + 2 × 0 − 1 × ( 2 − 1 ) − 1 2 + 1 = 111 + 1 = 112 \text{输出维度} = \frac{224 + 2 \times 0 - 1 \times (2 - 1) - 1}{2} + 1 = 111 +1 = 112 输出维度=2224+2×01×(21)1+1=111+1=112

conv3计算:-> ( 128 ∗ 112 ∗ 112 ) (128*112*112) (128112112)
输出维度 = ( 112 − 3 + 2 × 1 ) 1 + 1 = 112 \text{输出维度} = \frac{(112 - 3 + 2 \times 1)}{1} + 1 = 112 输出维度=1(1123+2×1)+1=112

conv4计算:-> ( 128 ∗ 112 ∗ 112 ) (128*112*112) (128112112)
输出维度 = ( 112 − 3 + 2 × 1 ) 1 + 1 = 112 \text{输出维度} = \frac{(112 - 3 + 2 \times 1)}{1} + 1 = 112 输出维度=1(1123+2×1)+1=112

pool2计算:-> ( 128 ∗ 56 ∗ 56 ) (128*56*56) (1285656)
输出维度 = 112 + 2 × 0 − 1 × ( 2 − 1 ) − 1 2 + 1 = 55 + 1 = 56 \text{输出维度} = \frac{112 + 2 \times 0 - 1 \times (2 - 1) - 1}{2} + 1 = 55 +1 = 56 输出维度=2112+2×01×(21)1+1=55+1=56

conv5计算:-> ( 256 ∗ 56 ∗ 56 ) (256*56*56) (2565656)
输出维度 = ( 56 − 3 + 2 × 1 ) 1 + 1 = 56 \text{输出维度} = \frac{(56 - 3 + 2 \times 1)}{1} + 1 = 56 输出维度=1(563+2×1)+1=56

conv6计算: -> ( 256 ∗ 56 ∗ 56 ) (256*56*56) (2565656)
输出维度 = ( 56 − 3 + 2 × 1 ) 1 + 1 = 56 {输出维度} = \frac{(56 - 3 + 2 \times 1)}{1} + 1 = 56 输出维度=1(563+2×1)+1=56

conv7计算: -> ( 256 ∗ 56 ∗ 56 ) (256*56*56) (2565656)
输出维度 = ( 56 − 3 + 2 × 1 ) 1 + 1 = 56 {输出维度} = \frac{(56 - 3 + 2 \times 1)}{1} + 1 = 56 输出维度=1(563+2×1)+1=56

pool3计算:-> ( 256 ∗ 28 ∗ 28 ) (256*28*28) (2562828)
输出维度 = 56 + 2 × 0 − 1 × ( 2 − 1 ) − 1 2 + 1 = 27 + 1 = 28 {输出维度} = \frac{56 + 2 \times 0 - 1 \times (2 - 1) - 1}{2} + 1 = 27 + 1 = 28 输出维度=256+2×01×(21)1+1=27+1=28

conv8计算:-> ( 512 ∗ 28 ∗ 28 ) (512*28*28) (5122828)
输出维度 = ( 28 − 3 + 2 × 1 ) 1 + 1 = 28 {输出维度} = \frac{(28 - 3 + 2 \times 1)}{1} + 1 = 28 输出维度=1(283+2×1)+1=28

conv9计算: -> ( 512 ∗ 28 ∗ 28 ) (512*28*28) (5122828)
输出维度 = ( 28 − 3 + 2 × 1 ) 1 + 1 = 28 {输出维度} = \frac{(28 - 3 + 2 \times 1)}{1} + 1 = 28 输出维度=1(283+2×1)+1=28

conv10计算:-> ( 512 ∗ 28 ∗ 28 ) (512*28*28) (5122828)
输出维度 = ( 28 − 3 + 2 × 1 ) 1 + 1 = 28 {输出维度} = \frac{(28 - 3 + 2 \times 1)}{1} + 1 = 28 输出维度=1(283+2×1)+1=28

pool4计算: -> ( 512 ∗ 14 ∗ 14 ) (512*14*14) (5121414)
输出维度 = 28 + 2 × 0 − ( 2 − 1 ) − 1 2 + 1 = 14 {输出维度} = \frac{28 + 2 \times 0 - (2 - 1) -1}{2} + 1 = 14 输出维度=228+2×0(21)1+1=14

conv11计算:-> ( 512 ∗ 14 ∗ 14 ) (512*14*14) (5121414)
输出维度 = ( 14 − 3 + 2 × 1 ) 1 + 1 = 14 {输出维度} = \frac{(14 - 3 + 2 \times 1)}{1} + 1 = 14 输出维度=1(143+2×1)+1=14

conv12计算: -> ( 512 ∗ 14 ∗ 14 ) (512*14*14) (5121414)
输出维度 = ( 14 − 3 + 2 × 1 ) 1 + 1 = 14 {输出维度} = \frac{(14 - 3 + 2 \times 1)}{1} + 1 = 14 输出维度=1(143+2×1)+1=14

conv13计算: -> ( 512 ∗ 14 ∗ 14 ) (512*14*14) (5121414)
输出维度 = ( 14 − 3 + 2 × 1 ) 1 + 1 = 14 {输出维度} = \frac{(14 - 3 + 2 \times 1)}{1} + 1 = 14 输出维度=1(143+2×1)+1=14

pool5计算: -> ( 512 ∗ 7 ∗ 7 ) (512*7*7) (51277)
输出维度 = 14 + 2 × 0 − 1 × ( 2 − 1 ) − 1 2 + 1 = 7 {输出维度} = \frac{14 + 2 \times 0 - 1 \times (2 - 1) - 1}{2} + 1 = 7 输出维度=214+2×01×(21)1+1=7

flatten1计算:-> 4096 4096 4096

flatten2计算:-> 4096 4096 4096

flatten3计算:-> n u m _ c l a s s e s ( 17 ) num\_classes(17) num_classes(17)

4. 设置超参数:定义损失函数,学习率,以及根据学习率定义优化器等

4.1 设置设置初始学习率,动态学习率,梯度下降优化器,损失函数

# loss_fn = nn.CrossEntropyLoss() # 创建损失函数

# learn_rate = 1e-3 # 初始学习率
# def adjust_learning_rate(optimizer,epoch,start_lr):
#     # 每两个epoch 衰减到原来的0.98
#     lr = start_lr * (0.92 ** (epoch//2))
#     for param_group in optimizer.param_groups:
#         param_group['lr'] = lr
        
# optimizer = torch.optim.Adam(model.parameters(),lr=learn_rate)
# 调用官方接口示例
loss_fn = nn.CrossEntropyLoss()

learn_rate = 1e-4
lambda1 = lambda epoch:(0.92**(epoch//2))

optimizer = torch.optim.Adam(model.parameters(),lr = learn_rate)
scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer,lr_lambda=lambda1) # 选定调整方法

4.2 动态学习率代码说明

假定初始学习率(start_lr)是0.01. 这是前10个epoch学习率的变化情况:

  • Epoch 0: ( 0.01 × 0.9 2 ( 0 / / 2 ) = 0.01 × 0.9 2 0 = 0.01 (0.01 \times 0.92^{(0 // 2)} = 0.01 \times 0.92^{0} = 0.01 (0.01×0.92(0//2)=0.01×0.920=0.01
  • Epoch 1: ( 0.01 × 0.9 2 ( 1 / / 2 ) = 0.01 × 0.9 2 0 = 0.01 (0.01 \times 0.92^{(1 // 2)} = 0.01 \times 0.92^{0} = 0.01 (0.01×0.92(1//2)=0.01×0.920=0.01
  • Epoch 2: ( 0.01 × 0.9 2 ( 2 / / 2 ) = 0.01 × 0.9 2 1 = 0.0092 (0.01 \times 0.92^{(2 // 2)} = 0.01 \times 0.92^{1} = 0.0092 (0.01×0.92(2//2)=0.01×0.921=0.0092
  • Epoch 3: ( 0.01 × 0.9 2 ( 3 / / 2 ) = 0.01 × 0.9 2 1 = 0.0092 (0.01 \times 0.92^{(3 // 2)} = 0.01 \times 0.92^{1} = 0.0092 (0.01×0.92(3//2)=0.01×0.921=0.0092
  • Epoch 4: ( 0.01 × 0.9 2 ( 4 / / 2 ) = 0.01 × 0.9 2 2 = 0.008464 (0.01 \times 0.92^{(4 // 2)} = 0.01 \times 0.92^{2} = 0.008464 (0.01×0.92(4//2)=0.01×0.922=0.008464
  • Epoch 5: ( 0.01 × 0.9 2 ( 5 / / 2 ) = 0.01 × 0.9 2 2 = 0.008464 (0.01 \times 0.92^{(5 // 2)} = 0.01 \times 0.92^{2} = 0.008464 (0.01×0.92(5//2)=0.01×0.922=0.008464
  • Epoch 6: ( 0.01 × 0.9 2 ( 6 / / 2 ) = 0.01 × 0.9 2 3 = 0.007867 (0.01 \times 0.92^{(6 // 2)} = 0.01 \times 0.92^{3} = 0.007867 (0.01×0.92(6//2)=0.01×0.923=0.007867
  • Epoch 7: ( 0.01 × 0.9 2 ( 7 / / 2 ) = 0.01 × 0.9 2 3 = 0.007867 (0.01 \times 0.92^{(7 // 2)} = 0.01 \times 0.92^{3} = 0.007867 (0.01×0.92(7//2)=0.01×0.923=0.007867
  • Epoch 8: ( 0.01 × 0.9 2 ( 8 / / 2 ) = 0.01 × 0.9 2 4 = 0.007238 (0.01 \times 0.92^{(8 // 2)} = 0.01 \times 0.92^{4} = 0.007238 (0.01×0.92(8//2)=0.01×0.924=0.007238
  • Epoch 9: ( 0.01 × 0.9 2 ( 9 / / 2 ) = 0.01 × 0.9 2 4 = 0.007238 (0.01 \times 0.92^{(9 // 2)} = 0.01 \times 0.92^{4} = 0.007238 (0.01×0.92(9//2)=0.01×0.924=0.007238

从计算中可以看出,学习率每两个epoch下降一次。这种逐渐减少有助于微调神经网络的权重,特别是当它开始收敛到最小损失时。降低学习率可以帮助避免超过这个最小值,潜在地导致更好和更稳定的训练结果。

5. 编写训练函数

# 训练函数
def train(dataloader,model,loss_fn,optimizer):
    size = len(dataloader.dataset) # 训练集大小
    num_batches = len(dataloader) # 批次数目
    
    train_loss,train_acc = 0,0
    
    for X,y in dataloader:
        X,y = X.to(device),y.to(device)
        
        # 计算预测误差
        pred = model(X)
        loss = loss_fn(pred,y)
        
        # 反向传播
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        
        # 记录acc与loss
        train_acc += (pred.argmax(1)==y).type(torch.float).sum().item()
        train_loss += loss.item()
        
    train_acc /= size
    train_loss /= num_batches
    
    return train_acc,train_loss

6. 编写测试函数

# 测试函数
def test(dataloader,model,loss_fn):
    size = len(dataloader.dataset)
    num_batches = len(dataloader)
    
    test_acc,test_loss = 0,0
    
    with torch.no_grad():
        for X,y in dataloader:
            X,y = X.to(device),y.to(device)
            
            # 计算loss
            pred = model(X)
            loss = loss_fn(pred,y)
            
            test_acc += (pred.argmax(1)==y).type(torch.float).sum().item()
            test_loss += loss.item()
            
    test_acc /= size
    test_loss /= num_batches
    
    return test_acc,test_loss

7. 正式训练

epochs = 40

train_acc = []
train_loss = []
test_acc = []
test_loss = []

for epoch in range(epochs):
    
    # 更新学习率——使用自定义学习率时使用
    # adjust_learning_rate(optimizer,epoch,learn_rate)
    
    model.train()
    epoch_train_acc,epoch_train_loss = train(train_dl,model,loss_fn,optimizer)
    scheduler.step() # 更新学习率——调用官方动态学习率时使用
    
    model.eval()
    epoch_test_acc,epoch_test_loss = test(test_dl,model,loss_fn)
    
    # 保存最佳模型到 best_model
    if epoch_test_acc > best_acc:
        best_acc = epoch_test_acc
        best_model = copy.deepcopy(model)
    
    train_acc.append(epoch_train_acc)
    train_loss.append(epoch_train_loss)
    test_acc.append(epoch_test_acc)
    test_loss.append(epoch_test_loss)
    
    # 获取当前学习率
    lr = optimizer.state_dict()['param_groups'][0]['lr']
    
    template = ('Epoch:{:2d},Train_acc:{:.1f}%,Train_loss:{:.3f},Test_acc:{:.1f}%,Test_loss:{:.3f},Lr:{:.2E}')
    print(template.format(epoch+1,epoch_train_acc*100,epoch_train_loss,epoch_test_acc*100,epoch_test_loss,lr))

print('Done')
Epoch: 1,Train_acc:67.8%,Train_loss:0.680,Test_acc:76.7%,Test_loss:0.539,Lr:7.79E-05
Epoch: 2,Train_acc:76.4%,Train_loss:0.513,Test_acc:77.9%,Test_loss:0.496,Lr:7.79E-05
Epoch: 3,Train_acc:76.0%,Train_loss:0.519,Test_acc:84.6%,Test_loss:0.405,Lr:7.16E-05
Epoch: 4,Train_acc:78.5%,Train_loss:0.458,Test_acc:85.0%,Test_loss:0.309,Lr:7.16E-05
Epoch: 5,Train_acc:86.0%,Train_loss:0.297,Test_acc:86.7%,Test_loss:0.272,Lr:6.59E-05
Epoch: 6,Train_acc:91.7%,Train_loss:0.200,Test_acc:90.8%,Test_loss:0.209,Lr:6.59E-05
Epoch: 7,Train_acc:94.9%,Train_loss:0.126,Test_acc:95.4%,Test_loss:0.112,Lr:6.06E-05
Epoch: 8,Train_acc:97.5%,Train_loss:0.089,Test_acc:95.8%,Test_loss:0.159,Lr:6.06E-05
Epoch: 9,Train_acc:96.8%,Train_loss:0.099,Test_acc:95.8%,Test_loss:0.138,Lr:5.58E-05
Epoch:10,Train_acc:96.9%,Train_loss:0.074,Test_acc:97.9%,Test_loss:0.060,Lr:5.58E-05
Epoch:11,Train_acc:97.8%,Train_loss:0.065,Test_acc:97.5%,Test_loss:0.064,Lr:5.13E-05
Epoch:12,Train_acc:98.5%,Train_loss:0.046,Test_acc:97.5%,Test_loss:0.056,Lr:5.13E-05
Epoch:13,Train_acc:99.1%,Train_loss:0.031,Test_acc:97.5%,Test_loss:0.065,Lr:4.72E-05
Epoch:14,Train_acc:99.3%,Train_loss:0.024,Test_acc:97.5%,Test_loss:0.058,Lr:4.72E-05
Epoch:15,Train_acc:99.3%,Train_loss:0.022,Test_acc:96.2%,Test_loss:0.117,Lr:4.34E-05
Epoch:16,Train_acc:97.8%,Train_loss:0.055,Test_acc:98.3%,Test_loss:0.071,Lr:4.34E-05
Epoch:17,Train_acc:97.8%,Train_loss:0.057,Test_acc:97.9%,Test_loss:0.036,Lr:4.00E-05
Epoch:18,Train_acc:99.1%,Train_loss:0.023,Test_acc:97.5%,Test_loss:0.041,Lr:4.00E-05
Epoch:19,Train_acc:99.1%,Train_loss:0.023,Test_acc:98.3%,Test_loss:0.045,Lr:3.68E-05
Epoch:20,Train_acc:99.8%,Train_loss:0.010,Test_acc:98.3%,Test_loss:0.066,Lr:3.68E-05
Epoch:21,Train_acc:99.4%,Train_loss:0.018,Test_acc:98.8%,Test_loss:0.028,Lr:3.38E-05
Epoch:22,Train_acc:99.3%,Train_loss:0.021,Test_acc:97.9%,Test_loss:0.056,Lr:3.38E-05
Epoch:23,Train_acc:99.6%,Train_loss:0.010,Test_acc:98.8%,Test_loss:0.030,Lr:3.11E-05
Epoch:24,Train_acc:99.6%,Train_loss:0.009,Test_acc:98.3%,Test_loss:0.039,Lr:3.11E-05
Epoch:25,Train_acc:99.5%,Train_loss:0.012,Test_acc:98.8%,Test_loss:0.031,Lr:2.86E-05
Epoch:26,Train_acc:99.4%,Train_loss:0.011,Test_acc:98.3%,Test_loss:0.040,Lr:2.86E-05
Epoch:27,Train_acc:98.8%,Train_loss:0.030,Test_acc:96.7%,Test_loss:0.132,Lr:2.63E-05
Epoch:28,Train_acc:99.6%,Train_loss:0.015,Test_acc:98.8%,Test_loss:0.031,Lr:2.63E-05
Epoch:29,Train_acc:99.6%,Train_loss:0.012,Test_acc:98.3%,Test_loss:0.031,Lr:2.42E-05
Epoch:30,Train_acc:99.4%,Train_loss:0.014,Test_acc:98.3%,Test_loss:0.032,Lr:2.42E-05
Epoch:31,Train_acc:99.9%,Train_loss:0.004,Test_acc:98.8%,Test_loss:0.042,Lr:2.23E-05
Epoch:32,Train_acc:100.0%,Train_loss:0.002,Test_acc:98.8%,Test_loss:0.027,Lr:2.23E-05
Epoch:33,Train_acc:99.9%,Train_loss:0.003,Test_acc:98.8%,Test_loss:0.038,Lr:2.05E-05
Epoch:34,Train_acc:99.9%,Train_loss:0.004,Test_acc:99.6%,Test_loss:0.014,Lr:2.05E-05
Epoch:35,Train_acc:100.0%,Train_loss:0.003,Test_acc:99.2%,Test_loss:0.017,Lr:1.89E-05
Epoch:36,Train_acc:99.9%,Train_loss:0.003,Test_acc:98.3%,Test_loss:0.047,Lr:1.89E-05
Epoch:37,Train_acc:99.8%,Train_loss:0.004,Test_acc:98.3%,Test_loss:0.063,Lr:1.74E-05
Epoch:38,Train_acc:99.8%,Train_loss:0.004,Test_acc:98.3%,Test_loss:0.071,Lr:1.74E-05
Epoch:39,Train_acc:100.0%,Train_loss:0.002,Test_acc:98.3%,Test_loss:0.042,Lr:1.60E-05
Epoch:40,Train_acc:99.6%,Train_loss:0.008,Test_acc:99.6%,Test_loss:0.014,Lr:1.60E-05
Done

8. 结果可视化

epochs_range = range(epochs)

plt.figure(figsize = (12,3))

plt.subplot(1,2,1)
plt.plot(epochs_range,train_acc,label = 'Training Accuracy')
plt.plot(epochs_range,test_acc,label = 'Test Accuracy')
plt.legend(loc = 'lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1,2,2)
plt.plot(epochs_range,train_loss,label = 'Test Accuracy')
plt.plot(epochs_range,test_loss,label = 'Test Loss')
plt.legend(loc = 'lower right')
plt.title('Training and validation Loss')
plt.show()

在这里插入图片描述

9. 模型的保存

# 自定义模型保存
# torch.save(model.'coffee_bean_rec_model.pth') # 保存整个模型

# 自定义模型加载
# model2 = torch.load('coffee_bean_rec_model.pth') 
# model2 = model2.to(device) # 理论上在哪里保存模型,加载模型也会优先在哪里,指定一下确保不会出错
# # vgg16官方模型参数保存
# # 状态字典保存
# torch.save(model.state_dict(),'coffee_bean_rec_model_state_dict.pth') # 仅保存状态字典

# # 加载状态字典到模型
# best_model = vgg16(pretrained = True).to(device) # 定义官方vgg16模型用来加载参数

# for param in best_model.parameters():
#     param.requires_grad = False # 冻结模型参数,这样子在训练的时候只训练最后一层的参数

# best_model.classifier._modules['6'] = nn.Linear(4096,len(classNames)) # 修改vgg16模型中最后一层全连接层,输出目标类别个数
# # best_model = vgg16().to(device) # 重新定义一个模型用来加载参数
# best_model.load_state_dict(torch.load('coffee_bean_rec_model_state_dict.pth')) # 加载状态字典到模型
# 自定义模型保存
# 状态字典保存
torch.save(model.state_dict(),'face_rec_model_state_dict.pth') # 仅保存状态字典

# 加载状态字典到模型
best_model = vgg16().to(device) # 定义官方vgg16模型用来加载参数

best_model.load_state_dict(torch.load('face_rec_model_state_dict.pth')) # 加载状态字典到模型
<All keys matched successfully>

10. 使用训练好的模型进行预测

# 指定路径图片预测
from PIL import Image
import torchvision.transforms as transforms

classes = list(total_data.class_to_idx) # classes = list(total_data.class_to_idx)

def predict_one_image(image_path,model,transform,classes):
    
    test_img = Image.open(image_path).convert('RGB')
    # plt.imshow(test_img) # 展示待预测的图片
    
    test_img = transform(test_img)
    img = test_img.to(device).unsqueeze(0)
    
    model.eval()
    output = model(img)
    print(output) # 观察模型预测结果的输出数据
    
    _,pred = torch.max(output,1)
    pred_class = classes[pred]
    print(f'预测结果是:{pred_class}')
# 预测训练集中的某张照片
predict_one_image(image_path='./data/coffee_bean_recognize/Light/light (1).png',
                 model = model,
                 transform = test_transforms,
                 classes = classes
                 )

tensor([[-31.8651,   8.0218,  20.2537,  -1.1075]], device='cuda:0',
       grad_fn=<AddmmBackward0>)
预测结果是:Light

11. 不同参数模型预测效果测试与记录-自定义模型(待完善)

固定学习率



动态学习率 + Adam

1e-4 测试集准确率99.6%
 Epoch: 1,Train_acc:67.8%,Train_loss:0.680,Test_acc:76.7%,Test_loss:0.539,Lr:7.79E-05
    Epoch: 2,Train_acc:76.4%,Train_loss:0.513,Test_acc:77.9%,Test_loss:0.496,Lr:7.79E-05
    Epoch: 3,Train_acc:76.0%,Train_loss:0.519,Test_acc:84.6%,Test_loss:0.405,Lr:7.16E-05
    Epoch: 4,Train_acc:78.5%,Train_loss:0.458,Test_acc:85.0%,Test_loss:0.309,Lr:7.16E-05
    Epoch: 5,Train_acc:86.0%,Train_loss:0.297,Test_acc:86.7%,Test_loss:0.272,Lr:6.59E-05
    Epoch: 6,Train_acc:91.7%,Train_loss:0.200,Test_acc:90.8%,Test_loss:0.209,Lr:6.59E-05
    Epoch: 7,Train_acc:94.9%,Train_loss:0.126,Test_acc:95.4%,Test_loss:0.112,Lr:6.06E-05
    Epoch: 8,Train_acc:97.5%,Train_loss:0.089,Test_acc:95.8%,Test_loss:0.159,Lr:6.06E-05
    Epoch: 9,Train_acc:96.8%,Train_loss:0.099,Test_acc:95.8%,Test_loss:0.138,Lr:5.58E-05
    Epoch:10,Train_acc:96.9%,Train_loss:0.074,Test_acc:97.9%,Test_loss:0.060,Lr:5.58E-05
    Epoch:11,Train_acc:97.8%,Train_loss:0.065,Test_acc:97.5%,Test_loss:0.064,Lr:5.13E-05
    Epoch:12,Train_acc:98.5%,Train_loss:0.046,Test_acc:97.5%,Test_loss:0.056,Lr:5.13E-05
    Epoch:13,Train_acc:99.1%,Train_loss:0.031,Test_acc:97.5%,Test_loss:0.065,Lr:4.72E-05
    Epoch:14,Train_acc:99.3%,Train_loss:0.024,Test_acc:97.5%,Test_loss:0.058,Lr:4.72E-05
    Epoch:15,Train_acc:99.3%,Train_loss:0.022,Test_acc:96.2%,Test_loss:0.117,Lr:4.34E-05
    Epoch:16,Train_acc:97.8%,Train_loss:0.055,Test_acc:98.3%,Test_loss:0.071,Lr:4.34E-05
    Epoch:17,Train_acc:97.8%,Train_loss:0.057,Test_acc:97.9%,Test_loss:0.036,Lr:4.00E-05
    Epoch:18,Train_acc:99.1%,Train_loss:0.023,Test_acc:97.5%,Test_loss:0.041,Lr:4.00E-05
    Epoch:19,Train_acc:99.1%,Train_loss:0.023,Test_acc:98.3%,Test_loss:0.045,Lr:3.68E-05
    Epoch:20,Train_acc:99.8%,Train_loss:0.010,Test_acc:98.3%,Test_loss:0.066,Lr:3.68E-05
    Epoch:21,Train_acc:99.4%,Train_loss:0.018,Test_acc:98.8%,Test_loss:0.028,Lr:3.38E-05
    Epoch:22,Train_acc:99.3%,Train_loss:0.021,Test_acc:97.9%,Test_loss:0.056,Lr:3.38E-05
    Epoch:23,Train_acc:99.6%,Train_loss:0.010,Test_acc:98.8%,Test_loss:0.030,Lr:3.11E-05
    Epoch:24,Train_acc:99.6%,Train_loss:0.009,Test_acc:98.3%,Test_loss:0.039,Lr:3.11E-05
    Epoch:25,Train_acc:99.5%,Train_loss:0.012,Test_acc:98.8%,Test_loss:0.031,Lr:2.86E-05
    Epoch:26,Train_acc:99.4%,Train_loss:0.011,Test_acc:98.3%,Test_loss:0.040,Lr:2.86E-05
    Epoch:27,Train_acc:98.8%,Train_loss:0.030,Test_acc:96.7%,Test_loss:0.132,Lr:2.63E-05
    Epoch:28,Train_acc:99.6%,Train_loss:0.015,Test_acc:98.8%,Test_loss:0.031,Lr:2.63E-05
    Epoch:29,Train_acc:99.6%,Train_loss:0.012,Test_acc:98.3%,Test_loss:0.031,Lr:2.42E-05
    Epoch:30,Train_acc:99.4%,Train_loss:0.014,Test_acc:98.3%,Test_loss:0.032,Lr:2.42E-05
    Epoch:31,Train_acc:99.9%,Train_loss:0.004,Test_acc:98.8%,Test_loss:0.042,Lr:2.23E-05
    Epoch:32,Train_acc:100.0%,Train_loss:0.002,Test_acc:98.8%,Test_loss:0.027,Lr:2.23E-05
    Epoch:33,Train_acc:99.9%,Train_loss:0.003,Test_acc:98.8%,Test_loss:0.038,Lr:2.05E-05
    Epoch:34,Train_acc:99.9%,Train_loss:0.004,Test_acc:99.6%,Test_loss:0.014,Lr:2.05E-05
    Epoch:35,Train_acc:100.0%,Train_loss:0.003,Test_acc:99.2%,Test_loss:0.017,Lr:1.89E-05
    Epoch:36,Train_acc:99.9%,Train_loss:0.003,Test_acc:98.3%,Test_loss:0.047,Lr:1.89E-05
    Epoch:37,Train_acc:99.8%,Train_loss:0.004,Test_acc:98.3%,Test_loss:0.063,Lr:1.74E-05
    Epoch:38,Train_acc:99.8%,Train_loss:0.004,Test_acc:98.3%,Test_loss:0.071,Lr:1.74E-05
    Epoch:39,Train_acc:100.0%,Train_loss:0.002,Test_acc:98.3%,Test_loss:0.042,Lr:1.60E-05
    Epoch:40,Train_acc:99.6%,Train_loss:0.008,Test_acc:99.6%,Test_loss:0.014,Lr:1.60E-05
    Done

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/591293.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Linux】学习笔记

文章目录 [toc]第一章&#xff1a;基础篇01|课程介绍02|内容综述03|什么是Linux04|Linux的内核版本及常见发行版内核版本发行版本Red Hat Enterprise LinuxFedoraCentOSDebianUbuntu 05|安装VirtualBox虚拟机VirtualBox下载url 06|在虚拟机中安装Linux系统Linux安装镜像下载 07…

020、Python+fastapi,第一个Python项目走向第20步:ubuntu 24.04 docker 安装mysql8、redis(一)

系列文章 pythonvue3fastapiai 学习_浪淘沙jkp的博客-CSDN博客https://blog.csdn.net/jiangkp/category_12623996.html 前言 docker安装起来比较方便&#xff0c;不影响系统整体&#xff0c;和前面虚拟环境有异曲同工之妙&#xff0c;今天把老笔记本T400拿出来装了个ubuntu24…

# 从浅入深 学习 SpringCloud 微服务架构(八)Sentinel(1)

从浅入深 学习 SpringCloud 微服务架构&#xff08;八&#xff09;Sentinel&#xff08;1&#xff09; 一、sentinel&#xff1a;概述 1、前言 – 服务熔断 Hystrix 的替换方案。 1&#xff09;2018年底 Netflix 官方宣布 Hystrix 已经足够稳定&#xff0c;不再积极开发 Hys…

redis中的双写一致性问题

双写一致性问题 1.先删除缓存或者先修改数据库都可能出现脏数据。 2.删除两次缓存&#xff0c;可以在一定程度上降低脏数据的出现。 3.延时是因为数据库一般采用主从分离&#xff0c;读写分离。延迟一会是让主节点把数据同步到从节点。 1.读写锁保证数据的强一致性 因为一般放…

值得推荐的5个免费 PDF 转 Word 转换器分享

最好的免费 PDF 转 Word 转换器专注于两件事&#xff1a;范围和速度。 大多数 PDF 转换工具不限于一种文件类型 - 它们允许您将 PDF 转换为Microsoft Word&#xff0c;以及转换 Excel、Powerpoint 和其他 Office 应用程序&#xff08;并再次转换回来&#xff09;。最好的人很快…

C语言:指针详解(3)

目录 一、字符指针 二、数组指针 1.数组指针的定义 2.数组指针的初始化 3. 二维数组传参的本质 三、函数指针 1.函数指针的创建 2.函数指针的使用 3.有趣的代码(1) 4.有趣的代码(2) 四、typedef关键字 1.typedef的使用方法 2.typedef和#define的区别 五、函数指针…

社交媒体数据恢复:新浪微博

当我们在使用新浪微博时&#xff0c;可能会遇到一些意外情况&#xff0c;如误删微博、账号出现问题等。这时&#xff0c;我们需要进行数据恢复。本文将详细介绍如何在新浪微博中进行数据恢复。 首先&#xff0c;我们需要了解新浪微博的数据恢复功能。根据微博的帮助中心&#…

【智能算法应用】麻雀搜索算法求解非线性方程组问题

目录 1.算法原理2.数学模型3.结果展示4.代码获取 1.算法原理 【智能算法】麻雀搜索算法&#xff08;SSA&#xff09;原理及实现 2.数学模型 非线性方程组为&#xff1a; 2 x 1 − x 2 e − x 1 − x 1 2 x 2 e − x 2 (1) \begin{aligned}&2x_1-x_2e^{-x_1}\\&-…

【Redis面试题】Redis常见的一些高频面试题

分享几个Redis入门级常见面试过程中遇到的题目! 你项目中哪里使用到了redis?可以讲一讲嘛 这个题目无论是大公司还是小公司都经常考,建议大家根据自己的项目做总结 redis的几种基础数据结构 redis为什么那么快&#xff1f; 1.基于内存实现&#xff1a;我们都知道内存读写是…

关于YOLO8学习(三)训练自定义的数据集

前文 关于YOLO8学习&#xff08;一&#xff09;环境搭建&#xff0c;官方检测模型部署到手机 关于YOLO8学习&#xff08;二&#xff09;数据集收集&#xff0c;处理 简介 本文将会讲解&#xff1a; &#xff08;1&#xff09;如何通过PyCharm&#xff0c;进行训练数据&#…

关于Clion开发stm32printf重定向问题简单解决问题方法

title: 关于Clion开发stm32printf重定向问题简单解决问题方法 tags: STM32Clion 参考来源1 这是另一种方法 在printf 重定向的基础上加上 一句 setbuf(stdout,NULL); 参考来源2 自己写的笔记啦

TCP重传机制——快速重传

TCP 有一种快速重传机制&#xff0c;它不以时间为驱动&#xff0c;而是以数据驱动重传。 在上图&#xff0c;发送方发出了 1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5 份数据&#xff1a; 第一份 Seq1 先送到了&#xff0c;于是就 Ack 回 2&#xff1b;结果 Seq2…

【研发管理】产品经理知识体系-产品创新流程

导读&#xff1a;产品创新流程是一个系统性的过程&#xff0c;旨在通过创造和引入新的产品或改进现有产品来满足市场需求、解决用户问题或实现竞争优势。 目录 1、产品创新引论 2、决策基本框架 3、模糊前端 4、产品创新流程模型概论 5、门径管理流程 6、并行工程和集成产…

AC+AP三层组网实验(华为)

一&#xff0c;技术简介 APAC架构是一种常见的无线局域网&#xff08;WLAN&#xff09;组网方式&#xff0c;主要由接入点&#xff08;Access Point&#xff0c;简称AP&#xff09;和接入控制器&#xff08;Access Controller&#xff0c;简称AC&#xff09;组成。 在APAC架构…

Gitea 上传用户签名

在 Gitea 的用户管理部分&#xff0c;有一个 SSH 和 GPG 的选项。 单击这个选项&#xff0c;可以在选项上添加 Key。 Key 的来源 如是 Windows 的用户&#xff0c;可以选择 Kleopatra 这个软件。 通过这个软件生成的 Key 的界面中有一个导出功能。 单击这个导出&#xff0c;…

【Python可视化】pyecharts

Echarts 是一个由百度开源的数据可视化&#xff0c;凭借着良好的交互性&#xff0c;精巧的图表设计&#xff0c;得到了众多开发者的认可。而 Python 是一门富有表达力的语言&#xff0c;很适合用于数据处理。当数据分析遇上数据可视化时&#xff0c;pyecharts 诞生了。 需要安…

three.js入门指南

WebGL和Three.js的概念 什么是WebGL WebGL是基于OpenGL ES 2.0的Web标准&#xff0c;可以通过HTML5 Canvas元素作为DOM接口访问。 也就是WebGL是作为OpenGL的网页端入口。它作为一个底层标准&#xff0c;然后我们可以通过JavaScript代码&#xff0c;在网页上实现三维图形的渲…

恶补《操作系统》4_2——王道学习笔记

4.1_5 文件存储空间管理 1、存储空间的划分与初始化 文件卷&#xff08;逻辑卷&#xff09;的概念目录区与文件区 2、几种管理方法 空闲表法&#xff1a;首位置长度&#xff0c;回收时注意修改空闲链表法&#xff08;空闲盘块链、空闲盘区链&#xff09;位示图法 成组链接法…

连接HiveMQ代理器实现MQTT协议传输

先下载MQTTX: MQTTX: Your All-in-one MQTT Client Toolbox 使用线上免费的MQTTX BROKER:The Free Global Public MQTT Broker | Try Now | EMQ 打开MQTTX,创建连接,点击NEW SUBSCRIPTION,创建一个主题,这里使用test/topic,在下面Json中填写配置好的主题,点击发送测试OK。…

Redis-单机安装

试图从官网注册不了我也不知道什么情况。 网盘自取吧&#xff0c;链接&#xff1a;https://pan.baidu.com/s/1KERBQaH9gCT10AGt9z0_jg?pwdyjen 安装比较简单&#xff0c;照着敲就完了每一步都试过了&#xff0c;先单机安装&#xff0c;后面搭建集群。 1.将安装包放到/usr/…