Fisher準則一維聚類
阿新 • • 發佈:2018-01-24
clas += log 間距 numpy spa post source 變化
在做FAQ系統時,用戶輸入一個查詢之後,返回若幹個打好分數的文檔。對於這些文檔,有些是應該輸出的,有些是不應該輸出的。那麽應該在什麽地方截斷呢?
這個問題其實是一個聚類問題,在一維空間中把若幹個點聚成兩類。
聚類就有標準:類內距離盡量小、類間距離盡量大。
由此想到Fisher準則。
那麽給定一個浮點數組,尋找這個浮點數組的fisher點,應該如何實現呢?
fisher準則目標函數為fisher=(s1+s2)/(m1-m2)^2
。
可以用O(n)復雜度實現。
但是有沒有更快速的方法呢?
從左往右掃描,如果fisher準則函數是一個類似二次函數的形狀,那麽就可以利用“三分法”求極值的策略將復雜度降為O(logN)。
為了驗證是否滿足“類似二次函數”的特性,我隨機出一堆數字,求fisher曲線。
實驗結果:並不滿足“類似二次函數”,但是大概率地滿足此條件。
本實驗一共測試了10000組長度在3~1000之間的數組。
下面的0,1,2...表示曲線斜率方向變化次數,右面數字表示出現次數。
可以發現,那些 不滿足“類似二次函數”的圖像看上去也都近似“V”形。
所以,如果為了較高的速度,可以使用三分法;如果為了較高的準確率,可以使用O(n)掃描法。
0: 7668
1: 1732
2: 416
3: 129
4: 34
5: 17
6: 3
7: 1
實驗代碼如下:
import numpy as np
import tqdm
def getfisher(a):
s = np.sum(a)
ss = np.sum(a * a)
now_s = 0
now_ss = 0
ret = []
for i in range(len(a) - 1):
now_s += a[i]
now_ss += a[i] ** 2
l_s = now_s / (i + 1)
l_ss = now_ss / (i + 1)
r_s = (s - now_s) / (len(a) - 1 - i)
r_ss = (ss - now_ss) / (len(a) - 1 - i)
fisher = (l_ss + r_ss) / (l_s - r_s) ** 2
ret.append(fisher)
return ret
def checkright(a):
dir = 0
cnt = 0
for i in range(1, len(a)):
if dir != np.sign(a[i] - a[i - 1]) and dir != 0 and np.abs(a[i]-a[i-1])>1e-2:
cnt += 1
dir = np.sign(a[i] - a[i - 1])
return cnt
def main():
c = dict()
for i in tqdm.tqdm(range(10000)):
x = np.sort(np.random.rand(np.random.randint(3, 1000)))
f = getfisher(x)
# plt.plot(x[:-1], f)
cnt = checkright(f)
if cnt not in c:
c[cnt] = 0
c[cnt] += 1
# plt.show()
print(c)
if __name__ == '__main__':
main()
Fisher準則一維聚類