graph_collective_inteligence_ch10

3154 days ago by takepwave

Hiroshi TAKEMOTO (take@pwv.co.jp)

Sageでグラフを再現してみよう:集合知プログラミング第10章

この企画は、雑誌や教科書にでているグラフをSageで再現し、 グラフの意味を理解すると共にSageの使い方をマスターすることを目的としています。

今回は、集合知プログラミング の第10章にできてきますニュースのクラスタリング図10.1「ニュースストーリーのクラスタリングのデンドログラム」 と因子分析したを取り上げます。

あらかじめ読み込んでおくライブラリ

3章の例に加えて、NMFの計算を行うnnmf.pyを読み込んでいます。

また、split.sageは、日本語をプログラム内で含むため、split.pyではなくsplit.sageとして保存してあります。

# RとPandasのデータフレームを相互に変換する関数を読み込む # Rの必要なライブラリ r('library(ggplot2)') r('library(jsonlite)') # RUtilの読み込み load(DATA + 'RUtil.py') 
       
# 必要なライブラリを読み込む load(DATA+'split.sage') load(DATA+'clusters.py') load(DATA + 'nnmf.py') 
       

日本語フォントの指定

3章と同様にImageFontに日本語のTrueタイプフォントを指定することで デンドログラムの文字化けを回避します。

# デンドログラムで日本語を表示するための修正 from PIL import Image,ImageDraw,ImageFont font = ImageFont.truetype('/usr/local/share/Fonts/MS Mincho.ttf', 14) 
       

リスト、辞書に含まれる日本語の表示

pythonの問題として、リストや辞書に含まれる日本語が表示できないという問題があります。

そこで、taichino.com永遠のネバーランドさんのページ で紹介されていた方法を使って日本語表示関数ppを使用しました。

# http://taichino.com/programming/1599 # から引用させて頂きました import json def pp(obj): if isinstance(obj, list) or isinstance(obj, dict): orig = json.dumps(obj, indent=4) print eval("u'''%s'''" % orig).encode('utf-8') else: print obj 
       

何もしないで、分かち書きspliterの戻り値を表示すると、以下のように16進のコードで 表示されてしまいます。

spliter('私の名前は竹本です') 
       
['\xe7\xa7\x81', '\xe5\x90\x8d\xe5\x89\x8d', '\xe7\xab\xb9\xe6\x9c\xac']
['\xe7\xa7\x81', '\xe5\x90\x8d\xe5\x89\x8d', '\xe7\xab\xb9\xe6\x9c\xac']

pp関数を使うときれいに日本語の文字列が表示されます。

pp(spliter('私の名前は竹本です')) 
       
[
    "私", 
    "名前", 
    "竹本"
]
[
    "私", 
    "名前", 
    "竹本"
]

フィードリスト

10章では、ニュースと株価を扱っているため、日経新聞の情報を取得したいと思ったのですが、 日経新聞ではRSSの情報を配信していません。

そこで、林檎(Mac)好きなぞうさんのメインサイト で提供されている日本経済新聞 RSSを利用させて頂きました。

この他にYahoo、共同通信、Google、ロイター、朝日新聞のRSSを追加し、ニュースのクラスタリングを 行いました。

# ニュースフィードとして、日経、Yahoo、共同通信、Google、ロイター、朝日新聞を使用 # 詳しくは以下のコメントを外してください。 # printFile('feedlist.txt') 
       

日本語関係でオリジナルから修正した部分

getwords

getwordsは、htmlではなく、ニュースタイトルがそのまま渡されるので、 単にspliterを呼び出すように修正しました。

# 日本語対応のgetwordsのプロトタイプ版 def getwords(txt): return spliter(txt) 
       

getwordcounts

getwordcountsでは、フィード情報のtitleのみを使用するように修正しました。

import feedparser # フィード情報のtitleのみを使用するように修正(Hiroshi TAKEMOTO) # Returns title and dictionary of word counts for an RSS feed def getwordcounts(url): # Parse the feed d=feedparser.parse(url) wc={} # Loop over all the entries for e in d.entries: # Extract a list of words words=spliter(str(e.title)) for word in words: wc.setdefault(word,0) wc[word]+=1 return str(d.feed.title),wc 
       

ニュースから単語の抽出

feedlist.txtに指定されたニュースから単語を抽出してみます。

