### 一、前言
Pytorch 中有一个 Unfold 算子,算是"卷积"算子的第一步,即 "只卷不积"。
```python
torch.nn.Unfold(kernel_size, dilation=1, padding=0, stride=1)
```
目的在于按 "kernel_size, dilation, padding, stride" 选取出目标区域,以便做后续计算。
### 二、Unfold 执行效果
假设我们有一个下述二维 Tensor:
```python
x = Tensor([[1, 2, 5, 6],
[3, 4, 7, 8],
[9, 10, 13, 14],
[11, 12, 15, 16]
])
```
对它做 (kernel_size=2, dilation=1, padding=0, stride=2) 的 Unfold 操作时,总共可以得到4块区域:

这 4 块区域会按如下方式放置:
每个块的左上角数据,放在第一行;右上角数据放在第二行;左下角数据放在第三行;右下角数据放在第四行。这就是 Unfold 操作得到的最终结果。
```python
[[ 1. 5. 9. 13.]
[ 2. 6. 10. 14.]
[ 3. 7. 11. 15.]
[ 4. 8. 12. 16.]]
```
如果 kernel_size =(x, y),意味着每块区域有 x\*y 个数据,每行有 x 个数据,每列有 y 个数据。Unfold 计算完成后得到的结果应该有 x\*y 行, 每行的数据个数将由于 stride 参数的不同而不同。可自行扩展思考。
### 三、用 numpy 造轮子
既然已经知道 Unfold 的计算原理和最终结果了,造一个轮子把它复现一遍就是理所应当的了。
首先确认一点:已知 **输入数据** 和 Unfold 的 **参数** 时,我们是可以计算出最终结果的 **shape** 的。
```python
x2 = np.zeros((4, 4)) # unfold 结果
tx1 = x[::2].reshape(-1,2) # 偶数行数据
tx2 = x[1::2].reshape(-1,2) # 奇数行数据
for i in range(2):
x2[i] = tx1[:,i]
for i in range(2):
x2[i+2]=tx2[:,i]
```
针对上面的 Tensor **x** , 对于 "(kernel_size=2, dilation=1, padding=0, stride=2)" 的 Unfold 操作,我们可以事先计算出 Unfold 完的结果大小为 **4x4** ,然后我们需要把 **第偶数行中的第偶数个数据** 放在最终结果的第一行;**第偶数行中的第奇数个数据** 放在最终结果的第二行;**第奇数行中的第偶数个数据** 放在最终结果的第三行;**第奇数行中的第奇数个数据** 放在最终结果的第四行; 【时刻记住数组下标从0开始】
这样我们就把最普通的 2 维 2x2 Unfold 完成了。
更进一步的,我们需要考虑 **NCHW** 格式的数据,如果仍然是做 2 维 Unfold ,则 **NC** 维可以先不考虑,在外面套两层循环就可以了。然后根据 **kernel_size** 和 **stride** 选取 **HW** 中的区域,选取方式前面已经说过了。
3 维和更高维的我暂时还没探索过,不过 **区域选取** 肯定差不多,我们需要关注的是选取完的区域应该怎么摆放而已。
后面我再想想办法看能不能规避掉 for 循环的操作。

造轮子之Pytorch的unfold算子解析