模型排序中的逆序?qū)τ嬎?/h1>

一、問題背景

在使用機(jī)器學(xué)習(xí)模型預(yù)測的各類場景中孽江,對象間的序關(guān)系是否預(yù)測準(zhǔn)確,是考量模型效果的指標(biāo)之一番电。

正序數(shù)岗屏、逆序數(shù)可作為模型效果的衡量指標(biāo)。當(dāng)樣本的label之間存在序關(guān)系時漱办,由樣本間兩兩組成的pair这刷,若模型預(yù)測結(jié)果的序關(guān)系與label之間的序關(guān)系相同,稱為正序娩井;若模型預(yù)測結(jié)果的序關(guān)系與label之間的序關(guān)系相反暇屋,稱為逆序。當(dāng)正序數(shù)量越多洞辣、逆序數(shù)量越少時咐刨,表明模型對序關(guān)系的刻畫越準(zhǔn)確,模型效果越好扬霜。正逆序即為正序數(shù)量與逆序數(shù)量的比值定鸟。

計算正序數(shù)量、逆序數(shù)量時著瓶,一種直觀的方法是暴力構(gòu)造所有的pair對并一一驗證联予,在N個樣本下時間復(fù)雜度為O(N^2),當(dāng)N的量級在5萬時普通的服務(wù)器上已需要耗費(fèi)近10分鐘(Python)蟹但,在更大量級時計算時間無法忍受躯泰。

進(jìn)一步,如果在模型訓(xùn)練過程中华糖,希望在訓(xùn)練的每一輪迭代時都查看正逆序的值(據(jù)此做終止條件)麦向;或是在多組參數(shù)間使用正逆序做驗證調(diào)參的標(biāo)準(zhǔn)時,正逆序的計算速度問題則更為突出客叉。我們需要復(fù)雜度更低的算法來快速計算出正逆序诵竭。

二话告、數(shù)學(xué)表述

給定N個樣本,現(xiàn)有人工打好的每個樣本的label卵慰,記為label_1,\ label_2,\ ...,\ label_N以及模型預(yù)測出的每個樣本的分值沙郭,記為predict_1,\ predict_2,\ ...,\ predict_N

| A | 表示集合A的大小, \land表示邏輯與裳朋、\lor表示邏輯或
以下i, j 均在 1,2,...N中取值病线、且均滿足 i < j ,不再特別寫出

構(gòu)造出的所有pair集合為(注意我們只對label不同的樣本構(gòu)造pair
Pair = \{ (i, j) \ | \ label_i \ne label_j \}

正序集合為
Right = \{ (i, j) \ | \ (label_i > label_j \land predict_i \ge predict_j) \lor (label_i < label_j \land predict_i \le predict_j)\}

逆序集合為
Wrong = \{(i, j) \ | \ (label_i > label_j \land predict_i \le predict_j) \lor (label_i < label_j \land predict_i \ge predict_j) \}

嚴(yán)格正序集合為
StrictRight = \{(i,j) \ | \ (label_i > label_j \land predict_i > predict_j) \lor (label_i < label_j \land predict_i < predict_j) \}

嚴(yán)格逆序集合為
StrictWrong = \{(i, j) \ | \ (label_i > label_j \land predict_i < predict_j) \lor (label_i < label_j \land predict_i > predict_j) \}

由以上的定義容易看出鲤嫡,
| Right | + |StrictWrong | = | Wrong | + |StrictRight | = | Pair |

三送挑、算法思路

注:以下排序均指升序排列。

MergeSort

熟悉排序算法的同學(xué)應(yīng)已看出:這個問題像極了經(jīng)典的找逆序?qū)栴}暖眼。

給定數(shù)組a惕耕,找出滿足 i < j \ \land \ a[i] > a[j](i,j) pair對數(shù)量。使用MergeSort可以在 O(N \log N)時間計算出結(jié)果诫肠。

MergeSort計算逆序的思路較為直接司澎,使用的是Divide-and-Conquer的思想:

  • 將數(shù)組二分
  • 左半邊數(shù)組排好序并計算出其內(nèi)部的逆序數(shù) (可通過遞歸調(diào)用實現(xiàn))
  • 右半邊數(shù)組排好序并計算出其內(nèi)部的逆序數(shù) (可通過遞歸調(diào)用實現(xiàn))
  • 左半邊數(shù)組與右半邊數(shù)組合并,得到整體排好序的數(shù)組栋豫、以及左半邊與右半邊形成的逆序?qū)?shù)量
  • 返回整體排好序的數(shù)組挤安、總逆序數(shù)(左半邊內(nèi)部 + 右半邊內(nèi)部 + 左右半邊聯(lián)合形成)

