dive deeper into tensor:从底层开始学习tensor

news2024/10/8 18:17:00

inspired by karpathy/micrograd: A tiny scalar-valued autograd engine and a neural net library on top of it with PyTorch-like API (github.com)and Taking PyTorch for Granted | wh (nrehiew.github.io).

这属于karpathy的karpathy/nn-zero-to-hero: Neural Networks: Zero to Hero ,(github.com)课程.事实上他还有很多值得一看的课程和repos.
tensor分成哪些部分?

一个tensor可以分为元数据区和存储区(Storage)

信息区主要保存着tensor的形状(size)、步长(stride)、数据类型(type),storage_offset,layout等信息,而真正的数据则保存成连续数组,存储在存储区

tensor的存储

tensor数据底层存储是连续的,相对应的就是链表. pytorch使用Storage类存储数据. 可以使用tensor.storage()访问存储的数据

data = torch.arange(9)
print(data.storage().dtype)
print(data.storage().device)
print(data.storage().data_ptr()) #这里 存储数据也能访问数据的属性

All storage classes except for torch.UntypedStorage will be removed in the future, and torch.UntypedStorage will be used in all cases.

但是在最新的python中除了untypedstorage类其他都已经deprecated了,而在untypedstorage中数据是字节类型,并且也无法调用dtype这些属性,变得更加纯粹了.

image-20240710221322389

tensor的访问

pytorch数据存储是一维的,但是会根据它的一些元数据改变对它的"解释",而影响解释的元数据就是tride (as_strided可以使得两个tensor的size,stride和storage_offset一致)

stride stride是从一个元素到指定维度的另一个元素的间隔数,如果不指定维度,就返回在每个维度上的stride的tuple

Stride is the jump necessary to go from one element to the next one in the specified dimension dim.

A tuple of all strides is returned when no argument is passed in. Otherwise, an integer value is returned as the stride in the particular dimension dim.

storage_offset 返回tensor的第一个元素与storage的第一个元素的偏移量。

Returns self tensor’s offset in the underlying storage in terms of number of storage elements (not bytes).

x = torch.tensor([1, 2, 3, 4, 5])
x.storage_offset()
x[3:].storage_offset()

pytorch中tensor存储区的数据是连续的,而stride规定了如何访问.访问的方式就是

data = torch.randn(1,20,20) 
stride = data.stride() # -> (400, 20, 1)
data[0][2][3] ->  0*stride[0]+2*stride[2]+3*stride[3]->也就是说这个数据在第2*20+3*1=43

注意torch存储数据是行优先,也就是说,像下面这样的数据,第二个是0.6960而不是-0.5163. 所以访问时就类似索引乘以对应的行/列数,从这个角度来看,stride就是一个映射函数.

tensor([[ 1.6427,  0.6960,  0.7865,  0.9934,  0.4952],
        [-0.5163, -0.0823, -1.2630, -0.9474,  1.1055],
        [ 0.1538,  1.0177, -1.8064,  0.6440, -1.4661],
        [ 0.3305,  0.2681,  0.2768, -0.3924,  0.1743],
        [-0.8965, -0.5499, -0.4545, -1.1470,  0.6883]])

image-20240710220152997

tensor操作

可以对tensor的数据进行操作,比如下面运算,会改变tensor的存储数据.

data = torch.randn((4, 2))
stride = data.stride()
print(data, stride)
data[0, 1] = 10
print(data, stride)
data.add_(torch.ones((4, 2)))
print(data, stride)

但是有些操作不会,其只会返回数据相同(指的是数据在底层存储上相同)的视图(view),这些操作包括t(),expand,transpose,permute,view,squeeze等等,操作后的tensor数据不变(也就是views),但stride可能会改变,也就是说解释数据的方式会变.

底层存储并没有改变,只需将映射函数从旧形状的坐标系调整为新形状的坐标系。 如果映射函数已经将形状作为输入,那么只需更改形状属性即可。

  • reshape()reshape_as()flatten() 可以返回视图或新张量,所以后续代码不要假定它返回的存储数据是否跟原本输入相同.
  • 如果输入的张量已经连续,contiguous() 会返回自身,否则会通过复制数据返回一个新的连续张量.

下面一个例子报错原因,就是stride的问题,具体来说,这里转置之后stride变了,size没变,使得后续的view操作不满足条件

