神经网络-pytorch版本

pytorch神经网络基础

torch简介

torch和numpy

python 复制代码
import torch
import numpy as np
np_data=np.arange(6).reshape((2,3))
torch_data=torch.from_numpy(np_data)
tensor2array=torch_data.numpy()
print(np_data,"\n",torch_data,"\n",tensor2array)

torch的数学运算

python 复制代码
data=[-1,-2,1,2]
tensor=torch.FloatTensor(data)
# 绝对值abs
print(np.abs(data),"\n",torch.abs(tensor))
#三角函数sin
print(np.sin(data),"\n",torch.sin(tensor))
#mean均值
print(np.mean(data),"\n",torch.mean(tensor))

# np.dot (x,y)是用于计算两个数组的点积,即对应元素相乘再相加,
# 而np.matmul (x,y)则是用于计算两个数组的矩阵乘积
data=[[1,2],[3,4]]
tensor=torch.FloatTensor(data)
print(np.matmul(data,data),"\n",torch.mm(tensor,tensor))

变量(variable)

在PyTorch中,Variable在早期版本中用于自动求导(autograd)的功能,但从PyTorch 0.4.0版本开始,Variable已经被弃用,取而代之的是Tensor的功能。因此,如果你使用的是PyTorch 0.4.0或更新版本,你不再需要使用Variable

以下是Variable和普通的变量(如Tensor)之间的一些区别,以及在新版本中的情况:

  1. 自动求导(Autograd):

    • Variable在早期版本中主要用于自动求导。你可以使用Variable包装一个Tensor,并在其上进行操作,PyTorch会跟踪这些操作以计算梯度。但在新版本中,Tensor自身具有自动求导功能,无需Variable
  2. In-Place操作:

    • 在早期版本中,Variable支持in-place操作(如var.data += 1),但这不是推荐的做法。在新版本中,in-place操作被明确禁止,以避免梯度计算中的错误。你应该使用.data属性来访问Tensor的数据,并避免in-place操作。
  3. 性能:

    • Variable的引入会增加一些额外的开销,因为它需要跟踪操作以进行自动求导。在新版本中,Tensor的性能更高,因为它不再需要这些额外的开销。

所以,总的来说,在PyTorch的新版本中,你应该使用Tensor而不是Variable来处理数据,因为Tensor包括了Variable的所有功能,而且性能更好。如果你在早期版本中使用Variable,你可能需要考虑升级你的代码以适应新的PyTorch版本。

什么是激励函数(activation function)

其实就是另外一个非线性函数. 比如说relu, sigmoid, tanh. 使得输出结果 y 也有了非线性的特征.

激励函数(activation)

一句话概括 Activation: 就是让神经网络可以描述非线性问题的步骤, 是神经网络变得更强大.

python 复制代码
import torch
import torch.nn.functional as F  # 激励函数都在这里

# 做一些假数据来观看图像
x=torch.linspace(-5,5,100)  # x data (tensor), shape=(100, 1)
print(x)
x_np=x.numpy()  # 换成 numpy array, 出图时用

# 几种常用的激励函数
y_relu=F.relu(x).numpy()
y_sigmoid=torch.sigmoid(x).numpy()
y_sigmoid=F.sigmoid(x).numpy()
y_tanh=torch.tanh(x).numpy()
y_softplus=F.softplus(x).numpy()

# 画图部分还没看matplotlib,后续再补充
import matplotlib.pyplot as plt  # python 的可视化模块, 我有教程 (https://mofanpy.com/tutorials/data-manipulation/plt/)

plt.figure(1, figsize=(8, 6))
plt.subplot(221)
plt.plot(x_np, y_relu, c='red', label='relu')
plt.ylim((-1, 5))
plt.legend(loc='best')

plt.subplot(222)
plt.plot(x_np, y_sigmoid, c='red', label='sigmoid')
plt.ylim((-0.2, 1.2))
plt.legend(loc='best')

plt.subplot(223)
plt.plot(x_np, y_tanh, c='red', label='tanh')
plt.ylim((-1.2, 1.2))
plt.legend(loc='best')

plt.subplot(224)
plt.plot(x_np, y_softplus, c='red', label='softplus')
plt.ylim((-0.2, 6))
plt.legend(loc='best')

plt.show()


torch.sigmoid(x)F.sigmoid(x) 都是计算输入张量 x 上的 sigmoid 函数,它们的输出结果是一样的。在最新版本的PyTorch中,通常建议使用 torch.sigmoid(x),而不再需要导入torch.nn.functional中的F.sigmoid(x),因为PyTorch的最新版本中已经将这些激活函数作为张量的方法进行了实现。

所以,以下两行代码是等价的:

python 复制代码
y_sigmoid = torch.sigmoid(x).numpy()
y_sigmoid = F.sigmoid(x).numpy()  # 在最新版本的PyTorch中也是有效的,但不推荐

通常情况下,建议使用 torch.sigmoid(x),因为它更符合PyTorch的现代风格。

建造第一个神经网络

关系拟合(回归)

要点

看神经网络是如何通过简单的形式将一群数据用一条线条来表示。

或者说, 是如何在数据当中找到他们的关系, 然后用神经网络模型来建立一个可以代表他们关系的线条。

建立数据集

python 复制代码
import torch
import matplotlib.pyplot as plt

x=torch.unsqueeze(torch.linspace(-1,1,100),dim=1)
y=x.pow(2)+0.2*torch.rand_like(x)

plt.scatter(x.numpy(),y.numpy())
plt.show()
x=torch.unsqueeze(torch.linspace(-1,1,100),dim=1)

这行代码使用了PyTorch库来创建一个包含100个元素的张量(tensor),该张量的值是从-1到1均匀分布的。

让我们一步一步来解释这行代码:

  1. torch.linspace(-1, 1, 100):这部分代码使用torch.linspace函数创建一个从-1到1的等间距数列,其中包含100个数值。torch.linspace函数的第一个参数是起始值(-1),第二个参数是结束值(1),第三个参数是要生成的数值的数量(100)。这将生成一个张量,其中包含了从-1到1之间的100个均匀分布的数值。

  2. torch.unsqueeze(..., dim=1):接下来,torch.unsqueeze函数被用来在张量中添加一个维度(dimension)。dim=1参数指定了要在哪个位置添加维度。在这里,我们将在第1维(即列维度)上添加一个维度。这将把原始的一维张量(形状为(100,))变成一个二维张量(形状为(100, 1)),其中有100行和1列。

最终的结果是一个形状为(100, 1)的二维张量,其中包含了从-1到1均匀分布的100个数值,每个数值都位于独立的行中。这种形式的张量通常在机器学习和深度学习中用于输入数据的处理,特别是当处理单个特征的数据时,常常需要将其转换为二维张量。

y=x.pow(2)+0.2*torch.rand_like(x)

这一行首先计算了 x 的平方,然后加上了一些噪声。让我们逐步解释:

  • x.pow(2):这部分代码计算了 x 中每个元素的平方。因为 x 是一个 PyTorch 张量,所以这个操作会对 x 中的每个元素都进行平方操作,生成一个新的张量,形状与 x 相同,但每个元素都是原来的元素的平方。

  • 0.2*torch.rand_like(x):这部分代码生成了一个与 x 具有相同形状的张量,但其中的每个元素都是从均匀分布 [0, 1) 中随机采样的数值,并且每个数值都乘以0.2。这个操作引入了一些噪声,将其与平方项相加,以创建一个新的张量 y

最终,y 包含了与 x 对应的平方项加上一些随机噪声,因此它是一个形状相同的二维张量,其中包含了与 x 相关的数值。这种操作常见于机器学习中的回归任务,其中 x 可能表示输入特征,而 y 表示对应的目标值(或标签),噪声用于模拟现实世界中的不确定性和随机性。这种情况下,模型的任务是拟合从 xy 的映射关系。

建立神经网络

python 复制代码
# 建立神经网络
import torch
import torch.nn as nn
class Net(nn.Module):
    # 搭建我们的层数时要用到的信息
    def __init__(self,n_feature,n_hidden,n_output):
        super(Net,self).__init__()   #可以暂时理解为初始化,这块要弄清楚是有难度的
        self.hidden=nn.Linear(n_feature,n_hidden)#隐藏层,关心有多少个输入,有多少个神经元个数
        self.predict=nn.Linear(n_hidden,n_output)#输出层,关心有多少个输入(从隐藏层而来的神经元),有多少个输出
    # 神经网络前向传递的过程,真正开始搭建神经网络
    def forward(self,x):
        x=torch.relu(self.hidden(x))#我们的x过了一个hidden层,输出了n_hidden个神经元的个数
        x=self.predict(x) #输出层接受x并输出结果
        # 为什么我们的预测不用激励函数呢。因为在大多数的情况下回归问题的取值可以从负无穷到正无穷,
        #而用了激励函数,会把取值范围截断一部分
        return x
