數據驅動!精細化運營!用機器學習做客戶生命周期與價值預估!⛵

💡 作者:韓信子@ShowMeAI
📘 機器學習實戰系列//www.showmeai.tech/tutorials/41
📘 本文地址//www.showmeai.tech/article-detail/330
📢 聲明:版權所有,轉載請聯繫平台與作者並註明出處
📢 收藏ShowMeAI查看更多精彩內容

現在的互聯網平台都有着海量的客戶,但客戶和客戶之間有很大的差異,了解客戶的行為方式對於充分理解用戶與優化服務增強業務至關重要。而藉助機器學習,我們可以實現更精細化地運營,具體來說,我們可以預測客戶價值,即在特定時間段內將為公司帶來多少價值。

本篇內容中使用的 🏆scanner在線交易數據集,可以直接在 ShowMeAI的百度網盤中下載獲取。

🏆 實戰數據集下載(百度網盤):公✦眾✦號『ShowMeAI研究中心』回復『實戰』,或者點擊 這裡 獲取本文 [26] 基於機器學習的客戶價值預估scanner在線交易數據集

ShowMeAI官方GitHub//github.com/ShowMeAI-Hub

本篇內容中我們的實現步驟包括:

  • 整合&處理數據
  • 基於遞歸 RFM 技術從數據構建有效特徵
  • 基於數據建模與預估

💡 整合&處理數據

💦 數據說明

ShowMeAI本篇使用到的數據集,是通過零售店『掃描』商品條形碼而獲得的流水銷售的詳細數據。數據集覆蓋一年時間,涵蓋 22625 個顧客、5242 個商品、64682 次交易。數據字段說明如下:

字段 含義
Date 銷售交易的日期
Customer_ID 客戶ID
Transaction_ID 交易ID
SKU_Category_ID 商品類別ID
SKU_ID 商品ID
Quantity 銷售數量
Sales_Amount 銷售金額(單價乘以數量)

💦 數據讀取 & 處理

本文數據處理部分涉及的工具庫,大家可以參考ShowMeAI製作的工具庫速查表和教程進行學習和快速使用。

📘數據科學工具庫速查表 | Pandas 速查表

📘圖解數據分析:從入門到精通系列教程

上述信息中最重要的3列是:客戶ID、銷售交易的日期、銷售金額,當然大家也可以在後續建模中囊括更多的豐富信息(如商品類別等)。這裡我們先讀取數據並針對時間字段做一點格式轉換。

import pandas as pd 

# 讀取CSV格式交易數據 
df = pd.read_csv(data_path) # 數據路徑 

# 日期型數據轉換 
df.Date = pd.to_datetime(df.Date)
df.head(10) 

💡 RFM & 特徵工程

關於機器學習特徵工程,大家可以參考 ShowMeAI 整理的特徵工程最全解讀教程。

📘機器學習實戰 | 機器學習特徵工程最全解讀

💦 RFM介紹

📘RFM 是一種量化客戶價值的方法,英文全稱為『Recency, Frequency and Monetary value』。RFM 模型的三個參數分別是 R(最近一次消費的時間間隔)、F(消費的頻率)和 M(消費金額)。

RFM的使用方法是,將訓練數據分成觀察期 Observed未來期 Future。 如果我們要預測客戶一年內會花費多少,就將未來期 Future的長度設置為一年。如下圖所示:

基於觀察期的數據特徵建模,並預測未來期的情況,下述代碼我們基於日期進行截斷:

# 截斷日期前的數據 
observed = df[df[date_col] < cut_off 

# 截斷日期後的數據 
future = df [(df[date_col] > cut_off) & (df[date_col] < cut_off + pd.Timedelta(label_period_days, unit='D'))] 

下面我們來看看 RFM 的3要素,並通過代碼進行實現:

💦 Recency / 時間間隔

它代表自最近一次交易以來的時間(小時/天/周)。 我們需要設置一個基準時間點來計算 Recency。 我們會計算客戶在基準時間點後多少天進行了交易。

def customer_recency(data, cut_off, date_column, customer_id_column):
  # 截斷前的數據
  recency = data[data[date_column] < cut_off].copy()
  recency[date_column] = pd.to_datetime(recency[date_column])
  # 按最新交易對客戶進行分組
  recency = recency.groupby(customer_id_column)[date_column].max()
  return ((pd.to_datetime(cut_off) - recency).dt.days).reset_index().rename(
      columns={date_column : 'recency'}
  )

💦 Frequency / 頻率

它代表客戶進行交易的不同時間段的數量。 這將使我們能夠跟蹤客戶進行了多少交易以及交易發生的時間。 我們還可以保留從截止日期開始計算這些指標的做法,因為以後會很方便。

def customer_frequency(data, cut_off, date_column, customer_id_column, value_column, freq='M'):
  # 截斷前的數據
  frequency = data[data[date_column] < cut_off].copy()
  # 設置日期列為索引
  frequency.set_index(date_column, inplace=True)
  frequency.index = pd.DatetimeIndex(frequency.index)
  # 按客戶鍵和不同時期對交易進行分組 
  # 並統計每個時期的交易 
  frequency = frequency.groupby([
      customer_id_column,
      pd.Grouper(freq=freq, level=date_column)
  ]).count()
  frequency[value_column] = 1 # 存儲所有不同的交易 
  # 統計匯總所有交易
  return frequency.groupby(customer_id_column).sum().reset_index().rename(
      columns={value_column : 'frequency'}
  )

💦 Monetary value / 消費金額

它代表平均銷售額。 在這裡我們可以簡單計算每個客戶所有交易的平均銷售額。 (當然,我們後續也會用到)

def customer_value(data, cut_off, date_column, customer_id_column, value_column):
  value = data[data[date_column] < cut_off]
  # 設置日期列為索引 
  value.set_index(date_column, inplace=True)
  value.index = pd.DatetimeIndex(value.index)
  # 獲取每個客戶的平均或總銷售額 
  return value.groupby(customer_id_column)[value_column].mean().reset_index().rename(
      columns={value_column : 'value'}
  )

💦 附加信息

用戶齡: 自第一次交易以來的時間。我們把每個客戶首次交易以來的天數也加到信息中。

def customer_age(data, cut_off, date_column, customer_id_column):
  age = data[data[date_column] < cut_off]
  # 獲取第一筆交易的日期 
  first_purchase = age.groupby(customer_id_column)[date_column].min().reset_index()
  # 獲取截止到第一次交易之間的天數
  first_purchase['age'] = (cut_off - first_purchase[date_column]).dt.days
  return first_purchase[[customer_id_column, 'age']]

最後我們定義一個函數把 RFM 涉及到的信息囊括進去:

def customer_rfm(data, cut_off, date_column, customer_id_column, value_column, freq='M'): 
  cut_off = pd.to_datetime(cut_off) 
  
  # 計算 
  recency = customer_recency(data, cut_off, date_column, customer_id_column) 
  
  # 計算頻率 
  frequency = customer_frequency(data, cut_off, date_column, customer_id_column, value_column, freq=freq) 
  
  # 計算平均值 
  value = customer_value(data, cut_off, date_column, customer_id_column, value_column) # 計算年齡 
  age = customer_age(data, cut_off, date_column, customer_id_column) 

  # 合併所有列 
  return recency.merge(frequency, on=customer_id_column).merge(on=customer_id_column).merge(age,on=customer_id_column) 

理想情況下,這可以捕獲特定時間段內的信息,看起來像下面這樣:

我們把每個客戶未來期間花費的金額作為標籤(即我們截斷數據的後面一部分,即future)。

labels = future.groupby(id_col)[value_col].sum() 

💡 建模思路 & 實現

通過上面的方式我們就構建出了數據樣本,但每個用戶只有1個樣本,如果我們希望有更多的數據樣本,以及在樣本中囊括不同的情況(例如時間段覆蓋節假日和 618 和 11.11 等特殊促銷活動),我們需要使用到『遞歸RFM』方法。

💦 遞歸 RFM

所謂的遞歸 RFM 相當於以滑動窗口的方式來把未來不同的時間段構建為 future 標籤,如下圖所示。假設數據從年初(最左側)開始,我們選擇一個頻率(例如,一個月)遍曆數據集構建未來 (f) 標籤,也即下圖的紅色 f 塊。

具體的實現代碼如下:

def recursive_rfm(data, date_col, id_col, value_col, freq='M', start_length=30, label_period_days=30):
  dset_list = []

  # 獲取數據集的起始時間
  start_date = data[date_col].min() + pd.Timedelta(start_length, unit="D")
  end_date = data[date_col].max() - pd.Timedelta(label_period_days, unit="D")
  # 獲取時間段
  dates = pd.date_range(start=start_date, end=end_date, freq=freq)
  data[date_col] = pd.to_datetime(data[date_col])

  for cut_off in dates:
    # 切分
    observed = data[data[date_col] < cut_off]
    future = data[
        (data[date_col] > cut_off) &
        (data[date_col] < cut_off + pd.Timedelta(label_period_days, unit='D'))
    ]

    rfm_columns = [date_col, id_col, value_col]
    print(f"computing rfm features for {cut_off} to {future[date_col].max()}:")
    _observed = observed[rfm_columns]
    # 計算訓練數據特徵部分(即observed部分)
    rfm_features = customer_rfm(_observed, cut_off, date_col, id_col, value_col)
    # 計算標籤(即future的總消費)
    labels = future.groupby(id_col)[value_col].sum()
    # 合併數據
    dset = rfm_features.merge(labels, on=id_col, how='outer').fillna(0) 
    dset_list.append(dset)
    # 完整數據
  full_dataset = pd.concat(dset_list, axis=0)
  res = full_dataset[full_dataset.recency != 0].dropna(axis=1, how='any')
  return res

rec_df = recursive_rfm(data_for_rfm, 'Date', 'Customer_ID', 'Sales_Amount')

接下來我們進行數據切分,以便更好地進行建模和評估,這裡依舊把數據切分為 80% 用於訓練,20% 用於測試。

from sklearn.model_selection import train_test_split
# 數據採樣,如果大家本地計算資源少,可以設置百分比進行採樣
rec_df = rec_df.sample(frac=1) 

# 確定特徵與標籤
X = rec_df[['recency', 'frequency', 'value', 'age']]
y = rec_df[['Sales_Amount']].values.reshape(-1)

# 數據集切分
test_size = 0.2
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=test_size, random_state=42, shuffle=True)

