分类: 深度学习

  • torch和luz做深度学习预测

    library(torch)
    library(luz)
    
    # input dimensionality (number of input features)
    # 定义输入的特征维度为 3
    d_in <- 3
    # 定义训练数据的样本数量为 1000
    # number of observations in training set
    n <- 1000
    
    # 生成一个形状为 (1000, 3) 的随机张量,作为输入特征
    x <- torch_randn(n, d_in)
    # 定义一个系数向量,用于线性映射
    coefs <- c(0.2, -1.3, -0.5)
    # 是生成的目标标签(模拟真实结果)
    # 通过将 x 和 coefs 进行矩阵乘法后
    # 加上形状为 (1000, 1) 的随机噪声生成
    y <- x$matmul(coefs)$unsqueeze(2) + torch_randn(n, 1)
    
    # 将张量 x 和 y 封装成一个数据集对象
    ds <- tensor_dataset(x, y)
    
    # 创建用于训练的批量加载器
    # 表示每次从数据集中加载 100 个样本
    # 在加载数据时数据会被打乱
    dl <- dataloader(ds, batch_size = 100, shuffle = TRUE)
    
    # dimensionality of hidden layer
    d_hidden <- 32   # 隐藏层的维度
    # output dimensionality (number of predicted features)
    d_out <- 1   # 输出层的维度
    
    # 定义神经网络模块
    net <- nn_module(
      initialize = function(d_in, d_hidden, d_out) {
        # 三个层组成的序列
        self$net <- nn_sequential(
          nn_linear(d_in, d_hidden),   # 线性全连接层
          nn_relu(),   # 激活函数
          nn_linear(d_hidden, d_out)   # 线性全连接层
        )
      },
      # 向前传播方法
      forward = function(x) {
        self$net(x)
      }
    )
    
    # 结果赋值给fitted
    fitted <- net %>%
    # 设置损失函数和优化器
      setup(loss = nn_mse_loss(), optimizer = optim_adam) %>%
      # 设置超参数
      set_hparams(
        d_in = d_in,   # 输入层维度
        d_hidden = d_hidden, d_out = d_out   # 隐藏层神经元数量和输出层维度
      ) %>%
      fit(dl, epochs = 200)   # 使用数据加载器,200个epoch
    
    # 从数据集的完整索引中随机抽取60%的索引作为训练集的索引
    train_ids <- sample(1:length(ds), size = 0.6 * length(ds))
    # 从剩余的索引中(即非训练集的部分)随机抽取20%的索引作为验证集的索引
    valid_ids <- sample(
      setdiff(1:length(ds), train_ids),
      size = 0.2 * length(ds)
    )
    # 将训练集和验证集的索引从完整索引中排除,剩下的索引作为测试集的索引
    test_ids <- setdiff(
      1:length(ds),
      union(train_ids, valid_ids)
    )
    
    # 创建基于训练集索引的子数据集
    train_ds <- dataset_subset(ds, indices = train_ids)
    # 基于验证集索引的数据集
    valid_ds <- dataset_subset(ds, indices = valid_ids)
    # 测试集索引的子数据集
    test_ds <- dataset_subset(ds, indices = test_ids)
    
    # 为训练集创建数据加载器,指定批量大小为100,并启用数据打乱(shuffle)
    train_dl <- dataloader(train_ds,
      batch_size = 100, shuffle = TRUE
    )
    # 为验证集创建数据加载器,指定批量大小为100
    valid_dl <- dataloader(valid_ds, batch_size = 100)
    # 为测试集创建数据加载器,指定批量大小为100
    test_dl <- dataloader(test_ds, batch_size = 100)
    fitted <- net %>%
    # 函数用于设置模型的基本配置
      setup(
        loss = nn_mse_loss(),   # 指定使用均方误差 (MSE) 作为损失函数
        optimizer = optim_adam,   # 选择使用 Adam 优化器
        metrics = list(luz_metric_mae())   # 评估指标使用平均绝对误差 (MAE)
      ) %>%
      set_hparams(   # 设置模型的超参数
        d_in = d_in,   # 输入维度
        d_hidden = d_hidden, d_out = d_out   # 隐藏层维度和输出维度
      ) %>%
    #   训练模型,训练加载,200个周期,验证集
      fit(train_dl, epochs = 200, valid_data = valid_dl)
    # fitted 在测试数据集 test_dl 上进行评估
    fitted %>% evaluate(test_dl)
    
    # 另一个训练
    fitted <- net %>%
      setup(
        loss = nn_mse_loss(),
        optimizer = optim_adam,
        metrics = list(luz_metric_mae())
      ) %>%
      set_hparams(d_in = d_in,
                  d_hidden = d_hidden,
                  d_out = d_out) %>%
      fit(
        train_dl,
        epochs = 200,
        valid_data = valid_dl,
        callbacks = list(   # 回调函数提供训练过程中的特殊功能
        # 模型检查点功能,自动保存性能最佳的模型,保存在./modoels/
          luz_callback_model_checkpoint(path = "./models/",
                                        # 只保存最佳模型
                                        save_best_only = TRUE),
          # 提前停止功能,当验证集性能在 10 轮内没有改善时停止训练,避免过拟合
          luz_callback_early_stopping(patience = 10)
        )
      )
    # 使用训练好的模型(fitted)对测试数据集 (test_dl) 进行预测
    fitted %>% predict(test_dl)
    
    
  • torch获取MNIST数据集并初始化

    # 获取 mnist 的数据集
    ds <- mnist_dataset(
      # 存放目录
      root = dir,
      # 下载训练集
      train = TRUE, # default
      download = TRUE,   # 如果不存在,则下载
      # 将图像数据转换为张量
      transform = function(x) {
        x %>% transform_to_tensor() 
      }
    )
    # 从数据集中取出第一条数据(图像和标签)
    first <- ds[1]
    # 打印第一张图片的形状和对应的标签
    cat("Image shape: ", first$x$shape, " Label: ", first$y, "\n")
    
    
    展开/折叠结果
    Processing...
    Done!
    
    Image shape:  1 28 28  Label:  6 
    
    length(dl)
    
    展开/折叠结果
    [1] 1875
    
    first_batch <- dl %>%   # 链式操作
      # obtain an iterator for this dataloader
      dataloader_make_iter() %>%   # 生成迭代器
      dataloader_next()   # 从迭代器中读取下一批数据
    
    dim(first_batch$x)   # 检查x的维度
    dim(first_batch$y)   # 检查y的维度
    
    展开/折叠结果
    [1] 32  1 28 28
    [1] 32
    
  • torch读取数据

    library(torch)
    library(palmerpenguins)
    library(dplyr)
    
    # 以摘要形式展示数据集的结构和内容
    penguins %>% glimpse()
    
    展开/折叠结果
    Rows: 344
    Columns: 8
    $ species            Adelie, Adelie, Adelie, Adelie, Adelie…
    $ island             Torgersen, Torgersen, Torgersen, Torge…
    $ bill_length_mm     39.1, 39.5, 40.3, NA, 36.7, 39.3, 38.9…
    $ bill_depth_mm      18.7, 17.4, 18.0, NA, 19.3, 20.6, 17.8…
    $ flipper_length_mm  181, 186, 195, NA, 193, 190, 181, 195,…
    $ body_mass_g        3750, 3800, 3250, NA, 3450, 3650, 3625…
    $ sex                male, female, female, NA, female, male…
    $ year               2007, 2007, 2007, 2007, 2007, 2007, 20…
    
    # 数据框转换为张量
    penguins_dataset <- dataset(
      name = "penguins_dataset()",
      # 初始化
      initialize = function(df) {
        # 去除NA
        df <- na.omit(df)
        # 取数据框第3到第6列,转换为矩阵,然后进一步转换为PyTorch张量
        self$x <- as.matrix(df[, 3:6]) %>% torch_tensor()
        # 将species列(物种信息)转换为数值型(类别编码),再转为PyTorch长整型张量
        self$y <- torch_tensor(
          as.numeric(df$species)
        )$to(torch_long())
      },
      # 根据索引 i 返回一个单独的数据样本
      .getitem = function(i) {
        list(x = self$x[i, ], y = self$y[i])
      },
      # 返回特征数据 self$x 的行数,即数据集的样本总数
      .length = function() {
        dim(self$x)[1]
      }
    )
    
    # 张量赋值给 ds 函数
    ds <- penguins_dataset(penguins)
    # 查看张量的长度
    length(ds)
    # 查看第 1 行数据
    ds[1]
    
    展开/折叠结果
    [1] 333
    $x
    
    torch_tensor
       39.1000
       18.7000
      181.0000
     3750.0000
    [ CPUFloatType{4} ]
    
    $y
    torch_tensor
    1
    [ CPULongType{} ]
    
    
    # 创建随机由三个随机张量10个组成的数据集
    three <- tensor_dataset(
      # 从正态分布中生成随机数,张量的维数是10
      # 即包含10个元素的1维向量
      torch_randn(10), torch_randn(10), torch_randn(10)
    )
    # 从数据集中获取第一个样本
    three[1]
    
    展开/折叠结果
    [[1]]
    torch_tensor
     1.3905
    [ CPUFloatType{1} ]
    
    [[2]]
    torch_tensor
     2.1180
    [ CPUFloatType{1} ]
    
    [[3]]
    torch_tensor
    -0.3222
    [ CPUFloatType{1} ]
    
    # 去除带有 NA 的行
    penguins <- na.omit(penguins)
    # 构建函数,3:6 为矩阵张量;
    ds <- tensor_dataset(
      torch_tensor(as.matrix(penguins[, 3:6])),
      torch_tensor(
        # species转化为数值型再转化为长整数型再转化为张量
        as.numeric(penguins$species)
      )$to(torch_long())
    )
    # 取数据集中第一条数据,两个部分:
    # (1)特征张量(相当于x)
    # (2)标签(相当于y)
    ds[1]
    
    展开/折叠结果
    [[1]]
    torch_tensor
       39.1000    18.7000   181.0000  3750.0000
    [ CPUFloatType{1,4} ]
    
    [[2]]
    torch_tensor
     1
    [ CPULongType{1} ]
    
  • 模块化神经网络

    library(torch)
    # input dimensionality (number of input features)
    # 输入维度(特征的数量)被设定为3,每个输入数据点有3个特征
    d_in <- 3
    # number of observations in training set
    # 训练集中的观测数量被设定为100
    n <- 100
    # 创建一个100行3列的矩阵x
    x <- torch_randn(n, d_in)
    # 定义了一个包含系数的向量coefs
    coefs <- c(0.2, -1.3, -0.5)
    # 进行矩阵乘法运算,将矩阵x与系数coefs相乘
    # 并对每个元素添加噪声(通过unsqueeze添加一个额外的维度)
    # 最终生成目标变量y。
    y <- x$matmul(coefs)$unsqueeze(2) + torch_randn(n, 1)
    # 隐藏层32个维度
    d_hidden <-32
    # 输出层维度为1
    d_out <- 1
    # 创建序列模型
    net <- nn_sequential(
      nn_linear(d_in, d_hidden),   # 全连接层,线性层
      nn_relu(),   # 激活函数,引入非线性层
      nn_linear(d_hidden, d_out)   # 线性层,从隐藏层输出
    )
    # 初始化Adam优化器
    opt <- optim_adam(net$parameters)
    ### training loop --------------------------------------
    
    for (t in 1:200) {
      
      ### -------- Forward pass --------
      # 向前传递,通过net生成预测值
      y_pred <- net(x)
      
      ### -------- Compute loss -------- 
      # 使用均方误差(MSE)计算预测值y_pred与实际值y之间的损失
      loss <- nnf_mse_loss(y_pred, y)
      # 每10个周期输出一次损失值
      if (t %% 10 == 0)
        cat("Epoch: ", t, "   Loss: ", loss$item(), "\n")
      
      ### -------- Backpropagation --------
      # 在反向传播之前重置梯度为零
      opt$zero_grad()
      # 使用反向传播计算损失相对于网络参数的梯度
      loss$backward()
      
      ### -------- Update weights -------- 
      # 根据计算出的梯度更新网络的权重,以减少下一次迭代中的损失
      opt$step()
    
    }
    
    展开/折叠结果
    Epoch:  10    Loss:  3.248455 
    Epoch:  20    Loss:  3.051522
    Epoch:  30    Loss:  2.866707 
    Epoch:  40    Loss:  2.689961 
    Epoch:  50    Loss:  2.51879
    Epoch:  60    Loss:  2.350682 
    Epoch:  70    Loss:  2.18356 
    Epoch:  80    Loss:  2.018582 
    Epoch:  90    Loss:  1.856642 
    Epoch:  100    Loss:  1.700728
    Epoch:  110    Loss:  1.553768 
    Epoch:  120    Loss:  1.417934 
    Epoch:  130    Loss:  1.294667 
    Epoch:  140    Loss:  1.187055 
    Epoch:  150    Loss:  1.096451
    Epoch:  160    Loss:  1.022662 
    Epoch:  170    Loss:  0.9643999 
    Epoch:  180    Loss:  0.9189036 
    Epoch:  190    Loss:  0.8844466 
    Epoch:  200    Loss:  0.8586743 
    
  • 损失函数和梯度的关系

    损失函数(Loss Function),也叫做代价函数(Cost Function),是在机器学习和统计学中用来估量模型预测值与真实值之间差异的一个函数。它是一个衡量模型性能的关键指标,用于在训练过程中指导算法对模型参数的调整,以使模型的预测更加准确。常见的损失函数包括均方误差(Mean Squared Error, MSE)用于回归问题,交叉熵损失(Cross-Entropy Loss)用于分类问题等。

    梯度(Gradient) 是一个向量,表示函数在某一点处沿不同方向的变化率或斜率。在多维空间中,梯度的方向指向函数增长最快的方向,其大小表示增长的速率。

    在机器学习的训练过程中,通常采用梯度下降(Gradient Descent)或其变体作为优化算法,通过迭代的方式逐步调整模型参数以最小化损失函数。

    以最简单的公式y=ax+b为例,它的一般流程为:

    1. 随机生成a和b两个参数,计算它的损失函数,公式的a和b为该值。
    2. 根据梯度下降算法,选取其他a和b,并计算损失函数。
    3. 如果损失函数小于步骤1的损失函数,则更新公式的参数,否则保留步骤1的参数。
    4. 重复步骤2和步骤3,直到a和b不再变化。

    这里有两个概念,一个是固定参数a和b,也就是公式中显示的a和b;还有一个模型参数a和b,这个参数不会显示在公式上,只是临时用来评估损失函数的参数。虽然都叫参数,但是意义和作用都相差很大。

    梯度的作用是指导临时的a和b参数朝着哪个方向变化,是增大还是减小。无论梯度怎么指导临时参数,当临时参数的损失函数大于固定参数a和b时,不会对公式上的a和b参数造成任何影响,需要继续评估下一个临时参数的损失函数。