net=Net(n_feature=1,n_hidden=10,n_output=1)
print(net)

在这段代码中,self.hiddenself.predict 是神经网络模型 Net 类的成员变量(或属性),它们分别用来表示神经网络的隐藏层和输出层。这些成员变量的值来自于PyTorch中的 nn.Linear 类,它是PyTorch中用于创建线性层的类。

让我更详细地解释这些成员变量的来源:

  1. self.hidden = nn.Linear(n_feature, n_hidden)

    • 这一行代码创建了一个名为 hidden 的成员变量,并将其初始化为一个 nn.Linear 对象。
    • nn.Linear 类表示一个线性层,通常用于神经网络的前向传播。这个线性层将具有 n_feature 个输入特征和 n_hidden 个神经元。
    • 在神经网络的前向传播过程中,输入数据将通过 self.hidden 这个线性层,并且在传递时会经过线性变换,然后传递给激活函数(在你的代码中是ReLU),最终产生隐藏层的输出。
  2. self.predict = nn.Linear(n_hidden, n_output)

    • 类似地,这一行代码创建了一个名为 predict 的成员变量,并将其初始化为另一个 nn.Linear 对象。
    • 这个 nn.Linear 对象表示输出层,它接受来自隐藏层的输出(n_hidden 个输入特征)并生成模型的最终输出,通常在回归任务中,输出层只有一个神经元。

这些 nn.Linear 层是PyTorch中提供的模块,它们封装了神经网络中的线性变换操作,可以自动进行权重初始化以及在模型的反向传播中计算梯度。这简化了神经网络的构建和训练过程,因为你只需要定义网络的结构,而不必手动管理权重和梯度。这些操作都由PyTorch的自动微分系统自动处理。

训练网络

python 复制代码
# 训练网路
import torch
import torch.nn as nn
import torch.optim as optim
# 创建优化器和损失函数
optimizer=optim.SGD(net.parameters(),lr=0.2)
loss_func=nn.MSELoss()
for t in range(100):
    prediction=net(x)               # 喂给net训练数据x,输出预测值
    loss=loss_func(prediction,y)    # 计算两者的误差
    optimizer.zero_grad()           # 清空上一步的残余更新参数值
    loss.backward()                 # 误差反向传播,计算参数更新值
    optimizer.step()                # 将参数更新值施加到net的parameters上
    print(loss)

可视化训练过程

python 复制代码

后面学了matplotlib再放

区分类型(分类)

要点

我们来看看神经网络是怎么进行事物的分类

建立数据集

python 复制代码
import torch
import matplotlib.pyplot as plt

# 生成随机数据
n_data = torch.ones(100, 2)
print(n_data[:5])
x0 = torch.normal(2 * n_data, 1)
print(x0[:5])
y0 = torch.zeros(100, dtype=torch.long)
print(y0[:5])
x1 = torch.normal(-2 * n_data, 1)
print(x1[:5])
y1 = torch.ones(100, dtype=torch.long)
print(y1[:5])

# 合并数据
x = torch.cat((x0, x1), 0)
print(x[:5])
y = torch.cat((y0, y1))
print(y)
# 绘制散点图
plt.scatter(x[:, 0], x[:, 1], c=y, s=100, lw=0, cmap='RdYlGn')
plt.show()


在PyTorch中,数据类型(dtype)是一个非常重要的概念,它指定了张量中元素的数据类型。在代码中,y0 = torch.zeros(100, dtype=torch.long) 将创建一个包含100个零的张量,并将其数据类型设置为 torch.long

加入 dtype=torch.long 的原因是,通常在深度学习中,torch.long 数据类型被用来表示整数类型的标签或类别。在分类任务中,标签通常是整数,每个整数代表一个不同的类别。例如,如果你正在处理一个图像分类任务,每个图像的类别标签可以是从0到(类别总数-1)的整数,这些整数用来表示不同的类别。

设置数据类型为 torch.long 有两个主要作用:

  1. 确保整数类型的存储torch.long 数据类型确保了张量中的元素都是整数。这是因为默认情况下,PyTorch 创建的张量可能具有 torch.float32(32位浮点数)数据类型,但类别标签应该是整数,因此需要显式设置数据类型为 torch.long 以确保它们被解释为整数。

  2. 与损失函数兼容 :许多损失函数(例如交叉熵损失)要求标签是整数类型,因此将标签的数据类型设置为 torch.long 可以确保它们与损失函数兼容,而不会引发数据类型不匹配的错误。

总之,dtype=torch.long 的设置是为了明确指定标签张量的数据类型为整数类型,以确保与深度学习任务中的标签和损失函数兼容。如果标签是整数,通常都应该使用这种设置。

torch.cat() 是PyTorch中的一个张量拼接函数,用于将多个张量沿指定的维度进行拼接(连接)操作。这个函数的目的是将多个张量合并成一个更大的张量。让我们详细解释一下 torch.cat() 函数的用法和参数:

python 复制代码
torch.cat(tensors, dim=0, out=None) -> Tensor

参数说明:

  • tensors:一个包含要拼接的张量(可以是一个张量列表或元组)。
  • dim:指定拼接的维度。默认是0,表示在第一个维度上进行拼接,即按行拼接。
  • out:可选参数,用于指定输出张量,如果不提供将会创建一个新的张量来保存拼接后的结果。

示例:

python 复制代码
import torch

# 创建两个张量
x = torch.tensor([[1, 2], [3, 4]])
y = torch.tensor([[5, 6]])

# 在第一个维度上拼接(按行拼接)
result = torch.cat((x, y), dim=0)
print(result)

在这个示例中,我们创建了两个张量 xy,然后使用 torch.cat() 函数将它们在第一个维度(按行)上进行拼接。结果是一个新的张量 result,其中包含了两个原始张量的内容:

tensor([[1, 2],
        [3, 4],
        [5, 6]])

torch.cat() 可以用于在任何维度上进行拼接操作,你可以根据需要选择合适的维度。这在深度学习中经常用于连接不同批次的数据、连接不同特征的数据等各种情况。

python 复制代码
plt.scatter(x[:, 0], x[:, 1], c=y, s=100, lw=0, cmap='RdYlGn')

这行代码使用了Matplotlib库的 scatter 函数来创建一个散点图,以可视化数据点。让我们逐个参数来解释这行代码:

  1. x[:, 0]x[:, 1]:这部分代码使用索引操作从 x 中选择特定的列。假设 x 是一个二维张量,这里 x[:, 0] 表示选择所有行的第一个特征,而 x[:, 1] 表示选择所有行的第二个特征。这将分别用作散点图的 x 和 y 坐标。

  2. c=y:这是 c 参数,用于设置散点的颜色。通常,c 接受一个数组,表示每个数据点的颜色。在这里,y 可能是一个数组,其中包含与 x 对应的类别或标签。这将根据标签值决定每个数据点的颜色,以区分不同的数据类别。

  3. s=100:这是 s 参数,用于设置散点的大小。在这里,所有的散点都设置为相同的大小,大小为100。

  4. lw=0:这是 lw 参数,用于设置散点的边界线宽度(linewidth)。在这里,将边界线的宽度设置为0,表示没有边界线。

  5. cmap='RdYlGn':这是 cmap 参数,用于设置颜色映射。它指定了用于将不同的标签值映射到不同颜色的颜色映射表。在这里,使用了名为 'RdYlGn' 的颜色映射,它代表了一种从红色到黄色再到绿色的颜色渐变。

综合起来,这行代码创建了一个散点图,其中每个数据点的 x 和 y 坐标由 x 的两个特征决定,颜色由 y 的值决定,散点的大小相同,没有边界线,并且使用 'RdYlGn' 的颜色映射表来表示不同的标签值。这种可视化方式有助于可视化数据的分布和类别信息,特别是在二维空间中。

建立神经网络

python 复制代码
# 建立神经网络
import torch
import torch.nn as nn
class Net(nn.Module):
    def __init__(self,n_feature,n_hidden,n_output):
        super(Net,self).__init__()
        self.hidden=nn.Linear(n_feature,n_hidden)
        self.out=nn.Linear(n_hidden,n_output)
    def forward(self,x):
        x=torch.relu(self.hidden(x))
        x=self.out(x)
        return x
net=Net(n_feature=2,n_hidden=10,n_output=2)
print(net)

训练网络

