在这里插入图片描述

PyTorch CPP 高校计算机硕士研一课程

张量介绍

**张量(Tensor)**是 PyTorch 中将要使用的主要数据结构。如果你使用过 NumPy,你会发现 PyTorch 张量非常熟悉。从本质上讲,张量是多维数组,与 NumPy 的 ndarray 非常相似。

可以把张量看作是常见数学对象的推广:

  • 一个标量(单个数字,例如 7)是 0 维张量(或 0 阶张量)。
  • 一个向量(一维数字列表或数组,例如 [1, 2, 3])是 1 维张量(或 1 阶张量)。
  • 一个矩阵(二维数字网格,例如 [[1, 2], [3, 4]])是 2 维张量(或 2 阶张量)。
  • 依此类推,可以有 3 维张量(例如数字立方体,常用于 RGB 图像:高 x 宽 x 通道)、4 维张量(常用于图像批次:批大小 x 高 x 宽 x 通道)等。

标量 (7)0维张量向量 [1, 2, 3]1维张量增加维度矩阵 [[1, 2], [3, 4]]2维张量增加维度3维张量(例如:图像 高x宽x通道)增加维度高维张量(例如:批次 批大小x高x宽x通道)…

一种将张量看作标量、向量和矩阵的推广的方式,维度逐渐增加。

在深度学习中,张量用来表示几乎所有数据:

  • 输入数据: 图像批次、文本序列或特征表格。
  • 模型参数: 神经网络层的权重和偏置。
  • 中间激活: 网络内部各层的输出。
  • 梯度: 反向传播过程中计算的值,用于更新模型参数。

与标准 Python 列表甚至 NumPy 数组相比,是什么让 PyTorch 张量特别适合深度学习呢?

  1. GPU 加速: 张量可以轻松地移动到图形处理器 (GPU) 或其他硬件加速器上进行处理。这为深度学习中常见的计算密集型操作提供了大幅加速。
  2. 自动微分: PyTorch 张量通过 Autograd 系统内置了对自动微分的支持(我们将在第 3 章中介绍)。这种机制自动计算梯度,这对通过反向传播训练神经网络来说非常重要。

尽管其原理与 NumPy 数组相似,但这两个特性使得 PyTorch 张量成为高效构建和训练模型的主要工具。在接下来的部分中,我们将介绍如何创建和操作这些重要的数据结构。

创建张量

张量是 PyTorch 中的基本数据结构,代表深度学习模型中使用的多维数组。代码中提供了创建张量的实用方法。PyTorch 提供了一系列灵活的函数来创建张量,无论使用现有数据,还是需要特定形状和初始值。

从现有数据创建张量

创建张量最直接的方法通常是使用现有的 Python 数据结构,比如列表或 NumPy 数组。主要用于此目的的函数是 torch.tensor()。这个函数会根据输入数据自动判断数据类型 (dtype),但您也可以明确指定。重要的是,torch.tensor() 总是复制输入数据。

我们来看看实际操作。首先,请确保已导入 PyTorch:

import torch.*
import numpy as np

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.bytedeco.javacpp.*;
import org.bytedeco.pytorch.*;
import org.bytedeco.pytorch.global.torch;
import org.bytedeco.pytorch.global.torch_cuda;
import static org.bytedeco.pytorch.global.torch.*;

现在,从 Python 列表创建一个张量:

// 从 Scala 列表创建张量
val data_list = Seq(Seq(1, 2), Seq(3, 4))
val tensor_from_list = torch.tensor(data_list)

println("从列表生成的张量:")
println(tensor_from_list)
println(s"数据类型: ${tensor_from_list.dtype}")
println(s"形状: ${tensor_from_list.shape}")
     float[][] dataList = {{1, 2}, {3, 4}};

            // 2. 计算数组总长度和形状
            int rows = dataList.length;
            int cols = dataList[0].length;
            int totalElements = rows * cols;

            // 3. 将二维数组展平为一维数组(PyTorch 张量底层是连续内存)
            float[] flatData = new float[totalElements];
            int index = 0;
            for (float[] floats : dataList) {
                for (int j = 0; j < cols; j++) {
                    flatData[index++] = floats[j];
                }
            }
            // 4. 将一维数组转换为 javacpp FloatPointer(PyTorch Java 绑定的核心类型)  *flatdata
            FloatPointer floatPointer = new FloatPointer(flatData);

            // 5. 定义张量形状(rows × cols)
            SizeTPointer shape1 = new SizeTPointer(new long[]{rows, cols});
