张量创建
1. 将 Rust 向量转换为 RSTSR 张量
1.1 从 Rust 向量创建 1-D 张量
RSTSR 张量可以通过 (拥有所有权的) 向量对象创建。
在以下情况下,向量对象 vec
的内存将转移到张量对象 tensor
1。除了相对较小的开销 (生成张量的 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 向量创建 -D 张量
对于 -D 张量,推荐从现有向量创建而不进行显式内存复制的方法是:
- 首先,从连续的内存创建 1-D 张量;
- 其次,将其重塑为您所需的 -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>>
) 生成 -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 张量创建函数是 arange
和 linspace
。
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 张量创建函数是 eye
和 diag
。
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 通用的 -D 张量创建函数
最常用的 -D 张量创建函数是 zeros
、ones
和 empty
。这些函数可以构建具有任何所需 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 中,属性函数 c
和 f
用于生成 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]
一个特殊的 -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
-
这将为默认的 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]> -
通过 Rust 切片
&[T]
初始化TensorView
是通过ManuallyDrop
内部实现的。对于科学计算通常关注的数据类型T
(例如f64
、Complex<f64>
),它不会导致内存泄漏。然而,如果类型T
有自己的析构函数 (drop
函数),您可能需要仔细检查内存泄漏的安全性。这也适用于通过可变 Rust 切片&mut [T]
初始化的TensorMut
。 ↩ -
许多 RSTSR 函数,尤其是张量创建函数,是签名重载的。输入应通过元组传递多个函数参数。 ↩