异端审判器!一个泛用型文本聚类模型的实现(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”、“>"'>” 呢?微信

或许你已经不耐烦了:这点安全素养仍是有的!检测关键字和特殊符号呀!markdown

你已经不打算让我再“若是”下去了:没有什么是一段正则表达式搞不定的,若是有,那就该再学一次。网络

好,可是如今,咱们须要的是,用同一个模型实现上述全部场景——当字符串有长有短,它要将长度异常的字符串分开。当有常规字符串和包含特殊符号的字符串,它能把特殊的那些拎出来。当字符串混有不一样的语言,它能进行“净化”。甚至,还有各类不在乎料之中的情形。app

这段代码就像是在宗教战争中审判异端,不管是中出了一个叛徒仍是干脆分裂成了两类,它老是能根据字符串的长相,把少数派给抓出来。oop

若是你刚好作过一些事,例如探索深度学习对网络安全的应用,相信你看着数据集,能很快想到这个“异端审判器”的实用价值。

让咱们默契地眨眨眼。在后文里,咱们会实现这样一个玩具。

主教的自我修养:看脸

北京与成都之间相距再远,也能够用欧式距离轻松度量。但 “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)。欢迎留言讨论,咱们会尽量回复。

感谢您的阅读。

相关文章
相关标签/搜索