, June 27, 2022

0 kết quả được tìm thấy

Data Driven Growth (P.9): Thiết kế và thực hiện A/B Testing

  • Đăng bởi  Pum
  •  Jun 15, 2022

  •   15 min reads
Data Driven Growth (P.9): Thiết kế và thực hiện A/B Testing

Thử nghiệm được xem là một trong những cách tốt nhất để kiểm tra mô hình machine learning, nhằm cải thiện các mô hình hiện có.

Ví dụ: bạn có một mô hình Churn đang hoạt động với độ chính xác là 95%. Với nhóm khách hàng không còn mua hàng nhiều, lúc này bạn sẽ đưa ra một lời đề nghị hấp dẫn chẳng hạn như các chương trình khuyến mãi.

Bạn sẽ giả định rằng 10% trong số khách hàng sẽ bị hấp dẫn bởi đề nghị của bạn và điều đó giúp bạn  thêm được 20 đô hàng tháng từ mỗi khách hàng.

Chúng ta sẽ có một số giả định sau:

  • Độ chính xác của mô hình là 95%, điều đó có thực sự đúng không? Bạn đã tạo ra một mô hình dựa vào dữ liệu của tháng trước. Nhưng sang tháng sau thì độ chính xác sẽ không còn khớp nhau nữa vì nhiều yếu tố như sẽ có người dùng mới, tính năng sản phẩm mới, hoạt động tiếp thị và thương hiệu khác…. Lúc này bạn không thể đưa ra được kết luận nếu như không làm thử nghiệm.
  • Bằng cách xem lại kết quả của các chiến dịch trước để cho ra một giả định. Bạn sẽ giả định có 10% chuyển đổi, nhưng vẫn không đảm bảo được vì sẽ có nhiều yếu tố khác tác động vào.
  • Nếu ngày hôm nay, những khách hàng đó giúp bạn thu thêm được 20 đô thì cũng  có gì đảm bảo là ngày mai hay trong tương lai họ vẫn giúp bạn thu thêm được lợi nhuận.

Chúng ta cần tiến hành thử nghiệm A/B. Trong bài viết này, chúng ta sẽ thực hiện thử nghiệm của mình theo chương trình và làm báo cáo số liệu thống kê. Trước khi bắt đầu viết mã, có hai điểm quan trọng bạn cần suy nghĩ kỹ trước khi thiết kế và kiểm tra A/B.

1.Giả thuyết của bạn là gì?

Tương tự như ví dụ trên, giả thuyết sẽ là nhóm thử nghiệm có nhiều tỷ lệ giữ chân khách hàng hơn.

Nhóm A -> ưu đãi -> tỷ lệ giữ chân khách hàng cao hơn.

Nhóm B -> không có ưu đãi -> tỷ lệ giữ chân khách hàng thấp hơn.

Điều này cũng góp phần giúp chúng ta kiểm tra lại độ chính xác của mô hình. Nếu tỷ lệ giữ chân khách hàng của nhóm B là 50% thì điều đó chứng tỏ mô hình đang không hoạt động. Tương tự vậy, chúng ta sẽ áp dụng đo lường doanh thu từ những người dùng đó.

2. Chỉ số thành công của bạn là gì?

Trong trường hợp này, chúng ta sẽ kiểm tra tỷ lệ giữ chân khách hàng của cả nhóm.

Thử nghiệm A/B lập trình

Đối với ví dụ mã hoá này, chúng ta sẽ tạo ra tập dữ liệu của riêng mình bằng cách sử dụng thư viện numpy để đánh giá kết quả kiểm tra A/B.

Chúng ta sẽ bắt đầu nhập dữ liệu:

#import libraries
from datetime import datetime, timedelta,date
import pandas as pd
%matplotlib inline
from sklearn.metrics import classification_report,confusion_matrix
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
from __future__ import division
from sklearn.cluster import KMeans


import plotly.plotly as py
import plotly.offline as pyoff
import plotly.graph_objs as go
import plotly.figure_factory as ff

import sklearn
import xgboost as xgb
from sklearn.model_selection import KFold, cross_val_score, train_test_split
import warnings
warnings.filterwarnings("ignore")

