P Stage 2 : Project Day 11
Day 11 list
- VASP 논문 발표
- RecVAE 학습 pipeline 설계 및 리팩토링
시간이 늦어서 좀 간단하게 적고 가겠습니다.
VASP 논문 발표
- MovieLens에서 좋은 성능을 보이고 있는 VASP 논문 발표를 진행함
- 준비가 좀 덜 된 상태에서 발표하다보니 약간 아쉬움이 존재했음
- 좀 더 자세한 내용은 현재 포스팅으로 작성중
- 발표자표 링크
RecVAE 학습 pipeline 설계 및 리팩토링
- 지난 Multi-VAE와 마찬가지로 학습 파이프라인 구축이 덜 된 모델을 파이프라인 형태로 설계했음
- 해당 과정에서 일부 pandas 버전이 맞지 않는 부분에 대해서도 리팩토링을 진행함
- VAE 계열의 모델은 preprocessing 과정이 동일해서 1개의 전처리 코드로 통일하는 것이 좋아보임
- RecVAE에 대한 이해가 없는 상태에서 일단 기존 코드를 재가공했는데, Recall@10이 초반에 확 컸다가 끝으로 갈수록 내려가는 이상한 현상이 나타남
- 모델의 인코더와 디코더를 학습하는 과정이 존재하는데 이 과정에서 모델 자체가 global로 유지되야 할 것 같음
- 근데 파이썬은 모델을 인자로 넘겨줘도 유지가 될텐데…
- 내일 팀원한테 물어보거나 모델 코드 원본이나 논문을 좀 읽어볼 필요가 있을듯
- 리팩토링 과정에서 코드적인 부분에서 몇가지 의문점이 드는데 간단히 작성해두자
torch.zeros
VS torch.Tensor
후 data.fill_(0)
RecVAE에서 CompositePrior
의 구현부에서 원본 코드는 다음과 같이 작성되어 있었습니다.
1
2
3
4
5
6
7
8
self.mu_prior = nn.Parameter(torch.Tensor(1, latent_dim), requires_grad=False)
self.mu_prior.data.fill_(0)
self.logvar_prior = nn.Parameter(torch.zeros(1, latent_dim), requires_grad=False)
self.logvar_prior.data.fill_(0)
self.logvar_uniform_prior = nn.Parameter(torch.Tensor(1, latent_dim), requires_grad=False)
self.logvar_uniform_prior.data.fill_(10)
이 부분에서 왜 굳이 torch.zeros
를 안쓰고 torch.Tensor
를 활용하고 0으로 초기화하는지 이해가 가지 않았습니다. 큰 영향을 주진 않을 거 같지만 좀 더 좋은 코드를 위해 아래와 같이 수정했습니다.
1
2
3
4
self.mu_prior = nn.Parameter(torch.zeros(1, latent_dim), requires_grad=False)
self.logvar_prior = nn.Parameter(torch.zeros(1, latent_dim), requires_grad=False)
self.logvar_uniform_prior = nn.Parameter(torch.Tensor(1, latent_dim), requires_grad=False)
self.logvar_uniform_prior.data.fill_(10)
반복문을 활용한 코드의 압축은 항상 좋을까?
이 부분은 사실 미관상 너무 길고 반복된 코드를 좋아하진 않는 편이라 진행한 코드 수정과정입니다. 우선 원본 코드부터 보여드리겠습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Encoder(nn.Module):
def __init__(self, hidden_dim, latent_dim, input_dim, eps=1e-1):
super(Encoder, self).__init__()
self.fc1 = nn.Linear(input_dim, hidden_dim)
self.ln1 = nn.LayerNorm(hidden_dim, eps=eps)
self.fc2 = nn.Linear(hidden_dim, hidden_dim)
self.ln2 = nn.LayerNorm(hidden_dim, eps=eps)
self.fc3 = nn.Linear(hidden_dim, hidden_dim)
self.ln3 = nn.LayerNorm(hidden_dim, eps=eps)
self.fc4 = nn.Linear(hidden_dim, hidden_dim)
self.ln4 = nn.LayerNorm(hidden_dim, eps=eps)
self.fc5 = nn.Linear(hidden_dim, hidden_dim)
self.ln5 = nn.LayerNorm(hidden_dim, eps=eps)
self.fc_mu = nn.Linear(hidden_dim, latent_dim)
self.fc_logvar = nn.Linear(hidden_dim, latent_dim)
RecVAE에서 Encoder 부분의 레이어 선언부입니다. 입력에 연결되는 self.fc1
과 self.ln1
을 제외하고 나머지 레이어 세트 (fcn
, lnn
)은 반복되는 형태를 띄고 있습니다. 따라서 적은 수의 반복문을 돌기 때문에 코드 간소화를 하고자 nn.ModuleDict
를 활용하여 다음과 같이 수정했습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Encoder(nn.Module):
def __init__(self, hidden_dim, latent_dim, input_dim, eps=1e-1):
super(Encoder, self).__init__()
self.encoder = nn.ModuleDict()
# original code is too long
for i in range(0, 5):
if i == 0:
self.encoder[f"fc{i+1}"] = nn.Linear(input_dim, hidden_dim)
self.encoder[f"ln{i+1}"] = nn.LayerNorm(hidden_dim, eps=eps)
else:
self.encoder[f"fc{i+1}"] = nn.Linear(hidden_dim, hidden_dim)
self.encoder[f"ln{i+1}"] = nn.LayerNorm(hidden_dim, eps=eps)
self.fc_mu = nn.Linear(hidden_dim, latent_dim)
self.fc_logvar = nn.Linear(hidden_dim, latent_dim)
근데 이렇게 수정하고 문득 들은 생각은 과연 반복문으로 코드를 압축한게 좋은 코드일까?라는 생각이 들었습니다. 우스갯소리로 ‘1~10까지 출력하는 코드를 짤 때 반복문을 쓰는 것도 좋지만 때로는 그냥 출력문 10개 적는게 나을 수도 있다’는 말도 있는데 이게 이런 상황에서 적용되는게 아닐까라는 생각이 들긴했습니다.
막 엄청 성능에 영향을 미치지는 않을 것으로 보이나 과연 다른 사람이 봤을때 이 코드는 원본 코드보다 Encoder module의 구조를 잘 알 수 있을까? 라는 생각이 들기는 합니다.
이번주 할 것 (Rough)
- EASE 모델 코드 확인해보기
- AutoRec 구현 및 대회에 적용해보기
- NGCF 최적화 작업 시도해보기
- 앙상블 아이디어 고려하기
- 모델 관점보다 데이터적인 관점을 좀 더 바라볼 필요가 있을듯
- 추천 모델을 본격적으로 다뤄보다보니 데이터를 너무 간과하고 있었음
- EDA를 처음부터 다시 하나하나 구상해볼 필요가 있을듯
Comments powered by Disqus.