//            TensorOptions options = new TensorOptions().dtype(kFloat).device(kCPU);
            var tensorOptions = new TensorOptions() //tensorBuilder
                    .layout(new LayoutOptional(Layout.Strided))
                    .dtype(new ScalarTypeOptional(ScalarType.Float))
                    .device(new DeviceOptional(new Device(DeviceType.CPU)))
                    .memory_format(new MemoryFormatOptional(MemoryFormat.Contiguous));
            // 6. 创建张量(指定数据、形状、数据类型、设备)
            Tensor tensor = torch.tensor(floatPointer, tensorOptions).reshape(new LongArrayRef());
            




        // 创建一个简单的张量
        auto x = torch::rand({2, 3});
        std::cout << "成功创建了一个张量:\n" << x << std::endl;

        std::vector<std::vector<float>> dataList{{1.f, 2.f}, {3.f, 4.f}};
        int rows = static_cast<int>(dataList.size());
        int cols = static_cast<int>(dataList[0].size());
        int totalElements = rows * cols;

        std::vector<float> flatData(totalElements);
        int index = 0;
        for (const auto& row : dataList) {
            for (float value : row) {
                flatData[index++] = value;
            }
        }

        auto tensorOptions = torch::TensorOptions()
                                  .layout(torch::kStrided)
                                  .dtype(torch::kFloat32)
                                  .device(torch::kCPU)
                                  .memory_format(torch::MemoryFormat::Contiguous);
        std::vector<int64_t> shape1{rows, cols};
        auto tensor = torch::from_blob(flatData.data(), shape1, tensorOptions).clone();

        auto flattened = tensor_toolkit::flatten(dataList);
        auto tensor_from_list = torch::tensor(flattened, torch::dtype(torch::kFloat32)).reshape(shape1);
        std::cout << "从数组生成的张量:\n" << tensor_from_list << std::endl;

这将输出:

从列表生成的张量:
tensor([[1, 2],
        [3, 4]])
数据类型: torch.int64
形状: torch.Size([2, 2])

请注意 PyTorch 如何根据输入整数判断数据类型为 torch.int64(一个 64 位整数)。

您也可以从 NumPy 数组开始,获得相同的结果:

// 从 NumPy 数组创建张量
val data_numpy = np.array(Seq(Seq(5.0, 6.0), Seq(7.0, 8.0)))
val tensor_from_numpy = torch.tensor(data_numpy)

println("\n从 NumPy 数组生成的张量:")
println(tensor_from_numpy)
println(s"数据类型: ${tensor_from_numpy.dtype}")
println(s"形状: ${tensor_from_numpy.shape}")
        std::vector<float> data_array{5.f, 6.f, 7.f, 8.f};
        auto data_tensor = torch::tensor(data_array);
        std::cout << data_tensor << std::endl;

输出:

从 NumPy 数组生成的张量:
tensor([[5., 6.],
        [7., 8.]], dtype=torch.float64)
数据类型: torch.float64
形状: torch.Size([2, 2])
        std::vector<int64_t> flatDataLong{1, 2, 3, 4};
        auto longTensor = torch::from_blob(flatDataLong.data(), std::vector<int64_t>{2, 2}, torch::dtype(torch::kInt64)).clone();
        std::cout << "从数组生成的 long 类型张量:\n" << longTensor << std::endl;


       auto rand_tensor = torch::rand({4, 3});
        std::cout << rand_tensor << std::endl;
        std::cout << "\n从数组生成的张量:\n" << rand_tensor << std::endl;
        std::cout << "数据类型: " << rand_tensor.dtype() << std::endl;
        std::cout << "形状: " << sizesToString(rand_tensor.sizes()) << std::endl;

        auto tensor_from_array = torch::tensor(data_array);
        std::cout << "\n从数组创建的张量:\n" << tensor_from_array << std::endl;
        std::cout << "数据类型: " << tensor_from_array.dtype() << std::endl;
        std::cout << "形状: " << sizesToString(tensor_from_array.sizes()) << std::endl;

     // 如果是 long 类型
            long[] flatDataLong = {1, 2, 3, 4};
            LongPointer longPtr = new LongPointer(flatDataLong);
            var tensorOptions2 = new TensorOptions()
                    .layout(new LayoutOptional(Layout.Strided))
                    .dtype(new ScalarTypeOptional(ScalarType.Long))
                    .device(new DeviceOptional(new Device(DeviceType.CPU)))
                    .memory_format(new MemoryFormatOptional(MemoryFormat.Contiguous));
// 注意:Java long 对应 C++ int64 (kLong)
            Tensor longTensor = torch.from_blob(longPtr, new long[]{2, 2}, tensorOptions2).clone();
            System.out.println("从数组生成的 long 类型张量:" + longTensor);
         

