1、torch.max

correct = 0
total = 0
for xb,yb in valid_dl:
    outputs = model(xb)
    _,predicted = torch.max(outputs.data,1)
    total += yb.size(0)  #yb.size(0) 返回的是张量 yb 在第 0 维的大小,也就是 yb 中的样本数量。
    correct += (predicted == yb).sum().item()  
print('正确率:%d %%'%(100 * correct /total))
def accuracy(predictions, labels):
    # predictions.data 是模型的预测结果
    # torch.max(predictions.data, 1) 返回每一行的最大值和对应的索引。 
    # [1] 表示我们只关心索引,即选择模型预测类别的索引。
    pred = torch.max(predictions.data, 1)[1]   # 取出每一行的最大值索引,得到预测类别
    
    # labels.data.view_as(pred) 将 labels 调整为与 pred 形状一致,方便比较
    # eq() 函数逐元素比较 pred 和 labels 的值,如果相等返回 True,不相等返回 False
    rights = pred.eq(labels.data.view_as(pred)).sum()  # 统计预测正确的样本数
    '''
    pred.eq(labels.data.view_as(pred)):

pred 是模型预测的类别(索引形式)。
labels.data.view_as(pred) 将标签(真实类别)转换为与 pred 相同的形状(通常是一个一维向量),这样就可以逐元素比较。
eq() 是 torch 中的一个方法,用于比较两个张量的元素,返回一个布尔张量,元素相等则返回 True,否则返回 False。
    '''
    # 返回正确的数量以及总样本数
    return rights, len(labels)

torch.max(input, dim)

  • 输入:一个张量 input,以及一个维度 dim
  • 输出:返回一个元组 (values, indices):
    • values:沿着 dim 维度的最大值。
    • indices:最大值对应的索引。

torch.max(outputs.data, 1)与torch.max(outputs.data, 0)

如果 outputs 的形状是 [784, 10],这意味着你有 784 个样本,每个样本对应 10 个类的输出(比如 logits 或预测值)。在这种情况下,torch.max(outputs.data, 1)torch.max(outputs.data, 0) 的结果含义会有所不同,具体如下:

1. torch.max(outputs.data, 1)

  • 维度dim=1

  • 操作:沿着第二维(每个样本的类别)查找最大值。

  • 输出

    • 返回一个张量,其中包含每个样本的最大值(即 logits 的最高值),以及一个索引张量,表示该最大值对应的类别。
    • 输出形状为 [784][784](索引),表示每个样本的预测类别。
  • 示例

    import torch
    
    # 假设 outputs 是 [784, 10] 的张量
    outputs = torch.randn(784, 10)  # 随机生成示例输出
    
    max_values, predicted = torch.max(outputs.data, 1)  # 查找每个样本的最大值及其对应的索引。outputs.data[i][0]---outputs.data[i][9]
    print(predicted)  # 输出:每个样本的预测类别(索引)
    

2. torch.max(outputs.data, 0)

  • 维度dim=0

  • 操作:沿着第一维(所有样本)查找每个类别的最大值。

  • 输出

    • 返回一个张量,包含每个类别在所有样本中的最大值,以及对应的索引。
    • 输出形状为 [10](最大值)和 [784](索引),表示每个类别在 784 个样本中的最大值。
  • 示例

    import torch
    
    # 假设 outputs 是 [784, 10] 的张量
    outputs = torch.randn(784, 10)  # 随机生成示例输出
    
    max_values, indices = torch.max(outputs.data, 0)  # 查找每个类别的最大值及其对应的样本索引
    print(max_values)  # 输出:每个类别的最大值
    print(indices)     # 输出:每个最大值对应的样本索引
    

总结

操作维度输出形状作用
torch.max(outputs, 1)1[784] (最大值), [784] (索引)返回每个样本的最大值及对应的类索引(预测类别)
torch.max(outputs, 0)0[10] (最大值), [784] (索引)返回每个类别在所有样本中的最大值及对应的样本索引