#initiate plotly
pyoff.init_notebook_mode()

Bây giờ chúng ta sẽ tạo tập dữ liệu của riêng mình. Tập dữ liệu sẽ chứa các cột bên dưới:

  • customer_id: định danh duy nhất của khách hàng
  • Phân khúc: phân khúc của khách hàng như khách hàng giá trị cao hoặc khách hàng giá trị thấp
  • Nhóm: cho biết liệu khách hàng có thuộc nhóm thử nghiệm hay nhóm kiểm  hay không
  • Purchase_count: # giao dịch mua đã được khách hàng hoàn tất

Ba nhóm đầu sẽ khá dễ để thực hiện

df_hv = pd.DataFrame()
df_hv['customer_id'] = np.array([count for count in range(20000)])
df_hv['segment'] = np.array(['high-value' for _ in range(20000)])
df_hv['group'] = 'control'
df_hv.loc[df_hv.index<10000,'group'] = 'test'

Điều kiện thuận lợi nhất là số lượng mua hàng là một sự phân bố Poisson. Lúc này, sẽ có những khách hàng không mua hàng và chúng ta sẽ có ít khách hàng mua hàng. Vì thế, hãy sử dụng numpy.random.poisson () để làm ra các bản phân phối khác nhau nhằm để kiểm tra lại.

df_hv.loc[df_hv.group == 'test', 'purchase_count'] = np.random.poisson(0.6, 10000)
df_hv.loc[df_hv.group == 'control', 'purchase_count'] = np.random.poisson(0.5, 10000)

Hãy xem dữ liệu của chúng ta

Chúng ta có mọi thứ để đánh giá thử nghiệm A/B của mình. Giả sử, chúng ta áp dụng ưu đãi cho 50% khách hàng có giá trị cao mà chúng ta đã quan sát họ trong một thời gian nhất định. Cách đúng nhất để hình dung ra nó là chúng ta sẽ kiểm tra mật độ mua hàng thường xuyên của khách hàng.

test_results = df_hv[df_hv.group == 'test'].purchase_count
control_results = df_hv[df_hv.group == 'control'].purchase_count

hist_data = [test_results, control_results]

group_labels = ['test', 'control']

# Create distplot with curve_type set to 'normal'
fig = ff.create_distplot(hist_data, group_labels, bin_size=.5,
                         curve_type='normal',show_rug=False)

fig.layout = go.Layout(
        title='High Value Customers Test vs Control',
        plot_bgcolor  = 'rgb(243,243,243)',
        paper_bgcolor  = 'rgb(243,243,243)',
    )


# Plot!
pyoff.iplot(fig)

Đầu ra:

Kết quả thực sự tốt, mật độ mua hàng của nhóm thử nghiệm tốt hơn bắt đầu từ 1. Nhưng làm thế nào mà chúng ta có thể chắc chắn rằng thử nghiệm này thành công mà không bị tác động bởi các yếu tố khác?

Để trả lời cho câu hỏi này, chúng ta cần kiểm tra xem sự gia tăng trong nhóm thử nghiệm có ý nghĩa thống kê hay không. Thư viện .scipy sẽ giúp chúng ta lập trình để kiểm tra điều này.

from scipy import stats 
test_result = stats.ttest_ind(test_results, control_results)
print(test_result)

Đầu ra:

Phương thức ttest_ind () cho ra hai dữ liệu:

  • Thống kê t: thể hiện sự khác biệt giữa giá trị trung bình của nhóm thử nghiệm và nhóm kiểm soát dựa theo đơn vị sai số chuẩn. Giá trị thống kê t cao hơn có nghĩa là sự khác biệt lớn hơn hỗ trợ giả thuyết của chúng ta.
  • p-value: đo xác suất của giả thuyết rỗng là đúng.

Vậy giả thuyết vô hiệu (null) là gì?

Nếu giả thuyết vô hiệu là đúng, điều đó có nghĩa là không có sự khác biệt đáng kể giữa nhóm thử nghiệm và nhóm kiểm tra. Giá trị p thấp hơn sẽ càng tốt.

