【深度学习】跟李沐学ai 线性回归 从零开始的代码实现超详解

目录

  • 一、引言
  • 二、本文代码做了什么
    • 如何利用数据集训练
  • 三、代码实现与解析
    • 一、导包
    • 二、相应的函数实现
      • 1 生成样本(数据集)
      • 2 按批量读取数据集
      • 3 定义模型 损失函数 算法
        • 1 定义模型
        • 2 定义损失函数(均方误差)
        • 3 定义优化算法(梯度下降法)
    • 三、开始训练
  • 四、完整代码如下

一、引言

  最近在学习深度学习,看李沐老师的b站视频,光是开头的线性回归的代码实现便看了好多遍。
  由于本人水平不高,基础不厚实,在阅读py代码时出现了极大问题。幸运的是有评论区,弹幕区,dive into deep learning等网络资源帮助我的理解。
  特此整理,线性回归从零开始的代码实现。既帮自己复习回顾,也向别的初学同学分享一下我的思路。

二、本文代码做了什么

1 根据设定的true_w,true_b生成数据集
2 利用数据集进行训练, 得到w和b
3 观察w和true_w , b和true_b的误差,以验证训练效果
4 极大增强了初学者的自信心(狗头

如何利用数据集训练

1 初始化参数,获得随机的w和b
2 计算该w和b下的损失值
3 计算梯度,利用梯度下降法,调整w和b
4 重复2和3,重复次数是epoch*example_nums/batch
(轮数 * 样本数/每次的批量数目)

注意点:
对数据集进行乱序,避免对相同数据集进行重复训练

三、代码实现与解析

一、导包

#后续用到random.shuffle方法实现对列表内元素乱序
import random
#主要是用于张量的数据结构和数学操作(算张量蹭蹭快那种)
import torch

什么是张量(tensor)
移步百度:张量,向量,矩阵,标量…的概念

二、相应的函数实现

1 生成样本(数据集)

#传入的w是true_w,b是true_b,num_example是样本数量
#y=xw+b
#生成数据集
def create_data(w, b, nums_example):
    #生成 均值0 标准差1 形状1000*2 的张量X
    X = torch.normal(0, 1, (nums_example, len(w)))
    #生成y,y的表达式是y=xw+b
    y = torch.matmul(X, w) + b
    #增加噪声,否则太理想化
    y += torch.normal(0, 0.01, y.shape)
	#此时y的形状是行向量,需要将其转化为列向量方便后续计算
    #返回值是特征值X和预测值y,X和y都是张量
    #在本代码中,返回值的X具体形状是1000*2 y形状是1000*1
    return X, y.reshape(-1, 1) 

根据给定的w和b生成数据集

true_w = torch.tensor([2, -3.4])
true_b = 4.2
#生成数据集
#features特征值 labels标签(预测值)
#这里的特征值不是线性代数里的特征值
features, labels = create_data(true_w, true_b, 1000)

补充:torch.matmul()的用法
torch.matmul是张量的乘法
1 如果输入都是一维时,则是向量点积

2 如果输入都是二维时,则是普通的矩阵乘法,和tensor.mm函数用法相同(大一线性代数学的矩阵乘法)。

3 如果第一个tensor是二维的,第二个tensor是一维的,则将第二个tensor扩充到二维(维数后面插入长度为1的新维度),然后执行矩阵乘法,得到结果后再将此维度去掉,得到的结果与第二个tensor的维度相同。(本文代码属于这种情况)

4 如果第一个tensor是一维的,第二个tensor是二维的,则将第一个tensor扩充到二维(维数后面插入长度为1的新维度),然后执行矩阵乘法,得到结果后再将此维度去掉,得到的结果与第一个tensor的维度相同。

小技巧:
如果tensor1是二维,形状是1000 * 2。tensor2是一维,形状是2。
然后执行matmul(tensor1,tensor2),只需要把一维的tensor2的形状看成是2 * 0就好了。
得到1000 * 0的张量也就是一维的向量。

5 其他情况,暂时用不到

2 按批量读取数据集

# 读数据集
# 参数:批量数目 特征值 预测值 
# 本代码指定批量数目是10
def read_data(batch_size, features, lables):
    nums_example = len(features)#计算样本的数量(行数)
    indices = list(range(nums_example))#生成0-999的range元组,将其转为列表
    random.shuffle(indices)  # 将列表中的所有元素乱序
    #为什么要乱序?因为使得每一轮epoch中,不出现相同的批次

    #对数据集进行扫描(遍历)
    for i in range(0, nums_example, batch_size):  # range(start, stop, step(步长))
        index_tensor = torch.tensor(indices[i: min(i + batch_size, nums_example)])#取min:防止溢出(数组越界)
        #将列表切片转成tensor
        
        yield features[index_tensor], lables[index_tensor]  
        # 张量也可以切片
        # 取出index_tensor对应的位置的元素组成新的张量
		#tensor[tensor]->new tensor

设置批量数目

#设置批量数目是10
batch_size = 10

补充:yield关键字的用法
点击进入同站链接 【python】【关键字】yield的用法和相关知识

3 定义模型 损失函数 算法

1 定义模型

输入特征值 返回计算结果值

# 定义返回值模型
def net(X, w, b):
    return torch.matmul(X, w) + b

2 定义损失函数(均方误差)

输入预测值y_hat和实际值y ,返回损失值(损失值是形状与y_hat同的张量)
:张量 y_hat和y整形后的形状是1000**1 ,根据函数,
相同位置的元素作差再平方再除以2,
得到新张量的对应位置的元素,
这个张量的形状也是是1000*1。

# 定义损失函数
def loss(y_hat, y):
    return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2  
# 为什么要加 reshape:?两个张量形状不一样
# y_hat_shape:torch.Size([10, 1])  
# y_shape: torch.Size([10])

3 定义优化算法(梯度下降法)

梯度下降法:s=s-l*▽
l(learningrate):学习率(步长) , ▽:梯度
l过小:收敛缓慢
l过大:锯齿效应

# 定义优化算法
def sgd(params, batch_size, lr):
    with torch.no_grad():  
	# with torch.no_grad() 主要用于停止autograd(自动求导)模块的工作
	# 可见https://zh-v2.d2l.ai/chapter_preliminaries/autograd.html
        for param in params:
            param -= lr * param.grad / batch_size  
            ##  如果用param = param - lr * param.grad / batch_size会导致导数丢失, zero_()函数报错,可能和自动求导的底层原理有关
            # 更正:y=y+?  这个新的y的地址不是原先y的地址了
            # 但是y+=?  y的地址不变 应该是由于地址变化的原因导致错误
            param.grad.zero_()
            # 每次都把梯度清零 不然会累加  
            ## 导数如果丢失了,会报错‘NoneType’ object has no attribute ‘zero_’

三、开始训练

##初始化参数
# 给w和b赋值,requires_grad参数设置为True,反向传播时,该tensor就会自动求导
w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)

