관리 메뉴

TEAM EDA

Chapter 4 : 위키피디아 외부 링크를 이용한 추천 시스템 구축 본문

책 내용 정리/딥러닝 쿡북

Chapter 4 : 위키피디아 외부 링크를 이용한 추천 시스템 구축

김현우 2019. 4. 7. 11:36

Note : 이 포스터는 (주)느린생각의 지원을 받아 딥러닝 쿡북이라는 교재로 스터디를 하고 작성하는 포스터입니다. 코드는 아래의 저자 링크(https://github.com/Dosinga/deep_learning_cookbook)를 활용하였습니다. 

 

목차 

4.1 데이터 수집하기

4.2 영화 임베딩 학습하기 

4.3 영화 추천 시스템 만들기

4.4 단순 영화 평점 예측 

 

이번 챕터에서는 위키피디아의 외부 링크를 기반으로 데이터를 수집할 것 입니다. 그리고 이를 바탕으로 임베딩 훈련을 

시킨 후 SVM 모델로 간단한 추천시스템을 구현해보도록 하는 작업을 하겠습니다. 

 

4.1 데이터 수집하기. 

먼저 위키피디아의 덤프 페이지에서 최신 덤프 데이터를 수집합니다. 

# https://dumps.wikimedia.org/enwiki/이라는 주소로 GET 요청(request)을 보냄. 
index = requests.get('https://dumps.wikimedia.org/enwiki/').text

# 위에서 받아온 index를 html.parser을 통해 파싱 
soup_index = BeautifulSoup(index, 'html.parser')

# 파싱한 soup_index에서 href a가 달린 부분을 찾아서 href에 해당하는 부분을 dumps 리스트에 저장 
dumps = [a['href'] for a in soup_index.find_all('a') 
             if a.has_attr('href') and a.text[:-1].isdigit()]

그리고 난 이후에 가장 최신 데이터를 불러오는 코드를 작성합니다. 아래의 코드는 주어진 데이터를 정렬한 이후에 가장 최신을 뽑아내는 코드입니다. 참고로 크롤링할시에 IP 차단을 피하기 위해서 sleep() 이라는 명령어를 통해서 인위적으로 호출을 지연시키는 작업을 해줘야 합니다.  

for dump_url in sorted(dumps, reverse=True):
    print(dump_url)
    dump_html = index = requests.get('https://dumps.wikimedia.org/enwiki/' + dump_url).text
    soup_dump = BeautifulSoup(dump_html, 'html.parser')
    pages_xml = [a['href'] for a in soup_dump.find_all('a') 
                 if a.has_attr('href') and a['href'].endswith('-pages-articles.xml.bz2')]
    
    if pages_xml:
        break
    # 0.8s 지연 
    time.sleep(0.8)

 

4.2 영화 임베딩 학습하기 

문제 : 개체간의 링크 데이터를 사용하여 "당신이 어떤 것을 좋아했다면 이것에도 관심이 있을 수 있다" 와 같은 제안을 할 수 있을까? 

 

보통 위와 같은 문제를 추천시스템이라고 정의합니다. 내가 이제까지 좋아했던 목록을 통해서 추천을 할 수도 있고, 나와 비슷한 사람들이 좋아하는 목록을 통해서 추천을 해줄 수도 있습니다. 본 챕터에서는 영화끼리의 유사도를 계산하여 비슷한 영화를 추천해주는 방법을 선택합니다. 진행순서는 아래와 같습니다. 

 

1. 인기순으로 상위 몇 편의 영화를 사용 

2. 영화와 링크가 비슷한지를 학습 

3. 위의 학습된 내용을 토대로 비슷한 영화 추천

( 위에서 링크가 비슷한지를 학습한 이유는 동일한 페이지로 연결이 되는 영화는 직관적으로 비슷하다고 가정했기 때문입니다. )

 

movies가 주어졌을 때, 

'Deadpool (film)',
  {'image': 'Deadpool poster.jpg',
   'name': 'Deadpool',
   'cinematography': 'Ken Seng',
   'Software Used': 'Adobe Premier Pro',
   'alt': "Official poster shows the titular hero Deadpool standing in front of the viewers, with hugging his hands, and donning his traditional black and red suit and mask, and the film's name, credits and billing below him.",
   'distributor': '20th Century Fox',
   'caption': 'Theatrical release poster',
   'gross': '$783.1 million',
   'country': 'United States',
   'director': 'Tim Miller',
   'runtime': '108 minutes',
   'editing': 'Julian Clarke',
   'language': 'English',
   'music': 'Tom Holkenborg',
   'budget': '$58 million'}

아래의 코드를 통해서 모든 영화에 대해 검색 된 횟수를 추출합니다. 

link_counts = Counter() #Counter()를 통해서 링크가 몇번 검색되었는지 횟수를 셀 함수 호출
for movie in movies: #movie마다 검색된 갯수를 세고 추출
	# dictionary에 movie[2]를 추가 
    link_counts.update(movie[2])
    
# 가장 많이 나온 3개의 값을 출력 
link_counts.most_common(3)
[('Rotten Tomatoes', 9393), ('Category:English-language films', 5882), ('Category:American films', 5867)] 

그리고 최소 n번 이상 추출 된 영화만을 뽑아냅니다. 아래의 코드에서는 items()를 통해서 key와 values를 추출해놓고 value가 3보다 크거나 같은 값들의 link만을 추출합니다. 그 후 enumerate를 통해서 index와 link를 추출하고 dictionary로 다시 저장하는 작업을 거칩니다. 

top_links = [link for link, c in link_counts.items() if c >= 3]
link_to_idx = {link: idx for idx, link in enumerate(top_links)}
movie_to_idx = {movie[0]: idx for idx, movie in enumerate(movies)}
pairs = []
for movie in movies:
    pairs.extend((link_to_idx[link], movie_to_idx[movie[0]]) for link in movie[2] if link in link_to_idx)
pairs_set = set(pairs)
len(pairs), len(top_links), len(movie_to_idx)

 

이후에 embedding작업을 통해서 link를 학습할 모델과 movie를 학습 할 모델을 만들어줍니다. 여기서 embedding은 keras의 모델을 가져와서 사용합니다. 

def movie_embedding_model(embedding_size=50):
    link = Input(name='link', shape=(1,))
    movie = Input(name='movie', shape=(1,))
    link_embedding = Embedding(name='link_embedding', 
                               input_dim=len(top_links), 
                               output_dim=embedding_size)(link)
    movie_embedding = Embedding(name='movie_embedding', 
                                input_dim=len(movie_to_idx), 
                                output_dim=embedding_size)(movie)
    dot = Dot(name='dot_product', normalize=True, axes=2)([link_embedding, movie_embedding])
    merged = Reshape((1,))(dot)
    model = Model(inputs=[link, movie], outputs=[merged])
    model.compile(optimizer='nadam', loss='mse')
    return model

model = movie_embedding_model()
model.summary()

 

4.3 영화 추천 시스템 만들기 

 

위의 작업까지 진행하면 영화끼리의 유사도를 계산할 수 있습니다. 그리고 유사도를 계산한 것을 토대로 영화를 추천해줄 수 있었습니다. 하지만 일반적인 추천 시스템에서는 사용자가 평가한 일련의 영화를 기반으로 추천을 합니다. 이를 만들기 위해 아래의 영화목록을 사용자가 부여한 평점이라고 가정해보겠습니다. 

 

best = ['Star Wars: The Force Awakens', 'The Martian (film)', 'Tangerine (film)', 'Straight Outta Compton (film)',
        'Brooklyn (film)', 'Carol (film)', 'Spotlight (film)']
worst = ['American Ultra', 'The Cobbler (2014 film)', 'Entourage (film)', 'Fantastic Four (2015 film)',
         'Get Hard', 'Hot Pursuit (2015 film)', 'Mortdecai (film)', 'Serena (2014 film)', 'Vacation (2015 film)']

y = np.asarray([1 for _ in best] + [0 for _ in worst]) # best는 1로 worst는 0으로 추출해서 array로 입력
X = np.asarray([normalized_movies[movie_to_idx[movie]] for movie in best + worst]) 
X.shape

 

위의 사용자 평점을 기반으로 SVM 분류기를 구성하여 훈련을 하고 학습하는 과정을 거칩니다. 

clf = svm.SVC(kernel='linear')
clf.fit(X, y)

 

그리고 아래의 코드를 통해서 예측을하고 상위 5개와 하위 5개의 영화를 추출해 낼 수 있습니다. 

estimated_movie_ratings = clf.decision_function(normalized_movies)
best = np.argsort(estimated_movie_ratings)
print('best:')
for c in reversed(best[-5:]):
    print(c, movies[c][0], estimated_movie_ratings[c])

print('worst:')
for c in best[:5]:
    print(c, movies[c][0], estimated_movie_ratings[c])
best:
307 Les Misérables (2012 film) 1.246511730519127
66 Skyfall 1.1888723752441601
481 The Devil Wears Prada (film) 1.1348285888204566
630 The Tree of Life (film) 1.1295026844583682
81 Birdman (film) 1.1121067681173762
worst:
9694 The Marine (film series) -1.6472428525072056
5097 Ready to Rumble -1.6412750149090598
8837 The Santa Clause (film series) -1.6391878640118387
1782 Scooby-Doo! WrestleMania Mystery -1.610221193972685
3188 Son of the Mask -1.6013579562623643

 

4.4 단순 영화 평점 예측 

위에서 한 내용이 내가 평가했던 내용을 토대로 추천을 했다면, 이번에는 임베딩 모델을 통해 학습한 벡터를 토대로 영화의 평점을 예측하는 과정입니다. 과정은 되게 단순합니다. 먼저 평점(y)와 속성(x)를 만들고 이를 선형회귀를 이용해 학습시킨 후 예측하면 끝입니다. 아래의 코드로 내용을 보면

 

# 1. x와 y를 만듬
rotten_y = np.asarray([float(movie[-2][:-1]) / 100 for movie in movies if movie[-2]])
rotten_X = np.asarray([normalized_movies[movie_to_idx[movie[0]]] for movie in movies if movie[-2]])
# 2. 데이터를 8:2로 분할 후 학습시킨 후 예측
TRAINING_CUT_OFF = int(len(rotten_X) * 0.8)
regr = LinearRegression()
regr.fit(rotten_X[:TRAINING_CUT_OFF], rotten_y[:TRAINING_CUT_OFF])
# 3. 에러 계산
error = (np.mean(rotten_y[:TRAINING_CUT_OFF]) - rotten_y[TRAINING_CUT_OFF:])
'mean square error %2.2f' % np.mean(error ** 2)

 

 

참고 

https://github.com/DOsinga/deep_learning_cookbook/