異端審判器!一個泛用型文本聚類模型的實現(1)

給你的入侵檢測系統提供一個靈感。前端


若是給你一大堆用戶輸入,裏面有大量的中文地名,像是「北京」、「成都」、「東莞」,不幸的是,其中也混有一些羅馬地名,好比 「Singapore」、「New York」、「Tokyo」。你的任務是將它們分開,你會如何去作?python

固然,有不少方法能夠輕易作到。正則表達式

若是是一堆 「good」、「fine」、「not bad」、「amazing」、"nice" 的簡短反饋裏混有 「Fallout 4 is the epitemy of everything wrong with modern gaming, it has a total of 2 compelling quests, its gameplay is worse then the rest, and to top it off they added microtransactions to it. it is the worst of the fallout series.」 這樣的長篇抱怨呢?算法

你可能會想,這不更簡單了嘛,檢測字符串長度甚至標點符號數目就行呀。安全

若是是一堆 「12345678」、「5201314」、「password」裏混有「password' and (select count(*) from data)>0 and 'a'='a」、「>"'>」 呢?微信

或許你已經不耐煩了:這點安全素養仍是有的!檢測關鍵字和特殊符號呀!網絡

你已經不打算讓我再「若是」下去了:沒有什麼是一段正則表達式搞不定的,若是有,那就該再學一次。app

好,可是如今,咱們須要的是,用同一個模型實現上述全部場景——當字符串有長有短,它要將長度異常的字符串分開。當有常規字符串和包含特殊符號的字符串,它能把特殊的那些拎出來。當字符串混有不一樣的語言,它能進行「淨化」。甚至,還有各類不在乎料之中的情形。學習

這段代碼就像是在宗教戰爭中審判異端,不管是中出了一個叛徒仍是乾脆分裂成了兩類,它老是能根據字符串的長相,把少數派給抓出來。spa

若是你剛好作過一些事,例如探索深度學習對網絡安全的應用,相信你看着數據集,能很快想到這個「異端審判器」的實用價值。

讓咱們默契地眨眨眼。在後文裏,咱們會實現這樣一個玩具。

主教的自我修養:看臉

北京與成都之間相距再遠,也能夠用歐式距離輕鬆度量。但 「Beijing」 與 「Chengdu」 之間的距離呢?

咱們須要看臉,根據字符串「外貌」的特徵,去定義和量化這樣一種差別。

