机器学习与深度学习-1-线性回归从零开始实现

news/2024/11/8 18:26:18 标签: 机器学习, 深度学习, 线性回归

机器学习深度学习-1-线性回归从零开始实现

1 前言

​ 内容来源于沐神的《动手学习深度学习》课程,本篇博客对线性回归从零开始实现(即不调用封装好的库,如SGD优化器、MSE损失函数等)进行重述,并且修改了沐神的课堂示例代码以符合PEP8代码编写规范(如内参、外参等)。我先发布代码实现的文章,过后会把线性回归的数学推导发布。

2 问题背景–以房价预测为例

​ 通常,我们希望通过过去的房价历史数据去预测未来的房价走向。但实际上,房价与很多因素有关。因此为了以房价预测为例子引出线性回归问题,我们做以下假设:

  • 房价只与房屋的面积与房屋年限有关;
  • 房价与两个因素是线性关系

​ 基于以上假设,有如下式子:

p r i c e = ω a r e a ⋅ a r e a   +   ω a g e ⋅ a g e   +   b price = \omega_{area}\cdot area ~+~\omega_{age} \cdot age~+~b price=ωareaarea + ωageage + b
其中, p r i c e price price为回归模型预测的房价; ω a r e a \omega_{area} ωarea ω a g e \omega_{age} ωage为房屋面积与房屋年限的权重,也为模型训练的目标; b b b为偏置项。

3 编程实现

​ 首先,我们先调用一些基本的库:

import random  # 用于生成随机数
import torch  # 导入Pytorch库
from d2l import torch as d2l  # 可视化数据结果

其中,前两个库是基本库。random用于生成随机数,而torch深度学习的基本框架–Pytorch的库。d2l这个库需要用pip或者conda自行下载。笔者的环境:Python版本:3.10;CUDA版本:11.8;torch版本:2.5.0;GPU:RTX 4060;CPU:i9-14900HX

​ 接着,我们生成数据集:

def create_dataset(input_W, input_b, num_examples):
    """
    生成 y = xw + b + 噪声
    
    :param input_w:权重参数
    :param input_b:偏差参数
    :param num_examples:样本数
    """
    input_x = torch.normal(0, 1, (num_examples, len(input_w)))  # 随机生成输入数据,服从正态分布
    output_y = torch,matmul(input_x, input_w) + input_b  # 计算输出数据
    output_y += torch.normal(0, 0.01, oytput_y.shape)  # 加入噪声
    
    """
    返回数据集,其中通过reshape这个函数去重塑output_y的形状。
    :param -1:自动推断该向量的维度
    :param  1:将output_y变成一列
    
    所以input_x与output_y均为列向量
    """
    return input_x, output_y.reshape((-1, 1))

沐神的代码中,内参与外参用的是同一个变量符号。为了防止因为内参与外参的优先级而导致的变量覆盖,本文将内参的变量符号修改以区别于外参。

​ 定义实际的权重向量与偏差量,并调用刚刚定义好的函数create_dataset生成一个示例数据集:

true_w = torch.tensor([2, -3.4])  # 定义真实的权重
true_b = 4.2  # 定义真实的偏差
features, labels = create_dataset(true_w, true_b, 1000)  # 生成数据集

​ 在控制台打印数据集并输出数据的散点图:

print("features", features[0], "\n label", label[0])  # 在控制台展示生成的数据序列

d2l.set_figsize()  # 设置图窗的尺寸,自动调整一个舒适的尺寸
d2l.plt.scatter(features[:, 1].detach().numpy(), labels.detach().numpy(), 1)  # 
d2l.plt.show()

有几个函数用法要说明一下:

  • .detach():用于返回一个新的张量,与原张量共享相同的数据,但与计算题目无关联(计算梯度是一个很”贵“的事情);
  • .numpy():将张量格式转换为NumPy数组格式;
  • 1为散点的大小。

​ 接着我们需要根据批量的大小生成批量数据:

def data_iter(input_batch_size, input_features, input_labels):
    """
    生成批量数据
    :param input_batch_size:批量大小
    :param input_features:输入数据
    :param input_labels:输出数据
    :return:返回批量数据
    """
    num_examples = len(input_features)  # 样本数
    indices = list(range(num_examples))  # 样本的索引列表
    random.shuffle(indices)  # 样本的读取顺序随机
    
    for i in range(0, num_examples, input_batch_size):  # 按批量分割样本
        batch_indices = torch.tensor(
            indices[i + min(input_batch_size, num_examples)])  # 生成批量的索引,后面的min()保证索引值不会超出样本
        yield input_features[batch_indices], input_labels[batch_indices]  # 批量的输入输出数据,以迭代器的形式输出(可以节省内存)
    

​ 定义批量大小,并读取批量数据:

batch_size = 32  # 批量大小


for x, y in data_iter(batch_size, features, labels):  # 读取批量大小
    print(x, "\n", y)  # 打印结果
    break
    

​ 实际上这个批量大小的设置是有讲究的,沐神在他的教学案例设置的是10,往后我会结合我的课题出一个深度学习模型设计的一般步骤,在那里会提到。

​ 接着我们要探索出回归效果最好的权重向量,首先进行一个初始化:

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

requires_grad这一项的意思是计算其梯度,这一步是优化过程的重要一步。

​ 定义线性回归模型:

def linreg(input_x, input_w, input_b):
    return torch.matmul(input_x, input_w) + input_b

​ 定义损失函数:

def squared_loss(y_hat, output_y):
    return (y_hat - output_y.reshape(y_hat.reshape)) ** 2 / 2

​ 定义小批量随机梯度下降优化算法:

def sgd(params, learning_rate, input_batch_size):
    with torch.no_grad():  # 不跟踪梯度
        for param in params:  # 遍历参数
            param -= learning_rate * param.grad / input_batch_size  # 更新参数
            param.grad.zeros_()  # 清空梯度,减少计算负担

​ 接下来是最重要的训练过程:

lr = 0.03  # 学习率
num_epochs = 50  # 迭代次数
net = linreg  # 线性模型
loss = squared_loss  # MSE损失

