지난 포스트
[Machine Learning/기타] - RecSys - Factorization Machines (1)
[Machine Learning/기타] - ResSys - Factorization Machines (2)
[Machine Learning/기타] - RecSys - Factorization Machines (3), Pytorch 구현
Factorization Machines (FM)은 sparse한 데이터 상황에서 각 feature 간의 상호관계를 모델링하기 위해 Equation 1과 같이 각 feature의 잠재벡터를 (임베딩 벡터) 학습하고 feature의 선형결합과 잠재벡터의 내적으로 타겟을 예측했습니다.

CTR (Click Through Rate)을 예측하기 위한 Table 1과 같은 데이터가 있다고 가정해 보겠습니다. Espn / Vogue / NBC 는 'Publisher' 라는 feature 필드에 속해있고 Nike / Gucci / Adidas 는 'Advertiser' 라는 feature 필드에 속해 있는데 우리가 접하는 일반적인 추천 데이터는 Table 1처럼 여러 개의 범주로 구성된 여러 feature 필드의 조합으로 되어 있습니다. 간단하게 예를 들면 one-hot encoding 으로 표현된 유저 Id나 영화 Id feature는 각각 유저, 영화라는 feature 필드에 속해있는 것이죠.

조금 더 자세하게 살펴보기 위해 성별이라는 새로운 태그가 Figure 1과 같이 추가되었다고 가정해 보겠습니다. Equation 1의 feature 상호작용을 담당하는 오른쪽 항에 Figure 1을 적용해보면 Equation 2와 같이 표현될 겁니다. (여기서 w는 Equation 1의 v와 같습니다.)


Equation 2와 같이 FM 식을 적용하면 다른 feature 와의 상호관계를 학습하기 위해 각 feature 마다 하나의 잠재벡터만을 (wESPN,wNike,wMale) 가지게 됩니다. 즉, ESPN feature의 하나의 잠재벡터 wESPN가 Nike, Male 과의 관계를 학습하기 위해 동시에 이용된다는 것이죠. 하지만 Nike는 'Advertiser' 라는 feature 필드의 범주이고 Male은 'Gender' 라는 feature 필드의 범주이므로 (ESPN, Nike)와 (ESPN, Male)의 관계에 동시에 사용되는 ESPN의 잠재벡터는 각 필드에 대해 다를 가능성이 높습니다. 즉, 하나의 잠재벡터 만으로 서로 다른 feature 필드와의 관계를 충분히 표현하지 못한다는 것이죠.
즉, 이번 포스트에서 다루고자 하는 Field-aware Factorization Machines (FFM) 에서는 각 feature가 속한 필드를 감안하여 각 feature 가 자신이 속한 필드 이외의 feature 필드들에 대해 여러 개의 잠재벡터를 가지게 됩니다. 즉, Figure 1의 경우에 ESPN의 잠재벡터는 'Advertiser' 필드에 속한 feature 와의 상관관계를 나타내는 wESPN,A와 'Gender' 필드에 속한 feature 와의 상관관계를 나타내는 wESPN,G 두개가 생기고 이를 수식으로 표현하면 Equation 3과 같습니다.

정리하면 (ESPN, Nike) 의 상관관계를 학습하기 위해서 wESPN,A가 사용되고 (ESPN, Male) 의 상관관계를 학습하기 위해서 wESPN,G가 사용되는 것처럼 어떠한 feature 와의 상호작용을 파악하기 위한 잠재벡터를 필드마다 여러 개 둔다는 것이죠. 이를 일반화하면 Equation 4와 같습니다. (기존 FM의 Equation 1에서 오른쪽 항만 Equation 4와 같이 바뀐 것입니다.)

