跳到主要内容

张量创建

1. 将 Rust 向量转换为 RSTSR 张量

1.1 从 Rust 向量创建 1-D 张量

RSTSR 张量可以通过 (拥有所有权的) 向量对象创建。

在以下情况下,向量对象 vec 的内存将转移到张量对象 tensor1。除了相对较小的开销 (生成张量的 layout) 外,不会发生显式的数据复制

// move ownership of vec to 1-D tensor (default CPU device)
let vec = vec![1.0, 2.968, 3.789, 4.35, 5.575];
let tensor = rt::asarray(vec);

// only print 2 decimal places
println!("{:.2}", tensor);
// output: [ 1.00 2.97 3.79 4.35 5.58]

1.2 从 Rust 向量创建 nn-D 张量

对于 nn-D 张量,推荐从现有向量创建而不进行显式内存复制的方法是:

  • 首先,从连续的内存创建 1-D 张量;
  • 其次,将其重塑为您所需的 nn-D 张量;
// generate 2-D tensor from 1-D vec, without explicit data copy
let vec = vec![1, 2, 3, 4, 5, 6];
let tensor = rt::asarray(vec).into_shape_assume_contig([2, 3]);
println!("{:}", tensor);

// if you feel function `into_shape_assume_contig` ugly, following code also works
let vec = vec![1, 2, 3, 4, 5, 6];
let tensor = rt::asarray(vec).into_shape([2, 3]);
println!("{:}", tensor);

// and even more concise
let vec = vec![1, 2, 3, 4, 5, 6];
let tensor = rt::asarray((vec, [2, 3]));
println!("{:}", tensor);

// output:
// [[ 1 2 3]
// [ 4 5 6]]

我们不推荐从嵌套向量 (即 Vec<Vec<T>>) 生成 nn-D 张量。在这种情况下,无论如何都会发生显式的内存复制。因此,对于嵌套向量,您可能希望首先生成一个扁平的 Vec<T>,然后对其进行重塑:

let vec = vec![vec![1, 2, 3], vec![4, 5, 6]];

// generate 2-D tensor from nested Vec<T>, WITH EXPLICIT DATA COPY
// so this is not recommended for large data
let (nrow, ncol) = (vec.len(), vec[0].len());
let vec = vec.into_iter().flatten().collect::<Vec<_>>();

// please also note that nested vec is always row-major,
// so using `.c()` to give row-major layout is more appropriate
let tensor = rt::asarray((vec, [nrow, ncol].c()));
println!("{:}", tensor);
// output:
// [[ 1 2 3]
// [ 4 5 6]]

2. 将 Rust 切片转换为 RSTSR TensorView

Rust 语言对变量的所有权非常敏感,这与 Python 不同。在 Rust 中,数据的连续内存引用通常表示为切片 &[T]。对于 RSTSR,这由 TensorView 存储2

// generate 1-D tensor view from &[T], without data copy
let vec = vec![1, 2, 3, 4, 5, 6];
let tensor = rt::asarray(&vec);

// note `tensor` is TensorView instead of Tensor, so it doesn't own data
println!("{:?}", tensor);

// check if pointer of vec and tensor's storage are the same
assert_eq!(vec.as_ptr(), tensor.storage().raw().as_ptr());

// output:
// === Debug Tensor Print ===
// [ 1 2 3 4 5 6]
// DeviceFaer { base: DeviceCpuRayon { num_threads: 0 } }
// 1-Dim (dyn), contiguous: CcFf
// shape: [6], stride: [1], offset: 0
// Type: rstsr_core::tensorbase::TensorBase<rstsr_core::tensor::data::DataRef<rstsr_core::storage::device::Storage<i32, rstsr_core::device_faer::device::DeviceFaer>>, [usize; 1]>

您还可以将可变切片 &mut [T] 转换为张量。对于 RSTSR,这由 TensorMut 存储:

// generate 2-D tensor mutable view from &mut [T], without data copy
let mut vec = vec![1, 2, 3, 4, 5, 6];
let mut tensor = rt::asarray((&mut vec, [2, 3]));

