线性神经网络(线性回归)

news2024/11/15 17:56:21

线性回归

目录

  • 线性回归
    • 导包
    • 生成数据集
      • 观察散点图
    • 读取数据集
    • 初始化模型参数
    • 定义模型
    • 定义损失函数
    • 定义优化算法
    • 训练
  • 简易实现(pytorch)
    • 生成数据集
    • 读取数据集
    • 定义模型
    • 初始化模型参数
    • 定义损失函数
    • 定义优化算法
    • 训练

导包

import random
import torch
from d2l import torch as d2l

生成数据集

def synthetic_data(w,b,num):
    #x通过正态分布生成
    x=torch.normal(0,1,(num,len(w)))
    y=torch.matmul(x,w)+b
    #数据集中加入噪声
    y+=torch.normal(0,0.01,y.shape)
    return x,y.reshape(-1,1)
w = torch.tensor([2,-3,4],dtype=torch.float32)
b=4.2
#生成1000个数据
features,labels=synthetic_data(w,b,1000) 

观察散点图

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

结果:
在这里插入图片描述

读取数据集

#读取数据集
def data_iter(batch_size,features,labels):
    num=len(features)
    indices=list(range(num))

    random.shuffle(indices)
    for i in range(0,num,batch_size):
        batch_indices=torch.tensor(indices[i:min(i+batch_size,num)])
        yield features[batch_indices],labels[batch_indices]

这段代码实现了一个数据集的迭代器,用于分批次读取数据,每次输出10个随机数据。

参数说明:

batch_size:每个批次的数据量大小。
features:所有数据的特征,例如所有图像的像素点。
labels:所有数据对应的标签,例如所有图像的真实类别。
具体实现步骤:

计算数据集总共的样本数。
创建一个包含所有数据集索引的列表。
随机打乱索引列表。
每次取出 batch_size 个索引,根据这些索引从特征和标签中取出对应的数据,返回这个 batch 的数据作为迭代器的一个元素。
具体来说,代码中用了 Python 的生成器函数 yield,每次迭代时返回一个批次的数据。在循环过程中,用 range 函数以步长为 batch_size 遍历整个数据集。对于每个 i,取出 indices 中从 i 开始的 batch_size 个索引,将它们作为一个 torch.tensor 对象 batch_indices。最后,通过 features[batch_indices] 和 labels[batch_indices] 从数据集中取出相应的数据,返回一个批次的数据作为迭代器的一个元素。

初始化模型参数

w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)

这段代码使用 PyTorch 创建了两个张量 w 和 b,用于实现一个简单的神经网络中的参数。

具体来说,w 是一个形状为 (2,1) 的张量,其中每个元素都是从均值为 0、标准差为 0.01 的正态分布中随机抽样得到的。这些值将作为神经网络的权重。requires_grad=True 意味着我们希望在反向传播时计算该张量的梯度,以便对其进行优化。

b 是一个形状为 (1,) 的张量,其中所有元素都是 0。这些值将作为神经网络的偏置。同样地,requires_grad=True 意味着我们希望在反向传播时计算该张量的梯度。

这两个张量是神经网络中的参数,可以通过反向传播算法来调整它们以最小化损失函数。

定义模型

def linereg(X,w,b):
    return torch.matmul(X,w)+b

定义损失函数

def squared_loss(y_hat, y):  #@save
    """均方损失"""
    return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2

定义优化算法

def sgd(params, lr, batch_size):  #@save
    """小批量随机梯度下降"""
    with torch.no_grad():
        for param in params:
            param -= lr * param.grad / batch_size
            param.grad.zero_()

这段代码实现了小批量随机梯度下降(SGD)优化算法的函数。它接受三个参数:

params:一个包含需要更新的张量(即模型参数)的列表。
lr:学习率,控制每次迭代参数更新的步长。
batch_size:每个小批量样本的大小,用于归一化梯度。
该函数使用了 PyTorch 的上下文管理器 torch.no_grad(),用于确保在更新参数时不会计算梯度。在函数中,对于每个张量 param,根据梯度下降的公式进行更新:
param -= lr * param.grad / batch_size
其中,param.grad 是张量 param 的梯度,lr 是学习率,batch_size 是小批量样本的大小。这个更新式子是梯度下降的一般形式,用于在每个迭代步骤中更新参数的值。
更新完参数后,param.grad 需要清零,以便进行下一次迭代:、


这个地方可能会有人疑惑:sgd函数没有返回值,里面的变量都是临时变量,上面这行代码只是在函数里做更新,更新的也只是临时变量,而且sgd函数也没有返回值,是怎么更新到w和b的呢?
造成你困惑的最主要原因的核心是“可变对象与不可变对象”。
int类型为一个不可变对象,torch类型为一个可变对象
一般对于可变对象来说“-=”操作符会直接改变self自身,地址不变。不可变对象则会构造一个新地址
举个例子:

import torch

x1 = 1
x2 = 2
params = [x1, x2]
for p in params:
    print(id(p), id(x1), id(x2))
    p -= 4
    print(id(p), id(x1), id(x2))
print(params)

x1 = torch.Tensor([1])
x2 = torch.Tensor([2])
params = [x1, x2]
for p in params:
    print(id(p), id(x1), id(x2))
    p -= 4
    print(id(p), id(x1), id(x2))
print(params)

结果为:

9784896 9784896 9784928
9784768 9784896 9784928
9784928 9784896 9784928
9784800 9784896 9784928
[1, 2]
139752445458112 139752445458112 139752445458176
139752445458112 139752445458112 139752445458176
139752445458176 139752445458112 139752445458176
139752445458176 139752445458112 139752445458176
[tensor([-3.]), tensor([-2.])]

可以看到对于int类型,地址变换了,而torch类型,地址却没有变化。
而若写成p = p - 4则会调用构造函数,并返回一个新的变量,也就不可能作用到原先的“可变对象”。
int类没有发生就地变化是因为它是一个不可变对象。


param.grad.zero_()
这个操作是必要的,因为 PyTorch 会累积梯度,即每次反向传播得到的梯度会被加到之前的梯度上。在每次迭代后清零梯度可以避免这种累积。

训练

超参数的定义

lr = 0.03  #学习率
num_epochs = 3  #训练轮次
net = linreg #网络结构
loss = squared_loss #损失函数
batch_size=10

训练过程

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),而不是一个标量。l中的所有元素被加到一起,
        # 并以此计算关于[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}')

这段代码实现了一个简单的线性回归模型的训练过程,使用小批量随机梯度下降(SGD)优化模型参数。具体来说,代码的功能如下:

定义了超参数 lr(学习率)、num_epochs(迭代周期数)、batch_size(批量大小);
定义了模型 net(线性回归模型)和损失函数 loss(均方误差损失函数);
循环 num_epochs 次,每次循环遍历数据集,使用小批量随机梯度下降更新模型参数;
在每次迭代结束后,计算当前模型在整个数据集上的损失,输出训练结果。
具体来说,代码中的循环遍历数据集的部分使用了一个函数 data_iter(batch_size, features, labels),用于按批次遍历数据集。这个函数的实现可以根据具体的数据集结构进行定制。

每次遍历数据集时,对于每个小批量数据,模型首先根据当前的模型参数(权重w和偏差b)计算模型输出,并根据模型输出和标签计算小批量数据的损失。然后,使用 PyTorch 自动求导功能,计算模型参数的梯度,并使用小批量随机梯度下降更新模型参数。

最后,在每个迭代周期结束后,使用 torch.no_grad() 上下文管理器,关闭自动求导功能,计算当前模型在整个数据集上的损失,并输出结果。

关注l.sum().backward():
当我们使用 PyTorch 自动求导功能进行反向传播时,每次计算完一次损失函数之后,我们需要调用 backward() 方法计算相应的梯度。在这段代码中,l 是一个形状为 (batch_size,1) 的 Tensor,表示当前小批量数据的损失值,它并不是一个标量。