# feedlistにあるニュースの単語を抽出し、出現一覧を作成する apcount={} wordcounts={} feedlist=[line for line in file(DATA+'feedlist.txt')] for feedurl in feedlist: try: title,wc=getwordcounts(feedurl) wordcounts[title]=wc for word,count in wc.items(): junk = apcount.setdefault(word,0) if count>1: apcount[word]+=1 except: print 'Failed to parse feed %s' % feedurl 
       
# ニュースのタイトルなので、すべて単語登録する wordlist=[] for w,bc in apcount.items(): frac=float(bc)/len(feedlist) wordlist.append(str(w)) 
       

単語の出現頻度マトリックスの作成

3章と同様に出現頻度マトリックスの作成します。

def _word(word, wc): if word in wc: return wc[word] else: return 0 
       
# クラスタ分析用に収集したワードカウントを加工 names = [name for name,wc in wordcounts.items()] words = wordlist data = [[_word(word, wc) for word in wordlist] for blog,wc in wordcounts.items()] 
       

ニュースのクラスタ分析

単語の出現頻度マトリックスdataをhcluster関数に渡してブログのクラスタ分析をします。

その結果をdrawdegrogram関数に渡してデンドログラムを表示します(newsclust.pngにも保存します)。

3章のブログよりもはっきりとニュースがクラスタリングされているのが、見て取れます。

# ニュースのクラスタリング clust=hcluster(data) #printclust(clust,labels=names) drawdendrogram(clust,names,png=DATA + 'newsclust.png') showPNG('newsclust.png', fac=1) 
       
# 単語一覧や頻度マトリックスを表示するときにコメントを外して実行してください # pp(wordlist) # print data 
       

NMFを使った因子分析

クラスタリングの次は、NMFを使ってニュース毎の特徴ベクトルと単語毎の特徴ベクトルを 計算します。

# NMFを使って特徴を分解する v = matrix(data) weigths, feat = factorize(v, pc=10, iter=50) 
       
679316.465853
4686.43669381
4124.64966115
4028.11051799
3999.71989198
679316.465853
4686.43669381
4124.64966115
4028.11051799
3999.71989198

特徴の出力

おりじなるのshowfeaturesでは日本語が文字化けするため、 pp関数を使って表示するように修正しました。

また、上位6個の単語に対し、上位3位でかつ特徴の値がweightLimit(ここでは1.0をセット) を超えるものだけを出力するようにしました。

# 出力する重みの最小値を定義 weightLimit = 1.0 
       
# 日本語表示ができるように修正 def showfeatures(w,h,titles,wordvec): pc,wc=shape(h) toppatterns=[[] for i in range(len(titles))] patternnames=[] # Loop over all the features for i in range(pc): slist=[] # Create a list of words and their weights for j in range(wc): slist.append((h[i,j],wordvec[j])) # Reverse sort the word list slist.sort() slist.reverse() # Print the first six elements n=[s[1] for s in slist[0:6]] pp(n) patternnames.append(n) # Create a list of articles for this feature flist=[] for j in range(len(titles)): # Add the article with its weight flist.append((w[j,i],str(titles[j]))) toppatterns[j].append((w[j,i],i,str(titles[j]))) # Reverse sort the list flist.sort() flist.reverse() # Show the top 3 articles for f in flist[0:3]: if f[0] > weightLimit: pp(list(f)) print '' # Return the pattern names for later use return toppatterns,patternnames 
       

showfeaturesの出力

2012年7月26日は、ロンドンオリンピックサッカー女子の予選が始まったので、 「サッカー」、「なでしこ」がスポーツ関連のニュースの重要なキーワードになっています。

topp, pn = showfeatures(weigths, feat, names, wordlist) 
       
WARNING: Output truncated!  
full_output.txt



[
    "PR", 
    "0", 
    "光", 
    "2", 
    "1", 
    "割"
]
[
    13.281899554450645, 
    "社会 - 朝日新聞デジタル"
]

[
    "場所", 
    "名古屋", 
    "大相撲", 
    "日馬", 
    "千秋楽", 
    "全勝"
]
[
    9.4390771861032388, 
    "日本経済新聞 スポーツ:大相撲"
]
[
    2.6506193809313454, 
    "日本経済新聞 スポーツ:ゴルフ"
]

[
    "さん", 
    "1", 
    "戦", 
    "PR", 
    "勝", 
    "2"
]
[
    10.992519829496324, 
    "文化 - 朝日新聞デジタル"
]

