跟着沐神学深度学习DAY3

08 线性回归 + 基础优化算法【动手学深度学习v2】

P1 线性回归

(1)线性模型,通过使得损失最小,找到最合适的w和b。

(2)求显示解时,将偏差加入权重是为了方便计算

 (3)求得最优解如下

具体求解细节(摘自评论区):

P2 基础优化算法

(1)梯度下降

  • 学习率,步长的超参数
  • 沿着梯度方向将增加损失函数的值

(2)小批量随机梯度下降

 总结:两个重要的超参数是批量大小和学习率。

P3 线性回归的从0开始实现

  1. torch.normal:这是 PyTorch 中的一个函数,用于生成正态分布的随机数。它的基本形式是 torch.normal(mean, std, size),其中:

    • size:生成随机数的维度。
    • std:正态分布的标准差。
    • mean:正态分布的均值。

(1)生成的数据的样子,x为1000行的,有两个自变量x1,x2;y也有1000行

def synthetic_data(w, b, num_examples):  #@save
    """生成y=Xw+b+噪声"""
    X = torch.normal(0, 1, (num_examples, len(w)))  #均值为0,方差为1,生成num_examples个样本,每个样本的特征个数为len(w)
    y = torch.matmul(X, w) + b   #计算y
    y += torch.normal(0, 0.01, y.shape)   #y加上噪音
    return X, y.reshape((-1, 1))       #第二维大小为1,即转化成了列向量

true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)

print('features:',features,'\nlables:',labels)

ps:torch.tensor([2, -3.4]) 创建的是一个一维张量,不是列向量也不是行向量。如果需要将其转换为列向量或行向量,需要显式地调整其形状。

列向量和行向量的定义

  • 列向量:在数学中,列向量通常是一个n×1 的二维张量(即有 n 行和 1 列)。在 NumPy 或 PyTorch 中,这通常是一个二维张量,例如 torch.tensor([[2], [-3.4]]),形状为 (2, 1)
  • 行向量:在数学中,行向量通常是一个 1×n 的二维张量(即有 1 行和 n 列)。在 NumPy 或 PyTorch 中,这通常是一个二维张量,例如 torch.tensor([[2, -3.4]]),形状为 (1, 2)

 

(2)在pycharm里面绘制图像,使用d2l出问题,不用d2l,直接使用 plt 就可以了:

import matplotlib.pyplot as plt 
plt.figure(figsize=(4, 3)) 
plt.scatter(features[:, 1], labels, 1) 
plt.show()

生成第二个特征features[:, 1]labels的散点图, 可以直观观察到两者之间的线性关系。

(3)我们定义一个data_iter函数, 该函数接收批量大小、特征矩阵和标签向量作为输入,生成大小为batch_size的小批量。

def data_iter(batch_size, features, labels):
    num_examples = len(features)
    indices = list(range(num_examples))      #提取0到num_examples-1,作为索引,返回list类型
    # 这些样本是随机读取的,没有特定的顺序
    random.shuffle(indices)        #将顺序打乱
    for i in range(0, num_examples, batch_size):     #从0开始到num_examples,每次跳batch_size个大小,那么就是1000/10=100次
        batch_indices = torch.tensor(
            indices[i: min(i + batch_size, num_examples)])       #从i开始,到i + batch_size,拿出batch_size数据,min函数保证了数据溢出的情况,将已有数据保存即可,只是b(样本数)的数量会减少罢了
        yield features[batch_indices], labels[batch_indices]   #yield 使得函数成为生成器,每次迭代时会返回一个批次的数据,而不是一次性返回所有数据。
#这个函数是调用一次返回一次

batch_size = 10

for X, y in data_iter(batch_size, features, labels):
    print(X, '\n', y)
    break     #注意这个break,没有这个break会全部输出100次,一次有10行的数据。

