天天看點

騰訊2019廣告算法大賽總結

寫在前面

第一次參加騰訊的廣告賽,這次比賽需要自己提取訓練資料和标簽,除了次元以外,資料在其他方面都很接近真實業務,我本身也是第一次接觸這樣的資料。最後初賽B榜成績85.2,排名第215。接下來做一些主觀和客觀的總結。

關于資料

今年的資料和往年一樣,需要自己先轉化為csv或者hdf5格式pandas.DataFrame.to_hdf。

在totalExposureLog.out中提取标簽,也就是日曝光,可以通過groupby(廣告id,年,月,日)這樣的方式來提取該廣告在當日出現的次數,即為曝光量。

ad_static_feature裡面記錄了廣告的一些元屬性,包括賬戶id,商品id,商品類别等。

ad_operation檔案中記錄了一些廣告的曆史修改記錄,在這裡出現的廣告均為cpc廣告。且static檔案中不是所有廣告有曆史修改記錄。

userdata中包含了使用者資料,在這次比賽中我沒有用到,不過聽别人說裡面有可以使用的資訊可以用來上分。

test為測試資料,隻有商品id等原始特征。在這裡面每條廣告id對應有不同的出價,需要分别預測曝光,且該曝光需要滿足下面的單調性。

沒有訓練集,需要自己建構特征來進行訓練,這次比賽最大的感覺就是非常接近真實業務,因為據我所了解,平常業務中也需要花很多時間與精力進行資料預處理。而在這道題中,訓練集需要從零開始建構,建構地越好(本質上是特征工程做的越好),訓練的模型越好。本題是一個回歸問題。

最後的評價名額有兩條:

騰訊2019廣告算法大賽總結

SAMPE要确定的是預測值和真實值的偏差。

騰訊2019廣告算法大賽總結

單調性要預測的是,是否你的預測曝光和出價成正比,單調性在最終的得分裡占比不小。最終的得分由SMAPE和單調性權重産生。

騰訊2019廣告算法大賽總結

資料預處理

Log資料大概有一億條,記憶體不夠的情況下需要先将資料進行切分。然後需要進行去重,大概有100萬條左右的重複資料。去重後可以按照前面所說的方法進行标簽的建立。

訓練資料選取隻在ad_operation内部有過曆史操作記錄的廣告id,因為如果不是cpc廣告也沒有訓練價值。

是以通過static和operation兩個資料,我們就可以建構初步的特征,從operation裡面提取出現過的id,再從static裡面找到該id對應的原始特征,同時在operation對每個Id也有建立這個操作,建立操作産生的特征(曝光時段等)在static裡面是沒有的。

同時需要對缺失值進行填充。對于缺失值太多的類似商品id這樣的特征可以直接舍棄。

在此階段大概可以生成十七萬左右的原始特征資料,将開始生成的标簽與其進行merge。得到初步的訓練集。此時未用到bid(出價)這個特征。

模型訓練

在生成初步的訓練集後,内部包含的特征有,廣告id,商品id,商品類别,賬戶id。。。這樣的類别特征(離散特征),也有像素材大小這樣的連續特征。我在本題中使用的是lightgbm。

有兩種方案可以處理,主要是針對離散特征:

  1. 針對lightgbm這樣的基于樹的模型,可以直接将離散特征輸入,但是需要提取标記,在lightgbm中
    lgb.Dataset(train_X, categorical_feature = categorical_list, label = train_y)
               
    将類别特征名單獨建立一個list,作為categorical_feature這個形參的實參。連續特征不需要做其他操作。
  2. 可以将類别資料進行one-hot操作,由于廣告資料每個離散特征可取值非常多,導緻次元非常大,這個時候記憶體無法正常容納,此時可以建立稀疏矩陣。
from sklearn.preprocessing import OneHotEncoder
from scipy import sparse
## onehot encoder
numerical_feature = ['material_size', 'old', 'ad_nums', 'ad_class_len', 'goods_id_len',
                    'bid_mean', 'time_mean']
one_hot_feature = ['ad_account_id', 'ad_class_id', 'goods_class',
        'build_time_category'] #'goods_id'