为了方便输出 我们写一个 展平数组的工具

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;

public class TensorToolkit {

    // Get Multidimensional array Shape
    public static long[] getShape(Object array) {
        if (array == null || !array.getClass().isArray()) return new long[0];
        List<Long> shape = new ArrayList<>();
        Object current = array;
        while (current != null && current.getClass().isArray()) {
            int len = Array.getLength(current);
            shape.add((long) len);
            current = (len > 0) ? Array.get(current, 0) : null;
        }
        return shape.stream().mapToLong(i -> i).toArray();
    }

    // flatten Multidimensional array
    public static Object flatten(Object multiDimArray) {
        if (multiDimArray == null) return null;
        Class<?> componentType = multiDimArray.getClass();
        while (componentType.isArray()) componentType = componentType.getComponentType();

        int size = 1;
        for (long d : getShape(multiDimArray)) size *= d;

        Object result = Array.newInstance(componentType, size);
        fill(multiDimArray, result, new int[]{0});
        return result;
    }

    private static void fill(Object src, Object dest, int[] idx) {
        int len = Array.getLength(src);
        for (int i = 0; i < len; i++) {
            Object el = Array.get(src, i);
            if (el != null && el.getClass().isArray()) fill(el, dest, idx);
            else Array.set(dest, idx[0]++, el);
        }
    }
}


这里,数据类型被判断为 torch.float64,因为 NumPy 数组包含浮点数。本章后面将讨论 PyTorch 张量与 NumPy 数组之间的关系,包括更有效的转换方法。

创建具有特定形状和值的张量

很多时候,您需要在没有预先数据的情况下创建特定大小的张量,例如用零、一或随机值进行初始化。PyTorch 为这些情况提供了专门的函数。形状通常作为元组或整数序列传递。

零张量、一张量或未初始化数据张量:

  • torch.zeros(*size, ...): 创建一个填充零的张量。
  • torch.ones(*size, ...): 创建一个填充一的张量。
  • torch.empty(*size, ...): 创建一个指定大小的张量,但其内容未被设定。其中的值是分配内存时该位置的任意数据。如果您计划在创建后立即填充张量,使用 torch.empty 会快一些,但请注意,初始值是不确定的。
// 定义期望的形状
val shape = Seq(2, 3) // 2 行, 3 列

// 创建具有特定值的张量
val zeros_tensor = torch.zeros(shape)
val ones_tensor = torch.ones(shape)
val empty_tensor = torch.empty(shape) // 值是任意的

println(s"\n零张量 (形状 {shape}):")
println(zeros_tensor)
println(s"\n一张量 (形状 {shape}):")
println(ones_tensor)

println(s"\n空张量 (形状 {shape}):")
println(empty_tensor)
   long[] shape = {2, 3}; // 2 行, 3 列

            // 创建具有特定值的张量
  Tensor zeros_tensor = torch.zeros(shape);
  Tensor ones_tensor = torch.ones(shape);
  Tensor empty_tensor = torch.empty(shape); // 值是任意的

  System.out.println("\n零张量 (形状 " + shape[0] + "x" + shape[1] + "):");
  print(zeros_tensor);
  System.out.println("\n一张量 (形状 " + shape[0] + "x" + shape[1] + "):");
  print(ones_tensor);
  System.out.println("\n空张量 (形状 " + shape[0] + "x" + shape[1] + "):");
  print(empty_tensor);

      std::vector<int64_t> shape{2, 3};
        auto zeros_tensor = torch::zeros(shape);
        auto ones_tensor = torch::ones(shape);
        auto empty_tensor = torch::empty(shape);
        std::cout << "\n零张量 (形状 " << shape[0] << "x" << shape[1] << "):\n" << zeros_tensor << std::endl;
        std::cout << "\n一张量 (形状 " << shape[0] << "x" << shape[1] << "):\n" << ones_tensor << std::endl;
        std::cout << "\n空张量 (形状 " << shape[0] << "x" << shape[1] << "):\n" << empty_tensor << std::endl;


输出(请注意 empty_tensor 的值会有所不同):

零张量 (形状 (2, 3)):
tensor([[0., 0., 0.],
        [0., 0., 0.]])

一张量 (形状 (2, 3)):
tensor([[1., 1., 1.],
        [1., 1., 1.]])

空张量 (形状 (2, 3)):
tensor([[2.1321e-38, 5.0837e+24, 3.0763e-41],
        [3.0763e-41, 1.2326e-32, 1.8750e+00]])