代码中不懂的点记录:

 1.使用 batch_indices 索引 features

  • 索引操作

    • 当使用 features[batch_indices] 时,你是在从 features 中提取出指定索引的行。
    • 结果是一个新的张量,包含 batch_indices 指定的那些行的数据,形状为 (batch_size, num_features)

    举个例子,假设 features 的形状是 (6, 3),并且 batch_indices 是 torch.tensor([1, 3, 5]),那么:

    features = torch.tensor([
        [1.0, 2.0, 3.0],  # 样本 0
        [4.0, 5.0, 6.0],  # 样本 1
        [7.0, 8.0, 9.0],  # 样本 2
        [10.0, 11.0, 12.0],  # 样本 3
        [13.0, 14.0, 15.0],  # 样本 4
        [16.0, 17.0, 18.0],  # 样本 5
    ])
    
    batch_indices = torch.tensor([1, 3, 5])
    batch_features = features[batch_indices]
    

    batch_features 的结果将是:

    tensor([
        [ 4.0,  5.0,  6.0],  # 对应样本 1
        [10.0, 11.0, 12.0],  # 对应样本 3
        [16.0, 17.0, 18.0]   # 对应样本 5
    ])
    

    batch_features 的形状是 (3, 3),其中 3 是 batch_size,而 3 是每个样本的特征数量。

2.yield 使得函数成为生成器,每次迭代时会返回一个批次的数据,而不是一次性返回所有数据。

数据生成代码结果

 初始化模型参数及模型定义

w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)      #两行的列向量
b = torch.zeros(1, requires_grad=True)       # 标量

def linreg(X, w, b):  #@save
    """线性回归模型"""
    return torch.matmul(X, w) + b   #广播机制

划重点: 需要w,b 进行更新,所以才将requires_grad设置为True

广播机制: 当我们用一个向量加一个标量时,标量会被加到向量的每个分量上。

损失函数和优化算法

def squared_loss(y_hat, y):  #@save
    """均方损失"""
    return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2    #1/2(y_hat-y)² **代表幂运算,最后要除以2

def sgd(params, lr, batch_size):  #@save      #params表示我们的参数,可能是w,可能是b;而lr表示学习率,batch_size
    """小批量随机梯度下降"""
    with torch.no_grad():    #不计算梯度 why?   下面进行训练的时候是计算梯度的时候再计算梯度
        for param in params:
            param -= lr * param.grad / batch_size       #损失函数中没有求均值,故这里要求一下均值。可是我损失求均值干嘛呢?这里是10个样本累加的梯度,所以现在要除以10,得到每个样本的梯度
            param.grad.zero_()       #手动的将梯度设为0,这样下一次计算梯度的时候就不会与上一次相关了

此处无需计算梯度,因为训练部分在调用sgd函数前用l.sum().backward()计算了梯度

这里求均值的地方我一开始不理解,注意.grad求的是总梯度!:

(1)梯度的累加

param.grad 表示当前小批量所有样本的梯度总和。这是因为在每次调用 loss.backward() 计算梯度时,PyTorch 默认会将每个样本的梯度累加到参数的梯度中。

假设我们有一个小批量数据,其中包含 10 个样本。在进行反向传播时,PyTorch 会计算每个样本的梯度,并将这些梯度累加到 param.grad 中。具体的计算过程如下:

  • 对于每个样本 i,计算损失函数对每个参数的梯度。记作 grad_i

  • 将这些梯度累加起来,得到整个小批量的总梯度。例如,param.grad 会累加 10 个样本的梯度:

(2)计算平均梯度

在实际的梯度下降更新中,我们通常会计算平均梯度。因为 param.grad 是总梯度,除以 batch_size 可以得到每个样本的平均梯度,使得参数更新步幅与批量大小无关。这是为了保持梯度更新的一致性,不论小批量的大小如何。

param -= lr * param.grad / batch_size

在这行代码中,param.grad 是总梯度,而 param.grad / batch_size 是平均梯度。通过除以 batch_size,我们确保每次参数更新都基于每个样本的平均梯度,从而使得梯度更新的步伐更加平滑。

训练部分

lr = 0.03        #学习率
num_epochs = 3    #训练轮数
net = linreg     #模型名称,这里为线性回归模型
loss = squared_loss     #损失函数,这里为随机梯度下降

