廣告CTR預估中真實CTR的離散化方法

來源

在廣告CTR預估的過程中,有一個十分重要的特徵,那就是廣告本身的CTR,從直觀上也能明白,對於一個廣告,在曝光次數充分的前提下,基於統計的CTR很大程度上反應了廣告本身的信息。

作爲一個特徵,很自然的,我們可以直接把它作爲向量的某一維的數值放入樣本,這樣是有一定的作用的,但作用還不夠:首先,對於點擊率差距較小的廣告,它無法捕捉到相似性;其次,對於點擊率差距較大的廣告,也很難捕捉到差異性。這是因爲一維數值特徵的表達力欠缺引起的,那麼,怎麼解決這個問題呢?

設想,假如我們能有一種手段,能對CTR進行分類,那麼,我們就可以藉助One Hot Encoding的方法對這個特徵進行處理,這樣處理之後的特徵往往對線性模型更加友好,那麼怎麼實現這件事呢,我們有以下幾種考慮:

1.直接人工分段,人爲的根據CTR的分佈,來分區間;

2.使用GBDT,K-means等分類算法作映射

可以看到上述兩種方法,都有一個明顯的缺陷:依賴於先前觀測到的數據的分佈,這也就意味着,在特徵轉化的過程中,需要維護額外的數據結構,有沒有什麼稍微更加簡單的方式能做這件事呢?

我們希望存在一個函數滿足:

index=f(CTR)

最簡單的 f=round(CTR * (N-1)) ,其中N是我們預設的維度的數目,比如N=500,那麼CTR會被均勻的映射到0~499共500個維度上,這樣做有一定的效果,但是還不夠。

我們知道,CTR的分佈區間是在[0,1]上,但實際情況中卻往往不是這樣,實際的CTR往往非常的小,能夠超過10%點擊率的廣告幾乎是九牛一毛,所以,基於這樣的事實,我們是可以對CTR進行區間放縮的,定義一個 upperBound,一個lowerBound:

CTR_{new}=\frac{CTR_{original}-lowerBound}{upperBound-lowerBound}

實踐中也證明,在做完這樣一個放縮之後,CTR的分佈變得更加的均勻,實際中也取得了更好的效果


那麼該怎麼來評估這件事情呢?

對於一個一般的Category類型的特徵,要衡量它的好壞,我們會選擇卡方檢驗或者使用信息增益,對於信息增益,我們知道一般有如下定義:

IG(T)=H(C)-H(C|T)= -\sum_{i=1}^n{P(C_i)log_2(P(C_i))} +P(t)\sum_{i=1}^n{P(C_i|t)log_2{P(C_i|t)}} +P(\overline{t})\sum_{i=1}^nP(C_i|\overline{t})log_2P(C_i|\overline{t})

信息增益表徵了從總體中去掉該類特徵時,總體中丟失掉的信息量,也就客觀的反映了該特徵的重要性。

對於一份包含CTR與是否點擊(點擊爲1,否則爲0)的樣本,採用不同的CTR離散化方法,計算信息增益,信息增益大的,也就意味着這種方式可能比較有效。

計算信息增益的Python代碼如下:

import math

def entropy(p):
    if p <= 0 or p >= 1:
        return 0.0
    return - p * math.log(p, 2) - (1 - p) * math.log(1 - p, 2)


def calc_gain(lables, indices):
    max_index = max(indices)

    index_map = [[0, 0] for x in range(max_index + 1)]
    i = 0
    for index in indices:
        if lables[i] == 1:
            index_map[index][0] += 1
        else:
            index_map[index][1] += 1
        i += 1
    index_map = filter(lambda t: t[0] > 0 or t[1] > 0, index_map)

    # print "index map:", index_map

    total_positive_count = 0.0
    total_count = 0.0
    for x in index_map:
        total_positive_count += x[0]
        total_count += (x[0] + x[1])

    total_entropy = entropy(total_positive_count / total_count)

    conditional_entropy = 0.0
    for x in index_map:
        index_count = float(x[0] + x[1])
        conditional_entropy += (index_count / total_count) * entropy(x[0] / index_count)

    return total_entropy - conditional_entropy

同時,對於離散化之後的Index在每個桶內的分佈,我們可以畫一個頻率分佈直方圖,直覺上,這個直方圖越平緩越好,而不是像CTR分佈一樣,大家都擠在一堆,彼此不可分。以下是一個示例:

直接將CTR乘上500後數值分佈情況,可以看到數據分佈非常極端


仔細觀察這個頻率分佈直方圖,再聯繫到我們想要達到的目的:想要原本密集的區間的值儘可能的分散,原本分散的區間的值儘量的聚合。在圖像處理領域,有一類算法天然是爲了這類目的存在的:對比度增強算法

常用的對比度增強算法有對數變換、指數變換(伽馬變換)、灰度拉昇等,至於直方圖均衡化甚至是小波變換等一些更加複雜的算法,由於它們均依賴於數據的先驗分佈,因此我們暫時不作考慮:

對數變換:

s=c\cdot{log_{v+1}(1+v\cdot{r})} , r\in[0,1]

指數變換:

s=c\cdot{r^\gamma},r\in[0,1]

灰度拉伸:

s=\frac{1}{1+(\frac{m}{r+eps})^E}

我們嘗試着三種算法,其中指數變換的部分我們用開方實現,三種算法均事先作值域變換,默認與值域變換作對比,相關Python代碼如下:

def domain_transform(ctr, func):
    a = 0.002
    b = 0.3
    ctr = max(a, ctr)
    ctr = min(b, ctr)
    ctr = (ctr - a) / (b - a)
    fa, fb = func(0), func(1)
    assert fa >= 0
    return (func(ctr) - fa) / (fb - fa)


def map_func1(ctrs):
    return [domain_transform(ctr, lambda x: x) for ctr in ctrs]


def map_func2(ctrs):
    return [domain_transform(ctr, lambda x: math.log(1 + 100 * x)) for ctr in ctrs]


def map_func3(ctrs):
    return [domain_transform(ctr, lambda x: math.sqrt(x)) for ctr in ctrs]


def map_func4(ctrs):
    return [mat2gray(ctr) for ctr in ctrs]


def mat2gray(x):
    return x / (x + 0.01)

我們得到了如下的結果:

各種變換後CTR的分佈及對應的信息增益

圖中的gain爲信息增益,cv爲下標的覆蓋率,顯然是越高越好。

實際工程中,採用對數變換,取得了不錯的效果。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章