[
    "サッカー", 
    "なでしこ", 
    "1", 
    "選手", 
    "女子", 
    "五輪"
]
[
    6.1989779542505463, 
    "オリンピック・パラリンピック特集 - 朝日新聞デジタル"
]
[
    6.094523098990015, 
    "サッカー日本代表ニュース - 朝日新聞デジタル"
]

...

    "人", 
    "首相", 
    "大統領", 
    "氏"
]
[
    10.165450647673419, 
    "国際 - 朝日新聞デジタル"
]
[
    7.1350801060878801, 
    "政治 - 朝日新聞デジタル"
]
[
    2.3958580737444528, 
    "日本経済新聞 政治"
]

[
    "4", 
    "6月", 
    "~", 
    "米", 
    "%", 
    "円"
]
[
    11.884816950250961, 
    "日本経済新聞 財務"
]
[
    4.4697326638500945, 
    "日本経済新聞 国際"
]
[
    3.4524044024389289, 
    "日本経済新聞 インターネット"
]

[
    "野村", 
    "CEO", 
    "6月", 
    "~", 
    "渡部", 
    "2"
]
[
    7.3402444150464516, 
    "日本経済新聞 企業"
]
[
    7.0019108669336187, 
    "日本経済新聞 すべて"
]
[
    6.6058144393328364, 
    "日本経済新聞 経済"
]
WARNING: Output truncated!  
full_output.txt



[
    "PR", 
    "0", 
    "光", 
    "2", 
    "1", 
    "割"
]
[
    13.281899554450645, 
    "社会 - 朝日新聞デジタル"
]

[
    "場所", 
    "名古屋", 
    "大相撲", 
    "日馬", 
    "千秋楽", 
    "全勝"
]
[
    9.4390771861032388, 
    "日本経済新聞 スポーツ:大相撲"
]
[
    2.6506193809313454, 
    "日本経済新聞 スポーツ:ゴルフ"
]

[
    "さん", 
    "1", 
    "戦", 
    "PR", 
    "勝", 
    "2"
]
[
    10.992519829496324, 
    "文化 - 朝日新聞デジタル"
]

[
    "サッカー", 
    "なでしこ", 
    "1", 
    "選手", 
    "女子", 
    "五輪"
]
[
    6.1989779542505463, 
    "オリンピック・パラリンピック特集 - 朝日新聞デジタル"
]
[
    6.094523098990015, 
    "サッカー日本代表ニュース - 朝日新聞デジタル"
]

...

    "人", 
    "首相", 
    "大統領", 
    "氏"
]
[
    10.165450647673419, 
    "国際 - 朝日新聞デジタル"
]
[
    7.1350801060878801, 
    "政治 - 朝日新聞デジタル"
]
[
    2.3958580737444528, 
    "日本経済新聞 政治"
]

[
    "4", 
    "6月", 
    "~", 
    "米", 
    "%", 
    "円"
]
[
    11.884816950250961, 
    "日本経済新聞 財務"
]
[
    4.4697326638500945, 
    "日本経済新聞 国際"
]
[
    3.4524044024389289, 
    "日本経済新聞 インターネット"
]

[
    "野村", 
    "CEO", 
    "6月", 
    "~", 
    "渡部", 
    "2"
]
[
    7.3402444150464516, 
    "日本経済新聞 企業"
]
[
    7.0019108669336187, 
    "日本経済新聞 すべて"
]
[
    6.6058144393328364, 
    "日本経済新聞 経済"
]

ニュース毎の重要キーワードの表示

つぎに、showarticlesを使ってニュースフィード単位に、 重要キーワードを出力します。特徴ベクトルの値がweightLimit 以下のキーワードは表示されないようにしました。

# 日本語表示ができるように修正 def showarticles(titles,toppatterns,patternnames): # Loop over all the titles for j in range(len(titles)): print titles[j].encode('utf8') + ":" # Get the top features for this article and # reverse sort them toppatterns[j].sort() toppatterns[j].reverse() # Print the top three patterns for i in range(3): # weightがweightLimitより大きなものだけ出力するように修正 if toppatterns[j][i][0] > weightLimit: print str(toppatterns[j][i][0]) # リストの文字列を表示するために、ppを使用 pp(patternnames[toppatterns[j][i][1]]) print # 1行空ける 
       

