은닉층이 없는 신경망은 MF알고리즘과 기본적으로 동일한 모형이다.
MF를 keras로 구현
Input : 사용자, 아이템 데이터의 One-hot Encoding
1) 사용자 입력은 K개의 노드를 갖는 User Embedding과 연결된다.
2) 아이템 입력은 K개의 노드를 갖는 Item Embedding과 연결된다.
3) User Embedding과 Item Embedding은 내적 연산으로 결합된다.
4) 사용자 입력은 1개의 노드를 갖는 User Bias Embedding과 연결된다.
5) 아이템 입력은 1개의 노드를 갖는 Item Bias Embedding과 연결된다.
6) User Bias Embedding과 Item Bias Embedding은 내적 연산에 더해진다.
7) Flatten Layer는 최종 데이터와 계산에 사용된 행렬의 차원을 맞춰준다.
A) Input Layer : 사용자와 아이템으로부터 입력을 받는 부분
C) Element-wise Product Layer : Embedding 층의 두 요소 (사용자, 아이템)의 내적 연산을 위한 층
D) 사용자 평가경향(user bias)와 아이템 평가경향(item bias) 고려
E) Flatten Layer: 우리가 필요한 최종 데이터와 지금까지 계산된 행렬의 차원을 맞춰주기 위해 차원을 줄여줌.
@ 전체 평균은 신경망 모델에 직접 넣으면 복잡하기 때문에 신경망 투입 전에 모두 빼주고, 최종 예측값에 더해준다.
# 사용된 모듈
import pandas as pd
import numpy as np
import tensorflow as tf
from tensorflow.keras import layers
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, Embedding, Dot, Add, Flatten
from tensorflow.keras.regularizers import l2
from tensorflow.keras.optimizers import SGD, Adamax
# Variable 초기화
K = 200 # Latent factor 수 (잠재 요인의 수)
mu = ratings_train.rating.mean() # 전체 평균
M = ratings.user_id.max() + 1 # Number of users (사용자 아이디의 최대값 -> embedding에서 사용)
N = ratings.movie_id.max() + 1 # Number of movies (영화 아이디의 최대값)
# Keras model
# User input : 사용자 데이터 입력 형식
user = Input(shape=(1, ))
# Item input : 아이템 데이터 입력 형식
item = Input(shape=(1, ))
# (M, 1, K) : 사용자 임베딩 layer 지정 : M x K의 연결
P_embedding = Embedding(M, K, embeddings_regularizer=l2())(user)
# (N, 1, K) : 아이템 임베딩 layer 지정 : N x K의 연결
Q_embedding = Embedding(N, K, embeddings_regularizer=l2())(item)
# User bias term (M, 1, ) : 사용자 bias embedding layer 지정 : M x 1의 연결
user_bias = Embedding(M, 1, embeddings_regularizer=l2())(user)
# Item bias term (N, 1, ) : 아이템 bias embedding layer 지정 : N x 1의 연결
item_bias = Embedding(N, 1, embeddings_regularizer=l2())(item)
##### (2)
# (1, 1, 1) : 사용자, 아이템 임베딩 레이어를 내적 연산
R = layers.dot([P_embedding, Q_embedding], axes=2)
# 사용자 bias와 아이템 bias를 더함
R = layers.add([R, user_bias, item_bias])
# (1, 1) : 차원 압축(1차원 배열)
R = Flatten()(R)
# Model setting
model = Model(inputs=[user, item], outputs=R) #입력, 출력 정의
model.compile(
loss=RMSE,
optimizer=SGD(), #SGD, Adamax ...
metrics=[RMSE] # 측정지표
)
model.summary() # 모델 요약 정보
파라미터 개수
embedding : 사용자의 잠재 요인 (P) : 944 x 200 = 188,800
embedding_1 : 아이템의 잠재 요인 (Q) : 1683 x 200 = 336,600
embedding_2 : 사용자 bias layer -> 944 x 1
embedding_3 : 아이템 bias layer -> 1683 x 1
# Model fitting(학습)
result = model.fit(
x=[ratings_train.user_id.values, ratings_train.movie_id.values], #사용자와 아이템의 id를 입력으로
#출력은 평점 값에서 전체 평균을 빼서 사용
y=ratings_train.rating.values - mu,
epochs=60,
batch_size=256,
validation_data=(
[ratings_test.user_id.values, ratings_test.movie_id.values], #test-set의 입력
ratings_test.rating.values - mu
)
)
# Prediction
#예측 대상을 맨 처음 6개로 정함
user_ids = ratings_test.user_id.values[0:6]
movie_ids = ratings_test.movie_id.values[0:6]
#예측치를 구하고 전체 평균을 더해줌
predictions = model.predict([user_ids, movie_ids]) + mu
#실제값 출력
print("Actuals: \n", ratings_test[0:6])
print( )
#예측값 출력
print("Predictions: \n", predictions)
# RMSE check
def RMSE2(y_true, y_pred):
return np.sqrt(np.mean((np.array(y_true) - np.array(y_pred))**2))
#test-set
user_ids = ratings_test.user_id.values
movie_ids = ratings_test.movie_id.values
y_pred = model.predict([user_ids, movie_ids]) + mu #2차원의 array
y_pred = np.ravel(y_pred, order='C') #np.ravel을 사용하여 1차원 행렬로 바꾸기
y_true = np.array(ratings_test.rating) #실제 평점값
RMSE2(y_true, y_pred)
RMSE값이 1.0946의 값이 출력된다. 4장에서는 RMSE값이 약 0.91의 값이 나왔는데, 본 코드에서 성능이 낮아진 이유는 MF와 Keras 모델이 기본적인 것은 같지만 구체적인 부분에서 최적화가 아직 되지 않았기 때문이다.
#P, Q, 사용자 bias, 아이템 bias를 붙여서 하나의 layer를 만든다.
R = Concatenate()([P_embedding, Q_embedding, user_bias, item_bias]) # (2K + 2, )
# Neural network
R = Dense(2048)(R) #노드가 2048개인 dense layer
R = Activation('linear')(R)
R = Dense(256)(R)
R = Activation('linear')(R)
#노드가 1개인 dense layer를 추가하고, 이 층이 출력에 연결된다.
R = Dense(1)(R)
model = Model(inputs=[user, item], outputs=R)
model.compile(
loss=RMSE,
optimizer=SGD(),
#optimizer=Adamax(),
metrics=[RMSE]
)
model.summary()
파라미터 개수
embedding_4 : 사용자의 잠재 요인 (P) : 944 x 200 = 188,800
embedding_5 : 아이템의 잠재 요인 (Q) : 1683 x 200 = 336,600
embedding_6 : 사용자 bias layer -> 944 x 1
embedding_7 : 아이템 bias layer -> 1683 x 1
concatenate : 200(user embedding) + 200(item embedding) + 1(user bias) + 1(item bias)
dense (Dense) : (402 + 1) x 2048(노드 개수) = 825,344
dense_1 (Dense) : (2048 + 1) x 256 = 524,544
dense_2 (Dense) : 256 + 1
# Model fitting
result = model.fit(
x=[ratings_train.user_id.values, ratings_train.movie_id.values],
y=ratings_train.rating.values - mu, #출력 : 사용자-아이템별 평점값 - 평균
epochs=65,
batch_size=512, #한 번에 연산하는 batch 크기 지정
validation_data=(
[ratings_test.user_id.values, ratings_test.movie_id.values],
ratings_test.rating.values - mu
)
)
### 직업을 추가 변수로 고려
### 가정 : 사용자의 직업에 따라 영화를 평가하는 패턴이 다르다.
import pandas as pd
import numpy as np
# csv 파일에서 불러오기
r_cols = ['user_id', 'movie_id', 'rating', 'timestamp']
ratings = pd.read_csv('u.data', names=r_cols, sep='\t',encoding='latin-1')
ratings = ratings[['user_id', 'movie_id', 'rating']].astype(int) # timestamp 제거
# train test 분리
from sklearn.utils import shuffle
TRAIN_SIZE = 0.75
ratings = shuffle(ratings)
cutoff = int(TRAIN_SIZE * len(ratings))
ratings_train = ratings.iloc[:cutoff]
ratings_test = ratings.iloc[cutoff:]
# **수정된 부분 1**
u_cols = ['user_id', 'age', 'sex', 'occupation', 'zip_code']
users = pd.read_csv('u.user', sep='|', names=u_cols, encoding='latin-1')
#사용자 데이터를 읽어와서 ID와 직업만 남긴다.
users = users[['user_id', 'occupation']]
# Convert occupation(string to integer)
# 직업이 STRING 형태인데 이를 숫자로 바꾸는 작업
occupation = {}
def convert_occ(x):
if x in occupation: #이미 존재하면 그 값을 반환
return occupation[x]
else:
occupation[x] = len(occupation)
#새로운 ELEMENT를 새로운 INT값과 함께 딕셔너리에 추가
return occupation[x] #새로운 int 반환
users['occupation'] = users['occupation'].apply(convert_occ)
L = len(occupation) #직업의 개수 - DL에서 embedding에 활용
#trainset과 사용자 직업을 merge
train_occ = pd.merge(ratings_train, users, on='user_id')['occupation']
test_occ = pd.merge(ratings_test, users, on='user_id')['occupation']
# **수정된 부분 1 끝**
import tensorflow as tf
from tensorflow.keras import layers
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, Embedding, Dot, Add, Flatten
from tensorflow.keras.regularizers import l2
from tensorflow.keras.optimizers import SGD, Adam, Adamax
# Variable 초기화
K = 200 # Latent factor 수
mu = ratings_train.rating.mean() # 전체 평균
M = ratings.user_id.max() + 1 # Number of users
N = ratings.movie_id.max() + 1 # Number of movies
# Defining RMSE measure
def RMSE(y_true, y_pred):
return tf.sqrt(tf.reduce_mean(tf.square(y_true - y_pred)))
##### (2)
# Keras model
user = Input(shape=(1, ))
item = Input(shape=(1, ))
P_embedding = Embedding(M, K, embeddings_regularizer=l2())(user)
Q_embedding = Embedding(N, K, embeddings_regularizer=l2())(item)
user_bias = Embedding(M, 1, embeddings_regularizer=l2())(user)
item_bias = Embedding(N, 1, embeddings_regularizer=l2())(item)
# Concatenate layers
from tensorflow.keras.layers import Dense, Concatenate, Activation
P_embedding = Flatten()(P_embedding)
Q_embedding = Flatten()(Q_embedding)
user_bias = Flatten()(user_bias)
item_bias = Flatten()(item_bias)
# **수정된 부분 2**
occ = Input(shape=(1, )) #직업에 대한 입력을 새로 만든다.
#직업을 세개의 잠재요인으로 embedding
occ_embedding = Embedding(L, 3, embeddings_regularizer=l2())(occ)
#다른 embedding과 동일하게 차원을 줄인다.
occ_layer = Flatten()(occ_embedding)
# P, Q, 사용자 bias, 아이템 bias, 직업 모두 합쳐서 새로운 층을 만든다.
R = Concatenate()([P_embedding, Q_embedding, user_bias, item_bias, occ_layer])
# **수정된 부분 2 끝 **
# Neural network
R = Dense(2048)(R)
R = Activation('linear')(R)
R = Dense(256)(R)
R = Activation('linear')(R)
R = Dense(1)(R)
# **수정된 부분 3**
#직업까지 input값으로 넣어준다.
model = Model(inputs=[user, item, occ], outputs=R)
# **수정된 부분 3 끝 **
model.compile(
loss=RMSE,
optimizer=SGD(),
#optimizer=Adamax(),
metrics=[RMSE]
)
model.summary()