2019年華爲算法精英大賽--用戶人口屬性預測組比賽覆盤

距離比賽時間已經過去了將近一年。作爲第一次參加的算法類數據挖掘比賽,最終名次37,獲得了前Top3%的成績,算是不枉費研一暑假兩個月的努力吧,作爲一個渣渣,自然比不上排名靠前的很多大神,也比不上和我同時參加比賽得獎,直博交大的的師兄。近期由於面臨實習的原因,所以想好好整理一下當時比賽的思緒,希望爲自己之後的比賽可以有些借鑑,如果可以讓各位讀者有豁然開朗的地方,那也算是一點小小的貢獻吧。
在這裏插入圖片描述

比賽介紹

官網鏈接:算法精英大賽
比賽有兩個賽題,賽事題目如下:

賽題一:賬號用戶人口屬性預測

  • 嘗試解決的問題:對於手機設備廠商,獲取當前手機用戶的人口屬性信息(demographics)非常困難,當前華爲手機3.5億用戶中,大概只有5000萬用戶的性別和年齡信息,如何基於用戶的手機及使用偏好準確地預測其人口屬性信息是提升個性化體驗、構建精準用戶畫像的基礎

賽題二:CTR預測

  • 任務描述:基於用戶對廣告任務的歷史行爲和廣告任務屬性,選擇合適的算法預測用戶在特定上下文下對某個廣告任務的點擊概率。
  • 嘗試解決的問題:提高廣告點擊轉化率預估的準確性
  • 難點:廣告任務相對可推用戶數量非常少;有行爲的廣告任務較少,數據非常稀疏;廣告任務在投放週期的不同階段轉化率差異較大;存在誤點擊噪音數據;有效特徵識別困難

因爲當初是第一次參加此類的算法比賽,在諮詢導師的前提下,我們決定選擇難度稍小的人口屬性預測比賽。所以我們的賽題就這麼確定了。

初次探索

我們確定好了題目之後,就先來看了數據集。
主要分爲5個數據集。分別是

  • User_basic_info.csv:包含了用戶的性別、常住地址、手機型號、手機ram容量、ram剩餘容量比、rom容量、rom剩餘容量比、手機顏色、字體大小、上網類型、移動運營商和手機系統版本13個字段.
  • User_behavior_info.csv:包含了用戶標識、手機A特徵使用次數、手機B特徵使用次數、手機C特徵使用次數、手機D特徵使用次數、手機E特徵使用次數、手機F特徵使用次數、手機G特徵使用情況共9個字段。
  • User_app_actived.csv:包含了用戶標識、應用標識共2個字段。
  • User_app_usage.csv:包含了用戶標識、應用標識、使用時長、打開次數、使用日期共5個字段。
  • app_info.csv:包含了應用標識、應用類型共2個字段。
  • age_train.csv:訓練集中的年齡分佈。

訓練集和預測集的數據就是上述所說的內容。

首先我們對訓練集和測試集進行劃分。

age_test_UID_list=[]
for i in age_test['UID']:
    age_test_UID_list.append(i)
#age_test_UID_list

train_user_basic_info=user_basic_info[~user_basic_info['UID'].isin(age_test_UID_list)]
train_user_basic_info.sort_values('UID',inplace=True) 
train_user_basic_info.to_csv('./train/train_user_basic_info.csv',index=False)
train_user_basic_info=pd.read_csv('./train/train_user_basic_info.csv')
train_user_basic_info   #2010000 

進過上述代碼對幾個數據集進行劃分,發現訓練集一共包含2010000條數據,測試集包含502500條數據。這麼一看,數據量是非常大的,僅僅是讀進內存也是非常困難的。尤其是我們經過探索發現其中的一個表User_app_usage.csv中,因爲每一個APP在不同時間段的使用時長是不一樣的,也就是說每個APP需要30行纔可以囊括一個APP在30天內的使用時間。這樣一算,就包含兩個多億的數據量,是非常可以的。所以經過和隊員的討論,我們暫時摒棄User_app_usage.csv這個表格的數據(後來表明,這部分數據是非常重要的)。

