forward에서 세 종류의 delta가 만들어진다
먼저 입력을 출력으로 복사하고 delta를 0으로 초기화한다.
1
2
3
4
| memcpy(l.output, net.input,
l.outputs*l.batch*sizeof(float));
memset(l.delta, 0,
l.outputs*l.batch*sizeof(float));
|
별도 activation은 이 함수 안에 없다. 따라서 net.input이 어떤 범위인지 알려면 바로 앞 layer까지 확인해야 한다.
1. 배경 기본값
모든 픽셀은 우선 어떤 클래스에도 속하지 않는다고 가정한다.
1
2
3
4
5
6
| for(i = 0; i < l.classes; ++i){
for(k = 0; k < l.w*l.h; ++k){
int index = b*l.outputs + i*l.w*l.h + k;
l.delta[index] = 0 - l.output[index];
}
}
|
임베딩도 기본적으로 작은 값을 향하게 하지만 클래스 delta보다 0.1배로 시작한다.
1
2
3
| int index = b*l.outputs
+ (i+l.classes)*l.w*l.h + k;
l.delta[index] = .1 * (0 - l.output[index]);
|
2. mask 안의 클래스와 평균 임베딩
truth mask 값 v가 0이 아니면 해당 클래스 채널의 목표를 v로 바꾸고, 그 픽셀의 임베딩을 인스턴스별 sums[i]에 더한다.
1
2
3
4
5
6
7
8
| if(v){
l.delta[index] = v - l.output[index];
axpy_cpu(ids, 1,
l.output + b*l.outputs
+ l.classes*l.w*l.h + k,
l.w*l.h, l.sums[i], 1);
++l.counts[i];
}
|
axpy_cpu의 입력 stride가 w*h이므로 같은 픽셀 k에서 임베딩 채널만 건너가며 더한다. 이후 픽셀 수로 나눠 인스턴스의 평균 임베딩을 만든다.
1
2
| scal_cpu(ids, 1.f/l.counts[i],
l.sums[i], 1);
|
중간의 mse 배열은 각 mask 픽셀과 해당 인스턴스 평균 사이의 제곱 거리를 계산해 출력하는 진단값이다. 최종 l.cost에는 이 배열을 직접 합하지 않고, 완성된 l.delta의 크기를 사용한다.
3. 같은 인스턴스는 당기고 다른 인스턴스는 민다
mask 픽셀마다 현재 인스턴스 i와 batch에 존재하는 모든 인스턴스 j를 비교한다.
1
2
3
4
5
| float diff = l.sums[j][z] - l.output[index];
if(j == i) l.delta[index] +=
diff < 0 ? -.1 : .1;
else l.delta[index] +=
-(diff < 0 ? -.1 : .1);
|
같은 인스턴스 평균 쪽으로는 부호 방향의 delta를 더하고, 다른 인스턴스 평균에는 반대 방향을 더한다. 마지막에는 모든 임베딩 delta를 다시 0.01배 한다.
즉, 클래스 오차와 임베딩 오차의 크기는 코드에 박힌 .1, .01 계수의 영향을 받는다. 결과를 해석할 때 이 scale을 빼놓으면 두 항의 기여를 잘못 비교하게 된다.