graph_collective_inteligence_ch3

1383 days ago by takepwave

Hiroshi TAKEMOTO (take@pwv.co.jp)

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

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

今回は、集合知プログラミング の第3章にできてきます似たブログをグルーピイングした図3.3「ブログのクラスタのデンドログラム」 を取り上げます。

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

MeCabを使った単語切り分け

以前chasenを使って同じグラフの表示を行った python/集合知3章 では、chasenが英語の単語を扱えないため、トリッキーな処理を行いましたが、 MeCabではそのまま英語の単語が扱えるので、すっきりした形になります。

MeCabのインストール方法については、 MeCab を参照してください。

日本語のテキストから分かち書きの単語の内、partsに指定した形容詞、形容動詞、副詞、連体詞、名詞、動詞、未知語を 日本語の単語として抽出することにしました。また、minlenより小さな単語は取り除くようにしました。

pythonようのMeCabは、Taggerメソッドで生成し、parseメソッドで解析するというのが通常ですが、 ここでは単語の品詞と動詞の原形を使用したいので、parseToNodeメソッドでノードに分割した結果を 使って単語抽出を行いました。

import sys import MeCab import re # splitの定義 def split(txt, minlen=2): m = MeCab.Tagger ("-Ochasen") n = m.parseToNode(txt) parts = ['^形容詞.*', '^形容動詞.*', '^副詞.*', '^連体詞.*', '^名詞.*', '^動詞.*', '^未知語.*'] ret = [] n = n.next while n.next != None: features = n.feature.split(',') if len(features) > 7: surface = features[6] else: surface = n.surface feature = features[0] n = n.next if len(surface) >= minlen: for part in parts: pat = re.compile(part) if pat.match(str(feature)): #print surface, feature ret.append(surface) return ret 
       

単語区切りのテスト

まずは、簡単なテキストを入力して正しく単語が抽出できているか確認してみます。

splitの結果は、単語で区切られた文字列のリストを返すのですが、そのままだと \xnnの形式で表示されてしまうので、1単語ずつ表示してみました。

# テスト用のコード txt = 'オリジナルのソースは、原書著者TobesのページからPCI_Code.zipと してダウンロードできます。' #print split(txt) for s in split(txt): print s 
       
オリジナル
ソース
原書
著者
Tobes
ページ
PCI
Code
zip
する
ダウンロード
できる
オリジナル
ソース
原書
著者
Tobes
ページ
PCI
Code
zip
する
ダウンロード
できる

Sageのpythonを日本語対応する方法

Sageはそのままではunicodeから文字列への変換でエラーが発生します。

	UnicodeEncodeError: 'ascii' codec can't encode characters in position
	0-10: ordinal not in range(128)	

これに対する対応としては、Sageのpythonのsite-packagesにsitecustomize.pyを以下のように 作成します。私の場合には、/usr/local/sage/local/lib/python/site-packages/に配置しました。

	import sys
	sys.setdefaultencoding('utf-8')	

配置が完了したら、sageを再起動してください。

日本語対応のgetwords

splitができたので、htmlから単語のリストを返す getwordsはHTMLからタグをのぞいてsplitを呼び出すだけの処理となります。

# 日本語対応のgetwordsのプロトタイプ版 def getwords(html): ret = [] txt=re.compile(r'<[^>]+>').sub('',html) return split(txt) 
       

フィードリスト

フィードリストは、ワークシートのdataに含まれていますので、 ワークシートの上部のDataプルダウンからfeedlist.txtを選択して 編集することができます。

Sageのワークシートは実行可能なドキュメントですので、 自分の興味のあるブログリストをfeedlistにセットしてその結果を表示することが できます。

# 使用するフィードリスト # printFile("feedlist.txt") 
       

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

getwordcounts

Sageのpythonのデフォルト言語がasciiであるため、そのままでは 日本語のテキストを処理しようとするとエラーになります。

getwords関数を呼び出している部分でstr関数を使ってunicodeを 文字列に変換しています。

import feedparser # strを使ってunicodeから文字列に変換するように修正(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: if 'summary' in e: summary=e.summary else: summary=e.description # Extract a list of words words=getwords(str(e.title+' '+summary)) for word in words: wc.setdefault(word,0) wc[word]+=1 return str(d.feed.title),wc 
       

日本語フォントの指定

PILライブラリは、そのままでは日本語を表示できませんが、 ImageFontに日本語のTrueタイプフォントを指定することで 文字化けなしに表示できるようになります。

また、日本語を表示する部分では、unicodeに変換してからtextメソッド に渡す必要があります。

    utxt = unicode(labels[clust.id],'utf-8')
    draw.text((x+5,y-7),utxt,(0,0,0),font=font)	

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

ブログから単語の抽出

集合知3章の例題の通り、feedlist.txtに指定されたブログから 単語を抽出してみます。

# feedlistにあるブログの単語を抽出し、出現一覧を作成する # 2015/08/08 初版から時間が経過したので、なくなったブログもありました。 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(): 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) if frac>0.1 and frac<0.5: wordlist.append(str(w)) 
       
Failed to parse feed http://inoue-waka.blog.ocn.ne.jp/kimochi/atom.xml

Failed to parse feed http://rssblog.ameba.jp/otobe-ayako/rss20.xml

Failed to parse feed http://star-studio.jp/kinoshita-ayumi/rss.php

Failed to parse feed http://hiromiteatime.blog.ocn.ne.jp/time/atom.xml

Failed to parse feed http://kensuke.livedoor.biz/atom.xml

Failed to parse feed http://blog.qlep.com/syndicate/RSS/24407/

Failed to parse feed http://www.motokari.jp/blog/atom.xml

Failed to parse feed http://rssblog.ameba.jp/wakatsuki/rss20.xml
Failed to parse feed http://inoue-waka.blog.ocn.ne.jp/kimochi/atom.xml

Failed to parse feed http://rssblog.ameba.jp/otobe-ayako/rss20.xml

Failed to parse feed http://star-studio.jp/kinoshita-ayumi/rss.php

Failed to parse feed http://hiromiteatime.blog.ocn.ne.jp/time/atom.xml

Failed to parse feed http://kensuke.livedoor.biz/atom.xml

Failed to parse feed http://blog.qlep.com/syndicate/RSS/24407/

Failed to parse feed http://www.motokari.jp/blog/atom.xml

Failed to parse feed http://rssblog.ameba.jp/wakatsuki/rss20.xml

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

wordlistにはすべてのブログに出現した単語のリストが入っているため、 各ブログの単語の出現頻度を計算する場合、ブログに指定単語が含まれる かをチェックする必要があります。_word関数を以下のように定義して クラスタ分析に渡す、出現頻度マトリックスの作成します。

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

ブログのクラスタ分析

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

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

アメブロの芸能人のブログが固まっていたり、ちょっとお固そうなブログが集まっていたりとそれらしい分類が されているのが見て取れます。

load(DATA+'clusters.py') 
       
# ブログのクラスタリング clust=hcluster(data) #printclust(clust,labels=blognames) drawdendrogram(clust,blognames,png=DATA + 'blogclust.png') showPNG('blogclust.png')