for epoch in range(num_epochs):
    for X, y in data_iter(batch_size, features, labels):     #首先循环批次
        with autograd.record():
            l = loss(net(X, w, b), y)  # X和y的小批量损失
        # 计算l关于[w,b]的梯度
        l.backward()
        sgd([w, b], lr, batch_size)  # 使用参数的梯度更新参数
    with torch.no_grad():
        train_l = loss(net(features, w, b), labels)      #利用整个数据进行评估评价
        print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')

完整代码

  pycharm中实现

import random
import torch
import matplotlib.pyplot as plt
import mxnet as mx
from mxnet import autograd, nd


def synthetic_data(w, b, num_examples):  #@save
    """生成y=Xw+b+噪声"""
    X = torch.normal(0, 1, (num_examples, len(w)))  #均值为0,方差为1,生成num_examples个样本,每个样本的特征个数为len(w)
    y = torch.matmul(X, w) + b   #计算y
    y += torch.normal(0, 0.01, y.shape)   #y加上噪音
    return X, y.reshape((-1, 1))       #第二维大小为1,即转化成了列向量

true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)

print('features:',features[0],'\nlables:',labels[0])


# plt.figure(figsize=(4, 3))
# plt.scatter(features[:, 1], labels, 1)
# plt.show()

def data_iter(batch_size, features, labels):
    num_examples = len(features)
    indices = list(range(num_examples))      #提取0到num_examples-1,作为索引,返回list类型
    # 这些样本是随机读取的,没有特定的顺序
    random.shuffle(indices)        #将顺序打乱
    for i in range(0, num_examples, batch_size):     #从0开始到num_examples,每次跳batch_size个大小,那么就是1000/10=100次
        batch_indices = torch.tensor(
            indices[i: min(i + batch_size, num_examples)])       #从i开始,到i + batch_size,拿出batch_size数据,min函数保证了数据溢出的情况,将已有数据保存即可,只是b(样本数)的数量会减少罢了
        yield features[batch_indices], labels[batch_indices]   #yield 使得函数成为生成器,每次迭代时会返回一个批次的数据,而不是一次性返回所有数据。
#这个函数是调用一次返回一次

batch_size = 10

for X, y in data_iter(batch_size, features, labels):
    print(X, '\n', y)
    break     #注意这个break,没有这个break会全部输出100次,一次有10行的数据。

w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)      #两行的列向量
b = torch.zeros(1, requires_grad=True)       # 标量

def linreg(X, w, b):  #@save
    """线性回归模型"""
    return torch.matmul(X, w) + b   #广播机制

def squared_loss(y_hat, y):  #@save
    """均方损失"""
    return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2    #1/2(y_hat-y)² **代表幂运算,最后要除以2

def sgd(params, lr, batch_size):  #@save      #params表示我们的参数,可能是w,可能是b;而lr表示学习率,batch_size
    """小批量随机梯度下降"""
    with torch.no_grad():    #不计算梯度 why?   下面进行训练的时候是计算梯度的时候再计算梯度
        for param in params:
            param -= lr * param.grad / batch_size       #损失函数中没有求均值,故这里要求一下均值。可是我损失求均值干嘛呢?这里是10个样本累加的梯度,所以现在要除以10,得到每个样本的梯度
            param.grad.zero_()       #手动的将梯度设为0,这样下一次计算梯度的时候就不会与上一次相关了

lr = 0.03        #学习率
num_epochs = 3    #训练轮数
net = linreg     #模型名称,这里为线性回归模型
loss = squared_loss     #损失函数,这里为随机梯度下降

for epoch in range(num_epochs):
    for X, y in data_iter(batch_size, features, labels):     #首先循环批次
        with autograd.record():
            l = loss(net(X, w, b), y)  # X和y的小批量损失
        # 计算l关于[w,b]的梯度
        l.backward()
        sgd([w, b], lr, batch_size)  # 使用参数的梯度更新参数
    with torch.no_grad():
        train_l = loss(net(features, w, b), labels)      #利用整个数据进行评估评价
        print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')

P4 线性回归的简洁实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值