背景
機械学習の勉強を重ねて、本ブログの推薦システムを(初期モデル、後日更新する予定)作って見ました。
本ブログはユーザー数も記事数も少ないことを考慮して、今回はコンテンツベースフィルタリングを実装しました。
コンテンツベースフィルタリングはざっくり言うとアイテムの内容をベクトル化して、そのベクトル間の類似度により、類似するベクトルを探し出す方法です。
処理の流れ
本ブログでは、rss feed機能が実装されています。従って、https://www.ganbaruyo.net/feedから、本サイトの全ての記事が取得できます。
1. RSS feedから、記事タイトル、内容を取得
2. 記事内容(テキスト)に対して、形態素解析を行い、単語単位で区切る
3. tf-idf手法を使って、各文章の単語に点数をつける (tf-idf値)
4. 記事内容を単語のtf-idf値からなるベクトルに変換
5. 上のベクトルを使って、記事間の類似度(今回はコサイン類似度を使う)を計算
6. 各記事に置いて、自分と類似度が高い記事を類似記事として推薦欄に表示
実装環境
1. [MeCab :オープンソース 形態素解析] (http://taku910.github.io/mecab/)
2. [BeautifulSoup : HTML, XML parser] (http://kondou.com/BS4/)
3. [scikit-learn: オープンソース機械学習ライブラリ] (http://scikit-learn.org/)
4. Python
ソースコード
割と簡単で理解しやすいため、詳しい説明は省略して、直接コードを添付します。
ブログ記事取得部分
blog.py
#!/usr/bin/env python3
#coding: utf-8
import requests
import sys, io
import re
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
def get_blog_contents():
response = requests.get('https://blog.ganbaruyo.net/feed')
result = response.json()
size = result['length']
titles = []
contents = []
pattern=r'([+-]?[0-9]+\.?[0-9]*)'
tmp = 0
for count in range(size-1, -1, -1):
tmp = tmp + 1
item = 'item' + str(count)
time = result[item]['date']
titles.append(result[item]['title'])
text = result[item]['title']+' '\
+result[item]['body'].strip().replace('\r', ' ').replace('\n', ' ').replace('\t', ' ')
text = re.sub(pattern, ' ', text)
contents.append(text)
return titles, contents
推薦システムのメイン部分
main.py
# !/usr/bin/env python
# coding: utf-8
import numpy as np
from sklearn.feature_extraction import text
from sklearn.feature_extraction.text import TfidfVectorizer
import MeCab
import urllib.request
import bs4
from bs4 import BeautifulSoup
from sklearn.metrics.pairwise import linear_kernel
from blog import get_blog_contents
def get_stop_words():
#stopwords(文章の属性に関わらず頻出する単語)を除外するために、slothlibの日本語よりストッ
プワードリストを取得。
#urllibとBeautifulSoupによりソースをパース。
url = 'http://svn.sourceforge.jp/svnroot/slothlib/CSharp/Version1/SlothLib/NLP/Filter/StopWord/word/Japanese.txt'
soup = bs4.BeautifulSoup(urllib.request.urlopen(url).read(), "html.parser")
ss = str(soup)
return ss
if __name__ == "__main__":
titles, contents = get_blog_contents()
my_stop_words = text.ENGLISH_STOP_WORDS.union(get_stop_words())
vectorizer = TfidfVectorizer(use_idf=True, token_pattern=u'(?u)\\b\\w+\\b',
ngram_range=(1, 3), stop_words=(my_stop_words),
lowercase=True, analyzer='word')
vecs = vectorizer.fit_transform(contents)
tfidf_matrix = vecs.toarray()
cosine_sims = linear_kernel(tfidf_matrix, tfidf_matrix)
recommend_id = list()
for index, sim in enumerate(cosine_sims):
sim_sorted = sorted(range(len(sim)), key=lambda i: sim[i], reverse=True)[:6]
recommend_id.extend(sim_sorted[1:])
print(index, titles[index])
for i in range(1, 6):
print("----: ", titles[sim_sorted[i]])
実行結果
0 Python テストコードの書き方について [Python] [Django]
----: Viewに複数のmodelを指定する方法 [Django]
----: ページネーション機能の追加 [Django]
----: python manage.py migrationが効かない時の対処法 [Django]
----: Django project: pythonからjavascriptにデータを渡す方法 [django]
----: Error Pageを追加する方法 : [Django]
1 Pythonリストの常用使い方まとめ [Python]
----: 416 Error of Requests [Python3]
----: Django project: pythonからjavascriptにデータを渡す方法 [django]
----: Django ProjectのHTTPSサーバー設定: Let's Encrypt [Django]
----: Node.js + Express + mysqlを使った簡単な RESTful API デモ [NodeJS]
----: EC2に自分のメールサーバーを構築する[mailserver, ec2, ubuntu]
... ... ... ... ... ...
まとめ
一応、上のように割と短いコードでいわゆるコンテンツベースフィルタリング機能を実装しました。
ただし、
1. 記事数がすくない
2. 記事内容的に短い記事もけっこある
ということから、推薦結果があまり類似していない記事も含むことがあります。