因此,当 outputs 的形状是 [784, 10] 时,选择 torch.max(outputs.data, 1) 可以用来获取每个样本的预测类别,而 torch.max(outputs.data, 0) 可以用来分析每个类别在样本中的最大预测值。

2、Dropout

关于 Dropout(0.5) 的理解,我们来详细说明一下它的工作原理。

Dropout 的工作原理

  1. 定义

    • Dropout(0.5) 表示在每次前向传播中,每个神经元有 50% 的概率被“丢弃”。这意味着有 50% 的神经元在当前迭代中不会参与计算,输出值被设置为 0。
  2. 示例

    • 假设你的输入张量形状为 [64, 784],这里 64 是批量大小,784 是每个样本的特征数(例如,一个 28x28 的图像展平后得到的特征向量)。
    • 在前向传播中,Dropout 会随机选择 50% 的神经元进行丢弃。重要的是,这并不意味着只会有 32 个神经元参与计算,而是对于每个样本(每一行),可能有一部分神经元会被丢弃。

具体过程

  • 对于输入的每个样本(每一行),Dropout 会独立地选择哪些神经元被丢弃。例如,假设我们有以下输入:

    Input (x): [64, 784] (64 个样本,每个样本 784 个特征)
    
  • ==在每次前向传播时,Dropout 会随机将某些特征(神经元)设置为 0。==比如说,某次前向传播可能会得到如下的输出:

    Output (x'): [64, 784] (仍然是 64 个样本,但某些特征被置为 0)
    
    Sample 1: [0.5, 0, 0.2, 0, 0.8, ...]  # 一些神经元被丢弃
    Sample 2: [0, 0.3, 0, 0.9, 0.7, ...]  # 不同的丢弃情况
    

关键点

  • 样本数量不变:输入张量的形状 [64, 784] 在经过 Dropout 后仍然是 [64, 784],而不是 [32, 784]。每个样本都有 784 个特征(神经元)。
  • 随机性:每次前向传播时丢弃的神经元都是随机的,不同的迭代可能会丢弃不同的神经元。
  • 训练与推理:在训练时使用 Dropout,而在推理(测试)时,所有神经元都被使用,通常输出会根据 Dropout 的比例进行缩放(乘以保留的概率)。

总结

  • Dropout 会随机丢弃一些神经元的输出,但并不改变输入的样本数量和形状。输入 [64, 784] 经过 Dropout 处理后,仍然是 [64, 784],只是某些神经元的输出被置为 0。

2、常用模块

这些包在 Python 中有各自的作用,通常用于处理图像、文件操作和时间管理等。下面我逐一解释这些包的功能及其典型用途。

1. imageio

  • 作用

    • imageio 是一个用于读取和写入图像、视频、动图等多媒体文件的库。
    • 它支持多种图像格式,如 PNG、JPEG、GIF、TIFF,以及视频格式,如 MP4、AVI 等。
    • 常用于读取图片或视频帧、保存图片、生成 GIF 等。
  • 常用方法

    • imageio.imread():读取图像文件。
    • imageio.imwrite():保存图像文件。
    • imageio.mimwrite():保存多张图像作为 GIF 动图。
    • imageio.get_reader()imageio.get_writer():用于读取或写入视频。
  • 示例

    import imageio
    img = imageio.imread('image.png')  # 读取图片
    imageio.imwrite('new_image.png', img)  # 保存图片
    

2. time

  • 作用

    • time 是 Python 的标准库,用于处理时间相关的任务。
    • 它可以获取当前时间、暂停程序执行、格式化时间、计算时间差等。
  • 常用方法

    • time.time():返回当前时间的时间戳(从 1970 年 1 月 1 日以来的秒数)
    • time.sleep(seconds):暂停程序执行指定的秒数。
    • time.localtime():将时间戳转换为结构化时间。
    • time.strftime():格式化时间为字符串。
  • 示例

    import time
    print(time.time())  # 输出当前时间的时间戳
    time.sleep(2)  # 暂停程序执行 2 秒
    

3. copy

  • 作用

    • copy 是 Python 的标准库,提供了对象的浅复制和深复制功能。
    • 浅复制 只复制对象的引用,某些情况下修改副本会影响原对象。
    • 深复制 递归复制对象中的所有内容,副本与原对象完全独立。
  • 常用方法

    • copy.copy():执行浅复制。
    • copy.deepcopy():执行深复制。
  • 示例

    import copy
    original = [1, 2, [3, 4]]
    shallow_copy = copy.copy(original)  # 浅复制
    deep_copy = copy.deepcopy(original)  # 深复制
    

4. PIL.Image(Pillow)

  • 作用

    • PIL(Python Imaging Library)是一个强大的图像处理库,后来被分支为 Pillow,用于处理图像文件的读取、操作和保存。
    • 它支持多种图像格式(如 JPEG、PNG、BMP 等),并提供丰富的图像操作功能,如裁剪、缩放、旋转、滤镜处理等。
  • 常用方法

    • Image.open():打开图像文件。
    • Image.save():保存图像文件。
    • Image.resize():调整图像大小。
    • Image.rotate():旋转图像。
    • Image.convert():转换图像模式(如 RGB、灰度等)。
  • 示例

    from PIL import Image
    img = Image.open('image.jpg')  # 打开图片
    img = img.resize((100, 100))  # 调整图片大小
    img.save('resized_image.jpg')  # 保存图片
    

总结

  • imageio:用于读取和保存多媒体文件(图像、视频、GIF等)。
  • time:用于时间处理,如暂停、获取当前时间等。
  • copy:用于对象的浅复制和深复制。
  • PIL.Image:图像处理库,支持多种图像操作(读取、保存、裁剪、旋转等)。

这些库在图像处理、时间控制和数据复制中被广泛使用,通常会用于一些涉及图片、视频的应用中,如机器学习的预处理、生成 GIF、视频分析等。

3、datasets.ImageFolder

当你使用 torchvision.datasets.ImageFolder 给定一个 root 目录时,root 是一个包含子文件夹的主目录。ImageFolder 并不会直接加载 root 目录下的所有图片,而是会查找 root 目录下每个子文件夹,并将这些子文件夹中的图片归类到对应的类别。

具体行为如下:

  1. 子文件夹代表类别ImageFolder 会将 root 目录下的每一个子文件夹的名称作为类别标签。每个子文件夹内部的所有图片都被视为属于该类别。

    例如,假设你的 root 目录结构如下:

    root/
        ├── cat/
        │   ├── image1.jpg
        │   ├── image2.jpg
        └── dog/
            ├── image1.jpg
            ├── image2.jpg
    
    • 在这种情况下,cat 文件夹中的图片会被分配标签 0dog 文件夹中的图片会被分配标签 1
    • 图片路径 root/cat/image1.jpg 会被标记为类别 0,而 root/dog/image1.jpg 会被标记为类别 1
  2. 图片的加载顺序ImageFolder 会递归地遍历每个类别文件夹下的所有图片,并按文件路径的顺序加载它们。

  3. 文件类型ImageFolder 只会加载文件扩展名符合常见图片格式的文件(如 .jpg, .png, .bmp 等)。

因此,ImageFolder 只加载 root 目录中的子文件夹内的图片,而不会直接加载 root 目录中的图片(如果 root 目录本身包含图片,这些图片将会被忽略)。

举个例子

假设 root 目录为 data/train/,结构如下:

data/train/
   ├── class1/
   │   ├── img1.jpg
   │   ├── img2.jpg
   ├── class2/
   │   ├── img3.jpg
   │   ├── img4.jpg

使用 ImageFolder 加载该数据集:

from torchvision import datasets, transforms

# 定义图像预处理
transform = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.ToTensor()
])

