当前位置: 首页 > news >正文

深度学习之图像分类(二)

前言

  文章主要是通过实战项目——食品分类来理解分类项目的整体流程。除此之外,还需要对半监督学习,迁移学习,数据增广,Adam和AdamW进行了解。

数据增广

  图片增广(Image Data Augmentation)是深度学习中一种重要的技术,通过有目的地对图像进行变换或处理,增加数据集的多样性,从而提升模型的泛化能力和鲁棒性。

图片增广的主要方法

图片增广的方法可以分为以下几类:

1. 几何变换

几何变换是最常见的图片增广方法,包括:

  • 旋转(Rotation):随机旋转图像一定角度。

  • 翻转(Flip):水平翻转(RandomHorizontalFlip)和垂直翻转(RandomVerticalFlip),但需根据数据集的特性选择是否适用。

  • 裁剪(Crop):随机裁剪图像的一部分并调整大小(RandomResizedCrop),可以模拟不同视角。

  • 缩放(Resize/Scale):改变图像的尺寸。

  • 平移(Translate):将图像在水平或垂直方向上移动。

2. 颜色变换

颜色变换通过调整图像的颜色属性来增加多样性:

  • 亮度、对比度、饱和度调整:通过ColorJitter随机改变图像的亮度、对比度和饱和度。

  • 色调分离(Posterize):减少图像的色彩层次。

  • 高斯模糊(Gaussian Blur):对图像进行模糊处理。

3. 噪声注入

向图像中添加噪声,模拟不同成像质量的图像:

  • 高斯噪声、椒盐噪声:增强模型对噪声的鲁棒性。

  • 对抗样本(Adversarial Examples):通过对抗训练增强模型的鲁棒性

4. 图像合成

通过混合多幅图像生成新的训练样本:

  • Mixup:将两幅图像的像素值和标签向量进行线性加权。

  • CutMix:将一幅图像的部分区域替换为另一幅图像的区域,并更新标签。

  • AugMix:对同一幅图像进行多次不同的混合变换,再加权合成。

5. 基于学习的增广策略

  • AutoAugment:使用强化学习搜索最优的增广策略。

  • RandAugment:随机选择多种变换方法并调整强度。

图片增广的作用

图片增广通过增加数据的多样性,降低模型对特定属性的依赖,从而提升模型的泛化能力。例如,通过随机裁剪和旋转,模型可以学习到目标物体在不同位置和角度的特征,减少对物体位置的依赖。

实现示例

以下是使用PyTorch实现图片增广的代码示例:

test_transform = transforms.Compose([transforms.ToTensor(),
])              # 测试集只需要转为张量train_transform = transforms.Compose([transforms.ToPILImage(),transforms.RandomResizedCrop(HW),transforms.RandomHorizontalFlip(),autoaugment.AutoAugment(),transforms.ToTensor(),# transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])
])                   # 训练集需要做各种变换。   

应用

        if mode == 'test':x = self._readfile(imgPaths,label=False)self.transform = test_transform                         #从文件读数据,测试机和无标签数据没有标签, trans方式也不一样elif mode == 'train':x, y =self._readfile(imgPaths,label=True)self.transform = train_transformelif mode == 'val':x, y =self._readfile(imgPaths,label=True)self.transform = test_transformelif mode == 'train_unl':x = self._readfile(imgPaths,label=False)self.transform = train_transform

通过这些方法,可以有效地扩充数据集,提升模型性能。

但是对于不同的数据部分需要区别处理

图形增广和数据增广

  • 数据增广是一个通用的概念,适用于所有类型的数据,目的是通过增加数据的多样性来提升模型的泛化能力。

  • 图片增广是数据增广在图像数据上的具体应用,专注于通过几何变换、颜色变换等方法扩充图像数据集,以提升计算机视觉模型的性能。

Adam和AdamW

Adam和AdamW都是深度学习中常用的优化算法,用于训练神经网络。它们都是基于梯度下降法的变体,旨在加速训练过程并提高模型性能。

1. 梯度的改变

  • 梯度:在深度学习中,梯度是指损失函数相对于模型参数的导数,它指示了参数应该如何调整以最小化损失。

  • Adam和AdamW:这两种算法都利用梯度的一阶矩(均值)和二阶矩(未中心化的方差)来动态调整每个参数的学习率,从而实现更有效的参数更新。

2. 学习率的改变

  • 学习率:学习率是梯度下降法中的一个关键超参数,它决定了在每次迭代中参数更新的步长。

  • 动态调整:Adam和AdamW算法通过计算梯度的指数加权平均来动态调整学习率,这有助于在训练过程中自适应地调整步长,从而加速收敛并避免陷入局部最小值。

图表解释

学习率并不是恒定的,而是随着训练过程的进行而变化。这种变化可能是由于:

  • 学习率衰减:随着训练的进行,学习率逐渐减小,以帮助模型在接近最优解时更精细地调整参数。

  • 自适应调整:Adam和AdamW算法根据梯度信息自适应地调整学习率,以优化训练过程。

Adam和AdamW的区别

  • AdamW:是Adam算法的一个变种,它在更新参数时对权重衰减(weight decay)进行了修正。在标准的Adam算法中,权重衰减是加在梯度上的,这可能导致在进行梯度更新时对权重衰减的影响被放大。AdamW通过将权重衰减直接应用于参数更新,从而解决了这个问题,使得权重衰减的效果更加稳定和一致。

总的来说,这张图强调了Adam和AdamW算法在训练深度学习模型时对梯度和学习率的动态调整,这是它们能够有效优化模型性能的关键因素。

迁移学习

  核心思想是利用在一个任务上已经学到的知识来帮助解决另一个相关但不同的任务。

  可以显著减少模型训练所需的数据量和计算资源,同时提高模型在新任务上的性能。够显著加快模型训练速度并提高其泛化能力。

  迁移学习在许多实际应用中都得到了广泛使用,特别是在数据不足或训练成本较高的场景下。

如何将一个在大规模数据集(如ImageNet)上预训练的模型应用于特定任务(如医学图像分类)?

迁移学习的过程

  1. 预训练模型

    • 使用ImageNet数据集预训练一个深度学习模型,该数据集包含1000个类别的图像。

    • 预训练模型在大量图像上学习了丰富的特征表示。

  2. 迁移学习

    • 将预训练模型应用于特定任务,如医学图像分类。

    • 由于预训练模型已经学习了通用的特征表示,因此可以迁移到新任务上,而无需从头开始训练。

  3. 微调(Fine-tuning)

    • 在新任务的数据集上对预训练模型进行微调。

    • 通过在特定任务的数据集上继续训练,模型可以学习到与新任务相关的特定特征。

    • 微调通常涉及调整模型的最后几层,以适应新任务的类别数。

  4. 特征提取器(Feature Extractor)

    • 预训练模型的前面几层可以作为特征提取器,用于提取图像的高级特征。

    • 这些特征可以用于新任务的分类器。

  5. 分类器(Classifier)

    • 在特征提取器的输出上添加一个新的分类器层,用于新任务的分类。

    • 分类器层的输出对应于新任务的类别数(如11类)。

  6. 应用示例

    • 图中展示了一个医学图像分类任务,需要区分正常(Normal)和异常(Abnormal)两类。

    • 使用迁移学习可以显著减少训练数据的需求,并提高分类的准确性。

  迁移学习和微调技术在实际应用中非常广泛,特别是在数据稀缺或计算资源有限的情况下。

好的,我来更详细地解释一下微调和特征提取器的概念。

微调(Fine-tuning)

微调是迁移学习中的一个重要步骤,它的目标是让预训练模型更好地适应新任务。具体来说,微调包括以下几个关键点:

  1. 保留预训练模型的前面几层

    • 预训练模型已经在大规模数据集(如ImageNet)上训练过,前面的层(如卷积层)已经学习到了丰富的通用特征(如边缘、纹理等)。

    • 这些通用特征对于新任务也是有用的,因此我们保留这些层,不从头开始训练。

  2. 替换或调整最后几层

    • 预训练模型的最后一层通常是分类层,其输出类别数与训练数据集的类别数相匹配(如1000类)。

    • 对于新任务,类别数可能不同(如11类),因此我们需要替换或调整最后几层,以适应新任务的类别数。

  3. 在新任务数据集上继续训练

    • 使用新任务的数据集对模型进行继续训练,让模型学习到与新任务相关的特定特征。

    • 这个过程称为微调,因为它是在预训练模型的基础上进行的,而不是从头开始训练。

  4. 冻结部分层

    • 在微调过程中,可以冻结预训练模型的前面几层,只训练最后几层。

    • 这样可以避免破坏前面层学到的通用特征,同时让模型专注于学习新任务的特定特征。

特征提取器(Feature Extractor)

特征提取器是迁移学习中的一个关键概念,它指的是利用预训练模型的前面几层来提取图像的高级特征。具体来说:

  1. 提取通用特征

    • 预训练模型的前面几层(如卷积层)已经学习到了丰富的通用特征,这些特征对于多种图像任务都是有用的。

    • 通过将这些层作为特征提取器,我们可以复用这些通用特征,而无需从头开始学习。

  2. 减少训练数据需求

    • 由于特征提取器已经提供了高级特征,因此新任务的分类器只需要学习如何组合这些特征来进行分类。

    • 这大大减少了新任务所需的训练数据量,因为分类器更容易从少量数据中学习到有效的模式。

  3. 提高模型性能

    • 使用特征提取器可以提高新任务模型的性能,因为它利用了预训练模型在大规模数据集上学到的知识。

    • 这可以帮助新任务模型更快地收敛,并在测试数据上取得更好的结果。

  4. 灵活性

    • 特征提取器可以与不同的分类器结合使用,以适应不同的任务需求。

    • 例如,可以添加一个全连接层或支持向量机作为分类器,以适应新任务的类别数和输出格式。

总结

微调和特征提取器是迁移学习中的两个关键步骤,它们利用预训练模型的知识来加速新任务的学习过程。

  • 微调:在新任务的数据集上继续训练预训练模型,调整最后几层以适应新任务的类别数。

  • 特征提取器:利用预训练模型的前面几层提取图像的高级特征,为新任务的分类器提供有用的输入。

通过结合微调和特征提取器,迁移学习可以在减少训练数据需求的同时,提高新任务模型的性能和泛化能力。

半监督学习

半监督学习是什么?

  半监督学习是一种机器学习方法,它结合了有标签数据(已经知道答案的数据)和无标签数据(没有答案,需要预测的数据)来训练模型。

为什么使用半监督学习?

  • 数据标签成本高:在很多情况下,获取大量有标签数据的成本非常高,因为需要专家来标记。

  • 利用无标签数据:半监督学习允许我们利用大量的无标签数据来提高模型的性能,而不需要为这些数据支付标签费用。

  • 提高模型泛化能力:通过结合有标签和无标签数据,模型可以更好地理解和泛化新数据。

