hello_world.py

"Doing good is part of our code"

Pythonで関連記事を抽出する

f:id:tanajun99:20150624232758j:plain

この前、RSSのテキストデータを解析して関連記事を抽出するというのをやりました。そのことについての自分なりのまとめです。

自然言語処理とは?

自然言語処理(しぜんげんごしょり、英語: natural language processing、略称:NLP)は、人間が日常的に使っている自然言語をコンピュータに処理させる一連の技術であり、人工知能言語学の一分野である。「計算言語学」(computational linguistics)との類似もあるが、自然言語処理は工学的な視点からの言語処理をさすのに対して、計算言語学言語学的視点を重視する手法をさす事が多い[1]。データベース内の情報を自然言語に変換したり、自然言語の文章をより形式的な(コンピュータが理解しやすい)表現に変換するといった処理が含まれる。応用例としては予測変換、IMEなどの文字変換が挙げられる。(wikipedia)

fmfm



類似文書の探し方

詳しくはこちらkesin.hatenablog.com

かなりわかりやすかったです。いろいろぐぐりながらやったのですが、ダントツで分かりやすかったです。すごい。

こちらで紹介されていたのはベクトルを使いどれだけ文章が似ているのかを数値として計算するほうほうです。


AppleMacというOSを作った
MicrosoftWindowsというOSを作った
Googleは優秀な検索エンジンを開発

この3つの文書から意味のある言葉を抜き出すと、(今回は助詞、接尾、BOS、EOS、記号、名詞,数、助動詞、を除く。)

[Apple, Mac, OS, 作った]
[Microsoft, Windows, OS, 作った]
[Google, 優秀, 検索エンジン, 開発]

というように文書を単語で表すことができました。
同じ単語が何回も出ていますね?これを利用する。
ではこの3つの文書をベクトル化。まずは登場した全ての単語の集合を作ります。

[Apple, Mac, OS, 作った, Microsoft, Windows, Google, 優秀, 検索エンジン, 開発]

そしてこの集合の左から、それぞれの文書の中にこの単語が登場したら1, 登場しなければ0と置いていくとベクトルを作ることができます。

{1, 1, 1, 1, 0, 0, 0, 0, 0, 0}
{0, 0, 1, 1, 1, 1, 0, 0, 0, 0}
{0, 0, 0, 0, 0, 0, 1, 1, 1, 1}

既にどの文書が似ているのか一目瞭然ですが、実際にどれだけ似ているかを計算します。ここではとても単純に2つのベクトルがなす角度を比べることにします。つまり、2つのベクトルの余弦を計算します。計算式はcos=ベクトルA,Bの内積/(ベクトルAのノルム*ベクトルBのノルム) です。これをcos類似度と呼びます。

cos類似について
コサイン類似度



ここでは10次元という大きな次元でベクトル計算する。

文書1と文書2の類似度: 0.5
文書1と文書3の類似度: 0

というような結果になりました。単語集合やベクトルのときから何となく分かっていた結果ですが、このように計算で数値的に文書がどれだけ似ているかを求めることができます。



MeCabによる形態素解析


次はどのように文書を品詞ごとに区別するのかをみます。
Pythonでは普通にMeCabが使えるのでこちらを使用します。
f:id:tanajun99:20150625193922p:plain

こんな感じで、品詞分解してくれます。すごい。。

という単語集合を得ることができます。単語さえ抜き出すことができれば後は簡単です。全ての文書の単語を抜き出してベクトルを作り、cos類似度を計算するだけです。

実際のコードがこちら

#coding: utf-8
import feedparser
import MeCab
import re
import math