在对一个非标量的 Tensor 求导时,需要指定求导方向,即梯度在哪些维度上进行计算。在这里,我们对损失函数关于 l 所有元素的梯度进行求导,并将其累加到 w 和 b 的梯度上。

为了将所有元素的梯度累加到 w 和 b 的梯度上,我们需要先将 l 中所有元素加起来,得到一个标量,然后再调用 backward() 方法。这就是代码中 l.sum().backward() 的作用。它将 l 中所有元素加起来,得到一个标量,然后对该标量进行求导,将求得的梯度累加到 w 和 b 的梯度上。

需要注意的是,在每次迭代周期开始时,我们需要将 w 和 b 的梯度清零,即调用 param.grad.zero_() 方法。这是因为 PyTorch 默认会将每次计算得到的梯度累加到原有的梯度上,而我们希望每次计算得到的梯度都是新的,所以需要在每次迭代周期开始时将梯度清零。

backward的使用:
在 PyTorch 中,要计算某个节点的梯度,我们需要调用该节点上的 backward() 方法。一般来说,我们需要在计算损失函数后,对损失函数调用 backward() 方法以计算模型参数的梯度。以下是使用 backward() 方法的一个示例:

import torch

x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
y = x**2
z = 2*y + 3

# 计算 z 对 x 的梯度
z.backward(torch.ones_like(x))

print(x.grad)  # 输出 [4.0, 8.0, 12.0]

在上面的代码中,我们首先创建了一个张量 x,并将 requires_grad 参数设置为 True,以便对 x 求导。接下来,我们定义了两个计算图节点 y 和 z,其中 y 是 x 的平方,而 z 是 2*y + 3。最后,我们对 z 调用 backward() 方法,以计算 z 对 x 的梯度。由于 z 是标量,我们需要在 backward() 方法中传入一个和 x 形状相同的张量作为梯度权重。在这个例子中,我们使用 torch.ones_like(x) 表示所有梯度权重都为1。最后,我们可以通过 x.grad 属性获取 x 的梯度,输出结果为 [4.0, 8.0, 12.0]。

简易实现(pytorch)

生成数据集

import numpy as np
import torch
from torch.utils import data
from d2l import torch as d2l

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

读取数据集

def load_array(data_arrays, batch_size, is_train=True):  #@save
    """构造一个PyTorch数据迭代器"""
    dataset = data.TensorDataset(*data_arrays)
    return data.DataLoader(dataset, batch_size, shuffle=is_train)

batch_size = 10
data_iter = load_array((features, labels), batch_size)

这段代码定义了一个数据迭代器 data_iter,用于分批次加载数据集,每个批次的大小为 batch_size。数据迭代器是一个重要的组件,用于在训练过程中批量读取数据,以提高数据加载效率。

在代码中,我们调用了 load_array 函数,该函数将数据集 features 和 labels 打包成一个 TensorDataset 对象,并通过 DataLoader 函数将其转换成数据迭代器。其中,is_train 参数指定是否需要对数据集进行随机洗牌,以增加数据的随机性和多样性。

这里使用 TensorDataset 和 DataLoader 这两个类,可以使数据集按批次读入,方便后续使用 PyTorch 中的各种优化算法进行训练。

定义模型

# nn是神经网络的缩写
from torch import nn

net = nn.Sequential(nn.Linear(2, 1))

我们将两个参数传递到nn.Linear中。 第一个指定输入特征形状,即2,第二个指定输出特征形状,输出特征形状为单个标量,因此为1。

初始化模型参数

net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)

定义损失函数

loss = nn.MSELoss()

定义优化算法

trainer = torch.optim.SGD(net.parameters(), lr=0.03)

训练

