1. Input
"S-Walk: Accurate and Scalable Session-based Recommendation with Random Walks" 에서 Input x는 사용자의 세션 데이터[시퀀스], 즉 아이템의 순차적 목록이다. 그래서 저번 1편에서 x를 만드는 작업까지 진행했다.
이 중 user_id 한 개를 x로 설정하고 random walks를 진행할 예정이다. 이를 통해 아이템간 연관성을 추출하고 관심사나 패턴을 파악한 후 추천할 아이템을 예측하는 형태이다.
2. 모델 구현
먼저 엣지와 가중치를 설정하여 그래프를 구성한다.
graph = nx.DiGraph()
for session in sessions:
for i in range(len(session) - 1):
item, next_item = session[i], session[i + 1]
if graph.has_edge(item, next_item):
graph[item][next_item]["weight"] += 1
else:
graph.add_edge(item, next_item, weight=1)
# Edge weight 정규화
for node in graph.nodes:
total_weight = sum(graph[node][neighbor]["weight"] for neighbor in graph.neighbors(node))
for neighbor in graph.neighbors(node):
graph[node][neighbor]["weight"] /= total_weight
Edge weight 정규화?
그래프 알고리즘에서 노드 간 연결 강도를 보여주는 Edge weight 가 너무 크거나 작을 때 계산이 불안정해지는 문제가 생길 수 있기 때문에 가중치를 정규화 한다.
방법은 여러가지가 있는데 그 중 위의 코드에서는 모든 edge weight를 각 노드의 가중치 합으로 나누는 방법을 사용했다.
random walk with restart 알고리즘을 구성하여 시작 노드로부터 다른 노드에 대한 확률을 계산한다.
각 노드에서 다른 노드로 확률을 계산하고, 특정 확률(restart_prob)로 다시 시작 노드로 돌아간다.
확률 분포가 수렴하면 결과 반환.
#random_walk_with_restart
nodes = list(graph.nodes)
n = len(nodes)
restart_prob = 0.15
max_iter = 100
# 인덱스-노드 맵 생성
node_to_index = {node: i for i, node in enumerate(nodes)} # 노드에서 인덱스로
index_to_node = {i: node for i, node in enumerate(nodes)} # 인덱스에서 노드로
# 초기 확률 분포 지정 (랜덤워크 시작점 설정)
prob = np.zeros(n)
prob[node_to_index[start_node]] = 1 # 시작 노드에서 시작
# RWR 반복
for _ in range(max_iter):
new_prob = np.zeros(n)
# 모든 노드에 대해 무작위 이동 수행
for node in nodes:
neighbors = list(graph.neighbors(node))
if not neighbors:
continue # 고립된 노드 예외 처리
# 각 이웃으로 확률 분배
for neighbor in neighbors:
new_prob[node_to_index[neighbor]] += (1 - restart_prob) * prob[node_to_index[node]] * graph[node][neighbor]["weight"]
# 다시 시작 지점으로 돌아갈 확률 추가
new_prob[node_to_index[start_node]] += restart_prob
# 확률 분포 수렴 체크
if np.allclose(prob, new_prob, atol=1e-6):
break
prob = new_prob # 업데이트 된 확률 할당
# 결과 반환 (노드 이름과 함께 확률 출력)
result = {index_to_node[i]: prob[i] for i in range(n)}
고립된 노드 예외 처리?
고립된 노드; 다른 노드와 연결되지 않은 노드 [엣지가 없거나 연결된 노드가 없는 노드]
고립된 노드로 인해 연산 과정에서 문제가 발생할 수 있기 때문에 예외 처리 해야 함
예외 처리하는 방법?
1. 임의로 연결
2. 고립된 노드 제외
3. 고립된 노드의 값을 특정 값으로 초기화 혹은 고정된 값으로 설정
4. 고립된 노드 예외 처리 (다른 방법들)
마지막으로 세션의 마지막 아이템을 기준으로 추천 아이템을 반환한다.
-> 이 때 위의 RWR로 계산된 확률을 기준으로 내림차순 정렬 후 상위 K개의 아이템을 추천하는데 세션에 이미 포함된 아이템은 추천에서 제외한다. [알고리즘에 따라 제외하는게 나을수도 아닐 수도 있으니 확인]
if not session:
return []
last_item = session[-1]
scores = random_walk_with_restart(last_item)
# 상위 k개 아이템 추천
top_k = 5
sorted_scores = sorted(scores.items(), key=lambda x: x[1], reverse=True)
recommendations = [item for item, score in sorted_scores if item not in session][:top_k]
3. 모델 결과
'ML-DL > RS' 카테고리의 다른 글
[개인 프로젝트] S-Walk 논문 모델로 추천 모델 구현해보기 -1 [MovieLens 데이터셋 설명, 코랩 구글드라이브 연동하기, 데이터 전처리] (0) | 2024.10.25 |
---|---|
GNN [Graph Neural Network] - 1 (5) | 2024.09.13 |