Theo tiêu chuẩn ngành, chúng ta chấp nhận giá trị p < 5% làm cho kết quả có ý nghĩa thống kê (nhưng nó phụ thuộc vào logic kinh doanh của bạn, có những trường hợp mọi người sử dụng 10% hoặc thậm chí 1%).

Để xem thử nghiệm của chúng ta có quan trọng về mặt thống kê hay không, hãy xây dựng một hàm và áp dụng vào tập dữ liệu của mình.

def eval_test(test_results,control_results):
    test_result = stats.ttest_ind(test_results, control_results)
    if test_result[1] < 0.05:
        print('result is significant')
    else:
        print('result is not significant')

Nhưng thật không may vì nó không đơn giản. Nếu bạn chọn một nhóm thử nghiệm thiên vị, kết quả của bạn sẽ là thống kê bị mặc định.

Ví dụ: chúng ta phân bổ nhiều khách hàng có giá trị cao hơn cho nhóm thử nghiệm và nhiều khách hàng có giá trị thấp hơn cho nhóm kiểm tra, thì thử nghiệm của chúng ta sẽ thất bại ngay từ đầu.

Đó là lý do việc chọn nhóm để làm thừ nghiệm A/B là một việc làm rất quan trọng.

Chọn nhóm thử nghiệm và nhóm kiểm tra

Cách phổ biến nhất để chọn ra nhóm thử nghiệm và nhóm kiểm tra là lấy mẫu ngẫu nhiên. Trước tiên, chúng ta sẽ tạo tập dữ liệu, trong ví dụ này chúng ta sẽ mặc định có 20k khách hàng có mệnh giá cao và 80k khách hàng có mệnh giá thấp:

#create hv segment
df_hv = pd.DataFrame()
df_hv['customer_id'] = np.array([count for count in range(20000)])
df_hv['segment'] = np.array(['high-value' for _ in range(20000)])
df_hv['prev_purchase_count'] = np.random.poisson(0.9, 20000)
df_lv = pd.DataFrame()
df_lv['customer_id'] = np.array([count for count in range(20000,100000)])
df_lv['segment'] = np.array(['low-value' for _ in range(80000)])
df_lv['prev_purchase_count'] = np.random.poisson(0.3, 80000)
df_customers = pd.concat([df_hv,df_lv],axis=0)

Bằng cách sử dụng hàm pandas 'smpect (), chúng ta có thể chọn các nhóm thử nghiệm của mình. Giả sử chúng ta có 90% nhóm thử nghiệm và nhóm kiểm soát là 10%:

df_test = df_customers.sample(frac=0.9)
df_control = df_customers[~df_customers.customer_id.isin(df_test.customer_id)]

Chúng ta sẽ trích xuất 90% toàn bộ nhóm và gắn nhãn cho nó là thử nghiệm. Nhưng có một vấn đề nhỏ có thể làm hỏng cuộc thử nghiệm của chúng ta. Đó là, sẽ có nhiều nhóm khác nhau đáng kể trong tập dữ liệu của mình (trong trường hợp này là giá trị cao và giá trị thấp), tốt hơn hết chúng ta nên thực hiện lấy mẫu ngẫu nhiên riêng biệt.

Nếu không, chúng ta sẽ không thể đảm bảo rằng tỷ lệ giữa giá trị cao và giá trị thấp là giống nhau đối với nhóm thử nghiệm và kiểm tra.

Để đảm bảo việc tạo ra nhóm thử nghiệm và nhóm kiểm tra một cách chính xác, chúng ta cần áp dụng mã sau:

df_test_hv = df_customers[df_customers.segment == 'high-value'].sample(frac=0.9)
df_test_lv = df_customers[df_customers.segment == 'low-value'].sample(frac=0.9)
df_test = pd.concat([df_test_hv,df_test_lv],axis=0)
df_control = df_customers[~df_customers.customer_id.isin(df_test.customer_id)]

