5.系统学习-PyTorch与多层感知机

PyTorch与多层感知机

前言

这里先了解简单的概念,直接上手尝试。

下一节进行实战

链接: minst数据集分类

在之后第7节会分部分进行说明。

线性回归/逻辑回归可以看成最简单的深度学习网络:

  • 网络包含一层全联接层
  • 采用梯度下降更新网络权重(回归参数)
  • 直到设定的迭代次数终止训练

重新回顾一下线性回归的内容,我们可以更加简单直观的理解深度学习。我们将采用当下学术以及工业界最流行深度学习框架 PyTorch ,其语法简介,并且借鉴了很多成功设计,比如 Numpy 数组的概念及函数,使得我们可以将很多其他库(Numpy 等)的使用经验迁移到 PyTorch 中来。


  • 了解 PyTorch
  • Tensor 创建、类型、维度与操作
  • Dataset
  • PyTorch 下逻辑回归与反向传播
  • 深层网络、非线性拟合能力
  • 模型保存、读取
  • 模型的分类、回归模型构建与训练

PyTroch 简介

PyTorch是由Meta(原Facebook)开源的深度学习框架,得益于其简洁干净的接口封装,与Numpy几乎一致化的向量操作,灵活且Pythonic的语法,用户群体增长非常迅速,2019年开始主流AI会议统计中采用Pytorch框架论文数量已经超过Tensorflow。原生Pytorch已经提供了非常全面的能力,包括模型搭建、训练、部署、分布式、线性代数运算等。

Pytorch安装非常简单,只需要进入官网:https://pytorch.org/, 选择你的操作系统、安装方法、硬件环境后,复制安装命令在终端下(terminal 或CMD)执行即可。需要注意的是,GPU用户需要提前安装Nvidia驱动,但不必安装CUDNN等加速组建,CUDNN等在进行Pytorch安装时候会自动安装其必要部分。

张量(Tensor)

在实际使用PyTorch的过程中,Tensor是我们操作的基本数据类型,其特性与Numpy Array几乎完全一致,只不过在pytorch中,能够通过进行梯度的计算与传播。在正式使用PyTorch进行深度学习建模之前,我们需要熟练掌握PyTorch中张量的基本操作。

c 复制代码
 # 
导入PyTorch包
 
import torch 
import numpy as np
print(torch.__version__)

张量创建

c 复制代码
 # 通过列表创建
 
t = torch.tensor([1, 2])  
t
c 复制代码
 # 通过numpy 数组创建张量
 
a = np.array((1, 2)) 
t1 = torch.tensor(a) 
t1 
c 复制代码
# 通过生成器创建,与numpy 完全一致
torch.arange(9) 
c 复制代码
 np.arange(9)
c 复制代码
# 随机创建一个均值为0、标准差为1的数据,与numpy 完全一致
torch.randn(3, 4)
c 复制代码
np.random.randn(3, 4)
c 复制代码
# torch.Tensor与np.ndarray转换
 
tensor = torch.tensor([1, 2]) 
print(tensor)
# tensor > numpy array 
array = tensor.numpy() 
print(array)
# numpy array > tensor 
 
tensor = torch.tensor(array) 
print(tensor)
tensor = torch.from_numpy(array)
print(tensor)

张量的类型

在神经网络建模过程中,最常用的是torch.float 和 torch.long类型,其中float类型用于连续变量,long类型用于整数变量。

数据类型和 dtype 对应表

数据类型 dtype
32bit 浮点数 torch.float32torch.float
64bit 浮点数 torch.float64torch.double
16bit 浮点数 torch.float16torch.half
8bit 无符号整数 torch.uint8
8bit 有符号整数 torch.int8
16bit 有符号整数 torch.int16torch.short
32bit 有符号整数 torch.int32torch.int
64bit 有符号整数 torch.int64torch.long
布尔类型 torch.bool
c 复制代码
# 初始化一个tensor 
 
tensor = torch.arange(10) 
print(tensor)
# 此时数据类型为long 
tensor.dtype 
c 复制代码
# 一般的,深度学习模型要求的输入大部分为float类型﴾float32,单精度﴿ 
# 转化为float 类型,有两种办法

