1. 程式人生 > >Fisher準則一維聚類

Fisher準則一維聚類

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準則一維聚類