Chúng ta đã khám phá cách thực hiện kiểm tra t và lựa chọn ra các nhóm thử nghiệm và nhóm kiểm tra. Nhưng điều gì sẽ xảy ra nếu chúng ta làm A / B / C test hoặc A / B test trên nhiều nhóm như trên. Đã đến lúc ra chúng ta cần các bài kiểm tra ANOVA.

ANOVA một chiều

Giả sử, chúng ta sẽ thử nghiệm cả 2 ưu đãi trên cùng một nhóm khách hàng. Sau đó, chúng ta sẽ áp dụng ANOVA một chiều để đánh giá thử nghiệm của mình. Chúng ta sẽ bắt đầu từ việc lập dữ liệu của mình.

#create hv segment
df_hv = pd.DataFrame()
df_hv['customer_id'] = np.array([count for count in range(30000)])
df_hv['segment'] = np.array(['high-value' for _ in range(30000)])
df_hv['group'] = 'A'
df_hv.loc[df_hv.index>=10000,'group'] = 'B' 
df_hv.loc[df_hv.index>=20000,'group'] = 'C' 

df_hv.loc[df_hv.group == 'A', 'purchase_count'] = np.random.poisson(0.4, 10000)
df_hv.loc[df_hv.group == 'B', 'purchase_count'] = np.random.poisson(0.6, 10000)
df_hv.loc[df_hv.group == 'C', 'purchase_count'] = np.random.poisson(0.2, 10000)

a_stats = df_hv[df_hv.group=='A'].purchase_count
b_stats = df_hv[df_hv.group=='B'].purchase_count
c_stats = df_hv[df_hv.group=='C'].purchase_count

hist_data = [a_stats, b_stats, c_stats]

group_labels = ['A', 'B','C']

# Create distplot with curve_type set to 'normal'
fig = ff.create_distplot(hist_data, group_labels, bin_size=.5,
                         curve_type='normal',show_rug=False)

fig.layout = go.Layout(
        title='Test vs Control Stats',
        plot_bgcolor  = 'rgb(243,243,243)',
        paper_bgcolor  = 'rgb(243,243,243)',
    )


# Plot!
pyoff.iplot(fig)

Đầu ra:

Để đánh giá kết quả, chúng ta sẽ áp dụng chức năng dưới đây:

def one_anova_test(a_stats,b_stats,c_stats):
    test_result = stats.f_oneway(a_stats, b_stats, c_stats)
    if test_result[1] < 0.05:
        print('result is significant')
    else:
        print('result is not significant')

Logic tương tự như t_test. Nếu giá trị p thấp hơn 5%, bài kiểm tra của chúng ta sẽ trở nên đáng kể:

df_hv.loc[df_hv.group == 'A', 'purchase_count'] = np.random.poisson(0.5, 10000)
df_hv.loc[df_hv.group == 'B', 'purchase_count'] = np.random.poisson(0.5, 10000)
df_hv.loc[df_hv.group == 'C', 'purchase_count'] = np.random.poisson(0.5, 10000)
a_stats = df_hv[df_hv.group=='A'].purchase_count
b_stats = df_hv[df_hv.group=='B'].purchase_count
c_stats = df_hv[df_hv.group=='C'].purchase_count
hist_data = [a_stats, b_stats, c_stats]
group_labels = ['A', 'B','C']
# Create distplot with curve_type set to 'normal'
fig = ff.create_distplot(hist_data, group_labels, bin_size=.5,
                         curve_type='normal',show_rug=False)
fig.layout = go.Layout(
        title='Test vs Control Stats',
        plot_bgcolor  = 'rgb(243,243,243)',
        paper_bgcolor  = 'rgb(243,243,243)',
    )
# Plot!
pyoff.iplot(fig)

Đầu ra và kết quả kiểm tra

Nếu muốn xem liệu có sự khác biệt nào giữa A, B hay C hay không thì bạn có thể áp dụng t_test.

ANOVA hai chiều

Giả sử, chúng ta sẽ thực hiện thử nghiệm trên cả hai nhóm khách hàng có giá trị cao và giá trị thấp. Chúng ta sẽ áp dụng ANOVA hai chiều cho trường hợp này, bắt đầu với việc tạo tập dữ liệu và xây dựng phương pháp đánh giá.