結果の分析

2012年7月26日の日経新聞のトップは、 「野村、体制刷新で巻き返し図る 渡部CEO辞任へ 」の記事で、 次に 「東証大引け、5日ぶり反発 業績不安薄れ景気敏感株に買い」 の記事が話題になっていました。

以下の出力で、「日本経済新聞 すべて」をみると ["野村", "CEO", "6月", "~", "渡部", "2"]と ["株", "反発", "東証", "小幅", "時", "円"]が きちんと抽出されています。

ニュースフィードのタイトルを抽出するだけの簡単な単語頻度マトリックスと NMF因子分析を使うことによって各ニュースの重要な記事のポイントとなる 単語が抽出できることが分かりました。

showarticles(names, topp, pn) 
       
WARNING: Output truncated!  
full_output.txt



日本経済新聞 科学:

日本経済新聞 国際:
4.46973266385
[
    "4", 
    "6月", 
    "~", 
    "米", 
    "%", 
    "円"
]
1.56222208197
[
    "野村", 
    "CEO", 
    "6月", 
    "~", 
    "渡部", 
    "2"
]

Top Stories - Google News:
2.25385967974
[
    "1", 
    "0", 
    "2", 
    "日", 
    "4", 
    "5"
]
1.28723425534
[
    "野村", 
    "CEO", 
    "6月", 
    "~", 
    "渡部", 
    "2"
]

Yahoo!ニュース・トピックス - 国内:

日本経済新聞 スポーツ:サッカー:
2.92494262279
[
    "サッカー", 
    "なでしこ", 
    "1", 
    "選手", 
    "女子", 
    "五輪"
]
1.81890385376
[
    "1", 
    "0", 
    "2", 

...

    "日", 
    "4", 
    "5"
]

日本経済新聞 政治:
4.6277673698
[
    "野村", 
    "CEO", 
    "6月", 
    "~", 
    "渡部", 
    "2"
]
2.39585807374
[
    "PR", 
    "光", 
    "人", 
    "首相", 
    "大統領", 
    "氏"
]

日本経済新聞 すべて:
7.00191086693
[
    "野村", 
    "CEO", 
    "6月", 
    "~", 
    "渡部", 
    "2"
]
2.38191543637
[
    "株", 
    "反発", 
    "東証", 
    "小幅", 
    "時", 
    "円"
]

日本経済新聞 スポーツ:大相撲:
9.4390771861
[
    "場所", 
    "名古屋", 
    "大相撲", 
    "日馬", 
    "千秋楽", 
    "全勝"
]

Yahoo!ニュース・トピックス - サイエンス:

Yahoo!ニュース・トピックス - エンターテインメント:
WARNING: Output truncated!  
full_output.txt



日本経済新聞 科学:

日本経済新聞 国際:
4.46973266385
[
    "4", 
    "6月", 
    "~", 
    "米", 
    "%", 
    "円"
]
1.56222208197
[
    "野村", 
    "CEO", 
    "6月", 
    "~", 
    "渡部", 
    "2"
]

Top Stories - Google News:
2.25385967974
[
    "1", 
    "0", 
    "2", 
    "日", 
    "4", 
    "5"
]
1.28723425534
[
    "野村", 
    "CEO", 
    "6月", 
    "~", 
    "渡部", 
    "2"
]

Yahoo!ニュース・トピックス - 国内:

日本経済新聞 スポーツ:サッカー:
2.92494262279
[
    "サッカー", 
    "なでしこ", 
    "1", 
    "選手", 
    "女子", 
    "五輪"
]
1.81890385376
[
    "1", 
    "0", 
    "2", 

...

    "日", 
    "4", 
    "5"
]

日本経済新聞 政治:
4.6277673698
[
    "野村", 
    "CEO", 
    "6月", 
    "~", 
    "渡部", 
    "2"
]
2.39585807374
[
    "PR", 
    "光", 
    "人", 
    "首相", 
    "大統領", 
    "氏"
]

日本経済新聞 すべて:
7.00191086693
[
    "野村", 
    "CEO", 
    "6月", 
    "~", 
    "渡部", 
    "2"
]
2.38191543637
[
    "株", 
    "反発", 
    "東証", 
    "小幅", 
    "時", 
    "円"
]

