root/lang/cplusplus/misc/clustercpp/python/wardcluster/hatenasample.py

Revision 12223, 6.1 kB (checked in by ayu, 4 years ago)
  • added example.
  • Property svn:executable set to *
Line 
1#!/usr/local/Python25/bin/python
2# _*_ coding: utf-8 _*_
3# Copyright (C)  2007 Ayukawa Hiroshi
4from collections import defaultdict
5from operator import add
6from itertools import groupby, chain
7import math
8import types
9
10import feedparser
11import MeCab
12
13from wardcluster import Matrix, DoubleVec, Ward
14
15
16MECAB_ENCODING = "utf-8"
17
18MEISHI = u"名詞".encode(MECAB_ENCODING)
19MICHI = u"未知語".encode(MECAB_ENCODING)
20GARBAGECLUSTER = 1
21BIGCLUSTER = 8
22
23URL = "http://b.hatena.ne.jp/hotentry?mode=rss"
24       
25def findcutplane(history, histmap):
26    """
27    平均高さで斬った切断面を求めます。
28    """
29    avgdistance = sum(map(lambda h: h.distance, history)) / float(len(history))
30    for i, h in histmap.iteritems():
31        if h.distance > avgdistance:
32            if not h.index1 in histmap or histmap[h.index1].distance <= avgdistance:
33                yield h.index1
34            if not h.index2 in histmap or histmap[h.index2].distance <= avgdistance:
35                yield h.index2
36
37def findleaf(t, leaftype=types.IntType):
38    """
39    ツリーの葉全体を返します。
40    """
41    if type(t) == leaftype:
42        yield t
43    else:
44        for n in chain(findleaf(t[0]), findleaf(t[1])):
45            yield n
46   
47def allparent(top, desc, n):
48    if n == top:
49        yield n
50        return
51    yield n   
52    for m in allparent(top, desc, desc[n]):
53        yield m
54
55def repairbags(bags, findplane):
56    #切断面から除外されて落ちた葉を拾い集めてどこかに押し込む
57    for i in list(bags.keys()):
58        if len(bags[i]) <= GARBAGECLUSTER:
59            n = findplane(i)
60            if n:
61                bags[n]+=bags[i]
62                bags.pop(i)
63            else:
64                bags["other"]+=bags[i]
65                bags.pop(i)
66    return bags
67
68def cutdendrogram(history, size=None, subset=None, dual=False):
69    """
70    樹形図をカットする関数
71    """
72    #クラスタリング結果データの整理
73    if subset:
74        tree = dict(zip(sorted(subset), sorted(subset)))
75    else:
76        tree = dict(zip(range(size), range(size))) # i=>i(葉)という対応を作る。
77    histmap = {}
78    desc = {}
79    for h in history:
80        #print h.newindex, "<-", h.index1, "+", h.index2, "(", h.distance,")"
81        tree[h.newindex] = (tree[h.index1] if h.index1 in tree else h.index1,
82                            tree[h.index2] if h.index2 in tree else h.index2)
83        histmap[h.newindex] = h
84        desc[h.index1] = h.newindex
85        desc[h.index2] = h.newindex
86
87    #切断面決定
88    cutplane = set(findcutplane(history, histmap))
89
90    #切断した葉ごとに分ける、同時に、少ない枝を切断面から除外
91    bags = defaultdict(list)
92    for i in list(sorted(cutplane)):
93        bag = list(findleaf(tree[i]))
94        if len(bag) <= GARBAGECLUSTER: # 少ない枝を除外
95            cutplane.remove(i)
96            bags[i] = bag
97        elif dual and len(bag) > BIGCLUSTER: #大きい枝をさらに分割(dualが設定されている場合)
98            parents = set()
99            for b in bag:
100                parents.update(allparent(i, desc, b))
101            subhistory = filter(lambda h: h.newindex in parents, history)
102            subbags = repairbags(*(cutdendrogram(subhistory, subset=bag, dual=dual)))
103            #print "subbags", subbags
104            bags.update(subbags)
105        else:
106            bags[i] = bag
107    def findplane(n):
108        """
109        最も近い切断面を求める。
110        """
111        if n in histmap:
112            if histmap[n].index1 in cutplane:
113                return histmap[n].index1
114            if histmap[n].index2 in cutplane:
115                return histmap[n].index2
116        if n == history[-1].newindex:
117            return None
118        return findplane(desc[n])
119    return bags, findplane
120
121def create(html=False):
122    """
123    分割します。
124    """
125    tagger = MeCab.Tagger()
126    feed = feedparser.parse(URL) #rss取得&解析
127    tagnum = 0
128    kwmaster = {}
129    kwnum = 0
130
131    vecs = []
132    docs = []
133
134    #分かち書きしてベクトル作成
135    for e in feed.entries:
136        docs.append(dict(title=e["title"], link=e["link"]))
137        tags = map(lambda x: x["term"], e.get("tags", []))
138        body = u"。".join([e["title"]]+tags)
139        node = tagger.parseToNode(body.encode(MECAB_ENCODING))
140        vec = defaultdict(int)
141        while node:
142            if node.feature.startswith(MEISHI) or node.feature.startswith(MICHI):
143                if not node.surface in kwmaster:
144                    kwmaster[node.surface] = kwnum
145                    vec[kwnum] += 1
146                    kwnum += 1
147                else:
148                    vec[kwmaster[node.surface]] += 1
149            node = node.next
150        vecs.append(vec)
151    size = len(vecs)  #ベクトル本数
152
153    #行列作成
154    dimension = max(kwmaster.values())
155    matrix = Matrix()
156    for vec in vecs:
157        dencev = [0.0]*(dimension+1)
158        for k,v in vec.iteritems():
159            dencev[k] = v #math.log(v+1)
160        matrix.append(DoubleVec(dencev))
161
162    #クラスタリング実行
163    ward = Ward()
164    history = ward.cluster(matrix, size)
165
166    #樹形図をカットする
167    bags = repairbags(*(cutdendrogram(history, size, dual=True)))
168
169    #結果表示
170    if html:
171        print "<html>"
172        print "<body>"
173        for i in bags.keys():
174            print "Group [", i, "]", "-"*20 #, "<br/>"
175            for j in bags[i]:
176                print (u"<a href='"+docs[j]["link"]+"'>"+docs[j]["title"]+u"</a>").encode("utf-8")
177        print "</body>"
178        print "</html>"
179    else:
180        for i in bags.keys():
181            print "Group [", i, "]", "-"*20
182            for j in bags[i]:
183                print j, docs[j]["title"].encode("utf-8")
184           
185if __name__ == "__main__":
186    import optparse
187   
188    parser = optparse.OptionParser(u"""
189    はてなブックマークのホットエントリーの記事をなんとなーく分類します。
190    """)
191    parser.add_option("-H", "--html", dest="html", default=False, action="store_true",
192                      help=u"結果をHTMLとして出力します。", default=False)
193    (options, args) = parser.parse_args()
194   
195    create(options.html)
196
Note: See TracBrowser for help on using the browser.