嚴(yán)格逆序 StrictWrong

我們從計算嚴(yán)格逆序集合StrictWrong入手。

根據(jù)上一點關(guān)于MergeSort的討論笼才,一個自然的想法出現(xiàn)了:

先按label排序得到數(shù)組a漱受,接著計算數(shù)組a中的關(guān)于predict的逆序?qū)?shù)量。

但這與我們的原始需求仍有細(xì)微的差別:在按label排序后骡送,
我們對于下標(biāo)i, j只能得到i < j \Rightarrow label_i \le label_j
然而 i < j \nRightarrow label_i < label_j
因此直接計算逆序?qū)Φ脑挵合郏瑫?img class="math-inline" src="https://math.jianshu.com/math?formula=label" alt="label" mathimg="1">相等的情形也算進(jìn)來,得不到正確答案摔踱。

為了消除這一影響虐先,我們可以使用一個小trick:

在按label排序時,排序的key不僅僅使用label派敷,而是按照二元組 (label,\ predict)排序蛹批。
即:先按label排序,當(dāng)label值相等時篮愉,按predict排序

于是
i < j \Rightarrow (label_i,\ predict_i) \le (label_j,\ predict_j) \Rightarrow (label_i < label_j) \lor (label_i = label_j \land predict_i \le predict_j)
因此在這種情形下我們不會統(tǒng)計到任何label相等時的逆序?qū)Ω帧?稍?img class="math-inline" src="https://math.jianshu.com/math?formula=O(N%20%5Clog%20N)" alt="O(N \log N)" mathimg="1">時間內(nèi)計算得到 |StrictWrong |

嚴(yán)格正序 StrictRight

思路與嚴(yán)格逆序完全一致试躏,只是不等號方向變反猪勇。為了程序復(fù)用,可將predict正負(fù)號變反后颠蕴、直接調(diào)用嚴(yán)格逆序計算的程序得到結(jié)果

正序Right, 逆序Wrong, Pair

因為 | Right | + |StrictWrong | = | Wrong | + |StrictRight | = | Pair |
結(jié)合前面的結(jié)果泣刹, 只需計算出 | Pair |助析, 即可獲得 | Right ||Wrong |
| Pair | 的計算較為簡單,將label排好序后椅您,依次遍歷處理即可外冀,總復(fù)雜度為 O(N \log N)

結(jié)論及實驗

根據(jù)以上討論,各個集合的大小均可在O(N \log N) 時間計算得出掀泳。
隨機(jī)構(gòu)造的樣本在普通服務(wù)器上的實際運(yùn)行時間統(tǒng)計如下(Python)雪隧,可以看出優(yōu)化后的算法執(zhí)行時間大幅提升。

樣本數(shù)量N 基于 MergeSort 計算時間(秒) 基于 暴力法 計算時間(秒)
500 0.017 0.051
5000 0.164 5.048
50000 2.017 512.371

四开伏、總結(jié)與展望

總結(jié)

  • 將原始問題轉(zhuǎn)為經(jīng)典的求解數(shù)組逆序?qū)栴}膀跌,并使用經(jīng)典的MergeSort進(jìn)行求解。在問題轉(zhuǎn)化建模的過程中使用多字段排序等trick固灵,使得逆序?qū)栴}與原始問題完全等價。
  • 最終將計算正逆序的時間復(fù)雜度由 O(N^2) 優(yōu)化至 O(N \log N)

展望

  • 在模型訓(xùn)練的迭代過程中劫流,label保持不變巫玻,且每一輪參數(shù)變化帶來的預(yù)測變化較小。是否有可能依據(jù)predict的變化計算逆序?qū)Φ淖兓艋恪⒓铀儆嬎悖ú槐孛看味紡念^開始)仍秤,使得多輪迭代時的逆序?qū)τ嬎阏w時間縮短?
  • 是否存在分布式的解決方案可很,能夠應(yīng)對海量樣本數(shù)量的正逆序計算诗力?

  • 本文中使用的各類名詞及其對應(yīng)英文表達(dá),均為隨手自創(chuàng)我抠,僅為上下文敘述方便(除了mergeSort等大眾熟知的術(shù)語)