def analysis_entrie(entrie_list, allnoun_set):  #エントリーの単語を解析
    t = MeCab.Tagger("")
    re_word = re.compile("助詞|接尾|BOS|EOS|記号|名詞,数|助動詞| ") #除外する品詞
    feed=[]
    for entrie in entrie_list['entries']:
        if not "PR:" in entrie.title and not "AD:" in entrie.title: #広告除外
            noun_set = set([])
            dict = {}
            m = t.parseToNode(entrie.title.encode('utf-8')) #MeCabはUnicodeに対応していないのでutf-8に変換
            while m:
                if not re_word.findall(m.feature) and not m.surface.isspace():  #品詞チェック
                    noun_set.add(m.surface.decode('utf-8')) #単語集合に追加
                    allnoun_set.add(m.surface.decode('utf-8')) #全体単語集合に追加
                m = m.next
            dict['title'] = entrie.title
            dict['noun'] = noun_set
            dict['date'] = entrie.updated
            dict['url'] = entrie.link
            feed.append(dict)
            print entrie.title
            print "["+",".join(noun_set)+"]"
    return feed
def get_vector(allnoun_set, noun_set): #記事ごとの単語と全単語からベクトル生成
    vector = []
    for noun in allnoun_set:
        if noun in noun_set:
            vector.append(1)
        else:
            vector.append(0)
    return vector
def cos_similar(vector_A, vector_B): #cos類似度計算
    inner=0.0
    for (a, b) in zip(vector_A, vector_B):
        inner += a*b                        #内積
    for i in range(len(vector_A)):
        vector_A[i] = vector_A[i]**2
    for i in range(len(vector_B)):
        vector_B[i] = vector_B[i]**2
    norm_A = math.sqrt(sum(vector_A))       #ノルムA
    norm_B = math.sqrt(sum(vector_B))       #ノルムB
    return inner / (norm_A * norm_B)
   
if __name__ == '__main__':
    hatena = feedparser.parse("http://b.hatena.ne.jp/hotentry/it.rss")
    mycom_apple = feedparser.parse("http://feeds.journal.mycom.co.jp/rss/mycom/pc/apple")
    ascii_apple = feedparser.parse("http://rss.rssad.jp/rss/ascii/mac/rss.xml")
    itmedia_iphone = feedparser.parse("http://rss.rssad.jp/rss/itm/1.0/kw_iphone.xml")
    site_list= []
    site_list.append(hatena)
    site_list.append(mycom_apple)
    site_list.append(ascii_apple)
    site_list.append(itmedia_iphone)
   
    allnoun_set = set([])
    feed_list = []
   
    for site in site_list:
        feed_list += analysis_entrie(site, allnoun_set)
    for entrie in feed_list:
        entrie['vector'] = get_vector(allnoun_set, entrie['noun'])
   
    while True:    #入力した記事番号と類似する記事を表示
        print "記事番号を入力:"
        num = input()
        find_entrie = feed_list[num]
       
        for entrie in feed_list: #選択した記事とのcos類似度を計算
            entrie['score'] = cos_similar(find_entrie['vector'], entrie['vector'])
        feed_list.sort(key=lambda x:x['score'], reverse=True)
       
        print '\n"'+find_entrie['title'] +"\t"+"["+",".join(find_entrie["noun"])+"]"
        print "に似た上位5記事は以下のものです:"
        for entrie in feed_list[:6]:
            if (entrie['score'] < 0.99 and entrie['score'] > 0): #score=1は元記事、=0は全く関係ないので除外
                print entrie['title'], "\t"+"score:"+str(entrie['score'])+" ["+",".join(entrie["noun"])+"]"


実行結果


f:id:tanajun99:20150626005819p:plain

記事を指定すると、その記事を形態素解析して不要な品詞を除き、関連記事をスコアの高い順に出してくれます。
これをFlaskとかでjinja表示するとすぐにアプリケーションとかで使えそう。


あと、三回に一回ぐらい、コードは全く同じなのにUnicodeErrorがでます。かなり調べたのですが解決できずじまいです。。。。どなたか教えていただきたいです。
時間でrssのテキストは当然かわるので、そこが原因かとおもうのですが。。。