一、賽題理解
1. 賽題名稱
? ? ? ? 用戶新增預測挑戰賽
2. 賽題數據集
? ? ? ? 賽題數據由約62萬條訓練集、20萬條測試集數據組成,共包含13個字段。其中uuid為樣本唯一標識,eid為訪問行為ID,udmap為行為屬性,其中的key1到key9表示不同的行為屬性,如項目名、項目id等相關字段,common_ts為應用訪問記錄發生時間(毫秒時間戳),其余字段x1至x8為用戶相關的屬性,為匿名處理字段。target字段為預測目標,即是否為新增用戶。
3. 賽題鏈接?
2023 iFLYTEK A.I.開發者大賽-訊飛開放平臺 (xfyun.cn)
4. 評估指標
? ? ? ? F1 score,具體公式如下:?
二、數據競賽開發步驟
1. 問題分析
? ? ? ? 問題分析是競賽的第一步,它涉及對問題進行定義、目標明確化和數據需求的識別。
2. ?數據清洗
????????在采集數據完后,對數據進行數據清洗,即把采集到的、不適合用來做機器學習訓練的數據進行預處理,從而轉化為適合機器學習的數據。
3. 數據探索
? ? ? ? 對清洗后的數據進行可視化和統計分析的過程。通過數據探索,可以深入了解數據的特征、分布、相關性等情況,發現數據之間的模式和趨勢,為特征工程和特征篩選提供指導。常見的數據探索方法包括繪制直方圖、散點圖、箱線圖等圖形,計算統計指標如均值、方差、相關系數等。
4. 特征工程
? ? ? ? 根據數據的領域知識和探索結果,對原始數據進行變換、組合、衍生等操作,以創建更有意義、更能表達問題特征的新特征。好的特征工程可以提高模型的性能和泛化能力。常見的特征工程包括特征縮放、編碼分類變量、處理時間序列數據、創建多項式特征等。
5. 特征篩選?
? ? ? ? 通過一定的評估方法選擇對模型訓練最有用的特征,去除冗余或不相關的特征。這樣可以降低模型復雜度,加快訓練速度,減少過擬合的可能性,并提高模型的解釋性。常見的特征篩選方法包括基于統計的方法、基于模型的方法和基于特征重要性的方法。
6. 模型訓練
? ? ? ? 使用清洗、探索和篩選后的數據來訓練機器學習模型的過程。根據問題的類型和數據的特點,可以選擇合適的機器學習算法和模型架構進行訓練。訓練過程涉及優化模型參數,使其能夠最好地擬合訓練數據,并能在未見過的數據上泛化。
7. 模型保存?
? ? ? ? 模型訓練完成后,需要將訓練得到的模型保存起來,以便在后續部署和使用中使用。模型保存可以包括保存模型權重、參數和架構,以及相關的輔助信息。在實際應用中,保存的模型可以用于預測新數據的結果或作為其他任務的基礎。
? ? ? ? 當然,對于上面的特征工程不是一步到位的,而是不斷的通過模型訓練的結果進行不斷的優化調整,直到最優的特征組合。
三、 baseline詳細解讀
1. 數據讀取
# 1. 導入需要用到的相關庫
# 導入 pandas 庫,用于數據處理和分析
import pandas as pd
# 導入 numpy 庫,用于科學計算和多維數組操作
import numpy as np
# 從 sklearn.tree 模塊中導入 DecisionTreeClassifier 類
# DecisionTreeClassifier 用于構建決策樹分類模型
from sklearn.tree import DecisionTreeClassifier# 2. 讀取訓練集和測試集
# 使用 read_csv() 函數從文件中讀取訓練集數據,文件名為 'train.csv'
train_data = pd.read_csv('用戶新增預測挑戰賽公開數據/train.csv')
# 使用 read_csv() 函數從文件中讀取測試集數據,文件名為 'test.csv'
test_data = pd.read_csv('用戶新增預測挑戰賽公開數據/test.csv')
2. 特征構造
(1)對'udmap' 列進行 One-Hot 編碼?
# 數據樣例:
# udmap key1 key2 key3 key4 key5 key6 key7 key8 key9
# 0 {'key1': 2} 2 0 0 0 0 0 0 0 0
# 1 {'key2': 1} 0 1 0 0 0 0 0 0 0
# 2 {'key1': 3, 'key2': 2} 3 2 0 0 0 0 0 0 0
# 具體實現代碼:
# 定義函數 udmap_onethot,用于將 'udmap' 列進行 One-Hot 編碼
def udmap_onethot(d):v = np.zeros(9) # 創建一個長度為 9 的零數組if d == 'unknown': # 如果 'udmap' 的值是 'unknown'return v # 返回零數組d = eval(d) # 將 'udmap' 的值解析為一個字典for i in range(1, 10): # 遍歷 'key1' 到 'key9'if 'key' + str(i) in d: # 如果當前鍵存在于字典中v[i-1] = d['key' + str(i)] # 將字典中的值存儲在對應的索引位置上return v # 返回 One-Hot 編碼后的數組# 使用 apply() 方法將 udmap_onethot 函數應用于每個樣本的 'udmap' 列
# np.vstack() 用于將結果堆疊成一個數組
train_udmap_df = pd.DataFrame(np.vstack(train_data['udmap'].apply(udmap_onethot)))
test_udmap_df = pd.DataFrame(np.vstack(test_data['udmap'].apply(udmap_onethot)))
# 為新的特征 DataFrame 命名列名
train_udmap_df.columns = ['key' + str(i) for i in range(1, 10)]
test_udmap_df.columns = ['key' + str(i) for i in range(1, 10)]
# 將編碼后的 udmap 特征與原始數據進行拼接,沿著列方向拼接
train_data = pd.concat([train_data, train_udmap_df], axis=1)
test_data = pd.concat([test_data, test_udmap_df], axis=1)
(2)?編碼 udmap 是否為空
# 使用比較運算符將每個樣本的 'udmap' 列與字符串 'unknown' 進行比較,返回一個布爾值的 Series
# 使用 astype(int) 將布爾值轉換為整數(0 或 1),以便進行后續的數值計算和分析
train_data['udmap_isunknown'] = (train_data['udmap'] == 'unknown').astype(int)
test_data['udmap_isunknown'] = (test_data['udmap'] == 'unknown').astype(int)
?(3)提取 eid 的頻次特征和標簽特征
# 使用 map() 方法將每個樣本的 eid 映射到訓練數據中 eid 的頻次計數
# train_data['eid'].value_counts() 返回每個 eid 出現的頻次計數
train_data['eid_freq'] = train_data['eid'].map(train_data['eid'].value_counts())
test_data['eid_freq'] = test_data['eid'].map(train_data['eid'].value_counts())# 使用 groupby() 方法按照 eid 進行分組,然后計算每個 eid 分組的目標值均值
# train_data.groupby('eid')['target'].mean() 返回每個 eid 分組的目標值均值
train_data['eid_mean'] = train_data['eid'].map(train_data.groupby('eid')['target'].mean())
test_data['eid_mean'] = test_data['eid'].map(train_data.groupby('eid')['target'].mean())
?(4)提取時間戳
# 使用 pd.to_datetime() 函數將時間戳列轉換為 datetime 類型
# 樣例:1678932546000->2023-03-15 15:14:16
# 具體實現代碼:
train_data['common_ts'] = pd.to_datetime(train_data['common_ts'], unit='ms')
test_data['common_ts'] = pd.to_datetime(test_data['common_ts'], unit='ms')# 使用 dt.hour 屬性從 datetime 列中提取小時信息,并將提取的小時信息存儲在新的列 'common_ts_hour'
train_data['common_ts_hour'] = train_data['common_ts'].dt.hour
test_data['common_ts_hour'] = test_data['common_ts'].dt.hour
3. 模型訓練
????????加載決策樹模型進行訓練(直接使用sklearn中導入的包進行模型建立);
????????對測試集進行預測,并保存結果到result_df中;
????????保存結果文件到本地
clf = DecisionTreeClassifier()
# 使用 fit 方法訓練模型
# train_data.drop(['udmap', 'common_ts', 'uuid', 'target'], axis=1) 從訓練數據集中移除列 'udmap', 'common_ts', 'uuid', 'target'
# 這些列可能是特征或標簽,取決于數據集的設置
# train_data['target'] 是訓練數據集中的標簽列,它包含了每個樣本的目標值
clf.fit(train_data.drop(['udmap', 'common_ts', 'uuid', 'target'], axis=1), # 特征數據:移除指定的列作為特征train_data['target'] # 目標數據:將 'target' 列作為模型的目標進行訓練
)# 9. 對測試集進行預測,并保存結果到result_df中
# 創建一個DataFrame來存儲預測結果,其中包括兩列:'uuid' 和 'target'
# 'uuid' 列來自測試數據集中的 'uuid' 列,'target' 列將用來存儲模型的預測結果
result_df = pd.DataFrame({'uuid': test_data['uuid'], # 使用測試數據集中的 'uuid' 列作為 'uuid' 列的值'target': clf.predict(test_data.drop(['udmap', 'common_ts', 'uuid'], axis=1)) # 使用模型 clf 對測試數據集進行預測,并將預測結果存儲在 'target' 列中
})# 10. 保存結果文件到本地
# 將結果DataFrame保存為一個CSV文件,文件名為 'submit.csv'
# 參數 index=None 表示不將DataFrame的索引寫入文件中
result_df.to_csv('submit.csv', index=None)
?四、后續提升方案
1. 構建交叉訓練框架
def cv_model(clf, train_x, train_y, test_x, clf_name, seed = 23):folds = 5kf = KFold(n_splits=folds, shuffle=True, random_state=seed)oof = np.zeros(train_x.shape[0])test_predict = np.zeros(test_x.shape[0])cv_scores = []for i, (train_index, valid_index) in enumerate(kf.split(train_x, train_y)):print('************************************ {} ************************************'.format(str(i+1)))trn_x, trn_y, val_x, val_y = train_x.iloc[train_index], train_y[train_index], train_x.iloc[valid_index], train_y[valid_index]if clf_name == "cat":params = {'learning_rate': 0.03, 'depth': 6, 'bootstrap_type':'Bernoulli','random_seed':2023,'od_type': 'Iter', 'od_wait': 100, 'random_seed': 23, 'allow_writing_files': False, 'task_type' : 'GPU'}model = clf(iterations=20000, **params, eval_metric='F1')model.fit(trn_x, trn_y, eval_set=(val_x, val_y),metric_period=100,use_best_model=True, cat_features=[],verbose=1)val_pred = model.predict_proba(val_x)[:,1]test_pred = model.predict_proba(test_x)[:,1]oof[valid_index] = val_predtest_predict += test_pred / kf.n_splitsF1_score = f1_score(val_y, [1 if prob >= 0.5 else 0 for prob in val_pred])cv_scores.append(F1_score)print(cv_scores)return oof, test_predict
2. 特征篩選
(1)相關性
? ? ? ? 通過統計單個特征與目標變量之間的相關性,選取相關性較高的特征。
(2)遞歸特征消除
? ? ? ? 通過遞歸地擬合模型并去除對模型性能貢獻較小的特征,直到達到所需的特征數量
(3)特征重要性
? ? ? ? 通過查看模型內部的特征重要性指標
(4)具有空重要性的特征選擇
? ? ? ? 使用目標排列的特征選擇過程測試與噪聲(混洗目標)擬合時特征重要性分布的實際重要性顯著性。
Feature Selection with Null Importances | Kaggle
強烈推薦此方案?
3. 特征交叉
? ? ? ? 選取特征重要性排名靠前進行特征交叉,可以選擇排名靠前的特征組合來構建新的特征,從而增強模型的表現。
4. 模型調參
? ? ? ? 機器學習和深度學習中,通過調整模型的超參數來優化模型性能。超參數是在模型訓練之前設置的參數,它們不會在訓練過程中自動學習,而需要手動選擇。
? ? ? ? 一般常用的調參方法:
? ? ? ? a. 網格搜索(Grid Search):遍歷所有超參數的組合,逐一嘗試。雖然它是全面搜索,但在超參數空間較大時計算代價較高。
? ? ? ? ?b. 隨機搜索(Random Search):隨機從超參數空間中抽取組合進行嘗試。相比網格搜索,它的計算代價較低,但可能需要更多的嘗試次數。
? ? ? ? ?c. 貝葉斯優化(Bayesian Optimization):通過構建超參數組合的概率模型,根據模型對性能的估計來選擇最有可能改善性能的組合。
? ? ? ? ?d. 遺傳算法(Genetic Algorithms):借鑒自然進化的原理,通過基因交叉和變異等方式逐漸優化超參數組合。
? ? ? ? 網格搜索一般在數據量很大時不建議使用,會導致迭代的次數很多,而無法跑出最優的參數組合。強烈建議使用貝葉斯優化進行模型調參
5. 特征構造
? ? ? ? (1)eid存在多個數據樣本,可以進行mean,max,min,std,skew等數據的統計。
? ? ? ? (2)補充完整頻次特征和標簽特征;
? ? ? ? (3)提取更多的時間信息;
? ? ? ? (4)對label進行閾值調整
?6. stacking集成
? ? ? ? stacking是一種分層模型集成框架。以兩層為例,第一層由多個基學習器組成,其輸入為原始訓練集,第二層的模型則是以第一層基學習器的輸出作為特征加入訓練集進行再訓練,從而得到完整的stacking模型。
五、降低內存方法
????????比賽按照上面的baseline會讀取較大的數據內存,因此在此處放入一個降低內存的方法:
? ? ? ? 通過減少數字列的數據類型并將對象列轉換為分類類型來優化 DataFrame 'train_df' 的內存使用量,從而減少內存使用量。
predictor_columns = [col for col in test_df.columns if col not in ['uuid','time','file']]
def reduce_mem_usage(df):start_mem = df.memory_usage().sum() / 1024**2print('Memory usage of dataframe is {:.2f} MB'.format(start_mem))for col in tqdm.tqdm(predictor_columns):col_type = df[col].dtypeif col_type != object:c_min = df[col].min()c_max = df[col].max()if str(col_type)[:3] == 'int':if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max:df[col] = df[col].astype(np.int8)elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max:df[col] = df[col].astype(np.int16)elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max:df[col] = df[col].astype(np.int32)elif c_min > np.iinfo(np.int64).min and c_max < np.iinfo(np.int64).max:df[col] = df[col].astype(np.int64)else:if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max:df[col] = df[col].astype(np.float16)elif c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max:df[col] = df[col].astype(np.float32)else:df[col] = df[col].astype(np.float64)else:df[col] = df[col].astype('category')end_mem = df.memory_usage().sum() / 1024**2print('Memory usage after optimization is: {:.2f} MB'.format(end_mem))print('Decreased by {:.1f}%'.format(100 * (start_mem - end_mem) / start_mem))return dftrain_df = reduce_mem_usage(train_df)
?https://datawhaler.feishu.cn/docx/HBIHd7ugzoOsMqx0LEncR1lJnCf