# 加载数据集
dataset = datasets.ImageFolder(root='data/train/', transform=transform)

# 输出类别和样本数
print(f'类别:{dataset.class_to_idx}')  # 类别标签映射 {'class1': 0, 'class2': 1}
print(f'数据集大小:{len(dataset)}')  # 数据集大小,应该是4张图片

总结:

  • ImageFolder 会遍历 root 目录下的每个子文件夹,并将子文件夹中的图片分配到对应的类别。
  • 如果 root 目录下有图片但没有子文件夹,这些图片将会被忽略。

4、数据集

batch_size = 128

image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x), data_transforms[x]) for x in ['train', 'valid']}  #同时读入了数据和标签
dataloaders = {x: torch.utils.data.DataLoader(image_datasets[x], batch_size=batch_size, shuffle=True) for x in ['train', 'valid']}
dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'valid']}
class_names = image_datasets['train'].classes #标签名

这两行代码:

image, label = image_datasets['train'][0]  #train的第一张图片
print(image_datasets['train'])
print(image.shape,label)  
print(image[0])    #这是图像的第一个通道(通常是 RGB 图像的红色通道)
Dataset ImageFolder
    Number of datapoints: 6552
    Root location: ./data/flower_data/train
    StandardTransform
Transform: Compose(
               Resize(size=[96, 96], interpolation=bilinear, max_size=None, antialias=True)
               RandomRotation(degrees=[-45.0, 45.0], interpolation=nearest, expand=False, fill=0)
               CenterCrop(size=(64, 64))
               RandomHorizontalFlip(p=0.5)
               RandomVerticalFlip(p=0.5)
               ColorJitter(brightness=(0.8, 1.2), contrast=(0.9, 1.1), saturation=(0.9, 1.1), hue=(-0.1, 0.1))
               RandomGrayscale(p=0.025)
               ToTensor()
               Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
           )