流程

  1. 有标签数据

    • 这些是已经知道答案的数据,比如一些图片已经被标记为“猫”或“狗”。

    • 这些数据用来训练模型,让模型学习到正确的分类方式。

  2. 无标签数据

    • 这些是没有标记的数据,我们不知道这些图片是什么。

    • 这些数据用来帮助模型做出预测,即使我们不知道正确答案。

  3. 模型

    • 模型就像是一个学生,它通过学习有标签数据来理解如何分类。

    • 然后,模型尝试用学到的知识来预测无标签数据的答案。

  4. 预测值

    • 模型对无标签数据做出的预测,比如它可能会说某张图片是“猫”。

    • 这些预测可能不是100%准确,但可以帮助模型更好地学习。

  5. 超过一定可信度

    • 当模型对某个预测非常有信心时(比如它99%确定某张图片是“猫”),这个预测就会被用来进一步训练模型。

    • 这样,模型就可以从自己的预测中学习,不断提高自己的准确性。

总结

半监督学习是一种聪明的方法,它让模型通过学习少量的有标签数据和大量的无标签数据来提高自己的预测能力。这种方法特别适合在标签数据稀缺但无标签数据丰富的情况下使用。

冻结权重(Freezing Weights)

核心概念

在 PyTorch 中,每个模型参数(如卷积层的权重、全连接层的权重等)都有一个属性叫做 requires_grad。这个属性决定了该参数是否会在训练过程中被更新。

  • requires_grad=True:表示该参数在训练时会计算梯度,并根据梯度更新权重。

  • requires_grad=False:表示该参数在训练时不会计算梯度,权重保持不变。

为什么需要冻结权重?

冻结权重的主要目的是保留预训练模型的特征提取能力。预训练模型(如在 ImageNet 上训练的 ResNet)已经学习到了通用的图像特征(如边缘、纹理等)。这些特征对于大多数图像任务都是有用的。

如果你在自己的任务上重新训练整个模型,可能会破坏这些通用特征,尤其是当你的数据量较小时。因此,冻结权重可以确保这些特征保持不变,只对模型的最后一部分(如分类层)进行训练。

类比

想象你有一辆已经调校好的赛车,它在各种赛道上都表现出色。现在你想要让它在新的赛道上跑得更快,但你不想重新调整整个赛车的引擎和底盘(因为它们已经很完美了)。你只需要调整赛车的轮胎(分类层),以适应新的赛道。冻结权重就像是保留了赛车的引擎和底盘,只调整轮胎。

代码示例

model = models.resnet18(pretrained=True)  # 加载预训练的 ResNet18# 冻结所有参数
for param in model.parameters():param.requires_grad = False# 替换最后一层(分类层),并允许其更新
num_ftrs = model.fc.in_features
model.fc = nn.Linear(num_ftrs, num_classes)  # 替换为新的分类层

线性探测(Linear Probing)

核心概念

线性探测是一种特殊的迁移学习方法,它的核心思想是:

  1. 冻结特征提取部分:保留预训练模型的特征提取层(如卷积层),不更新这些层的权重。

  2. 仅训练分类头:只训练模型的最后一层(分类层),以适应新的任务。

为什么使用线性探测?

线性探测的主要目的是快速评估预训练模型的特征提取能力。通过只训练分类层,你可以快速了解预训练模型的特征是否适用于你的任务,而不需要重新训练整个模型。

类比

想象你有一台已经调试好的相机,它拍出的照片质量很高。现在你想要用这些照片来训练一个图像分类器。你不需要重新调整相机的镜头(特征提取层),只需要调整照片的标签(分类层)。线性探测就像是直接使用相机拍出的照片,只训练标签部分。

代码示例

def set_parameter_requires_grad(model, linear_probing):if linear_probing:for param in model.parameters():param.requires_grad = False  # 冻结所有参数model = models.resnet18(pretrained=True)  # 加载预训练的 ResNet18
set_parameter_requires_grad(model, linear_probing=True)  # 冻结特征提取层# 替换最后一层(分类层),并允许其更新
num_ftrs = model.fc.in_features
model.fc = nn.Linear(num_ftrs, num_classes)  # 替换为新的分类层

冻结权重与线性探测的关系

线性探测是冻结权重的一个应用场景。具体来说:

  • 冻结权重:是 PyTorch 中的一个技术手段,用于控制哪些参数在训练时更新。

  • 线性探测:是一种迁移学习策略,通过冻结特征提取层,只训练分类层,快速适应新任务。


 为什么这些方法有用?

  1. 节省计算资源:重新训练整个模型需要大量的计算资源和时间,尤其是对于大型模型(如 ResNet、VGG 等)。通过冻结权重,你可以只训练一小部分参数,大大减少计算量。

  2. 保留通用特征:预训练模型的特征提取层已经学习到了通用的图像特征,这些特征对大多数任务都是有用的。冻结这些层可以保留这些特征,避免因过拟合而破坏它们。

  3. 快速适应新任务:线性探测允许你在短时间内评估预训练模型的特征是否适用于你的任务,而不需要重新训练整个模型。


实际应用中的例子

假设你有一个小规模的数据集(如 1000 张图像),目标是训练一个图像分类器。你可以选择以下几种策略:

  1. 从头开始训练:训练一个全新的模型,但需要大量的数据和计算资源。

  2. 迁移学习:加载一个预训练的模型,冻结所有层,只训练最后一层(分类层)。这可以快速适应新任务。

  3. 线性探测:加载一个预训练的模型,冻结特征提取层,只训练分类层。这可以快速评估模型的特征是否有效。


总结

  1. 冻结权重:通过设置 requires_grad=False,保留预训练模型的特征提取能力,只训练需要的部分。

  2. 线性探测:冻结特征提取层,只训练分类层,快速适应新任务。

  3. 类比:冻结权重就像是保留赛车的引擎,只调整轮胎;线性探测就像是直接使用相机拍出的照片,只训练标签部分。

迁移学习与线性探测的关系

  • 共同点

    • 都利用预训练模型:两者都从预训练模型开始,利用其在大规模数据集上学习到的特征。

    • 都替换分类层:两者都替换预训练模型的最后一层,以适应新的任务。

  • 不同点

    • 训练范围

      • 迁移学习:对整个模型(包括特征提取层和分类层)进行微调。

      • 线性探测:只训练分类层,冻结特征提取层。

    • 目标

      • 迁移学习:通过微调整个模型,使其更好地适应新任务。

      • 线性探测:快速评估预训练特征的有效性,避免重新训练整个模型。


为什么需要这两种方法?

  • 迁移学习

    • 适用场景:当你有足够的数据和计算资源时,可以对整个模型进行微调,以获得更好的性能。

    • 优点:可以充分利用预训练模型的特征,并根据新任务进行调整。

  • 线性探测

    • 适用场景:当你数据量较小或计算资源有限时,可以快速评估预训练模型的特征是否有效。

    • 优点:节省时间和计算资源,快速了解模型的适应性。


总结

  • 迁移学习

    • 操作:加载预训练模型,替换分类层,微调整个模型。

    • 目标:快速适应新任务,提高性能。

  • 线性探测

    • 操作:加载预训练模型,冻结特征提取层,只训练分类层。

    • 目标:快速评估特征的有效性,减少计算资源。

关系

  • 线性探测是迁移学习的一种特殊情况,专注于快速评估预训练特征的有效性。

  • 迁移学习更通用,可以对整个模型进行微调,以获得更好的性能。

随机种子

随机种子(Random Seed)是一个用于初始化随机数生成器(Random Number Generator, RNG)的值。它在深度学习和机器学习中非常重要,因为它可以确保每次运行代码时的随机行为是完全一致的。这在科学研究和工程实践中非常有用,因为可重复性是验证实验结果的关键。

 

随机种子的作用

  1. 确保可重复性

    • 深度学习中涉及大量的随机操作,例如权重初始化、数据增强、随机采样等。如果每次运行代码时这些随机操作的结果都不同,那么实验结果也会不同,这使得实验难以复现。

    • 通过设置随机种子,可以确保每次运行代码时的随机行为是一致的,从而保证实验结果的可重复性。

  2. 调试和验证

    • 在开发和调试模型时,能够复现问题是非常重要的。如果每次运行代码的结果都不同,很难确定问题的根源。

    • 通过设置随机种子,可以确保每次运行代码时的行为一致,便于调试和验证。

  3. 科学研究

    • 在科学研究中,实验结果的可重复性是验证研究有效性的关键。通过设置随机种子,可以确保其他研究者能够复现你的实验结果。

 

为什么随机种子不随机?

随机种子本身是一个固定的值,但它用于初始化随机数生成器,从而生成一系列随机数。虽然种子是固定的,但生成的随机数序列是确定的。这意味着,每次使用相同的种子时,生成的随机数序列是相同的。这就是为什么随机种子不随机的原因。

总结

随机种子是一个固定的值,用于初始化随机数生成器,从而生成一系列确定的随机数。通过设置随机种子,可以确保每次运行代码时的随机行为一致,从而保证实验结果的可重复性。虽然种子本身是固定的,但它生成的随机数序列是确定的,这就是为什么随机种子不随机的原因。

模型训练流程

逻辑链:处理数据集-> 定义模型 -> 训练模型 ->评估

处理数据集

