Run this notebook online: or Colab:

# 6.2. 图像的卷积¶

## 6.2.1. 互关联算子¶

Fig. 6.2.1 Two-dimensional cross-correlation operation. The shaded portions are the first output element and the input and kernel array elements used in its computation: $$0\times0+1\times1+3\times2+4\times3=19$$.

(6.2.1)$\begin{split}0\times0+1\times1+3\times2+4\times3=19,\\ 1\times0+2\times1+4\times2+5\times3=25,\\ 3\times0+4\times1+6\times2+7\times3=37,\\ 4\times0+5\times1+7\times2+8\times3=43.\end{split}$

%load ../utils/djl-imports

public NDArray corr2d(NDArray X, NDArray K){
// 计算二维互关联。
int h = (int) K.getShape().get(0);
int w = (int) K.getShape().get(1);

NDArray Y = manager.zeros(new Shape(X.getShape().get(0) - h + 1, X.getShape().get(1) - w + 1));

for(int i=0; i < Y.getShape().get(0); i++){
for(int j=0; j < Y.getShape().get(1); j++){
Y.set(new NDIndex(i + "," + j), X.get(i + ":" + (i+h) + "," + j + ":" + (j+w)).mul(K).sum());
}
}

return Y;
}


NDManager manager = NDManager.newBaseManager();
NDArray X = manager.create(new float[]{0,1,2,3,4,5,6,7,8}, new Shape(3,3));
NDArray K = manager.create(new float[]{0,1,2,3}, new Shape(2,2));
System.out.println(corr2d(X, K));

ND: (2, 2) gpu(0) float32
[[19., 25.],
[37., 43.],
]


## 6.2.2. 卷积层¶

public class ConvolutionalLayer{

private NDArray w;
private NDArray b;

public NDArray getW(){
return w;
}

public NDArray getB(){
return b;
}

public ConvolutionalLayer(Shape shape){
NDManager manager = NDManager.newBaseManager();
w = manager.create(shape);
b = manager.randomNormal(new Shape(1));
}

public NDArray forward(NDArray X){
}

}


## 6.2.3. 图像中的目标边缘检测¶

X = manager.ones(new Shape(6,8));
X.set(new NDIndex(":" + "," + 2 + ":" + 6), 0f);
System.out.println(X);

ND: (6, 8) gpu(0) float32
[[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.],
]


K = manager.create(new float[]{1, -1}, new Shape(1,2));


NDArray Y = corr2d(X, K);
Y

ND: (6, 7) gpu(0) float32
[[ 0.,  1.,  0.,  0.,  0., -1.,  0.],
[ 0.,  1.,  0.,  0.,  0., -1.,  0.],
[ 0.,  1.,  0.,  0.,  0., -1.,  0.],
[ 0.,  1.,  0.,  0.,  0., -1.,  0.],
[ 0.,  1.,  0.,  0.,  0., -1.,  0.],
[ 0.,  1.,  0.,  0.,  0., -1.,  0.],
]


corr2d(X.transpose(), K);

ND: (8, 5) gpu(0) float32
[[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
]


## 6.2.4. 学习内核¶

X = X.reshape(1,1,6,8);
Y = Y.reshape(1,1,6,7);

Loss l2Loss = Loss.l2Loss();

// 构造一个具有1个输出通道和一个
// 形核（1，2）。为了简单起见，我们忽略了这里的偏见
Block block = Conv2d.builder()
.setKernelShape(new Shape(1, 2))
.optBias(false)
.setFilters(1)
.build();

block.setInitializer(new NormalInitializer(), Parameter.Type.WEIGHT);
block.initialize(manager, DataType.FLOAT32, X.getShape());

// 二维卷积层使用四维输入和输出
// 输出格式为（例如，通道、高度、宽度），其中批次
// 大小（批次中的示例数）和通道数均为1

ParameterList params = block.getParameters();
NDArray wParam = params.get(0).getValue().getArray();

NDArray lossVal = null;
ParameterStore parameterStore = new ParameterStore(manager, false);

NDArray lossVal = null;

for (int i = 0; i < 10; i++) {

NDArray yHat = block.forward(parameterStore, new NDList(X), true).singletonOrThrow();
NDArray l = l2Loss.evaluate(new NDList(Y), new NDList(yHat));
lossVal = l;
gc.backward(l);
}
// 更新内核

if((i+1)%2 == 0){
System.out.println("batch " + (i+1) + " loss: " + lossVal.sum().getFloat());
}
}

batch 2 loss: 0.12571818
batch 4 loss: 0.09935227
batch 6 loss: 0.07851635
batch 8 loss: 0.062050212
batch 10 loss: 0.049037326


ParameterList params = block.getParameters();
NDArray wParam = params.get(0).getValue().getArray();
wParam

weight: (1, 1, 1, 2) gpu(0) float32 hasGradient
[[[[ 0.4475, -0.4477],
],
],
]


## 6.2.6. 总结¶

• 二维卷积层的核心计算是二维互相关运算。在最简单的形式中，它对二维输入数据和内核执行互相关操作，然后添加偏差。

• 我们可以设计一个内核来检测图像中的边缘。

• 我们可以从数据中学习内核的参数。

## 6.2.7. 练习¶

1. 构造一个带有对角边的图像X

• 如果对其应用内核K，会发生什么？

• 如果变换X顺序，会发生什么？

• 如果变换K顺序，会发生什么？

2. 当您尝试自动查找我们创建的 Conv2d 类的渐变时，您会看到什么样的错误消息？

3. 如何通过更改输入和内核数组将互相关运算表示为矩阵乘法？

4. 手动设计一些内核。

• 二阶导数的核的形式是什么？

• 拉普拉斯算子的核心是什么？

• 积分的核心是什么？

• 获得$$d$$导数的内核最小大小是多少？