for epoch in range(num_epochs):  # 训练模型
    for x, y in data_iter(batch_size, features, labels):  # 读取批量数据
        batch_loss = loss(net(x, w, b), y)  # 计算损失
        batch_loss.sum().backward()  # 反向传播
        sgd([w, b], lr, batch_size)  # 使用小批量随机梯度下降算法更新参数
    with torch.no_grad():  # 不跟踪梯度
        train_l = loss(new(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}")
        

4 结果分析

​ 代码结果:

epoch 1, loss 2.518388
epoch 2, loss 0.396580
epoch 3, loss 0.062917
epoch 4, loss 0.010110
epoch 5, loss 0.001669
epoch 6, loss 0.000314
epoch 7, loss 0.000095
epoch 8, loss 0.000059
epoch 9, loss 0.000054
epoch 10, loss 0.000053
epoch 11, loss 0.000052
epoch 12, loss 0.000052
epoch 13, loss 0.000052
epoch 14, loss 0.000052
epoch 15, loss 0.000052
epoch 16, loss 0.000052
epoch 17, loss 0.000052
epoch 18, loss 0.000052
epoch 19, loss 0.000052
epoch 20, loss 0.000052
epoch 21, loss 0.000052
epoch 22, loss 0.000052
epoch 23, loss 0.000052
epoch 24, loss 0.000052
epoch 25, loss 0.000052
epoch 26, loss 0.000052
epoch 27, loss 0.000052
epoch 28, loss 0.000052
epoch 29, loss 0.000052
epoch 30, loss 0.000052
epoch 31, loss 0.000052
epoch 32, loss 0.000052
epoch 33, loss 0.000052
epoch 34, loss 0.000052
epoch 35, loss 0.000052
epoch 36, loss 0.000052
epoch 37, loss 0.000052
epoch 38, loss 0.000052
epoch 39, loss 0.000052
epoch 40, loss 0.000052
epoch 41, loss 0.000052
epoch 42, loss 0.000052
epoch 43, loss 0.000052
epoch 44, loss 0.000052
epoch 45, loss 0.000052
epoch 46, loss 0.000052
epoch 47, loss 0.000052
epoch 48, loss 0.000052
epoch 49, loss 0.000052
epoch 50, loss 0.000052
w的估计误差为:tensor([0.0001, 0.0002], grad_fn=<SubBackward0>)
b的估计误差为:tensor([0.0004], grad_fn=<RsubBackward1>)

从结果可以看出,模型训练到第十轮左右就收敛了,且训练误差很小,证明训练的效果很好。


http://www.niftyadmin.cn/n/5744283.html

相关文章

【bug日志-水】解决本地开发下代理和url同名导致刷新404的问题

bug描述 在本地开发&#xff0c;并且路由是history的模式下&#xff0c;代理和url同名的情况下&#xff0c;刷新会404。 {path: /googleAds,//如果有个代理也叫googleAds&#xff0c;刷新时就会404name: googleAds,icon: sound,routes: [{path: /googleAds/GoogleAdsSettingPag…

MybatisPlus入门(九)MybatisPlus-DML编程控制

增删改 Insert Delete Update 操作中的一些问题。 一、主键生成策略 增加的时候主键生成的问题&#xff0c;不同的环境、不同的场景对应的主键生成策略可能是不一样的&#xff0c;比如日志表、购物订单表、外卖单。 主键生成策略设置方法&#xff1a; 示例代码&#xff1a; p…

qt QPixmapCache详解

1、概述 QPixmapCache是Qt框架中提供的一个功能强大的图像缓存管理工具类。它允许开发者在全局范围内缓存QPixmap对象&#xff0c;从而有效减少图像的重复加载&#xff0c;提高图像加载和显示的效率。这对于需要频繁加载和显示图像的用户界面应用来说尤为重要&#xff0c;能够…

vue种ref跟reactive的区别?

‌Vue中的ref和reactive的主要区别在于它们处理的数据类型、实现原理以及使用方式。‌ 处理的数据类型 ‌ref‌&#xff1a;可以处理基本数据类型&#xff08;如数字、字符串、布尔值&#xff09;和对象。ref通过Object.defineProperty()的get和set方法来实现响应式&#xff…

11.07学习

一、三中代码解决鸡兔同笼问题 1.直接解方程 #include <stdio.h> int main() { int heads, feet, chickens, rabbits; printf("请输入总头数&#xff1a;"); scanf("%d", &heads); printf("请输入总脚数&#xff1a;"); scanf(…

MySQL分组查询

问题&#xff1a;查询员工表中,每个不同部门分别的平均工资 分组查询&#xff1a;group by关键字实现分组,group by放在where条件语句之后,order by放置中group by的后面,一会儿还会学到的having关键字,总体的循序先后为&#xff1a; where条件 , group by 分组语句 , having…

关于Redis

Redis 基础 什么是 Redis&#xff1f; Redis &#xff08;REmote DIctionary Server&#xff09;是一个基于 C 语言开发的开源 NoSQL 数据库&#xff08;BSD 许可&#xff09;。与传统数据库不同的是&#xff0c;Redis 的数据是保存在内存中的&#xff08;内存数据库&#xf…

004-Kotlin界面开发快速入水之TicTacToe

程序界面和效果 快速入水 要学习一样跟程序设计有关的东西&#xff0c;最好的办法始终是把手打湿&#xff0c;整一个能够运行&#xff0c;可以实验的东西出来。 也只有在程序开发中&#xff0c;我们才能想一个魔法师而不是魔术师&#xff0c;我们真的能够创造一个东西。而且编…