第二课 张量(Tensor)的设计
一个张量类主要由以下3部分组成:
- 数据本身,可以为
double,int,float等等。 - 张量的维度形状
shape - 张量类的类方法,如返回张量的宽度、高度、填充数据和张量变形等等。
张量类的设计
本项目选择在arma::fcube(三维矩阵)基础上进行开发。
对于一个Tensor类,我们的工作主要为以下两个:
- 提供对外接口,在
arma::fcube基础上进行提供。 - 封装矩阵相关的计算功能。
1 | template <> |
数据的摆放顺序
两种形式:行主序和列主序。
Tensor方法概述
创建张量
首先,在所有事情开始前,我们需要创建一个张量。在创建张量——这一多维矩阵的过程中,自然而然想到我们需要用一个raw_shapes变量来存储张量的维度,而不同维度将决定了arma::fcube的具体结构。我们需要根据输入的维度信息创建相应维度的arma::fcube,且创建一个用于存储维度的变量。
- 如果张量是1维的,则
raw_shapes的长度就等于1; - 如果张量是2维的,则
raw_shapes的长度就等于2,以此类推; - 在创建3维张量时,则
raw_shapes的长度为3;
==值得注意的是,如果当channel和rows同时等于1时,raw_shapes的长度也会是1,表示此时Tensor是一维的;而当channel等于1时,raw_shapes的长度等于2,表示此时Tensor是二维的。==
创建1维张量
1 | Tensor<float>::Tensor(uint32_t size) { |
创建2维张量
1 | Tensor<float>::Tensor(uint32_t rows, uint32_t cols) { |
创建3维张量
1 | Tensor<float>::Tensor(uint32_t channels, uint32_t rows, uint32_t cols) { |
返回张量的维度信息
1 | int32_t Tensor<float>::rows() const { |
获取张量中的数据
1 | const arma::fmat& Tensor<float>::slice(uint32_t channel) const { |
以上方法用于返回fcube变量中的第channel个矩阵。换句话说,一个fcube作为数据的实际存储者,由多个矩阵叠加而成。当我们调用slice方法时,它会返回其中的第channel个矩阵。
1 | float Tensor<float>::at(uint32_t channel, uint32_t row, uint32_t col) const { |
以上方法用于访问三维张量中第(channel, row, col)位置的对应数据。对于以下的Tensor,访问(1, 1, 1)位置的元素,会得到6。

张量的填充
1 | void Tensor<float>::Fill(const std::vector<float>& values, bool row_major) { |
如果函数中的row_major参数为true,则表示按照行优先的顺序填充元素;如果该参数为false,则将按照列优先的顺序填充元素。
对张量中的元素依次处理
1 | void Tensor<float>::Transform(const std::function<float(float)>& filter) { |
Transform方法依次将张量中每个元素进行处理,处理的公式如下: $x=transform(x)$
对张量进行变形
1 | void Tensor<float>::Reshape(const std::vector<uint32_t>& shapes, |
这是一个复合程度更高的函数,用到了我们在之前构建好的方法。如果我们想对张量的维度进行调整,我们自然需要获取前后的形状,比如张量原先的大小是(channel1, row1, col1), 再进行reshape之后我们将张量的大小调整为(channel2, row2, col2)。此外,我们还需要对内部的 data_ 的维度也进行调整,用于存放之后的数据;最后只需要将准备好的数据进行填充即可。
需要注意的是,在调整的过程中,前后的两组维度要满足以下的关系:
$(channel1\times row1 \times col1)=(channel2 \times row2 \times col2)$
张量类的辅助函数
判断张量符合是否为空
1 | bool Tensor<float>::empty() const { return this->data_.empty(); } |
返回张量数据存储区域的起始地址
1 | const float* Tensor<float>::raw_ptr() const { |
上文说到,张量类中数据存储由三维矩阵类(fcube)负责,所以在raw_ptr的目的就是返回数据存储的起始位置。
返回张量的shape
1 | const std::vector<uint32_t>& Tensor<float>::raw_shapes() const { |
返回张量的三维形状[channels, rows, cols].
- 如果
channels = 1并且rows = 1,则raw_shapes返回一维形状[cols],张量是一个一维张量。 - 如果
channels = 1,则raw_shapes返回二维形状[rows, cols],张量是一个二维张量。 - 否则表明该
Tensor就是一个三维张量,raw_shapes返回三维形状[channels, rows, cols].
练习
Flatten
编写Tensor::Flatten方法,将多维展开成一维。

观察函数声明和单元测试
1 | void Tensor<float>::Flatten(bool row_major) { |
方法实现,调用Reshape即可
1 | void Tensor<float>::Flatten(bool row_major) { |
Padding
编写Tensor::Padding函数,在张量周围做填充

观察函数声明和单元测试
1 | /** |
思路是先把保存原始数据,接着把data_变成对应填充后的shape,之后创建全新数组pad_values并全部填充上padding_value,然后把pad_values对应位置填上原始数据,最后调用Fill方法将pad_values填充入data_。
1 | void Tensor<float>::Padding(const std::vector<uint32_t>& pads, |
若没有本文 Issue,您可以使用 Comment 模版新建。