來源:阿里雲天池,案例:機器學習實踐
業界廣泛流傳著這樣一句話:「數據和特徵決定了機器學習的上限,而模型和算法只是逼近這個上限而已」,由此可見特徵工程在機器學習中的重要性,今天我們將通過《阿里雲天池大賽賽題解析——機器學習篇》中的【天貓 用戶重複購買預測】案例來深入解析特徵工程在實際商業場景中的應用。
學習前須知
(1)本文特徵工程講解部分參考自圖書《阿里雲天池大賽賽題解析——機器學習篇》中的第二個賽題:天貓用戶重複購買預測。
(2)本文相關數據可以在阿里雲天池競賽平臺下載,數據地址:
https://tianchi.aliyun.com/competition/entrance/231576/information
一 數據集介紹
按照上面方法下載好數據集後,我們來看看具體數據含義。
test_format1.csv和train_format1.csv裡分別存放著測試數據和訓練數據,測試數據集最後一個欄位為prob,表示測試結果,訓練數據集最後一個欄位為label,訓練數據各欄位信息如下圖所示:
訓練數據集
user_log_format1.csv裡存放著用戶行為日誌,欄位信息如下圖所示:
用戶行為日誌數據
user_info_format1.csv裡存放著用戶行基本信息,欄位信息如下圖所示:
用戶基本信息數據
二 特徵構造
本賽題基於天貓電商數據,主要關心用戶、店鋪和商家這三個實體,所以特徵構造上也以用戶、店鋪和商家為核心,可以分為以下幾部分:
用戶-店鋪特徵構造
店鋪特徵構造
對店鋪特徵選取可以使用,如 Numpy 的 corrcoef(x,y)函數計算相關係數,保留相關係數小於0.9 的特徵組合,具體內容如圖 2-3。
商家特徵選取
用戶特徵構造
用戶購買商品特徵構造
利用時間提取特徵
總結以上內容,特徵主要基於基礎特徵、用戶特徵、店鋪特徵、用戶+店鋪四個方面,如下圖所示:
特徵總結
三 特徵提取
首先我們導入需要的工具包,進行數據分析和特徵提取。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt import seaborn as sns
from scipy import stats
import gc
from collections import Counter import copy
import warnings warnings.filterwarnings("ignore")
%matplotlib inline
接下來我們將按以下步驟進行特徵提取。
特徵提取步驟
1 讀取數據
直接調用Pandas的read_csv函數讀取訓練集和測試集及用戶信息、用戶日誌數據。
test_data = pd.read_csv('./data_format1/test_format1.csv') train_data = pd.read_csv('./data_format1/train_format1.csv')user_info = pd.read_csv('./data_format1/user_info_format1.csv')user_log = pd.read_csv('./data_format1/user_log_format1.csv')
2 數據預處理
對數據內存進行壓縮:
def reduce_mem_usage(df, verbose=True):
start_mem = df.memory_usage().sum() / 1024**2
numerics = ['int16', 'int32', 'int64', 'float16', 'float32', 'float64']
for col in df.columns: col_type = df[col].dtypes if col_type in numerics:
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)
end_mem = df.memory_usage().sum() / 1024**2
print('Memory usage after optimization is: {:.2f} MB'.format(end_mem))
首先測試數據添加到訓練數據後,然後將用戶基本信息合併到訓練數據左邊,並刪除不需要的變量,釋放內存。
all_data = train_data.append(test_data)
all_data = all_data.merge(user_info,on=['user_id'],how='left')
del train_data, test_data, user_info
gc.collect()
將用戶日誌數據各欄位合併成一個新的欄位item_id,並將其插入到用戶信息數據之後。
# 用戶日誌數據按時間排序
user_log = user_log.sort_values(['user_id', 'time_stamp'])
# 合併用戶日誌數據各欄位,新欄位名為item_id
list_join_func = lambda x: " ".join([str(i) for i in x])
agg_dict = {
'item_id': list_join_func,
'cat_id': list_join_func,
'seller_id': list_join_func,
'brand_id': list_join_func,
'time_stamp': list_join_func,
'action_type': list_join_func
}
rename_dict = {
'item_id': 'item_path',
'cat_id': 'cat_path',
'seller_id': 'seller_path',
'brand_id': 'brand_path',
'time_stamp': 'time_stamp_path',
'action_type': 'action_type_path'
}
def merge_list(df_ID, join_columns, df_data, agg_dict, rename_dict):
df_data = df_data.groupby(join_columns).agg(agg_dict).reset_index().rename(
columns=rename_dict)
df_ID = df_ID.merge(df_data, on=join_columns, how="left")
return df_ID
all_data = merge_list(all_data, 'user_id', user_log, agg_dict, rename_dict)
del user_log
gc.collect()
3 特徵統計函數定義
基於之前的特徵構造圖,我們提前編寫一些統計相關函數,依次有:數據總數、數據唯一值總數、數據最大值、數據最小值、數據標準差、數據中top N數據以及數據中top N數據的總數。
def cnt_(x):
try:
return len(x.split(' '))
except:
return -1
def nunique_(x):
try:
return len(set(x.split(' ')))
except:
return -1
def max_(x):
try:
return np.max([float(i) for i in x.split(' ')])
except:
return -1
def min_(x):
try:
return np.min([float(i) for i in x.split(' ')])
except:
return -1
def std_(x):
try:
return np.std([float(i) for i in x.split(' ')])
except:
return -1
def most_n(x, n):
try:
return Counter(x.split(' ')).most_common(n)[n-1][0]
except:
return -1
def most_n_cnt(x, n):
try:
return Counter(x.split(' ')).most_common(n)[n-1][1]
except:
return -1
基於上面編寫的基本統計方法,我們可以針對數據進行特徵統計。
def user_cnt(df_data, single_col, name):
df_data[name] = df_data[single_col].apply(cnt_)
return df_data
def user_nunique(df_data, single_col, name):
df_data[name] = df_data[single_col].apply(nunique_)
return df_data
def user_max(df_data, single_col, name):
df_data[name] = df_data[single_col].apply(max_)
return df_data
def user_min(df_data, single_col, name):
df_data[name] = df_data[single_col].apply(min_)
return df_data
def user_std(df_data, single_col, name):
df_data[name] = df_data[single_col].apply(std_)
return df_data
def user_most_n(df_data, single_col, name, n=1):
func = lambda x: most_n(x, n)
df_data[name] = df_data[single_col].apply(func)
return df_data
def user_most_n_cnt(df_data, single_col, name, n=1):
func = lambda x: most_n_cnt(x, n)
df_data[name] = df_data[single_col].apply(func)
return df_data
4 提取統計特徵
基於上一步中編寫的用戶數據統計函數,以店鋪特徵統計為例,統計與店鋪特點有關的特徵,如店鋪、商品、品牌等。
# 取2000條數據舉例
all_data_test = all_data.head(2000)
# 總次數
all_data_test = user_cnt(all_data_test, 'seller_path', 'user_cnt')
# 不同店鋪個數
all_data_test = user_nunique(all_data_test, 'seller_path', 'seller_nunique ')
# 不同品類個數
all_data_test = user_nunique(all_data_test, 'cat_path', 'cat_nunique')
# 不同品牌個數
all_data_test = user_nunique(all_data_test, 'brand_path',
'brand_nunique')
# 不同商品個數
all_data_test = user_nunique(all_data_test, 'item_path', 'item_nunique')
# 活躍天數
all_data_test = user_nunique(all_data_test, 'time_stamp_path',
'time_stamp _nunique')
# 不同用戶行為種數
all_data_test = user_nunique(all_data_test, 'action_type_path',
'action_ty pe_nunique')
此外還可以統計用戶最喜歡的店鋪、最喜歡的類目、最喜歡的品牌、最長見的行為動作等數據。
# 用戶最喜歡的店鋪
all_data_test = user_most_n(all_data_test, 'seller_path', 'seller_most_1', n=1)
# 最喜歡的類目
all_data_test = user_most_n(all_data_test, 'cat_path', 'cat_most_1', n=1)
# 最喜歡的品牌
all_data_test = user_most_n(all_data_test, 'brand_path', 'brand_most_1', n= 1)
# 最常見的行為動作
all_data_test = user_most_n(all_data_test, 'action_type_path', 'action_type _1', n=1)
5 利用countvector和tfidf提取特徵
CountVectorizer與TfidfVectorizer是Scikit-learn的兩個特徵數值計算的類,接下來我們將結合兩者進行特徵提取。
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.feature_extraction.text import ENGLISH_STOP_WORDS
from scipy import sparse
tfidfVec = TfidfVectorizer(stop_words=ENGLISH_STOP_WORDS,
ngram_range=(1, 1),
max_features=100)
columns_list = ['seller_path']
for i, col in enumerate(columns_list):
tfidfVec.fit(all_data_test[col])
data_ = tfidfVec.transform(all_data_test[col])
if i == 0:
data_cat = data_
else:
data_cat = sparse.hstack((data_cat, data_))
6 嵌入特徵
詞嵌入是一類將詞向量化的模型的統稱,核心思想是將每個詞都映射到低維空間(K 為50~300)上的一個稠密向量。
import gensim
model = gensim.models.Word2Vec(
all_data_test['seller_path'].apply(lambda x: x.split(' ')),
size=100,
window=5,
min_count=5,
workers=4)
def mean_w2v_(x, model, size=100):
try:
i = 0
for word in x.split(' '):
if word in model.wv.vocab:
i += 1
if i == 1:
vec = np.zeros(size)
vec += model.wv[word]
return vec / i
except:
return np.zeros(size)
def get_mean_w2v(df_data, columns, model, size):
data_array = []
for index, row in df_data.iterrows():
w2v = mean_w2v_(row[columns], model, size)
data_array.append(w2v)
return pd.DataFrame(data_array)
df_embeeding = get_mean_w2v(all_data_test, 'seller_path', model, 100)
df_embeeding.columns = ['embeeding_' + str(i) for i in df_embeeding.columns]
7 Stacking 分類特徵
以使用 lgb 和 xgb 分類模型構造 Stacking特徵為例子,實現方式如下:
# 1、使用 5 折交叉驗證
from sklearn.model_selection import StratifiedKFold, KFold
folds = 5
seed = 1
kf = KFold(n_splits=5, shuffle=True, random_state=0)
# 2、選擇 lgb 和 xgb 分類模型作為基模型
clf_list = [lgb_clf, xgb_clf]
clf_list_col = ['lgb_clf', 'xgb_clf']
# 3、獲取 Stacking 特徵
clf_list = clf_list
column_list = []
train_data_list=[]
test_data_list=[]
for clf in clf_list:
train_data,test_data,clf_name=clf(x_train, y_train, x_valid, kf, label_ split=None)
train_data_list.append(train_data)
test_data_list.append(test_data)
train_stacking = np.concatenate(train_data_list, axis=1)
test_stacking = np.concatenate(test_data_list, axis=1)
運行結果:
[1] valid_0's multi_logloss: 0.240875
Training until validation scores don't improve for 100 rounds.
[2] valid_0's multi_logloss: 0.240675
[226] train-mlogloss:0.123211 eval-mlogloss:0.226966
Stopping. Best iteration:
[126] train-mlogloss:0.172219 eval-mlogloss:0.218029
xgb now score is: [2.4208301225770263, 2.2433633135072886, 2.51909203146584 34, 2.4902898448798805, 2.5797977298125625]
xgb_score_list: [2.4208301225770263, 2.2433633135072886, 2.5190920314658434, 2.4902898448798805, 2.5797977298125625]
xgb_score_mean: 2.4506746084485203
「乾貨學習,分享在看三連↓