神经网络代码入门解析

神经网络代码入门解析

python 复制代码
import torch
import matplotlib.pyplot as plt

import random


def create_data(w, b, data_num):  # 数据生成
    x = torch.normal(0, 1, (data_num, len(w)))
    y = torch.matmul(x, w) + b  # 矩阵相乘再加b

    noise = torch.normal(0, 0.01, y.shape)  # 为y添加噪声
    y += noise

    return x, y

num = 500

true_w = torch.tensor([8.1, 2, 2, 4])
true_b = 1.1


X, Y = create_data(true_w, true_b, num)

# plt.scatter(X[:, 3], Y, 1)  # 画散点图 对X取全部的行的第三列,标签Y,点大小
# plt.show()

def data_provider(data, label, batchsize):  # 每次取batchsize个数据
    length = len(label)
    indices = list(range(length))
    # 这里需要把数据打乱
    random.shuffle(indices)

    for each in range(0, length, batchsize):
        get_indices = indices[each: each+batchsize]
        get_data = data[get_indices]
        get_label = label[get_indices]

        yield get_data, get_label  # 有存档点的return

batchsize = 16
# for batch_x, batch_y in data_provider(X, Y, batchsize):
#     print(batch_x, batch_y)
#     break

# 定义模型
def fun(x, w, b):
    pred_y = torch.matmul(x, w) + b
    return pred_y

# 定义loss
def maeLoss(pre_y, y):
    return torch.sum(abs(pre_y-y))/len(y)

# sgd(梯度下降)
def sgd(paras, lr):
    with torch.no_grad():  # 这部分代码不计算梯度
        for para in paras:
            para -= para.grad * lr  # 不能写成 para = para - paras.grad * lr !!!! 这句相当于要创建一个新的para,会导致报错
            para.grad.zero_()  # 将使用过的梯度归零


lr = 0.01
w_0 = torch.normal(0, 0.01, true_w.shape, requires_grad=True)
b_0 = torch.tensor(0.01, requires_grad=True)
print(w_0, b_0)

epochs = 50
for epoch in range(epochs):
    data_loss = 0
    for batch_x, batch_y in data_provider(X, Y, batchsize):
        pred_y = fun(batch_x, w_0, b_0)
        loss = maeLoss(pred_y, batch_y)
        loss.backward()
        sgd([w_0, b_0], lr)
        data_loss += loss

    print("epoch %03d: loss: %.6f" % (epoch, data_loss))

print("真实函数值:", true_w, true_b)
print("训练得到的函数值:", w_0, b_0)


idx = 0
plt.plot(X[:, idx].detach().numpy(), X[:, idx].detach().numpy()*w_0[idx].detach().numpy()+b_0.detach().numpy())
plt.scatter(X[:, idx].detach().numpy(), Y, 1)
plt.show()

逐步分析代码

1.数据生成

首先设计一个函数create_data,提供我们所需要的数据集的x与y

py 复制代码
def create_data(w, b, data_num):  # 数据生成
    x = torch.normal(0, 1, (data_num, len(w)))  # 生成特征数据,形状为 (data_num, len(w))
    y = torch.matmul(x, w) + b  # 计算目标值 y = x * w + b

    noise = torch.normal(0, 0.01, y.shape)  # 生成噪声,形状与 y 相同
    y += noise  # 为 y 添加噪声,模拟真实数据中的随机误差

    return x, y
  • torch.normal() 生成一个张量

    • torch.normal(0, 1, (data_num, len(w))):生成一个形状为 (data_num, len(w)) 的张量,其中的元素是从均值为 0、标准差为 1 的正态分布中随机采样的。
  • torch.matmul() 让矩阵相乘

    matmul: matrix multiply

  • 再使用torch.normal()生成一个张量,添加到y上,相当于为y添加了随机的噪声

    噪声的引入是为了模拟真实数据中的随机误差,使生成的数据更接近现实场景。

2.设计一个数据加载器

py 复制代码
def data_provider(data, label, batchsize):  # 每次取 batchsize 个数据
    length = len(label)
    indices = list(range(length))
    random.shuffle(indices)  # 打乱数据顺序,避免模型学习到顺序特征

    for each in range(0, length, batchsize):
        get_indices = indices[each: each+batchsize]  # 获取当前批次的索引
        get_data = data[get_indices]  # 获取当前批次的数据
        get_label = label[get_indices]  # 获取当前批次的标签

        yield get_data, get_label  # 返回当前批次的数据和标签

data_provider可以分批提供数据,并通过yield来返回已实现记忆功能

首先把list y顺序打乱,这样就相当于从生成的训练集y中随机读取,若不打乱数据,可能造成训练结果的不理想

打乱数据可以避免模型在训练过程中学习到数据的顺序特征,从而提高模型的泛化能力。