但是這時候數據量依舊是非常大的。如何解決這個問題呢?一般的話有三種解決方法:1.租一臺雲服務器,當然需要非常高,花費也不少。2.使用Google colab等類似的雲平臺,但是我們發現他們對數據內存的讀取也存在嚴格的制約。3.自費配置一臺服務器。當然我們選擇了第3種,這裏需要感謝我們的導師,他非常支持我們的競賽。自掏腰包幫我們配置了128G的內存,GTX1080ti顯卡。這樣我們的配置問題就這樣迎刃而解了。

缺失值填充

回到數據本身。發現數據存在大量的缺失值。

print('city缺失值個數:',len(train_user_basic_info[train_user_basic_info.city.isnull()]))
print('ramcapacity缺失值個數:',len(train_user_basic_info[train_user_basic_info.ramcapacity.isnull()]))
print('ramleftration缺失值個數:',len(train_user_basic_info[train_user_basic_info.ramleftration.isnull()]))
print('romcapacity缺失值個數:',len(train_user_basic_info[train_user_basic_info.romcapacity.isnull()]))
print('romleftration缺失值個數:',len(train_user_basic_info[train_user_basic_info.romleftration.isnull()]))
print('fontsize缺失值個數:',len(train_user_basic_info[train_user_basic_info.fontsize.isnull()]))
print('ct缺失值個數:',len(train_user_basic_info[train_user_basic_info.ct.isnull()]))
print('os缺失值個數:',len(train_user_basic_info[train_user_basic_info.os.isnull()]))

city缺失值個數: 8317
ramcapacity缺失值個數: 24757
ramleftration缺失值個數: 202200
romcapacity缺失值個數: 24757
romleftration缺失值個數: 173039
fontsize缺失值個數: 492558
ct缺失值個數: 113443
os缺失值個數: 984

可以看出,在User_basic_info.csv中,city屬性缺少8317個數據,fontsize有將近50萬的缺失值。經過討論,我們最終採用的是用衆數進行填充。採用的是手機的衆數對各個屬性的缺失值進行填充。至於爲什麼採用衆數進行填充,而不是隨便按照均值或者最大/最小值對每列進行填充,可能當時覺得缺少這麼多的數據如果太隨便填充的話,會對結果產生不好的影響,影響數據的分佈。

train_user_basic_info[train_user_basic_info.prodname=='p00195'].os.value_counts()#求衆數

這樣我們用手機同型號的衆數填充完畢,算是填充了這部分的缺失值。然而在比賽中結束後,經過和師兄的聊天,發現其實並不需要這麼複雜的填充,而且也並沒有什麼實際的提升效果。師兄就是簡單的用平均值填充,簡單來說就是數據量太大,計算填充的數據不是那麼精確,也不會對最終的數據分佈產生太大的偏差。

解決完這個表,我們再來看下錶User_behavior_info.csv。這個表沒有缺失值,但是存在異常值。比如手機特性使用次數存在負值,我們最終取絕對值來解決這個問題;還有的數本應該是整數,卻有小數點存在,我們也是給他直接四捨五入取整。

到目前爲止,我們暫時先完成了數據預處理部分。

再次探索

這時候我們把目光轉移到了表app_info.csv中。我們先來看下這個表的內容。
在這裏插入圖片描述
從圖可以看出一共有167622個APP,且APP一共被分成40個類別。
我們是這樣想的,直接利用這個表提供的數據,把40個類別作爲屬性,然後統計每個用戶所使用的APP的分類。如果用戶的APP在這40個類別中,那麼數字變爲1,如果不在這些類別中,那麼就爲0.後期我們發現,用戶中有些APP所屬類別並不在app_info.csv所提供的4個類別中,因爲我們更改爲41個屬性,另一個屬性就是防止沒有類別進行歸屬。經過劃分,我們得到結果如下。
在這裏插入圖片描述
可以看到,用戶的APP如果在類別中出現則爲1,否則爲0.

這樣我們預期就先利用三個表User_basic_info和User_behavior和app_info所提供的41個類別,把他們作爲特徵對年齡進行預測。

