CAM을 그리는 코드인데 bakward함수가 없어서 내 문제 해결할 수 있을 것 같음
(test시에 gradient를 계산해야 하는 상황.. torch.no_grad를 써야 gpu메모리 문제없이 학습가능해서)
cam
def cam(model, cam_topk=1, images=None, labels=None, truncate=False, shift=0.0):
if images is not None:
_ = model.forward(images)
if labels is None:
_, labels = torch.topk(
model.pred, k=cam_topk, dim=1, largest=True, sorted=True)
labels = labels[:, [cam_topk-1]]
labels = labels.squeeze()
last_layer = model.fc if model.last_layer == 'fc' else model.conv
_, score_map = model.avgpool(
model.feature_map, last_layer, truncate=truncate, shift=shift)
cams = batched_index_select(score_map, 1, labels)
return cams, labels
다행히 코드 길이는 굉장히 짧다.
그런데 이게 swin transformer feature map에 대해서도 효과가 있을지는 모르겠다.
일단 출력해보고 bounding box를 쳐보자 (좌표 값을 가져오자)
def cam(model, cam_topk=1, images=None, labels=None, truncate=False, shift=0.0):
model : CAM을 계산할 신경망 모델
cam_topk : CAM을 계산할 때 고려할 클래스, 몇개의 top predicted 클래스에 대해 CAM을 계산할 것인지 (default = 1)
images : 모델에 들어가는 입력 이미지.
labels : gt label. top-k 예측 클래스가 아니라, gt라벨에 대해 CAM을 구하고 싶을 때 입력 (default = None)
truncate : score map 값이 음수이면 0으로 세팅할 건지에 대한 여부 (이 경우 CAM시각화 향상, default=False)
shift : score map 값을 shift할건지에 대한 float value (default = 0.0)
if images is not None:
이미지가 주어지면
_ = model.forward(images)
모델에 이미지를 넣어 forward pass
if labels is None:
라벨이 주어지지 않았다면
_, labels = torch.topk(model.pred, k=cam_topk, dim=1, largest=True, sorted=True)
모델의 예측값으로 label을 세팅
labels = labels[:, [cam_topk-1]]
cam_topk에 따라 top-k label을 추출
labels = labels.squeeze()
dimension 축소
last_layer = model.fc if model.last_layer == 'fc' else model.conv
마지막 fc층을 다옴
_, score_map = model.avgpool(model.feature_map, last_layer, truncate=truncate, shift=shift)
feature map을 avgpooling하여 score map을 얻음
cams = batched_index_select(score_map, 1, labels)
score map을 인덱싱하여 cam을 얻는다.
결국 이 score map을 얻는게 관건...
score map을 계산하는 모듈은 avgpool 에 있다
ThresholdedAvgPool2D
class ThresholdedAvgPool2d(nn.Module):
def __init__(self, threshold=0.0):
super(ThresholdedAvgPool2d, self).__init__()
self.threshold = threshold
def forward(self, feature_map, layer, truncate=False, shift=0.0, bias=True):
# threshold feature map
batch_size, channel, height, width = feature_map.shape
max_vals, _ = torch.max(feature_map.view(batch_size, channel, -1), dim=2)
thr_vals = (max_vals * self.threshold).view(batch_size, channel, 1, 1).expand_as(feature_map)
thr_feature_map = torch.where(
torch.gt(feature_map, thr_vals), feature_map, torch.zeros_like(feature_map))
# divided by the number of positives
num_positives = torch.sum(torch.gt(thr_feature_map, 0.), dim=(2,3))
num_positives = torch.where(torch.eq(num_positives, 0),
torch.ones_like(num_positives),
num_positives).view(batch_size, channel, 1, 1).expand_as(feature_map)
avg_feature_map = torch.div(thr_feature_map, num_positives.float())
# convolve
#weight = layer.weight + compute_shift(shift, layer.weight)
weight = layer.weight
if truncate:
weight = torch.where(torch.gt(layer.weight, 0.),
layer.weight, torch.zeros_like(layer.weight))
if len(weight.shape) < 4:
weight = weight.unsqueeze(-1).unsqueeze(-1)
avgpooled_map = nn.functional.conv2d(
avg_feature_map, weight=weight, bias=None)
pred = torch.sum(avgpooled_map, dim=(2,3))
score_map = nn.functional.conv2d(
feature_map, weight=weight, bias=None)
if bias:
pred = pred + layer.bias.view(1, -1)
return pred, score_map
forward 부분만 보면 될듯
def forward(self, feature_map, layer, truncate=False, shift=0.0, bias=True):
feature_map : input feature map, 특정 층의 활성화를 표현하는 텐서, 주로 최종 컨볼루션층 중 하나
layer : 컨볼루션 연산에 사용될 가중치 (주로 fc나 컨볼루션 층)
truncate : 위와 같음
shift : 위와 같음
bias : 최종 예측에서 레이어에 bias를 추가할건지 여부 (default : True)
batch_size, channel, height, width = feature_map.shape
max_vals, _ = torch.max(feature_map.view(batch_size, channel, -1), dim=2)
thr_vals = (max_vals * self.threshold).view(batch_size, channel, 1, 1).expand_as(feature_map)
feature map의 최대값에 따라 threshold 값을 계산한다.
최댓값에 self.threshold(비율)만큼 곱한 값을 feature map 차원에 맞게 확장
thr_feature_map = torch.where(
torch.gt(feature_map, thr_vals), feature_map, torch.zeros_like(feature_map))
이제 위에서 계산한 threshold를 적용한 feature map을 생성
num_positives = torch.sum(torch.gt(thr_feature_map, 0.), dim=(2,3))
threshold를 적용한 feature map에서 양수값의 수를 얻는다.
num_positives = torch.where(torch.eq(num_positives, 0),
torch.ones_like(num_positives),
num_positives).view(batch_size, channel, 1, 1).expand_as(feature_map)
avg_feature_map = torch.div(thr_feature_map, num_positives.float())
threshold를 적용한 feature map을 양수의 개수로 나눠 avrage feature map을 계산한다
weight = layer.weight
if truncate:
weight = torch.where(torch.gt(layer.weight, 0.),
layer.weight, torch.zeros_like(layer.weight))
if len(weight.shape) < 4:
weight = weight.unsqueeze(-1).unsqueeze(-1)
truncat, shift파라미터에 따라 가중치를 수정
avgpooled_map = nn.functional.conv2d(
avg_feature_map, weight=weight, bias=None)
수정된 가중치를 이용하여 average feature map에 대한 컨볼루션 적용
pred = torch.sum(avgpooled_map, dim=(2,3))
average pooled map에 대해 sum을 적용하여 예측을 계산한다.
score_map = nn.functional.conv2d(
feature_map, weight=weight, bias=None)
수정된 가중치로 기존의 feature map에 대해 convoluion 연산 적용
nn.functional.conv2d
그럼 이 conv2d는 기존의 nn.conv2d와 뭐가 다를까?
가장 큰 차이는 weight다.
nn.functional.conv2d은 입력으로 input, weight를 받거나, 다른 filter을 사용하여 weight를 따로 만들어 다른 weight를 적용함
그러니까 nn.conv2d는 weight를 직접사용해주지 않는데, nn.functional.conv2d는 weight를 직접 선언 (즉, 외부에서 만든 filter를 사용)