python 复制代码
# 训练网络

import torch.optim as optim

# 创建优化器和损失函数
optimizer=optim.SGD(net.parameters(),lr=0.02)
loss_func=nn.CrossEntropyLoss()

for t in range(100):
    out=net(x)#喂给net训练数据x,输出分析值
    loss=loss_func(out,y)#计算两者的误差
    optimizer.zero_grad()#清空上一步的残余更新参数值
    loss.backward()#误差反向传播,计算参数更新值
    optimizer.step()#将参数更新值施加到net的parameters上
    print(loss)

可视化训练过程

python 复制代码

后续再补充

快速搭建法

要点

Torch 中提供了很多方便的途径, 同样是神经网络, 能快则快, 我们看看如何用更简单的方式搭建同样的回归神经网络.

快速搭建

我们之前是这样子搭建神经网络

python 复制代码
import torch
import torch.nn as nn
class Net(nn.Module):
    # 搭建我们的层数时要用到的信息
    def __init__(self,n_feature,n_hidden,n_output):
        super(Net,self).__init__()   #可以暂时理解为初始化,这块要弄清楚是有难度的
        self.hidden=nn.Linear(n_feature,n_hidden)#隐藏层,关心有多少个输入,有多少个神经元个数
        self.predict=nn.Linear(n_hidden,n_output)#输出层,关心有多少个输入(从隐藏层而来的神经元),有多少个输出
    # 神经网络前向传递的过程,真正开始搭建神经网络
    def forward(self,x):
        x=torch.relu(self.hidden(x))#我们的x过了一个hidden层,输出了n_hidden个神经元的个数
        x=self.predict(x) #输出层接受x并输出结果
        # 为什么我们的预测不用激励函数呢。因为在大多数的情况下回归问题的取值可以从负无穷到正无穷,
        #而用了激励函数,会把取值范围截断一部分
        return x
net=Net(n_feature=1,n_hidden=10,n_output=1)
print(net)

上面我们用 class 继承了一个 torch 中的神经网络结构, 然后对其进行了修改, 不过还有更快的方法

python 复制代码
import torch.nn as nn
net=nn.Sequential(
    nn.Linear(1,10),
    nn.ReLU(),
    nn.Linear(10,1)
)
print(net)

两种方法都可以用于构建神经网络,每种方法有其优点和适用场景。让我解释一下它们的优缺点以及何时使用哪种方法:

1. 自定义类方法(第一个示例):

  • 优点:

    • 更灵活:你可以定义自己的前向传播逻辑,可以实现非常复杂的模型结构。
    • 易于理解和调试:因为你可以明确地看到每个层的定义和前向传播的计算过程,更容易理解和调试。
  • 缺点:

    • 代码量较多:相对来说,需要编写更多的代码来定义每个层和前向传播的逻辑。
    • 可读性较差:对于简单的模型,可能会感到冗长,不够紧凑。

2. Sequential 方法(第二个示例):

  • 优点:

    • 简洁:使用 nn.Sequential 可以更紧凑地定义模型结构,特别适合简单的线性模型。
    • 减少代码量:避免了手动定义每个层的繁琐过程。
  • 缺点:

    • 有限灵活性:nn.Sequential 适用于线性堆叠的模型结构,对于复杂的模型结构和非线性连接不够灵活。
    • 难以定制:如果需要在不同层之间添加自定义操作或者非线性激活函数,使用 nn.Sequential 可能会不方便。

何时使用哪种方法取决于你的需求:

  • 自定义类方法 更适合需要灵活性的情况,例如:

    • 当你需要定义复杂的非线性模型结构时。
    • 当你想要添加自定义操作或层之间的复杂连接时。
    • 当你需要更详细的控制权和可调试性。
  • Sequential 方法 更适合简单的线性模型或者需要紧凑表示的情况,例如:

    • 当你构建简单的线性堆叠模型时(例如,多层感知器)。
    • 当你只需要一系列简单的层按顺序连接,不需要复杂的结构。

通常,如果你的模型足够简单,可以使用 nn.Sequential 来简化代码。但如果你需要更高级的功能、更复杂的模型结构或者更多的控制权,那么自定义类方法会更有优势。在实践中,你可能会在不同的情况下使用这两种方法,根据任务的复杂性和要求来选择合适的方式。

保存提取

要点

训练好了一个模型, 我们当然想要保存它, 留到下次要用的时候直接提取直接用, 这就是这节的内容啦. 我们用回归的神经网络举例实现保存提取.

保存

我们先快速建造数据,搭建网络

python 复制代码
import torch
import torch.nn as nn
import torch.optim as optim

# 设置随机种子以确保可重复性
torch.manual_seed(1)

#生成假数据
x=torch.unsqueeze(torch.linspace(-1,1,100),dim=1)
y=x.pow(2)+0.2*torch.rand(x.size())

def save():
    # 建立网络
    net1=nn.Sequential(
        nn.Linear(1,10),
        nn.ReLU(),
        nn.Linear(10,1)
    )

    # 定义优化器和损失函数
    optimizer=optim.SGD(net1.parameters(),lr=0.5)
    loss_func=nn.MSELoss()

    # 训练
    for t in range(100):
        prediction=net1(x)
        loss=loss_func(prediction,y)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    

代码解释:

  1. x = torch.unsqueeze(torch.linspace(-1, 1, 100), dim=1)

    • torch.linspace(-1, 1, 100) 创建了一个包含100个均匀分布在-1到1之间的数值的张量。这些值将作为输入特征 x
    • torch.unsqueeze() 函数将这个一维张量转换为一个二维张量。具体来说,dim=1 参数表示在第1维上添加一个维度,从而将原始的一维张量转换为形状为 (100, 1) 的二维张量,这样可以作为神经网络的输入。
  2. y = x.pow(2) + 0.2 * torch.rand(x.size())

    • x.pow(2) 对输入特征 x 中的每个元素进行平方操作,从而得到一个张量,其中包含了100个元素,每个元素都是输入特征的平方。
    • torch.rand(x.size()) 创建一个与 x 相同大小的随机张量,其中的值是在0到1之间均匀分布的随机数。这个随机张量表示添加到目标 y 中的噪声。
    • 最后,通过将 x.pow(2) 和随机噪声相加,得到最终的目标张量 y。这个操作在原始函数 x^2 的基础上引入了一些随机变化,模拟了真实世界中的数据噪声。

总之,这段代码用于生成一个带有噪声的数据集,其中 x 是输入特征,表示在-1到1之间均匀分布的值,y 是与 x 之间的二次函数关系,并添加了一些随机噪声,以模拟实际数据集。这种数据集常用于回归任务的训练和测试。

接下来我们有两种途径来保存(下面的代码是写在save()里的)

python 复制代码
# 保存整个网络:
torch.save(net1,'net1.pkl')
# 仅保存网络中的参数(推荐的方式,因为速度更快且占用内存更少):
torch.save(net1.state_dict(),'net1_params.pkl')

提取网络

python 复制代码
# 读取模型
def restore_net():
    net2=torch.load('net1.pkl')
    print(net2)

只提取网络参数

python 复制代码
def restore_params():
    # 新建net3,确保与之前的模型架构一致
    net3=torch.nn.Sequential(
        torch.nn.Linear(1,10),
        torch.nn.ReLU(),
        torch.nn.Linear(10,1)
    )
    # 将保存的参数复制到net3
    net3.load_state_dict(torch.load('net1_params.pkl'))
    print(net3)

显示结果

python 复制代码
# 保存 net1 (1. 整个网络, 2. 只有参数)
save()

# 提取整个网络
restore_net()

# 提取网络参数, 复制到新网络
restore_params()

批训练

要点

Torch 中提供了一种帮你整理你的数据结构的东西,,叫做 DataLoader, 我们能用它来包装自己的数据,进行批训练。

DataLoader

DataLoader 是 torch 给你用来包装你的数据的工具. 所以你要讲自己的 (numpy array 或其他) 数据形式装换成 Tensor, 然后再放进这个包装器中. 使用 DataLoader 有什么好处呢? 就是他们帮你有效地迭代数据,

python 复制代码
import torch
from torch.utils.data import DataLoader, TensorDataset

torch.manual_seed(1)  # 设置随机种子以实现可重复性

BATCH_SIZE = 5  # 批训练的数据个数

x = torch.linspace(1, 10, 10)
y = torch.linspace(10, 1, 10)

# 创建一个Dataset对象
dataset = TensorDataset(x, y)

# 创建一个DataLoader对象
loader = DataLoader(
    dataset=dataset,
    batch_size=BATCH_SIZE,
    shuffle=True,  # 要不要打乱数据(打乱比较好)
    num_workers=2,  # 多线程来读数据
)

