上文連接:異端審判器!一個泛用型文本聚類模型的實現(1)前端
上回,咱們提出了一種只要輸入一堆字符串,就能根據字符串的構造挑揀出「少數派」,以識別異常參數的構想。咱們將它稱做「異端審判」。python
前文中咱們已經定義好了一些必要概念,並寫出了函數實現。咱們的程序遞進地量化了字符之間的差別、字符串之間的差別,最終獲得了字符串集合之間的差別。有了這項指標,咱們就能完成分揀工做。微信
在生活中,咱們常有幾排人一塊兒合影的經歷。有時是前排蹲下後排站立,有時是矮個子站在前排高個子位居後排。不妨假想一下,若是你就是那位攝影師,正指揮你們列隊,你習慣於怎樣安排隊形呢?app
一般狀況下,你會直接要求站成大體均勻的兩排,再逐個調整細節,直到整個隊形看上去使人滿意。函數
這爲咱們識別「異端」提供了靈感。post
想象一位「主教」威立於尖塔的陽臺,望着城樓下的人羣,如今他要作的就是將人分紅兩類,一類大體可信,一類有些可疑,再逐個把後者中的信衆移進前者,「異端」天然被剩下。優化
這篇文章中,咱們就是要實現這樣一件事。spa
咱們先將每一個輸入都視做單獨的一類,以啓動整個流程。整個全集記做 C
。code
# 初始化
# 輸入一個列表,如['a','b','c']
# 輸出一個把每一個元素都封裝爲列表的列表,如[['a'],['b'],['c']]
def init(sample_list):
C = []
for x in sample_list:
C.append([x])
return C
複製代碼
基於此前定義的字符串集間距離(在文章中簡稱爲類間距離),選擇最接近的兩類,合併它們。cdn
這步操做聽上去很簡單,實際上確實也很簡單,但咱們會遇到一些麻煩:咱們一直使用列表來簡單表示集合這個數學概念,它們性質並不相同。集合的三個主要特性中,列表不知足無序性與互異性,所以須要一些額外的處理。
例如,找到最接近的兩類,不管如何咱們也須要計算出 n^2 個距離,這就不是一件輕鬆的事。咱們將最小距離記做d
——
def find_min(C):
# 邏輯告訴咱們不管怎樣作都必須計算兩兩之間的所有距離,這裏用一個二維列表來記錄
# 數學告訴咱們 a->b 與 b->a 的距離是同樣的,其實開銷能夠減少一半
# 做者告訴你們因爲我很懶,就不作這個優化了……
scale = len(C)
d = [[0 for i in range(scale)] for i in range(scale)]
min_d = classesDistanse(C[0], C[1])
where_min_d = [0, 1]
for i in range(scale):
for j in range(scale):
d[i][j] = classesDistanse(C[i], C[j])
if i != j and d[i][j] < min_d:
min_d = d[i][j]
where_min_d = [i, j]
return where_min_d
複製代碼
找到了最小的 d
之後,就該合併它們了。在進行並運算時,咱們就會遇到列表與集合的性質差別、邏輯與運算的表示差別等問題,咱們從新定義運算函數來彌補這些誤差。
若是這部分讓你有點眩暈,不要爲此擔憂。你能夠將它們都視做 dirty hack,記住咱們只是在作一件簡單的事情:將剛纔已經找到的類間距離最小的兩個集合,合併成一個。
# C:=C-Ci-Cj+CiUCj
# 輸入全集列表C及其選出的兩個子列表Ci、Cj,如C=[['a'],['b'],['c']],Ci=['a'], Cj=['b']
# 須要注意的是,邏輯上,集合Ci與集合Cj是集合C的【元素】,而交併差都是【集合】之間的運算
# 輸出合併Ci與Cj以後的全集列表,如[[['a'],['b']],['c']]
def merge(C, i, j):
# 在數學上,集合[[1],[2]]與集合[[1,2]]的並集有三個元素,由於[1],[2],[1,2]都是徹底不一樣的元素。但在這裏的邏輯上,須要結果爲[[1,2]],因此另外定義了特殊的「交集」運算
# 交集與差集的運算是針對集合的(如[[1]])而非元素(如[1]),因此須要手動裝進列表再傳參。(其實已經特殊處理的交集運算無必要這樣作,但爲了邏輯一致遵照了統一的寫法)
C_u = special_union([C[i]], [C[j]])
C_d = difference(difference(C, [C[i]]), [C[j]])
C_n = C_d
C_n.append(C_u)
return C_n
複製代碼
咱們將最接近的兩類合併成一類了,而目標是「一刀切」,即把整個全集劃分爲大體均勻的兩類。因此咱們不斷查找最接近的兩類,將其合併,直到有某個集合的總量超過全集的一半。
# 查找規模最大的一個子列表
# 輸入全集C,如[[['a'],['b']],['c']]
# 輸出規模最大即集合內元素最多的列表的下標,如 0
def find_largest(C):
s = [0] * len(C)
max_s = len(C[0])
where_max_s = 0
for x in range(len(C)):
s[x] = len(C[x])
if s[x] > max_s:
max_s = s[x]
where_max_s = x
return where_max_s
複製代碼
每一個步驟都已經定義就緒,整個操做流程是這樣的:
def layerClassification(sample_list):
C = init(sample_list)
while True:
where_min_d = find_min(C)
i, j = where_min_d
C = merge(C, i, j)
where_max_s = find_largest(C)
if count_elem(C[where_max_s]) > 0.5 * len(C):
break
CM = C[where_max_s]
CN = difference(C, [CM])
return flatten(CM), flatten(CN)
複製代碼
這段代碼中提到了兩個輔助函數,其中 count_elem()
用於遞歸遍歷每一個集合中實際包含的字符串個數(而非子元素個數),分類的最終結果可能出現複雜的多維列表,而咱們只須要兩個簡單的一維列表用於表示兩個集合,定義 flatten()
來展開嵌套。
通過了剛纔的分類,如今咱們有了兩個集合。其中的一個包含了本來聚類性比較明顯的元素,他們可能長相很是近似,剩下一半隻是單純被剩下了而已,風馬牛齊聚一堂,看上去亂糟糟的。
接下來就是「微調」時間啦,咱們要從那個泥沙俱下的集合中,把「信衆」逐個移動到前面那個相對齊整的集合裏,從而將「異端」孤立。
這件事的關鍵是什麼時候中止:移到哪一步時,那個混亂的集合剛好只剩「異端」,而又沒有「異端」錯誤地赦免呢?
好在咱們的主教無需落子無悔,移錯了就倒回去嘛。他甚至能夠命人把全部結果都羅列出來,由他來判斷哪個方案是最好的。
那咱們不妨先不考慮決策的事情,提供所有方案就好。
咱們將分類方案記做 S
,一個分類方案由兩個集合構成,即{C1, C2},一樣地,咱們使用列表來表示。爲了在不斷移動的過程當中,存儲每一時刻的 C1 與 C2,而不做爲引用跟隨變化,咱們須要使用深拷貝。
def note_solution(S, C1, C2, N):
_C1 = copy.deepcopy(C1)
_C2 = copy.deepcopy(C2)
S.append([_C1, _C2])
N = N + 1
return S
複製代碼
基於此前定義的類間距離,咱們可以選到 C2 中最接近 C1 的樣本:
def select_min(C1, C2):
min_x = C2[0]
min_d = classesDistance(C1, min_x)
for x in C2:
temp = classesDistance(C1, x)
if temp < min_d:
min_d = temp
min_x = x
return min_x
複製代碼
把這個樣本從 C2 中放進 C1:
def update(min_x, C1, C2):
C1.append(min_x)
C2.remove(min_x)
return [C1, C2]
複製代碼
咱們不斷搬運元素,直到那個沒有聚類性的 C2 被搬空。記錄下這個過程當中全部分類方案。除了所有分類方案 S 之外,咱們同時維護另外一個列表,記錄被移動的元素,以便於撤回。因爲這個列表裏全部元素都是咱們每一步選出的到 C1 距離最小元素,不妨就將這個列表稱做 M
,整個過程以下:
def iterateClassification(C):
N = 0
S = []
M = []
C1 = C[0]
C2 = C[1]
while True:
note_solution(S, C1, C2, N)
min_x = select_min(C1, C2)
M.append(min_x)
update(min_x, C1, C2)
if len(C2) == 0:
break
del(S[0])
return S, M
複製代碼
到這裏爲止,咱們反覆運用上篇文章中定義的類間距離,作了一次粗選,又列出了全部微調生成的方案。最佳方案必然就是其中之一,留給咱們大主教的,只剩一個優化問題。
讓咱們下回再見~
編者按:
本文未完待續,敬請期待後續推送。參考文獻及整理後的示例代碼將在完整文章末給出。
文 / YvesX
反正你也猜不出我是作什麼的
編 / 熒聲
本文由創宇前端做者受權發佈,版權屬於做者,創宇前端出品。 歡迎註明出處轉載本文。文章連接:www.yvesx.com/archives/he…
想要訂閱更多來自知道創宇開發一線的分享,請搜索關注咱們的微信公衆號:創宇前端(KnownsecFED)。歡迎留言討論,咱們會盡量回復。
感謝您的閱讀。