# 1﴿ 
tensor.float()
# 2﴿ 
tensor.type(torch.float)
c 复制代码
# 转换为长整型
 
t.long() 
c 复制代码
# 转化为双精度浮点型
 
t.double() 

张量的维度变换:

  1. tensor.reshape方法,能够灵活调整张量的 size,和numpy中的reshape 特性一致
  2. 舍弃维度 tensor.squeeze ,与 numpy.squeeze 特性一致
  3. 添加维度 tensor.unsqueeze ,与 numpy.expand_dims 特性一致
  4. 维度顺序转换, tensor.transpose适用于两个维度互换, tensor.permute 适用于对所有维度重新排序
c 复制代码
 # reshape 
x = torch.rand(4, 3, 2) 
 
x_reshpae = x.reshape(4, 6) 
 
print(x.shape, x_reshpae.shape)
c 复制代码
# squeeze 

x = torch.rand(4, 1) 
 
print(x.shape,  x.squeeze().shape)
c 复制代码
# unsqueeze 
 
x = torch.rand(4) 
 
print(x.shape, x.unsqueeze(0).shape, x.unsqueeze(1).shape)
c 复制代码
# transpose 
 
x = torch.rand(4, 3, 2) 
 
print(x.shape,  
      x.transpose(1, 2).shape, # 将dim=1, dim=2 维度互换
 
      x.transpose(0, 2).shape  # 将dim=0, dim=2 维度互换
 
     )
c 复制代码
 # permute 
 
x = torch.rand(4, 3, 2) 
 
print(x.shape,  
      x.permute(1, 2, 0).shape  # 将原本的1,2,0维度按照顺序重新排列
 
     )

张量的常用操作

c 复制代码
# 创建张量
t2 = torch.tensor([[1, 2], [3, 4]])

# 查看张量的属性
print(t2.numel())  # 总共有多少个元素 (num elements)
print(t2.shape)    # 张量的形状 (等价于 .size())
print(t2.size())   # 张量的形状 (同 .shape)
print(t2.ndim)     # 张量的维度数量 (number of dimensions)
c 复制代码
# 注意:当需要计算的结果张量转化为单独的数值进行输出,需要使用.item。
n = torch.tensor(1.0) 
print(n.shape)  # 这是一个0维张量
print(n) 

# 需要采用item 方法将0维张量转化为数值
n.item()
c 复制代码
# 张量的索引、切片与Numpy Array完全一致
data_1 = torch.arange(1, 15)

print(data_1[1: 6])  # 索引其中2-7号元素,并且左包含右不包含
print(data_1[2:10:2])  # 索引其中3-11号元素,左包含右不包含,且隔1个数取1个
print(data_1[2::3])  # 从第3个元素开始索引,一直到结尾,并且每隔2个数取1个
c 复制代码
# 有类似numpy.zeros函数
 
a = torch.zeros(2, 3) 
a z
c 复制代码
# 有类似numpy.ones 
b = torch.ones(2, 3) 
b
c 复制代码
np.concatenate#的第一个参数必须是一个元组或列表
c 复制代码
# 张量拼接 torch.cat ,完全类似于
np.concatenate 
torch.cat([a, b], 0) # 在第0个维度进行拼接
 
c 复制代码
torch.cat([a, b], dim=1) # 在第1个维度进行拼接

矩阵或张量计算

c 复制代码
 # 元素级别的四则运算﴾+ */﴿,要求矩阵shape完全一致或者可以进行广播,与numpy array特性一致。
 
 
x = torch.rand(4, 3) 
y = torch.rand(4, 3) 
 
print((x + y).shape) 
 
print((x * y).shape) 
c 复制代码
# 矩阵乘法,可以用魔法符号 @ 或者使用torch.mm 方法
 
print(x.shape,  y.T.shape) 
 
print((x @ y.T).shape) 
print((torch.mm(x, y.T).shape)) 
c 复制代码
# batch 矩阵乘法, 可以用魔法符号@ 或者使用torch.bmm 方法
 
 
batch_size = 4 
x = torch.rand(batch_size, 3, 2) 
y = torch.rand(batch_size, 2, 3) 
 
