深度学习每周学习总结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,datasetsimport PIL,random,os,pathlib
import matplotlib.pyplot as plt
import warningswarnings.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 ImageFolderNumber of datapoints: 1200Root 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_sizetrain_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 = 32train_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 Fclass vgg16(nn.Module):def __init__(self):super(vgg16,self).__init__()# 卷积块1self.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)))# 卷积块2self.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)))# 卷积块3self.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)))# 卷积块4self.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)))# 卷积块5self.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 xmodel = 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,792ReLU-2         [-1, 64, 224, 224]               0Conv2d-3         [-1, 64, 224, 224]          36,928ReLU-4         [-1, 64, 224, 224]               0MaxPool2d-5         [-1, 64, 112, 112]               0Conv2d-6        [-1, 128, 112, 112]          73,856ReLU-7        [-1, 128, 112, 112]               0Conv2d-8        [-1, 128, 112, 112]         147,584ReLU-9        [-1, 128, 112, 112]               0MaxPool2d-10          [-1, 128, 56, 56]               0Conv2d-11          [-1, 256, 56, 56]         295,168ReLU-12          [-1, 256, 56, 56]               0Conv2d-13          [-1, 256, 56, 56]         590,080ReLU-14          [-1, 256, 56, 56]               0Conv2d-15          [-1, 256, 56, 56]         590,080ReLU-16          [-1, 256, 56, 56]               0MaxPool2d-17          [-1, 256, 28, 28]               0Conv2d-18          [-1, 512, 28, 28]       1,180,160ReLU-19          [-1, 512, 28, 28]               0Conv2d-20          [-1, 512, 28, 28]       2,359,808ReLU-21          [-1, 512, 28, 28]               0Conv2d-22          [-1, 512, 28, 28]       2,359,808ReLU-23          [-1, 512, 28, 28]               0MaxPool2d-24          [-1, 512, 14, 14]               0Conv2d-25          [-1, 512, 14, 14]       2,359,808ReLU-26          [-1, 512, 14, 14]               0Conv2d-27          [-1, 512, 14, 14]       2,359,808ReLU-28          [-1, 512, 14, 14]               0Conv2d-29          [-1, 512, 14, 14]       2,359,808ReLU-30          [-1, 512, 14, 14]               0MaxPool2d-31            [-1, 512, 7, 7]               0Linear-32                 [-1, 4096]     102,764,544ReLU-33                 [-1, 4096]               0Linear-34                 [-1, 4096]      16,781,312ReLU-35                 [-1, 4096]               0Linear-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,0for 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与losstrain_acc += (pred.argmax(1)==y).type(torch.float).sum().item()train_loss += loss.item()train_acc /= sizetrain_loss /= num_batchesreturn train_acc,train_loss

6. 编写测试函数

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

7. 正式训练

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

在这里插入图片描述

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

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

相关文章

文章解读与仿真程序复现思路——电力自动化设备EI\CSCD\北大核心《电-氢-混氢天然气耦合的城市综合能源系统低碳优化调度》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

Java | Leetcode Java题解之第68题文本左右对齐

题目&#xff1a; 题解&#xff1a; class Solution {private String line(List<String> list,int maxWidth,int totalLength,boolean isLast){StringBuilder sb new StringBuilder();sb.append(list.get(0));if(list.size() 1){String ap " ".repeat(maxW…

二维数组的鞍点(C语言)

一、鞍点解释&#xff1b; 鞍点就是该位置上的元素在该行上最大、在该列上最小&#xff1b; 二、N-S流程图&#xff1b; 三、运行结果&#xff1b; 四、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>int main() {//初始化变量值&#xff…

定制开发AI名片商城AI导购系统:引领营销自动化的新时代

在数字营销日新月异的今天&#xff0c;一个革命性的工具——定制开发AI名片商城AI导购系统&#xff0c;正逐渐崭露头角&#xff0c;成为企业私域运营中的得力助手。它不仅仅是一个营销工具&#xff0c;更是一个拥有强大营销自动化能力和先进算法技术的在线助理&#xff0c;为企…

【neteq】tgcall的调用

G:\CDN\P2P-DEV\Libraries\tg_owt\src\call\call.cc基本是按照原生webrtc的来的:G:\CDN\P2P-DEV\tdesktop-offical\Telegram\ThirdParty\tgcalls\tgcalls\group\GroupInstanceCustomImpl.cpptg对neteq的使用 worker 线程创建call Call的config需要neteqfactory Call::CreateAu…

[1702]java旅游资源网上填报系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 java旅游资源网上填报系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysql…

模式识别作业:颜色算子的三种阈值分割算法

一、引言&#xff1a; 在图像处理中&#xff0c;我们往往需要提取图像的一些关键信息&#xff0c;比如本篇文章的内容——提取颜色&#xff0c;然而当我们需要提取某一种颜色时&#xff0c;无论图像余下的部分如何“丰富多彩”&#xff0c;他们都不再重要&#xff0c;需要被忽…

如何使用SSH密钥克隆仓库