num_epochs = 3
for epoch in range(num_epochs):
    for X, y in data_iter:
        l = loss(net(X) ,y)
        trainer.zero_grad()
        l.backward()
        trainer.step()
    l = loss(net(features), labels)
    print(f'epoch {epoch + 1}, loss {l:f}')

首先,num_epochs 变量定义了训练的总轮数。每一轮训练,数据集 data_iter 将会分成若干个大小为 batch_size 的批次,每个批次将会输入到模型中进行前向计算,然后计算损失函数 l。损失函数的值将用于后续的反向传播和优化器的更新。

在每个批次的前向计算结束后,我们调用 trainer.zero_grad() 方法清空梯度信息,然后调用 l.backward() 方法进行反向传播,计算出梯度信息并更新到对应的参数上。最后,我们通过调用 trainer.step() 方法对参数进行更新,完成了一次迭代训练。

在每轮训练结束后,我们对整个数据集进行一次前向计算,计算出该轮训练的平均损失,并打印输出。这样可以帮助我们监控模型的训练情况,以便进行后续调整和优化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/351826.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微服务相关概念

一、谈谈你对微服务的理解,微服务有哪些优缺点?微服务是由Martin Fowler大师提出的。微服务是一种架构风格,通过将大型的单体应用划分为比较小的服务单元,从而降低整个系统的复杂度。优点:1、服务部署更灵活&#xff1…

Python pickle模块:实现Python对象的持久化存储

Python 中有个序列化过程叫作 pickle,它能够实现任意对象与文本之间的相互转化,也可以实现任意对象与二进制之间的相互转化。也就是说,pickle 可以实现 Python 对象的存储及恢复。值得一提的是,pickle 是 python 语言的一个标准模…

2023美赛A题:受旱灾影响的植物群落

文章目录背景要求服务词汇表背景 不同的植物对压力有不同的反应。例如,草原对干旱非常敏感。干旱以不同的频率和严重程度发生。大量的观察表明,不同物种的数量对植物群落如何在连续多代干旱周期中适应起到了重要作用。在一些仅有一种植物的群落中&#…

Vue基础13之浏览器本地存储、TodoList本地存储、组件自定义事件

Vue基础13浏览器本地存储localStorage 本地存储sessionStorage 会话存储总结TodoList本地存储App.vue组件自定义事件子组件给父组件传值使用props方法App.vueSchool.vue子组件给父组件传值使用组件自定义事件——绑定第一种写法:使用v-on或App.vueStudent.vue第二种…

idea快捷编码:生成for循环、主函数、判空非空、生成单例方法、输出;自定义快捷表达式

前言 idea可根据输入的简单表达式进行识别,快速生成语句 常用的快捷编码:生成for循环、主函数、判空非空、生成单例方法、输出 自定义快捷表达式 博客地址:芒果橙的个人博客 【http://mangocheng.com】 一、idea默认的快捷表达式查看 Editor…

String对象的创建和比较

String类的概述 String类:代表字符串。 Java 程序中的所有字符串字面值(如 “abc” )都作 为此类的实例实现。 String是JDK中内置的一个类:java.lang.string 。 String表示字符串类型,属于引用数据类型,不…

饲养员喂养动物-课后程序(JAVA基础案例教程-黑马程序员编著-第四章-课后作业)

【案例4-2】饲养员喂养动物 记得 关注,收藏,评论哦,作者将持续更新。。。。 【案例目标】 案例描述 饲养员在给动物喂食时,给不同的动物喂不同的食物,而且在每次喂食时,动物都会发出欢快的叫声。例如&…

Native扩展开发的一般流程(类似开发一个插件)

文章目录大致开发流程1、编写对应的java类服务2、将jar包放到对应位置3、配置文件中进行服务配置4、在代码中调用5、如何查看服务调用成功大致开发流程 1、编写服务,打包为jar包2、将jar包放到指定的位置3、在配置文件中进行配置,调用对应的服务 1、编…

linux 之 ps命令介绍