之后分段遍历打乱的y,返回对应的局部的数据集来给神经网络进行训练

3.定义模型函数

py 复制代码
def fun(x, w, b):
    pred_y = torch.matmul(x, w) + b  # 计算预测值 y = x * w + b
    return pred_y

fun(x, w, b) 是一个线性模型,形式为 y = x * w + b,其中 x 是输入特征,w 是权重,b 是偏置。

4.定义Loss函数

py 复制代码
def maeLoss(pre_y, y):
    return torch.sum(abs(pre_y - y)) / len(y)  # 计算平均绝对误差 (MAE)
  • maeLoss 是平均绝对误差(Mean Absolute Error, MAE),它计算预测值 pre_y 和真实值 y 之间的绝对误差的平均值。
  • 公式为:MAE = (1/n) * Σ|pre_y - y|,其中 n 是样本数量。

5.梯度下降sgd函数

py 复制代码
# sgd(梯度下降)
def sgd(paras, lr):
    with torch.no_grad():  # 这部分代码不计算梯度
        for para in paras:
            para -= para.grad * lr  # 不能写成 para = para - paras.grad * lr !!!! 这句相当于要创建一个新的para,会导致报错
            para.grad.zero_()  # 将使用过的梯度归零

这里需要使用torch.no_grad()来避免重复计算梯度

在前向过程中已经累计过一次梯度了,如果在梯度下降过程中又累计了梯度,那么就会造成不必要的麻烦

PyTorch 会累积梯度,如果不手动清零,梯度会不断累积,导致参数更新错误。

para -= para.grad * lr就是将参数w修正的过程(w=w-(dy^/dw)*learningRate)

torch.no_grad() 是一个上下文管理器,用于禁用梯度计算。在参数更新时,禁用梯度计算可以避免不必要的计算和内存占用。

5.开始训练

py 复制代码
epochs = 50
for epoch in range(epochs):
    data_loss = 0
    num_batches = len(Y) // batchsize  # 计算批次数量

    for batch_x, batch_y in data_provider(X, Y, batchsize):
        pred_y = fun(batch_x, w_0, b_0)  # 前向传播
        loss = maeLoss(pred_y, batch_y)  # 计算损失
        loss.backward()  # 反向传播
        sgd([w_0, b_0], lr)  # 更新参数
        data_loss += loss.item()  # 累积损失

    print("epoch %03d: loss: %.6f" % (epoch, data_loss / num_batches))  # 打印平均损失

先定义一个训练轮次epochs=50,表示训练50轮

在每轮训练中将loss记录下来,以此评价训练的效果

首先用data_provider来获取数据集中随机的一部分

接着传入相应数据给模型函数,通过前向传播获得预测y值pred_y

调用Loss计算函数,获取这次的loss,再通过反向传播loss.backward()计算梯度

loss.backward() 是反向传播的核心步骤,用于计算损失函数对模型参数的梯度。

再通过梯度下降sgd([w_0, b_0], lr)来更新模型的参数

最终将这组数据的loss累加到这轮数据的loss中

6.结果绘制

py 复制代码
idx = 0
plt.plot(X[:, idx].detach().numpy(), X[:, idx].detach().numpy() * w_0[idx].detach().numpy() + b_0.detach().numpy())  # 绘制预测直线
plt.scatter(X[:, idx].detach().numpy(), Y, 1)  # 绘制真实数据点
plt.show()
相关推荐
deephub2 小时前
计算加速技术比较分析:GPU、FPGA、ASIC、TPU与NPU的技术特性、应用场景及产业生态
人工智能·深度学习·gpu·计算加速
意.远2 小时前
PyTorch参数管理详解:从访问到初始化与共享
人工智能·pytorch·python·深度学习
知来者逆3 小时前
计算机视觉——为什么 mAP 是目标检测的黄金标准
图像处理·人工智能·深度学习·目标检测·计算机视觉
MobiCetus3 小时前
Deep Reinforcement Learning for Robotics翻译解读2
人工智能·深度学习·神经网络·机器学习·生成对抗网络·计算机视觉·数据挖掘
搬砖的阿wei3 小时前
跳跃连接(Skip Connection)与残差连接(Residual Connection)
深度学习·residual·skip connection
Listennnn4 小时前
自动化网络架构搜索(Neural Architecture Search,NAS)
人工智能·深度学习·自动化
怎么全是重名4 小时前
OFP--2018
人工智能·神经网络·目标检测
weixin_398187754 小时前
YOLOv11训练教程:PyTorch与PyCharm在Windows 11下的完整指南
pytorch·yolo·pycharm
欲掩4 小时前
神经网络与深度学习:案例与实践——第三章(3)
人工智能·深度学习·神经网络
Blossom.1185 小时前
大数据时代的隐私保护:区块链技术的创新应用
人工智能·深度学习·自动化·区块链·智能合约