动手学深度学习笔记-线性回归
线性回归
基础算法
平方损失(也称为均方误差损失,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维的向量变成了实数,也叫做范数
- ||x||模长必须是非负的,而且只允许0长度的向量是0
- ||ax||=|a| ||x||
- ||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
模块是用来构建符号表达式的,这允许你定义计算图而不立即执行计算。
sym.var('a')
和sym.var('b')
创建了两个符号变量a
和b
。这些变量可以被用来构建更复杂的表达式。c = 2 * a + b
定义了一个新的符号表达式c
,它是a
的两倍加上b
。这里2 * a
是一个元素级别的乘法操作,即每个a
的元素都乘以 2,然后将结果与b
相加。
重要的是要注意,这段代码并没有真正执行任何计算。它仅仅构建了一个表达式树,描述了如何从输入变量 a
和 b
计算出输出 c
。为了实际进行计算,你需要绑定具体的数值到变量 a
和 b
,然后调用 forward
方法或使用 mxnet.ndarray
模块来执行计算。
from mxnet import sym
a=sym.var()
b=sym.var()
c=2*a+b#
隐式构造
- 将代码分解成操作子
- 将计算表示成一个无环图
显示构造:TensorFlow/Theano/MXNet
隐式构造:PyTorh/MXNet
autograd.record()
是一个上下文管理器,它会记录在其内部执行的所有计算操作。当进入这个上下文时,MXNet 开始记录所有操作,以便之后能够追踪这些操作并计算梯度。- 分别创建了两个形状为
(2, 1)
的 NDArray,其中所有的元素都是 1。a
和b
都是 2x1 的列向量,每个向量有两个元素,且每个元素的值都是 1。 - 最后定义了
c
,它是a
的两倍加上b
。由于a
和b
都是 NDArray,这里的运算会应用到数组的每一个元素上,即元素级的乘法和加法。因此,c
也将是一个(2, 1)
形状的 NDArray,每个元素的值为2*1+1=3
。 - 整个过程是在
autograd.record()
的上下文中进行的,这意味着 MXNet 会自动记录下a
,b
,c
之间的依赖关系,即计算图。这样,当你需要计算c
关于a
或b
的梯度时,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}')
随机梯度下降的随机是随机采样的意思