哈喽,大家好,我是有勇气的牛排(全网同名)🐮 有问题的小伙伴欢迎在文末评论,点赞、收藏是对我最大的支持!!!。 前言 如过想实现对进程监控,就需要使用到ps命…

macOS 13.3 Beta (22E5219e)发布

系统介绍2 月 17 日消息,苹果今日向 Mac 电脑用户推送了 macOS 13.3 开发者预览版 Beta 更新(内部版本号:22E5219e),本次更新距离上次发布隔了 37 天。macOS Ventura 带来了台前调度、连续互通相机、FaceTime 通话接力…

Vue3+Lodash:2023年了,我依然在使用Lodash

目录 前言: 为什么选择lodash? 看看lodash的地位 如何使用lodash? 1.安装lodash 2.引入lodash 我们到底还需不需要lodash? 再举一些例子 前言: 前两天,在水群的时候,发现居然有人不知lodash&#…

OpenCV-PyQT项目实战(6)项目案例02:滚动条应用

欢迎关注『OpenCV-PyQT项目实战 Youcans』系列,持续更新中 OpenCV-PyQT项目实战(1)安装与环境配置 OpenCV-PyQT项目实战(2)QtDesigner 和 PyUIC 快速入门 OpenCV-PyQT项目实战(3)信号与槽机制 …

基于感知动作循环的层次推理用于视觉问答

title:Hierarchical Reasoning Based on Perception Action Cycle for Visual Question Answering 基于感知动作循环的层次推理用于视觉问答 文章目录title:[Hierarchical Reasoning Based on Perception Action Cycle for Visual Question Answering](…

机器学习技术:多任务学习综述!

Datawhale干货 作者:陈敬,中国移动云能力中心前言本文对多任务学习(multi-task learning, MTL)领域近期的综述文章进行整理,从模型结构和训练过程两个层面回顾了其发展变化,旨在提供一份 MTL 入门指南,帮助大家快速了解…

MySQL的锁机制详解

目录 一.概述 分类 锁的特性 二.MyISAM表锁 如何加表锁 读锁演示 写锁演示 三.InnoDB行锁 行锁特点 行锁模式 一.概述 锁是计算机协调多个进程或线程并发访问某一资源的机制(避免争抢)。 在数据库中,除传统的计算资源(如…

常见的并发模型

介绍 常见解决并发的策略一般有两种:共享数据和消息传递 基于消息传递的实现有 CSP模型,典型的有Go语言中基于Channel的通讯Actor模型,典型的有Akka中的Actor模型 CSP模型和Actor模型的简单理解: Dont communicate by sharing…

如何使用MQTTX可视化工具

@ [TOC] 使用背景 最近在调试MQTT相关的程序,调试程序时使用的是MQTTX这个工具,它可以创建服务器连接,并且在连接后发布和订阅主题。在调试过程中总结了一些使用方法和技巧,在这里分享给大家。 MQTTX是EMQX公司推出的可视化MQTT调试工具,大家可以到官网下载使用,同时参…

技术实践干货:从零开始创建Node.js应用

作为一个程序员,我们常常会有很多想法和创意,然后用技术实现出来,这是一个很有成就感的事情。 在实践过程中,会发现很多想法都不能很好地落地,可能是技术、可能是团队氛围等等,于是就开始想着能够不能有一个…

类内默认函数

目录 前言: 1. 构造函数 1.1 概念 1.2 特性 1) 2) 2. 析构函数 2.1 概念 2.2 特性 3. 拷贝构造 3.1 概念 3.2 特征 4. 赋值运算符重载 4.1 运算符重载 4.2 赋值运算符重载 5.3 前置和后置的重载 前言: 问&#xf…

AI智能机器人,在这里也可以体验~

“大家好,我是雄雄,欢迎关注微信公众号:雄雄的小课堂。”现在是:2023年2月17日00:14:42前言最近AI智能chatgpt特别的火,相信好多人都已经体验过了,之前我的群里也接入过,奈何总是收到警告和限制…