news 2026/9/8 17:30:03

List、NumPy数组与PyTorch张量:从shape到reshape全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
List、NumPy数组与PyTorch张量:从shape到reshape全解析

最近在调试一个PyTorch训练脚本时,我又遇到了那个熟悉的画面:数据最初是Python的list,中间转成了np.array,最后又被封装成torch.Tensor;shape从(128,)变成(128,3,32,32),最后在模型输出里变成torch.Size([128, 10])。如果你是刚开始接触深度学习,看到这三种“装数据的容器”在代码里来回换来换去,肯定会犯迷糊:它们到底有什么区别?为什么不能从头到尾只用一种?为什么模型里到处都在看shape?

这篇文章想把这些基础概念一次性讲透。目标读者包括:学过Python但刚接触NumPy的人、已经写了几个PyTorch训练脚本却还没弄懂张量形状的人、以及想把代码里那些看似“魔法”的reshape操作真正搞明白的人。我不打算照搬官方文档,而是从实际用到的角度出发,解释list、np.array、torch.Tensor各自的职责边界,再讲清楚shape、reshape、view、flatten这套“形状操作”背后的逻辑。等你读完,再看到一段数据从list变成tensor再经过reshape进入模型,就不会觉得它神秘了。

1. 三种数据容器,其实是同一条数据流水线上的不同角色

1.1 从仓库到货架,再到带计算能力的货架

我习惯用一个不太严谨但很好记的比喻来理解这三者:list是仓库,np.array是仓库管理员整理好的标准货架,torch.Tensor则是把货架搬到了带有计算加速器的车间里,还顺便给每个货品挂上了“怎么算出来的”账本。

先看它们最直接的差异。Python原生list最大的特点是什么?随手就能创建,里面什么都能装,长度也能随时变。Python写起来舒服,很多临时数据处理都用它。但list不是为数值计算设计的,你不可能对list里的每一个元素都做“整体加3”这种操作,而且它也没有多维数组的概念。

np.array就是来解决这个问题的。NumPy会强制把数据放进一个同类型的、连续内存块里,然后你就可以对整块数据做批量运算。比如一个二维数组arr,arr + 3就是每个元素都加3,这种向量化运算速度远快于用for循环挨个处理list。代价是:数组创建后大小固定,而且里面的元素类型通常必须统一。

torch.Tensor则在np.array的基础上又加了两样关键的东西:device(设备)和自动微分。device决定了你的数据是在CPU上还是在GPU上算,这一条就把numpy在很多深度学习场景下淘汰掉了。自动微分则意味着你在构建网络和计算损失时,框架会记录张量之间的计算关系,backward的时候能自动算梯度。

这三者之间的转换代码也很简单:

import numpy as np import torch a = [1, 2, 3] # Python原生list b = np.array(a) # 一维numpy数组,shape是(3,) c = torch.tensor(a) # 一维张量,shape是torch.Size([3]) print(type(a)) # <class 'list'> print(type(b)) # <class 'numpy.ndarray'> print(type(c)) # <class 'torch.Tensor'>

1.2 一份可供快速查阅的能力对比表

角度listnp.ndarraytorch.Tensor
元素类型可混装任意类型通常是同一种数值类型通常是同一种数值类型,且更严格
长度可变性可append、pop创建后固定创建后固定
是否有shape没有有,返回tuple有,返回torch.Size
是否支持批量数值运算不支持支持且高效支持且高效
是否支持自动求梯度不支持不支持支持requires_grad、backward
是否可直接上GPU不可以不可以可以,通过.to(device)
典型使用阶段数据读取、临时收集传统运算、数据预处理模型前向、反向传播

为什么一个搞深度学习的人会被这三种容器弄晕?因为一个完整训练项目里,它们真的会被用在不同的阶段。数据刚读进来时,通常是Python list在存储;做清洗、预处理、维度整理时,很多人会先用numpy;一旦要交给模型训练,就必须转成torch.Tensor并送到GPU。这本身就是一个递进过程。

