본문 바로가기

프로그래머스 데브 코스/TIL

[6기] 프로그래머스 인공지능 데브코스 108일차 TIL

1217

[17주차 - Day2] Recommendation system

무비렌즈 데이터를 활용한 사용자 협업 필터링

# 데이터 학습 부분 실습 코드 정리

 

# 후반에 사용할 ID/Name으로 각각 Name/ID와 장르 반환해 주는 함수 선언

def getMovieName(movie_ratings, movieID):
  return movie_ratings[movie_ratings['movieId'] == movieID][['title', 'genres']].values[0]

def getMovieID(movie_ratings, movieName):
  return movie_ratings[movie_ratings['title'] == movieName][['movieId', 'genres']].values[0]
!wget "https://grepp-reco-test.s3.ap-northeast-2.amazonaws.com/movielens/ratings.csv"

reader = Reader(line_format='user item rating  timestamp', sep=',', skip_lines=1)
data = Dataset.load_from_file('ratings.csv', reader=reader)

from surprise import KNNBasic
import heapq
from collections import defaultdict

trainSet = data.build_full_trainset()

sim_options = {
    'name': 'cosine',
    'user_based': True
}

model = KNNBasic(sim_options=sim_options)
model.fit(trainSet)
simMatrix = model.compute_similarities()

 

testUser = '85'
k = 10

# 주어진 사용자와 가장 흡사한 사용자 N을 찾는다
# 먼저 이를 Surprise 내부 ID로 변환
testUserInnerID = trainSet.to_inner_uid(testUser)
# 이 사용자에 해당하는 레코드를 읽어 온다
similarityRow = simMatrix[testUserInnerID]

# users에 모든 사용자들을 일련번호와 유사도를 갖는 듀플의 형태로 저장
# 이때 본인은 제외
users = []
for innerID, score in enumerate(similarityRow):
  if (innerID != testUserInnerID):
    users.append((innerID, score))
    
# 이제 users 리스트에서 유사도 값을 기준으로 가장 큰 k개를 찾는다
kNeighbors = heapq.nlargest(k, users, key=lambda t: t[1])

# 이제 유사 사용자들을 하나씩 보면서 그들이 평가한 아이템들별로 원 사용자와 유사 사용자간의 유사도를 가중치로 준 평점을 누적한다

# candidates에는 아이템별로 점수를 누적한다
# 유사 사용자(u')의 평점 * 사용자(u)와 유사 사용자(u')의 유사도
candidates = defaultdict(float)

# 이 K명의 최고 유사 사용자를 한 명씩 루프를 돌면서 살펴본다
for similarUser in kNeighbors:
  # similarUser는 앞서 enumerates로 만든 그 포맷 - (내부ID, 유사도값)
  innerID = similarUser[0]
  userSimilarityScore = similarUser[1]

  # innerID에 해당하는 사용자의 아이템과 평점 정보를 읽어온다
  # theirRatings는 (아이템ID, 평점)의 리스트임
  theirRatings = trainSet.ur[innerID]
  # innerID가 평가한 모든 아이템 리스트를 하나씩 보면서
  # 아이템ID별로 평점 정보를 합산하되 사용자와의 유사도값을 가중치로 준다
  for rating in theirRatings:
    candidates[rating[0]] += (rating[1]) * userSimilarityScore
    
# 사용자가 이미 평가한 아이템들을 제거할 사전을 만든다
watched = {}
for itemID, rating in trainSet.ur[testUserInnerID]:
  watched[itemID] = 1
  
# 앞서 candidates에서 합산된 스코어를 기준으로 내림차순으로 소팅한 후
# 사용자(u)가 아직 못 본 아이템인 경우 추천한다
pos = 0
for itemID, ratingSum in sorted(candidates.items(), key=lambda k: k[1], reverse=True):
  if not itemID in watched:
    movieID = trainSet.to_raw_iid(itemID)
    print(movieID, getMovieName(movie_ratings, int(movieID)), ratingSum)
    pos += 1
    if (pos > 10):
      break

 

# 위의 코드들 한 함수 안에, 주석 제거하고 모아 놓은 코드

def recommendForUser(userID):
  testUserInnerID = trainSet.to_inner_uid(userID)
  similarityRow = simMatrix[testUserInnerID]

  users = []
  for innerID, score in enumerate(similarityRow):
    if (innerID != testUserInnerID):
      users.append((innerID, score))
  
  kNeighbors = heapq.nlargest(k, users, key=lambda t: t[1])

  candidates = defaultdict(float)

  for similarUser in kNeighbors:
    innerID = similarUser[0]
    userSimilarityScore = similarUser[1]
    theirRatings = trainSet.ur[innerID]
    for rating in theirRatings:
      candidates[rating[0]] += (rating[1]) * userSimilarityScore

  watched = {}
  for itemID, rating in trainSet.ur[testUserInnerID]:
    watched[itemID] = 1

  pos = 0
  for itemID, ratingSum in sorted(candidates.items(), key=lambda k: k[1], reverse=True):
    if not itemID in watched:
      movieID = trainSet.to_raw_iid(itemID)
      print(movieID, getMovieName(movie_ratings, int(movieID)), ratingSum)
      pos += 1
      if (pos > 10):
        break
recommendForUser('85')