默认情况下,这些函数创建的数据类型为 dtype=torch.float32 的张量。您可以使用 dtype 参数指定不同的数据类型:

      auto int32Options = torch::TensorOptions().dtype(torch::kInt32);
        auto ones_int_tensor = torch::ones(shape, int32Options);
        std::cout << "\n一张量 (dtype=torch.int32):\n" << ones_int_tensor << std::endl;


// 创建一个整数类型的一张量
val ones_int_tensor = torch.ones(shape, dtype=torch.int32)
println(s"\n一张量 (dtype=torch.int32):")
println(ones_int_tensor)

   TensorOptions int32Options = new TensorOptions().dtype(new ScalarTypeOptional(ScalarType.Int))
   Tensor ones_int_tensor = torch.ones(shape, int32Options);// dtype_int32());
   System.out.println("\n一张量 (dtype=torch.int32):");
   print(ones_int_tensor);

输出:

一张量 (dtype=torch.int32):
tensor([[1, 1, 1],
        [1, 1, 1]], dtype=torch.int32)

带随机值的张量:

初始化模型参数通常会从随机值开始。PyTorch 为此提供了函数:

  • torch.rand(*size, ...): 创建一个从区间 [0, 1) 均匀采样的张量。
  • torch.randn(*size, ...): 创建一个从标准正态分布(均值 0,方差 1)采样的张量。
// 创建带随机值的张量
val rand_tensor = torch.rand(shape) // 均匀分布 [0, 1)
val randn_tensor = torch.randn(shape) // 标准正态分布

println(s"\n随机张量 (均匀分布 [0, 1), 形状 {shape}):")
println(rand_tensor)

println(s"\n随机张量 (标准正态分布, 形状 {shape}):")
println(randn_tensor)
      // 创建带随机值的张量
            Tensor rand_tensor2 = torch.rand(shape); // 均匀分布 [0, 1)
            Tensor randn_tensor = torch.randn(shape); // 标准正态分布

            System.out.println("\n随机张量 (均匀分布 [0, 1), 形状 " + shape[0] + "x" + shape[1] + "):");
            print(rand_tensor2);
            System.out.println("\n随机张量 (标准正态分布, 形状 " + shape[0] + "x" + shape[1] + "):");
            print(randn_tensor);

        auto rand_tensor2 = torch::rand(shape);
        auto randn_tensor = torch::randn(shape);
        std::cout << "\n随机张量 (均匀分布 [0, 1), 形状 " << shape[0] << "x" << shape[1] << "):\n" << rand_tensor2 << std::endl;
        std::cout << "\n随机张量 (标准正态分布, 形状 " << shape[0] << "x" << shape[1] << "):\n" << randn_tensor << std::endl;



输出(每次运行结果会有所不同):

随机张量 (均匀分布 [0, 1), 形状 (2, 3)):
tensor([[0.6580, 0.5089, 0.1642],
        [0.3742, 0.5989, 0.7775]])

随机张量 (标准正态分布, 形状 (2, 3)):
tensor([[-0.2651, -0.3249, -1.0134],
        [ 1.1314,  1.1751, -0.1411]])

基于其他张量创建张量

有时,您需要创建一个新张量,使其与现有张量具有相同的属性(如形状和 dtype)。这对于确保操作兼容性很有帮助。PyTorch 为此提供了 _like 变体函数:

  • torch.zeros_like(input_tensor, ...)
  • torch.ones_like(input_tensor, ...)
  • torch.rand_like(input_tensor, ...)
  • torch.randn_like(input_tensor, ...)

这些函数接受一个现有张量作为输入,并返回一个新张量,其内容为指定的值(零、一、随机),但会与输入张量的形状和 dtype 匹配,除非明确覆盖。

// 使用现有张量作为模板
val base_tensor = torch.tensor(Seq(Seq(1, 2), Seq(3, 4)), dtype=torch.float32)
println(s"\n基础张量 (形状 {base_tensor.shape}, dtype {base_tensor.dtype}):")
println(base_tensor)

// 创建与基础张量属性匹配的张量
val zeros_like_base = torch.zeros_like(base_tensor)
val rand_like_base = torch.rand_like(base_tensor)

println("\n类似基础张量的零张量:")
println(zeros_like_base)
println(s"形状: ${zeros_like_base.shape}, dtype: ${zeros_like_base.dtype}")