print((x@y).shape) 
print((torch.bmm(x, y).shape)) 

Dataset and DataLoader

在PyTorch中,我们需要构造Dataset和DataLoader来迭代数据,训练模型,首先通过一个简单的案例讲解一下Dataset的使用.

c 复制代码
from torch.utils.data import Dataset, DataLoader
c 复制代码
class MyDataset(Dataset):
    def __init__(self, x, y):
        # 初始化,保存数据
        self.x = x
        self.y = y

    def __len__(self):
        # 返回数据集样本个数
        return len(self.y)

    def __getitem__(self, item):
        # 根据索引获取数据,并进行必要的类型转换
        xi = torch.tensor(self.x[item]).float()  # 转为浮点型张量
        yi = torch.tensor(self.y[item]).float()  # 转为浮点型张量
        return xi, yi
c 复制代码
X = np.random.rand(200, 12) 
Y = np.random.rand(200, 1) 
 
ds = MyDataset(X, Y)
ds[120]
c 复制代码
xi, yi = ds[0] 

print(xi.shape) 
print(yi.shape)
c 复制代码
dl = DataLoader(ds,  
                batch_size=4, 
                shuffle=True,    #  是否乱序
               )
c 复制代码
for batch_x, batch_y in dl: 
    break 
 
print(batch_x.shape, batch_y.shape)

PyTorch下逻辑回归与反向传播

在学习逻辑回归课程时,我们了解到其优化过程采用的是梯度下降方法,现在我们就以逻辑回归为案例,讲解PyTorch下如何搭建模型,并进行训练。因为逻辑回归只具备线性分类能力,因此我们构造一个线性可分的数据集,数据只有两个特征(x1,x2),当两个特征下的取值都为1时,分类标签为1,其他时候分类标签为0。

数据表格

x1 x2 y
0 0 0
1 0 0
0 1 0
1 1 1
c 复制代码
 导入库
 
import torch.nn as nn  # 模型搭建
 
import torch.nn.functional as F  # 内部封装了非常多的计算函数
 
import matplotlib.pyplot as plt 
%matplotlib inline 
 
torch.manual_seed(1)
c 复制代码
# 准备数据
 
x = torch.tensor([[0,0],[1,0],[0,1],[1,1]]).float() 
y = torch.tensor([0, 0, 1, 1]).float() 
 
class LRDataset(Dataset): 
    def __init__(self, x, y): 
        self.x = x 
        self.y = y 
    def __len__(self): 
        return len(self.y) 
    def __getitem__(self, item): 
        return self.x[item], self.y[item] 
 
ds = LRDataset(x, y) 
dl = DataLoader(ds, batch_size=4, shuffle=True) 
 
plt.scatter( 
    x.data.numpy()[:, 0],  #提取所有点的第一个坐标(x 轴坐标)
    x.data.numpy()[:, 1],  #提取所有点的第二个坐标(y 轴坐标)
    c=y.data.numpy(), s=100, lw=0) 
plt.show() 
c 复制代码
# 使用PyTorch中nn这个库来构建逻辑回归模型
import torch
import torch.nn as nn

class LogisticRegressionModel(nn.Module): 
    def __init__(self, n_feature): 
        super().__init__()    # 必须执行的标准步骤

        self.linear = nn.Linear(n_feature, 1)   # 线性层
        self.sigmoid = nn.Sigmoid()             # sigmoid激活函数
   
    def forward(self, x): 
        x = self.linear(x) 
        pred = self.sigmoid(x) 
        return pred 

# 初始化模型
model = LogisticRegressionModel(n_feature=2) 
print(model)

# 使用优化器SGD
optimizer = torch.optim.SGD(
    model.parameters(), # 需要优化的模型参数
    lr=0.1              # 学习率
)

# 定义损失函数,采用二分类交叉熵损失
loss_func = torch.nn.BCELoss()
c 复制代码
# 观察模型的输入与输出
for batch_x, batch_y in dl:  # 从 DataLoader 中取出一个 batch
    break  # 取出第一个 batch 后退出循环