1.创建SSH Key 在用户目录下查看有没有.ssh目录。如果有且该.ssh目录下有id_rsa&#xff08;私钥&#xff09;&#xff0c;和id_rse_pub(公钥)这俩文件&#xff0c;那么这一步就可以跳过。否则使用以下指令创建SSH Key ssh-keygen -t rsa -C "xxxqq.com" "xx…

8.k8s中网络资源service

目录 一、service资源概述 二、service资源类型 1.ClusterIP类型 2.service的nodeport类型 3.service的loadbalancer类型&#xff08;了解即可&#xff09; 4.service的externalname类型&#xff08;了解即可&#xff09; 三、nodeport的端口范围设置和svc的endpoint列表 1.修…

AJAX概述和基本使用

01 【AJAX概述和基本使用】 1.AJAX简介 AJAX 全称为Asynchronous JavaScript And XML&#xff0c;就是异步的JS 和XML 通过AJAX 可以在浏览器中向服务器发送异步请求&#xff0c;最大的优势&#xff1a;无刷新获取数据 AJAX 不是新的编程语言&#xff0c;而是一种将现有的标准…

刷代码随想录有感(53):合并二叉树

题干&#xff1a; 代码&#xff08;递归实现&#xff09;&#xff1a; TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {//前序好理解&#xff0c;直接将树覆盖到另一个上面if(root1 NULL)return root2;//当前遍历节点为空的话就让另一个的值覆盖过来if(root2 NUL…

对命令模式的理解

目录 一、场景1、文本编辑器并不是一个好的例子&#xff0c;设备控制器才是2、设备控制器的demo 二、不用命令模式1、代码2、问题 三、使用命令模式1、代码2、当需求变化时2.1 新增代码2.2 优点 四、进一步思考1、省略对Command的建模可以吗&#xff1f;2、命令模式的价值 一、…

GDPU unity游戏开发 碰撞器与触发器

砰砰叫&#xff0c;谁动了她的奶酪让你的小鹿乱撞了。基于此&#xff0c;亦即碰撞与触发的过程。 碰撞器与触发器的区别 通俗点讲&#xff0c;碰撞器检测碰撞&#xff0c;触发器检测触发&#xff0c;讲了跟没讲似的。碰撞器是用来检测碰撞事件的&#xff0c;在unity中&#xff…

蓝桥杯练习系统(算法训练)ALGO-949 勇士和地雷阵

资源限制 内存限制&#xff1a;256.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 问题描述 勇士们不小心进入了敌人的地雷阵&#xff08;用n行n列的矩阵表示&#xff0c;*表示某个位置埋有地雷&#xff0c;-表示某个…

yolov5-pytorch-Ultralytics训练+预测+报错处理记录

一、前言 玩一段时间大模型&#xff0c;也该回归一下图像识别。本项目用于记录使用基于Ultralytics的yolov5进行目标检测测试。为什么用Ultralytics呢&#xff1f;答案有3 1、其良好的生态&#xff0c;方便我们部署到其它语言和设备上。因此本次测试结论&#xff1a;大坑没有&…

UE5 蓝图入门

基础节点创建&#xff1a; 常量&#xff1a; 按住 1 &#xff0c;点击鼠标左键&#xff0c;创建常量 二维向量&#xff1a; 按住 2 &#xff0c;点击鼠标左键&#xff0c;创建二维向量 三维向量&#xff1a; 按住 3 &#xff0c;点击鼠标左键 乘法&#xff1a; 按住 m 键…

基于node.js+css+html+mysql博客系统

博主介绍&#xff1a; 大家好&#xff0c;本人精通Java、Python、Php、C#、C、C编程语言&#xff0c;同时也熟练掌握微信小程序、Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我有丰富的成品Java、Python、C#毕设项目经验&#xff0c;能够为学生提供各类…

Vue进阶之Vue项目实战(一)

Vue项目实战 项目搭建初始化eslint版本约束版本约束eslint配置 stylelintcspellcz-githusky给拦截举个例子 zx 项目搭建 node版本&#xff1a;20.11.1 pnpm版本&#xff1a;9.0.4 初始化 vue3最新的脚手架 pnpm create vite byelide-demo --template vue-ts pnpm i pnpm dev…

MIPS32 指令架构

指令格式 R 类型 说明&#xff1a; 用于寄存器和寄存器操作 参数说明: Op: 指令操作码Rs: 第一个源操作数寄存器号&#xff0c;参与运算使用Rd: 目的操作数寄存器号&#xff0c;保存结果使用Shamt: 位偏移量&#xff0c;仅在位移指令使用&#xff0c;在此直接置0Func: 指令函…

深入 Django 模型层:数据库设计与 ORM 实践指南

title: 深入 Django 模型层&#xff1a;数据库设计与 ORM 实践指南 date: 2024/5/3 18:25:33 updated: 2024/5/3 18:25:33 categories: 后端开发 tags: Django ORM模型设计数据库关系性能优化数据安全查询操作模型继承 第一章&#xff1a;引言 Django是一个基于Python的开源…