for epoch in range(3):  # 训练所有整套数据3次
    # enumerate()函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在for循环当中。
    for step, (batch_x, batch_y) in enumerate(loader):  # 每一步 loader 释放一小批数据用来学习,enumerate会帮你把数据加个索引
        ###这里就是你训练的地方

        # 模拟打印出来的数据
        print('Epoch:', epoch, '| Step:', step, '| batch_x:', batch_x.numpy(), '| batch_y:', batch_y.numpy())

这段看上去没有问题,但是我们运行的话会报错

大概是类似这样子的错误

RuntimeError: 
        An attempt has been made to start a new process before the
        current process has finished its bootstrapping phase.

        This probably means that you are not using fork to start your
        child processes and you have forgotten to use the proper idiom
        in the main module:

            if __name__ == '__main__':
                freeze_support()
                ...

        The "freeze_support()" line can be omitted if the program
        is not going to be frozen to produce an executable.
    _check_not_importing_main()

这个错误是由于在Windows操作系统上使用多进程数据加载器时引发的,通常是由于操作系统限制导致的。要解决这个问题,你可以尝试以下几种方法之一:

  1. if __name__ == '__main__':块内运行代码 :在Windows上使用多进程时,通常需要将代码放在一个特定的if __name__ == '__main__':块内。这可以通过以下方式修改你的代码:

    python 复制代码
    import torch
    from torch.utils.data import DataLoader, TensorDataset
    import numpy as np
    
    def main():
        torch.manual_seed(1)  # 设置随机种子以实现可重复性
    
        BATCH_SIZE = 5  # 批训练的数据个数
    
        x = torch.linspace(1, 10, 10)
        y = torch.linspace(10, 1, 10)
    
        # 创建一个Dataset对象
        dataset = TensorDataset(x, y)
    
        # 创建一个DataLoader对象
        loader = DataLoader(
            dataset=dataset,
            batch_size=BATCH_SIZE,
            shuffle=True,  # 要不要打乱数据(打乱比较好)
            num_workers=2,  # 多线程来读数据
        )
    
        for epoch in range(3):  # 训练所有整套数据3次
            # enumerate()函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在for循环当中。
            for step, (batch_x, batch_y) in enumerate(loader):  # 每一步 loader 释放一小批数据用来学习,enumerate会帮你把数据加个索引
                ###这里就是你训练的地方
    
                # 模拟打印出来的数据
                print('Epoch:', epoch, '| Step:', step, '| batch_x:', batch_x.numpy(), '| batch_y:', batch_y.numpy())
    
    if __name__ == '__main__':
        main()

    这种方式通常可以解决多进程在Windows上的问题。

  2. 设置num_workers=0 :如果你不需要多进程数据加载,可以将num_workers参数设置为0,这将在单个进程中运行数据加载,但可能会降低数据加载的速度:

    python 复制代码
    loader = DataLoader(
        dataset=dataset,
        batch_size=BATCH_SIZE,
        shuffle=False,  # 要不要打乱数据(打乱比较好)
        num_workers=0,  # 在单个进程中加载数据
    )

请尝试上述方法之一,看看哪一个适用于你的情况,并解决这个多进程数据加载器的问题。

for step, (batch_x, batch_y) in enumerate(loader): 中的 (batch_x, batch_y) 是通过 DataLoader 对象的迭代来获取的。DataLoader 在每个循环迭代中会返回一个小批次的数据,其中 batch_x 包含输入数据,而 batch_y 包含目标数据。

这是如何工作的:

  1. enumerate(loader) 会将 loader 转换为一个可迭代对象,允许你在循环中使用 for step, ... 这样的语法。

  2. 在每个循环迭代中,loader 会从数据集中加载一个小批次的数据,具体大小由 BATCH_SIZE 参数定义。

  3. (batch_x, batch_y) 是在每个迭代步骤中通过 loader 提供的,其中 batch_x 包含当前小批次的输入数据,而 batch_y 包含当前小批次的目标数据。这两个张量的形状和大小与你的数据集和 BATCH_SIZE 参数有关。

总之,(batch_x, batch_y) 是在循环迭代中自动从 loader 中提取的,它代表了每个小批次的输入和目标数据,供你在训练循环中使用。你无需手动选择它们,而是通过 for step, ... 循环直接获取。

加速神经网络训练

Stochastic Gradient Descent (SGD)

随机梯度下降(Stochastic Gradient Descent,简称 SGD)是深度学习和机器学习中最基本且常用的优化算法之一。它用于训练神经网络和其他机器学习模型,以最小化损失函数并更新模型的参数,使其能够更好地拟合训练数据。下面是 SGD 的主要特点和工作原理:

  1. 随机性:SGD 是随机性的优化算法,每次迭代使用一个随机选择的小批次(mini-batch)来计算梯度和更新模型参数。这与传统的梯度下降算法不同,传统梯度下降会使用整个训练数据集来计算梯度。

  2. 迭代更新:SGD 对模型参数进行迭代更新,通过计算损失函数对每个参数的梯度(即损失函数关于参数的导数),然后按照梯度的方向和一个学习率的大小来更新参数。这个过程重复进行多次,通常称为"epoch"。

  3. 小批次处理:SGD 将训练数据集分成小批次,每个小批次包含一定数量的训练示例。这有助于加速训练过程和降低内存要求。通常,小批次的大小是一个超参数,可以根据具体问题进行调整。

  4. 随机性和噪声:由于每次迭代都使用了不同的小批次数据,SGD 会引入一定的随机性和噪声。这有时可以帮助模型跳出局部极小值并更快地收敛到全局最小值。

  5. 学习率:学习率是一个重要的超参数,用于控制参数更新的步长大小。过大的学习率可能导致算法不稳定,而过小的学习率可能导致收敛速度过慢。通常,学习率需要进行调整和优化。

SGD 的工作原理可以总结为以下步骤:

  • 从训练数据集中随机选择一个小批次数据(通常是随机抽样的)。
  • 计算损失函数关于这个小批次数据的梯度。
  • 使用梯度和学习率来更新模型参数。
  • 重复上述步骤,直到达到停止条件(如达到一定的迭代次数或损失函数足够小)。

SGD 的优点包括其简单性和适用于大规模数据集的能力。然而,SGD 也有一些缺点,例如它可能会收敛到局部最小值,学习率的选择需要谨慎,以及收敛速度可能相对较慢。因此,研究人员开发了许多改进版本的 SGD,如 Mini-Batch SGD、Momentum SGD、Adagrad、Adam 等,以解决这些问题。这些改进的版本在实际应用中更常见,并且通常能够更快地训练出更好的模型。

Momentum

Momentum(动量)是一种用于改进随机梯度下降(Stochastic Gradient Descent,SGD)优化算法的技术。它的目标是解决 SGD 在更新模型参数时可能出现的震荡和收敛速度慢的问题。

Momentum 的基本思想是引入一个动量项(momentum term),它表示了参数更新的惯性。动量的作用类似于物理中的动量,使参数更新在方向上更加连续,从而减少了在损失函数表面上的震荡。具体来说,Momentum 更新方法的工作原理如下:

  1. 初始化一个动量(momentum)向量为零向量,通常表示为 v

  2. 在每次迭代中,计算当前小批次数据的梯度,并使用动量来更新参数。更新规则如下:

    v = beta * v + (1 - beta) * gradient
    parameter = parameter - learning_rate * v
    
    • beta 是动量系数,通常设置在0.0和1.0之间,它控制了动量的影响力。常见的值是0.9,但可以根据问题进行调整。
    • v 是动量向量,保存了之前迭代中参数更新的累积信息。
    • gradient 是当前小批次数据的梯度。
    • parameter 是要更新的模型参数。
    • learning_rate 是学习率,控制了参数更新的步长大小。
  3. 重复上述步骤,直到达到停止条件(如达到一定的迭代次数或损失函数足够小)。

Momentum 的主要优点包括:

  • 提高了训练速度:由于动量项的引入,参数更新更加连续,减少了在损失函数表面上的震荡,因此通常能够更快地收敛。

  • 改善了参数更新路径:Momentum 使参数更新路径更加平滑,有助于避免 SGD 陷入局部最小值。

  • 减少了学习率的依赖:Momentum 能够在更新方向上增加一定的动量,因此在选择学习率时可以更加宽松,不容易出现学习率设置不当导致的问题。

总之,Momentum 是一种改进的优化方法,通常在训练神经网络和其他机器学习模型时使用,以加速收敛并提高模型的训练效果。它是深度学习中常用的优化算法之一,特别是与 SGD 相比,当处理大规模数据和复杂模型时效果更为明显。