💦 機器學習建模

關於機器學習建模部分,大家可以參考 ShowMeAI 的機器學習系列教程與模型評估基礎知識文章。

📘圖解機器學習算法:從入門到精通系列教程

📘圖解機器學習 | 隨機森林模型詳解

📘圖解機器學習算法(2) | 模型評估方法與準則

有很多機器學習模型都可以進行建模,在本例中我們使用最常用且效果良好的隨機森林進行建模,因為是回歸任務,我們直接使用 scikit-learn 中的隨機森林回歸器,代碼如下。

from sklearn.ensemble import RandomForestRegressor 
# 在訓練數據集上初始化和擬合模型 
rf = RandomForestRegressor().fit(X_train, y_train) 

擬合後,我們可以在數據框中查看我們對測試集的預測。

from sklearn.metrics import mean_squared_error

# 訓練集:標準答案與預估值
predictions = pd.DataFrame()
predictions['true'] = y_train
predictions['preds'] = rf.predict(X_train)

# 測試集:標準答案與預估值
predictions_test = pd.DataFrame()
predictions_test['true'] = y_test
predictions_test['preds'] = rf.predict(X_test)

# 模型評估
train_rmse = mean_squared_error(predictions.true, predictions.preds)**0.5
test_rmse = mean_squared_error(predictions_test.true, predictions_test.preds)**0.5
print(f"Train RMSE: {train_rmse}, Test RMSE: {test_rmse}")

輸出:

Train RMSE :10.608368028113563, Test RMSE :28.366171873961612 

這裡我們使用的均方根誤差 (RMSE) 作為評估準則,它計算的是訓練數據和測試數據上『標準答案』和『預估值』的偏差平方和與樣本數 N 比值的平方根。 即如下公式:

測試集上評估結果 RMSE 約為 28.4,這意味着我們對未見數據的預測值相差約 28.40 美元。 不過,我們發現,訓練集上的 RMSE 明顯低於測試集上的 RMSE,說明模型有一些過擬合了。 如果我們把訓練集和測試集的每個樣本預估值和真實值繪製出來,是如下的結果,也能看出差異:

機器學中的過擬合問題,可以通過對模型的調參進行優化,比如在隨機森林模型中,可能是因為樹深太深,葉子節點樣本數設置較小等原因導致,大家可以通過調參方法(如網格搜索、隨機搜索、貝葉斯優化)等進行優化。可以在 ShowMeAI的過往機器學習實戰文章中找到調參模板:

人力資源流失場景機器學習建模與調優

基於Airbnb數據的民宿房價預測模型

參考資料