动手学深度学习笔记-线性回归

线性回归

基础算法

平方损失(也称为均方误差损失,Mean Squared Error Loss,MSE Loss)是一种用于量化预测值与实际值之间差异的损失函数,主要用于回归分析中。在回归任务中,模型的目标是预测一个连续的数值,而不是分类标签。

平方损失函数定义为预测值与真实值之差的平方,对于单个数据点,其公式如下:

$$ L(y, \hat{y}) = \frac{1}{2} (y - \hat{y})^2 $$

这个叫做平方损失,乘以二分之一是之后求导方便于消元。其中y是真实值,$\hat{y}$是估计值 损失函数

$$ \ell(\mathbf X, \mathbf y, \mathbf w, b) = \frac{1}{2n} \sum_{i=1}^{n} (y_i - \langle x_i; w \rangle - b)^2 =\frac{1}{2n} || y - Xw - b || $$

公式的意思是第i个样本,的真实结果yi-x于权重的内积再减去偏差,中间是代数写法,最后是矩阵写法,最后向量y-矩阵X于矩阵w-偏置

为了最小化损失

$$ \mathbf w*, \mathbf b* = \arg \min_{ w, b}{\ell(\mathbf X,\mathbf y,\mathbf w, b)} $$

这里的w和b表示权重向量偏置的最佳参数,一般用星号也就是superscript来表示最佳参数。

因为线性模型是存在显示解的,

显示解(Explicit Solution),在数学分析和微分方程领域,指的是一个可以直接通过基本函数的组合表示出来的解,这种解形式上明确,不需要额外的计算步骤去近似或迭代求解。显示解通常以独立变量的显式函数形式给出,可以直接观察到解与变量之间的关系。

说人话就是不需要循环迭代梯度,直接计算出最有参数

如果我们将偏差加入权重x<-[x,1] w<-[w b]

这里的权重向量w通过在最后添加偏置项b来扩展

$$ \begin{align} \ell(\mathbf X,\mathbf y,\mathbf w) = \frac{1}{2n} ||y - X\mathbf w||^2 \end{align} $$

范数:范数可以定义在任何线性空间上

模长是一个映射,即向量距离原点的长度,他把一个n维的向量变成了实数,也叫做范数

  1. ||x||模长必须是非负的,而且只允许0长度的向量是0
  2. ||ax||=|a| ||x||
  3. ||x+y||<=||x||+||y||

最常见的p-norm

$$ \lVert \mathbf x \rVert_p := \sqrt[p]{|x_1|^p + \cdots + |x_n|^p}, p \geq 1 $$

它的定义就是把x各个分量的程度的p次方和开p次方

1范数就是各个分量长度的和

$$ \lVert \mathbf x \rVert_1 := \lVert x1 \rVert +\lVert x2 \rVert $$

2范数就是我们平时用的模长

$$ \lVert \mathbf x \rVert_2 := \sqrt[2]{x_1^2 + \cdots + x_n^2} $$

1范数是曼哈顿距离

曼哈顿距离,也被称为城市街区距离、L1距离或出租车距离,是一种在网格布局中衡量两点之间距离的方法。它来源于在曼哈顿这样的城市中,街道布局通常是网格状的,车辆通常只能沿街道(水平或垂直方向)行驶而不能斜行,因此从一个地点到另一个地点的最短路径是沿着街道的直线距离。

2范数是欧几里得距离(欧式范数)

比如$x \begin{bmatrix} 4 \\ 3 \end{bmatrix}$他的1范数是7,二范数是5,无穷范数,就是看分量当中较大的一个数,也就是4

||y-Xw|| 表示向量y-Xw的范数,这里应该是2范数。

下面的式子是对上式求偏导的结果。

$$ \begin{equation} \frac{\partial}{\partial w} \ell(\mathbf X,\mathbf y,\mathbf w) = \frac{1}{n} (\mathbf y - \mathbf X\mathbf w)^T X \end{equation} $$

损失是凸函数,所以最优解满足与梯度为0的地方。

俩个箭头是推到过程

$$ \frac{\partial}{\partial w} \ell(X,y,w) = 0 \Leftrightarrow \frac{1}{n} (\mathbf y - \mathbf X\mathbf w)^T \mathbf X = 0 \Leftrightarrow \mathbf w^* = (\mathbf X^T\mathbf X)^{-1}\mathbf X\mathbf y $$

所以可知

线性回归一般不需要做多层神经网络,

  • 线性回归是对n维输入的加权,外加偏差
  • 使用平方损失来衡量预测值和真实值的差异
  • 线性回归有显示解
  • 线性回归可以看做单层神经网络

机器学习通常是用来学习解决np-complete所以能得到显示解的模型过于简单,一般不会使用

np-hard:解可能无法在多项式时间内验证

np-complete:解可以在多项式时间内验证

NP-complete是NP-hard的一个子集

极大似然估计

数据与函数对应的欧氏距离可以作为有效指标,距离越近越准确

$$ \mathcal L =\sum^n_i(y_i-\hat y_i) $$

