深度学习_线性回归从零开始实现

前言

         最近在跟着李沐老师学习深度学习,听着课跟着老师的思路走的时候感觉很简单,自己动手就不断踩坑,磕磕绊绊加看着老师的代码,耗时一下午,终于写出了一版自己的(数据划分那里直接copy的老师的哈哈哈哈,菜鸟不会写)。附上学习链接:http://【08 线性回归 + 基础优化算法【动手学深度学习v2】-哔哩哔哩】 https://b23.tv/ItY64i9

代码书写流程: 

第一步:生成数据集 

        这里线性函数假设为  y = Xw + b + 随机噪声

def create_data(w, b, num_datas):
    """create:y = Xw + b + 噪声。"""
    X = torch.normal(0, 1, (num_datas, 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 = torch.tensor([4.2])
features, labels = create_data(true_w, true_b, 1000)

第二步:构建数据集读取函数

def data_split(batch_size, features, labels):
    num_examples = len(features)
    indices = list(range(num_examples))
    # 这些样本是随机读取的,没有特定的顺序
    random.shuffle(indices)
    # 划分标准是根据索引来的
    # 关于 yield 函数,大家可以参考下面的链接
    # https://blog.csdn.net/mieleizhi0522/article/details/82142856
    for i in range(0, num_examples, batch_size):
        batch_indices = torch.tensor(
            indices[i: min(i + batch_size, num_examples)])
        yield features[batch_indices], labels[batch_indices]

 第三步:初始化模型参数

         这一步记得给这两个张量加上requires_grad=True的属性,这样才可以进行下面的自动求导操作,对谁求导,就要给谁加上requires_grad=True属性。

w = torch.tensor([[0.0], [0.2]], requires_grad=True)
b = torch.tensor([0.5], requires_grad=True)

第四步:定义模型

        这一步就不用加噪声了 

def linear_regression(x0, w0, b0):
    return torch.matmul(x0, w0) + b0

 第五步:定义损失函数

         这里使用的是均方损失

def mse_loss(y_pred, y):
    los = pow(y_pred - y, 2) / 2
    return los

 第六步:定义优化算法

         这里使用的是梯度下降算法

def optimize_w(lr, w, b, batch_size):
    with torch.no_grad():
        w += - lr * w.grad / batch_size
        b += - lr * b.grad / batch_size
        w.grad.zero_()
        b.grad.zero_()

第七步 训练及画图

num_epochs = 20
batch_size = 32
lr = 0.03
Net = linear_regression
all_loss = np.zeros(num_epochs, float)
for epoch in range(num_epochs):
    for X, y in data_split(batch_size, features, labels):
        y_pred = Net(X, w, b)
        loss = mse_loss(y_pred, y)
        # l = loss.sum()
        loss.sum().backward()
        optimize_w(lr, w, b, batch_size)
    """
    with torch.no_grad() 后面的操作就是在计算所有数据进行一遍训练后,模型
    训练结果的平均损失
    """
    with torch.no_grad():
        # torch.no_grad() 意味着这里的计算不进行梯度累计
        train_l = mse_loss(Net(features, w, b), labels)
        print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')
        all_loss[epoch] = train_l.mean()

x = np.arange(1, len(all_loss) + 1, 1, int)
print(x)
plt.plot(x, all_loss)
plt.xlabel("epochs")  # 设置 x 轴标签
plt.ylabel("loss")  # 设置 y 轴标签
plt.title("loss_curve")   # 设置标题
plt.show()

print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - b}')

总结 

        完全不看答案的情况下自己基本构思不出来完整的步骤,因为目前许多python函数和包也都不太会用,写代码的过程中遇到两个很有意思的错误,在这里和大家分享一下。

错误一:element 0 of tensors does not require grad and does not have a grad_fn

         这个错误出现在第七步训练时,loss.bacward()的过程中,调试了一下出错具体在loss函数里面,具体的loss函数设计代码如下,个人初步猜想应该是地址的问题,也就是使用los = torch.tensor(pow(y_pred - y, 2)) 计算loss的时候,相当于将pow(y_pred - y, 2)的值赋给了一个新的los变量,这个变量并不在最后自动求导的计算图里面,只有使用los = pow(y_pred - y, 2) / 2,才会将loss的结果记录在自动求导的计算图里面。

def mse_loss(y_pred, y):
    """
    # 使用报错: element 0 of tensors does not require grad and does not have a grad_fn
    los = torch.tensor(pow(y_pred - y, 2)) 得到结果值为tensor(number)
    """
    # los = pow(y_pred - y, 2) / 2通过,没有报上面的错
    # 得到结果值为tensor(number,grad_fn=<SumBackward0>)
    # 可以得知这个提醒还是很专业的,有grad_fn属性的参数才能反向传播
    # 菜鸟看不懂,为什么第一种方式会没有grad_fn
    # 难道是相当于重新写了一个tensor张量,梯度属性就没有传递过来
    los = pow(y_pred - y, 2) / 2
    return los

错误二: 'NoneType' object has no attribute 'zero_'

        这个错误出现在程序第六步的优化算法里面,具体的地方是在梯度清零里面,w.grad.zero_()这一行,因为前面赋值的时候:w = w - lr * w.grad / batch_size,和第一个错误应该是一样的,w为一个新的,和原始w地址不同的值,不在自动求导的计算图里面了,因此改成w += - lr * w.grad / batch_size,程序就可以正常运行了。调试后,两者输出类型分别为tensor([[number],[number]])和tensor([[number],[number]],requires_grad=True),可以看出,一个是要求求梯度,一个是没有要求求梯度的,因此在计算时,前者就会报错(没有要求求梯度,怎么会有梯度清零)

    with torch.no_grad():
        """
            w = w - lr * w.grad / batch_size
            b = b - lr * b.grad / batch_size
            在 w.grad.zero_() 行报错: 'NoneType' object has no attribute 'zero_'
            得到的 w 结果值为tensor([[number],[number]])
        """
        # w += - lr * w.grad / batch_size通过,没有报上面的错
        # 得到结果值为tensor([[number],[number]],requires_grad=True)
        # 可以看出对于求导参数来说,只有有requires_grad=True,才可以执行一系列求导操作
        # 包括梯度清零
        w += - lr * w.grad / batch_size
        b += - lr * b.grad / batch_size
        w.grad.zero_()
        b.grad.zero_()

原因:运行一些操作可能会导致为结果分配新内存

        下面是李沐老师在课程中讲到的一些有关节省内存方面的东西,感觉是我这两个错误出现的原因,在这里贴出来记录一下 ,以后遇到类似问题就可以从这里找解决方法:

 

        还有其他的执行原地操作的方法,具体参考老师讲义中:pytorch/chapter_preliminaries/ndarray.ipynb  最后节省内存一节 

 完整代码:

import torch
import matplotlib.pylab as plt
import random
import numpy as np


# 线性回归从0开始实现
# 01生成数据集
def create_data(w, b, num_datas):
    """create:y = Xw + b + 噪声。"""
    X = torch.normal(0, 1, (num_datas, 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 = torch.tensor([4.2])
features, labels = create_data(true_w, true_b, 1000)

# 画出数据分布
# plt.scatter(features[:, 1].detach().numpy(), labels.detach().numpy(), 1)
# plt.show()

# 02读取数据集
"""
机器学习训练时会输入大量数据,通常我们需要将整个数据集进行划分然后丢给网络进行训练
这里可以设计一个data_split函数,可以按照特定批次大小划分数据集,分别进行训练,
该函数接收批量大小、特征矩阵和标签向量作为输入,生成大小为batch_size的小批量]。
每个小批量包含一组特征和标签。
"""


def data_split(batch_size, features, labels):
    num_examples = len(features)
    indices = list(range(num_examples))
    # 这些样本是随机读取的,没有特定的顺序
    random.shuffle(indices)
    # 划分标准是根据索引来的
    # 关于 yield 函数,大家可以参考下面的链接
    # https://blog.csdn.net/mieleizhi0522/article/details/82142856
    for i in range(0, num_examples, batch_size):
        batch_indices = torch.tensor(
            indices[i: min(i + batch_size, num_examples)])
        yield features[batch_indices], labels[batch_indices]


# 03初始化模型参数
w = torch.tensor([[0.0], [0.2]], requires_grad=True)
b = torch.tensor([0.5], requires_grad=True)


# 04定义模型
def linear_regression(x0, w0, b0):
    return torch.matmul(x0, w0) + b0


# 05定义损失函数
def mse_loss(y_pred, y):
    """
    # 使用报错: element 0 of tensors does not require grad and does not have a grad_fn
    los = torch.tensor(pow(y_pred - y, 2)) 得到结果值为tensor(number)
    """
    # los = pow(y_pred - y, 2) / 2通过,没有报上面的错
    # 得到结果值为tensor(number,grad_fn=<SumBackward0>)
    # 可以得知这个提醒还是很专业的,有grad_fn属性的参数才能反向传播
    # 菜鸟看不懂,为什么第一种方式会没有grad_fn
    # 难道是相当于重新写了一个tensor张量,梯度属性就没有传递过来
    los = pow(y_pred - y, 2) / 2
    return los


# 06定义优化算法
def optimize_w(lr, w, b, batch_size):

    with torch.no_grad():
        """
            w = w - lr * w.grad / batch_size
            b = b - lr * b.grad / batch_size
            在 w.grad.zero_() 行报错: 'NoneType' object has no attribute 'zero_'
            得到的 w 结果值为tensor([[number],[number]])
        """
        # w += - lr * w.grad / batch_size通过,没有报上面的错
        # 得到结果值为tensor([[number],[number]],requires_grad=True)
        # 可以看出对于求导参数来说,只有有requires_grad=True,才可以执行一系列求导操作
        # 包括梯度清零
        w += - lr * w.grad / batch_size
        b += - lr * b.grad / batch_size
        w.grad.zero_()
        b.grad.zero_()


# 07训练
num_epochs = 20
batch_size = 32
lr = 0.03
Net = linear_regression
all_loss = np.zeros(num_epochs, float)
for epoch in range(num_epochs):
    for X, y in data_split(batch_size, features, labels):
        y_pred = Net(X, w, b)
        loss = mse_loss(y_pred, y)
        # l = loss.sum()
        loss.sum().backward()
        optimize_w(lr, w, b, batch_size)
    """
    with torch.no_grad() 后面的操作就是在计算所有数据进行一遍训练后,模型
    训练结果的平均损失
    """
    with torch.no_grad():
        # torch.no_grad() 意味着这里的计算不进行梯度累计
        train_l = mse_loss(Net(features, w, b), labels)
        print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')
        all_loss[epoch] = train_l.mean()

x = np.arange(1, len(all_loss) + 1, 1, int)
print(x)
plt.plot(x, all_loss)
plt.xlabel("epochs")  # 设置 x 轴标签
plt.ylabel("loss")  # 设置 y 轴标签
plt.title("loss_curve")   # 设置标题
plt.show()

print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - b}')

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值