AdaGrad

AdaGrad(Adaptive Gradient)是一种自适应学习率的优化算法,用于训练机器学习模型,特别是在处理稀疏数据时表现出色。它的主要思想是根据每个参数的历史梯度信息来自动调整学习率,从而对每个参数进行不同程度的更新。这可以帮助算法更好地收敛到全局最小值,特别是在非凸损失函数的情况下。

AdaGrad 的工作原理如下:

  1. 初始化一个全局学习率 eta,通常设置为一个小正数,例如0.01。

  2. 对每个模型参数 theta,初始化一个历史梯度累积的变量 G,初始值为0。

  3. 在每次迭代中,计算当前小批次数据的梯度 g

  4. 更新历史梯度累积 G,通过将当前梯度的平方逐元素相加到 G 中:

    G = G + g * g
    
  5. 使用自适应学习率来更新参数 theta

    theta = theta - (eta / sqrt(G + epsilon)) * g
    
    • eta 是全局学习率。
    • epsilon 是一个小正数,通常设置为一个很小的值,例如1e-8,以避免除以零。
    • G 是历史梯度累积,它会逐渐累积每个参数的梯度平方和。
    • g 是当前小批次数据的梯度。
  6. 重复上述步骤,直到达到停止条件(如达到一定的迭代次数或损失函数足够小)。

AdaGrad 的主要优点包括:

  • 自适应学习率:AdaGrad 根据每个参数的历史梯度信息来自动调整学习率,对于频繁出现的参数(即梯度较大的参数),学习率会相应减小,从而更加谨慎地更新这些参数。这使得算法在处理稀疏数据和非凸损失函数时更为有效。

  • 不需要手动调整学习率:相对于传统的梯度下降算法,AdaGrad 不需要手动设置全局学习率,因为它会自动适应不同参数的学习率。

然而,AdaGrad 也有一些缺点,包括:

  • 学习率衰减过快:由于历史梯度累积 G 逐渐增加,学习率可能在训练的早期阶段下降得太快,导致收敛速度变慢。为了解决这个问题,后续的自适应学习率算法如 RMSprop 和 Adam 被提出。

总之,AdaGrad 是一种自适应学习率的优化算法,适用于各种机器学习任务,特别是在处理稀疏数据和非凸损失函数时表现出色。然而,在某些情况下,它可能需要较多的迭代才能收敛,因此后续的算法对其进行了改进。

RMSProp

RMSProp(Root Mean Square Propagation)是一种自适应学习率的优化算法,用于训练机器学习模型,特别是在深度学习中广泛使用。RMSProp 的主要目标是解决 AdaGrad 算法中学习率过早衰减的问题,并改进梯度下降的收敛性。

RMSProp 的工作原理如下:

  1. 初始化一个全局学习率 eta,通常设置为一个小正数,例如0.001。

  2. 对每个模型参数 theta,初始化一个历史梯度平方的指数移动平均值 E[g^2],初始值为0。这里的 g 表示当前小批次数据的梯度。

  3. 在每次迭代中,计算当前小批次数据的梯度 g

  4. 更新历史梯度平方的指数移动平均值 E[g^2]

    E[g^2] = beta * E[g^2] + (1 - beta) * (g^2)
    
    • beta 是一个介于0和1之间的衰减率,通常设置为0.9。它控制了历史梯度平方的权重,使得较早的梯度信息被逐渐遗忘。
  5. 使用自适应学习率来更新参数 theta

    theta = theta - (eta / sqrt(E[g^2] + epsilon)) * g
    
    • eta 是全局学习率。
    • epsilon 是一个小正数,通常设置为一个很小的值,例如1e-8,以避免除以零。
    • E[g^2] 是历史梯度平方的指数移动平均值。
    • g 是当前小批次数据的梯度。
  6. 重复上述步骤,直到达到停止条件(如达到一定的迭代次数或损失函数足够小)。

RMSProp 的优点包括:

  • 自适应学习率:RMSProp 根据每个参数的历史梯度平方信息来自动调整学习率。与 AdaGrad 不同,它避免了学习率过早衰减的问题,因此更适用于深度学习任务。

  • 减少了参数更新的方差:通过引入历史梯度平方的指数移动平均值,RMSProp 能够减少参数更新的方差,从而有助于更稳定的训练。

RMSProp 是深度学习中常用的优化算法之一,特别适用于处理大规模数据和复杂模型。然而,它仍然有一些限制,例如学习率的选择可能需要仔细调整,以及对于非凸损失函数,可能需要更复杂的自适应学习率算法,如 Adam。

Adam

Adam(Adaptive Moment Estimation)是一种自适应学习率的优化算法,它结合了动量法(Momentum)和自适应学习率方法(RMSProp)的优点,被广泛用于深度学习和机器学习中。Adam 的主要目标是解决传统的梯度下降算法中学习率选择不当和模型参数更新方差较大的问题,从而更快地收敛到全局最小值。

Adam 的工作原理如下:

  1. 初始化模型参数 theta

  2. 初始化动量项 m 和梯度平方的指数移动平均值 v,初始值分别为0。

  3. 在每次迭代中,计算当前小批次数据的梯度 g

  4. 更新动量项 m 和梯度平方的指数移动平均值 v

    m = beta1 * m + (1 - beta1) * g
    v = beta2 * v + (1 - beta2) * (g^2)
    
    • beta1beta2 是衰减率,通常设置为0.9 和 0.999,它们控制了动量项 m 和梯度平方 v 的权重,使得较早的信息逐渐遗忘。
  5. 修正动量项和梯度平方的偏差:

    m_hat = m / (1 - beta1^t)
    v_hat = v / (1 - beta2^t)
    
    • t 表示当前迭代的次数。
  6. 使用自适应学习率来更新参数 theta

    theta = theta - (eta / sqrt(v_hat + epsilon)) * m_hat
    
    • eta 是全局学习率。
    • epsilon 是一个小正数,通常设置为一个很小的值,例如1e-8,以避免除以零。
    • m_hat 是修正后的动量项。
    • v_hat 是修正后的梯度平方。
  7. 重复上述步骤,直到达到停止条件(如达到一定的迭代次数或损失函数足够小)。

Adam 的主要优点包括:

  • 自适应学习率:Adam 根据每个参数的历史梯度信息来自动调整学习率,不需要手动设置学习率。

  • 低内存要求:Adam 不需要存储历史梯度平方的完整信息,而只需要存储动量项 m 和梯度平方 v 的指数移动平均值,因此具有较低的内存要求。

  • 高效收敛:Adam 结合了动量法和自适应学习率方法的优点,通常能够更快地收敛到全局最小值。

Adam 是深度学习中常用的优化算法之一,通常在训练神经网络和其他复杂模型时表现出色。然而,与其他优化算法一样,它的性能取决于超参数的选择,因此仍然需要进行调优。

优化器

python 复制代码
import torch
import torch.utils.data as Data
import torch.nn.functional as F
import matplotlib.pyplot as plt

torch.manual_seed(1)

lr=0.01
batch_size=32
epoch=12

# 假数据
x=torch.unsqueeze(torch.linspace(-1,1,1000),dim=1) # unsqueeze用于在指定的维度上增加一个新的维度
y=x.pow(2)+0.1*torch.normal(torch.zeros(*x.size()))

plt.scatter(x.numpy(),y.numpy())
plt.show()

torch_dataset=Data.TensorDataset(x,y)
loader=Data.DataLoader(dataset=torch_dataset,batch_size=batch_size,shuffle=True)

class Net(torch.nn.Module):
    def __init__(self):
        super(Net,self).__init__()
        self.hidden=torch.nn.Linear(1,20)
        self.predict=torch.nn.Linear(20,1)
    def forward(self,x):
        x=F.relu(self.hidden(x))
        x=self.predict(x)
        return x

# 为每个优化器创建一个 net
net_SGD=Net()
net_Momentum=Net()
net_RMSprop=Net()
net_Adam=Net()
nets=[net_SGD,net_Momentum,net_RMSprop,net_Adam]

opt_SGD=torch.optim.SGD(net_SGD.parameters(),lr=lr)
opt_Momentum=torch.optim.SGD(net_Momentum.parameters(),lr=lr,momentum=0.8)
opt_RMSprop=torch.optim.RMSprop(net_RMSprop.parameters(),lr=lr,alpha=0.9)
opt_Adam=torch.optim.Adam(net_Adam.parameters(),lr=lr,betas=(0.9,0.99))
optimizers=[opt_SGD,opt_Momentum,opt_RMSprop,opt_Adam]

