机器学习笔记20241017
文章目录
- torchvision
- dataloader
- nn.module
- 卷积
- 非线性激活
- 模型选择
- 训练误差
- 泛化误差
- 正则化
- 权重衰退的基本概念
- 数学表示
- 权重衰退的效果
- 物理解释
- 数值稳定性(Gradient Vanishing)
- 梯度消失
- 原因
- 解决方法
- 梯度爆炸(Gradient Explosion)
- 定义
- 原因
- 解决方法
- 总结
继续跟着小土堆学pytorch
torchvision
# 导入torchvision库,主要用于处理图像数据集和图像变换
import torchvision
# 从torch.utils.tensorboard中导入SummaryWriter,用于将数据写入TensorBoard
from torch.utils.tensorboard import SummaryWriter
# 创建一个图像变换的组合,使用ToTensor()将PIL图像或numpy数组转换为PyTorch张量
dataset_transform = torchvision.transforms.Compose([
# 将图像转换为Tensor(张量)
torchvision.transforms.ToTensor()
])
# 下载并加载CIFAR-10训练集,设置transform为我们定义的dataset_transform(将图像转换为张量)
train_set = torchvision.datasets.CIFAR10(
root="./dataset", # 数据集存储的根目录
train=True, # 设置为True表示加载训练集
transform=dataset_transform, # 使用我们定义的图像变换
download=True # 如果数据集不存在,则下载它
)
# 下载并加载CIFAR-10测试集,transform同样为将图像转换为张量
test_set = torchvision.datasets.CIFAR10(
root="./dataset", # 数据集存储的根目录
train=False, # 设置为False表示加载测试集
transform=dataset_transform, # 使用相同的图像变换
download=True # 如果数据集不存在,则下载它
)
# 创建一个SummaryWriter实例,参数是log文件的存储路径,"p10"为log文件目录
writer = SummaryWriter("p10")
# 从测试集中获取第一张图像及其对应的标签
img, target = test_set[0]
# 打印图像张量的值,方便调试
print(img)
# 打印图像对应的标签(数字形式),方便调试
print(target)
# 打印测试集中该标签对应的类别名称,如'cat', 'dog'等
print(test_set.classes[target])
# 循环处理测试集中的前10张图像
for i in range(10):
# 获取第i张图像及其标签
img, target = test_set[i]
# 将图像写入TensorBoard,标签名为"test_set",图像的全局步数为i
writer.add_image("test_set", img, i)
# 关闭SummaryWriter,确保数据写入完成
writer.close()
dataloader
# 导入必要的库
import torchvision # 用于加载和转换图像数据
from torch.utils.data import DataLoader # 用于批量加载数据
from torch.utils.tensorboard import SummaryWriter # 用于将数据可视化到TensorBoard
# 下载并加载CIFAR-10数据集,这里设置train=True表示我们加载训练数据集
test_data = torchvision.datasets.CIFAR10(
root="./dataset", # 数据集下载到当前文件夹中的'dataset'文件夹中
train=True, # 加载训练集
transform=torchvision.transforms.ToTensor(), # 将图像转换为Tensor(张量),因为神经网络处理的是张量
download=True # 如果数据集不存在,则从网上下载
)
# 使用DataLoader将数据集分批加载,方便我们在训练时批量处理数据
test_loader = DataLoader(
dataset=test_data, # 需要加载的数据集
batch_size=64, # 每次加载64张图片
shuffle=True, # 打乱数据集中的图片顺序,防止模型记住图片的顺序,影响训练效果
num_workers=0, # 加载数据的子进程数量,0表示不使用额外进程
drop_last=True # 如果最后一批次数据少于64张,丢弃这批次
)
# 查看单张图片和对应标签
img, target = test_data[0] # 取出第0张图片和它的标签
print(img.shape) # 打印图片的形状,应该是[3, 32, 32],表示3个颜色通道的32x32像素图片
print(target) # 打印图片的标签(数字),每个数字对应一个分类
# 创建SummaryWriter对象,用于将数据写入TensorBoard
writer = SummaryWriter("DataLoader") # 'DataLoader'是日志文件的保存路径
# 模拟进行2个轮次(epochs)的训练(虽然这里没有真正训练,只是模拟数据处理流程)
for epoch in range(2):
step = 0 # 记录每个epoch中的步骤数
for data in test_loader: # 遍历加载的数据
imgs, targets = data # 从批次中取出图片和对应的标签
# 将批次中的图片写入TensorBoard,每次写入64张图片
writer.add_images("Epoch:{}".format(epoch), imgs, step)
step += 1 # 记录当前步数
# 关闭SummaryWriter,结束写入
writer.close()
nn.module
# 导入 PyTorch 库
import torch
# 从 torch 库中导入 nn 模块,nn 模块是用于构建神经网络的工具
from torch import nn
# 定义一个名为 Tudui 的类,继承自 nn.Module
class Tudui(nn.Module):
# 初始化方法,构造函数
def __init__(self):
# 调用父类的初始化方法,确保 nn.Module 的初始化
super().__init__()
# 前向传播方法,定义神经网络的计算过程
def forward(self, input):
# 将输入加 1,计算输出
output = input + 1
# 返回输出
return output
# 创建 Tudui 类的一个实例
tudui = Tudui()
# 创建一个张量(tensor),值为 1.0
x = torch.tensor(1.0)
# 将张量 x 传入 tudui 实例,得到输出
output = tudui(x)
# 打印输出
print(output)
卷积
在 PyTorch 中,二维卷积函数 F.conv2d 需要输入一个 4D 张量,形状为 (N, C, H, W)将输入张量重塑为 4D 张量,形状为 (N, C, H, W)
import torch # 导入 PyTorch 库
import torch.nn.functional as F # 从 PyTorch 导入神经网络功能模块
# 定义输入张量(模拟图像数据)
input = torch.tensor([[1, 2, 0, 3, 1], # 5x5 的二维张量
[0, 1, 2, 3, 1],
[1, 2, 1, 0, 0],
[5, 2, 3, 1, 1],
[2, 1, 0, 1, 1]])
# 定义卷积核(过滤器)
kernel = torch.tensor([[1, 2, 1], # 3x3 的卷积核
[0, 1, 0],
[2, 1, 0]])
# 将输入张量重塑为 4D 张量,形状为 (N, C, H, W)
# N: 批量大小(batch size),这里是 1
# C: 通道数(channel),这里是 1(灰度图像)
# H: 高度(height),这里是 5
# W: 宽度(width),这里是 5
input = torch.reshape(input, (1, 1, 5, 5))
# 将卷积核重塑为 4D 张量,形状为 (N, C, H, W)
# 这里同样是 1 个批量,1 个通道,卷积核的高度和宽度均为 3
kernel = torch.reshape(kernel, (1, 1, 3, 3))
# 打印输入张量的形状
print(input.shape) # 输出: torch.Size([1, 1, 5, 5])
# 打印卷积核的形状
print(kernel.shape) # 输出: torch.Size([1, 1, 3, 3])
# 执行卷积操作,步幅为 1
output = F.conv2d(input, kernel, stride=1)
print(output) # 打印卷积后的输出
# 执行卷积操作,步幅为 2
output2 = F.conv2d(input, kernel, stride=2)
print(output2) # 打印卷积后的输出
# 执行卷积操作,步幅为 1,并且添加了填充(padding)为 1
output3 = F.conv2d(input, kernel, stride=1, padding=1)
print(output3) # 打印卷积后的输出
import torch
import torchvision
from torch import nn
from torch.nn import Conv2d
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
import os
print(os.getcwd())
# 加载 CIFAR-10 数据集,使用 ToTensor 转换为张量格式
dataset = torchvision.datasets.CIFAR10("../data", train=False, transform=torchvision.transforms.ToTensor(),
download=True)
# 创建数据加载器,以便从数据集中按批次加载数据
dataloader = DataLoader(dataset, batch_size=64)
# 定义一个简单的卷积神经网络类
class Tudui(nn.Module):
def __init__(self):
# 初始化父类
super(Tudui, self).__init__()
# 定义一个卷积层:输入通道为3(RGB),输出通道为6,卷积核大小为3,步幅为1,填充为0
self.conv1 = Conv2d(in_channels=3, out_channels=6, kernel_size=3, stride=1, padding=0)
def forward(self, x):
# 定义前向传播过程
x = self.conv1(x) # 输入数据通过卷积层
return x
# 实例化网络模型
tudui = Tudui()
# 创建 TensorBoard 的 SummaryWriter,用于记录训练过程中的数据
#不知道为什么无法生成文件只能改成绝对路径
#writer = SummaryWriter("../logs")
writer = SummaryWriter("g:/ML/logs")
# 初始化步骤计数器
step = 0
# 遍历数据加载器,获取数据批次
for data in dataloader:
imgs, targets = data # imgs 是输入图像,targets 是对应的标签
output = tudui(imgs) # 将输入图像传递给模型,获取输出
# 打印输入和输出的张量形状
#print(imgs.shape) # 输入图像的形状:torch.Size([64, 3, 32, 32])
#print(output.shape) # 输出形状:torch.Size([64, 6, 30, 30])
# 将输入图像记录到 TensorBoard
writer.add_images("input", imgs, step) # 记录输入图像,标签为 'input'
#output=torch.reshape(output,(-1,3,30,30))
output = output[:, :3, :, :]
# 输出的形状是 torch.Size([64, 6, 30, 30]),应直接记录,而不是重塑
# 在这里需要注意的是,输出形状是 [batch_size, out_channels, height, width]
# 直接将其添加到 TensorBoard 中
writer.add_images("output", output, step) # 记录输出图像,标签为 'output'
step += 1 # 步骤计数器加1
writer.close()
dilation 空洞卷积。池化函数使用某一位置的相邻输出的总体统计特征来代替网络在该位置的输出。本质是 降采样,可以大幅减少网络的参数量。
import torch
import torchvision
from torch import nn
from torch.nn import MaxPool2d
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
# 加载 CIFAR-10 数据集,使用 ToTensor 转换为张量格式
dataset = torchvision.datasets.CIFAR10("../data", train=False, download=True,
transform=torchvision.transforms.ToTensor())
# 创建数据加载器,以便从数据集中按批次加载数据
dataloader = DataLoader(dataset, batch_size=64)
# 定义简单的卷积神经网络类
class Tudui(nn.Module):
def __init__(self):
super(Tudui, self).__init__()
# 定义最大池化层,池化核大小为 3,ceil_mode=False 表示使用 floor 整除
self.maxpool1 = MaxPool2d(kernel_size=3, ceil_mode=False)
def forward(self, input):
# 定义前向传播过程
output = self.maxpool1(input)
return output
# 实例化模型
tudui = Tudui()
# 创建 TensorBoard 的 SummaryWriter
writer = SummaryWriter("G:/ML/logs_maxpool")
# 初始化步骤计数器
step = 0
# 遍历数据加载器,获取数据批次
for data in dataloader:
imgs, targets = data # imgs 是输入图像,targets 是对应的标签
# 将原始输入图像写入 TensorBoard
writer.add_images("input", imgs, step)
# 将图像传入模型,获取池化后的输出
output = tudui(imgs)
# 将池化后的单通道图像扩展为 3 通道以便写入 TensorBoard
output = output.repeat(1, 3 // output.shape[1], 1, 1)
# 将池化后的输出图像写入 TensorBoard
writer.add_images("output", output, step)
# 步骤计数器加 1
step += 1
# 关闭 TensorBoard 的 SummaryWriter
writer.close()
非线性激活
import torch
import torchvision
from torch import nn
from torch.nn import ReLU, Sigmoid
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
# 定义一个输入张量,包含一些示例数据
input_tensor = torch.tensor([[1, -0.5], # 第一个样本的两个特征
[-1, 3]]) # 第二个样本的两个特征
# 将输入张量重塑为四维张量,形状为 (batch_size, channels, height, width)
# 这里使用 -1 来自动推断 batch_size,因此最终形状为 (2, 1, 2, 2)
input_tensor = torch.reshape(input_tensor, (-1, 1, 2, 2))
print(input_tensor.shape) # 打印重塑后的形状,应该是 torch.Size([2, 1, 2, 2])
# 加载 CIFAR-10 数据集,使用 ToTensor 转换为张量格式
# 这里的 train=False 表示加载测试集,download=True 表示如果数据集不存在则下载
dataset = torchvision.datasets.CIFAR10("../data", train=False, download=True,
transform=torchvision.transforms.ToTensor())
# 创建数据加载器,以便从数据集中按批次加载数据
# batch_size=64 表示每次加载64张图像
dataloader = DataLoader(dataset, batch_size=64)
# 定义一个简单的卷积神经网络类
class Tudui(nn.Module):
def __init__(self):
super(Tudui, self).__init__() # 调用父类构造函数
self.relu1 = ReLU() # 定义 ReLU 激活函数
self.sigmoid1 = Sigmoid() # 定义 Sigmoid 激活函数
def forward(self, input):
# 定义前向传播过程
output = self.relu1(input) # 将输入数据通过 ReLU 激活
output = self.sigmoid1(output) # 然后通过 Sigmoid 激活
return output # 返回处理后的输出
# 实例化网络模型
tudui = Tudui()
# 创建 TensorBoard 的 SummaryWriter,用于记录训练过程中的数据
# 指定日志保存路径
writer = SummaryWriter("G:/ML/logs_relu")
step = 0 # 初始化步骤计数器
# 遍历数据加载器,获取数据批次
for data in dataloader:
imgs, targets = data # imgs 是输入图像,targets 是对应的标签
writer.add_images("input", imgs, global_step=step) # 记录输入图像到 TensorBoard,标签为 'input'
output = tudui(imgs) # 将输入图像传递给模型,获取输出
writer.add_images("output", output, step) # 记录输出图像到 TensorBoard,标签为 'output'
step += 1 # 步骤计数器加1,表示记录了一个新的批次
# 关闭 SummaryWriter,保存所有记录的数据
writer.close()
模型选择
训练误差
在训练的数据集中的误差,类似于模考
泛化误差
在测试的结果数据集中的误差,需要重点关注,类似于高考
正则化
权重衰退(Weight Decay) 是机器学习和深度学习中常用的一种正则化技术,主要用于防止模型过拟合。
权重衰退的基本概念
在训练神经网络时,模型通过最小化损失函数来更新其权重(参数)。如果模型的参数(权重)过大,模型可能会在训练数据上表现得非常好,但在测试数据上表现不佳,导致过拟合。权重衰退正是通过对权重施加一个约束,来防止模型在训练过程中产生过大的权重。
权重衰退的核心思想是将损失函数加入权重的惩罚项,约束模型的权重大小,使得权重值不会变得过大,以提高模型的泛化能力。
数学表示
设模型的原始损失函数为 L ( θ ) L(\theta) L(θ),其中 θ \theta θ 是模型的参数向量。为了加入权重衰退,通常会引入一个惩罚项。常见的权重衰退方式有两种:L2正则化 和 L1正则化。
-
L2正则化(最常用):
- 在损失函数中加入权重的 L 2 L2 L2 范数惩罚项:
L n e w ( θ ) = L ( θ ) + λ ∑ i θ i 2 L_{new}(\theta) = L(\theta) + \lambda \sum_{i} \theta_i^2 Lnew(θ)=L(θ)+λi∑θi2
其中, λ \lambda λ 是正则化系数,用来控制惩罚项的大小。 ∑ i θ i 2 \sum_{i} \theta_i^2 ∑iθi2 是权重的平方和,即 L 2 L2 L2 范数。
含义: L 2 L2 L2 正则化通过将较大的权重拉向接近于零的值,从而限制模型复杂度,使得模型对输入数据更平滑。
-
L1正则化:
- 在损失函数中加入权重的 L 1 L1 L1 范数惩罚项:
L n e w ( θ ) = L ( θ ) + λ ∑ i ∣ θ i ∣ L_{new}(\theta) = L(\theta) + \lambda \sum_{i} |\theta_i| Lnew(θ)=L(θ)+λi∑∣θi∣
含义: L 1 L1 L1 正则化鼓励权重变为零,这意味着它更倾向于产生稀疏权重向量,在某些情况下可以用于特征选择。
权重衰退的效果
权重衰退通过限制权重的大小,减小模型的复杂度,进而避免模型过度拟合训练数据,提高模型的泛化能力。具体效果有:
- 防止过拟合:通过对权重值的惩罚,使得模型不会依赖于训练数据的噪声,从而提升在测试集上的表现。
- 增加泛化能力:权重衰退可以使模型对未见数据的表现更为鲁棒,减小模型对小的扰动或噪声的敏感度。
物理解释
权重衰退的物理意义可以理解为在优化过程中引入了“摩擦力”。就像物理系统中摩擦力会阻止物体无限制加速,权重衰退通过施加“阻力”来防止模型参数在训练中变得过大,以保证模型的稳定性和鲁棒性。
梯度爆炸(Gradient Explosion)和梯度消失(Gradient Vanishing)是深度学习中常见的问题,尤其在训练深层神经网络时。以下是对这两个现象的详细解释:
在这里插入代码片
数值稳定性(Gradient Vanishing)
核心解决问题:
- 梯度设置在合理的范围内
- 乘法变加法:残差连接,LSTM
- 归一化:梯度归一化
- 合理的权重初始和激活函数
梯度消失
梯度消失是指在反向传播过程中,随着层数的增加,梯度逐渐变得非常小,接近于零。这导致前面的层无法有效更新参数,从而影响模型的学习能力。
原因
- 激活函数的选择:使用 Sigmoid 或 Tanh 函数时,当输入值很大或很小的时候,梯度接近于零。这使得网络在训练时,前面的层几乎不更新。
- 网络结构:深层网络结构会导致梯度在反向传播时被逐层“压缩”,使得早期层的梯度非常小。
解决方法
- 使用 ReLU 激活函数:ReLU(Rectified Linear Unit)在正区间有常数梯度,避免了梯度消失的问题。
- Batch Normalization:标准化每一层的输入,使得梯度在训练过程中更稳定。
- 残差网络(ResNet):通过跳跃连接(skip connections)直接将输入信息传递到较深的层,减少信息的损失。
梯度爆炸(Gradient Explosion)
定义
梯度爆炸是指在反向传播过程中,梯度迅速增大,导致模型权重更新过大,最终导致模型不收敛或训练不稳定。
原因
- 网络结构:过深的网络或不适当的初始化可以导致梯度爆炸。
- 激活函数的选择:使用某些激活函数(如 Sigmoid)可能会导致在特定条件下梯度快速增大。
- 学习率过高:如果学习率设置过高,可能会使得每次参数更新过大,导致梯度爆炸。
解决方法
- 梯度裁剪(Gradient Clipping):在反向传播时,限制梯度的最大值,防止梯度过大。
- 使用合适的学习率:使用自适应学习率优化算法(如 Adam)可以帮助防止梯度爆炸。
- 权重初始化:采用合适的权重初始化方法,如 Xavier 初始化或 He 初始化,以防止初始阶段梯度过大。
总结
- 梯度消失:随着层数增加,梯度逐渐减小,影响学习能力。
- 梯度爆炸:梯度迅速增大,导致参数更新过大,训练不稳定。
了解这两种现象及其解决方法,有助于设计更有效的深度学习模型,提高训练的稳定性和效率。