# 训练模型
lr = 0.03#设置学习率为0.03
num_epochs = 3#设置训练轮数为3

for epoch in range(0, num_epochs):
    for X, y in read_data(batch_size, features, labels):
        f = loss(net(X, w, b), y)
        # `f`形状是(`batch_size`, 1),是张量。
        # `f`中的所有元素加到一起,并计算关于[`w`, `b`]的梯度
        f.sum().backward()#获取反向传播参数
        sgd([w, b], batch_size, lr)  # 使用参数的梯度更新参数
    with torch.no_grad():
    #with torch.no_grad的作用:
    #在该模块下,所有计算得出的tensor的requires_grad都自动设置为False。
        train_l = loss(net(features, w, b), labels)
        print("w {0} \nb {1} \nloss {2:f}".format(w, b, float(train_l.mean())))

print("w误差 ", true_w - w, "\nb误差 ", true_b - b)
# 由于loss的返回值是一个张量,每个元素都是差值的平方。
# train.mean求得是均方误差。

四、完整代码如下

import random
import torch
#构造数据集
def create_data(w, b, nums_example):
    X = torch.normal(0, 1, (nums_example, len(w)))
    y = torch.matmul(X, w) + b
    y += torch.normal(0, 0.01, y.shape) 
    return X, y.reshape(-1, 1)  

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


# 读数据集
def read_data(batch_size, features, lables):
    nums_example = len(features)
    indices = list(range(nums_example)) 
    random.shuffle(indices) 

    for i in range(0, nums_example, batch_size):  # range(start, stop, step)
        index_tensor = torch.tensor(indices[i: min(i + batch_size, nums_example)])
        yield features[index_tensor], lables[index_tensor]  

batch_size = 10

    
# 定义模型
def net(X, w, b):
    return torch.matmul(X, w) + b
# 定义损失函数
def loss(y_hat, y):
    return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2  
# 定义优化算法
def sgd(params, batch_size, lr):
    with torch.no_grad():
        for param in params:
            param -= lr * param.grad / batch_size  
            param.grad.zero_()


##初始化参数
w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)


# 训练模型
lr = 0.03
num_epochs = 3

for epoch in range(0, num_epochs):
    for X, y in read_data(batch_size, features, labels):
        f = loss(net(X, w, b), y)
        f.sum().backward()
        sgd([w, b], batch_size, lr)  # 使用参数的梯度更新参数
    with torch.no_grad():
        train_l = loss(net(features, w, b), labels)
        print("\nloss{0:d} {1:f}".format(epoch,float(train_l.mean())))

print("w误差 ", true_w - w, "\nb误差 ", true_b - b)

你可能感兴趣的:(人工智能,线性回归,机器学习)