所以要找什么参数呢,要找所有的距离相加整体平方和最小的那个数。这种方法也被叫做最小二乘估计

高斯分布的参数是由mu和sigma控制,参数mu是均值,mu变化分布在坐标轴平移,sigma是标准差,sigma越大分布越散。均值为0,标准差唯一的高斯分布又被叫做正态分布。

基础优化算法

挑选一个初始值w0

重复迭代参数t=1,2,3

这一次的迭代值是上一次的迭代值乘以学习率,他会沿着梯度下降最快的方向走。学习率就是走多远

$$ \mathbf w_t = \mathbf w_{t-1} - \eta \frac{\partial l}{\partial \mathbf w_{t-1}} $$

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

学习率的选择不能太小也不能太大,计算梯度的代价比较高昂,学习率过低会导致模型的收敛的速度变慢,太大了会导致错过最优点。

小批量随机下降

我们随机采样b个样本,i1,i2,i3,ib来近似损失

  • b是批量的大小

$$ \frac{1}{b}\sum_{i \in I_b}^{}\ell(x_i, y_i, \mathbf w) $$

每次计算批量太小,不适合并行来最大化资源利用率

如果太大,内存消耗增加,浪费计算

梯度下降就是沿着梯度的反方向更新参数求解。

自动求导

求导方式

符号求导

输入:$D[4x^3+x^2,x]$

输出:$2x+12x^2$

数值求导

$$ \frac{\partial f(x)}{\partial x} = \lim_{h \to 0} \frac{f(x+h) - f(x)}{h} $$

计算图(符号求导)

将代码分解成操作子,将计算表示成一个无环图

显示构造

  • 将代码分解成操作子
  • 将计算表示成一个无环图
  • 显示构造(数学上用的都是显示的构造)

在 MXNet 中,sym 模块是用来构建符号表达式的,这允许你定义计算图而不立即执行计算。

  1. sym.var('a')sym.var('b') 创建了两个符号变量 ab。这些变量可以被用来构建更复杂的表达式。
  2. c = 2 * a + b 定义了一个新的符号表达式 c,它是 a 的两倍加上 b。这里 2 * a 是一个元素级别的乘法操作,即每个 a 的元素都乘以 2,然后将结果与 b 相加。

重要的是要注意,这段代码并没有真正执行任何计算。它仅仅构建了一个表达式树,描述了如何从输入变量 ab 计算出输出 c。为了实际进行计算,你需要绑定具体的数值到变量 ab,然后调用 forward 方法或使用 mxnet.ndarray 模块来执行计算。

from mxnet import sym
a=sym.var()
b=sym.var()
c=2*a+b#

隐式构造

  • 将代码分解成操作子
  • 将计算表示成一个无环图

显示构造:TensorFlow/Theano/MXNet

隐式构造:PyTorh/MXNet

  1. autograd.record() 是一个上下文管理器,它会记录在其内部执行的所有计算操作。当进入这个上下文时,MXNet 开始记录所有操作,以便之后能够追踪这些操作并计算梯度。
  2. 分别创建了两个形状为 (2, 1) 的 NDArray,其中所有的元素都是 1。ab 都是 2x1 的列向量,每个向量有两个元素,且每个元素的值都是 1。
  3. 最后定义了 c,它是 a 的两倍加上 b。由于 ab 都是 NDArray,这里的运算会应用到数组的每一个元素上,即元素级的乘法和加法。因此,c 也将是一个 (2, 1) 形状的 NDArray,每个元素的值为 2*1+1=3
  4. 整个过程是在 autograd.record() 的上下文中进行的,这意味着 MXNet 会自动记录下 a, b, c 之间的依赖关系,即计算图。这样,当你需要计算 c 关于 ab 的梯度时,MXNet 可以通过反向传播算法自动计算这些梯度。
from mxnet import autograd,nd
with autograd.record():
    a=nd.ones((2,1))
    b=nd.ones((2,1))
    c=2*a+b

自动求导的俩种方式

自动求导有俩种模式,正向积累和反向积累又称反向传递

前向:执行图,存储中间结果

反向:从相反的方向执行图

  • 去除不需要的枝

复杂度

O(n),n是操作子个数通常正向和方向的代价类似

他们时间复杂度类似,但是正向累计空间复杂度是O(n)但是反向累计的空间复杂度是O(1)

代码

import torch

# 返回1维0-3的tensor
x = torch.arange(5.0)
# 在计算y关于x的梯度之前,我们需要一个地方来存储梯度
# 设置张量requires_grad 的属性为True,这个属性决定了是否要为该张量记录操作历史以便于自动求导
x.requires_grad_(True)
y = 2 * torch.dot(x, x)  # x与x的内积乘以2
y.backward()
print(x.grad == 4 * x)  # 输出四个true

# 现在我们计算x的另一个函数
# 清零x的梯度
x.grad.zero_()
# 计算sum()
y = x.sum()
y.backward()
# 对内部所有元素求导,所以每个都输出1
print(x.grad)

