其實這個算法也可以作爲聚類算法來用,計算出兩兩樣本之間的相似度,作爲這個算法裏邊的權重,可以去掉值很低的,然後進行聚類。我們假設一個圖有m個節點n條邊,label propagation的複雜度是O(kn) (不確定)k是迭代次數。在一般情況下,n<<m2 因此是個和圖規模線性關係的算法。如果聚類最後一步採用這種方法,那麼計算兩兩相似度得到圖結構,需要O(m2)應該是主要開銷。
之前也介紹過這個算法: http://blog.csdn.net/lgnlgn/archive/2011/01/29/6168756.aspx 算法叫label propagation,基本思想很簡單,就是一個節點的所在類別由與其相連的節點共同決定,實際就是類標的馬爾科夫隨機遊走過程。計算的時候需要迭代多次,每個節點選擇它鄰接節點類標數最多的那一個。
原版算法在選擇類標時候過於嚴格,只選一個;其實很容易想到,可以有各種擴展的辦法,比如選若干個,分別賦予隸屬度,這樣每個節點可以屬於多個類別,類別差距大的,可以確定成一個。
具體地就是
首先:每個節點把自己的類標傳播到鄰居;然後:每個節點根據鄰居傳過來的消息作出選擇
很容易看到,這兩步都可以同步地進行,因此非常適合用MapReduce的框架完成,很多圖算法基於隨機遊走模型的,其實都適用,如pagerank。
我簡單實現了一個python的版本,雖然是mapreduce的思路,但是純粹的順序執行。代碼不多 直接貼了,隨便建立一個文本文件 每行記錄一個節點id,它的鄰居節點和權重 tuple,如下
1,((2,1),(3,1),(4,1))
2,((1,1),)
3,((1,1),(4,1))
4,((1,1),(3,1))
代碼裏面解析直接用了eval方法 所以格式得注意保證。這個算法是無向圖的,因此邊要多寫一次,例如(1,4) 在(4,1)也要寫一份。
from itertools import imap
global gdata_file
global label_vector
global group_map
path = "d:/data/graph.txt"
def getMaxId():
return max(imap(lambda x:eval(x)[0],file(path,'r').xreadlines()))+1
def mapFunc(line): ##voting
node,edges = eval(line.strip())
## edges = ((node,1),) + edges
labels = label_vector[node]
if labels:
return [(edge+(labels,)) for edge in edges]
else:
return [(edge+({node:1},)) for edge in edges]
def mergeMap(a,b,weight):##merge b to a
for k,v in b.iteritems():
g = a.get(k)
if g:
a[k] = g + v * weight
else:
a[k] = v * weight
return a
def reduceFunc(map_phrase): ##merge
tmp = {}
for map_results in map_phrase:
for map_result in map_results:
l = tmp.get(map_result[0])
if l:
mergeMap(l,map_result[2],map_result[1])
else:
tmp[map_result[0]] = mergeMap(dict(),map_result[2],map_result[1])
return tmp
def select(m): ##select top k labels
u = sorted(m.items(),key = lambda x:x[1],reverse=True)
if len(u) >=3 and ((u[0][1] - u[1][1]) > (u[1][1] - u[2][1])):
uu = u[:2]
else:
uu = u[:3]
s = sum([x[1] for x in uu])
return dict( [(x[0],(x[1]+0.0)/s) for x in uu])
def close():
print label_vector
label_vector = [None] * getMaxId()
group_map = {}
if __name__ == '__main__':
for loop in xrange(7):
gdata_file = file(path,"r")
map_phrase = map(mapFunc, gdata_file.xreadlines())
group_map = reduceFunc(map_phrase)
gdata_file.close()
for k,v in group_map.iteritems():
label_vector[k] = select(v)
close()
每次map是一個解析圖結構的過程,將節點類標投得到每個鄰居,reduce過程就是簡單地把所有結果合併。從main開始,迭代多次,每次節點保留隸屬度最大的2~3個節點,作爲下一次計算的依據,最後close方法用來整理輸出。
我還有個疑問,就是向鄰居投票的時候,需要包含自己的類標嗎?這個我在map階段註釋掉了
熟悉python的話看起來不難,代碼寫得非常業餘和不規範。另外只測試能跑和簡單的正確性檢查。