算法一開始採用的是比賽大殺器XGBoost,但是發現效果並不好,原因是矩陣比較稀疏,使用樹模型達不到良好的效果。我們轉而使用BP神經網絡算法,經過不斷調參和優化,準確率逐漸穩定在0.43左右。

當然,這時候排名提升了100多名。說明對數據進行分析還是有用的。

提取新特徵

再盡力過上述結果之後,我們幾天一直停滯不前,原因是沒有想到好的特徵。這時候不斷翻看相關的文獻和資料,終於在論文《Mining User Attributes Using Large-Scale APP Lists of Smartphones》中得到了思考。

我們看下User_app_actived表的內容。
在這裏插入圖片描述
可以看出,每個用戶下載了多個APP,最多的下載了100多個,最少的也有十幾個。
上面我們也說過,一共存在16萬個app,如果把這些app全部作爲屬性列出來,屬性爆炸,是無法想象的。

因此我們對這個表進行了統計,提取了用戶使用最多的前500,1000,1500,2000,2500,3000個app。

def get_data(data):
    appnames = {}
    print('開始計算', datetime.datetime.now())
    for i, rows in data.iterrows():
        if rows['appid'] in appnames:
            appnames[rows['appid']] += 1
        else:
            appnames[rows['appid']] = 1
        if i % 100000 == 0:
        print('進度', (i / len(data))*100)
        
    # 無序的dict 共有9401個app
    print('無序的字典', datetime.datetime.now())
    f = open('./temp/appid_dict.txt', 'w')
    f.write(str(appnames))
    f.close()
    
    # 有序的dict
    list_sort = sorted(appnames.items(), key=lambda x: x[1], reverse=True)
    print('有序的字典', datetime.datetime.now())
    f = open('./temp/sort_appid_dict.txt', 'w')
    f.write(str(list_sort))
    f.close()
    
    #取出前1000個app
    count = 0
    ten_hundred_nums = []
    for app in list_sort:
        count += 1
        # 取出的app是tuple類型
        ten_hundred_nums.append(app[0])
        if count == 1000:
            break
    f = open('./temp/ten_number_sort_appid_dict.txt', 'w')
    f.write(str(ten_hundred_nums))
    f.close()


print('開始尋找1000個高頻app', datetime.datetime.now())
get_data(user_app_usage)
print('高頻app已經尋找完畢', datetime.datetime.now())

然後我們結合之前的特徵,再次跑一下結果。發現當APP數量是2500的時候,效果最佳,達到了59%。相比較之前有了十幾個百分點的提升,效果算是不錯了,我們又提升了很多名次。

但是遺憾的是,通過其他幾名獲獎的同學演講時,我們發現,他們提取的APP數量有的甚至達到了幾萬個,我當時就特麼幾乎吐血了。本來數據量就大,你倒是怎麼跑得起來呀。後來,我發現這對人家不是問題,畢竟南大周志華老師那組的學生人家不用PC機,也不用傳統的服務器,人家有集羣,集羣,集羣!終究輸在了硬件上,我認輸。

再次提取新特徵

這時候,我們再回過頭來,發現只有user_app_usage.csv這個表我們沒有用到了。

我們先看下錶的內容
在這裏插入圖片描述
UID代表用戶標識,appid就是APP名稱,duration表示1天內用戶對某APP的使用時長,times表示1天內用戶對某APP的累積打開次數,date表示用戶對某app的使用日期。

經過分析,我們提取了新的特徵。
在這裏插入圖片描述
通過可視化,可以很清晰發現這個規律。
在這裏插入圖片描述
加上這部分特徵之後,我們的精度達到了0.61.也是有了小幅度的提升。

後記

後來的將近半個月時間。別人都已經離校,我們還在實驗室不斷提着特徵。大概前前後後相處了將近10中的新特徵,但是不幸,最終都被PASS掉了,因爲性能不升反降。這樣,臨近比賽結束的時候,我們就不再提取新的特徵了,就進行特徵融合了。最終對XGBoost+Bp進行融合,不斷調參,到了0.63最高的精度。

前面說的比較口語化,我們最後公佈一下我們的文檔,感興趣的同學可以看一下。

極客算法精英大賽算法實現說明
1 算法實現思路說明
1.1算法整體框架