1.3 转换方向很方便,但别忽视“是否共享内存”

代码写多了,你会发现list、np.array、torch.Tensor三者互相转换基本是无痕的:

list_a = [1, 2, 3] arr_b = np.asarray(list_a) tensor_c = torch.as_tensor(arr_b)

不过有个坑值得提前说:torch.from_numpy(arr)返回的张量和原numpy数组共享底层内存。也就是说,你改了arr的值,对应的tensor也会变;反过来也一样。这在一些需要追求性能的场景很好用,能省一次数据拷贝,但在你没意识到共享存在的时候,它可能会变成让你摸不着头脑的“灵异修改”。如果你不想要这种共享关系,建议主动用.clone()拷贝一份。关于共享问题,在后面第七节还会再展开。

2. list没有shape,但AI项目里到处都需要它做“临时容器”

2.1 数据处理开始前,全世界的Python代码几乎都在用list

很多人学完numpy和torch之后,会觉得list是个该被淘汰的“低级货”。真不是。实际项目里,越是靠近数据源头,list越常见。比如写一个自定义Dataset,你通常要先把所有样本的文件路径放进list,再把对应的标签放进另一个list;读图后,你又把一张张图片暂存在list里,方便后面统一转成batch。

list之所以适合干这个,是因为它动态、灵活且不需要预先知道总长度。你可以边扫描目录边append,也可以在循环里把多个batch的预测结果先攒起来,最后再统一拼接。这类“边跑边收集”的需求,用np.array或torch.Tensor都很别扭,因为它们创建后大小固定,想动态扩容只能重新建一个更大的再拷贝,效率低,代码也丑。

典型的收集过程长这样:

preds_list = [] labels_list = [] with torch.no_grad(): for images, labels in valid_loader: outputs = model(images) preds_list.append(outputs.argmax(dim=1).cpu()) labels_list.append(labels.cpu()) preds = torch.cat(preds_list) labels = torch.cat(labels_list)

这里list容器就承担了batch之间的“暂存区”角色,最终一次性拼接成完整张量,避免每一步都事先分配大块显存。

2.2 为什么list没有shape属性

很多初学者第一反应是“list也有行和列啊,为什么没有shape?”因为list根本没有“规则形状”这种概念。

一维list的len()可以返回元素个数,这个好理解。但二维list,严格说并不是语言层面的“二维结构”,它只是“外层list套了内层list”。你写len(matrix)只能拿到外层有几个元素,而内层子list的长度是否一致,Python不管。

row1 = [1, 2, 3] row2 = [4, 5, 6] matrix = [row1, row2] # 视觉效果是2行3列 print(len(matrix)) # 2 print(matrix[0]) # [1, 2, 3] print(matrix[0][2]) # 3 # matrix.shape会直接报错: # AttributeError: 'list' object has no attribute 'shape'

重点在于:当嵌套list每一层长度都规规矩矩时,我们心里可以把它当“二维数组”,但Python并不承诺它一定长这样。你可以随时写row2.append(7),于是matrix就变成了2行3列加2行2列的混合体。这一点对后续转换到np.array极其重要。

2.3 用list时最容易犯的“规则形状”错误

既然list不保证规则形状,当你想把它转成np.array时,意外就来了:

bad_data = [[1, 2, 3], [4, 5]] arr = np.array(bad_data)

这段代码在很多NumPy版本里不会直接报错,而是给出一个warn,然后生成一个dtype为object的数组,形状可能变成(2,),而不是你期望的(2, 3)。object数组之所以麻烦,是因为它内部存的还是Python对象引用,数值批量运算和GPU加速全都用不上,放到深度学习里很容易变成后续报错的源头。

所以我的经验是:如果数据是从不同来源收集的图片或文本序列,第一件事就要确认长度是否一致。尤其文本任务里,每个句子长度天然不同,必须先做padding,把所有样本补齐到同一长度,再转成tensor。torch.nn.utils.rnn.pad_sequence就是干这个的:

import torch seqs = [torch.tensor([1, 2, 3]), torch.tensor([4, 5])] padded = torch.nn.utils.rnn.pad_sequence(seqs, batch_first=True) print(padded.shape) # torch.Size([2, 3])

短的那条会用0自动补全。这才是把不等长数据送进模型前该做的操作,而不是直接丢给np.array或torch.tensor。

3. np.array的轴思维:从np.arange(1,13).reshape(3,4)说起

3.1 热身案例:把1到12排成三行四列

如果你搜索过numpy相关的问题,一定见过这个经典表达式:np.arange(1,13).reshape(3,4)。它到底做了什么?先看看结果:

import numpy as np arr = np.arange(1, 13).reshape(3, 4) print(arr)

输出是:

[[ 1 2 3 4] [ 5 6 7 8] [ 9 10 11 12]]

这里的核心秘密是:reshape没有改变底层数据的先后顺序,只是把一段长度为12的连续数据重新“切

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 17:29:49

激光切割路径优化:从空移时间看TSP算法如何挤出产能

做激光加工这些年&#xff0c;我见过太多机器配置很高、但实际产出上不去的案例。切割头从A点移到B点&#xff0c;激光并没有在干活&#xff0c;这段空移路径看着不起眼&#xff0c;积累起来却是很多工厂沉默的效率杀手。今天想聊的&#xff0c;就是路径优化算法如何把一个&quo…

作者头像 李华
网站建设 2026/9/8 17:29:31

Claude Code + 8 个 MCP Server:让 AI 从聊天框变高级开发者

老实说&#xff0c;我一开始用 Claude Code 的时候&#xff0c;有点失望。它不是不强&#xff0c;而是像一个刚入职、满脑子理论知识但没接上手头项目的实习生&#xff1a;你让它写代码&#xff0c;它能写&#xff0c;但它看不见你的仓库结构&#xff0c;读不懂最新文档&#x…

作者头像 李华
网站建设 2026/9/8 17:25:52

06-JVM垃圾回收器之CMS

本篇是「JVM 与性能调优系列」第 6 篇。G1 之前&#xff0c;CMS 是低延迟的代名词。它敢在程序运行的同时回收老年代。但它也留下了「碎片化的诅咒」&#xff0c;最终被官方废弃。看懂 CMS&#xff0c;才懂 G1 为什么是答案。一、CMS 要解决什么 Parallel Old 回收老年代时是全…

作者头像 李华
网站建设 2026/9/8 17:25:46

开源免费远程桌面软件RustDesk的安装与使用

RustDesk是免费开源的远程桌面软件&#xff0c;支持Windows、Mac、Linux、Androd等系统&#xff0c;可以跨不同系统进行远程桌面访问。可以穿透内网&#xff0c;不论是单位内网电脑还是家里电脑&#xff0c;都可以远程访问。在低带宽网络环境中也能保持高速传输&#xff0c;电脑…

作者头像 李华
网站建设 2026/9/8 17:23:20

IntelliJ IDEA 社区版:从源码到跑通 IDE 的3步路径

IntelliJ IDEA 社区版&#xff1a;从源码到跑通 IDE 的3步路径 【免费下载链接】intellij-community IntelliJ IDEA & IntelliJ Platform 项目地址: https://gitcode.com/GitHub_Trending/in/intellij-community 如果你曾想弄清代码补全这类功能内部到底怎么实现&am…

作者头像 李华
网站建设 2026/9/8 17:22:41

国产MCU嵌入式开发实战:从选型到量产全流程体验

一个小项目&#xff0c;让我重新认识了国产MCU&#xff0c;有些话不吐不快 先说结论&#xff1a;这次用国产MCU做完整个项目&#xff0c;我是真的服气了。以前总觉得国产芯片就是“能用但折腾”&#xff0c;数据手册写得像机翻、开发工具链各种别扭、出了问题社区里连个提问的人…

作者头像 李华