// you may perform arithmetic operations on `tensor`
tensor *= 2;
println!("{:}", tensor);
// output:
// [[ 2 4 6]
// [ 8 10 12]]

// you may also see variable `vec` is also changed
println!("{:?}", vec);
// output: [2, 4, 6, 8, 10, 12]

3. 内置的 RSTSR 张量创建函数

3.1 1-D 张量创建函数

最常用的 1-D 张量创建函数是 arangelinspace

arange 创建具有规则递增值的张量。以下代码展示了生成张量的多种方式3

let tensor = rt::arange(10);
println!("{:}", tensor);
// output: [ 0 1 2 ... 7 8 9]

let device = DeviceFaer::new(4);
let tensor = rt::arange((2.0, 10.0, &device));
println!("{:}", tensor);
// output: [ 2 3 4 5 6 7 8 9]

let tensor = rt::arange((2.0, 3.0, 0.1));
println!("{:}", tensor);
// output: [ 2 2.1 2.2 ... 2.7000000000000006 2.8000000000000007 2.900000000000001]

linspace 将创建一个具有指定数量元素的张量,并在指定的起始值和结束值之间均匀分布。

use num::complex::c64;

let tensor = rt::linspace((0.0, 10.0, 11));
println!("{:}", tensor);
// output: [ 0 1 2 ... 8 9 10]

let tensor = rt::linspace((c64(1.0, 2.0), c64(-15.0, 10.0), 5, &DeviceFaer::new(4)));
println!("{:}", tensor);
// output: [ 1+2i -3+4i -7+6i -11+8i -15+10i]

3.2 2-D 张量创建函数

最常用的 2-D 张量创建函数是 eyediag

eye 生成单位矩阵。在许多情况下,您只需提供行数,eye(n_row) 将返回一个方阵单位矩阵,或者如果设备是关注点,则使用 eye((n_row, &device))。如果您希望生成具有偏移的矩形单位矩阵,可以调用 eye((n_row, n_col, offset))

let device = DeviceFaer::new(4);
let tensor: Tensor<f64> = rt::eye((3, &device));
println!("{:}", tensor);
// output:
// [[ 1 0 0]
// [ 0 1 0]
// [ 0 0 1]]

let tensor: Tensor<f64> = rt::eye((3, 4, -1));
println!("{:}", tensor);
// output:
// [[ 0 0 0 0]
// [ 1 0 0 0]
// [ 0 1 0 0]]

diag 从 1-D 张量生成对角 2-D 张量,或从 2-D 张量的对角线生成 1-D 张量。diag 被定义为重载函数;如果关注对角线的偏移,您可以调用 diag((&tensor, offset))

let vec = rt::arange(3) + 1;
let tensor = vec.diag();
println!("{:}", tensor);
// output:
// [[ 1 0 0]
// [ 0 2 0]
// [ 0 0 3]]

let tensor = rt::arange(9).into_shape([3, 3]);
let diag = tensor.diag();
println!("{:}", diag);
// output: [ 0 4 8]

3.3 通用的 nn-D 张量创建函数

最常用的 nn-D 张量创建函数是 zerosonesempty。这些函数可以构建具有任何所需 shape (或 layout) 的张量。

  • zeros 用零值填充张量;
  • ones 用一值填充张量;
  • 不安全的 empty 提供未初始化的张量;
  • fill 用用户提供的相同值填充张量;

我们将主要使用 zeros 作为示例。对于常见用法,您可能希望生成具有指定 shape (或额外绑定设备的张量):

// generate tensor with default device
let tensor: Tensor<f64> = rt::zeros([2, 2, 3]); // Tensor<f64, Ix3>
println!("{:}", tensor);
// output:
// [[[ 0 0 0]
// [ 0 0 0]]
//
// [[ 0 0 0]
// [ 0 0 0]]]

// generate tensor with custom device
// note: the third type annotation refers to device type, hence is required if not default device
// Tensor<f64, Ix2, DeviceCpuSerial>
let tensor: Tensor<f64, _> = rt::zeros(([3, 4], &DeviceCpuSerial));
println!("{:}", tensor);
// output:
// [[ 0 0 0 0]
// [ 0 0 0 0]
// [ 0 0 0 0]]