#create hv segment
df_hv = pd.DataFrame()
df_hv['customer_id'] = np.array([count for count in range(20000)])
df_hv['segment'] = np.array(['high-value' for _ in range(20000)])
df_hv['group'] = 'control'
df_hv.loc[df_hv.index<10000,'group'] = 'test' 
df_hv.loc[df_hv.group == 'control', 'purchase_count'] = np.random.poisson(0.6, 10000)
df_hv.loc[df_hv.group == 'test', 'purchase_count'] = np.random.poisson(0.8, 10000)


df_lv = pd.DataFrame()
df_lv['customer_id'] = np.array([count for count in range(20000,100000)])
df_lv['segment'] = np.array(['low-value' for _ in range(80000)])
df_lv['group'] = 'control'
df_lv.loc[df_lv.index<40000,'group'] = 'test' 
df_lv.loc[df_lv.group == 'control', 'purchase_count'] = np.random.poisson(0.2, 40000)
df_lv.loc[df_lv.group == 'test', 'purchase_count'] = np.random.poisson(0.3, 40000)

df_customers = pd.concat([df_hv,df_lv],axis=0)

ANOVA hai chiều yêu cầu xây dựng một mô hình như dưới đây:

import statsmodels.formula.api as smf 
from statsmodels.stats.anova import anova_lm
model = smf.ols(formula='purchase_count ~ segment + group ', data=df_customers).fit()
aov_table = anova_lm(model, typ=2)

Bằng cách sử dụng phân đoạn và hóm, mô hình cố gắng đạt được  purchase_count. aov_table giúp chúng ta xem liệu thử nghiệm có thành công không:

Cột cuối cùng thể hiện kết quả và cho chúng ta thấy sự khác biệt là đáng kể. Nếu không, nó sẽ trông giống như dưới đây:

Điều này cho thấy, phân khúc (có giá trị cao hoặc giá trị thấp) ảnh hưởng đáng kể đến số lượng mua hàng nhưng nhóm thì không vì nó gần như là 66%, cao hơn 5%.

Bây giờ, chúng ta biết cách chọn nhóm của mình và đánh giá kết quả. Nhưng còn thiếu một bước để đạt được ý nghĩa thống kê, kích thước mẫu của chúng ta phải đủ. Hãy xem làm thế nào để chúng ta có thể tính toán nó.

Tính toán kích thước mẫu

Để tính toán cỡ mẫu cần thiết, trước tiên chúng ta cần hiểu hai khái niệm:

Kích thước hiệu ứng: thể hiện mức độ khác biệt giữa giá trị trung bình của nhóm thử nghiệm và nhóm kiểm tra. Nó là phương sai trung bình giữa nhóm thử nghiệm và nhóm kiểm tra chia cho độ lệch chuẩn của nhóm kiểm tra.

Power: đề cập đến xác suất tìm thấy ý nghĩa thống kê trong thử nghiệm của bạn. Để tính kích thước mẫu, 0,8 là giá trị phổ biến đang được sử dụng.

Hãy xây dựng tập dữ liệu và xem phép tính kích thước mẫu trong ví dụ sau:

from statsmodels.stats import power
ss_analysis = power.TTestIndPower()
#create hv segment
df_hv = pd.DataFrame()
df_hv['customer_id'] = np.array([count for count in range(20000)])
df_hv['segment'] = np.array(['high-value' for _ in range(20000)])
df_hv['prev_purchase_count'] = np.random.poisson(0.7, 20000)
purchase_mean = df_hv.prev_purchase_count.mean()
purchase_std = df_hv.prev_purchase_count.std()

Trong ví dụ này, giá trị trung bình của các lần mua hàng (purchase_mean) là 0,7 và độ lệch chuẩn (purchase_std) là 0,84.

Giả sử, chúng ta muốn tăng purchase_mean lên 0,75 trong thử nghiệm này, chúng ta sẽ tính kích thước hiệu ứng như sau:

effect_size = (0.75 - purchase_mean)/purchase_std