x = torch.arange(9).reshape(3, 3) # 3 x 3 stride (3,1)  size(3,3)
x.t().view(1, -1) # x.t() stride (1,3)  size(3,3)
# >> RuntimeError: view size is not compatible with input tensor's 
# size and stride. Use .reshape() instead

首先,view作用是返回一个存储数据相同,但shape/size可能不同的视图,要求新的视图与输入数据相兼容( 1.新的视图的每个dimension必须是输入的子空间或者2.新的视图的维度满足下面条件(连续性),否则不能得到新的视图.
假设得到的新的 t e n s o r 维度涉及 d , . . , d + k , 对其中所有维度要求 : stride [ i ] = stride [ i + 1 ] × size [ i + 1 ] 假设得到的新的tensor维度涉及d,..,d+k,对其中所有维度要求:\\ \text{stride}[i]=\text{stride}[i+1]\times\text{size}[i+1] 假设得到的新的tensor维度涉及d,..,d+k,对其中所有维度要求:stride[i]=stride[i+1]×size[i+1]

如果不清楚是否可以执行 view(),建议使用 reshape()

reshape:如果形状兼容,则返回视图,否则返回拷贝(相当于调用 contiguous)

view之后size变为(1,9),这符合条件1,也就是size相符,再看这里(1,9)表明第二个维度跨域(span across)了原本输入的两个维度,而原本输入的两个维度中的第一个维度需要满足连续性条件,但是 stride[0] = 1 , stride[1]*size[1] = 1*3=3 不符合,所以view操作失败.

更抽象地说,因为没有办法在不改变底层数据的情况下对张量进行flatten处理.

我们能否从tensor的stride(3,1)推得tensor size是(3,3)?

答案是不能,它的size也完全可以是(4,3). 反过来size也不能推出stride.

在上面的例子中,比如底层数据是[1,2,3,4,5,6,7,8,9],stride是[3,1]. 也就是说在第一个维度下,数据到相同维度的下个数据间隔为3,同理第二个维度间隔为1,

经过转置之后,因为解释数据的方式变了,因为需要改变解释数据的方式,所以stride需要改变为(1,3). 再进行view(1,-1),如果不报错的话,size就是(1,9),你可能会认为结果不就是[[1,4,7,2,5,8…]]吗,但这个tensor的stride为多少呢? 是(9,1)吗,并不是.为什么呢,

因为底层数据[1,2,3,4,5,6,7,8,9],要查找第二个维度上的数据,比如1到4,在底层数据中是stride是3,而4到7也是3,所以是stride是(9,3),然而这跟size(1,9)不匹配(stride乘起来应该跟size乘起来应该相同,这是最起码的保证).

tensor([[1, 2, 3],
        [4, 5, 6],
        [7, 8, 9]]) 
        ⬇⬇
        tensor([[1, 4, 7],
        [2, 5, 8],
        [3, 6, 9]])
在这里插入图片描述

tensor的广播

PyTorch 的广播规则:(如何说一个tensor是可广播的)

  • 两个张量必须至少有一个维度

  • 从最右边的维开始,两个维必须大小相等,其中一个为 1 或者其中一个不存在。

    Two tensors are “broadcastable” if the following rules hold:

    • Each tensor has at least one dimension.
    • When iterating over the dimension sizes, starting at the trailing dimension, the dimension sizes must either be equal, one of them is 1, or one of them does not exist.

    Broadcasting semantics — PyTorch 2.3 documentation

    If two tensors x, y are “broadcastable”, the resulting tensor size is calculated as follows:

    • If the number of dimensions of x and y are not equal, prepend 1 to the dimensions of the tensor with fewer dimensions to make them equal length.
    • Then, for each dimension size, the resulting dimension size is the max of the sizes of x and y along that dimension.
# from https://nrehiew.github.io/blog/pytorch/
for each dimension, starting from the right:
	if both shapes have this dimension:
		if they are different:
				neither is 1: error
				else: use larger dimension 
		else they are the same: use dimension
	else:
		use whichever dimension exists

广播,不存在数据copy.这意味着,如果将一个小张量广播到一个大得多的形状,就不会产生内存或性能开销。

其次,由于较小张量中使用的实际元素是相同的,因此梯度会沿着这个较小维度中的项目累积.这在调试梯度或执行涉及广播的自定义自动梯度函数时特别有用。

x=torch.empty(5,1,4,1)
y=torch.empty(  3,1,1)
(x+y).size()

x=torch.empty(1)
y=torch.empty(3,1,7)
(x+y).size()

x=torch.empty(5,2,4,1)
y=torch.empty(3,1,1)
(x+y).size()

如果一个 PyTorch 操作支持广播,那么它的张量数据就会自动扩展为大小相等的数据(无需复制数据),所以本质上也是返回一个视图.

利用矩阵乘法进行广播

矩阵是二维的,但是tensor是不限制的.

多维tensor如何相乘的呢?

  1. 取两个张量的最后两个维度,检查它们是否可以相乘.如果不能,则出错
  2. 广播剩余维数.结果形状为 [广播后的维数] + [矩阵乘法的结果形状]。
  3. 将 [广播后的维数] 作为批处理维度,执行batched matrix multiplication(其实就是矩阵乘法,但是两个相乘的矩阵分别来自不同的batch中的相同的index)

使用torch.matmul进行tensor相乘,它的计算方式如下

  • 如果都是一维,进行点乘

  • 如果都是二维,进行矩阵乘,如果是1维和二维,在一维度之前添加一个维度在进行矩阵乘,乘完之后再去掉.

  • 如果是二维和1维,进行矩阵-向量乘法,得到向量.

  • 如果两个参数都至少为 1 维,且至少一个参数为 N 维(N > 2),则返回一个batched matrix multiplication.

    如果第一个参数是一维的,那么在进行batched matrix multiplication,会在其维度前添加一维,然后删除.

    如果第二个参数是一维的,则在其维度后加上 1,以便进行batched matrix multiply,并在运算后删除.

    非矩阵(即批处理)维度将被广播(因此必须是可广播的)

    比如(jx1xnxn)和(kxnxn)得到(jxkxnxn),在batch上广播得到(jxk),简单来说就是最后两维(不够进行广播)进行相乘,除了后面两维,其他维度直接进行广播.

注意:广播逻辑在确定输入是否可广播时,只查看批次维度,而不查看矩阵维度。

这跟上面的广播逻辑不同.

a = torch.randn((3, 4, 1, 2)) # 3 x 4 x 1 x 2
b = torch.randn((1, 2, 3)) # 1 x 2 x 3

# Matrix Multiply Shape: 1x2 @ 2x3 -> 1x3
# Batch Shape: We broadcast (3, 4) and (1) -> (3, 4)
# Result shape: 3 x 4 x 1 x 3
c = torch.zeros((3, 4, 1, 3))
# iterate over the batch dimensions of (3, 4)
for i in range(3):
	for j in range(4):
		a_slice = a[i][j] # 1 x 2
		b_slice = b[0] # 2 x 3	
		c[i][j] = a_slice @ b_slice # 1 x 3
assert torch.equal(torch.matmul(a, b), c)

反向传播

PyTorch 的核心是它的自动微分引擎.一般来说,每次在两个张量之间进行微分操作时,PyTorch 都会通过回调函数自动构建出整个计算图. 然后,当调用 .backward() 时,每个张量的梯度都会被更新. 这是 PyTorch 最大的抽象.

从标量的求导开始扩展到高维,这并不困难.首先需要理解标量的基本运算中的加/减法,乘法,幂、指以及对数.一个softmax操作就包含了加,幂指的操作.

可以将矩阵乘法看作是多个标量值的一系列乘法和加法运算,只需指定这些标量运算的后向运算,两个矩阵相乘的导数就自然而然地产生了.

从标量的角度来考虑梯度还有一个好处,就是可以直观地了解张量操作对梯度的影响。

例如,.reshape()、.transpose()、.cat 和 .split()等操作不会影响单个值及其在标量的梯度。 因此这些操作对张量梯度的影响自然就是操作梯度本身。

例如,使用 .reshape(-1) 对张量进行扁平化处理,对梯度的影响与调用 .reshape(-1) 对张量的梯度的影响相同。

优化

矩阵相乘

不使用 GPU 也能实现的优化方法.

一种可能的优化方法是利用内存访问模式,而不是改变算法。 回想一下,在给定 A @ B 的情况下,我们正在重复计算 A 中的一行和 B 中的一列的点乘.

简单的解决方法是转置,将其转为列主模式(column-first),这样每次从内存加载时,我们就可以在同一缓存行中加载 B 列中的正确项目.

转置是一种O(N) 操作,因此只适用于较大的矩阵.

另一种无需缓存的算法是对矩阵块进行运算,而不是一次性对整个矩阵进行运算。 这就是所谓的块矩阵乘法。 其原理是将矩阵分解成较小的块,然后在这些块上执行矩阵乘法。 这样做的另一个好处是减少了高速缓存的读取次数,因为我们现在是在矩阵的较小块上进行运算。

内存和中间值

这里原文Taking PyTorch for Granted | wh (nrehiew.github.io)似乎有typos,我进行了修正

在反向传播时,符合直觉的想法是保留中间值的梯度,以便后续计算leaf tensor的梯度.但是有些时候并不需要中间值的梯度

比如(a*b)+(c*d)=e,进行反向传播求e在a的梯度时如下

_t1 = a * b
_t2 = c * d
e = _t1 + _t2

其实就是求b,所以并不需要保留_t1和_t2的值.

参考资料

  1. karpathy/micrograd: A tiny scalar-valued autograd engine and a neural net library on top of it with PyTorch-like API (github.com)
  2. karpathy/nn-zero-to-hero: Neural Networks: Zero to Hero (github.com)
  3. karpathy/nanoGPT: The simplest, fastest repository for training/finetuning medium-sized GPTs. (github.com)
  4. pytorch笔记(一)——tensor的storage()、stride()、storage_offset()_pytorch storage-CSDN博客
  5. PyTorch中张量的shape和stride的关系_shape和strides-CSDN博客
  6. PyTorch internals : ezyang’s blog

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

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

相关文章

【数据结构】深入理解哈希及其底层数据结构

目录 一、unordered系列关联式容器 二、底层结构 2.1 哈希的概念 2.2 哈希冲突(哈希碰撞) 2.3 哈希函数 2.4 哈希冲突处理 2.4.1 闭散列(开放定址法) 2.4.1.1 代码实现: 2.4.2 开散列(链地址法&…

利用视频识别做一个土粒实时监测系统

要利用视频识别技术构建一个土粒实时监测系统,我们可以参考以下方案,该方案结合了计算机视觉、深度学习以及相关技术的要点。 一、系统概述 土粒实时监测系统基于先进的视频识别技术,旨在实现对土壤颗粒的实时、准确监测。该系统可以应用于…

Android启动优化之精确测量启动各个阶段的耗时

1. 直观地观察应用启动时长 我们可以通过观察logcat日志查看Android应用启动耗时,过滤关键字"Displayed": ActivityTaskManager: Displayed com.peter.viewgrouptutorial/.activity.DashboardActivity: 797ms 启动时长(在这个例子中797ms)表示…

水库大坝安全监测险情主要内容

水库常见险情主要包括洪水漫顶、脱坡滑坡、坝体裂缝、 散浸、渗漏、漏洞、陷坑、管涌等,此外风浪冲击、水流冲刷等也会加剧险情的扩大。大坝险情万一抢护不及时,易导致发 生溃坝事故,造成极为严重的灾难性后果。要做到及时有效地 抢护大坝险情…

智慧金融-数据可视化

智慧金融-数据可视化 导入所需的库 import numpy as np import numpy_financial as npf import matplotlib.pyplot as plt from pylab import mpl mpl.rcParams[font.sans-serif][FangSong] mpl.rcParams[axes.unicode_minus]False单图曲线图 r 0.05 # 贷款的年利率 n 30…

28.IP核理论知识(Xilinx)

(1)ip核是什么? IP(Intellectual Property)即知识产权,在半导体产业中,将IP核定义为“用于ASIC或FPGA中的预先设计好的电路功能模块”,简而言之,这里的IP即电路功能模块。…

使用 `useAppConfig` :轻松管理应用配置

title: 使用 useAppConfig :轻松管理应用配置 date: 2024/7/11 updated: 2024/7/11 author: cmdragon excerpt: 摘要:本文介绍了Nuxt开发中useAppConfig的使用,它便于访问和管理应用配置,支持动态加载资源、环境配置切换、权限…

MacOS 开发 — Packages 程序 macOS新版本 演示选项卡无法显示

MacOS 开发 — Packages 程序 macOS新版本 演示选项卡无法显示 问题描述 : 之前写过 Packages 的使用以及如何打包macOS程序。最近更新了新的macOS系统,发现Packages的演示选项卡无法显示,我尝试从新安转了Packages 也是没作用,…

医院人员管理系统03_上午:JDBC连接数据库,完成简单的增删改查

文章目录 jdbc连接数据库什么是jdbc完成jdbc的步骤导入jar包写三个类DBConn.java加载驱动类:找到对应的然后写上获取连接关闭连接代码解释 最后写一个main方法调用测试一下运行结果 Students.javaStudentDao.java 运行结果![](https://i-blog.csdnimg.cn/direct/ba2…

bC一体化:推拉联动双向引擎促增长

在数字化浪潮的推动下,快消品行业正面临着前所未有的变革。在过去的几十年里,快速消费品的渠道分销模式发展经历了几个重要的阶段。传统渠道分销模式,从多级分销商的“批发制胜”到“深度分销”的人海战术,在过去的几十年内虽各有…

工业智能网关的功能特点及应用

工业智能网关的功能特点及应用 工业智能网关作为工业互联网架构中的关键组件,扮演着数据桥梁与边缘计算核心的角色,它不仅促进了物理设备与云端平台之间的高效通信,还通过集成的高级功能推动了工业4.0时代的智能化转型。以下是对其功能特点及…

【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第一篇 嵌入式Linux入门篇-第二十五章 Source Insight 的安装和使用

i.MX8MM处理器采用了先进的14LPCFinFET工艺,提供更快的速度和更高的电源效率;四核Cortex-A53,单核Cortex-M4,多达五个内核 ,主频高达1.8GHz,2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT…

智慧景区建设方案PPT(54页)

智慧景区建设方案摘要 背景简述智慧景区建设旨在通过信息技术改进和创新传统旅游景区,整合旅游资源和产业链,实现精准网络营销,提升旅游品牌价值,改善信息共享和业务协同,提高旅游产业链效率。基础设施包括高速光纤网络…

FunAudioLLM SenseVoice语音转录(ASR)与CosyVoice语音合成(TTS)及语音克隆使用案例;webui可视化页面操作使用

参考: https://fun-audio-llm.github.io/ 1、SenseVoice语音转录 在线体验:https://modelscope.cn/studios/iic/CosyVoice-300M 参考:https://github.com/FunAudioLLM/SenseVoice 下载: pip install -U funasr使用: from funasr import AutoModelmodel_dir = "…

Nature Climate Change | 气候暖化使土壤碳库易受微生物快速降解影响

本文首发于“生态学者”微信公众号! 整个北半球土壤有机碳总量的一半富集在北极地区,其原因是气温较低导致微生物对永久冻土带土壤有机碳的分解缓慢,有利于有机碳的积累。但由于人类活动的影响,近几十年来北极出现了明显的升温&am…

第三方软件测试报告内容详解,如何获取专业软件测试报告?

在现代社会中,软件已经渗透到了各个行业的方方面面,而软件的质量也成为了企业成功的重要因素之一。然而,在软件开发过程中,由于人力、物力、时间等各种因素的限制,难免会出现一些问题和bug,而这些问题又可能…

【Linux系统】信号量(初次理解)

五个概念 多个执行流(进程),能看到的一份资源:共享资源被保护起来的资源可以叫临界资源(同步和互斥) --- 用互斥的方式保护共享资源就叫临界资源互斥:任何时刻只能有一个进程在访问共享资源资源…

【待补充】【来可USB-CAN】个人使用总结

文章目录 EVN简介10. 环境搭建20. 环境测试 EVN windows7 LIKE USB-CAN简介 官网介绍、资料下载 https://www.njlike.com/product/CAN-interfaces/USBCAN/01121.html 10. 环境搭建 USB-CAN 驱动 下载地址:https://www.njlike.com/uploads/soft/190625/1_1425474881…

Redis实践经验

优雅的Key结构 Key实践约定: 遵循基本格式:[业务名称]:[数据名]:id例:login:user:10长度步超过44字节(版本不同,上限不同)不包含特殊字符 优点: 可读性强避免key冲突方便管理节省内存&#x…

Pandas基础03:数据排序与增删

上一节我们介绍了通过按行索引和按列索引找出相关数据的方法。本章节将进一步介绍如何筛选数据,并对数据进行排序、增删的方法。 示例表格和上一节相同。 1.数据筛选 Python中可以通过区域筛选,即获取某几行某几列的方法得到数据。例如,我要…