目录
- 前言
- 一、自求导的方法实现线性回归
- 1.1自求导的方法实现线性回归的理论讲解
- 1.1.1 线性回归是什么?
- 1.1.2线性回归方程是什么?
- 1.1.3散点输入
- 1.2参数初始化
- 1.2.1 参数与超参数
- 1.2.1.1 参数定义
- 1.2.1.2 参数内容
- 1.2.1.3 超参数定义
- 1.2.1.4 超参数内容
- 1.2.2 参数设定
- 1.3 损失函数
- 1.4 开始迭代
- 1.5 反向传播
- 1.6 显示频率设置
- 1.9 梯度下降显示
- 2.0 自求导实现线性回归程序
- 二、深度学习框架Pytorch的tensor
- 2.2 PyTorch是什么?
- 2.3 PyTorch的特点
- 2.4 tensor是什么?
- 2.5 torch 安装命令
- 2.6 tensor的存储机制
- 2.6.1 tensor
- 2.6.2 数据类型
- 2.6.2.1 基本构成
- 2.6.2.2 计算方式
- 2.6.2.3 实战计算
- 2.6.3 Storage 存储与共享
- 2.7 tensor的步长
- 2.8 tensor的偏移
- 2.9 Tensor的连续性
- 2.9.1 tensor的连续性是什么?
- 2.9.2 tensor的不连续性是什么?
- 2.9.3 不连续的缺点与解决方案
- 总结
前言
书接上文
线性回归的前向传播、反向传播与数学求解详解-CSDN博客文章浏览阅读1k次,点赞40次,收藏19次。本文从前向传播的代码实现出发,展示了如何利用线性模型对二维数据进行拟合及误差分析,接着深入讲解了反向传播中的学习率和梯度下降算法的理论基础及优化方法,结合Python代码动态演示了参数更新和损失函数的变化过程;最后,文章通过数学推导详细揭示了线性回归模型参数的计算公式,并用代码实现了数学解法的拟合过程,帮助读者全面掌握线性回归的基本原理、优化方法及编程实现。https://blog.csdn.net/qq_58364361/article/details/147264719?spm=1011.2415.3001.10575&sharefrom=mp_manage_link
一、自求导的方法实现线性回归
从以下2个方面对自求导的方法实现线性回归算法进行介绍
1.自求导的方法实现线性回归算法理论讲解
2.编程实例与步骤
上面这2方面的内容,让大家,掌握并理解自求导的方法实现线性回归算法。
1.1自求导的方法实现线性回归的理论讲解
在了解前向传播、反向传播、损失函数、学习率、梯度下降这些概念后,我们就可以通过自求导的方式来实现线性回归算法。
1.1.1 线性回归是什么?
定义:
线性回归是一种用于建立自变量与因变量之间关系的统计方法。
它假设因变量(或响应变量)与一个或多个自变量(或预测变量)之间的关系是线性的。
其主要目标是通过拟合一个线性模型来预测因变量的数值。
1.1.2线性回归方程是什么?
公式:
线性回归模型可以表示为:
Y=β_0+β_1X_1+β_2X_2+...+β_nX_n+ε
其中:
Y是因变量
X_1,X_2,...,X_n是自变量
β_0,β_1,...,β_n是模型的系数,表示自变量对因变量的影响。
ε是误差项,表示模型无法解释的随机误差
1.1.3散点输入
算法实现需要的数据,一些散点,将它们绘制在一个二维坐标中,其分布如下图所示:
1.2参数初始化
1.2.1 参数与超参数
1.2.1.1 参数定义
模型中可调整的变量,它们用来捕捉数据中的模式和特征。这些参数在模型训练过程中被不断调整以最小化损失函数或优化某种目标。
1.2.1.2 参数内容
权重(Weights):用来表示不同输入特征与神经元之间的连接强度
偏置(Biases):用于调整每个神经元的激活阈值,使模型能够更好地拟合数据。
1.2.1.3 超参数定义
超参数不是通过训练数据学习得到的,而是在训练过程之前需要手动设置的参数。
1.2.1.4 超参数内容
包括学习率、正则化参数、迭代次数、批量大小、神经网络层数和每层的神经元数量、激活函数。
学习率(Learning Rate):用于控制优化算法中每次更新参数时的步长。较小的学习率会导致训练收敛较慢,而较大的学习率可能导致训练不稳定或震荡。
正则化参数(Regularization Parameter):用于控制正则化的强度,如L1正则化和L2正则化。较大的正则化参数会增强正则化效果,有助于防止过拟合。
迭代次数(Number of Iterations):用于控制训练的迭代次数。迭代次数太小可能导致模型未完全学习数据的特征,而迭代次数太大可能导致过拟合。
批量大小(Batch Size):用于控制每次训练时用于更新参数的样本数量。批量大小的选择会影响训练速度和内存消耗。
神经网络层数和每层的神经元数量(Number of Layers and Neurons per Layer):用于定义神经网络的结构。
激活函数(Activation Function):用于控制神经网络每个神经元的输出范围,如Sigmoid、ReLU等。
1.2.2 参数设定
在前向传播实验中,知道了参数不同时对应的损失函数值也不同,所以需要先初始化一下参数和超参数,进行一次前向传播,得到损失值,这样才能通过反向传播减小损失,使直线的拟合效果更好。这里通过“参数初始化”组件来初始化w和b以及学习率这三个参数/超参数
1.3 损失函数
与前向传播实验和反向传播实验不同,此时b的值不再是0,也就意味着损失函数不仅仅受到w的影响,还会受到b的影响,将 y=wx+b 带入损失函数的表达式后,新的损失函数就变成了如下图所示的表达式:
1.4 开始迭代
迭代次数通常指的是反向传播的次数,即通过反向传播来更新模型的参数,直到达到一定的迭代次数或者达到收敛的条件为止。
迭代的次数越多,模型参数就越接近最优解,从而使得损失函数达到最小值,但是可能会造成过拟合(过拟合在之后的实验会讲到)。
一般来说,迭代次数需要根据具体情况来确定,通常需要进行多次迭代来更新模型参数,直到达到收敛的条件为止,在“开始迭代”组件中默认的是500次。
1.5 反向传播
当参数和损失函数都设置好之后,就开始反向传播了,也就是损失函数对w和b进行求导并且不断更新w和b的过程,是一个复合函数
求导的过程。损失函数对w和b的求导过程如下:
1.6 显示频率设置
求导完成之后,使用梯度下降的方法来更新w和b的值。为了更好的在实验中看现象,这里有一个“显示频率设置”组件,就是每经过多少次迭代,绘制一次当前参数的拟合线及损失函数的大小。
1.9 梯度下降显示
迭代的结果通过“梯度下降显示”组件进行查看。如下图所示,38.4是w 的实时值,24.58是b的实时值,42.68是最后的损失值。
除此之外,该组件还有三个图,左边的是实时的直线图,参数每更新一次,该直线就更新一次,右边的是损失值的图像,显示的是经过迭代后的损失值的大小,下方的图是W和B随着不断的更新而变化的等高线图
2.0 自求导实现线性回归程序
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspecfrom 机器学习.test import iter_num_list # 导入外部模块中的iter_num_list(此处未使用)# 1. 准备数据:散点坐标
data = [[-0.5, 7.7],[1.8, 98.5],[0.9, 57.8],[0.4, 39.2],[-1.4, -15.7],[-1.4, -37.3],[-1.8, -49.1],[1.5, 75.6],[0.4, 34],[0.8, 62.3]]# 转换为numpy数组,便于矩阵运算
data = np.array(data)
x_data = data[:, 0] # 取第一列作为x数据
y_data = data[:, 1] # 取第二列作为y数据# 初始化线性模型参数w和b
w = 0
b = 0# 设置学习率
learning_rate = 0.01# 定义损失函数(均方误差)
def loss_function(x_data, y_data, w, b):predicted = np.dot(x_data, w) + b # 线性预测y_mean = np.mean((y_data - predicted) ** 2) # 均方误差计算return y_mean# 创建画布,采用2行2列的格子布局
fig = plt.figure("show figure")
gs = gridspec.GridSpec(2, 2)# 左上子图:绘制散点图及拟合直线
ax1 = fig.add_subplot(gs[0, 0])
ax1.set_xlabel('X')
ax1.set_ylabel('y')
ax1.set_title('figure1 data')# 左下子图:绘制损失随迭代变化图
ax2 = fig.add_subplot(gs[1, 0])
ax2.set_xlabel('iter')
ax2.set_ylabel('e')
ax2.set_title('figure2 data')# 右上子图:绘制损失函数关于w和b的三维表面
ax3 = fig.add_subplot(gs[0, 1], projection='3d')# 设置权重w和偏置b的取值范围
w_values = np.linspace(-20, 80, 100)
b_values = np.linspace(-20, 80, 100)# 生成网格数据用于绘制曲面
W, B = np.meshgrid(w_values, b_values)# 初始化对应损失函数值矩阵
loss_values = np.zeros_like(W)# 计算每个(w, b)组合的损失值
for i, w_value in enumerate(w_values):for j, b_value in enumerate(b_values):loss_values[j, i] = loss_function(x_data, y_data, w_value, b_value)# 绘制三维曲面图(颜色映射采用viridis)
ax3.plot_surface(W, B, loss_values, cmap='viridis', alpha=0.8)# 设置三维坐标轴标签和标题
ax3.set_xlabel('w')
ax3.set_ylabel('b')
ax3.set_zlabel('loss')
ax3.set_title('figure3 surface plot')# 右下子图:绘制损失函数的等高线填充图
ax4 = fig.add_subplot(gs[1, 1])
ax4.set_xlabel('w')
ax4.set_ylabel('b')
ax4.set_title('coutour plot')
ax4.contourf(W, B, loss_values, levels=20, cmap='viridis')# 迭代次数设置
num_iterations = 100# 初始化列表,用于存储损失(sh)、迭代次数(cs)、和参数轨迹(gj)
sh = [] # 记录每次迭代的损失
cs = [] # 记录迭代次数
gj = [] # 记录每次迭代的(w, b)参数值轨迹# 梯度下降主循环
for i in range(1, num_iterations + 1):gj.append((w, b)) # 保存当前参数yc = np.dot(x_data, w) + b # 预测值e = np.mean((y_data - yc) ** 2) # 当前均方误差sh.append(e) # 记录损失cs.append(i) # 记录迭代次数# 计算梯度(损失对w和b的偏导数)dw = (-2 * (y_data - yc).dot(x_data)) / len(x_data)db = np.mean(-2 * (y_data - yc))# 参数更新,梯度下降法w = w - learning_rate * dwb = b - learning_rate * db# 每隔10次迭代或第1次迭代,更新绘图显示f = 10if i % f == 0 or i == 1:# 清空左上子图,重新绘制散点和拟合直线ax1.clear()ax1.set_xlabel('X')ax1.set_ylabel('y')ax1.set_title('figure1 data')ax1.scatter(x_data, y_data, c='b', marker='o') # 绘制散点x_min, x_max = x_data.min(), x_data.max()y_min, y_max = w * x_min + b, w * x_max + bax1.plot([x_min, x_max], [y_min, y_max], c='r') # 绘制拟合直线# 清空左下子图,重新绘制损失随迭代次数的变化曲线ax2.clear()ax2.set_xlabel('iter')ax2.set_ylabel('e')ax2.set_title('figure2 data')ax2.plot(cs, sh, c='g') # 绘制损失曲线# 绘制右上三维曲面上的梯度下降轨迹if len(gj) > 0:g_w, g_b = zip(*gj) # 解包轨迹参数# 绘制轨迹点(散点线)ax3.plot(g_w, g_b, [loss_function(x_data, y_data, w_, b_) for w_, b_ in gj], c='b')ax3.set_xlim(-20, 80)ax3.set_ylim(-20, 80)ax3.set_xlabel('w')ax3.set_ylabel('b')ax3.set_zlabel('loss')ax3.set_title('figure3 surface plot')# 绘制当前参数对应的点,用黑点高亮ax3.scatter(w, b, loss_function(x_data, y_data, w, b), c='b', s=20)# 右下等高线图绘制迭代轨迹曲线ax4.plot(g_w, g_b)# 暂停0.01秒,更新动画效果
二、深度学习框架Pytorch的tensor
从以下4个方面对深度学习框架Pytorch的tensor进行介绍
1.PyTorch是什么?
2.tensor是什么?
3.tensor的存储机制
4.tensor的连续性
上面这4方面的内容,让大家,掌握并理解深度学习框架Pytorch的tensor。
2.2 PyTorch是什么?
概念:
PyTorch是一个开源的深度学习框架,由Meta公司(原名:Facebook)的人工智能团队开发和维护。它提供了一个灵活、动态的计算图计算模型,使得在深度学习领域进行实验和开发变得更加简单和直观。
pytorch网址
PyTorch documentation — PyTorch 2.6 documentationhttps://pytorch.org/docs/stable/index.html
2.3 PyTorch的特点
2.1动态计算图
PyTorch使用动态计算图,这意味着计算图在运行时构建的,而不是在编译时静态定义的。
2.2 自动求导(微分)
PyTorch提供了自动求导机制,称为Autograd。它能够自动计算张量的梯度,这对于训练神经网络和其他深度学习模型非常有用。
2.3 丰富的神经网络库
PyTorch 提供了丰富的神经网络库,包括各种各样的层,损失函数、优化器等。这些库使得构建和训练神经网
络变得更加容易。
2.4支持GPU加速
PyTorch 充分利用了GPU的并行计算能力,能够在GPU上高效地进行计算,加速模型训练过程。
2.4 tensor是什么?
tensor是一种多维数组,类似于NumPy的ndarray.它是Pytorch中最基本的数据结构,用于存储和操作数据。
tensor可以是标量、向量、矩阵或者更高维度的数组,可以包含整数、浮点数或者其他数据类型的元素。
PyTorch的Tensor和NumPy的ndarray非常相似,但在设计和功能上有一些不同之处。主要的区别包括:GPU加速、自动求导、动态计算图。
2.5 torch 安装命令
python -m pip install --upgrade pip
pip install torch==2.4.1 -i https://pypi.tuna.tsinghua.edu.cn/simple
!pip install torch==2.4.1 -i Simple Index
import torch""" 01
-------------------打印标量 向量 矩阵
"""# 打印一个标量张量
# 标量是只有一个数值的张量,这里值为3.14
scalar_tensor = torch.tensor(3.14)
print("scalar_tensor:", scalar_tensor, "\n")# 打印一个向量张量
# 向量是1维张量,这里包含5个元素:[1, 2, 3, 4, 5]
vector_tensor = torch.tensor([1, 2, 3, 4, 5])
print("vector_tensor:", vector_tensor, "\n")# 打印一个矩阵张量
# 矩阵是2维张量,这里是2行2列的矩阵[[1, 2], [3, 4]]
matrix_tensor = torch.tensor([[1, 2], [3, 4]])
print("matrix_tensor:", matrix_tensor, "\n")
D:\python_huanjing\.venv1\Scripts\python.exe C:\Users\98317\PycharmProjects\study_python\机器学习\test.py
scalar_tensor: tensor(3.1400) vector_tensor: tensor([1, 2, 3, 4, 5]) matrix_tensor: tensor([[1, 2],[3, 4]]) 进程已结束,退出代码为 0
2.6 tensor的存储机制
2.6.1 tensor
在PyTorch中,tensor包含了两个部分,即Storage 和metadata。Storage(存储):存储是tensor中包含的实际的底层缓冲区,它是一维数组,存储了tensor的元素值。不同tensor可能共享相同的存储,即使它们具有不同的形状和步幅。存储是一块连续的内存区域,实际上存储了tensor中的数据。
Metadata(元数据):元数据是tensor的描述性信息,包括tensor的形状、数据类型、步幅、存储偏移量、设备等。元数据提供了关于tensor的结构和属性信息,但并不包括tensor中的实际数据。元数据允许PyTorch知道如何正确地解释存储中的数据以及如何访问它们。
描述性信息元数据:
tensor | tensor:tensor([[1,2,3],[4,5,6]]) |
Metadata | 形状 数据类型 步幅/步长 存储偏移量 设备 |
Storage | 一维数组 |
import torch
import torch# tensor 存储示例
# 创建一个2行3列的浮点数张量
tensor_2_3_float = torch.tensor([[1.0, 2.0, 3.0], [4, 5, 6]])# 打印张量的数据类型(dtype),比如float32
print(tensor_2_3_float.dtype, "\n")# 打印张量的底层存储结构(storage),显示存储的数据信息
print(tensor_2_3_float.storage(), "\n")# 将底层存储的数据转换为列表,方便查看具体元素顺序
print(tensor_2_3_float.storage().tolist(), "\n")# 打印存储偏移量,表示张量数据起始位置相对底层存储的偏移索引
print(tensor_2_3_float.storage_offset(), "\n")# 再次打印存储偏移量,结果相同
print(tensor_2_3_float.storage_offset(), "\n")
D:\python_huanjing\.venv1\Scripts\python.exe C:\Users\98317\PycharmProjects\study_python\机器学习\test.py
torch.float32 1.02.03.04.05.06.0
[torch.storage.TypedStorage(dtype=torch.float32, device=cpu) of size 6] [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] 0 0 C:\Users\98317\PycharmProjects\study_python\机器学习\test.py:11: UserWarning: TypedStorage is deprecated. It will be removed in the future and UntypedStorage will be the only storage class. This should only matter to you if you are using storages directly. To access UntypedStorage directly, use tensor.untyped_storage() instead of tensor.storage()print(tensor_2_3_float.storage(), "\n")进程已结束,退出代码为 0
2.6.2 数据类型
整型、无符号整数类型
浮点型
torch.Float16,也被称为FP16或半精度浮点数,是一种用于表示浮点数的数据类型,在计算机科学中广泛应用于各种领域。以下是对Float16的详细说明:
2.6.2.1 基本构成
Float16使用16位(即2个字节)来表示一个浮点数,这16位被分为三部分:
符号位:1位,用于表示数的正负。0代表正数,1代表负数。
指数位:5位,用于表示数的大小。其范围是00001~11110,对应的十进制数是1~30。为了得到实际的指数值,需要从这些值中减去一个偏置值15,因此实际指数的范围是-14~15。
尾数位:10位,用于表示数的精度。其范围是0~1023,这些值除以1024后得到实际的尾数值,范围是0~0.9990234375。
2.6.2.2 计算方式
Float16类型的数的计算公式是:
2.6.2.3 实战计算
对于给出的float16值:0011101000000000
根据计算公式得出:为0.75
符号位:0(表示这是一个正数)
指数部分:01110(二进制),转换为十进制是2^3+2^2+2^1=8+4+2=14
小数部分(尾数):1000000000(二进制)
接下来,将指数部分转换为实际的指数值。在float16中,指数的偏移量是15,因为最大为2^(5-1)-1=15,所以实际的指数是14-15=-1。
将小数部分转成十进制:2^(9)=512; 512/1024=0.5; 0.5+1=1.5
最终计算方法:(-1)^符号位×2^(-1)×1.5=0.75。
2.6.3 Storage 存储与共享
tensor结构图
对张量进行操作时(比如调整形状(不是reshape是view),转置等),它不会创建一个新的张量,而是返回一个指向相同数据的视图。即PyTorch通常会共享相同的存储对象,以节省内存和提高效率。
import torch# 验证storage存储与共享
# 1. 查看两个张量的底层存储是否相同,判断它们是否共享存储
# 2. 通过比较存储的内容和内存地址,确认它们是否指向同一块内存空间
# 3. 如果两者的底层存储内容和内存地址都一致,则说明它们共享同一份数据# 使用 arange 生成0到11的连续整数,共12个元素
# 然后使用 reshape 将其变形成3行4列的张量
tensorA = torch.arange(12).reshape(3, 4)# 打印张量tensorA的底层storage内容(以列表形式)
print(tensorA.storage().tolist())# 对tensorA进行转置操作,交换第0维和第1维,生成tensorB
tensorB = tensorA.transpose(0, 1)# 打印tensorB的底层storage内容(以列表形式)
# 可观察转置操作后底层存储数据是否发生变化
print(tensorB.storage().tolist(), "\n")# 打印tensorA存储内容的内存地址(指针)
print(tensorA.storage().data_ptr(), "\n")# 打印tensorB存储内容的内存地址(指针)
print(tensorB.storage().data_ptr(), "\n")# 打印tensorA对象的id(内存地址,用于区分不同对象)
print(id(tensorA), "\n")# 打印tensorB对象的id,和tensorA相比确认是否为不同对象
print(id(tensorB), "\n")
D:\python_huanjing\.venv1\Scripts\python.exe C:\Users\98317\PycharmProjects\study_python\机器学习\test.py
C:\Users\98317\PycharmProjects\study_python\机器学习\test.py:13: UserWarning: TypedStorage is deprecated. It will be removed in the future and UntypedStorage will be the only storage class. This should only matter to you if you are using storages directly. To access UntypedStorage directly, use tensor.untyped_storage() instead of tensor.storage()print(tensorA.storage().tolist())
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] 5948693287232 5948693287232 1573108881744 1573115855632 进程已结束,退出代码为 0
Storage存储的具体过程
在PyTorch中,张量的存储是通过torch.Storage类来管理的。张量的值被分配在连续的内存块中,这些内存块中,这些内存块是大小可变的一维数组,可以包含不同类型的数据,如float或int32。
具体来说,当我们创建一个张量时,PyTorch会根据我们提供的数据和指定的数据类型来分配一块连续的内存空间。这块内存空间由torch.Storage对象管理,而张量本身则提供了一种视图,让我们可以通过索引来访问这些数据。
此外,PyTorch还提供了一系列的函数和方法来操作张量,包括改变形状,获取元素、拼接和拆分等。这些操作通常不会改变底层的存储,而是返回一个新的张量视图,这个视图指向相同的数据但是可能有不同的形状或索引方式。
总的来说,张量的存储实现是PyTorch能够高效进行张量运算的关键。通过管理一块连续的内存空间,并提供了丰富的操作方法,使得用户可以方便地对多维数组进行各种计算和变换。
2.7 tensor的步长
步长指的是在每个维度上移动一个元素时在底层存储中需要跨越的元素数。
import torch"""
04
展示张量的步长(stride)概念及其转置操作对步长和存储的影响
"""
# 创建一个3行4列的张量,元素为0到11,数据类型为float32
tensor_A = torch.arange(12, dtype=torch.float32).reshape(3, 4)# 打印原张量
print(tensor_A, "\n")# 打印原张量的步长,每个维度跨越内存元素的步长
print(tensor_A.stride(), "\n")# 打印原张量的转置(交换行列)
print(tensor_A.T, "\n")# 打印转置后张量的步长,步长的变化反映了维度的交换
print(tensor_A.T.stride(), "\n")# 打印转置张量底层存储的所有元素,存储不变只是视图变换
print(tensor_A.T.untyped_storage().tolist())
D:\python_huanjing\.venv1\Scripts\python.exe C:\Users\98317\PycharmProjects\study_python\机器学习\test.py
tensor([[ 0., 1., 2., 3.],[ 4., 5., 6., 7.],[ 8., 9., 10., 11.]]) (4, 1) tensor([[ 0., 4., 8.],[ 1., 5., 9.],[ 2., 6., 10.],[ 3., 7., 11.]]) (1, 4) [0, 0, 0, 0, 0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, 0, 0, 128, 64, 0, 0, 160, 64, 0, 0, 192, 64, 0, 0, 224, 64, 0, 0, 0, 65, 0, 0, 16, 65, 0, 0, 32, 65, 0, 0, 48, 65]进程已结束,退出代码为 0
tensor([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
(4, 1)
第一个数字4表示矩阵的行(第一个维度)上移动一个元素时需要跨越的存储单元数。因为矩阵的每行包含4个元素,所以每次沿着行移动一个元素需要跨越4个存储单元。
第二个数字1表示在矩阵的列(第二个维度)上移动一个元素时需要跨越的存储单元数,因为矩阵的列数是1,所以在列上移动一个元素时只需要跨越一个存储单元。
2.8 tensor的偏移
偏移是指从张量的第一个元素开始的索引位置。
偏移程序演示
tensor的偏移
import torch""" 04
张量偏移(storage_offset)示例说明
"""
# 创建一个3行4列的浮点型张量,元素为0到11
tensor_A = torch.arange(12, dtype=torch.float32).reshape(3, 4)# 打印张量tensor_A的内容
print("tensor_A的内容:\n", tensor_A)# 打印张量tensor_A底层存储的所有元素列表
print("tensor_A的底层存储元素列表:\n", tensor_A.storage().tolist())# 打印tensor_A的存储偏移量,指示张量数据在底层存储中的起始位置
print("tensor_A的存储偏移量:\n", tensor_A.storage_offset())# 利用切片选取tensor_A第2行到第3行、第2列的数据,生成子张量tensorB
tensorB = tensor_A[1:3, 1:2]# 打印子张量tensorB的内容
print("tensorB的内容:\n", tensorB)# 打印tensorB的存储偏移量,可以看到相对于底层存储的起始位置发生了变化
print("tensorB的存储偏移量:\n", tensorB.storage_offset())# 打印tensorB底层存储的所有元素,注意存储仍为tensor_A共享的完整元素列表
print("tensorB的底层存储元素列表:\n", tensorB.storage().tolist())
D:\python_huanjing\.venv1\Scripts\python.exe C:\Users\98317\PycharmProjects\study_python\机器学习\test.py
C:\Users\98317\PycharmProjects\study_python\机器学习\test.py:13: UserWarning: TypedStorage is deprecated. It will be removed in the future and UntypedStorage will be the only storage class. This should only matter to you if you are using storages directly. To access UntypedStorage directly, use tensor.untyped_storage() instead of tensor.storage()print("tensor_A的底层存储元素列表:\n", tensor_A.storage().tolist())
tensor_A的内容:tensor([[ 0., 1., 2., 3.],[ 4., 5., 6., 7.],[ 8., 9., 10., 11.]])
tensor_A的底层存储元素列表:[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0]
tensor_A的存储偏移量:0
tensorB的内容:tensor([[5.],[9.]])
tensorB的存储偏移量:5
tensorB的底层存储元素列表:[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0]进程已结束,退出代码为 0
2.9 Tensor的连续性
2.9.1 tensor的连续性是什么?
Tensor的连续性指的是其元素在内存中按照其在张量中的顺序紧密存储,没有间隔。
生成的tensor根据"行优先"策略,在展开后与Storage存储的顺序一致,即该tensor连续。
2.9.2 tensor的不连续性是什么?
对上面的tensor进行转置操作
生成的tensor根据"行优先"策略,在展开后与Storage存储的顺序不一致,即该tensor不连续。
2.9.3 不连续的缺点与解决方案
当对Tensor进行某些操作,如转置(transpose)时,可能会导致Tensor变得不连续。
连续的tensor优势
高效的内存访问:连续的张量在内存中占用一块连续的空间,这使得CPU可以高效地按顺序访问数据,减少了内存寻址的时间,从而提高了数据处理的速度。
优化的计算性能:在进行数学运算时,连续张量可以减少数据的移动和复制,因为数据已经按照计算所需的顺序排序排列,这样可以减少计算中的延迟,提高整体的计算性能。
其它:在不连续的tensor上进行view()操作会报错
连续性和非连续性编程
import torch"""06
连续性和非连续性示例说明
"""
# 创建一个包含0到11的张量,并将其形状调整为3行4列
tensorA = torch.arange(12).reshape(3, 4) # arange生成0到11的序列,reshape调整形状为3x4# 将tensorA展平成一维张量,默认按行优先顺序
print("tensorA展平结果:", tensorA.flatten()) # 按行优先展平# 转置tensorA,交换0轴和1轴,生成tensorB
tensorB = tensorA.transpose(0, 1)# 尝试将tensorB展平成一维张量,默认按行优先顺序
print("tensorB展平结果:", tensorB.flatten())# 打印tensorB数据在内存中的起始地址
print("tensorB数据起始地址:", tensorB.data_ptr())# 判断tensorB是否是内存中连续存储的张量,返回False表示不连续
print("tensorB是否连续:", tensorB.is_contiguous())# 由于tensorB不是连续的,不能直接使用view改变形状,以下语句会报错
# tensorB.view(1, 12)# 将tensorB转化为内存连续的张量,生成新的tensorB
tensorB = tensorB.contiguous()# 对转化后的tensorB执行展平操作,按行优先顺序展开
print("转化为连续后tensorB展平结果:", tensorB.flatten())# 查看tensorB底层存储中的元素列表
print("tensorB底层存储元素列表:", tensorB.storage().tolist())# 打印转化后tensorB的数据起始地址,地址可能改变
print("连续化后tensorB数据起始地址:", tensorB.data_ptr())# 使用view成功改变tensorB的形状为1行12列
print("使用view改变形状为(1,12):", tensorB.view(1, 12))
D:\python_huanjing\.venv1\Scripts\python.exe C:\Users\98317\PycharmProjects\study_python\机器学习\test.py
tensorA展平结果: tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
tensorB展平结果: tensor([ 0, 4, 8, 1, 5, 9, 2, 6, 10, 3, 7, 11])
tensorB数据起始地址: 4949521994112
tensorB是否连续: False
转化为连续后tensorB展平结果: tensor([ 0, 4, 8, 1, 5, 9, 2, 6, 10, 3, 7, 11])
tensorB底层存储元素列表: [0, 4, 8, 1, 5, 9, 2, 6, 10, 3, 7, 11]
连续化后tensorB数据起始地址: 4949521994432
使用view改变形状为(1,12): tensor([[ 0, 4, 8, 1, 5, 9, 2, 6, 10, 3, 7, 11]])
C:\Users\98317\PycharmProjects\study_python\机器学习\test.py:34: UserWarning: TypedStorage is deprecated. It will be removed in the future and UntypedStorage will be the only storage class. This should only matter to you if you are using storages directly. To access UntypedStorage directly, use tensor.untyped_storage() instead of tensor.storage()print("tensorB底层存储元素列表:", tensorB.storage().tolist())进程已结束,退出代码为 0
解决方案:
通过contiguous()方法将不连续的张量转换为连续的张量。
如果tensor不是连续的,则会重新开辟一块内存空间保证数据是在内存中是连续的。
如果tensor是连续的,则contiguous()无操作
不论是reshape view 或者其他的操作 如果连续就只改变形状不改变storage ,如果不连续就直接申请新的内存确保连续。
总结
本文系统介绍了自求导方法实现线性回归的理论基础与具体实现步骤,首先阐述线性回归的定义、模型方程及参数初始化,详细讲解了损失函数构建、迭代优化过程及反向传播求导更新参数的原理与过程,辅以完整的Python编程实例,直观展示了梯度下降的迭代效果和损失变化。此外,文章深入解析了深度学习框架PyTorch中的张量(tensor)结构,包括其定义、存储机制、数据类型以及连续性等关键概念,说明了tensor底层的Storage及元数据构成,探讨了步长和偏移对内存访问的影响,并通过代码示例演示了tensor的存储共享、不连续性的成因及解决方案,帮助读者全面理解PyTorch tensor的底层机制及高效操作方法,为实际深度学习模型开发打下坚实基础。