import numpy as np
from torch.utils.data import Dataset,DataLoader
import torch
from sklearn.model_selection import train_test_split
import os
import cv2
from torchvision.transforms import transforms,autoaugment
from tqdm import tqdm
import random
from PIL import Image
import matplotlib.pyplot as pltHW = 224
imagenet_norm = [[0.485, 0.456, 0.406], [0.229, 0.224, 0.225]]test_transform = transforms.Compose([transforms.ToTensor(),
])              # 测试集只需要转为张量train_transform = transforms.Compose([transforms.ToPILImage(),transforms.RandomResizedCrop(HW),transforms.RandomHorizontalFlip(),autoaugment.AutoAugment(),transforms.ToTensor(),# transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])
])                   # 训练集需要做各种变换。class foodDataset(Dataset):         #数据集三要素: init , getitem , lendef __init__(self, path, mode):y = Noneself.transform = Noneself.mode = modepathDict = {'train': 'training/labeled','train_unl': 'training/unlabeled','val': 'validation','test': 'testing'}imgPaths = path + '/' + pathDict[mode]                       # 定义路径if mode == 'test':x = self._readfile(imgPaths, label=False)self.transform = test_transform                         #从文件读数据,测试机和无标签数据没有标签, trans方式也不一样elif mode == 'train':x, y =self._readfile(imgPaths, label=True)self.transform = train_transformelif mode == 'val':x, y =self._readfile(imgPaths, label=True)self.transform = test_transformelif mode == 'train_unl':x = self._readfile(imgPaths, label=False)self.transform = train_transformif y is not None:                                    # 注意, 分类的标签必须转换为长整型: int64.y = torch.LongTensor(y)self.x, self.y = x, ydef __getitem__(self, index):                        # getitem 用于根据标签取数据orix = self.x[index]                              # 取index的图片if self.transform == None:xT = torch.tensor(orix).float()else:xT = self.transform(orix)                     # 如果规定了transformer, 则需要transfif self.y is not None:                       # 有标签, 则需要返回标签。 这里额外返回了原图, 方便后面画图。y = self.y[index]return xT, y, orixelse:return xT, orixdef __len__(self):                      # len函数 负责返回长度。return len(self.x)class noLabDataset(Dataset):def __init__(self,dataloader, model, device, thres=0.85):super(noLabDataset, self).__init__()self.model = model      #模型也要传入进来self.device = deviceself.thres = thres      #这里置信度阈值 我设置的 0.99x, y = self._model_pred(dataloader)        #核心, 获得新的训练数据if x == []:                            # 如果没有, 就不启用这个数据集self.flag = Falseelse:self.flag = Trueself.x = np.array(x)self.y = torch.LongTensor(y)# self.x = np.concatenate((np.array(x), train_dataset.x),axis=0)# self.y = torch.cat(((torch.LongTensor(y),train_dataset.y)),dim=0)self.transformers = train_transformdef _model_pred(self, dataloader):model = self.modeldevice = self.devicethres = self.threspred_probs = []labels = []x = []y = []with torch.no_grad():                                  # 不训练, 要关掉梯度for data in dataloader:                            # 取数据imgs = data[0].to(device)pred = model(imgs)                              #预测soft = torch.nn.Softmax(dim=1)             #softmax 可以返回一个概率分布pred_p = soft(pred)pred_max, preds = pred_p.max(1)          #得到最大值 ,和最大值的位置 。 就是置信度和标签。pred_probs.extend(pred_max.cpu().numpy().tolist())labels.extend(preds.cpu().numpy().tolist())        #把置信度和标签装起来for index, prob in enumerate(pred_probs):if prob > thres:                                  #如果置信度超过阈值, 就转化为可信的训练数据x.append(dataloader.dataset[index][1])y.append(labels[index])return x, ydef __getitem__(self, index):                          # getitem 和lenx = self.x[index]x= self.transformers(x)y = self.y[index]return x, ydef __len__(self):return len(self.x)def get_semi_loader(dataloader,model, device, thres):semi_set = noLabDataset(dataloader, model, device, thres)if semi_set.flag:                                                    #不可用时返回空dataloader = DataLoader(semi_set, batch_size=dataloader.batch_size,shuffle=True)return dataloaderelse:return Nonedef getDataLoader(path, mode, batchSize):assert mode in ['train', 'train_unl', 'val', 'test']dataset = foodDataset(path, mode)if mode in ['test','train_unl']:shuffle = Falseelse:shuffle = Trueloader = DataLoader(dataset,batchSize,shuffle=shuffle)                      #装入loaderreturn loaderdef samplePlot(dataset, isloader=True, isbat=False,ori=None):           #画图, 此函数不需要掌握。if isloader:dataset = dataset.datasetrows = 3cols = 3num = rows*cols# if isbat:#     dataset = dataset * 225datalen = len(dataset)randomNum = []while len(randomNum) < num:temp = random.randint(0,datalen-1)if temp not in randomNum:randomNum.append(temp)fig, axs = plt.subplots(nrows=rows,ncols=cols,squeeze=False)index = 0for i in range(rows):for j in range(cols):ax = axs[i, j]if isbat:ax.imshow(np.array(dataset[randomNum[index]].cpu().permute(1,2,0)))else:ax.imshow(np.array(dataset[randomNum[index]][0].cpu().permute(1,2,0)))index += 1ax.set(xticklabels=[], yticklabels=[], xticks=[], yticks=[])plt.show()plt.tight_layout()if ori != None:fig2, axs2 = plt.subplots(nrows=rows,ncols=cols,squeeze=False)index = 0for i in range(rows):for j in range(cols):ax = axs2[i, j]if isbat:ax.imshow(np.array(dataset[randomNum[index]][-1]))else:ax.imshow(np.array(dataset[randomNum[index]][-1]))index += 1ax.set(xticklabels=[], yticklabels=[], xticks=[], yticks=[])plt.show()plt.tight_layout()if __name__ == '__main__':   #运行的模块,  如果你运行的模块是当前模块print("你运行的是data.py文件")filepath = '../food-11_sample'train_loader = getDataLoader(filepath, 'train', 8)for i in range(3):samplePlot(train_loader,True,isbat=False,ori=True)val_loader = getDataLoader(filepath, 'val', 8)for i in range(100):samplePlot(val_loader,True,isbat=False,ori=True)##########################

重点

  1. 数据预处理和增强

    • 为什么需要数据增强?

    • torchvision.transforms 的常用方法RandomResizedCropRandomHorizontalFlipAutoAugment 等。

    • 归一化的作用Normalize

  2. 自定义数据集类

    • Dataset 类的结构__init____getitem____len__ 方法。

    • 不同模式的数据加载:训练集、验证集、测试集和未标记数据集。

    • 数据增强的应用:在 __getitem__ 方法中应用 train_transformtest_transform

  3. 半监督学习的数据处理

    • 伪标签生成的逻辑:模型预测、置信度筛选。

    • noLabDataset 类的作用:生成伪标签数据集。

    • 动态更新数据加载器get_semi_loader 函数。

  4. 数据加载器

    • DataLoader 的作用:批量加载数据。

    • 如何创建数据加载器getDataLoader 函数。

    • shuffle 参数的作用:是否打乱数据。

  5. 数据可视化

    • samplePlot 函数的作用:随机选择样本并可视化。

    • 如何使用 matplotlib 绘制图像imshow 方法。

好的,我将对第2、3、4部分进行详细补充,帮助你更深入地理解每个部分的实现和逻辑。


1. 自定义数据集类 foodDataset

详细解析

foodDataset 是一个自定义的 Dataset 类,用于加载和处理食物分类数据集。它支持四种模式:训练集(train)、验证集(val)、测试集(test)和未标记数据集(train_unl)。


1.1 数据集类的结构
  • __init__ 方法

    • 初始化数据集,加载图像路径和标签。

    • 根据模式选择不同的预处理和增强方法。

    • path:数据集的根路径。

    • mode:数据集的模式(trainvaltesttrain_unl)。

    • transform:根据模式选择不同的数据增强方法。

  • __getitem__ 方法

    • 根据索引获取单个样本及其标签。

    • 如果有标签,返回预处理后的图像和标签;如果没有标签,只返回预处理后的图像。

    • orix:原始图像。

    • xT:预处理后的图像。

    • y:标签(如果有)。

  • __len__ 方法

    • 返回数据集的长度。


1.2 数据加载逻辑
def __init__(self, path, mode):y = Noneself.transform = Noneself.mode = modepathDict = {'train': 'training/labeled','train_unl': 'training/unlabeled','val': 'validation','test': 'testing'}imgPaths = path + '/' + pathDict[mode]  # 定义路径if mode == 'test':x = self._readfile(imgPaths, label=False)self.transform = test_transformelif mode == 'train':x, y = self._readfile(imgPaths, label=True)self.transform = train_transformelif mode == 'val':x, y = self._readfile(imgPaths, label=True)self.transform = test_transformelif mode == 'train_unl':x = self._readfile(imgPaths, label=False)self.transform = train_transformif y is not None:y = torch.LongTensor(y)self.x, self.y = x, y
  • _readfile 方法

    • 从文件系统中加载图像和标签。

    • 如果是测试集或未标记数据集,没有标签。

    • 如果是训练集或验证集,加载标签并将其转换为长整型(torch.LongTensor)。

  • xy

    • x:图像数据。

    • y:标签数据(如果有)。


1.3 数据预处理和增强
def __getitem__(self, index):orix = self.x[index]  # 取index的图片if self.transform is None:xT = torch.tensor(orix).float()else:xT = self.transform(orix)  # 如果规定了transformer,则需要transfif self.y is not None:  # 有标签,则需要返回标签y = self.y[index]return xT, y, orixelse:return xT, orix
  • transform

    • 根据模式选择不同的预处理和增强方法。

    • 训练集使用 train_transform,包含随机裁剪、水平翻转和自动增强。

    • 测试集和验证集使用 test_transform,仅将图像转换为张量。


2. 半监督学习的数据处理 noLabDataset

详细解析

noLabDataset 是一个自定义的 Dataset 类,用于从未标记数据中生成伪标签,并将其作为可信的训练数据。


2.1 数据集类的结构
class noLabDataset(Dataset):def __init__(self, dataloader, model, device, thres=0.85):...def _model_pred(self, dataloader):...def __getitem__(self, index):...def __len__(self):...
  • __init__ 方法

    • 初始化数据集,使用预训练模型对未标记数据进行预测。

    • 根据置信度阈值筛选出高置信度的样本及其伪标签。

    • dataloader:未标记数据的数据加载器。

    • model:预训练模型。

    • device:运行设备(CPU 或 GPU)。

    • thres:置信度阈值。

  • _model_pred 方法

    • 使用预训练模型对未标记数据进行预测。

    • 根据置信度阈值筛选出高置信度的样本及其伪标签。

  • __getitem__ 方法

    • 根据索引获取单个样本及其伪标签。

    • 应用数据增强。

  • __len__ 方法

    • 返回数据集的长度。

主要功能是从未标记数据中生成高置信度的伪标签,并将这些伪标签数据作为可信的训练数据。具体步骤如下:

  1. 使用预训练模型对未标记数据进行预测。

  2. 计算每个样本的最大预测概率(置信度)和对应的标签。

  3. 根据置信度阈值筛选出高置信度的样本。

  4. 将筛选出的样本及其伪标签存储起来,用于后续的半监督学习

 


2.2 伪标签生成逻辑
def _model_pred(self, dataloader):model = self.modeldevice = self.devicethres = self.threspred_probs = []labels = []x = []y = []with torch.no_grad():  # 不训练,要关掉梯度for data in dataloader:  # 取数据imgs = data[0].to(device)pred = model(imgs)  # 预测soft = torch.nn.Softmax(dim=1)  # softmax 可以返回一个概率分布pred_p = soft(pred)pred_max, preds = pred_p.max(1)  # 得到最大值,和最大值的位置。即置信度和标签。pred_probs.extend(pred_max.cpu().numpy().tolist())labels.extend(preds.cpu().numpy().tolist())  # 把置信度和标签装起来for index, prob in enumerate(pred_probs):if prob > thres:  # 如果置信度超过阈值,就转化为可信的训练数据x.append(dataloader.dataset[index][1])y.append(labels[index])return x, y

 原始未标注数据 → 分批次加载 → 模型预测 → Softmax归一化 → 提取置信度

  • Softmax

    • 将模型的预测结果(logits)转换为概率分布。

  • pred_maxpreds

    • pred_max:每个样本的最大预测概率(置信度)。

    • preds:每个样本的预测标签。

  • 置信度筛选

    • 如果某个样本的置信度超过阈值,则将其作为可信的训练数据。