torch.Size([3, 64, 64]) 0
tensor([[-0.1828, -0.2342, -0.2856,  ..., -1.6213, -1.6213, -1.7069],
        [-0.1999, -0.2856, -0.3198,  ..., -1.5357, -1.6213, -1.7069],
        [-0.2856, -0.3541, -0.4226,  ..., -1.5014, -1.6213, -1.6898],
        ...,
        [-1.2959, -1.3130, -1.3815,  ..., -1.1075, -1.1418, -0.8849],
        [-1.2959, -1.3302, -1.4158,  ..., -1.0219, -1.0904, -0.7308],
        [-1.2959, -1.3473, -1.4500,  ..., -0.9877, -1.0733, -0.6623]])
#print(image_datasets['train'][0].shape)
print(image_datasets['train'][0][0][0].shape) #训练集第一个文件夹的第一张图片的B通道有负数,因为被标准化了 #训练集第一个文件夹的第一张图片的B通道有负数,因为被标准化了
print(image_datasets['train'][0][0][0])
torch.Size([64, 64])
tensor([[-1.7925, -1.8097, -1.7925,  ..., -0.9877, -1.0733, -1.1075],
        [-1.7754, -1.7754, -1.8268,  ..., -0.9705, -1.0733, -1.1932],
        [-1.7412, -1.7754, -1.8097,  ..., -1.0390, -1.1589, -1.2274],
        ...,
        [ 0.3309,  0.4166,  0.4679,  ..., -1.8953, -1.9124, -1.9124],
        [ 0.3823,  0.4679,  0.4337,  ..., -1.8782, -1.8953, -1.8953],
        [ 0.5364,  0.4679,  0.5022,  ..., -1.8268, -1.8782, -1.8953]]
  • image_datasets['train'][0] 返回一个元组 (image, label),其中:

    • image 是经过数据变换后的图像张量。
    • label 是对应的标签(类别)。

    这里,image 是一个三维张量 (C, H, W),即通道、高度、宽度,label 是一个整数,表示图像的类别。

image, label = image_datasets['train'][0]

这一行将元组解包为 imagelabel,分别表示图片张量和标签。然后你打印 image[0],即图像张量的第一个通道,以及标签。

在 Python 中,解包(unpacking) 指的是将一个包含多个元素的容器(如元组、列表等)中的各个元素分别赋值给多个变量。解包可以让你快速地从容器中提取元素,方便后续操作。

元组解包

假设你有一个包含两个元素的元组:

tup = (10, 20)

现在,你可以使用解包将 tup 中的两个元素分别赋值给变量 ab

a, b = tup
print(a)  # 输出 10
print(b)  # 输出 20

在这里,a 被赋值为元组中的第一个元素(10),b 被赋值为第二个元素(20)。这种方式被称为解包。

代码中的解包

在你的代码中:

image, label = image_datasets['train'][0]
  • image_datasets['train'][0] 返回的其实是一个包含两个元素的元组 (image, label)
    • image 是图像张量。
    • label 是图像对应的标签(类别)。

通过解包,imagelabel 分别被赋值为元组中的第一个和第二个元素。

相当于以下代码:

# 没有解包的情况下:
tup = image_datasets['train'][0]  # tup 是一个元组 (image, label)
image = tup[0]  # 获取第一个元素(图像)
label = tup[1]  # 获取第二个元素(标签)

但是,通过解包,你可以直接用 image, label = image_datasets['train'][0] 一步完成,而不需要额外的 tup[0]tup[1] 访问操作,简化了代码。

总结

解包 就是从元组(或其他容器)中将每个元素分别赋值给多个变量。例如,你可以从 image_datasets['train'][0] 返回的元组中提取出图像张量 image 和对应的标签 label,简化后续代码的处理。

5、优化器&加载保存模型

1. optimizer.param_groups

optimizer.param_groups 是 PyTorch 中优化器的一个属性,表示优化器管理的参数组。每个参数组是一个字典,包含与优化相关的信息,比如模型的参数(权重)、学习率等。

常见内容:
  • params: 这是模型中要更新的参数列表(张量),通常是模型的权重和偏置。
  • lr: 学习率,用于控制梯度下降时的步长。
  • momentum: 动量参数,适用于带动量的优化算法(如 SGD)。
  • weight_decay: 权重衰减(L2正则化)系数。
  • dampening: 动量的抑制因子。
  • nesterov: 是否使用 Nesterov 动量。
示例:
# 打印所有参数组信息
for param_group in optimizer.param_groups:
    print(param_group)
示例输出(一个带有动量的 SGD 优化器):
{'params': [tensor1, tensor2, ...], 'lr': 0.001, 'momentum': 0.9, 'weight_decay': 0.01, 'nesterov': True}
  • 这里 params 存储模型中的参数(如权重和偏置),其他则是用于调整这些参数的超参数。

2. model.state_dict()

model.state_dict() 是 PyTorch 中一个非常重要的函数,用于获取模型的参数和持久性状态,并以字典形式返回。这个字典的键是模型中各层的名称,值是这些层对应的张量(即权重和偏置)。

常见用途:
  • 保存模型参数:可以通过 state_dict() 来保存模型的所有参数,通常保存为 .pt 文件。
  • 加载模型参数:通过加载保存的 state_dict() 来恢复模型的参数。
示例:
# 打印模型的 state_dict
for param_tensor in model.state_dict():
    print(param_tensor, "\t", model.state_dict()[param_tensor].size())
示例输出:
fc.weight      torch.Size([10, 512])
fc.bias        torch.Size([10])
conv1.weight   torch.Size([64, 3, 7, 7])
conv1.bias     torch.Size([64])
  • 输出中,fc.weightfc.bias 是全连接层的权重和偏置,conv1.weightconv1.bias 是卷积层的权重和偏置。
常用操作:
  1. 保存模型参数

    torch.save(model.state_dict(), 'model_weights.pth')
    
  2. 加载模型参数

    model.load_state_dict(torch.load('model_weights.pth'))
    

总结:

  • optimizer.param_groups:包含优化器参数和超参数(如学习率、动量等),帮助控制模型权重的更新过程。
  • model.state_dict():保存或加载模型的所有参数(权重、偏置等),通常用于模型的持久化和恢复。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部