loss_func=torch.nn.MSELoss()
losses_his=[[],[],[],[]]  # 记录 training 时不同神经网络的 loss

for epoch in range(epoch):
    print('Epoch:',epoch)
    for step,(b_x,b_y) in enumerate(loader):
        for net,opt,l_his in zip(nets,optimizers,losses_his):
            output=net(b_x)               # 得到每个网络的输出
            loss=loss_func(output,b_y)    # 计算loss
            opt.zero_grad()               # 清除梯度,防止叠加对下一次训练有影响
            loss.backward()               # 反向传播,计算梯度
            opt.step()                    # 应用梯度
            l_his.append(loss.data.numpy())   # 记录loss
    print(loss)

# 画图
labels=['SGD','Momentum','RMSprop','Adam']
for i,l_his in enumerate(losses_his):
    plt.plot(l_his,label=labels[i])
plt.legend(loc='best') #loc 参数设置为 'best',表示图例的位置将会自动选择最佳位置来避免遮挡数据。
plt.xlabel('Steps')
plt.ylabel('Loss')
plt.ylim((0,0.2))
plt.show()


高级神经网络结构

CNN卷积神经网络

什么是卷积神经网络CNN

卷积神经网络是近些年逐步兴起的一种人工神经网络结构, 因为利用卷积神经网络在图像和语音识别方面能够给出更优预测结果, 这一种技术也被广泛的传播和应用. 卷积神经网络最常被应用的方面是计算机的图像识别, 不过因为不断地创新, 它也被应用在视频分析, 自然语言处理, 药物发现, 等等. 近期最火的 Alpha Go, 让计算机看懂围棋, 同样也是有运用到这门技术.

卷积和神经网络

当我们谈论卷积和神经网络时,可以这样理解:

卷积(Convolution): 这就像我们看一张图片时,不是一下子看完整张图片,而是先看一小块,再移到下一小块,然后再下一小块,以此方式来看整张图片。这就是卷积的概念,它是一种处理图像和数据的方式,让计算机能够关注数据的局部特征。

神经网络(Neural Network): 想象一下你的大脑是一个处理信息的机器,它由许多小的处理单元组成,这些单元协作以理解复杂的信息。神经网络模仿了这个思想,它由许多小的"神经元"组成,每个神经元都有一些权重和规则,用来处理数据。这些神经元一起工作,就像大脑中的神经元一样,以便计算机可以理解和学习复杂的信息。

卷积神经网络(CNN): 这是一种特殊类型的神经网络,专门用于处理图像和类似的数据。它使用卷积来帮助计算机看待图像的方式,就像我们看待图像一样,一小块一小块地。这样,CNN可以很好地识别图像中的不同部分,从而用于图像识别、人脸识别和其他计算机视觉任务。

所以,卷积和神经网络一起构建了卷积神经网络,这是一种用于处理图像和数据的强大工具,它可以帮助计算机更好地理解和分析视觉信息。

池化

池化(Pooling)是卷积神经网络(CNN)中的一种操作,它的作用有点像筛子。想象一下你有一杯沙子,要用筛子把大石头和小石子分开。池化的目标是从输入数据中提取出最重要的信息,同时减少数据的大小,以便在神经网络中更有效地处理。

池化通常在卷积层之后应用,它会将每个小区域的数据进行一种特殊的处理,比如在一小块区域内选择最大值(最大池化)或者计算平均值(平均池化)。这个处理会将每个小区域的信息变成一个单一的值,然后这些值被用来构建新的数据表示。

举个例子,假设你有一张图像,想要识别图像中的物体。池化可以帮助你缩小图像的尺寸,同时保留最重要的特征,比如图像中是否有边缘、纹理或者颜色变化。这样,神经网络就可以更快速地学习和理解图像的重要特征,而不需要处理太多的冗余信息。

所以,池化就像筛子一样,帮助神经网络筛选出重要的信息,同时缩小数据的规模,使得神经网络更高效地工作。这有助于提高图像识别和其他任务的性能。

流行的CNN结构

目前比较流行的搭建结构是:

  1. 输入图片:这是网络的输入,通常是一张图像。

  2. 卷积层:第一层卷积层用来提取图像的特征。通过卷积操作,它可以检测到图像中的边缘、纹理等低级特征。在卷积层后通常会有一个非线性激活函数,如ReLU(Rectified Linear Unit)。

  3. 池化层:池化层用于降低特征图的尺寸,并且减少计算量。Max pooling是一种常用的池化方式,它选择每个小区域中的最大值。

  4. 第二次卷积层和池化层:这些层可以进一步提取图像中的更高级别的特征。

  5. 全连接神经层:全连接层用于将卷积层提取的特征映射到输出的分类标签上。这些层通常包括多个神经元,并且与前一层的每个神经元都有连接。

  6. 分类器:这是用于执行最终的分类预测的部分,通常是一个softmax分类器,将网络输出映射到类别概率分布。

这个结构是一个通用的卷积神经网络架构,适用于许多图像分类任务。但需要注意的是,实际应用中的网络结构可能会根据任务的复杂性和数据集的特点而有所不同。有时需要更深层次或更复杂的结构来获得更好的性能。

CNN 卷积神经网络代码实现

要点
MNIST手写数据
python 复制代码
import torch
import torch.nn as nn
import torch.utils.data as Data
import torchvision
import matplotlib.pyplot as plt

torch.manual_seed(1)

epoch=1 # 训练整批数据多少次, 为了节约时间, 我们只训练一次
batch_size=50
lr=0.001
download_mnist=True# 如果你已经下载好了mnist数据就写上 False

train_data=torchvision.datasets.MNIST(root='./mnist',train=True,
                                      transform=torchvision.transforms.ToTensor(),# 转换 PIL.Image or numpy.ndarray 成
                                                    # torch.FloatTensor (C x H x W), 训练的时候 normalize 成 [0.0, 1.0] 区间
                                      download=download_mnist)

test_data=torchvision.datasets.MNIST(root='./mnist',train=False,
                                     transform=torchvision.transforms.ToTensor(),
                                     download=download_mnist)

train_loader=Data.DataLoader(dataset=train_data,batch_size=batch_size,shuffle=True)

test_x=torch.unsqueeze(test_data.data,dim=1).type(torch.FloatTensor)[:2000]/255.
test_y=test_data.targets[:2000]

# 选择要查看的图片的索引
image_index = 0  # 选择第一张图像

# 获取对应索引的图像和标签
image, label = test_data[image_index]

# 将图像从Tensor转换为NumPy数组,并将像素值从[0, 1]范围反归一化到[0, 255]范围
image = (image * 255).numpy().astype('uint8')

# 使用Matplotlib显示图像
plt.imshow(image[0], cmap='gray')  # 使用灰度色彩映射
plt.title(f'Label: {label}')
plt.show()

在机器学习和深度学习中,通常将图像的像素值缩放到0到1之间以进行预处理。这是因为神经网络在输入数据的值范围较小时更容易训练和收敛。

在上述代码中,test_data.data 包含了测试图像的像素值数据,/255. 是将这些像素值除以255的操作。这样做的目的是将像素值从0到255的范围缩放到0到1之间。这是因为通常情况下,图像像素值的范围是0(黑色)到255(白色),除以255后,像素值会变成0到1之间的小数,这种预处理有助于神经网络更好地处理图像数据。

你可能有的时候会遇到test_data.test_data的写法,这个也是对的,应该是旧的写法了,现在不太推荐用,不过看到这种形式要明白和test_data.data是一样的

这两个属性只是命名上的不同,都可以用于获取测试图像的像素值数据。例如,你可以使用test_data.datatest_data.test_data来访问测试图像的像素值。以下是示例:

python 复制代码
# 使用 test_data.data 访问测试图像的像素值
test_images = test_data.data

# 或者使用 test_data.test_data 访问测试图像的像素值,效果相同
test_images = test_data.test_data

这两种方式都可以用来获取测试图像的像素数据,选择其中一个即可。

CNN模型
python 复制代码
print(test_data.data.size())

class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        self.conv1=nn.Sequential(
            nn.Conv2d(in_channels=1,out_channels=16,kernel_size=5,stride=1,padding=2),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2)
        )
        self.conv2=nn.Sequential(
            nn.Conv2d(16,32,5,1,2),
            nn.ReLU(),
            nn.MaxPool2d(2)
        )
        self.out=nn.Linear(in_features=32*7*7,out_features=10)
    def forward(self,x):
        x=self.conv1(x)
        x=self.conv2(x)
        x=x.view(x.size(0),-1)
        output=self.out(x)
        return output