# 打印输入和输出的维度
print(f"输入维度: {batch_x.shape}")  # 输入特征的维度
print(f"输出维度: {model(batch_x).shape}")  # 模型输出的维度
print(f"标签维度: {batch_y.shape}")  # 标签(真实值)的维度
print(f"model squeeze 输出维度: {model(batch_x).squeeze().shape}")  # 模型输出的维度(降维后)

# 说明
# 在计算损失函数时,将模型输出使用 squeeze 降低维度,以保持与标签的维度一致,从而正确计算 loss。
c 复制代码
# 定义一个训练函数,训练过程中,根据loss,进行梯度下降
def train_fn(model, optimizer, dl):
    losses = []  # 用于存储每个批次的损失值

    for batch_x, batch_y in dl:
        # 模型预测
        pred = model(batch_x).squeeze()  # 模型输出,并使用squeeze降维

        # 计算损失
        loss = loss_func(pred, batch_y)

        # 梯度清零
        optimizer.zero_grad()

        # 反向传播,计算梯度
        loss.backward()

        # 执行梯度下降,更新参数
        optimizer.step()

        # 获取损失值并保存到CPU中,避免GPU内存占用过多
        loss = loss.item()  # 取出当前批次的损失值
        losses.append(loss)  # 保存损失值

    # 返回平均损失和最后一次预测结果
    return np.mean(losses), pred
c 复制代码
for t in range(1,201): 
    loss, pred = train_fn(model, optimizer, dl)        
    if t % 50 == 0: 
        print(loss) 

DNN(全连结网络)

逻辑回归&线性回归只包含一个线性层(需要注意,逻辑回归虽然对数据进行了sigmoid映射,但本质的分类平面依然是线性平面),不具备对非线性关系的拟合能力。DNN包括多个线性层和激活层,其中线性层通过学习一个映射矩阵,对输入进行线性映射,激活层使用ReLU,Tahn等非线性函数使得模型具备非线性建模能力。如果不加入激活层,只是简单的堆叠线性层,多层网络和单层网络本质上是等价的。

c 复制代码
# ReLU 函数图像
 
 
relu = torch.nn.ReLU() 
 
x = torch.arange(-1, 1, 0.1) 
y = relu(x) 
 
plt.plot(x.numpy(), y.numpy())
c 复制代码
# Tanh 函数图像
 
tanh = torch.nn.Tanh() 
x = torch.arange(-10, 10, 0.1) 
y = tanh(x) 
plt.plot(x.numpy(), y.numpy())

回归案例

c 复制代码
x = torch.unsqueeze(torch.linspace(-1, 1, 100), dim=1)  # 生成数据x 
y = x.pow(2) + 0.2*torch.rand(x.size())  # 生成x对应的数据y 
               
plt.scatter(x.data.numpy(), y.data.numpy()) 
plt.show() 
c 复制代码
# 创建网络
 
class Net(torch.nn.Module): 
    def __init__(self, n_feature, n_hidden, n_output): 
        super(Net, self).__init__() 
        self.fc1 = torch.nn.Linear(n_feature, n_hidden)   # 隐藏层
 
        self.activation = nn.ReLU() 
        self.fc2 = torch.nn.Linear(n_hidden, n_output)   # 输出层
 
 
    def forward(self, x): 
        x = self.activation(self.fc1(x))      # 对隐藏层数据使用激活函数
 
        y = self.fc2(x)             # 输出
 
        return y
c 复制代码
net = Net(n_feature=1, n_hidden=10, n_output=1)     # 定义网络
 
print(net)  # net architecture
c 复制代码
optimizer = torch.optim.SGD(net.parameters(), lr=0.2) # 优化器
 
loss_func = torch.nn.MSELoss()  # 损失函数
c 复制代码
# 这里我们没有使用Dataset和DataLoader,每次迭代使用全部数据。

import matplotlib.pyplot as plt

plt.ion()  # 打开交互模式,便于动态更新绘图