println("\n类似基础张量的随机张量:")
println(rand_like_base)
println(s"形状: ${rand_like_base.shape}, dtype: ${rand_like_base.dtype}")
  int [][] existing_array = {{1, 2}, {3,4}};
            var flatArray = TensorToolkit.flatten(existing_array);
            var shapes = TensorToolkit.getShape(existing_array);
            Tensor base_tensor = torch.tensor((int[]) flatArray).reshape(shapes).to(ScalarType.Float);
            System.out.println("\n基础张量 (形状 " + base_tensor.sizes() + ", dtype " + base_tensor.dtype() + "):");
            print(base_tensor);

            // 创建与基础张量属性匹配的张量
            Tensor zeros_like_base = torch.zeros_like(base_tensor);
            Tensor rand_like_base = torch.rand_like(base_tensor);

            System.out.println("\n类似基础张量的零张量:");
            print(zeros_like_base);
            System.out.println("形状: " + zeros_like_base.sizes() + ", dtype: " + zeros_like_base.dtype());

            System.out.println("\n类似基础张量的随机张量:");
            print(rand_like_base);
            System.out.println("形状: " + rand_like_base.sizes() + ", dtype: " + rand_like_base.dtype());


       std::vector<std::vector<int32_t>> existing_array{{1, 2}, {3, 4}};
        auto base_tensor = tensorFromNested(existing_array, torch::kInt32).to(torch::kFloat32);
        std::cout << "\n基础张量 (形状 " << sizesToString(base_tensor.sizes()) << ", dtype " << base_tensor.dtype() << "):\n" << base_tensor << std::endl;

        auto zeros_like_base = torch::zeros_like(base_tensor);
        auto rand_like_base = torch::rand_like(base_tensor);
        std::cout << "\n类似基础张量的零张量:\n" << zeros_like_base << std::endl;
        std::cout << "形状: " << sizesToString(zeros_like_base.sizes()) << ", dtype: " << zeros_like_base.dtype() << std::endl;
        std::cout << "\n类似基础张量的随机张量:\n" << rand_like_base << std::endl;
        std::cout << "形状: " << sizesToString(rand_like_base.sizes()) << ", dtype: " << rand_like_base.dtype() << std::endl;


private:
    template <typename Nested>
    static torch::Tensor tensorFromNested(const Nested& array, c10::ScalarType dtype) {
        auto flat = tensor_toolkit::flatten(array);
        auto shape = tensor_toolkit::getShape(array);
        auto tensor = torch::tensor(flat, torch::dtype(dtype));
        return tensor.reshape(shape);
    }

namespace tensor_toolkit {

template <typename T>
struct is_vector : std::false_type {};

template <typename T, typename Alloc>
struct is_vector<std::vector<T, Alloc>> : std::true_type {};

template <typename T>
struct base_type {
    using type = T;
};

template <typename T, typename Alloc>
struct base_type<std::vector<T, Alloc>> {
    using type = typename base_type<T>::type;
};

template <typename Nested, typename Scalar = typename base_type<Nested>::type>
void flattenImpl(const Nested& value, std::vector<Scalar>& out) {
    if constexpr (is_vector<Nested>::value) {
        for (const auto& elem : value) {
            flattenImpl(elem, out);
        }
    } else {
        out.push_back(value);
    }
}

template <typename Nested>
std::vector<typename base_type<Nested>::type> flatten(const Nested& value) {
    std::vector<typename base_type<Nested>::type> out;
    flattenImpl(value, out);
    return out;
}

template <typename Nested>
void shapeImpl(const Nested& value, std::vector<int64_t>& shape) {
    if constexpr (is_vector<Nested>::value) {
        shape.push_back(static_cast<int64_t>(value.size()));
        if (!value.empty()) {
            shapeImpl(value.front(), shape);
        }
    }
}

template <typename Nested>
std::vector<int64_t> getShape(const Nested& value) {
    std::vector<int64_t> shape;
    shapeImpl(value, shape);
    return shape;
}

} // namespace tensor_toolkit

输出:

基础张量 (形状 torch.Size([2, 2]), dtype torch.float32):
tensor([[1., 2.],
        [3., 4.]])

类似基础张量的零张量:
tensor([[0., 0.],
        [0., 0.]])
形状: torch.Size([2, 2]), dtype: torch.float32

类似基础张量的随机张量:
tensor([[0.1216, 0.5908],
        [0.3264, 0.9272]])
形状: torch.Size([2, 2]), dtype: torch.float32

如您所见,新张量 zeros_like_baserand_like_base 自动从 base_tensor 继承了 shape (torch.Size([2, 2])) 和 dtype (torch.float32)。

这些方法为您生成所需的张量提供了一套全面的方式,它们支撑着深度学习模型中的计算。在接下来的章节中,我们将了解如何操作这些张量并进行重要运算。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