enc = OneHotEncoder()
# 先将數值特征放入
train_x = train_df[numerical_feature]
test_x = test_df2[numerical_feature]
train_y = train_df['expose_days']

train_X = train_df[one_hot_feature]
test_X = train_df[one_hot_feature]

data = pd.concat([train_X,test_X])

# 将類别特征one-hot之後放入
for feature in one_hot_feature:
    print(feature)
    enc.fit(data[feature].values.reshape(-1, 1))
    train_a=enc.transform(train_df[feature].values.reshape(-1, 1))
    test_a = enc.transform(test_df2[feature].values.reshape(-1, 1))
    train_x= sparse.hstack((train_x, train_a))
    test_x = sparse.hstack((test_x, test_a)) 
           

      這裡先将數值特征放入後,再放入one-hot特征。最後生成一個稀疏矩陣。即可開始進行訓練,并通過模型得到一個初步答案。驗證集這裡選的是最後一天也就是3月19日的資料進行驗證。到目前為止沒有涉及到規則,此時送出的話,使用A榜測試集應該已經可以拿到83-84左右的分數。

規則模型

規則模型需要有一定的業務了解(雖然部落客自身對于業務了解程度也不夠)。本題通過統計A測試集可以發現很多廣告id是老廣告id,有一部分(大概6000條)是新廣告id,新廣告id指的是在opetation中沒有出現過的廣告(無曆史操作記錄)。對于老廣告id,可以采取統計該廣告的曆史曝光,然後求得平均值,将其作為我們的預測值(也需要進行單調性調整)。這就是該題中一種常用的規則。在将規則預測的曝光,與原始訓練的曝光進行融合(替換)。

在初賽後期,官方更新了b榜資料,最後排名使用b榜資料得分,在b榜中,新廣告id占了三分之二左右,此時前面的規則模型提分效果降低。同時由于新廣告數量的增加,也舍棄了廣告id這個特征。因為在訓練集内這個特征内部全為老廣告id,可以預見該特征對新廣告id的泛化能力較弱。

調整單調性

在模型訓練以及規則後,由于沒有放入出價這個特征,是以預測時也未用到該特征,于是對于同樣的廣告id,雖然它有不同的出價,但此時我們預測的曝光量是相同的。前面沒有放入出價的目的就在于為了友善後面調整單調性,此時可以針對不同的出價,對初始預測曝光+bid/10000 便得到了最後預測的曝光量,該預測值滿足了“相同的廣告id,在出價越高的情況下,根據業務直覺,曝光量應該越高”的單調性。此時的a榜測試集分數應該可以達到85左右。

到上述為止,就完成了一個初步的模型,再想繼續上分就需要做一些更有意義的操作,對于lgb模型訓練,可以加入一些更有意義的特征,比如對于人群定向的統計特征,時間統計特征等,也可以對于統計特征生成一些組合特征,還可以通過目标編碼等建構新的特征等。對于規則模型,又可以加入一些更具有業務内涵的規則。比如

  1. 在多個不同屬性檔案中提取原始類别特征和統計特征,建構組合特征
  2. 提取使用者的曝光記錄,将其轉化為文本序列,然後使用word2vec對廣告id進行嵌入,可以得到廣告id的embedding
  3. 将曆史曝光資料中曝光量超過100的廣告id标記為大廣告id,預測新廣告id是否為大廣告id會出現樣本不均衡現象,通過下采樣結合多個分類模型組合的方式預測
  4. 對高維類别特征采用onehot編碼,結合稀疏矩陣,節省記憶體

最後在A榜的得分可以達到86.5左右。由于B榜更新了大量的新廣告id,選擇去掉了廣告id這個特征進行訓練,以及新加入了部分特征,選擇五折交叉驗證。但是總體思路和上述相差不大,最後得分85.2。

總結

這次比賽,問題出在可能還是自己對于業務背景不太了解,真實業務資料由于格式,髒資料等問題需要自己一步一步清洗,最後得到幹淨的訓練集,這可能需要花費很多時間與精力。個人對于CTR的了解仍然不夠深入,還需要更多學習。