for t in range(100):  # 训练100次
    prediction = net(x)  # 将输入数据送入模型
    loss = loss_func(prediction, y)  # 计算预测值和真实值的损失

    optimizer.zero_grad()  # 梯度清零
    loss.backward()  # 反向传播
    optimizer.step()  # 梯度优化,更新参数

    if t % 20 == 0:  # 每隔20次迭代更新一次绘图
        # 显示学习过程
        plt.cla()  # 清除当前图形
        plt.scatter(x.data.numpy(), y.data.numpy())  # 绘制训练数据的散点图
        plt.plot(x.data.numpy(), prediction.data.numpy(), 'r-', lw=5)  # 绘制拟合曲线
        plt.text(0.5, 0, 'Loss=%.4f' % loss.data.numpy(), fontdict={'size': 20, 'color': 'red'})  # 显示当前损失
        plt.pause(0.1)  # 暂停0.1秒,用于更新绘图

plt.ioff()  # 关闭交互模式
plt.show()  # 显示最终图像





模型创建

  1. 定义模型类
  2. 使用nn.Sequential 快速创建简单的顺序执行的模型
c 复制代码
# 通常在定义比较复杂的模型时,我们倾向与使用定义模型类的方法
 
# 在__init__ 中定义所有模型的组件
 
# 在forward 中定义模型的计算图

class Net(torch.nn.Module): 
    def __init__(self): 
        super(Net, self).__init__() 
        self.fc1 = torch.nn.Linear(2, 10) 
        self.activation = nn.ReLU() 
        self.fc2 = torch.nn.Linear(10, 1) 
 
    def forward(self, x): 
        x = self.activation(self.fc1(x))       
        x = self.fc2(x)             
        return x 
 
net1 = Net() 
print(net1) 
c 复制代码
# 简单的按顺序执行的模型我们可以采用nn.Sequential 定义
 
net2 = nn.Sequential( 
    nn.Linear(2, 10),  
    nn.ReLU(), 
    nn.Linear(10, 1) 
) 
 
print(net2) 
c 复制代码
x = torch.rand(4, 2) 
 
print(net1(x).shape) 
print(net2(x).shape)

保存和加载模型

c 复制代码
#保存和加载模型 
# 1. 当模型训练好后,保存模型参数`state_dict` 
 
torch.save(net1.state_dict(), 'model.bin') 
 
# 2. 加载模型时,首先初始化模型,再加载模型参数
 
net1 = Net() 
net1.load_state_dict(torch.load('model.bin')) 

模型训练与验证

c 复制代码
#模型训练与验证
net1.train()  # 该语句让模型进入训练状态
 
net1.eval()   # 该语句让模型进入验证/预测状态
相关推荐
power-辰南几秒前
Pytorch 三小时极限入门教程
人工智能·pytorch·深度学习
杂货铺的小掌柜几秒前
spring mvc源码学习笔记之二
学习·spring·mvc
JINGWHALE114 分钟前
设计模式 结构型 代理模式(Proxy Pattern)与 常见技术框架应用 解析
前端·人工智能·后端·设计模式·性能优化·系统架构·代理模式
youcans_22 分钟前
【YOLO 项目实战】(12)红外/可见光多模态目标检测
人工智能·yolo·目标检测·计算机视觉·多模态
深蓝学院23 分钟前
Visual CoT:解锁视觉链式思维推理的潜能
人工智能·计算机视觉·目标跟踪
AI追随者24 分钟前
超越YOLO11!DEIM:先进的实时DETR目标检测
人工智能·深度学习·算法·目标检测·计算机视觉
卧式纯绿27 分钟前
自动驾驶3D目标检测综述(六)
人工智能·算法·目标检测·计算机视觉·3d·目标跟踪·自动驾驶
虾球xz34 分钟前
游戏引擎学习第73天
学习·游戏引擎
Ming__GoGo1 小时前
MyBatis-plus sql拦截器
java·sql·学习·鉴权·mybatis-plus·过滤·sql拦截
KeyPan1 小时前
【机器学习:一、机器学习简介】
人工智能·数码相机·算法·机器学习·计算机视觉