本題是一個多分類問題,需要我們從混合複雜的信息中提取戶的特徵,通過已有標籤訓練,並預測未知標籤(此處標籤爲6個不同年齡段)。首先對數據集進行的笥單的統計,用戶id爲1000001、32512500,共2512500個,其中201 傭00個有標籤,5025傭個無標籤。共有488124個app,40個app類別。我們從已給文件中提取了5個子特徵矩陣,並合併成了一個總的特徵矩陣。最後,我們通過雙層神經網絡進行K折交叉得到最終的預測結果。下圖爲算法哐架圖.
在這裏插入圖片描述
其中step step5爲將徵提取步驟,step6描述瞭如何使用神經網絡訓練與預

1.2 特徵提取

爲了節省內存空間,我們提取的特徵都以俑at16的形式存儲,特徵矩陣的每一行代表一個戶的特徵向量。例如特徵矩陣第一行就表示用戶1000001的特徵向量。

Stepl:從User_Behavior_info中構建F-Table1
數據形式.
在這裏插入圖片描述
該數據表述的是用戶對8個特性的使用次數,爲連續特徵,0表示缺失值 其中出現的小於零的數我們把它當作異常值,賦值爲0。我們把每個用戶對應的特性使用次數投射到他id對應的那一行去,形成大小爲2512500*8的將徵矩陣Behavior,接苕按圖1變換生成F一Table1
在這裏插入圖片描述
其中作表示Log_Behavior=log2(Behavior+1), Norma腸e表示對矩陣的每一列進行規範化,即對每一列的元素減去均值處以方差。
Step2:從User_Basic_info中構建F-TabIe2
在這裏插入圖片描述
數據形式:
在這裏插入圖片描述
該數據表述的是厙戶的離散屬性,如性別,居住地,手機型號等等。此文件中缺失值爲NULL,我們使用0填充缺失值。接着,我們把離散字符屬性數值化,對於每一列,我們按屬性出現的順序給它賦值。例如,居住城市這一屬性出現順序爲C00145’,C001 7乙C00435’,則’ m0145 '變爲1,C00177’變爲2。得到大小爲251250 1 2的特徵矩陣Basic,再通過One-Hot將Basic轉換爲F_Table2。

Step3:從User-App-Actived中提取F-Table3 數據形式:
數據形式:
在這裏插入圖片描述
該數據表述的是用戶所激活的所有app的標識。我們首先遍歷了數據,統計發現該文件中共有94傭個app,接着我們對這9400個app的激活人次進行統計,我們從中選取激活用戶最多的前60傭個app,每個用戶對這6000個app進行0/ 1 編碼形成用戶對應的特徵向量。例如,若用戶使用了激活人數最多的app,則他對應的特徵向量的第一維爲1。如此我們得到大小爲251250伊6傭0的矩陣AP P-Actived0接着,我們考慮提取app之間的相互關係,此處我們只考慮前1000 個appo首先我們通過所有用戶的激活記錄,得到一個app間的聯通矩陣s。再通過該聯通矩陣求每個厙戶對應的PageRank向量 組合成特徵矩陣App_PageRank
假設有幾條用戶激活記錄如下:
user1:appl ,app2,app3
user2: app2,app3
user3: app3,app4

如果兩個app在同一個用戶的激活記錄裏出現,則認爲它們有一次連接,我們可以很容易的得到它的聯通圖和聯通矩陣s。
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在求得轉換矩陣Transform_s後,我們將它與APP_Actived相乘得到App_PageRank,最後我們將App_Actived與App_PageRank拼接得到F_Table3.

Step4:從User_app_usage文件生成F_Table4
在這裏插入圖片描述
數據形式:
在這裏插入圖片描述
該文件表述了戶在一個月內每一天的使厙app的次數和時間。與似我們選取使用戶最多的前600個app構建特徵矩陣。我們使原始文件第三維的D酊ati。n和第維的Time屬性各構建一個601維的特徵矩陣。例如,對於用戶丿有和,表示用戶使用挑選出的第一位的app的總次數,代表戶使用所有未被挑選的app的總次數,同理。再拼接爲一個2512500 ×1202的矩陣,最後對整體數據進行Log化並按列Max_Min歸一化,得到2512500 ×1202的矩陣F_Table4.

