Lab2-向量化计算
Lab2-向量化计算
实验简介
1 | Numpy 代码一般采用向量化(矢量化)描述,这使得代码中没有任何显式的循环,索引等,这样的代码有以下好处: |
环境配置
- 安装 Python 3.11.4 并添加到环境变量
- 使用
pip install NumPy
命令安装 NumPy
基准代码
1 | def bilinear_interp_baseline(a: np.ndarray, b: np.ndarray) -> np.ndarray: |
尝试去除两重 for 循环
代码实现
由于 i 和 j 的对称性,我希望将两个一起向量化
1 | def bilinear_interp_vectorized(a: np.ndarray, b: np.ndarray) -> np.ndarray: |
思路分析
初始化数据
对
b[n]
进行切片得到x
和y
数组,再将x
和y
数组强转为整数得到x_idx
和y_idx
,通过向量加减可以直接得到_x
和_y
,这里与三重循环时没有明显差异。进行分块化
对于基准代码中的式子,我们可以将其写成矩阵的形式
1
2
3
4res[n, i, j] = a[n, x_idx, y_idx] * (1 - _x) * (1 - _y) + \
a[n, x_idx + 1, y_idx] * _x * (1 - _y) + \
a[n, x_idx, y_idx + 1] * (1 - _x) * _y + \
a[n, x_idx + 1, y_idx + 1] * _x * _y\[ \begin{bmatrix} 1-\_\rm{y} & \_\rm{y} \end{bmatrix} \begin{bmatrix} \rm{a}[\rm{n}, \rm{x\_idx}, \rm{y\_idx}] & \rm{a}[\rm{n}, \rm{x\_idx} + 1, \rm{y\_idx}] \\ \rm{a}[\rm{n}, \rm{x\_idx}, \rm{y\_idx} + 1] & \rm{a}[\rm{n}, \rm{x\_idx} + 1, \rm{y\_idx} + 1] \end{bmatrix} \begin{bmatrix} 1-\_\rm{x} \\ \_\rm{x} \end{bmatrix} \]
这分别是代码中的 A、B、C 三个矩阵。不难发现,通过向量
*
运算,可以使得每个位置上得到对应的结果,最终将四个分块矩阵相加就能得到答案。为了方便下一步的广播运算,我将 A、C 矩阵改写了一下。
\[ \rm{A} = \begin{bmatrix} 1-\_\rm{y} & 1-\_\rm{y} \\ \_\rm{y} & \_\rm{y} \end{bmatrix} \quad \rm{C} = \begin{bmatrix} 1-\_\rm{x} & \_\rm{x} \\ 1-\_\rm{x} & \_\rm{x} \end{bmatrix} \]
矩阵计算
通过广播机制进行向量的
*
运算,我们首先需要通过transpose
函数对矩阵进行转置,使其符合广播运算的形式,做完运算后再将其恢复成原状。最后将分块矩阵的四个块通过向量加法相加就能得到答案。
输出结果
最终完成向量化实现
- 代码实现
1 | def bilinear_interp_vectorized(a: np.ndarray, b: np.ndarray) -> np.ndarray: |
思路分析
大体思路与去除两重 for 循环时相同,唯一需要注意的点是对于数组索引 B 的处理。
B.shape
输出(2,2,N1,N1,H2,W2,C)
, 但是我们需要的是(2,2,N1,H2,W2,C)
, 多出一维的原因是没有处理第一个N1
与第二个N1
之间的约束关系。也就是说,在a[:, x_idx, y_idx, :]
这个式子中,当 a 数组第一维取 0 的时候, 索引数组x_idx
和y_idx
的第一维也相应要取定成 0 。因此这里可以通过创建一个对角布尔数组再进行一次索引来解决这个问题。