日本経済新聞 スポーツ:大相撲:
9.4390771861
[
    "場所", 
    "名古屋", 
    "大相撲", 
    "日馬", 
    "千秋楽", 
    "全勝"
]

Yahoo!ニュース・トピックス - サイエンス:

Yahoo!ニュース・トピックス - エンターテインメント:
# 3個以上のカウントのある語のリスト [ s for s in flatten(data) if s > 2] 
       
[3, 3, 3, 3, 3, 7, 3, 6, 5, 7, 6, 4, 3, 3, 4, 4, 4, 4, 3, 6, 3, 7, 4, 4,
5, 5, 3, 5, 4, 4, 3, 3, 3, 3, 4, 3, 3, 6, 3, 3, 3, 5, 3, 6, 3, 3, 6, 4,
4, 5, 3, 3, 4, 4, 6, 3, 3, 3, 3, 3, 3, 5, 7, 4, 5, 7, 5, 4, 3, 4, 4, 3,
9, 3, 3, 3, 8, 3, 7, 10, 5, 6, 3, 3, 3, 3, 12, 3, 3, 6, 3, 3, 3, 4, 5,
3, 3, 3, 3, 6, 6, 3, 6, 3, 4, 4, 3, 7, 3, 4, 5, 3, 3, 3, 7, 3, 3, 3, 3,
4, 4, 8, 4, 6, 3, 4, 5, 5, 5, 3, 6, 3, 3, 3, 3, 4, 13, 5, 6, 4, 3, 3, 5,
4, 5, 4, 3, 3, 3, 16, 18, 6, 4, 11, 5, 7, 4, 3, 7, 3, 3, 14, 5, 3, 3, 3,
3, 9, 3, 3, 3, 3, 3, 3, 5, 3, 5, 3, 5, 3, 4, 3, 5, 3, 3, 4, 3, 3, 3, 3,
3, 3, 4, 3, 16, 9, 7, 6, 4, 5, 3, 8, 16, 18, 8, 5, 10, 3, 3, 5, 5, 3, 3,
3, 4, 9, 3, 3, 4, 4, 12, 11, 10, 3, 5, 5, 3, 4, 11, 4, 4, 3, 3, 4, 3, 6,
3, 3, 3, 3, 4, 3, 3, 4, 4, 3, 4, 4, 3, 3, 3, 5, 5, 5, 8, 3, 8, 3, 3, 3,
3, 3, 3, 7, 4, 4]
[3, 3, 3, 3, 3, 7, 3, 6, 5, 7, 6, 4, 3, 3, 4, 4, 4, 4, 3, 6, 3, 7, 4, 4, 5, 5, 3, 5, 4, 4, 3, 3, 3, 3, 4, 3, 3, 6, 3, 3, 3, 5, 3, 6, 3, 3, 6, 4, 4, 5, 3, 3, 4, 4, 6, 3, 3, 3, 3, 3, 3, 5, 7, 4, 5, 7, 5, 4, 3, 4, 4, 3, 9, 3, 3, 3, 8, 3, 7, 10, 5, 6, 3, 3, 3, 3, 12, 3, 3, 6, 3, 3, 3, 4, 5, 3, 3, 3, 3, 6, 6, 3, 6, 3, 4, 4, 3, 7, 3, 4, 5, 3, 3, 3, 7, 3, 3, 3, 3, 4, 4, 8, 4, 6, 3, 4, 5, 5, 5, 3, 6, 3, 3, 3, 3, 4, 13, 5, 6, 4, 3, 3, 5, 4, 5, 4, 3, 3, 3, 16, 18, 6, 4, 11, 5, 7, 4, 3, 7, 3, 3, 14, 5, 3, 3, 3, 3, 9, 3, 3, 3, 3, 3, 3, 5, 3, 5, 3, 5, 3, 4, 3, 5, 3, 3, 4, 3, 3, 3, 3, 3, 3, 4, 3, 16, 9, 7, 6, 4, 5, 3, 8, 16, 18, 8, 5, 10, 3, 3, 5, 5, 3, 3, 3, 4, 9, 3, 3, 4, 4, 12, 11, 10, 3, 5, 5, 3, 4, 11, 4, 4, 3, 3, 4, 3, 6, 3, 3, 3, 3, 4, 3, 3, 4, 4, 3, 4, 4, 3, 3, 3, 5, 5, 5, 8, 3, 8, 3, 3, 3, 3, 3, 3, 7, 4, 4]