티스토리 뷰

- SASRec 모델을 변형해보는 식으로 미니 프로젝트를 진행했고, 그 중에서 나는 Position Embedding 방식을 고치는 역할을 맡았다! 

- Position Embedding 과 Positional Encoding 관련해서 연구하면서 척척학사정도 된 것 같은데 이 내용을 정리해보도록 하겠다.

 

원래 SASRec의 Position Embedding

Self-Attention에서는 위치정보를 알 수 있는 정보가 없기에 SASRec 모델에 학습가능한 Position Embedding Vector 더해줌!

처음에 maxlen 크기만큼 0~ maxlen까지 순차적인 값을 가지도록 설정되어 있는 걸 nn.Embedding을 이용해 임베딩 벡터로 바꿔주고 역전파를 통해서 이 임베딩 벡터가 갱신됨!

position 정보를 고정값이 아닌 parameter로서 가지면서 학습을 통해 포지션끼리의 거리 관계 정보를 유추하게 된다!

positions = np.tile(np.array(range(log_seqs.shape[1])), [log_seqs.shape[0], 1])
"""maxlen = 50일때, 다음과 같이 설정됨
        [[ 0  1  2 ... 47 48 49]
         [ 0  1  2 ... 47 48 49]
         [ 0  1  2 ... 47 48 49]
         ...
         [ 0  1  2 ... 47 48 49]
         [ 0  1  2 ... 47 48 49]
         [ 0  1  2 ... 47 48 49]]
        """
seqs += self.pos_emb(torch.LongTensor(positions).to(self.dev)) # position embedding - (batch_size * max_len *d) 크기, 아이템 임베딩이랑 더해짐
"""# cf) self.pos_emb = torch.nn.Embedding(args.maxlen, args.hidden_units) nn.Embedding에 태우면
[
  [ -0.51,  0.46, -0.15, -1.70,  0.47, -0.30, -1.86,  0.87 ],
  [ -1.00, -0.44,  1.08, -0.31,  0.00,  0.32,  0.89, -2.33 ],
  [ -0.41,  0.48,  1.15,  0.88,  0.70, -1.44, -1.20, -1.74 ],
  [  1.50,  1.86, -1.07,  0.61,  0.17,  0.28,  0.06, -0.65 ],
  [  0.32, -0.02,  0.64, -1.61, -0.11, -0.29, -0.54, -0.72 ],
  [ -0.26, -0.46,  0.01, -0.94, -2.22, -0.09,  0.39, -0.37 ],
  [ -0.19, -1.34, -0.83, -0.25,  0.20,  0.81, -1.66, -2.10 ],
  [  1.30, -0.79, -0.13,  0.23, -0.70, -0.15,  0.79,  1.04 ],
  [  0.06,  1.26,  0.69,  0.40,  1.16,  0.23, -1.68,  0.18 ],
  [  1.44, -0.81,  2.69,  0.28, -0.63, -0.27,  1.23, -0.65 ],
  [  2.07,  0.24, -1.21, -1.10, -1.22,  1.72, -0.69, -0.61 ],
  [ -0.08,  0.41, -1.18, -0.50, -1.05,  1.54, -1.10, -1.07 ],
  [  0.35, -0.79, -0.69,  1.02, -0.82, -1.47,  2.20,  0.94 ],
] 요런식으로 임베딩 됨 - 이 경우는 maxlen이13이고, hidden unit가 8인 경우
"""

 

시도1. Position Embedding 대신 Sinusoidal positional Encoding 사용

- SASRec 모델이 기본적으로 Transformer의 Self-Attention 구조와 매우 유사하므로, <Attention Is All You Need> 에 등장하는  Positional Encoding 방식을 사용해봄.

- 앞선, position embedding의 경우 학습되는 파라미터로, 역전파 과정에서 임베딩 벡터가 갱신되었다면, Sinusodial positional encoding 방식은 파라미터가 아닌  고정된 position embedding vector로 모델 학습 시 갱신되지 않음!

- Sinusoidal Positional Encoding은 포지션끼리 가까우면 내적이 커지고 멀면 내적이 작아져서 내적으로부터 거리관계를 유추!

 

SASRec 변형 코드

# PositionalEncoding class 정의해줌
class PositionalEncoding(torch.nn.Module):
    def __init__(self, hidden_units, max_len):
        super().__init__()

        # Positional Encoding 초기화
        # (max_len,hidden_units) 크기의 tensor생성
        self.P_E = torch.zeros(max_len, hidden_units)
        # 학습되는 파라미터가 아니므로 requires_grad 을 False로 설정
        self.P_E.requires_grad = False

        # pos (0~max_len) 생성 (row 방향 => unsqueeze(dim=1))
        pos = torch.arange(0, max_len, dtype=torch.float).unsqueeze(dim=1)

        # _2i (0~2i) 생성 (col 방향)
        # 2i는 step = 2 를 활용하여 i의 2배수를 만듦
        _2i = torch.arange(0, hidden_units, step= 2, dtype=torch.float)

        # sinusodial positional encoding 생성 - 짝수일땐 sin / 홀수 일땐 cos
        self.P_E[:, 0::2] = torch.sin(pos / 10000 ** (_2i / hidden_units))
        self.P_E[:, 1::2] = torch.cos(pos / 10000 ** (_2i / hidden_units))

    def forward(self,x):
        # x seq 길이에 맞춰 PE return 
        # (seq_len, hidden_units)
        batch_size, seq_len = x.size()
        PE_for_x = self.P_E[:seq_len,:]

        return PE_for_x

- <Attention Is All You Need> 논문에서 둘 중 어느 방법을 사용하든지 비슷한 성능이라고 하는데, 여기서도 마찬가지였다!

- 기존 : NDCG@10 = 0.6048, HR@10 : 0.8509

- 시도1: NDCG@10= 0.6047 , HR@10 : 0.8432

시도2. Position Embedding 초기화 시, Random한 값으로 초기화

- 기존 SASRec의  position embedding은 [ 0 1 2 ... 47 48 49] 와 같이 0~ maxlen까지 오름차순으로 증가하는 값을 가지도록 초기화됨

- 이 초기화된 값은 역전파를 통해서 계속해서 갱신되는데, 어차피 갱신되는 값이므로 이를  랜덤값으로 초기화 시켜보자라는 시도!

- 최근 논문에서 딥러닝에 들어가는 행렬을 랜덤 초기화했을 때 가장 좋은 성능을 얻을 수 있다고 들어서 이러한 방법을 시도해보았고, 일반화 능력이 올라가지 않을까해서 도전해봄

positions = np.random.permutation(np.arange(log_seqs.shape[1]))
positions = np.tile(positions, (log_seqs.shape[0], 1))
seqs += self.pos_emb(torch.LongTensor(positions).to(self.dev)) # position embedding - (batch_size * max_len *d) 크기, 아이템 임베딩이랑 더해짐

- 하지만 그 결과, 비슷한 성능 보여줌!

- 기존 : NDCG@10 = 0.6048, HR@10 : 0.8509

- 시도2: NDCG@10= 0.6040 , HR@10 : 0.8458