您还可以指定 layout:是 C 连续 (行优先) 还是 Fortran 连续 (列优先)4。在 RSTSR 中,属性函数 cf 用于生成 C/Fortran 连续 layout:

// generate tensor with c-contiguous
let tensor: Tensor<f64> = rt::zeros([2, 2, 3].c());
println!("shape: {:?}, stride: {:?}", tensor.shape(), tensor.stride());
// output: shape: [2, 2, 3], stride: [6, 3, 1]

// generate tensor with f-contiguous
let tensor: Tensor<f64> = rt::zeros([2, 2, 3].f());
println!("shape: {:?}, stride: {:?}", tensor.shape(), tensor.stride());
// output: shape: [2, 2, 3], stride: [1, 2, 4]

一个特殊的 nn-D 情况是 0-D 张量 (标量)。您也可以通过 zeros 生成 0-D 张量:

// generate 0-D tensor
let mut a: Tensor<f64> = rt::zeros([]);
println!("{:}", a);
// output: 0

// 0-D tensor arithmetics are also valid
a += 2.0;
println!("{:}", a);
// output: 2

let b = rt::arange(3.0) + 1.0;
let c = a + b;
println!("{:}", c);
// output: [ 3 4 5]

您还可以初始化一个不填充特定值的张量。这是不安全的。

// generate empty tensor with default device
let tensor: Tensor<i32> = unsafe { rt::empty([10, 10]) };
println!("{:?}", tensor);

该库尚未实现随机初始化的 API。但是,您仍然可以通过 asarray 执行此类任务。

use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};

// generate f-contiguous layout and it's memory buffer size
let layout = [2, 3].f();
let size = layout.size();

// generate random numbers to vector
let seed: u64 = 42;
let mut rng = StdRng::seed_from_u64(seed);
let random_vec: Vec<f64> = (0..size).map(|_| rng.gen()).collect();

// create a tensor from random vector and f-contiguous layout
let tensor = rt::asarray((random_vec, layout));

// print tensor with 3 decimal places with width of 7
println!("{:7.3}", tensor);

Footnotes

  1. 这将为默认的 CPU 设备生成张量对象。 在没有进一步配置的情况下,RSTSR 选择 DeviceFaer 作为默认的张量设备,并使用所有对 rayon 可见的线程。 如果您对其他设备感兴趣 (例如单线程的 DeviceCpuSerial),或者您希望限制 DeviceFaer 的线程数,则可以使用另一个版本的 asarray。 例如,要限制计算时使用 4 个线程,您可以通过以下代码初始化张量:

    // move ownership of vec to 1-D tensor
    // custom CPU device that limits threads to 4
    let vec = vec![1, 2, 3, 4, 5];
    let device = DeviceFaer::new(4);
    let tensor = rt::asarray((vec, &device));
    println!("{:?}", tensor);

    // output:
    // === Debug Tensor Print ===
    // [ 1 2 3 4 5]
    // DeviceFaer { base: DeviceCpuRayon { num_threads: 4 } }
    // 1-Dim (dyn), contiguous: CcFf
    // shape: [5], stride: [1], offset: 0
    // Type: rstsr_core::tensorbase::TensorBase<rstsr_core::tensor::data::DataOwned<rstsr_core::storage::device::Storage<i32, rstsr_core::device_faer::device::DeviceFaer>>, [usize; 1]>
  2. 通过 Rust 切片 &[T] 初始化 TensorView 是通过 ManuallyDrop 内部实现的。对于科学计算通常关注的数据类型 T (例如 f64Complex<f64>),它不会导致内存泄漏。然而,如果类型 T 有自己的析构函数 (drop 函数),您可能需要仔细检查内存泄漏的安全性。这也适用于通过可变 Rust 切片 &mut [T] 初始化的 TensorMut

  3. 许多 RSTSR 函数,尤其是张量创建函数,是签名重载的。输入应通过元组传递多个函数参数。

  4. https://en.wikipedia.org/wiki/Row-_and_column-major_order