不難發現,字符串之間的距離至少應該包括以下組分:

  • 字符串長度差別(如 catmiaomiaomiaomiaomiao
  • 字符集差別(如 123abc
  • 字符序列差別(如 上海自來水水來自海上

長度差別

這有什麼好說的……長度爲5的字符串顯然比長度爲3的字符串多出一個2……

在此略過。

def strLengthDiffer(str1, str2):
	return abs(len(str1) - len(str2))
複製代碼

字符集差別

字符集的差別是爲了刻畫不一樣字符串在字符選擇上的差別,咱們應該對差別較大的字符串——特別是出現了不一樣類別的字符時——進行距離上的懲罰。

爲了實現這個目標,首先要定義字符間的距離。這裏,咱們把相同字符間距離定義爲 0, 同類字符(如ab)間距離定義爲 1,不一樣類字符間距離定義爲 10。

字符分類能夠爲小寫字母、大寫字母、數字和其餘,固然讀者也能夠根據本身的實際用途進行分類,把系統須要敏感識別的差別分爲不一樣的兩類。

有了字符間距離,咱們定義字符 A(1) 與字符集 B 的距離爲該 A(1) 到 B 中每個字符的距離的最小值。

在上述基礎上,咱們進一步定義字符集 A 到字符集 B 間的距離爲:A 中每個字符到 B 的距離的算術和。

顯然:

  • 字符距離(a, b) = 字符距離(b, a)
  • 字符到字符集距離(a, B) = 字符集到字符距離(B, a)
  • 字符集間距離(A, B) = 字符集間距離(B, A)

由此,咱們對字符集間距離完成了符合認知的定義。

def charSetDiffer(s1, s2):
    # 因爲筆者使用的代碼版本在這裏有更復雜的邏輯,就不提供代碼細節了
    # 已經講得這麼明確了,寫寫看吧
    return s
複製代碼

字符序列差別

對於開發者而言,用戶輸入是 alert("test") 仍是 aeelrstt""(),顯然有着徹底不一樣的含義。後面這種意味不明的字符串根本不會讓人多看一眼,而前者若是被用戶執行成功,那麼他後續多半會再搞些別的破壞,很是邪惡。

這個故事告訴咱們,字符序列的差別不容忽視。

在這裏,咱們使用 N-Gram 語言模型,藉助 N=2 時的 Gram 數目來度量兩個序列的差別。

若是你並不知道我在說什麼,那麼具體而言是像這樣的計算:

  1. 假設咱們有字符串 S1 與 S2。
  2. 將字符串 S1 每兩個連續字符做爲一個元素,構成集合 G1,同理也有 G2。
  3. 字符串 S1 與 S2 之間的序列差別就是 G1 與 G2 中不一樣元素的數目。顯然,你能夠經過他們的交集減去他們的並集取到該值。
def n_grams(a):
    z = (islice(a, i, None) for i in range(2))
    return list(zip(*z))
複製代碼
def groupDiffer(s1, s2):
    len1 = len(list(set(s1).intersection(set(s2))))
    len2 = len(list(set(s1).union(set(s2))))
    return abs((len2 - len1))
複製代碼

總算有了字符串間距離

到如今爲止,咱們對兩個字符串間三個形式維度的差別都有了量化,接下來作的就是經過精妙絕倫的加權求和,算出那個使人拍案叫絕的字符串間距離。

在此,筆者使用的方法是——

def samplesDistance(str1, str2):
    a = strLengthDiffer(str1, str2)
    b = charSetDiffer(str1, str2)
    s1 = n_grams(str1)
    s2 = n_grams(str2)
    c = groupDiffer(s1, s2)
    d = a+b+c
    return d
複製代碼

是的!簡單相加……

山不在高,有廟則有人送錦旗,算法不在複雜,有用就行。

你固然能夠根據本身的須要,去調節系統對於其中三個維度的不一樣敏感度,但筆者認爲字符集差別的值自然就比另外兩種差別的值要大,已經符合個人須要,就再也不調整啦。

你好像和他們不太同樣

有了字符串間的距離,進一步,就有一個字符串到另外一堆字符串的距離。咱們定義以下:

字符串樣本與字符串集合的距離 = 該字符串樣本到字符串集合中每一個字符串樣本的距離的算術平均值

即:

def sampleClassDistance(sample, class1):
    list_0 = []
    length = len(class1)
    for item in class1:
        list_0.append(samplesDistance(sample, item))
    return sum(list_0)/length
複製代碼

大家是兩類

由上一節的一個字符串到一堆字符串的距離出發,咱們能夠獲得一堆字符串到另外一堆字符串的距離。它的定義形式很類似:

字符串集合間的距離 = 該字符串集合中的每同樣本到字符串集合的距離的算術平均值 = 該字符串集合中每同樣本到另外一字符串中每同樣本的距離的算術平均值

即:

def classesDistance(class1, class2):
    list_0 = []
    class1 = flatten(class1)
    class2 = flatten(class2)
    m = len(class1)
    n = len(class2)
    for item1 in class1:
        for item2 in class2:
            list_0.append(sampleDistance(item1, item2))
    return sum(list_0)/(m*n)
複製代碼

類內無派,千奇百怪

同理,也能夠定義「類內距離」做爲一堆字符串內部的屬性。它在實際意義上可能有些接近於方差。咱們規定:

類內距離 = 該字符串集合到本身的距離

def innerClassesDistanse(class1):
    return classesDistance(class1, class1)
複製代碼

讓咱們停下來整理一下思路

到這裏你可能已經暈了,定義這麼多距離到底要幹嗎?

咱們說過,要把兩類不肯定的形式不一樣的字符串分開,關鍵是定義差別,也就是去量化「長得顯然不一樣」到底有多不一樣。

因而咱們發明了一些「距離」做爲量化屬性,兩個字符串之間,有長度不一樣、構成的字符不一樣、字符序列不一樣,那麼這兩個字符串就有可量化的距離。

兩個字符串有距離,那麼一個字符串到另外一類字符串、一類字符串到另外一類字符串、同一類字符串內部也有距離。

當你混跡人羣,最重要的事情是弄清誰是朋友、誰是敵人。而當你須要把人羣分爲兩類,最重要的事情就是知道兩類人有多不一樣,以及每類人內部有多一致

放在分類字符串的情景,就是要可以量化類間距離類內距離

嘿,這不,咱們已經有了 classesDistance()innerClassesDistanse()

就到這裏,咱們下次再會 :)


編者按:

本文未完待續,敬請期待後續推送。參考文獻及示例代碼將在完整文章中給出。

做者認爲清晰的描述能讓不會寫代碼的人寫出代碼,因此文中代碼來自並不會寫 Python 的朋友,代碼風格可能有些奇怪。

文 / YvesX

反正你也猜不出我是作什麼的

編 / 熒聲

本文已由做者受權發佈,版權屬於創宇前端。歡迎註明出處轉載本文。本文連接:knownsec-fed.com/2018-09-25-…

想要訂閱更多來自知道創宇開發一線的分享,請搜索關注咱們的微信公衆號:創宇前端(KnownsecFED)。歡迎留言討論,咱們會盡量回復。

感謝您的閱讀。

相關文章
相關標籤/搜索