0基础动手学深度学习-李沐-ML-线性回归及其从零开始实现

引子:房价预测

一个简化模型:

​ 假设1:卧室个数、卫生间个数和居住面积会影响房价,记为x1,x2,x3

​ 假设2:成交价是关键因素的加权和 请添加图片描述

			权重和偏差的实际值在之后决定

线性模型(单层的神经网络):

请添加图片描述

衡量预估值:

​ 比较真实值和预估值

平方损失:

​ (下方公式中 y y y是真实值)

在这里插入图片描述

训练数据:

​ 一些可以计算参数值(权重和偏差)的数据点,通常越多越好

假设有n个样本

这里的xi是一个列向量,X转置之后的每一行就是一个样本数据,每一个yi就是一个实际值(房价预测中代表最终成交价)
参数学习:

在这里插入图片描述

训练损失:

在这里插入图片描述

最小化损失学习参数:

​ 这里的 a r g min ⁡ arg \min argmin的意思时获取当后面计算式最小时的参数值(权值和偏差)

在这里插入图片描述

线性回归的显式解:
将偏差加入权重:

在这里插入图片描述

此时损失的计算为:(注意:这里在求一阶导数时少了一个负号)

在这里插入图片描述

因为这里计算损失的二阶导数大于0,所以损失是一个凸函数,则最优解(梯度等于0的位置)满足:(最后一行最后一个X应该为XT)

在这里插入图片描述

基础优化算法

梯度下降Stochastic Gradient Descent

原理:

沿着梯度方向函数增加的最快,因此为了使损失函数的值最小, 沿着负梯度方向进行一定的减少,这个就叫做梯度下降。

算法过程:

​ 1.挑选一个初始值w0

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

在这里插入图片描述
在这里插入图片描述

参数 η η η

称为学习率,步长的负参数(学习率的选取要适当)

小批量随机梯度下降

​ 对整个训练集上进行梯度下降,时间成本很高,空间损耗也比较高,因此提出了小批量随机梯度下降。

流程:

在原有的数据样本集中随机选取b个数据样本(数据量不可过大或过小)来计算近似损失函数,对此近似损失函数进行梯度下降

在这里插入图片描述

参数b(batch size):

​ 批量大小,另一个重要的超参数

线性回归的从零开始实现

线性回归:

​ 利用数理统计中的回归分析来确定两种或两种以上变量间相互依赖的定量关系的统计分析方法。

实现时用到的函数

torch.normal(means, std, out=None):
功能:

​ 返回一个张量,包含从给定参数means,std的离散正态分布中抽取随机数。

参数:
means(均值):

一个张量,包含每个输出元素相关的正态分布的均值

std(方差):

一个张量,包含每个输出元素相关的正态分布的标准差。

注意:均值(means)和方差(std)的形状不须匹配,但每个张量的元素个数须相同。且最终的返回值张量中的元素个数即为均值(方差)张量元素的个数
out(可选的输出):

一个张量

例:
>>> torch.normal(means=torch.arange(1, 11), std=torch.arange(1, 0, -0.1))

 1.5104
 1.6955
 2.4895
 4.9185
 4.9895
 6.9155
 7.3683
 8.1836
 8.7164
 9.8916
[torch.FloatTensor of size 10]
# torch.normal(mean=0.0, std, out=None)
# 所有抽取的样本共享均值
# torch.normal(means, std=1.0, out=None)
# 所有抽取的样本共享标准差
torch.matmul()函数:
功能:

tensor的乘法,输入可以是高维(与torch.mm()函数不同,torch.mm()只能适用于两个二维矩阵相乘),有两个参数,且两个参数必须都要是张量(维度可以不同)

当输入都是二维张量时:

就是矩阵乘法,与tensor.mm()函数用法相同

当输入有多维张量时:

把多出的一维作为batch提出来,其他部分做矩阵乘法

一个3维一个2维的例子

请添加图片描述

两个3维的例子:

由于广播机制的存在,会先在b的第0维进行复制,复制为2,与a的第0维匹配,最后后两维做矩阵乘法

请添加图片描述

官网例子:

将上述例子所用方法进行组合即可

请添加图片描述

d2l.set_figsize()函数:
功能:

用于设置matplotlib图形的大小,可以根据需求调整图形的尺寸,使得图形更好地展示数据特征和模式,并方便进行比较和理解。

参数:
figsize:

一个包含两个元素的列表或元组,分别表示图形的宽度和高度。

实例:
import d2l
# 设置图形大小为宽度为6,高度为4
d2l.set_figsize(figsize=(6, 4))
d2l.plt.scatter()函数:
功能:

在二维平面上绘制散点图,可以用于可视化数据的分布和关系。

参数:
x:

表示散点图中点的x坐标的数组或列表。

y:

表示散点图中点的y坐标的数组或列表。

s:

表示散点的大小,默认值为20。

ax:

表示散点图所在的坐标轴,默认值为None,表示使用当前坐标轴。

# 该代码生成一个包含100个随机位置、大小和颜色的散点图,散点的大小和颜色是随机生成的。
x = np.random.rand(100)
y = np.random.rand(100)
colors = np.random.rand(100)
sizes = np.random.rand(100) * 100

d2l.plt.scatter(x, y, s=sizes, c=colors, marker='o')
d2l.plt.show()
range(start, end[, step])函数:
功能:

生成包含连续多个整数的range对象

参数:
start:

计数从start开始,默认是从0开始

stop:

计数到stop结束,但是不包括stop。

step:

步长,默认为1.

注意:range()函数返回的是一个迭代器,而不是一个数字列表,如果想通过迭代器生成列表,需要写为list(range())
例:
print(list(range(0, 5)))
#########输出为#############
# [0, 1, 2, 3, 4]
random.shuffle()函数:
功能:

将一个列表中的元素打乱顺序,但并不会生成新的列表

例:
import random

x = list(range(10))
print(x)
random.shuffle(x)
print(x)
#######输出为#########
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# [2, 5, 4, 8, 0, 3, 7, 9, 1, 6]

生成器函数:

含有 ‘yield’ 关键字的函数,调用该函数时会返回一个生成器。

def foo():
    print("starting...")
    while True:
        res = yield 4
        print("res:",res)
g = foo()
print(next(g))
print("*"*20)
print(next(g))
##############输出为##########
# starting...
# 4
# ********************
# res: None
# 4
注:一个带有 yield 的函数就是一个生成器函数,当我们使用 yield 时,它帮我们自动创建了__iter__() 和 next() 方法,在没有数据时,会抛出 StopIteration 异常,也就是创建了一个迭代器
代码运行顺序:

​ 1.程序开始执行以后,因为foo函数中有yield关键字,所以foo函数并不会真的执行,而是先得到一个生成器g

​ 2.直到调用next方法,foo函数正式开始执行,先执行foo函数中的print方法,然后进入while循环

​ 3.程序遇到yield关键字,然后把yield想象成return,return了一个4之后,程序停止,并没有执行赋值给res操作,此时next(g)语句执行完成,所以输出的前两行(第一个是while上面的print的结果,第二个是return出的结果)是执行print(next(g))的结果

​ 4.程序执行print(“*”*20),输出20个

​ 5.执行下面的print(next(g)),这时是从刚才那个next程序停止的地方开始执行的,也就是要执行res的赋值操作,这时候要注意,这个时候赋值操作的右边是没有值的(因为刚才那个是return出去了,并没有给赋值操作的左边传参数),所以这个时候res赋值是None,所以接着下面的输出就是res:None

​ 6.程序会继续在while里执行,又一次碰到yield,这个时候同样return 出4,然后程序停止,print函数输出的4就是这次return出的4.

生成器之send函数:
实例:
def foo():
    print("starting...")
    while True:
        res = yield 4
        print("res:",res)
g = foo()
print(next(g))
print("*"*20)
print(g.send(7))
###########输出为##########
# starting...
# 4
# ********************
# res: 7
# 4
代码运行顺序:

​ 1-4.与上文执行流程一致

​ 5.程序执行g.send(7),程序会从yield关键字那一行继续向下运行,send会把7这个值赋值给res变量(相当于在原来yield返回4之后,仅在这一轮循环中将赋值符右边的值替换为7)

​ 6.由于send方法中包含next()方法,所以程序会继续向下运行执行print方法,然后再次进入while循环

​ 7.由于send方法中包含next()方法,所以程序会继续向下运行执行print方法,然后再次进入while循环

with torch.no_grad()用法:
使用背景:

在使用pytorch时,并不是所有的操作都需要进行计算图的生成,只是想要网络结果的话就不需要反向传播 ,如果需要通过网络输出的结果去进一步优化网络的话,就需要反向传播了。

不使用with torch.no_grad():

此时有grad_fn=属性,计算的结果在一计算图当中,可以进行梯度反向传播优化参数等操作。

使用with torch.no_grad():

表明当前计算不需要反向传播,使用之后,强制后边的内容不进行计算图的构建,此时grad_fn属性赋值为None

使用with torch.no_grad()与否并不会影响计算的结果,只会影响到是否生成计算图,计算图会占用一定的内存空间,当数据集很大时,内存的耗费就会很大
联系密切的requires_grad:

tensor有一个requires_grad参数,如果设置为True,则反向传播时,该tensor就会自动求导。tensor的requires_grad的属性默认为False,若一个节点(叶子变量:自己创建的tensor)requires_grad被设置为True,那么所有依赖它的节点requires_grad都为True(即使其他相依赖的tensor的requires_grad = False)

with torch.no_grad的作用:

在该模块下,所有【计算】得出的tensor的requires_grad都自动设置为False。直接【赋值】一个有梯度的tensor,所得到的tensor还是有梯度的

线性回归从零实现代码

import random
import torch
from d2l import torch as d2l
import matplotlib.pyplot as plt


def synthetic_data(w, b, num_examples):
    """生成y = Xw + b + 噪声"""
    # 生成一个具有num_examples行,len(w)列的张量,这个张量的每一行代表一个样本数据,一共有num_examples个样本数据
    X = torch.normal(0, 1, (num_examples, len(w)))
    # 计算X矩阵与w向量(权值)相乘所得的列向量,之后与b(偏差)求和,所得结果即为公式中的 Xw + b
    y = torch.matmul(X, w) + b
    # 在上一步计算的结果上添加一个以0为均值,0.01为方差的正态分布上的随机值(即为公式中的噪声),因为y是可变数据类型,所以这一步是原地操作
    y += torch.normal(0, 0.01, y.shape)
    # 将创建的样本集作为返回值,同时将经过计算所得的预测值y向量reshape成为一个列向量作为另一个返回值
    return X, y.reshape((-1, 1))


true_w = torch.tensor([2, -3.4])
true_b = 4.2
# 形成1000个特征,从而构成数据集,同时形成标注,训练样本创建完成
features, labels = synthetic_data(true_w, true_b, 1000)
# 设置matplotlib图形的大小
d2l.set_figsize()
# 在二维平面上绘制一个散点图,横轴为features第一列的元素值,纵轴为labels的元素值,一一对应
d2l.plt.scatter(features[:, 1].detach().numpy(), labels.detach().numpy(), 1)


# 获取样本集
def data_iter(batch_size, features, labels):
    """根据批量大小、特征矩阵和标签向量作为输入,生成大小为batch_size的小批量"""
    # 样例数目
    num_examples = len(features)
    # 生成一个与样例数目一一对应的下标
    indices = list(range(num_examples))
    # 将样本数据集打乱,随机读取,无特定顺序
    random.shuffle(indices)
    # 生成多组大小为batch_size的小批量
    # 因为上一步使用到了random.shuffle打乱了数据集,所以这一步可以保证是随机抽样
    for i in range(0, num_examples, batch_size):
        # 获取一个小批量中所有样本的下标,min()函数是为了保证不超出样本集大小
        batch_indices = torch.Tensor(indices[i:min(i + batch_size, num_examples)]).type(torch.long)
        # 这里使用生成器函数(带有yield的函数)是为了节省内存空间
        yield features[batch_indices], labels[batch_indices]


# 每次随机从样本集中抽取10个样本构成一个小批量的样本集(批量大小)
batch_size = 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):
    """线性回归模型"""
    return torch.matmul(X, w) + b


# 定义损失函数
def squared_loss(y_hat, y):
    """"均方损失"""
    # y_hat是真实值,为了保证预测值y能够与y_hat进行向量加减法(前提是两个向量的形状相同)
    # 因此使用reshape函数将真实值的形状与预测值的形状统一
    return (y_hat - y.reshape(y_hat.shape))**2 / 2  # 并没有对所有的样本取均值


# 定义优化算法
def sgd(params, lr, batch_size):
    """小批量随机梯度下降"""
    # params(list)包含所有的参数(w和b)
    # lr是学习率
    # batch_size是批量大小
    # 计算图中的梯度无需更新,只需要梯度计算后的结果,无需生成计算图(无需进行梯度反向传播)
    with torch.no_grad():
        # 梯度下降更新参数,使得损失函数的值最小
        for param in params:
            # 梯度下降的数学表达式,由于在均方损失函数中并没有计算均值,因此在求梯度时要求均值
            # 对所有损失求梯度后求和取均值与对所有损失的均值求梯度本质上是一致的(线性运算)
            param -= lr * param.grad / batch_size
            # 在下一次计算梯度时,由于python不会自动清空梯度,要将梯度手动清空
            param.grad.zero_()


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):
        # 计算在当前权重和偏差下的小批量损失
        l = loss(net(X, w, b), y)
        # 由于squared_loss函数最终求得小批量损失形状是(batch_size, 1),而不是一个标量
        # 求解梯度是一般用到标量,因此将l中的所有元素加到一起,并以此计算关于[w, b]的梯度
        l.sum().backward()
        sgd([w, b], lr, batch_size)  # 使用参数的梯度更新参数
    # 只需要计算通过反向传播更新出的新的参数,并不需要更新计算图中各个节点的梯度
    with torch.no_grad():
        train_l = loss(net(features, w, b), labels)
        # 由于squared_loss函数计算的返回值是一个向量,是小批量数据集对应的损失值,求均值
        print(f'epoch{epoch + 1},loss{float(train_l.mean()):f}, w {w}, b {b}')
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值