cnn=CNN()
print(cnn)

在这个CNN模型中,32*7*7 是输出层的输入特征数量(in_features),这个值是如何计算的呢?

首先,让我们来看看模型的构建:

  1. self.conv1 包含一个卷积层,其输出通道数为16。在进行池化之前,没有改变图像的空间尺寸,所以输出特征图的大小与输入特征图大小相同。

  2. self.conv2 包含另一个卷积层,其输出通道数为32。同样,池化操作之前,输出特征图的大小与输入特征图大小相同。

  3. nn.MaxPool2d(2) 操作会将特征图的大小减半。这是因为每个最大池化操作都会将特征图的高度和宽度减半。

  4. 最后,x=x.view(x.size(0),-1) 将特征图展平为一个向量。这个向量的大小等于特征图的通道数乘以特征图的高度和宽度。

因此,如果输入的图像大小为28x28像素,并且经过两次卷积和池化操作后,输出特征图的大小为7x7像素(高度和宽度都减半了),那么最后的全连接层的输入特征数量就是 32*7*7。这是因为最后一层卷积层的输出通道数是32,而特征图的高度和宽度都是7。所以,全连接层的输入大小是32乘以7乘以7,即32*7*7

训练
python 复制代码
optimizer=torch.optim.Adam(cnn.parameters(),lr=lr)
loss_func=nn.CrossEntropyLoss()

for epoch in range(epoch):
    for step,(b_x,b_y) in enumerate(train_loader):
        output=cnn(b_x)
        loss=loss_func(output,b_y)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    print(loss)

# 我们再来取10个数据, 看看预测的值到底对不对
test_output=cnn(test_x[:10])
pred_y=torch.max(test_output,1)[1].data.numpy().squeeze()
print(pred_y,'prediction number')
print(test_y[:10].numpy(),'real number')

下面是对这行代码的详细解释:

python 复制代码
pred_y = torch.max(test_output, 1)[1].data.numpy().squeeze()
  1. test_output 是模型对一批输入数据的输出,通常是一个包含类别分数的张量。

  2. torch.max(test_output, 1) 用于在模型的输出中找到每个输入的最大值和对应的索引。具体来说,dim=1 参数告诉PyTorch在每一行中找到最大值。这将返回两个张量,一个包含最大值,另一个包含最大值的索引。

  3. [1] 选择返回的结果中的索引部分,这个索引部分包含了模型对每个输入的预测类别。

  4. .data 将PyTorch张量转换为其对应的数据数组(NumPy数组),以便进一步处理。

  5. .numpy() 将PyTorch张量转换为NumPy数组。

  6. .squeeze() 用于去除数组中尺寸为1的维度,以确保最终得到的数组是一维的。通常情况下,模型的输出是一维的,但在某些情况下可能包含额外的维度,使用.squeeze() 可以确保结果是一个平铺的一维数组。

最终,pred_y 包含了模型对输入数据的预测类别标签,它是一个NumPy数组,每个元素表示模型对相应输入的预测类别。这个数组可以用于进一步的分析、可视化或评估模型的性能。

可视化训练

后续补充

什么是循环神经网络RNN

循环神经网络(Recurrent Neural Network,RNN)是一种用于处理序列数据的神经网络。它之所以称为"循环",是因为它可以处理具有时间顺序或序列性质的数据,如文本、语音、时间序列等,而在处理这些数据时,RNN会利用先前的信息来影响后续的预测或输出。

通俗来说,想象一下你在阅读一本小说,当你阅读到一句话时,你可能会记住前面的内容,这有助于你理解后面的情节。RNN的工作原理有些类似,它会记住之前处理的输入数据,将它们的信息储存在内部状态中,并在处理下一个输入时考虑之前的状态。

RNN的结构中包含一个循环单元,这个单元会根据当前输入和之前的状态来计算输出和新的状态。这种循环结构使得RNN能够捕捉到数据中的顺序、依赖关系和上下文信息,因此在自然语言处理、语音识别、时间序列预测等任务中表现出色。

然而,传统的RNN也有一些问题,例如在处理长序列时容易出现梯度消失或梯度爆炸的问题,这导致它难以处理长期依赖关系。因此,有一些改进型的循环神经网络,如长短时记忆网络(LSTM)和门控循环单元(GRU),它们通过引入门控机制来解决这些问题,使得RNN在更复杂的任务中表现更好。

我将使用数学和通俗易懂的方式来解释为什么RNN可能会遇到梯度消失和梯度爆炸的问题。

考虑一个简单的RNN,它的计算过程如下:

  1. 在每个时间步(t)上,RNN接收一个输入 ( x t ) (x_t) (xt)和一个隐藏状态 ( h t − 1 ) (h_t-1) (ht−1)。
  2. 它使用这些输入来计算一个新的隐藏状态 ( h t ) (h_t) (ht)和一个输出 ( y t ) (y_t) (yt)。

这个计算过程可以用以下数学公式表示:

h t = f ( W h h ∗ h t − 1 + W h x ∗ x t ) h_t = f(W_{hh} * h_{t-1} + W_{hx} * x_t) ht=f(Whh∗ht−1+Whx∗xt)
y t = W y h ∗ h t y_t = W_{yh} * h_t yt=Wyh∗ht

其中, h t h_t ht是当前时间步的隐藏状态, h t − 1 h_{t-1} ht−1是上一个时间步的隐藏状态, x t x_t xt是当前时间步的输入, f f f是激活函数, W h h W_{hh} Whh、 W h x W_{hx} Whx和 W y h W_{yh} Wyh是权重矩阵。

现在,让我们来看看为什么会出现梯度消失和梯度爆炸的问题:

  1. 梯度消失

    • 如果权重矩阵 W h h W_{hh} Whh和 W h x W_{hx} Whx都接近于1,而激活函数 f f f的导数也接近于1,那么计算出的新隐藏状态 h t h_t ht会接近于 h t − 1 h_{t-1} ht−1和 x t x_t xt的线性组合。
    • 这意味着每个时间步的新隐藏状态 h t h_t ht会依赖于前一个时间步的隐藏状态 h t − 1 h_{t-1} ht−1和当前时间步的输入 x t x_t xt,但是它们的影响会非常小,因为都接近于1。
    • 当梯度反向传播时,导数的连续相乘会导致梯度逐渐变得非常小,最终趋近于零,这就是梯度消失的原因。
  2. 梯度爆炸

    • 如果权重矩阵 W h h W_{hh} Whh和 W h x W_{hx} Whx很大,而激活函数 f f f的导数也较大,那么计算出的新隐藏状态 h t h_t ht会迅速增长。
    • 这意味着每个时间步的新隐藏状态 h t h_t ht会非常依赖前一个时间步的隐藏状态 h t − 1 h_{t-1} ht−1和当前时间步的输入 x t x_t xt,它们的影响会非常大。
    • 当梯度反向传播时,导数的连续相乘会导致梯度逐渐变得非常大,可能超出计算机可以表示的范围,这就是梯度爆炸的原因。

因此,梯度消失和梯度爆炸的问题源于权重矩阵和激活函数的选择,以及它们对隐藏状态的影响。为了解决这些问题,通常需要采取一些技巧,如使用特殊的权重初始化方法(如Xavier初始化)、使用梯度剪切(gradient clipping)等,以确保梯度在合适的范围内传播,从而稳定训练深度循环神经网络。

什么是LSTM循环神经网络

当我们处理序列数据(例如文本、语音或时间序列数据)时,常常需要一种能够记住之前信息的神经网络结构。这时就可以使用一种特殊的循环神经网络(Recurrent Neural Network,简称RNN)的变体,称为长短时记忆网络(Long Short-Term Memory,简称LSTM)。

LSTM是一种具有内部记忆机制的神经网络。通常的RNN会在处理长序列时出现梯度消失或梯度爆炸的问题,而LSTM通过引入三个关键门控单元来解决这个问题,这些门控单元分别是:

  1. 遗忘门(Forget Gate):决定哪些信息需要被遗忘或清除,以便网络可以记住更重要的信息。
  2. 输入门(Input Gate):决定哪些新信息需要被存储到内部状态中,以更新记忆。
  3. 输出门(Output Gate):根据内部状态的内容和输入,决定网络的输出。

LSTM的核心思想是它能够选择性地记住和忘记信息,这使得它在处理长序列时能够更好地捕捉到关键信息。这些门控单元通过一系列数学操作来实现,允许LSTM网络在不同的时间步骤上执行不同的操作,以有效地处理序列数据。