附苇本、代碼片段

(代碼中的變量true即為上文中的label,取"groundtruth"之意菜拓;pred即為上文中的predict

from itertools import groupby


class InversionCounter(object):
    @classmethod
    def merge_sort_count_sub(cls, vals):
        if len(vals) <= 1:
            return vals, 0

        n = len(vals)
        left_vals, left_cnt = cls.merge_sort_count_sub(vals[:n/2])
        right_vals, right_cnt = cls.merge_sort_count_sub(vals[n/2:])

        left_i = 0
        right_i = 0
        
        mid_cnt = 0
        new_vals = []
        while True:
            if left_vals[left_i][1] <= right_vals[right_i][1]:
                new_vals.append(left_vals[left_i])
                left_i += 1
            elif left_vals[left_i][1] > right_vals[right_i][1]:
                mid_cnt += (len(left_vals) - left_i)
                new_vals.append(right_vals[right_i])
                right_i += 1

            if left_i == len(left_vals):
                new_vals.extend(right_vals[right_i:])
                break
            if right_i == len(right_vals):
                new_vals.extend(left_vals[left_i:])
                break

        return new_vals, left_cnt + mid_cnt + right_cnt


    @classmethod
    def merge_sort_count_strict_right(cls, trues, preds):
        neg_preds = (-p for p in preds)
        vals = zip(trues, neg_preds)
        vals.sort()
        return cls.merge_sort_count_sub(vals)[1] 


    @classmethod
    def merge_sort_count_strict_wrong(cls, trues, preds):
        vals = zip(trues, preds)
        vals.sort()
        return cls.merge_sort_count_sub(vals)[1]


    @classmethod
    def merge_sort_count_right(cls, trues, preds):
        return cls.merge_sort_count_pair(trues) - cls.merge_sort_count_strict_wrong(trues, preds)


    @classmethod
    def merge_sort_count_wrong(cls, trues, preds):
        return cls.merge_sort_count_pair(trues) - cls.merge_sort_count_strict_right(trues, preds)


    @classmethod
    def merge_sort_count_pair(cls, trues, preds=None):
        '''
            preds: dummpy variable, no need inside function
        '''
        trues = sorted(trues)
        acc_num = 0
        pair = 0
        for k, ks in groupby(trues):
            current_num = sum(1 for _ in ks)
            acc_num += current_num
            pair += (len(trues) - acc_num) * current_num

        return pair
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者

  • 序言:七十年代末瓣窄,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子纳鼎,更是在濱河造成了極大的恐慌俺夕,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贱鄙,死亡現(xiàn)場離奇詭異劝贸,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)逗宁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進(jìn)店門映九,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人疙剑,你說我怎么就攤上這事氯迂〖” “怎么了?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵嚼蚀,是天一觀的道長禁灼。 經(jīng)常有香客問我,道長轿曙,這世上最難降的妖魔是什么弄捕? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮导帝,結(jié)果婚禮上守谓,老公的妹妹穿的比我還像新娘。我一直安慰自己您单,他們只是感情好斋荞,可當(dāng)我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著虐秦,像睡著了一般平酿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上悦陋,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天蜈彼,我揣著相機(jī)與錄音,去河邊找鬼俺驶。 笑死幸逆,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的暮现。 我是一名探鬼主播还绘,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼送矩!你這毒婦竟也來了蚕甥?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤栋荸,失蹤者是張志新(化名)和其女友劉穎菇怀,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體晌块,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡爱沟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了匆背。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片呼伸。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出括享,到底是詐尸還是另有隱情搂根,我是刑警寧澤,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布铃辖,位于F島的核電站剩愧,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏娇斩。R本人自食惡果不足惜仁卷,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望犬第。 院中可真熱鬧锦积,春花似錦、人聲如沸歉嗓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鉴分。三九已至基矮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間冠场,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工本砰, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留碴裙,地道東北人。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓点额,卻偏偏與公主長得像舔株,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子还棱,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,691評論 2 361

推薦閱讀更多精彩內(nèi)容