2、JAX张量操作:创建张量、张量属性、数据类型、索引与切片、广播机制、重塑与转置

好,咱们进入正题。张量操作,说白了就是JAX这门语言的「基本功」。你想想看,不管是图像处理还是生成模型,底层全是张量在跑来跑去。我刚开始接触JAX时,觉得它跟NumPy长得太像了,差点掉以轻心。结果呢?踩了几个坑才明白——相似归相似,细节上JAX有自己的脾气。

2.1 创建张量:从哪儿来?

创建张量,最直接的方式就是用jnp.array()。我个人习惯先写NumPy再转JAX,因为调试起来更顺手。

import jax.numpy as jnp
import numpy as np

# 从Python列表创建
a = jnp.array([1, 2, 3, 4])
print(a)  # [1 2 3 4]

# 从NumPy数组创建
b = jnp.array(np.random.randn(3, 4))
print(b.shape)  # (3, 4)

除了array(),还有几个常用函数:

  • jnp.zeros((2, 3)) — 全零张量
  • jnp.ones((2, 3)) — 全一张量
  • jnp.eye(4) — 单位矩阵
  • jnp.arange(10) — 等差数列
  • jnp.linspace(0, 1, 5) — 等间隔序列

小提示:JAX默认创建的是32位浮点数,跟NumPy的64位不一样。我在做图像生成时,经常用jnp.float32来省显存。

2.2 张量属性:看看它长什么样

拿到一个张量,第一件事就是看它的形状、维度和数据类型。这三个属性我几乎每天都要用。

x = jnp.array([[1, 2, 3], [4, 5, 6]])

print(x.shape)   # (2, 3)
print(x.ndim)    # 2
print(x.dtype)   # int32
print(x.size)    # 6

嗯,这里要注意:shape返回的是一个元组,ndim就是元组的长度。我曾经在写模型时,把shape[0]当成了batch size,结果发现维度搞反了——调试了半小时才发现。

2.3 数据类型:别小看它

JAX支持的数据类型跟NumPy基本一致,但默认是32位。这一点跟PyTorch很像,跟NumPy不同。

类型说明常用场景
jnp.float3232位浮点模型参数、图像数据
jnp.float6464位浮点高精度计算(但JAX默认禁用)
jnp.int3232位整型索引、标签
jnp.bool_布尔型掩码、条件判断

注意:JAX默认不支持float64!如果你需要高精度,得在启动时设置jax.config.update("jax_enable_x64", True)。我第一次用JAX做科学计算时,发现结果精度不对,查了半天才发现是这个原因。

类型转换也很简单:

x = jnp.array([1, 2, 3], dtype=jnp.float32)
y = x.astype(jnp.int32)

2.4 索引与切片:怎么取数据?

索引和切片,跟NumPy完全一样。说白了就是方括号里写坐标。

x = jnp.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

# 取单个元素
print(x[1, 2])  # 6

# 取一行
print(x[0, :])  # [1 2 3]

# 取一列
print(x[:, 1])  # [2 5 8]

# 花式索引
print(x[[0, 2], [0, 2]])  # [1 9]

我个人觉得,花式索引在图像处理中特别有用。比如你要从一批图片中随机选几张,用数组索引就行。

避坑指南:JAX的索引返回的是新数组,不是视图。这意味着你修改索引结果不会影响原数组。我曾经以为能像NumPy那样原地修改,结果发现JAX的数组是不可变的。

2.5 广播机制:自动扩展的魔法

广播机制,说白了就是让不同形状的张量能一起做运算。JAX的广播规则跟NumPy完全一致:从最后一个维度开始比较,要么相等,要么一个是1,要么缺失。

# 标量广播
a = jnp.array([1, 2, 3])
b = 2
print(a + b)  # [3 4 5]

# 向量广播到矩阵
a = jnp.array([[1, 2, 3], [4, 5, 6]])
b = jnp.array([10, 20, 30])
print(a + b)  # [[11 22 33], [14 25 36]]

你想想看,如果没有广播机制,你得手动把b复制成跟a一样的形状,多麻烦。我在做图像归一化时,经常用广播来减去均值、除以标准差。

注意:广播虽然方便,但容易出错。我曾经把形状为(3, 1)和(1, 3)的两个数组做加法,结果得到了(3, 3)的矩阵——完全不是我想要的结果。所以,做广播前最好用reshape()显式调整形状。

2.6 重塑与转置:改变形状的艺术

重塑和转置,是张量操作中最常用的两个功能。重塑改变形状但不改变数据顺序,转置交换维度。

x = jnp.array([[1, 2, 3], [4, 5, 6]])

# 重塑
y = x.reshape(3, 2)
print(y)
# [[1 2]
#  [3 4]
#  [5 6]]

# 转置
z = x.T
print(z)
# [[1 4]
#  [2 5]
#  [3 6]]

嗯,这里有个细节:reshape是按行优先(C风格)填充的。如果你想要按列优先,可以用order='F'参数。

还有一个常用操作是flatten(),把多维张量拉成一维:

x = jnp.array([[1, 2], [3, 4]])
y = x.flatten()
print(y)  # [1 2 3 4]

小技巧:在卷积神经网络中,经常要把特征图从(batch, height, width, channels)转成(batch, channels, height, width)。用transpose()可以轻松搞定:x.transpose(0, 3, 1, 2)

知识体系总览

下面这张图,是我自己总结的张量操作知识体系。你可以把它当成一张地图,随时回来查漏补缺。

JAX张量操作 创建张量 array, zeros, ones eye, arange, linspace 张量属性 shape, ndim, dtype size 数据类型 float32, int32, bool 类型转换 索引与切片 基本索引、花式索引 切片操作 广播机制 自动扩展维度 广播规则 重塑与转置 reshape, transpose flatten 核心要点 JAX张量不可变,默认float32 广播规则与NumPy一致,注意维度匹配

好了,这一章的内容就这些。张量操作是JAX的基石,你练得越熟,后面写模型就越顺手。别急,慢慢来,每个操作都亲手敲一遍代码,比光看强十倍。


公众号:蓝海资料掘金营,微信deep3321