x.grad.zero_()
y = x * x
print(y)
# 等价于y.backward(torch.ones(len(x)))
# 我们很少对于向量的函数来进行求导,基本上都是对于标量来进行求导,所以要先对y中的所有向量进行求和
y.sum().backward()
print(x.grad)  # 求导是2x,所以最后的输出是每个tensor*2

print("------------------")
# 将某些计算挪到计算图以外
x.grad.zero_()
y = x * x
# detach用于分离一个张量,使得该张量在随后的操作中不会被跟踪。
u = y.detach()
z = u * x
z.sum().backward()
print(x.grad == u)  # 全为true,因为拿系数乘以x后求导还是x数

x.grad.zero_()
y.sum().backward()
print(x.grad == 2 * x)


# 计算构建的计算图需要通过复杂的python控制流,如条件循环等,我们依旧可以求导
def f(a):
    b = a * 2
    while b.norm() < 1000:  # 对张量算2范数
        b = b * 2
    if b.sum() > 0:
        c = b
    else:
        c = 100 * b
    return c


# randn是pytorch从从随机正态分布中采样,size为空表示传一个空元素,表示生成的张量没有维度(标量)
a = torch.randn(size=(), requires_grad=True)
d = f(a)  # 这个d其实就是ka
d.backward()
print(a.grad == d / a)  # ka/a==对ka求导

再深度学习中对于标量求导而不是向量,是因为很多时候都是对loss求导,而loss是一个标量。

线性回归代码

从0实现线性回归

import random
import torch
from d2l import torch as d2l


# 生成随机数据
def synthetic_data(w, b, num_examples):
    # 生成y=Xw+b+噪声,normal从正态分布进行采样,均值为0标准差为1 第三个参数是采样结果的shape
    X = torch.normal(0, 1, (num_examples, len(w)))  # 样本数量为num_examples,len是w权重的维度
    y = torch.matmul(X, w) + b  # 对w和x进行矩阵乘法 这里是真值
    # print('y.reshape((-1, 1)', y.reshape((-1, 1)))
    # 这里是对真值增加噪声
    y += torch.normal(0, 0.01, y.shape)  # 均值为0,方差为0.01,的随机噪音,y.shape返回张量y的维度信息
    # reshape 输入一个shape为[12]的变量,会输出一个[12,1] 返回的是随机采样的点,
    return X, y.reshape((-1, 1))  # 作用是将一个1d向量(张量)转换为一个2d矩阵 -1是一个特殊值,表示自动计算


# 接收批量大小、特征矩阵和标签向量作为输入,生成大小为batch_size的小批量
def data_iter(batch_size, features, labels):
    num_examples = len(features)  # 样本数
    indices = list(range(num_examples))  # 生成[0- num_examples-1]的数组
    random.shuffle(indices)  # 打乱,原地操作
    for i in range(0, num_examples, batch_size):
        # 从i开始,到i+batch_size,拿b个index出来,min是没有拿满的话取最小值
        batch_indices = torch.tensor(indices[i:min(i + batch_size, num_examples)])
        yield features[batch_indices], labels[batch_indices]


# 线性模型
def linreg(X, w, b):
    return torch.matmul(X, w) + b


# 定义损失函数,均方损失
def squared_loss(y_hat, y):
    return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2


# 小批量随机梯度下降  Stochastic Gradient Descent
def sgd(params, lr, batch_size):
    # 禁用自动梯度计算
    with torch.no_grad():
        for param in params:
            param -= lr * param.grad / batch_size
            # 手动把梯度设置为0
            param.grad.zero_()


# 创建一个张量,[2, -3.4]
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)

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

d2l.set_figsize()
d2l.plt.scatter(features[:, 1].detach().numpy(), labels.detach().numpy(), 1)
d2l.plt.show()

batch_size = 10
for X, y in data_iter(batch_size, features, labels):
    print(X, '\n', y)
    break

# 创建一个2x1的张量,其中的元素是从均值为0,标准差为0.01的正态分布抽取的随机权重
# requires_grad=True:这个参数告诉PyTorch需要记录这个张量上的所有操作,
# 以便可以进行自动微分。这意味着你可以在这个张量上执行反向传播来计算梯度
w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True)
# 第一个参数是shape,这是创建一个 1行元素为0
b = torch.zeros(1, requires_grad=True)

# 学习率
lr = 0.03
# 扫三次
num_epochs = 3
# 这是使用线性回归模型,这样定义之后方便修改模型
net = linreg
# loss这里是均方损失
loss = squared_loss

for epoch in range(num_epochs):
    for X, y in data_iter(batch_size, features, labels):
        l = loss(net(X, w, b), y)  # x和y的小批量损失
        # 因为l形状是(batch_size,1)而不是一个标量
        # 并以此计算[w,b]的梯度
        l.sum().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}')

print(f'w的估计误差:{true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差:{true_b - b}')

随机梯度下降的随机是随机采样的意思

Last modification:July 8, 2024
如果觉得我的文章对你有用,请随意赞赏