2.3 动态更新数据加载器
def get_semi_loader(dataloader, model, device, thres):semi_set = noLabDataset(dataloader, model, device, thres)if semi_set.flag:  # 不可用时返回空dataloader = DataLoader(semi_set, batch_size=dataloader.batch_size, shuffle=True)return dataloaderelse:return None
  • semi_set.flag

    • 如果生成了有效的伪标签数据,则 flag=True,否则 flag=False

  • DataLoader

    • 根据生成的伪标签数据创建新的数据加载器。


3. 数据加载器 getDataLoader

详细解析

getDataLoader 函数用于创建数据加载器,支持训练集、验证集、测试集和未标记数据集。


3.1 数据加载器的结构
def getDataLoader(path, mode, batchSize):assert mode in ['train', 'train_unl', 'val', 'test']dataset = foodDataset(path, mode)if mode in ['test', 'train_unl']:shuffle = Falseelse:shuffle = Trueloader = DataLoader(dataset, batchSize, shuffle=shuffle)return loader
  • assert

    • 确保模式是有效的(trainvaltesttrain_unl)。

  • foodDataset

    • 根据路径和模式创建自定义数据集。

  • shuffle

    • 训练集和验证集通常需要打乱数据,以避免模型对数据顺序产生依赖。

    • 测试集和未标记数据集不需要打乱。

  • DataLoader

    • 创建数据加载器,支持

定义模型

import torch
import torch.nn as nn
import numpy as np
from timm.models.vision_transformer import PatchEmbed, Block
import torchvision.models as modelsdef set_parameter_requires_grad(model, linear_probing):if linear_probing:for param in model.parameters():param.requires_grad = False                             # 一个参数的requires_grad设为false, 则训练时就会不更新class MyModel(nn.Module):                  #自己的模型def __init__(self,numclass = 2):super(MyModel, self).__init__()self.layer0 = nn.Sequential(nn.Conv2d(in_channels=3,out_channels=64,kernel_size=3,stride=1,padding=1,bias=True),nn.BatchNorm2d(64),nn.ReLU(inplace=True),nn.MaxPool2d(2))  #112*112self.layer1 = nn.Sequential(nn.Conv2d(in_channels=64,out_channels=128,kernel_size=3,stride=1,padding=1,bias=True),nn.BatchNorm2d(128),nn.ReLU(inplace=True),nn.MaxPool2d(2))  #56*56self.layer2 = nn.Sequential(nn.Conv2d(in_channels=128,out_channels=256,kernel_size=3,stride=1,padding=1,bias=True),nn.BatchNorm2d(256),nn.ReLU(inplace=True),nn.MaxPool2d(2))  #28*28self.layer3 = nn.Sequential(nn.Conv2d(in_channels=256,out_channels=512,kernel_size=3,stride=1,padding=1,bias=True),nn.BatchNorm2d(512),nn.ReLU(inplace=True),nn.MaxPool2d(2))  #14*14self.pool1 = nn.MaxPool2d(2)#7*7self.fc = nn.Linear(25088, 512)# self.drop = nn.Dropout(0.5)self.relu1 = nn.ReLU(inplace=True)self.fc2 = nn.Linear(512, numclass)def forward(self,x):x = self.layer0(x)x = self.layer1(x)x = self.layer2(x)x = self.layer3(x)x = self.pool1(x)x = x.view(x.size()[0],-1)       #view 类似于reshape  这里指定了第一维度为batch大小,第二维度为适应的,即剩多少, 就是多少维。# 这里就是将特征展平。  展为 B*N  ,N为特征维度。x = self.fc(x)# x = self.drop(x)x = self.relu1(x)x = self.fc2(x)return x# def model_Datapara(model, device,  pre_path=None):
#     model = torch.nn.DataParallel(model).to(device)
# 
#     model_dict = torch.load(pre_path).module.state_dict()
#     model.module.load_state_dict(model_dict)
#     return model#传入模型名字,和分类数, 返回你想要的模型
def initialize_model(model_name, num_classes, linear_prob=False, use_pretrained=True):# 初始化将在此if语句中设置的这些变量。# 每个变量都是模型特定的。model_ft = Noneinput_size = 0if model_name =="MyModel":if use_pretrained == True:model_ft = torch.load('model_save/MyModel')else:model_ft = MyModel(num_classes)input_size = 224elif model_name == "resnet18":""" Resnet18"""model_ft = models.resnet18(pretrained=use_pretrained)            # 从网络下载模型  pretrain true 使用参数和架构, false 仅使用架构。set_parameter_requires_grad(model_ft, linear_prob)            # 是否为线性探测,线性探测: 固定特征提取器不训练。num_ftrs = model_ft.fc.in_features  #分类头的输入维度model_ft.fc = nn.Linear(num_ftrs, num_classes)            # 删掉原来分类头, 更改最后一层为想要的分类数的分类头。input_size = 224elif model_name == "resnet50":""" Resnet50"""model_ft = models.resnet50(pretrained=use_pretrained)set_parameter_requires_grad(model_ft, linear_prob)num_ftrs = model_ft.fc.in_featuresmodel_ft.fc = nn.Linear(num_ftrs, num_classes)input_size = 224elif model_name == "googlenet":""" googlenet"""model_ft = models.googlenet(pretrained=use_pretrained)set_parameter_requires_grad(model_ft, linear_prob)num_ftrs = model_ft.fc.in_featuresmodel_ft.fc = nn.Linear(num_ftrs, num_classes)input_size = 224elif model_name == "alexnet":""" Alexnet"""model_ft = models.alexnet(pretrained=use_pretrained)set_parameter_requires_grad(model_ft, linear_prob)num_ftrs = model_ft.classifier[6].in_featuresmodel_ft.classifier[6] = nn.Linear(num_ftrs,num_classes)input_size = 224elif model_name == "vgg":""" VGG11_bn"""model_ft = models.vgg11_bn(pretrained=use_pretrained)set_parameter_requires_grad(model_ft, linear_prob)num_ftrs = model_ft.classifier[6].in_featuresmodel_ft.classifier[6] = nn.Linear(num_ftrs,num_classes)input_size = 224elif model_name == "squeezenet":""" Squeezenet"""model_ft = models.squeezenet1_0(pretrained=use_pretrained)set_parameter_requires_grad(model_ft, linear_prob)model_ft.classifier[1] = nn.Conv2d(512, num_classes, kernel_size=(1,1), stride=(1,1))model_ft.num_classes = num_classesinput_size = 224elif model_name == "densenet":""" Densenet"""model_ft = models.densenet121(pretrained=use_pretrained)set_parameter_requires_grad(model_ft, linear_prob)num_ftrs = model_ft.classifier.in_featuresmodel_ft.classifier = nn.Linear(num_ftrs, num_classes)input_size = 224elif model_name == "inception":""" Inception v3Be careful, expects (299,299) sized images and has auxiliary output"""model_ft = models.inception_v3(pretrained=use_pretrained)set_parameter_requires_grad(model_ft, linear_prob)# 处理辅助网络num_ftrs = model_ft.AuxLogits.fc.in_featuresmodel_ft.AuxLogits.fc = nn.Linear(num_ftrs, num_classes)# 处理主要网络num_ftrs = model_ft.fc.in_featuresmodel_ft.fc = nn.Linear(num_ftrs,num_classes)input_size = 299else:print("Invalid model_utils name, exiting...")exit()return model_ft, input_sizedef prilearn_para(model_ft, linear_prob):# 将模型发送到GPUdevice = torch.device("cuda:0")model_ft = model_ft.to(device)# 在此运行中收集要优化/更新的参数。# 如果我们正在进行微调,我们将更新所有参数。# 但如果我们正在进行特征提取方法,我们只会更新刚刚初始化的参数,即`requires_grad`的参数为True。params_to_update = model_ft.parameters()print("Params to learn:")if linear_prob:params_to_update = []for name, param in model_ft.named_parameters():if param.requires_grad == True:params_to_update.append(param)print("\t",name)else:for name, param in model_ft.named_parameters():if param.requires_grad == True:print("\t", name)## # 观察所有参数都在优化# optimizer_ft = optim.SGD(params_to_update, lr=0.001, momentum=0.9)def init_para(model):def weights_init(model):classname = model.__class__.__name__if classname.find('Conv') != -1:nn.init.normal_(model.weight.data, 0.0, 0.02)elif classname.find('BatchNorm') != -1:nn.init.normal_(model.weight.data, 1.0, 0.02)nn.init.constant_(model.bias.data, 0)model.apply(weights_init)return model

要理解这段代码,需要从以下几个方面入手,并掌握一些基础知识:

1. PyTorch 基础

  • PyTorch 的核心概念

    • 张量(Tensor):类似于 NumPy 的数组,但可以在 GPU 上加速计算。

    • 模型(nn.Module:用于定义神经网络结构。

    • 层(nn.Conv2d, nn.Linear 等):用于构建神经网络的基本模块。

    • 激活函数(nn.ReLU, nn.Sigmoid 等):为网络引入非线性。

    • 优化器(torch.optim:用于更新模型参数。

    • 损失函数(nn.CrossEntropyLoss 等):用于衡量模型预测与真实值的差异。

  • torch.nn 模块

    • 用于构建和定义神经网络的层和模块。

    • 例如,nn.Conv2d 是卷积层,nn.Linear 是全连接层,nn.BatchNorm2d 是批量归一化层。

  • torchvision 模块

    • 提供了许多预训练的模型(如 ResNet、VGG、Inception 等)。

    • torchvision.models 中的模型可以直接加载并用于迁移学习。

2. 模型定义

自定义模型 MyModel
class MyModel(nn.Module):def __init__(self, numclass=2):super(MyModel, self).__init__()self.layer0 = nn.Sequential(nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3, stride=1, padding=1, bias=True),nn.BatchNorm2d(64),nn.ReLU(inplace=True),nn.MaxPool2d(2))...self.fc = nn.Linear(25088, 512)self.fc2 = nn.Linear(512, numclass)
  • 功能

    • 定义了一个简单的卷积神经网络,包含多个卷积层、池化层和全连接层。

    • 使用了批量归一化(BatchNorm2d)和 ReLU 激活函数。

    • 最后通过全连接层输出分类结果。

CNN模型的结构

  • 卷积层:用于提取局部特征。

  • 批量归一化层:用于调整特征的分布,加速训练。

  • 激活层:引入非线性,使模型能够学习复杂的函数。

  • 池化层:降低特征图的空间维度,减少计算量。

  • 全连接层:用于输出最终的预测结果。

模型初始化
def initialize_model(model_name, num_classes, linear_prob=False, use_pretrained=True):...
  • 功能

    • 根据输入的模型名称(如 resnet18vgg 等),加载预训练模型或自定义模型。

    • 如果 linear_prob=True,则冻结模型的特征提取部分,仅训练分类头(线性探测)。

    • 如果 use_pretrained=True,则加载预训练权重。

  • 需要理解的知识

    • 迁移学习:使用预训练模型的权重作为初始化,然后根据自己的任务进行微调。

    • 线性探测(Linear Probing):冻结特征提取部分,仅训练分类头。

    • 模型权重的冻结(requires_grad=False:防止某些层的权重在训练时更新。

3. 模型参数管理

冻结和解冻参数
def set_parameter_requires_grad(model, linear_probing):if linear_probing:for param in model.parameters():param.requires_grad = False
  • 功能

    • 如果 linear_probing=True,则将模型的所有参数设置为不可训练(requires_grad=False)。

  • 需要理解的知识

    • requires_grad:控制参数是否参与梯度计算和更新。

    • 冻结参数:在迁移学习中,冻结预训练模型的权重,仅训练最后一层。

4. 模型初始化和权重设置

权重初始化
def init_para(model):def weights_init(model):classname = model.__class__.__name__if classname.find('Conv') != -1:nn.init.normal_(model.weight.data, 0.0, 0.02)elif classname.find('BatchNorm') != -1:nn.init.normal_(model.weight.data, 1.0, 0.02)nn.init.constant_(model.bias.data, 0)model.apply(weights_init)
  • 功能

    • 使用自定义的权重初始化方法初始化模型的权重。

    • 卷积层权重初始化为正态分布,批量归一化层权重初始化为 1,偏置初始化为 0。

  • 需要理解的知识

    • 权重初始化:对模型的权重进行初始化,可以影响模型的收敛速度和性能。

    • nn.init:PyTorch 提供的初始化方法。

      好的,让我们深入探讨一下均值和标准差的设置以及偏置的作用,以及为什么在权重初始化中要这样设置。

      1. 均值和标准差的设置

      背景

      在深度学习中,权重初始化是一个非常重要的步骤,因为它直接影响模型的收敛速度和训练稳定性。如果权重初始化不当,可能会导致以下问题:

    • 梯度消失:权重太小,导致梯度在反向传播过程中逐渐趋近于零,使得模型难以训练。

    • 梯度爆炸:权重太大,导致梯度在反向传播过程中迅速增大,使得模型训练不稳定。

    • 为了避免这些问题,我们需要合理地设置权重的初始值。


      2. 均值和标准差的作用

      均值(Mean)

    • 定义:均值是权重分布的中心位置。

    • 作用:在权重初始化中,均值通常设置为 0 或接近 0,因为这可以帮助模型在训练初期保持对称性,避免某些神经元的输出过大或过小。

    • 标准差(Standard Deviation)

    • 定义:标准差是权重分布的离散程度,表示权重值的波动范围。

    • 作用:标准差决定了权重的初始值的范围。如果标准差太小,权重值会集中在均值附近,可能导致梯度消失;如果标准差太大,权重值会过于分散,可能导致梯度爆炸。


    • 3. 常见的初始化方法
    • 正态分布初始化(nn.init.normal_

    • 公式:权重 W 从正态分布 N(μ,σ2) 中采样。

      • μ 是均值。

      • σ 是标准差。

    • 常见设置

      • 卷积层:均值 μ=0.0,标准差 σ=0.02。

        • 原因:较小的标准差可以避免权重值过大,从而减少梯度爆炸的风险。

        • 目的:让权重值在训练初期保持在一个合理的范围内,使得梯度能够有效地传播。

  • Xavier 初始化(nn.init.xavier_normal_nn.init.xavier_uniform_
    • 公式:权重 W 的标准差由输入和输出神经元的数量决定。

      • σ=nin​+nout​2​​

    • 目的:保持输入和输出的方差一致,避免梯度消失或爆炸。

    • Kaiming 初始化(nn.init.kaiming_normal_nn.init.kaiming_uniform_

    • 公式:权重 W 的标准差由输入神经元的数量决定。

      • σ=nin​2​​

    • 目的:适用于 ReLU 激活函数,可以更好地保持梯度的传播。


    • 4. 偏置(Bias)的初始化

      定义

    • 偏置是神经网络中的一个参数,用于在激活函数之前添加一个常数偏移量。

    • 作用

    • 在卷积层和全连接层中

      • 偏置通常初始化为 0,因为初始时不需要引入额外的偏移。

      • 这种设置可以简化模型的初始状态,避免某些神经元的输出在训练初期就偏离零点。

    • 在批量归一化层中

      • 批量归一化层的偏置(beta)也初始化为 0

      • 原因:批量归一化的作用是调整特征的均值和方差,使其接近标准正态分布。偏置初始化为 0 可以保持这种调整的初始状态。

    • 均值为 0.0:保持权重的对称性,避免某些神经元的输出在训练初期过大或过小。

    • 标准差为 0.02:较小的标准差可以避免权重值过大,从而减少梯度爆炸的风险,同时允许梯度有效地传播。

    • 批量归一化层

    • 权重(gamma)初始化为正态分布(均值为 1.0,标准差为 0.02

      • 原因:批量归一化的作用是调整特征的尺度。权重初始化为接近 1 可以保持特征的初始尺度不变。

      • 目的:在训练初期,特征的尺度保持稳定,避免因权重过大或过小导致的不稳定。

    • 偏置(beta)初始化为 0

      • 原因:偏置的作用是调整特征的偏移量。初始化为 0 可以保持特征的初始偏移量为零。

      • 目的:在训练初期,特征的偏移量保持稳定,避免因偏置过大导致的不稳定。


    • 总结
    • 均值和标准差的设置

      • 卷积层:均值为 0.0,标准差为 0.02,以保持权重的对称性和避免梯度爆炸。

      • 批量归一化层:权重初始化为正态分布(均值为 1.0,标准差为 0.02),以保持特征的初始尺度稳定。

    • 偏置的设置

      • 偏置初始化为 0,以保持特征的初始偏移量为零,避免训练初期的不稳定。

    • 这些设置的目的是为了在训练初期保持模型的稳定,加速收敛,并避免梯度消失或爆炸的问题。


5. 模型并行化和设备管理

模型并行化
# model = torch.nn.DataParallel(model).to(device)
  • 功能

    • 使用 DataParallel 将模型并行化,利用多 GPU 加速训练。

  • 需要理解的知识

    • 多 GPU 训练:使用 DataParallelDistributedDataParallel 在多 GPU 上训练模型。

    • 设备管理:将模型和数据移动到 GPU 上(to(device))。

6. 模型参数更新

参数更新
def prilearn_para(model_ft, linear_prob):params_to_update = model_ft.parameters()if linear_prob:params_to_update = [param for param in model_ft.parameters() if param.requires_grad]
  • 功能

    • 根据 linear_prob 的值,选择需要更新的参数。

    • 如果 linear_prob=True,则只更新 requires_grad=True 的参数。

  • 需要理解的知识

    • 参数更新:在训练过程中,只有 requires_grad=True 的参数会更新。

    • 优化器:如 SGDAdam,用于更新模型参数。


训练模型

代码 

from tqdm import tqdm
import torch
import time
import matplotlib.pyplot as plt
import numpy as np
from model_utils.data import samplePlot, get_semi_loaderdef train_val(para):########################################################model = para['model']semi_loader = para['no_label_Loader']train_loader =para['train_loader']val_loader = para['val_loader']optimizer = para['optimizer']loss = para['loss']epoch = para['epoch']device = para['device']save_path = para['save_path']save_acc = para['save_acc']pre_path = para['pre_path']max_acc = para['max_acc']val_epoch = para['val_epoch']acc_thres = para['acc_thres']conf_thres = para['conf_thres']do_semi= para['do_semi']semi_epoch = 10
###################################################no_label_Loader = Noneif pre_path != None:model = torch.load(pre_path)model = model.to(device)# model = torch.nn.DataParallel(model).to(device)# model.device_ids = [0,1]plt_train_loss = []plt_train_acc = []plt_val_loss = []plt_val_acc = []plt_semi_acc = []val_rel = []max_acc = 0for i in range(epoch):start_time = time.time()model.train()train_loss = 0.0train_acc = 0.0val_acc = 0.0val_loss = 0.0semi_acc = 0.0for data in tqdm(train_loader):                    #取数据optimizer.zero_grad()                           # 梯度置0x, target = data[0].to(device), data[1].to(device)pred = model(x)                                 #模型前向bat_loss = loss(pred, target)                   # 算交叉熵lossbat_loss.backward()                                 # 回传梯度optimizer.step()                                    # 根据梯度更新train_loss += bat_loss.item()    #.detach 表示去掉梯度train_acc += np.sum(np.argmax(pred.cpu().data.numpy(),axis=1) == data[1].numpy())# 预测值和标签相等,正确数就加1.  相等多个, 就加几。if no_label_Loader != None:for data in tqdm(no_label_Loader):optimizer.zero_grad()x , target = data[0].to(device), data[1].to(device)pred = model(x)bat_loss = loss(pred, target)bat_loss.backward()optimizer.step()semi_acc += np.sum(np.argmax(pred.cpu().data.numpy(),axis=1)== data[1].numpy())plt_semi_acc .append(semi_acc/no_label_Loader.dataset.__len__())print('semi_acc:', plt_semi_acc[-1])plt_train_loss.append(train_loss/train_loader.dataset.__len__())plt_train_acc.append(train_acc/train_loader.dataset.__len__())if i % val_epoch == 0:model.eval()with torch.no_grad():for valdata in val_loader:val_x , val_target = valdata[0].to(device), valdata[1].to(device)val_pred = model(val_x)val_bat_loss = loss(val_pred, val_target)val_loss += val_bat_loss.cpu().item()val_acc += np.sum(np.argmax(val_pred.cpu().data.numpy(), axis=1) == valdata[1].numpy())val_rel.append(val_pred)val_acc = val_acc/val_loader.dataset.__len__()if val_acc > max_acc:torch.save(model, save_path)max_acc = val_accplt_val_loss.append(val_loss/val_loader.dataset.__len__())plt_val_acc.append(val_acc)print('[%03d/%03d] %2.2f sec(s) TrainAcc : %3.6f TrainLoss : %3.6f | valAcc: %3.6f valLoss: %3.6f  ' % \(i, epoch, time.time()-start_time, plt_train_acc[-1], plt_train_loss[-1], plt_val_acc[-1], plt_val_loss[-1]))else:plt_val_loss.append(plt_val_loss[-1])plt_val_acc.append(plt_val_acc[-1])if do_semi and plt_val_acc[-1] > acc_thres and i % semi_epoch==0:         # 如果启用半监督, 且精确度超过阈值, 则开始。no_label_Loader = get_semi_loader(semi_loader, semi_loader, model, device, conf_thres)plt.plot(plt_train_loss)                   # 画图。plt.plot(plt_val_loss)plt.title('loss')plt.legend(['train', 'val'])plt.show()plt.plot(plt_train_acc)plt.plot(plt_val_acc)plt.title('Accuracy')plt.legend(['train', 'val'])plt.savefig('acc.png')plt.show()


1. 函数定义

def train_val(para):
  • 输入参数 para 是一个字典,包含了训练所需的配置信息,例如模型、数据加载器、优化器、损失函数等。


2. 参数初始化

model = para['model']
semi_loader = para['no_label_Loader']
train_loader = para['train_loader']
val_loader = para['val_loader']
optimizer = para['optimizer']
loss = para['loss']
epoch = para['epoch']
device = para['device']
save_path = para['save_path']
save_acc = para['save_acc']
pre_path = para['pre_path']
max_acc = para['max_acc']
val_epoch = para['val_epoch']
acc_thres = para['acc_thres']
conf_thres = para['conf_thres']
do_semi = para['do_semi']
  • para 字典中提取训练所需的参数。


3. 模型加载和设备配置

if pre_path is not None:model = torch.load(pre_path)  # 加载预训练模型
model = model.to(device)  # 将模型移动到指定设备
  • 如果提供了预训练模型路径 pre_path,则加载预训练模型。

  • 将模型移动到指定的设备(如 GPU)。


4. 初始化记录变量

plt_train_loss = []
plt_train_acc = []
plt_val_loss = []
plt_val_acc = []
plt_semi_acc = []
val_rel = []
max_acc = 0
  • 初始化用于记录训练和验证损失、准确率的列表,以及用于保存最佳模型准确率的变量。


5. 训练循环

for i in range(epoch):start_time = time.time()model.train()train_loss = 0.0train_acc = 0.0val_acc = 0.0val_loss = 0.0semi_acc = 0.0
  • 遍历指定的训练轮数 epoch

  • 初始化每轮的训练和验证损失、准确率。


6. 训练过程

for data in tqdm(train_loader):  # 遍历训练数据加载器optimizer.zero_grad()  # 清空梯度x, target = data[0].to(device), data[1].to(device)  # 将数据移动到设备pred = model(x)  # 模型前向传播bat_loss = loss(pred, target)  # 计算损失bat_loss.backward()  # 反向传播optimizer.step()  # 更新参数train_loss += bat_loss.item()  # 累加训练损失train_acc += np.sum(np.argmax(pred.cpu().data.numpy(), axis=1) == target.cpu().numpy())  # 累加训练准确率
  • 使用 tqdm 显示进度条。

  • 对每个批次的数据进行前向传播、计算损失、反向传播和参数更新。

  • 累加训练损失和准确率。

过程:梯度清零(防止累计梯度)->前向传播(计算预测值)->计算损失(衡量预测误差)->反向传播(计算梯度)->参数更新(根据梯度调整权重)

为什么需要 optimizer.zero_grad()?
梯度是累积计算的,如果不清零会导致不同批次的梯度叠加,影响参数更新方向。

loss.backward() 发生了什么?
通过反向传播算法,从输出层到输入层逐层计算每个参数对损失的贡献(梯度)。


7. 半监督学习

if no_label_Loader is not None:for data in tqdm(no_label_Loader):  # 遍历半监督数据加载器optimizer.zero_grad()x, target = data[0].to(device), data[1].to(device)pred = model(x)bat_loss = loss(pred, target)bat_loss.backward()optimizer.step()semi_acc += np.sum(np.argmax(pred.cpu().data.numpy(), axis=1) == target.cpu().numpy())plt_semi_acc.append(semi_acc / no_label_Loader.dataset.__len__())print('semi_acc:', plt_semi_acc[-1])
  • 如果启用了半监督学习(no_label_Loader 不为空),则对未标记数据进行训练。

  • 累加半监督学习的准确率。


8. 记录训练和验证指标

plt_train_loss.append(train_loss / train_loader.dataset.__len__())
plt_train_acc.append(train_acc / train_loader.dataset.__len__())
  • 计算并记录训练损失和准确率。


9. 验证过程

if i % val_epoch == 0:  # 每隔 val_epoch 轮进行一次验证model.eval()with torch.no_grad():  # 关闭梯度计算for valdata in val_loader:val_x, val_target = valdata[0].to(device), valdata[1].to(device)val_pred = model(val_x)val_bat_loss = loss(val_pred, val_target)val_loss += val_bat_loss.cpu().item()val_acc += np.sum(np.argmax(val_pred.cpu().data.numpy(), axis=1) == val_target.cpu().numpy())val_acc = val_acc / val_loader.dataset.__len__()if val_acc > max_acc:torch.save(model, save_path)  # 保存最佳模型max_acc = val_accplt_val_loss.append(val_loss / val_loader.dataset.__len__())plt_val_acc.append(val_acc)print('[%03d/%03d] %2.2f sec(s) TrainAcc : %3.6f TrainLoss : %3.6f | valAcc: %3.6f valLoss: %3.6f  ' % \(i, epoch, time.time() - start_time, plt_train_acc[-1], plt_train_loss[-1], plt_val_acc[-1], plt_val_loss[-1]))

 

  • 每隔 val_epoch 轮进行一次验证。

  • 计算验证集的损失和准确率。

  • 如果当前轮次的验证准确率高于历史最高准确率,则保存当前模型。

  • 打印训练和验证的损失及准确率。


10. 动态启用半监督学习

if do_semi and plt_val_acc[-1] > acc_thres and i % semi_epoch == 0:  # 如果启用半监督,且精确度超过阈值no_label_Loader = get_semi_loader(semi_loader, semi_loader, model, device, conf_thres)
  • 根据条件动态启用半监督学习。

  • 如果验证准确率超过阈值 acc_thres,则从未标记数据中生成新的伪标签数据加载器。


11. 绘制训练和验证曲线

plt.plot(plt_train_loss)
plt.plot(plt_val_loss)
plt.title('loss')
plt.legend(['train', 'val'])
plt.show()plt.plot(plt_train_acc)
plt.plot(plt_val_acc)
plt.title('Accuracy')
plt.legend(['train', 'val'])
plt.savefig('acc.png')
plt.show()
  • 使用 matplotlib 绘制训练和验证的损失及准确率曲线。


总结

这个函数实现了一个完整的训练和验证流程,支持半监督学习。它包括以下关键步骤:

  1. 模型加载和设备配置:加载预训练模型并将其移动到指定设备。

  2. 训练过程:对训练数据进行前向传播、计算损失、反向传播和参数更新。

  3. 半监督学习:利用未标记数据生成伪标签,并对这些数据进行训练。

  4. 验证过程:定期验证模型性能,保存最佳模型。

  5. 动态启用半监督学习:根据条件从未标记数据中生成新的伪标签数据加载器。

  6. 绘制训练和验证曲线:可视化训练和验证的损失及准确率。

主函数

import random
import torch
import torch.nn as nn
import numpy as np
import osfrom model_utils.model import initialize_model
from model_utils.train import train_val
from model_utils.data import getDataLoader# os.environ['CUDA_VISIBLE_DEVICES']='0,1'def seed_everything(seed):torch.manual_seed(seed)torch.cuda.manual_seed(seed)torch.cuda.manual_seed_all(seed)torch.backends.cudnn.benchmark = Falsetorch.backends.cudnn.deterministic = Truerandom.seed(seed)np.random.seed(seed)os.environ['PYTHONHASHSEED'] = str(seed)
#################################################################
seed_everything(0)
###############################################model_name = 'resnet18'
##########################################num_class = 11
batchSize = 32
learning_rate = 1e-4
loss = nn.CrossEntropyLoss()
epoch = 10
device = 'cuda' if torch.cuda.is_available() else 'cpu'
##########################################
filepath = 'food-11_sample'
# filepath = 'food-11'
###########################读数据
train_loader = getDataLoader(filepath, 'train', batchSize)
val_loader = getDataLoader(filepath, 'val', batchSize)
no_label_Loader = getDataLoader(filepath,'train_unl', batchSize)#模型和超参数
model, input_size = initialize_model(model_name, 11, use_pretrained=False)print(input_size)optimizer = torch.optim.AdamW(model.parameters(), lr=learning_rate,weight_decay=1e-4)save_path = 'model_save/model.pth'trainpara = {"model" : model,'train_loader': train_loader,'val_loader': val_loader,'no_label_Loader': no_label_Loader,'optimizer': optimizer,'batchSize': batchSize,'loss': loss,'epoch': epoch,'device': device,'save_path': save_path,'save_acc': True,'max_acc': 0.5,'val_epoch' : 1,'acc_thres' : 0.7,'conf_thres' : 0.99,'do_semi' : True,"pre_path" : None}

这段代码是一个完整的深度学习训练流程的入口脚本,用于训练一个图像分类模型,支持半监督学习。让我们逐步解析这段代码,理解每个部分的作用和逻辑。


1. 导入必要的库

import random
import torch
import torch.nn as nn
import numpy as np
import osfrom model_utils.model import initialize_model
from model_utils.train import train_val
from model_utils.data import getDataLoader
  • random:用于设置 Python 的随机种子。

  • torchtorch.nn:PyTorch 深度学习框架,用于构建和训练模型。

  • numpy:用于数值计算。

  • os:用于操作系统相关的操作,如设置环境变量。

  • initialize_model:从 model_utils.model 中导入,用于初始化模型。

  • train_val:从 model_utils.train 中导入,用于训练和验证模型。

  • getDataLoader:从 model_utils.data 中导入,用于加载数据。


2. 设置随机种子

def seed_everything(seed):torch.manual_seed(seed)torch.cuda.manual_seed(seed)torch.cuda.manual_seed_all(seed)torch.backends.cudnn.benchmark = Falsetorch.backends.cudnn.deterministic = Truerandom.seed(seed)np.random.seed(seed)os.environ['PYTHONHASHSEED'] = str(seed)seed_everything(0)
  • 作用:确保实验结果的可重复性。

  • torch.manual_seed:设置 PyTorch 的随机种子。

  • torch.cuda.manual_seedtorch.cuda.manual_seed_all:设置 CUDA 的随机种子。

  • torch.backends.cudnn.benchmarktorch.backends.cudnn.deterministic:关闭 cuDNN 的基准测试模式,启用确定性模式。

  • random.seednp.random.seed:设置 Python 和 NumPy 的随机种子。

  • os.environ['PYTHONHASHSEED']:设置 Python 的哈希种子。


3. 配置训练参数

model_name = 'resnet18'
num_class = 11
batchSize = 32
learning_rate = 1e-4
loss = nn.CrossEntropyLoss()
epoch = 10
device = 'cuda' if torch.cuda.is_available() else 'cpu'
filepath = 'food-11_sample'
  • model_name:使用的模型名称,这里选择 resnet18

  • num_class:分类任务的类别数,这里为 11。

  • batchSize:每个批次的样本数,这里为 32。

  • learning_rate:学习率,这里为 10−4。

  • loss:损失函数,这里使用交叉熵损失。

  • epoch:训练轮数,这里为 10。

  • device:运行设备,优先使用 GPU(cuda),否则使用 CPU。

  • filepath:数据集的路径。


4. 数据加载

train_loader = getDataLoader(filepath, 'train', batchSize)
val_loader = getDataLoader(filepath, 'val', batchSize)
no_label_Loader = getDataLoader(filepath, 'train_unl', batchSize)
  • getDataLoader:从 model_utils.data 中导入的函数,用于加载数据。

  • train_loader:训练集的数据加载器。

  • val_loader:验证集的数据加载器。

  • no_label_Loader:未标记数据集的数据加载器,用于半监督学习。


5. 模型初始化

model, input_size = initialize_model(model_name, 11, use_pretrained=False)
  • initialize_model:从 model_utils.model 中导入的函数,用于初始化模型。

  • model_name:模型名称,这里为 resnet18

  • num_class:分类任务的类别数,这里为 11。

  • use_pretrained:是否使用预训练权重,这里设置为 False,表示不使用预训练权重。

  • input_size:模型输入图像的大小。


6. 优化器配置

optimizer = torch.optim.AdamW(model.parameters(), lr=learning_rate, weight_decay=1e-4)
  • torch.optim.AdamW:使用 AdamW 优化器,它结合了 Adam 优化器和权重衰减。

  • lr:学习率,这里为 10−4。

  • weight_decay:权重衰减系数,这里为 10−4。


7. 配置训练参数字

trainpara = {"model": model,'train_loader': train_loader,'val_loader': val_loader,'no_label_Loader': no_label_Loader,'optimizer': optimizer,'batchSize': batchSize,'loss': loss,'epoch': epoch,'device': device,'save_path': save_path,'save_acc': True,'max_acc': 0.5,'val_epoch': 1,'acc_thres': 0.7,'conf_thres': 0.99,'do_semi': True,"pre_path": None
}
  • trainpara:一个字典,包含训练所需的参数。

  • model:训练的模型。

  • train_loaderval_loaderno_label_Loader:训练集、验证集和未标记数据集的数据加载器。

  • optimizer:优化器。

  • batchSize:批次大小。

  • loss:损失函数。

  • epoch:训练轮数。

  • device:运行设备。

  • save_path:保存模型的路径。

  • save_acc:是否保存最佳模型。

  • max_acc:初始最高准确率。

  • val_epoch:验证间隔。

  • acc_thres:启用半监督学习的准确率阈值。

  • conf_thres:生成伪标签的置信度阈值。

  • do_semi:是否启用半监督学习。

  • pre_path:预训练模型的路径(如果有)。


总结

这段代码实现了一个完整的深度学习训练流程,支持半监督学习。它包括以下关键步骤:

  1. 设置随机种子:确保实验结果的可重复性。

  2. 配置训练参数:包括模型名称、类别数、批次大小、学习率、损失函数等。

  3. 数据加载:加载训练集、验证集和未标记数据集。

  4. 模型初始化:初始化模型,可以选择是否使用预训练权重。

  5. 优化器配置:配置优化器和学习率。

  6. 训练参数字典:将所有训练参数封装到一个字典中。

  7. 启动训练和验证:调用 train_val 函数,启动训练和验证流程。

相关文章:

深度学习之图像分类(二)

前言 文章主要是通过实战项目——食品分类来理解分类项目的整体流程。除此之外&#xff0c;还需要对半监督学习&#xff0c;迁移学习&#xff0c;数据增广&#xff0c;Adam和AdamW进行了解。 数据增广 图片增广&#xff08;Image Data Augmentation&#xff09;是深度学习中一种…...

数据库高安全—openGauss安全整体架构安全认证

openGauss作为新一代自治安全数据库&#xff0c;提供了丰富的数据库基础安全能力&#xff0c;并逐步完善各类高阶安全能力。这些安全能力涵盖了访问登录认证、用户权限管理、审计与追溯及数据安全隐私保护等。本章节将围绕openGauss安全机制进行源码解读&#xff0c;以帮助数据…...

利用开源小智AI制作桌宠机器狗

本文主要介绍如何利用开源小智AI制作桌宠机器狗 1 源码下载 首先下载小智源码,下载地址, 下载源码后,使用vsCode打开,需要在vscode上安装esp-idf,安装方式请自己解决 2 源码修改 2.1添加机器狗控制代码 在目录main/iot/things下添加dog.cc文件,内容如下; #include…...

uniapp在app下使用mqtt协议!!!支持vue3

什么&#xff1f;打包空白&#xff1f;分享一下我的解决方法&#xff01; 第一步 找大师算过了&#xff0c;装4.1版本运气好&#xff01; 所以根目录执行命令… npm install mqtt4.1.0第二步 自己封装一个mqtt文件方便后期开坛做法&#xff01; // utils/mqtt.js import mqt…...

Orange 开源项目 - 集成阿里云大模型

1 阿里云的大模型服务平台百炼 阿里云的大模型服务平台百炼是一站式的大模型开发及应用构建平台。不论是开发者还是业务人员&#xff0c;都能深入参与大模型应用的设计和构建。您可以通过简单的界面操作&#xff0c;在5分钟内开发出一款大模型应用&#xff0c;或在几小时内训练…...

DSP芯片C6678的SRIO及其中断跳转的配置

C6678SRIO读写测试门铃中断跳转测试 SRIO简述代码前言SRIO配置原始代码1.使能电源2.初始化SRIO回环修改 3.SRIO测试 Doorbell门铃中断1.初始化中断函数2.中断向量表建立3.中断向量表的链接 本博客基于创龙“678ZH产品线”的SRIO代码&#xff0c;部分参考于网友们的博客&#xf…...

MongoDB#常用脚本

批量插入数据脚本 const oneDayAgo new Date(Date.now() - 1 * 24 * 60 * 60 * 1000);const documents []; for (let i 1; i < 100; i) {documents.push({id: i, // 递增的 idcreateTime: oneDayAgo, // 1天前的日期data: Sample data ${i} // 其他字段&#xff08;可选…...

MySQL 主从集群同步延迟问题分析与解决方案

MySQL 主从复制&#xff08;Replication&#xff09;是构建高可用架构的核心技术&#xff0c;但在实际应用中&#xff0c;主从同步延迟&#xff08;Replication Lag&#xff09;是常见且棘手的问题。延迟会导致从库数据不一致、读请求返回旧数据&#xff0c;甚至引发业务逻辑错…...

论文笔记(七十二)Reward Centering(五)

Reward Centering&#xff08;五&#xff09; 文章概括摘要附录B 理论细节C 实验细节D 相关方法的联系 文章概括 引用&#xff1a; article{naik2024reward,title{Reward Centering},author{Naik, Abhishek and Wan, Yi and Tomar, Manan and Sutton, Richard S},journal{arX…...

FFmpeg 是什么?为什么?怎么用?

摘要&#xff1a;本文介绍了 FFmpeg&#xff0c;一个功能强大的开源多媒体处理工具&#xff0c;广泛应用于视频和音频文件的处理。FFmpeg 支持多种多媒体格式&#xff0c;能够实现视频编码/解码、格式转换、裁剪、合并、音频提取、流媒体处理等功能。本文详细阐述了 FFmpeg 的主…...

雷池WAF动态防护技术实测

作者&#xff1b; Hacker / 0xh4ck3r 介绍 长亭雷池&#xff08;SafeLine&#xff09;是由北京长亭科技有限公司耗时近10年研发并推出的Web应用防火墙&#xff08;WAF&#xff09;&#xff0c;其核心检测能力由智能语义分析算法驱动。雷池旨在为用户提供高质量的Web攻击防护、…...

BUU40 [CSCCTF 2019 Qual]FlaskLight1【SSTI】

模板&#xff1a; {{.__class__.__base__.__subclasses__()[80].__init__.__globals__[__builtins__].eval("__import__(os).popen(type flag.txt).read()")}} 是个空字符串&#xff0c;.__class__代表这个空字符串的类是什么&#xff08;这里是单引号双引号都行&a…...

【每日八股】Redis篇(二):数据结构

Redis 数据类型&#xff1f; 主要有 STRING、LIST、ZSET、SET 和 HASH。 STRING String 类型底层的数据结构实现主要是 SDS&#xff08;简单动态字符串&#xff09;&#xff0c;其主要应用场景包括&#xff1a; 缓存对象&#xff1a;可以用 STRING 缓存整个对象的 JSON&…...

VScode+stfp插件,实现文件远程同步保存【2025实操有效】

目录 1 痛点2 准备工作3 操作步骤3.1 第一步&#xff0c;下载STFP插件3.2 第二步&#xff0c;修改配置文件3.3 第三步&#xff0c;测试是否成功 4 后记 1 痛点 我一直用vscode远程连接服务器&#xff0c;传代码文件等到服务器上面&#xff0c;突然有一次服务器那边尽心维修&am…...

115 道 MySQL 面试题,从简单到深入!

1. 什么是数据库事务&#xff1f; 数据库事务是一个作为单个逻辑工作单元执行的一系列操作。事务具有ACID属性&#xff0c;即原子性&#xff08;Atomicity&#xff09;、一致性&#xff08;Consistency&#xff09;、隔离性&#xff08;Isolation&#xff09;和持久性&#xf…...

不同安装路径重复R包清理

df <- as.data.frame(installed.packages()) table(duplicated(df$Package)) ids <- df$Package[duplicated(df$Package)] df2 <- subset(df, df$Package %in% ids)...

Grouped-Query Attention(GQA)详解: Pytorch实现

Grouped-Query Attention&#xff08;GQA&#xff09;详解 Grouped-Query Attention&#xff08;GQA&#xff09; 是 Multi-Query Attention&#xff08;MQA&#xff09; 的改进版&#xff0c;它通过在 多个查询头&#xff08;Query Heads&#xff09;之间共享 Key 和 Value&am…...

选择排序:简单高效的选择

大家好&#xff0c;今天我们来聊聊选择排序&#xff08;Selection Sort&#xff09;算法。这是一个非常简单的排序算法&#xff0c;适合用来学习排序的基本思路和操作。选择排序在许多排序算法中以其直观和易于实现的特点著称&#xff0c;虽然它的效率不如其他高效算法&#xf…...

(教程)PDF 字体技术入门

PDF字体技术 许多人觉得PDF字体令人困惑的主要原因在于PDF文件可以使用多种不同的字体技术。PDF文件规范已经存在16年&#xff0c;在此期间&#xff0c;出现了多种不同的字体技术&#xff08;既有技术方面的原因&#xff0c;也有商业方面的原因&#xff09;。因此&#xff0c;…...

LabVIEW中CFURL.llb 工具库说明

CFURL.llb 是 LabVIEW 2019 安装目录下 C:\Program Files (x86)\National Instruments\LabVIEW 2019\vi.lib\Platform\ 路径下的工具库&#xff0c;主要用于处理 LabVIEW 与 URL 相关的操作&#xff0c;涵盖 URL 解析、HTTP 请求发送、数据传输等功能模块&#xff0c;帮助开发者…...

BGP配置华为——路径优选验证

实验拓扑 实验要求 实现通过修改AS-Path属性来影响路径选择实现通过修改Local_Preference属性来影响路径选择实现通过修改MED属性来影响路径选择实现通过修改preferred-value属性来影响路径选择 实验配置与效果 1.改名与IP配置 2.as300配置OSPF R3已经学到R2和R4的路由 3.…...

Linux8-互斥锁、信号量

一、前情回顾 void perror(const char *s);功能&#xff1a;参数&#xff1a; 二、资源竞争 1.多线程访问临界资源时存在资源竞争&#xff08;存在资源竞争、造成数据错乱&#xff09; 临界资源&#xff1a;多个线程可以同时操作的资源空间&#xff08;全局变量、共享内存&a…...

【Springboot3】Springboot3 搭建RocketMQ 最简单案例

说来也奇怪&#xff0c;RocketMQ 不能很好的兼容Springboot3&#xff0c;刚开始上手Springboot3集成RocketMQ会发现总是不能实例化RocketMQTemplate&#xff0c;老是启动时报错。本项目采用Springboot3&#xff0c;JDK21 &#xff0c;Maven 3.9&#xff0c;提供一个非常简单的示…...

使用docker安装mysql 挂起之后 再次运行无法连接问题

# 首先 vim /usr/lib/sysctl.d/00-system.conf # 在最后面添加 net.ipv4.ip_forward 1 # 然后保存退出&#xff0c;接着重启网络服务 systemctl restart network # 重启以后&#xff0c;输入以下命令&#xff0c;查看IPv4转发状态 sysctl net.ipv4.ip_forward # 显示net.ipv4…...

hot100-二叉树

二叉树 二叉树递归 相当于这个的顺序来回调换 class Solution {private List<Integer> res new ArrayList<>();public List<Integer> inorderTraversal(TreeNode root) {if(root null)return res;inorderTraversal(root.left);res.add(root.val);inorde…...

从零开始用react + tailwindcs + express + mongodb实现一个聊天程序(二)

1.安装mogondb数据库 参考MongoDB安装配置教程&#xff08;详细版&#xff09;_mongodb安装详细步骤-CSDN博客 安装mondbcompass数据库连接工具 参考https://www.mongodb.com/zh-cn/docs/compass/current/connect/ 2.后端服务 1.创建src文件夹 并在src文件夹下创建 index…...

基于Spring Boot的党员学习交流平台设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…...

Plantsimulation中机器人怎么通过阻塞角度设置旋转135°

创建一个这样的简单模型。 检查PickAndPlace的角度表。源位于180的角位置&#xff0c;而物料终结位于90的角位置。“返回默认位置”选项未被勾选。源每分钟生成一个零件。启动模拟时&#xff0c;Plant Simulation会选择两个位置之间的最短路径。示例中的机器人无法绕135的角位…...

2025.2.23机器学习笔记:PINN文献阅读

2025.2.23周报 一、文献阅读题目信息摘要Abstract创新点网络架构架构A架构B架构C 实验结论后续展望 一、文献阅读 题目信息 题目&#xff1a; Physics-Informed Neural Networks for Modeling Water Flows in a River Channel期刊&#xff1a; IEEE TRANSACTIONS ON ARTIFICI…...

关于Postman自动获取token

在使用postman测试联调接口时&#xff0c;可能每个接口都需要使用此接口生成的令牌做Authorization的Bearer Token验证&#xff0c;最直接的办法可能会是一步一步的点击&#xff0c;如下图&#xff1a; 在Authorization中去选择Bearer Token&#xff0c;然后将获取到的token粘贴…...

Android KMP初探

Android KMP初探 前言&#xff1a; 最近线上听了Kotlin官网举行的KMP会议&#xff0c;感觉听神奇的&#xff0c;于是就把官方demo下载下来尝试了一下&#xff0c;下载插件和所需要的依赖都用了很久&#xff0c;但是发现里面的代码很少&#xff0c;于是尝试自己手写了一下&…...

ncDLRES:一种基于动态LSTM和ResNet的非编码RNA家族预测新方法

现有的计算方法主要分为两类&#xff1a;第一类是通过学习序列或二级结构的特征来预测ncRNAs家族&#xff0c;另一类是通过同源序列之间的比对来预测ncRNAs家族。在第一类中&#xff0c;一些方法通过学习预测的二级结构特征来预测ncRNAs家族。二级结构预测的不准确性可能会导致…...

前端项目打包过滤指定icon文件

1.需求背景 项目中有部分功能需要vip权限才可以使用&#xff0c;所有部分筛选、按钮 等有vip的icon提示 如下图 此项目衍生出一个特殊版本&#xff0c;此版本无需登录且拥有最高权限&#xff0c;所以产品要求去除项目中的所有vip相关的提示。 2.解决思路 &#xff08;1&am…...

蓝桥杯 Java B 组之最短路径算法(Dijkstra、Floyd-Warshall)

Day 2&#xff1a;最短路径算法&#xff08;Dijkstra、Floyd-Warshall&#xff09; &#x1f4d6; 一、最短路径算法简介 最短路径问题是图论中的经典问题&#xff0c;主要用于求解 单源最短路径 或 多源最短路径。在实际应用中&#xff0c;最短路径广泛应用于 导航系统、网络…...

科普:HTTP端口80和HTTPS端口443

你会发现&#xff0c;有的网址不带端口号&#xff0c;怎么回事&#xff1f; HTTP协议默认端口&#xff1a;HTTP协议的默认端口是80。当用户在浏览器中输入一个没有指定端口的以http://开头的网址时&#xff0c;浏览器会自动使用80端口与服务器建立连接&#xff0c;进行超文本数…...

如何安装vm和centos

安装 VMware Workstation Pro 步骤 1&#xff1a;下载 VMware Workstation Pro 访问 VMware 官方网站&#xff08;Desktop Hypervisor Solutions | VMware &#xff09;&#xff0c;根据你的操作系统选择合适的版本进行下载。 步骤 2&#xff1a;运行安装程序 找到下载的安装…...

鸿蒙-验证码输入框的几种实现方式-上

文章目录 效果图、优缺点多TextInput多 TextCanvas 绘制 多个 TextInput 拼接放置四个输入框焦点移动输入时向后移动输入完成回调删除时向前移动 防止点击总结 最近在做应用鸿蒙化&#xff0c;说白了就是把原来Android、iOS的代码重新用ArkTS写一遍&#xff0c;我负责基础建设和…...

Vi 编辑器基本使用指南

一、Vi 编辑器的启动与退出 启动 Vi 编辑器 在终端中&#xff0c;输入vi加上要编辑的文件名&#xff0c;如vi example.txt&#xff0c;如果example.txt存在&#xff0c;Vi 编辑器会打开该文件&#xff1b;若不存在&#xff0c;则会创建一个新的空文件并打开。如果只输入vi&am…...

centos 7 安装python3 及pycharm远程连接方法

安装openssl 使用pip3安装 virtualenv的时候会提示WARNING: pip is configured with locations that require TLS/SSL, however the ssl module in Python is not available. 这是因为缺少openssl 2.0以上版本 解决办法&#xff1a; 一、先确认版本 openssl version 二、安…...

PostgreSQL 使用pgAdmin 4 数据库还原sql文件报错问题分析

sql执行报错问题: C:\Program Files\PostgreSQL\17\bin\pg_restore.exe --host "localhost" --port "5433" --username "postgres" --no-password --dbname "ry_postgresql-final" --verbose "E:\\PostgreSQLProject\\Ruoyi-Po…...

gihub上适合练手的Python项目

GitHub 上有许多适合练手的 Python 项目&#xff0c;涵盖了从初学者到中级开发者的不同难度级别。以下是一些推荐的项目类型和具体示例&#xff0c;帮助你提升 Python 编程技能&#xff1a; 1. 基础项目 适合初学者&#xff0c;帮助掌握 Python 基础语法和常用库。 示例项目&…...

3D Web轻量化引擎HOOPS Communicator如何赋能航空航天制造?

在当今航空航天制造领域&#xff0c;精确度、效率和协作是推动行业发展的关键要素。随着数字化技术的飞速发展&#xff0c;3D Web可视化开发包HOOPS Communicator 为航空航天制造带来了革命性的变化。它凭借强大的功能和灵活的应用&#xff0c;助力企业在设计、生产、培训等各个…...

AWQ和GPTQ量化的区别

一、前言 本地化部署deepseek时发现&#xff0c;如果是量化版的deepseek&#xff0c;会节约很多的内容&#xff0c;然后一般有两种量化技术&#xff0c;那么这两种量化技术有什么区别呢&#xff1f; 二、量化技术对比 在模型量化领域&#xff0c;AWQ 和 GPTQ 是两种不同的量…...

通过恒定带宽服务器调度改进时间敏感网络(TSN)流量整形

论文标题 英文标题&#xff1a;Improving TSN Traffic Shaping with Constant Bandwidth Server Scheduling 中文标题&#xff1a;通过恒定带宽服务器调度改进时间敏感网络&#xff08;TSN&#xff09;流量整形 作者信息 作者&#xff1a;Benjamin van Seggelen 指导教师&am…...

气象干旱触发水文(农业)干旱的概率及其触发阈值的动态变化-贝叶斯copula模型

前言 在干旱研究中&#xff0c;一个关键的科学问题是&#xff1a;在某一地区发生不同等级的气象干旱时&#xff0c;气象干旱会以何种概率引发不同等级的水文干旱、农业干旱和地下水干旱&#xff1f;换句话说&#xff0c;气象干旱的不同程度会分别引发其他类型干旱的哪种等级&a…...

自定义Spring Boot Starter(官网文档解读)

摘要 本文将详细介绍自定义 Spring Boot Starter 的完整过程。要构建自定义 Starter&#xff0c;首先需掌握 Spring Boot 中 Auto-configuration 以及相关注解的工作原理&#xff0c;同时了解 Spring Boot 提供的一系列条件注解。在具备这些知识基础后&#xff0c;再按照特定步…...

开发 picgo-plugin-huawei 插件,解决华为云社区外链限制问题

开发 picgo-plugin-huawei 插件&#xff0c;解决华为云社区外链限制问题 在技术博客平台中&#xff0c;外链的使用常常受到限制&#xff0c;这给我们的写作和内容展示带来了一定的不便。为了应对这一问题&#xff0c;我开发了 picgo-plugin-huawei 插件&#xff0c;它能够有效…...

最长回文子串

标题 1.1 问题描述 给你一个字符串 s&#xff0c;找到 s 中最长的回文子串。 1.2 示例 1.2.1 示例1 输入&#xff1a;s “babad” 输出&#xff1a;“bab” 解释&#xff1a;“aba” 同样是符合题意的答案。 1.2.2 示例2 输入&#xff1a;s “cbbd” 输出&#xff1a;“bb…...

JavaSE学习笔记26-集合(Collection)

集合 Java 中的集合&#xff08;Collection&#xff09;是 Java 标准库中非常重要的一部分&#xff0c;用于存储和操作一组对象。Java 集合框架&#xff08;Java Collections Framework&#xff09;提供了一套丰富的接口和类&#xff0c;用于处理各种数据结构&#xff0c;如列…...

开源神器KRR:用数据驱动K8s资源优化

引言:云原生时代的资源管理之痛 在Kubernetes集群中,过度配置导致资源浪费与配置不足引发稳定性风险的矛盾始终存在。CNCF调研显示,企业平均有35%的云资源处于闲置状态。本文将揭秘开源神器KRR(Kubernetes Resource Recommender),通过数据驱动方式实现精准资源配置,实测…...