3주차 학습 정리

1) 강의 복습 내용

강의는 PyTorch 과제를 수행하며 필요한 내용 위주로 학습하면서 들었습니다. 강의 자체에서 새로운 내용을 알게 되었다기 보다는 기존에 혼자 학습하면서 익혔던 기능들을 다시 훑게되니 새롭게 PyTorch를 바라보게 되어서 좋았습니다. Tensorflow의 compile을 해야 하는 static graph와 자동으로 input_features를 계산해주는 기능에 익숙해져서, 기존에 PyTorch는 잘 사용하지 않아서 "하수" 수준에 불과했습니다. 그러나 이번 주 강의를 통해 "하수"는 벗어난 것 같은 느낌을 받았습니다.

이번 주 강의는 별도로 정리하진 않았고, 아래 필수 과제 해결 과정을 설명하면서 주요 내용을 정리하겠습니다.

또한, 지난주 최성준 교수님의 마스터 클래스 내용에, 더 공부한 내용을 첨가해서 uncertainty modeling에 관한 내용을 정리해봤습니다.

Uncertainty Modeling - To Know What We Do Not Know (최성준 교수님)

2) 과제 수행 과정 및 결과물

필수 과제

이번주 필수 과제는 총 2개가 주어졌습니다.

첫번째 과제는 PyTorch model building에 관한 것으로, 다양한 custom model 개발을 위한 기법들이 소개되었습니다! PyTorch를 조금 써봤다고 자부했으나... 정말 새로운 기능들이 많다는 것을 매번 깨달으며, 겸손해지는 한 주였습니다...🥲 특히 gather() 함수나 기존의 모델을 변형시키는 것은 쉽지 않았습니다. Python class의 attribute를 확인하고 새로 할당할 수 있는 hasattr() 함수와 setattr() 함수, 그리고 partial()의 활용법까지 익히는 것은 쉽지 않았습니다.

하지만 이를 통해서 기존의 pre-trained 모델이나 외부에서 구현된 모델을 직접 변경할 때, 어떤 식으로 접근해야 할지를 알 수 있었습니다. 특히 nn.Module의 sub-class로 모듈을 구현하면, 이를 다른 모듈 내에서 불러와서 중첩시킬 수 있는 것은 PyTorch의 가장 큰 장점이라고 생각합니다. 물론, 순환 참조와 같이 고려해야 할 사항이 몇 존재하지만, ResNet, VGG16 등과 같은 반복적인 모델을 구현할 때 도움될 것 같습니다.

복잡한 모델을 구현할 때 indexing, slicing을 구현하는 것은 필수적입니다. 이를 구현하는 방법은 대표적으로 .index_select(dim, index)를 사용하거나 .gather(dim, index)를 사용하는 것입니다. 특히 .gather()는 훨씬 더 복잡한 수준의 indexing을 구현할 수 있어서, 원하는 특정 원소들만 볼 수 있도록 하는 것이 가능합니다. .gather() 함수를 이해하는 가장 원시적이면서도 쉬운 방법은 프로그래밍 for 루프에서 흔히 사용되는 ijk를 떠올리는 것입니다.

A = torch.Tensor([[[1, 2],
									 [3, 4]],

									[[5, 6],
									 [7, 8]]])

위와 같이 2x2x2 크기의 3차원 Tensor가 존재할 때, .gather() 함수 사용은 아래와 같습니다.

idx = torch.tensor([[[1],
										 [0]],

										[[0],
										 [1]]])

res = A.gather(dim = 0, idx)

일단, dim 인자로는 기준이 되는 차원이 들어갑니다. 그리고 index 인자료 만약, dim = 0 일 때, .gather() 함수를 for 루프로 구현하면 아래와 같습니다.

idx = torch.tensor([[[1],
										 [0]],

										[[0],
										 [1]]])

res = torch.empty_like(idx)

for i in range(idx.shape[0]):
	for j in range(idx.shape[1]):
		for k in range(idx.shape[2]):
			res[i][j][k] = A[idx[i][j][k]][j][k]

즉, i에 관해서 index가 idx 라는 Tensor로 주어지게 되고, 나머지 jk는 일반적인 for loop가 돌게 됩니다. input Tensor와 output Tensor의 차원수는 동일해야 합니다. 그리고 outputindex Tensor와 동일한 shape을 갖게 됩니다. 마지막으로 모든 차원에 대해서 index.size(d)input.size(d)보다 클 수는 없습니다. 참고적으로 PyTorch에서는 모든 index로 받는 Tensor에 대해 torch.LongTensor 타입일 것을 의무적으로 규정하고 있습니다.

따라서 이를 응용하여 3차원 Tensor의 대각 원소만을 반환하는 함수를 구현하면 아래와 같습니다.

def get_diag_elems_3D(A):
	dim     = 2
	C, H, W = A.shape
	to      = min(H, W)
	
	idx = torch.arange(0, to).unsqueeze(1).expand(C, -1, -1)
	out = A.gather(dim, idx).view(C, to)
	
	return out