Equation 4의 f1,f2는 j1,j2의 필드를 나타내고 잠재벡터 차원과 필드 개수를 각각 k,f라 하면 FFM 파라미터의 개수는 nfk 이며 시간복잡도는 x의 평균 non-zero 개수를 ˉn이라 했을 때 O(ˉn2k)가 소요됩니다. 특히 FFM 에서는 잠재벡터가 해당되는 필드에 대해서만 학습하면 되니 kFFM을 kFM에 비해 매우 작게 잡습니다.
Pytorch implementation
데이터셋은 지난 포스트에서 사용한 MovieLens 20M을 그대로 사용한다고 가정하고 모델만 구성해보도록 하겠습니다. 각 feature의 선형결합은 FM 구현에서의 "FeaturesLinear" 클래스를 그대로 사용하고 Equation 4만 구현하면 됩니다. 입력 텐서는 [배치 사이즈, 필드개수=2] 크기이고 feature 축에 대해서 'UserId', 'MovieId'가 담겨 있습니다. 먼저 FFM 에서는 각 feature가 필드 별로 여러 개의 잠재벡터를 가지므로 필드 개수만큼의 임베딩 파라미터를 선언해줍니다. 지난 번과 마찬가지로 필드 차원을 입력에 더해주어 전체 feature 상에서의 위치를 "self.offset" 변수에 담습니다.
class FieldAwareFactorizationMachine(torch.nn.Module):
def __init__(self, field_dims, embed_dim=4):
super().__init__()
self.num_fields = len(field_dims)
self.embeddings = torch.nn.ModuleList([
torch.nn.Embedding(sum(field_dims), embed_dim) for _ in range(self.num_fields)
])
self.offsets = np.array((0, *np.cumsum(field_dims)[:-1]), dtype=np.long)
for embedding in self.embeddings:
torch.nn.init.xavier_uniform_(embedding.weight.data)
forward 메소드에서는 입력에 대해 필드 개수만큼 선언한 임베딩 벡터를 추출합니다. 따라서 [배치사이즈, 2, 임베딩 차원=4] 텐서가 필드 개수만큼 리스트에 담기겠죠. 이후에는 Equation 4와 같이 각 필드 별로 입력에 대해 추출된 임베딩 벡터에 대해서 다른 필드의 임베딩 벡터와 곱해줍니다.
def forward(self, x):
"""
:param x: Long tensor of size ``(batch_size, num_fields)``
"""
x = x + x.new_tensor(self.offsets).unsqueeze(0)
# self.embeddings[i](x): [batch_size, num_fields, embed_dim]
xs = [self.embeddings[i](x) for i in range(self.num_fields)]
ix = list()
for i in range(self.num_fields - 1):
for j in range(i + 1, self.num_fields):
ix.append(xs[j][:, i] * xs[i][:, j]) # [batch_size, embed_dim]
ix = torch.stack(ix, dim=1) # [batch_size, _, embed_dim]
return ix
최종적인 FFM 모델은 다음과 같습니다. 위의 forward 메소드로 계산된 결과에 대해 각 필드 별로 내적으로 곱해져 계산된 임베딩 결과를 dim=1에 대해 합쳐주고 마지막으로 임베딩을 합쳐줍니다. 최종 텐서는 [배치 사이즈, 1] 크기이므로 squeeze 메소드를 통해 마지막 차원을 없애주고 이진 분류를 위해 sigmoid 함수를 붙입니다.
class FieldAwareFactorizationMachineModel(torch.nn.Module):
"""
A pytorch implementation of Field-aware Factorization Machine.
Reference:
Y Juan, et al. Field-aware Factorization Machines for CTR Prediction, 2015.
"""
def __init__(self, field_dims, embed_dim):
super().__init__()
self.linear = FeaturesLinear(field_dims)
self.ffm = FieldAwareFactorizationMachine(field_dims, embed_dim)
def forward(self, x):
"""
:param x: Long tensor of size ``(batch_size, num_fields)``
"""
ffm_term = torch.sum(torch.sum(self.ffm(x), dim=1), dim=1, keepdim=True)
x = self.linear(x) + ffm_term
return torch.sigmoid(x.squeeze(1))
참조
'Machine Learning Tasks > Recommender Systems' 카테고리의 다른 글
RecSys - DeepFM (0) | 2021.07.15 |
---|---|
RecSys - Factorization Machines (3), Pytorch 구현 (2) | 2021.06.23 |
ResSys - Factorization Machines (2) (0) | 2021.06.22 |
RecSys - Factorization Machines (1) (4) | 2021.06.22 |