Sau đó, tính toán kích thước mẫu khá đơn giản:

alpha = 0.05
power = 0.8
ratio = 1
ss_result = ss_analysis.solve_power(effect_size=effect_size, power=power,alpha=alpha, ratio=ratio , nobs1=None) 
print(ss_result)

Alpha là ngưỡng có ý nghĩa thống kê (5%) và tỷ lệ giữa kích thước mẫu thử nghiệm và kiểm tra của chúng ta bằng nhau đều là 1. Do đó, kích thước mẫu yêu cầu của chúng ta có đầu ra của ss_result là 4868.

Hãy tự xây dựng một hàm để có thể sử dụng nó bất kỳ khi nào bạn muốn.

def calculate_sample_size(c_data, column_name, target,ratio):
    value_mean = c_data[column_name].mean()
    value_std = c_data[column_name].std()
    
    value_target = value_mean * target
    
    effect_size = (value_target - value_mean)/value_std
    
    power = 0.8
    alpha = 0.05
    ss_result = ss_analysis.solve_power(effect_size=effect_size, power=power,alpha=alpha, ratio=ratio , nobs1=None) 
    print(int(ss_result))

Đối với chức năng này, chúng ta cần cung cấp tập dữ liệu của mình, tên_cột đại diện cho giá trị, trong trường hợp của chúng ta là số tiền mua, giá trị trung bình mục tiêu của chúng ta là 0,75 và tỷ lệ.

Trong tập dữ liệu ở trên, giả sử chúng ta muốn tăng số lượng mua hàng trung bình lên 5% nhưng vẫn muốn giữ nguyên kích thước của cả hai nhóm.

calculate_sample_size(df_hv, 'prev_purchase_count', 1.05,1)

Sau đó, kết quả trở thành 8961.

Đây là bài viết cuối trong loạt series bài viết “Data Driven Growth”. Hy vọng bạn thích các bài viết và bắt đầu áp dụng các thực hành vào kho dữ liệu của bạn.

Bài viết được dịch từ đây.

Bài viết cùng seri

Bài viết liên quan

Data Driven Growth (P.8): Mô hình Uplift

Câu hỏi lúc này được đặt ra là liệu bạn có cần phải gửi ưu đãi đến cho tất cả khách hàng hay không?...

Data Driven Growth (P.8): Mô hình Uplift
Data Driven Growth (P.7): Các mô hình đáp ứng thị trường

Làm thế nào để tăng doanh số bán hàng của mình lên? Nếu hôm nay chúng ta giảm giá, liệu sẽ có bao nhiêu khách hàng mua hàng?...

Data Driven Growth (P.7): Các mô hình đáp ứng thị trường
Data Driven Growth (P.6): Dự đoán doanh số - Predicting Sales

Trong bài viết này, chúng ta sẽ tập trung vào phương pháp Long Short-term Memory (LSTM), một phương pháp phổ biến nếu bạn sử dụng Deep Learning....

Data Driven Growth (P.6): Dự đoán doanh số - Predicting Sales
Data Driven Growth (P.5): Dự đoán ngày mua hàng tiếp theo của khách hàng

Dự đoán ngày mua hàng tiếp theo của khách hàng. Điều gì sẽ xảy ra nếu một khách hàng mua hàng sau 7 ngày?...

Data Driven Growth (P.5): Dự đoán ngày mua hàng tiếp theo của khách hàng
Data Driven Growth (P.4): Dự đoán lòng trung thành của khách hàng - Churn Prediction

Tỷ lệ giữ chân là dấu hiệu cho thấy sản phẩm phù hợp với nhu cầu của thị trường. Công cụ mạnh mẽ để cải thiện tỷ lệ này là Churn Prediction....

Data Driven Growth (P.4): Dự đoán lòng trung thành của khách hàng - Churn Prediction
You've successfully subscribed to 200Lab Blog
Great! Next, complete checkout for full access to 200Lab Blog
Xin chào mừng bạn đã quay trở lại
OK! Tài khoản của bạn đã kích hoạt thành công.
Success! Your billing info is updated.
Billing info update failed.
Your link has expired.