
# 基本张量操作
对 PyTorch 张量执行基本操作是一项主要技能。张量支持多种数学和逻辑运算。这些运算许多都与 NumPy 中的对应功能类似,都是按元素进行的。这些运算是神经网络计算的根本。
### 按元素算术运算
最常见的运算是将标准算术函数独立应用于参与运算的张量的每个元素。这些运算通常要求张量具有兼容的形状(关于形状兼容性,我们将在下一章讨论广播时详细说明)。
您可以使用标准 Python 算术运算符或等效的 `torch` 函数:
- **加法**: `+` 或 `torch.add()`
- **减法**: `-` 或 `torch.sub()`
- **乘法**: `*` 或 `torch.mul()`
- **除法**: `/` 或 `torch.div()`
- **幂运算**: `**` 或 `torch.pow()`
让我们看看这些操作如何运行:
```scala 3
import torch.*
// 创建两个张量
val a = torch.tensor(Seq(Seq(1., 2.), Seq(3., 4.)))
val b = torch.tensor(Seq(Seq(5., 6.), Seq(7., 8.)))
// 加法
val sum_tensor = a + b
println("加法 (a + b):\n", sum_tensor)
println("加法 (torch.add(a, b)):\n", torch.add(a, b))
// 减法
val diff_tensor = a - b
println("\n减法 (a - b):\n", diff_tensor)
// 按元素乘法
val mul_tensor = a * b
println("\n按元素乘法 (a * b):\n", mul_tensor)
println("按元素乘法 (torch.mul(a, b)):\n", torch.mul(a, b))
// 除法
val div_tensor = a / b
println("\n除法 (a / b):\n", div_tensor)
// 幂运算
val pow_tensor = a ** 2
println("\n幂运算 (a ** 2):\n", pow_tensor)
println("幂运算 (torch.pow(a, 2)):\n", torch.pow(a, 2))
```
```java
// 创建两个张量
var floats = new float[][]{{1.0f, 2.0f}, {3.0f, 4.0f}};
var floatB = new float[][]{{5.0f, 6.0f}, {7.0f, 8.0f}};
var flatenA = TensorToolkit.flatten(floats);
var shapeA = TensorToolkit.getShape(floats);
var flatenB = TensorToolkit.flatten(floatB);
var shapeB = TensorToolkit.getShape(floatB);
// var floatPointers = new FloatPointer(convertAnyDimFloatArrayToBuffer(floats));
Tensor a = torch.tensor((float[]) flatenA).reshape(shapeA);
Tensor b = torch.tensor((float[]) flatenB).reshape(shapeB);
// 加法
Tensor sum_tensor = torch.add(a, b);
System.out.println("加法 (a + b):");
print(sum_tensor);
System.out.println("加法 (torch.add(a, b)):");
print(add(a, b));
// 减法
Tensor diff_tensor = torch.sub(a, b);
System.out.println("\n减法 (a - b):");
print(diff_tensor);
// 按元素乘法
Tensor mul_tensor = torch.mul(a, b);
System.out.println("\n按元素乘法 (a * b):");
print(mul_tensor);
System.out.println("按元素乘法 (torch.mul(a, b)):");
print(mul(a, b));
// 除法
Tensor div_tensor = torch.div(a, b);
System.out.println("\n除法 (a / b):");
print(div_tensor);
// 幂运算
Tensor pow_tensor = torch.pow(a,new Scalar(2));
System.out.println("\n幂运算 (a ** 2):");
print(pow_tensor);
System.out.println("幂运算 (torch.pow(a, 2)):");
print(pow(a, new Scalar(2)));
```
这些操作会创建包含结果的*新*张量。原始张量 `a` 和 `b` 保持不变。
### 就地操作
PyTorch 也提供许多操作的就地版本。这些操作直接修改张量,而不创建新对象,这可以节省内存。就地函数通常可以通过名称中末尾的下划线 `_` 来识别(例如,`add_`、`mul_`)。
```scala 3
import torch.*
val a = torch.tensor(Seq(Seq(1., 2.), Seq(3., 4.)))
val b = torch.tensor(Seq(Seq(5., 6.), Seq(7., 8.)))
println("原始张量 'a':\n", a)
// 执行就地加法
a.add_(b) // a 被直接修改
println("\na.add_(b) 后张量 'a':\n", a)
// 如果取消注释,这将引发错误,
// 因为 a + b 的结果是一个新张量,
// 不适合直接重新赋值给 'a' 的内存
// a = a + b // 标准加法会创建一个新张量
// 另一个就地操作
a.mul_(2) // 将 'a' 就地乘以 2
println("\na.mul_(2) 后张量 'a':\n", a)
```
```java
var fl = new float[][]{{1.0f, 2.0f}, {3.0f, 4.0f}};
var fb = new float[][]{{5.0f, 6.0f}, {7.0f, 8.0f}};
var flatenA2 = TensorToolkit.flatten(fl);
var shapeA2 = TensorToolkit.getShape(fl);
var flatenB2 = TensorToolkit.flatten(fb);
var shapeB2 = TensorToolkit.getShape(fb);
// var floatPointers2 = new FloatPointer(fl);
Tensor a2 = torch.tensor((float[]) flatenA2).reshape(shapeA2);
Tensor b2 = torch.tensor((float[]) flatenB2).reshape(shapeB2);
System.out.println("原始张量 'a2':");
print(a2);
// 执行就地加法
a2.add_(b2); // a 被直接修改
System.out.println("\na2.add_(b2) 后张量 'a2':");
print(a2);
// 另一个就地操作
a2.mul_(new Scalar(2)); // 将 'a2' 就地乘以 2
System.out.println("\na2.mul_(2) 后张量 'a2':");
print(a2);
```
虽然就地操作可以提高内存效率,但请谨慎使用。如果在计算图中的其他地方需要原始值,就地修改张量可能会导致自动求导(Autograd)(第 3 章会介绍)中的梯度计算问题。通常更稳妥的做法,尤其是在学习时,是使用返回新张量的标准操作。
### 标量操作
您可以对张量和单个数字(标量)执行算术运算。PyTorch 会自动扩展标量以匹配张量的形状,从而进行按元素运算。
```scala 3
import torch.*
val t = torch.tensor(Seq(Seq(1., 2., 3.), Seq(4., 5., 6.)))
val scalar = 10.0
// 加标量
println("t + 标量:\n", t + scalar)
// 乘以标量
println("\nt * 标量:\n", t * scalar)
// 减去标量
println("\nt - 标量:\n", t - scalar)
```
```java
var floatArr = new float[][]{{1.0f, 2.0f, 3.0f}, {4.0f, 5.0f, 6.0f}};
var flatFloatArr = TensorToolkit.flatten(floatArr);
var shapeFloatArr = TensorToolkit.getShape(floatArr);
Tensor t = torch.tensor((float[]) flatFloatArr).reshape(shapeFloatArr);
float scalar = 10.0f;
// 加标量
System.out.println("t + 标量:");
print(torch.add(t, new Scalar(scalar)));
// 乘以标量
System.out.println("\nt * 标量:");
print(torch.mul(t, new Scalar(scalar)));
// 减去标量
System.out.println("\nt - 标量:");
print(torch.sub(t, new Scalar(scalar)));
// 千万不要使用 torch.tensor(new Scalar(1.0f)) 类似操作,否则会jvm crash
```
### 其他数学函数
PyTorch 提供丰富的数学函数库,这些函数按元素对张量进行操作,类似于 NumPy 的通用函数(ufuncs)。
```scala 3
import torch.*
val t = torch.tensor(Seq(Seq(1., 4.), Seq(9., 16.)))
// 平方根
println("平方根 (torch.sqrt(t)):\n", torch.sqrt(t))
// 指数
println("\n指数 (torch.exp(t)):\n", torch.exp(t)) // e^x
// 自然对数
// 注意:确保对数的值为正
val t_pos = torch.abs(t) + 1e-6 // 添加小的 epsilon 以提高稳定性(如果存在零)
println("\n自然对数 (torch.log(t_pos)):\n", torch.log(t_pos))
// 绝对值
val t_neg = torch.tensor(Seq(Seq(-1., 2.), Seq(-3., 4.)))
println("\n绝对值 (torch.abs(t_neg)):\n", torch.abs(t_neg))
```
```java
var flatT2 = TensorToolkit.flatten(new float[][]{{1.0f, 4.0f}, {9.0f, 16.0f}});
var shapeT2 = TensorToolkit.getShape(new float[][]{{1.0f, 4.0f}, {9.0f, 16.0f}});
Tensor t2 = torch.tensor((float[]) flatT2).reshape(shapeT2);
// 平方根
System.out.println("平方根 (torch.sqrt(t2)):");
print(torch.sqrt(t2));
// 指数
System.out.println("\n指数 (torch.exp(t2)):");
print(torch.exp(t2)); // e^x
// 自然对数
// 注意:确保对数的值为正
Tensor t_pos = torch.add(abs(t), new Scalar(1e-6f)); // 添加小的 epsilon 以提高稳定性(如果存在零)
System.out.println("\n自然对数 (torch.log(t_pos)):");
print(torch.log(t_pos));
//
var flatTneg = TensorToolkit.flatten(new float[][]{{-1.0f, 2.0f}, {-3.0f, 4.0f}});
shapeTneg = TensorToolkit.getShape(new float[][]{{-1.0f, 2.0f}, {-3.0f, 4.0f}});
Tensor t_neg = torch.tensor((float[]) flatTneg).reshape(shapeTneg);
System.out.println("\n绝对值 (torch.abs(t_neg)):");
print(torch.abs(t_neg));
```
`torch` 模块中还有许多其他函数,例如 `torch.sin()`、`torch.cos()`、`torch.tanh()`、`torch.sigmoid()` 等。
### 归约操作
归约操作会减少张量中的元素数量,通常用于汇总信息。常见例子包括求和、求平均值、求最小值和最大值。
```scala 3
import torch.*
val t = torch.tensor(Seq(Seq(1., 2., 3.), Seq(4., 5., 6.)))
println("原始张量:\n", t)
// 所有元素的和
val total_sum = torch.sum(t)
println("\n所有元素的和 (torch.sum(t)):", total_sum)
// 所有元素的平均值
// 注意:平均值计算需要浮点张量
val mean_val = torch.mean(t.float())
println("所有元素的平均值 (torch.mean(t.float())):", mean_val)
// 最大值
val max_val = torch.max(t)
println("张量中的最大值 (torch.max(t)):", max_val)
// 最小值
val min_val = torch.min(t)
println("张量中的最小值 (torch.min(t)):", min_val)
```
```java
var flp = new float[][]{{1.0f, 2.0f, 3.0f}, {4.0f, 5.0f, 6.0f}};
var flatT3 = TensorToolkit.flatten(flp);
var shapeT3 = TensorToolkit.getShape(flp);
Tensor t3 = torch.tensor((long[]) flatT3).reshape(shapeT3);
System.out.println("原始张量:");
print(t3);
// 所有元素的和
Tensor total_sum = torch.sum(t3);
System.out.println("\n所有元素的和 (torch.sum(t3)):");
print(total_sum);
// 所有元素的平均值
// 注意:平均值计算需要浮点张量
Tensor mean_val = torch.mean(t3.to(ScalarType.Float));
System.out.println("所有元素的平均值 (torch.mean(t3.float())):");
print(mean_val);
// 最大值
Tensor max_val = torch.max(t3);
System.out.println("张量中的最大值 (torch.max(t3)):");
print(max_val);
// 最小值
Tensor min_val = torch.min(t3);
System.out.println("张量中的最小值 (torch.min(t3)):");
print(min_val);
```
您还可以使用 `dim` 参数沿着特定维度执行归约操作。这会折叠指定的维度,返回一个维度减少的张量。
```scala 3
import torch.*
// 创建一个张量
val t = torch.tensor(Seq(Seq(1., 2., 3.), Seq(4., 5., 6.)))
println("原始张量:\n", t)
// 沿着维度 0 求和(对行求和)
val sum_dim0 = torch.sum(t, dim=0)
println("\n沿着 dim=0 求和(列):\n", sum_dim0)
// 沿着维度 1 求和(对列求和)
val sum_dim1 = torch.sum(t, dim=1)
println("\n沿着 dim=1 求和(行):\n", sum_dim1)
// 沿着维度 1 求平均值
val mean_dim1 = torch.mean(t.float(), dim=1)
println("\n沿着 dim=1 求平均值(行):\n", mean_dim1)
```
```java
// 所有元素的和 on 指定维度 0
Tensor total_sum_dim0 = torch.sum(t3, 0);
System.out.println("\n所有元素的和 on dim 0 (torch.sum(t3)):");
print(total_sum_dim0);
// 所有元素的和 on 指定维度 1
Tensor total_sum_dim1 = torch.sum(t3,1);
System.out.println("\n所有元素的和 on dim 1 (torch.sum(t3)):");
print(total_sum_dim1);
// 所有元素的平均值
// 注意:平均值计算需要浮点张量
Tensor mean_dim1 = torch.mean(t3.to(ScalarType.Float),1);
System.out.println("所有元素的平均值 (torch.mean(t3.float())):");
print(mean_dim1);
```
> 
>
>
>
> 对张量 `[[1, 2, 3], [4, 5, 6]]` 沿着 `dim=1` 求和,结果为 `[1+2+3, 4+5+6] = [6, 15]`。
了解 `dim` 的工作原理对许多深度学习操作很重要,例如计算每个批次项的损失或应用批归一化。
### 比较操作
您可以使用标准比较运算符(`>`、`<`、`>=`、`<=`、`==`、`!=`)按元素比较张量。结果是一个布尔值张量(`torch.bool`)。
```scala 3
import torch.*
// 创建张量
val a = torch.tensor(Seq(Seq(1, 2), Seq(3, 4)))
val b = torch.tensor(Seq(Seq(1, 5), Seq(0, 4)))
println("张量 'a':\n", a)
println("张量 'b':\n", b)
// 相等检查
println("\na == b:\n", a == b)
// 大于检查
println("\na > b:\n", a > b)
// 小于或等于检查
println("\na <= b:\n", a <= b)
```
```java
var ina = new int[][]{{1, 2}, {3, 4}};
var inb = new int[][]{{1, 5}, {0, 4}};
var flatenA3 = TensorToolkit.flatten(ina);
var shapeA3 = TensorToolkit.getShape(ina);
var flatenB3 = TensorToolkit.flatten(inb);
var shapeB3 = TensorToolkit.getShape(inb);
Tensor a3 = torch.tensor((int[]) flatenA3).reshape(shapeA3);
Tensor b3 = torch.tensor((int[]) flatenB3).reshape(shapeB3);
System.out.println("张量 'a':");
print(a3);
System.out.println("张量 'b':");
print(b3);
// 相等检查
System.out.println("\na3 == b3:");
print(torch.eq(a3, b3));
// 大于检查
System.out.println("\na3 > b3:");
print(torch.gt(a3, b3));
// 小于或等于检查
System.out.println("\na3 <= b3:");
print(torch.le(a3, b3));
```
布尔张量对掩码操作很有用,您将在更高级的张量操作中遇到它们。
### 逻辑操作
逻辑操作(`torch.logical_and()`、`torch.logical_or()`、`torch.logical_not()`)按元素对布尔张量或可在布尔上下文中求值的张量进行操作(其中 0 为假,非零为真)。
```scala 3
import torch.*
// 创建布尔张量
val bool_a = torch.tensor(Seq(Seq(true, false), Seq(true, true)))
val bool_b = torch.tensor(Seq(Seq(false, true), Seq(true, false)))
println("布尔张量 'bool_a':\n", bool_a)
println("布尔张量 'bool_b':\n", bool_b)
// 逻辑与
println("\ntorch.logical_and(bool_a, bool_b):\n", torch.logical_and(bool_a, bool_b))
// 逻辑或
println("\ntorch.logical_or(bool_a, bool_b):\n", torch.logical_or(bool_a, bool_b))
// 逻辑非
println("\ntorch.logical_not(bool_a):\n", torch.logical_not(bool_a))
```
```java
var bool1 = new Boolean[][]{{true, false}, {true, true}};
var bool2 = new Boolean[][]{{false, true}, {true, false}};
var flattenBool1 = TensorToolkit.flatten(bool1);
var shapeBool1 = TensorToolkit.getShape(bool1);
var flattenBool2 = TensorToolkit.flatten(bool2);
var shapeBool2 = TensorToolkit.getShape(bool2);
Tensor bool_a = torch.tensor((byte[])flattenBool1).reshape(shapeBool1);
Tensor bool_b = torch.tensor((byte [])flattenBool2).reshape(shapeBool2);
System.out.println("布尔张量 'bool_a':");
print(bool_a);
System.out.println("布尔张量 'bool_b':");
print(bool_b);
// 逻辑与
System.out.println("\ntorch.logical_and(bool_a, bool_b):");
print(torch.logical_and(bool_a, bool_b));
// 逻辑或
System.out.println("\ntorch.logical_or(bool_a, bool_b):");
print(torch.logical_or(bool_a, bool_b));
// 逻辑非
System.out.println("\ntorch.logical_not(bool_a):");
print(torch.logical_not(bool_a));
```
这些基本操作为更复杂的计算提供了基本组成部分。掌握它们是在 PyTorch 中实现数值算法和神经网络层的第一步。在下一章中,我们将介绍更高级的张量操作方法,包括索引、切片、重塑和广播。
# 与 NumPy 的关联
如果你对 Python 中的科学计算有经验,你可能很熟悉 NumPy 及其 `ndarray` 对象。NumPy 提供了一种强大的 N 维数组结构,已成为 Python 中数值操作的标准。PyTorch 认识到这种普遍性,并提供了与 NumPy 数组出色的互操作性。事实上,PyTorch 张量与 NumPy 数组非常相似:它们都是数字多维网格的抽象。
这种密切关联使得两者之间的切换变得简单,让你在 PyTorch 生态系统中工作时,能够运用现有的 NumPy 代码或库。
### 相似结构,不同能力
从本质上看,PyTorch 张量和 NumPy 数组都表示多维的密集数值数据。你可能在 NumPy 数组上执行的许多操作,在 PyTorch 张量中都有直接对应,通常具有相似的命名约定:
- **创建:** 两个库都提供函数来创建填充零、一、随机数或从现有 Python 列表创建数组/张量。
- **数学运算:** 逐元素加法、减法、乘法、除法、求幂、三角函数等,操作方式相似。
- **索引和切片:** 访问和修改元素或子数组/子张量使用可比较的语法。
- **形状操作:** 改变数组/张量的形状、转置和连接遵循相似的原则。
然而,主要由于 PyTorch 对深度学习的侧重,两者之间存在一些基本区别:
1. **GPU 加速:** PyTorch 张量可以移到图形处理单元 (GPU) 上处理。这使得大规模并行计算成为可能,为深度学习中常见的矩阵乘法及其他操作提供了显著的速度提升。NumPy 数组主要为 CPU 计算设计。
2. **自动微分:** PyTorch 张量通过 `Autograd` 系统(第 3 章会讲到)内置支持自动微分。这种机制会自动跟踪对需要梯度的张量执行的操作,并在反向传播期间计算这些梯度,这对训练神经网络来说很重要。NumPy 数组不具备此能力。
### NumPy 数组与张量之间的转换
PyTorch 使得在这两种数据结构之间转换变得简单。
#### NumPy 数组到 PyTorch 张量
你可以使用 `torch.from_numpy()` 函数直接从 NumPy 数组创建 PyTorch 张量。
```scala 3
import numpy as np
import torch.*
// 创建一个 NumPy 数组
val numpy_array = np.array(Seq(Seq(1, 2), Seq(3, 4)), dtype=np.float32)
println(f"NumPy 数组:\n{numpy_array}")
println(f"NumPy 数组类型: {numpy_array.dtype}")
// 将 NumPy 数组转换为 PyTorch 张量
val pytorch_tensor = torch.from_numpy(numpy_array)
println(f"\nPyTorch 张量:\n{pytorch_tensor}")
println(f"PyTorch 张量类型: {pytorch_tensor.dtype}")
```
**内存共享的重要说明:** 使用 `torch.from_numpy()` 时,生成的 PyTorch 张量和原始 NumPy 数组在 CPU 上共享相同的底层内存位置。这意味着修改一个对象会影响另一个。这种行为很高效,因为它避免了数据复制,但你需要注意这一点。
```scala 3
// 修改 NumPy 数组
numpy_array(0, 0) = 99
println(f"\n修改后的 NumPy 数组:\n{numpy_array}")
println(f"修改 NumPy 数组后的 PyTorch 张量:\n{pytorch_tensor}")
# 修改 PyTorch 张量
pytorch_tensor(1, 1) = -1
println(f"\n修改后的 PyTorch 张量:\n{pytorch_tensor}")
println(f"修改 PyTorch 张量后的 NumPy 数组:\n{numpy_array}")
```
如你所见,更改会反映在两个对象中,因为它们指向内存中的相同数据。
#### PyTorch 张量到 NumPy 数组
反之,你可以使用 `.numpy()` 方法将位于 CPU 上的 PyTorch 张量转换回 NumPy 数组。
```scala 3
// 在 CPU 上创建一个 PyTorch 张量
val cpu_tensor = torch.tensor(Seq(Seq(10.0, 20.0), Seq(30.0, 40.0)))
println(f"原始 PyTorch 张量 (CPU):\n{cpu_tensor}")
// 将张量转换为 NumPy 数组
val numpy_array_converted = cpu_tensor.numpy()
println(f"\n转换后的 NumPy 数组:\n{numpy_array_converted}")
println(f"NumPy 数组类型: {numpy_array_converted.dtype}")
```
同样,生成的 NumPy 数组和原始 CPU 张量共享相同的底层内存。对一个的修改会影响另一个。
```scala 3
// 修改张量
cpu_tensor(0, 1) = 25.0
println(f"\n修改后的 PyTorch 张量:\n{cpu_tensor}")
println(f"修改张量后的 NumPy 数组:\n{numpy_array_converted}")
# 修改 NumPy 数组
numpy_array_converted(1, 0) = 35.0
println(f"\n修改后的 NumPy 数组:\n{numpy_array_converted}")
println(f"修改 NumPy 数组后的张量:\n{cpu_tensor}")
```
**GPU 张量:** `.numpy()` 方法仅适用于存储在 CPU 上的张量。如果你的张量在 GPU 上,你必须先使用 `.cpu()` 方法将其移到 CPU,然后才能将其转换为 NumPy 数组。直接在 GPU 张量上调用 `.numpy()` 会导致错误。
```scala 3
// 假设有 GPU 可用的示例
if torch.cuda.is_available() then
val gpu_tensor = torch.tensor(Seq(Seq(1.0, 2.0), Seq(3.0, 4.0)), device='cuda')
println(f"\nGPU 上的张量:\n{gpu_tensor}")
// 这将导致错误: numpy_from_gpu = gpu_tensor.numpy()
// 正确方法: 先移到 CPU
val cpu_tensor_from_gpu = gpu_tensor.cpu()
val numpy_from_gpu = cpu_tensor_from_gpu.numpy()
println(f"\n转换后的 NumPy 数组 (来自 GPU 张量):\n{numpy_from_gpu}")
// 注意: numpy_from_gpu 与 cpu_tensor_from_gpu 共享内存,
// 但不与原始的 gpu_tensor 共享。
else
println("\nCUDA 不可用,跳过 GPU 到 NumPy 的示例。")
```
```java
// 检查CUDA(GPU支持)是否可用
if (cuda_is_available()) {
// System.out.println("CUDA is available. Device: " + cuda_get_device_name(0));
// 获取PyTorch将使用的默认设备
var gpuData = new float[][]{{1.0f, 2.0f}, {3.0f, 4.0f}};
var flatGpuData = TensorToolkit.flatten(gpuData);
var shapeGpuData = TensorToolkit.getShape(gpuData);
Tensor gpu_tensor = torch.tensor((float[]) flatGpuData).reshape(shapeGpuData).to(new Device(DeviceType.CUDA), ScalarType.Float);
Device device = gpu_tensor.device();
System.out.println("Default device: " + device);
} else {
System.out.println("CUDA not available. Using CPU.");
Device device = new Device(DeviceType.CPU);
System.out.println("Default device: " + device);
}
```
### 结合两者的优势
轻松地在 NumPy 数组和 PyTorch 张量之间转换的能力非常实用。你可能使用熟悉的 NumPy 函数或其他操作 NumPy 数组的库来执行初始数据加载和预处理。然后,当需要构建或训练深度学习模型时,你可以将数据转换为 PyTorch 张量,以借助于 GPU 加速和自动微分。同样,模型输出(即张量)可以转换回 NumPy 数组,以便使用 Matplotlib 或 Seaborn 等库进行分析或可视化。
理解这种关联和内存共享的含义,让你能够编写高效的代码,有效连接通用科学 Python 生态系统与 PyTorch 提供的专门深度学习能力。
# 动手实践:环境配置与张量基本操作
这些实践练习将提供PyTorch安装和基本张量对象的实践经验。它们将巩固对环境配置和执行基本张量操作的理解,这些都是使用PyTorch构建任何深度学习模型的前提条件。
### 验证你的PyTorch安装
首先,让我们确保PyTorch已正确安装并在你的Python环境中可用。打开你的Python解释器或Jupyter Notebook并运行以下命令:
```scala 3
import torch.*
import numpy as np
// 之后我们将用NumPy进行比较
// 打印PyTorch版本
println(f"PyTorch Version: {torch.__version__}")
// 检查CUDA(GPU支持)是否可用
if torch.cuda.is_available() then
println(f"CUDA is available. Device: {torch.cuda.get_device_name(0)}")
// 获取PyTorch将使用的默认设备
val device = torch.device("cuda")
else
println("CUDA not available. Using CPU.")
val device = torch.device("cpu")
println(f"Default device: {device}")
```
执行这段代码可以确认`torch`库能够被导入并显示其版本。它还会检查GPU的可用性,这对于后续章节中加速计算很重要。如果你有兼容的NVIDIA GPU并且安装了正确的PyTorch版本,应该会看到CUDA被报告为可用。目前,我们将主要使用CPU,但知道如何检查GPU也很重要。
### 创建张量
让我们练习使用之前介绍的多种方法创建张量。
**1. 从Python列表创建:** 从一个嵌套的Python列表创建一个2x3的张量。
```scala 3
// 从Python列表创建张量
val data = Seq(Seq(1, 2, 3), Seq(4, 5, 6))
val tensor_from_list = torch.tensor(data)
println("从列表创建的张量:")
println(tensor_from_list)
println(f"形状: {tensor_from_list.shape}")
println(f"数据类型: {tensor_from_list.dtype}") // 通常默认为int64
```
```java
int[][] data = {{1, 2, 3}, {4, 5, 6}};
var flatDataInt = TensorToolkit.flatten(data);
var shapeDataInt = TensorToolkit.getShape(data);
Tensor tensor_from_list2 = torch.tensor((int[]) flatDataInt).reshape(shapeDataInt
System.out.println("从数组创建的张量:");
print(tensor_from_list2);
System.out.println("形状: " + tensor_from_list2.sizes());
System.out.println("数据类型: " + tensor_from_list2.dtype()); // 通常默认为int64
```
**2. 指定数据类型:** 创建相同的张量,但显式地将其数据类型设置为32位浮点数。
```scala 3
// 创建指定数据类型的张量
val tensor_float32 = torch.tensor(data, dtype=torch.float32)
println("\nfloat32数据类型的张量:")
println(tensor_float32)
println(f"形状: {tensor_float32.shape}")
println(f"数据类型: {tensor_float32.dtype}")
```
```java
Tensor tensor_float32 = torch.tensor(new IntPointer(convertAnyDimIntArrayToBuffer(data))).to(ScalarType.Float);
System.out.println("\nfloat32数据类型的张量:");
print(tensor_float32);
System.out.println("形状: " + tensor_float32.sizes());
System.out.println("数据类型: " + tensor_float32.dtype()); // 现在是float32
public static IntBuffer convertAnyDimIntArrayToBuffer(Object multiDimArray) {
// 1. 递归展平任意维度数组
List<Integer> flatList = new ArrayList<>();
flattenIntArray(multiDimArray, flatList);
// 2. 转换为一维 int 数组
int[] flatArray = new int[flatList.size()];
for (int i = 0; i < flatList.size(); i++) {
flatArray[i] = flatList.get(i);
}
// 3. 创建直接内存 IntBuffer
return ByteBuffer
.allocateDirect(flatArray.length * Integer.BYTES)
.order(ByteOrder.nativeOrder())
.asIntBuffer()
.put(flatArray);
}
```
注意`dtype`的变化以及数字的表示方式(例如,`1.` 而不是 `1`)。
**3. 使用工厂函数:** 创建具有特定形状和初始值的张量。
```scala 3
// 创建一个3x4的全零张量
val zeros_tensor = torch.zeros(3, 4)
println("\n全零张量 (3x4):")
println(zeros_tensor)
// 创建一个2x2的全一张量,类型为整数
val ones_tensor_int = torch.ones(2, 2, dtype=torch.int32)
println("\n全一张量 (2x2, int32):")
println(ones_tensor_int)
// 创建一个表示数字范围的一维张量
val range_tensor = torch.arange(start=0, end=5, step=1) // 类似于Python的range函数
println("\n范围张量 (0到4):")
println(range_tensor)
// 创建一个包含随机值(0到1均匀分布)的2x3张量
val rand_tensor = torch.rand(2, 3)
println("\n随机张量 (2x3):")
println(rand_tensor)
```
```java
Tensor zeros_tensor2 = torch.zeros(3, 4);
System.out.println("\n全零张量 (3x4):");
print(zeros_tensor2);
// 创建一个2x2的全一张量,类型为整数
Tensor ones_tensor_int2 = torch.ones(2, 2).to(ScalarType.Int);
System.out.println("\n全一张量 (2x2, int32):");
print(ones_tensor_int2);
// 创建一个表示数字范围的一维张量
Tensor range_tensor = torch.arange(new Scalar(0), new Scalar(5), new Scalar(1)); // 类似于Python的range函数
System.out.println("\n范围张量 (0到4):");
print(range_tensor);
// 创建一个包含随机值(0到1均匀分布)的2x3张量
Tensor rand_tensor21 = torch.rand(2, 3);
System.out.println("\n随机张量 (2x3):");
print(rand_tensor21);
```
```java
// 创建3x4的全零张量
Tensor zeros_tensor2 = torch.zeros(3, 4);
System.out.println("===== 原有示例:全零张量 (3x4) =====");
print(zeros_tensor2);
// 创建2x2的全一张量(整数类型)
Tensor ones_tensor_int2 = torch.ones(2, 2).to(ScalarType.Int);
System.out.println("\n===== 原有示例:全一张量 (2x2, int32) =====");
print(ones_tensor_int2);
// 创建范围张量(0到4,步长1)
Tensor range_tensor = torch.arange(new Scalar(0), new Scalar(5), new Scalar(1));
System.out.println("\n===== 原有示例:范围张量 (0到4) =====");
print(range_tensor);
// 创建2x3的均匀分布随机张量(0到1)
Tensor rand_tensor21 = torch.rand(2, 3);
System.out.println("\n===== 原有示例:随机张量 (2x3) =====");
print(rand_tensor21);
// ========== 新增示例:eye ==========
// 1. 基础用法:创建3x3的单位矩阵(对角线为1,其余为0)
Tensor eye_tensor1 = torch.eye(3);
System.out.println("\n===== 新增示例:eye (3x3单位矩阵) =====");
print(eye_tensor1);
// 2. 扩展用法:创建3x5的矩阵(对角线为1,其余为0)
Tensor eye_tensor2 = torch.eye(3, 5);
System.out.println("\n===== 新增示例:eye (3x5矩阵,对角线为1) =====");
print(eye_tensor2);
// 3. 指定数据类型:创建4x4的单位矩阵(int64类型)
Tensor eye_tensor3 = torch.eye(4).to(ScalarType.Long);
System.out.println("\n===== 新增示例:eye (4x4, int64) =====");
print(eye_tensor3);
// ========== 新增示例:linspace ==========
// 1. 基础用法:从0到10,生成5个等间距的数(闭区间)
Tensor linspace_tensor1 = torch.linspace(new Scalar(0), new Scalar(10), 5);
System.out.println("\n===== 新增示例:linspace (0到10,5个点) =====");
print(linspace_tensor1);
// 2. 指定数据类型:从-5到5,生成11个等间距的浮点数(float32)
Tensor linspace_tensor2 = torch.linspace(new Scalar(-5), new Scalar(5), 11).to(ScalarType.Float);
System.out.println("\n===== 新增示例:linspace (-5到5,11个点, float32) =====");
print(linspace_tensor2);
// 3. 禁用端点:从2到8,生成4个点(不包含8,仅javacpp-pytorch 1.10+支持)
Tensor linspace_tensor3 = torch.linspace(new Scalar(2), new Scalar(8), 4, false);
System.out.println("\n===== 新增示例:linspace (2到8,4个点, 不含端点) =====");
print(linspace_tensor3);
// ========== 新增示例:randint ==========
// 1. 基础用法:生成0到9之间的随机整数,形状为2x3
Tensor randint_tensor1 = torch.randint(new Scalar(0), new Scalar(10), new long[]{2, 3});
System.out.println("\n===== 新增示例:randint (0-9,2x3) =====");
print(randint_tensor1);
// 2. 指定数据类型:生成10到20之间的int32随机整数,形状为3x3
Tensor randint_tensor2 = torch.randint(new Scalar(10), new Scalar(21), new long[]{3, 3},
torch.tensorOptions().dtype(ScalarType.Int));
System.out.println("\n===== 新增示例:randint (10-20,3x3, int32) =====");
print(randint_tensor2);
// 3. 一维张量:生成-5到5之间的long类型随机整数,形状为5
Tensor randint_tensor3 = torch.randint(new Scalar(-5), new Scalar(6), new long[]{5},
torch.tensorOptions().dtype(ScalarType.Long));
System.out.println("\n===== 新增示例:randint (-5到5,一维5个, int64) =====");
print(randint_tensor3);
// ========== 新增示例:randn ==========
// 1. 基础用法:生成标准正态分布(均值0,方差1)的随机数,形状为2x4
Tensor randn_tensor1 = torch.randn(2, 4);
System.out.println("\n===== 新增示例:randn (标准正态分布,2x4) =====");
print(randn_tensor1);
// 2. 指定数据类型:生成标准正态分布的float32类型,形状为3x2
Tensor randn_tensor2 = torch.randn(3, 2).to(ScalarType.Float);
System.out.println("\n===== 新增示例:randn (float32,3x2) =====");
print(randn_tensor2);
// 3. 批量生成:生成标准正态分布的三维张量(2x3x2)
Tensor randn_tensor3 = torch.randn(new long[]{2, 3, 2});
System.out.println("\n===== 新增示例:randn (三维张量 2x3x2) =====");
print(randn_tensor3);
```
这些工厂函数便于初始化张量,无需使用像列表这样的预先存在的数据结构。
### 张量基本操作
现在,让我们对已创建的张量执行一些基本操作。
```scala 3
// 使用之前创建的float32张量
val a = torch.tensor(Seq(Seq(1, 2), Seq(3, 4)), dtype=torch.float32)
val b = torch.ones(2, 2) // 默认为float32
println("张量 'a':")
println(a)
println("张量 'b':")
println(b)
// 元素级加法
val sum_tensor = a + b
// 另一种写法:sum_tensor = torch.add(a, b)
println("\n元素级和 (a + b):")
println(sum_tensor)
// 元素级乘法
val prod_tensor = a * b
// 另一种写法:prod_tensor = torch.mul(a, b)
println("\n元素级积 (a * b):")
println(prod_tensor)
// 标量乘法
val scalar_mult = a * 3
println("\n标量乘法 (a * 3):")
println(scalar_mult)
// 就地加法(修改张量'a')
println(f"\n就地加法前 'a':ID {id(a)}")
a.add_(b) // 注意就地操作的下划线后缀
println("就地加法后 'a' (a.add_(b)):")
println(a)
println(f"就地加法后 'a':ID {id(a)}") // ID保持不变
// 矩阵乘法
// 确保维度兼容矩阵乘法
// 让我们创建兼容的张量:x (2x3), y (3x2)
val x = torch.rand(2, 3)
val y = torch.rand(3, 2)
val matmul_result = torch.matmul(x, y)
// 另一种写法:matmul_result = x @ y
println("\n矩阵乘法 (x @ y):")
println(f"张量 x 的形状: {x.shape}, 张量 y 的形状: {y.shape}")
println(f"结果形状: {matmul_result.shape}")
println(matmul_result)
```
```java
var data3 = new int[][]{{1, 2}, {3, 4}};
var flatData3 = TensorToolkit.flatten(data3);
var shapeData3 = TensorToolkit.getShape(data3);
Tensor a4 = torch.tensor((int[]) flatData3).reshape(shapeData3).to(ScalarType.Float);
Tensor b4 = torch.ones(2, 2); // 默认为float32
System.out.println("张量 'a4':");
print(a4);
System.out.println("张量 'b4':");
print(b4);
// 元素级加法
Tensor sum_tensor2 = torch.add(a4, b4);
System.out.println("\n元素级和 (a4 + b4):");
print(sum_tensor2);
// 元素级乘法
Tensor prod_tensor = torch.mul(a4, b4);
System.out.println("\n元素级积 (a4 * b4):");
print(prod_tensor);
// 标量乘法
Tensor scalar_mult = torch.mul(a4, new Scalar(3));
System.out.println("\n标量乘法 (a4 * 3):");
print(scalar_mult);
// 就地加法(修改张量'a')
System.out.println("\n就地加法前 'a4':");
print(a4);
a4.add_(b4); // 注意就地操作的下划线后缀
System.out.println("就地加法后 'a4' (a4.add_(b4)):");
print(a4);
// 矩阵乘法
// 确保维度兼容矩阵乘法
// 让我们创建兼容的张量:x (2x3), y (3x2)
Tensor x2 = torch.rand(2, 3);
Tensor y = torch.rand(3, 2);
Tensor matmul_result = torch.matmul(x2, y);
System.out.println("\n矩阵乘法 (x @ y):");
System.out.println("张量 x2 的形状: " + x2.sizes() + ", 张量 y 的形状: " + y.sizes());
System.out.println("结果形状: " + matmul_result.sizes());
print(matmul_result);
```
请注意元素级操作(如`+`、`*`)与矩阵乘法(`torch.matmul`或`@`)之间的区别。此外,还要注意就地操作(如`add_`)如何直接修改张量,而不创建新对象。
### 与NumPy的交互
PyTorch与NumPy良好配合。让我们练习在NumPy数组和PyTorch张量之间进行转换。
```scala 3
// 1. NumPy数组到PyTorch张量
val numpy_array = np.array([[1.0, 2.0], [3.0, 4.0]])
println("\nNumPy数组:")
println(numpy_array)
println(f"类型: {type(numpy_array)}")
// 转换为PyTorch张量
val tensor_from_numpy = torch.from_numpy(numpy_array)
println("\n从NumPy数组创建的张量:")
println(tensor_from_numpy)
println(f"类型: {type(tensor_from_numpy)}")
// 重要提示:在CPU上,torch.from_numpy与NumPy数组共享内存
// 修改其中一个会影响另一个
numpy_array(0, 0) = 99.0
println("\n修改后的NumPy数组:")
println(numpy_array)
println("修改NumPy数组后的张量(共享内存):")
println(tensor_from_numpy)
// 2. PyTorch张量到NumPy数组
// 让我们使用不同的张量,以避免之前的修改
val another_tensor = torch.tensor(Seq(Seq(5, 6), Seq(7, 8)), dtype=torch.float64)
println("\n另一个PyTorch张量:")
println(another_tensor)
// 转换为NumPy数组
val numpy_from_tensor = another_tensor.numpy()
println("\n从张量创建的NumPy数组:")
println(numpy_from_tensor)
println(f"类型: {type(numpy_from_tensor)}")
// 同样,在CPU上内存是共享的
another_tensor(1, 1) = 100.0
println("\n修改后的张量:")
println(another_tensor)
println("修改张量后的NumPy数组(共享内存):")
println(numpy_from_tensor)
```
NumPy数组和CPU张量之间的这种内存共享行为效率高,但需要谨慎处理,因为可能会发生意外修改。如果你需要一个独立的副本,可以在转换之前使用张量的`.clone()`方法,或者使用标准的Python/NumPy复制机制。
本实践环节涵盖了验证你的配置、以多种方式创建张量、执行基本的算术和矩阵操作,以及在PyTorch张量和NumPy数组之间进行转换。掌握这些基础知识很重要,因为我们将在后续章节转向自动微分和构建神经网络模块等更复杂的主题。