Step5:結åApp」nfo,User_App_Actived User_App_Usage生成F-Table5
數據形式
在這裏插入圖片描述
該文件描述了每個app對應的類別,類別總數爲40。對應的此處可以構建CatA CT,CatTIME,CatDURA三個子矩陣。構建方式類似與Step3、Step4,不同的是此處我們按照類別索引進行投射,而不是appid使人次的順序索引。最後我們把三個子矩陣拼接在一起,並進行L。g化和按行規範化生成F_Table5。
總共有40類,採類似Step4的操作,利用User-app-actived,和User-app_usage產生三個251250041的特徵矩陣,拼接爲2512500 123的矩陣並進行log化,最終得到F_Table5。
在這裏插入圖片描述
最終拼接所有子特徵矩陣F_Tablel~F_Table5爲總特徵矩陣F.

1.3 模型擬合

step6: K折交叉褲經網絡
由於設備計算能力有限,我們本次競賽沒有使用較流行的LightGBM、XGBoost等。我們只使用了簡單的雙隱層褲經網絡這一種分類器,進行了10折交叉平均。我們在喼層使用u爲激活函數,輸出層使用softmax爲激活函數,對輸入label做one-hot處理,頇測值得到樣本對應類別概率向量,選取概率最大的作爲預測類別。在模型集成方面,我們採取10折交叉的方法,每一折訓練5個神經網絡,最後通過50個褲經網絡預測類別概率向量的簡單平均求得模型最終的頇測值。
模型效率:
模型訓練耗時.單個褲經網絡18分鐘達到收斂,50個褲經網絡模型訓練耗時9 傭分鐘。
模型預測耗時 50個模型求和平均耗時不到50分鐘。

2 算法實現說明
2.1算法運行環境

操作系統:Window10
編譯軟件:Anaconda3 Spyder編譯器Python3.7
第三方庫:keras tensorflow, numpy, skleam
CPU: i7一9700K
內存:128G
顯卡、ATX1080TI

2.2實驗過程

我們單個褲經網絡就能達到線上0.618的成績集成後能到0.6386。下面陳列出我們的部分嘗試與發現。
1.經過大量嘗試,我們發現在數據規範化過程中對於那些連續的,跨度很大的,具有長尾分佈的數據,先採取g化會使得屬性對於褲經網絡很友好。
2.在處理離散屬性時,one-hot的方法會比我們轉換爲概率的方法好那麼一點 但是它會帶來更多的維度。
3.在處理User-App-Usage數據時,我們嘗試過採用分時間段提取信息,如節假日和非節假日,星期一、星期日,這些對算法並沒有什麼幫助。
4.實驗結果顯示,加人越多的屬性,NN預測就越精準,採ÅRLDA、PCAß#維方法或其他Embedding方法後精度都會下降,此外,防止過擬合的方法Ll、L2或 Dr叩。ut都會使得精度下降。
5、在模型構建方面,我們發現基於樹的算法實在是太慢了。只有
法效率還尚可,但是它單模的精度也就和NN差不多,我們考慮最後進行模型融
6.考慮改變損失函數,改爲pair-wise,初步嘗試和現在精度變化不大,但覺得可以用作最後模型融合

7.Bagging集成的方法實驗結果不如簡單的K折平均法。
8.我們把預訓練的50個褲經網絡模型訓練產生的結果作爲一個新的學習器的輸入,可能是由於預訓練的褲經網絡存在過擬合的原因,新學習器的擬合結果並不理想。
9.嘗試對子分類器進行Stacking集成,效果不如普通平均。

總結

這一次參賽收穫還是蠻多的,之前僅僅是從書上對算法進行單方面的瞭解,並沒有深入進行探索。在比賽中,也發現了很多有趣的小夥伴,也有第一次參見數據挖掘類比賽就拿到了季軍。後面還有一個比賽,有時間還是會和大家分享。如果有小夥伴對這次比賽的數據集感興趣,想要自己跑跑,也可以在直接私信我,我會把當時的代碼和思路、文檔一起發給你。

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