因此,LSTM可以看作是一种强大的序列建模工具,能够用于各种任务,包括自然语言处理、语音识别、时间序列预测等。它具有很好的记忆性能,能够处理长序列,同时避免了传统RNN的梯度消失问题。这使得LSTM成为深度学习中非常重要的一部分。

让我用数学公式来说明LSTM和RNN的区别,尽量通俗易懂。

RNN(循环神经网络)

RNN的隐藏状态更新公式如下:

h t = f ( W h h ⋅ h t − 1 + W h x ⋅ x t ) h_t = f(W_{hh} \cdot h_{t-1} + W_{hx} \cdot x_t) ht=f(Whh⋅ht−1+Whx⋅xt)

其中:

  • h t h_t ht 是当前时间步的隐藏状态。
  • W h h W_{hh} Whh 和 W h x W_{hx} Whx 是权重矩阵。
  • f f f 是激活函数,通常是 tanh 或 sigmoid。
  • h t − 1 h_{t-1} ht−1 是上一个时间步的隐藏状态。
  • x t x_t xt 是当前时间步的输入。

RNN的问题在于,它的梯度可以通过反向传播不断地迭代相乘,导致在处理长序列时梯度消失或梯度爆炸。

LSTM(长短时记忆网络)

LSTM引入了门控机制,其隐藏状态更新公式如下:

h t = f ( W h h ⋅ h t − 1 + W h x ⋅ x t ) ⊙ o t h_t = f(W_{hh} \cdot h_{t-1} + W_{hx} \cdot x_t) \odot o_t ht=f(Whh⋅ht−1+Whx⋅xt)⊙ot

其中:

  • h t h_t ht 仍然是当前时间步的隐藏状态。
  • W h h W_{hh} Whh 和 W h x W_{hx} Whx 是权重矩阵。
  • f f f 是激活函数,通常是 tanh 或 sigmoid。
  • h t − 1 h_{t-1} ht−1 是上一个时间步的隐藏状态。
  • x t x_t xt 是当前时间步的输入。
  • ⊙ \odot ⊙ 表示逐元素相乘。
  • o t o_t ot 是输出门控制的值,用于选择性地更新隐藏状态。

LSTM的关键区别在于引入了输出门 o t o_t ot,以及遗忘门、输入门等门控机制。这些门控机制允许LSTM选择性地记住、忘记和更新信息,因此能够更好地处理长序列,避免了梯度消失或梯度爆炸问题。

总之,LSTM通过引入门控机制改进了RNN的问题,使其能够更好地捕捉长距离依赖关系,适用于处理序列数据。

RNN循环神经网络(分类)

MNIST手写数据

python 复制代码
import torch
import torchvision.datasets
import torch.utils.data as Data
from torch import nn
import torchvision.transforms as transforms
import matplotlib.pyplot as plt

torch.manual_seed(1)

epoch=1
batch_size=64
# 因为图片是28*28的,每一时间间隔读取一行像素点,所以下面都是28
time_step=28       #rnn时间步数/图片高度
input_size=28      #rnn每步输入值/图片每行像素
lr=0.01
download_mnist=True

train_data=torchvision.datasets.MNIST(
    root='./mnist',
    train=True,
    transform=transforms.ToTensor(),
    download=download_mnist
)

test_data = torchvision.datasets.MNIST(root='./mnist/', train=False)

# 批训练 50samples, 1 channel, 28x28 (50, 1, 28, 28)
train_loader = Data.DataLoader(dataset=train_data, batch_size=batch_size, shuffle=True)

# 为了节约时间, 我们测试时只测试前2000个
test_x = torch.unsqueeze(test_data.data, dim=1).type(torch.FloatTensor)[:2000]/255.   # shape from (2000, 28, 28) to (2000, 1, 28, 28), value in range(0,1)
test_y = test_data.targets[:2000]

RNN模型

python 复制代码
class RNN(nn.Module):
    def __init__(self):
        super(RNN, self).__init__()
        self.rnn=nn.LSTM(
            input_size=28,     #图片每行的像素数据点
            hidden_size=64,    #隐藏层的神经元个数
            num_layers=1,      #RNN层的层数
            batch_first=True   #input & output 会是以 batch size 为第一维度的特征集
                               # e.g. (batch, time_step, input_size)
        )
        self.out=nn.Linear(64,10)
    def forward(self,x):
        # x shape (batch, time_step, input_size)
        # r_out shape (batch, time_step, output_size)
        # h_n shape (n_layers, batch, hidden_size)   LSTM 有两个 hidden states, h_n 是分线, h_c 是主线
        # h_c shape (n_layers, batch, hidden_size)
        r_out,(h_n,h_c)=self.rnn(x,None) # None 表示 hidden state 会用全0的 state
        out=self.out(r_out[:, -1, :]) # (batch, time_step, input_size)
        return out
rnn=RNN()
print(rnn)

这段代码定义了一个基于PyTorch的循环神经网络(RNN)模型,该模型用于处理图像数据的分类任务。以下是对代码的解释:

  1. class RNN(nn.Module)::定义了一个名为 RNN 的PyTorch模型类,该类继承自 nn.Module,这是PyTorch中构建神经网络模型的常见方式。

  2. def __init__(self)::这是类的构造函数,用于初始化模型的结构。

  3. self.rnn = nn.LSTM(...):这一部分定义了模型的RNN层,具体包括以下参数:

    • input_size=28:输入数据的特征维度,这里是每行的像素数据点数目,通常是28(假设是处理28x28像素的图像)。
    • hidden_size=64:RNN隐藏层的神经元个数,这里设定为64个。
    • num_layers=1:RNN层的层数,这里设定为1层。
    • batch_first=True:表示输入和输出的数据格式中,批次大小(batch size)是第一个维度。例如,输入数据格式为 (batch_size, time_step, input_size)
  4. self.out = nn.Linear(64, 10):定义了模型的输出层,这是一个全连接层(Linear Layer),用于将RNN的输出映射到类别标签的预测结果。具体包括以下参数:

    • 64:输入特征的维度,与RNN隐藏层的神经元个数相匹配。
    • 10:输出特征的维度,这里假设模型用于对10个不同的类别进行分类。
  5. def forward(self, x)::定义了模型的前向传播方法,用于计算模型的输出。

  6. r_out, (h_n, h_c) = self.rnn(x, None):这一行代码表示对输入 x 进行RNN前向传播操作,其中:

    • r_out 是RNN层的输出,它包含了每个时间步的输出。
    • (h_n, h_c) 是RNN层的隐藏状态,分别表示最后一个时间步的隐藏状态和细胞状态。在这里,None 表示不需要手动初始化隐藏状态,模型会自动处理。
  7. out = self.out(r_out[:, -1, :]):这一行代码将RNN的输出 r_out 中的最后一个时间步的输出提取出来,并通过全连接层 self.out 进行分类预测,得到最终的输出结果 out

总之,这段代码定义了一个简单的RNN模型,用于图像分类任务,输入图像的每行像素数据点作为序列数据,通过RNN层处理后,最后进行分类预测。这是一个基本的序列分类模型示例。

训练

python 复制代码

RNN循环神经网络(回归)

什么是自编码

AUTOEncoder

什么是DQN

什么是生成对抗网络(GAN)

GAN

相关推荐
hunteritself1 分钟前
AI Weekly『12月16-22日』:OpenAI公布o3,谷歌发布首个推理模型,GitHub Copilot免费版上线!
人工智能·gpt·chatgpt·github·openai·copilot
IT古董1 小时前
【机器学习】机器学习的基本分类-强化学习-策略梯度(Policy Gradient,PG)
人工智能·机器学习·分类
centurysee1 小时前
【最佳实践】Anthropic:Agentic系统实践案例
人工智能
mahuifa1 小时前
混合开发环境---使用编程AI辅助开发Qt
人工智能·vscode·qt·qtcreator·编程ai
四口鲸鱼爱吃盐1 小时前
Pytorch | 从零构建GoogleNet对CIFAR10进行分类
人工智能·pytorch·分类
落魄君子1 小时前
ELM分类-单隐藏层前馈神经网络(Single Hidden Layer Feedforward Neural Network, SLFN)
神经网络·分类·数据挖掘
蓝天星空1 小时前
Python调用open ai接口
人工智能·python
睡觉狂魔er1 小时前
自动驾驶控制与规划——Project 3: LQR车辆横向控制
人工智能·机器学习·自动驾驶
scan7241 小时前
LILAC采样算法
人工智能·算法·机器学习
leaf_leaves_leaf1 小时前
win11用一条命令给anaconda环境安装GPU版本pytorch,并检查是否为GPU版本
人工智能·pytorch·python