پیش بینی ریزش مشتریان با داده کاوی و R – راهنمای جامع


پیدا کردن یک شغل خوب در حوزه «دادهکاوی» (Data Mining) کار سادهای نیست. دانستن چگونگی کار با کتابخانههایی مانند «سایکیتلِرن» (scikit-learn) و «ggplot2» برای کسب یک فرصت شغلی مناسب کفایت نمیکند. پرسشی که در این وهله مطرح میشود این است که راهکار پیدا کردن یک شغل خوب در زمینه دادهکاوی چیست؟! راه حل، انجام پروژههای عملی است که بتوانند مهارت فرد را در تحلیل داده و کار با ابزارها نشان دهند. یکی از گزینههای خوب در این راستا انجام تحلیل جهت نشان دادن «تاثیر کسبوکار» (Business Impact) است. منظور از «تاثیر کسبوکار» چیست؟ هر کسبوکاری در پایان روز روی چیزی کار میکند که مربوط به بخش تجاری کارهای آن است. این موضوع میتواند کاهش هزینهها، افزایش درآمد، بهبود تجربه مشتریان و مواردی مانند اینها باشد. در این مطلب چگونگی ساخت یک مدل «پیش بینی ریزش مشتریان» (Customer Churn) که مساله مهمی در تاثیر کسبوکار محسوب میشود، به صورت گام به گام آموزش داده خواهد شد. مباحثی که در ادامه مورد بررسی قرار میگیرند به شرح زیر هستند:
- زمینه کسبوکار
- رگرسیون لجستیک
- آمادهسازی دادهها
- «برازش» (Fitting) مدل
- انجام پیشبینی
- تاثیر کسبوکار
زمینه کسبوکار
در آغاز هر پروژه «علم داده» (Data Science) جهان واقعی، نیاز به پرسیدن چند سوال است. برخی از آنها در ادامه بیان شدهاند.
- مسالهای که برای حل آن تلاش میشود چیست؟
- راهکار بالقوه برای انجام این کار چیست؟
- چگونه میتوان مدل را ارزیابی کرد؟
اکنون، در ادامه موضوع رویگردانی مشتریان، فرض میشود که دادهکاو در یک شرکت مخابراتی کار میکند. یک روز رئیس شرکت از او میپرسد: «چگونه میتوانیم کسبوکار خودمان را با استفاده از دادههای موجود ارتقا دهیم؟».
مسالهای که قرار است حل شود چیست؟
دادهکاو پس از بررسی دادههای موجود، متوجه میشود به دست آوردن مشتری جدید نسبت به حفظ مشتریان موجود پنج برابر هزینه بیشتری برای سازمان در پی دارد. اکنون سوالی که بیشتر روی آن متمرکز میشود این است که: «چگونه میتوان نرخ حفظ مشتری را افزایش داد تا هزینهها کاهش پیدا کنند؟».
راهکار بالقوه چیست؟
برای دسترسی داشتن به دادههای مشتریان، میتوان یک مدل یادگیری ماشین را ساخت که «رویگردانی مشتریان» را پیشبینی کند. برای ممانعت از پیچیده شدن مسائل با چیزی که بسیار خلاصه و قابل ارائه به مدیر است، دادهکاو از مدل «رگرسیون لجستیک» (Logistic Regression) استفاده میکند.
چگونه میتوان مدل را ارزیابی کرد؟
در اینجا از یک سری از سنجههای ارزیابی مدل از جمله «منحنی مشخصه عملکرد سیستم» (Receiver Operating Characteristic | ROC)، «ناحیه زیر نمودار» (Area Under the Curve | AUC)، «حساسیت» (Sensitivity) و «ویژگی» (Specificity) و سنجههای مرتبط با کسبوکار (صرفهجویی در هزینهها) استفاده میشود.
رگرسیون لجستیک
اکنون آشنایی با زمینه کسبوکار حاصل و بر همین اساس دامنه مساله تعیین شد. مدلهای «یادگیری ماشین» (Machine Learning) متعددی وجود دارند که میتوان از آنها استفاده کرد. همه این روشها مزایا و معایب خود را دارند. برای ساده نگهداشتن مسائل، در اینجا از رگرسیون لجستیک استفاده میشود.
رگرسیون لجستیک یک «دستهبند خطی» (Linear Classifier) محسوب میشود. با توجه به اینکه هدف تعیین این است که کدام مشتریان رویگردان هستند و کدام نیستند، دستهبندی دقیقا رویکرد مورد نیاز این مساله محسوب میشود.
این مدل عالی است به این دلیل که «تفسیر» آن نسبت به بسیاری از دیگر مدلها مانند «جنگل تصادفی» (Random Forest) سادهتر خواهد بود. منظور از تفسیر، آن است که میتوان به سادگی رابطه بین ویژگیها و خروجی را دید. ویژگی منفی رگرسیون لجستیک، دارا بودن «سوگیری» (Bias | بایاس) به سمت برازشهای خطی است. اگر مرزهای تصمیم خطی نباشند، عملکرد این روش ممکن است به خوبی جنگل تصادفی نباشد. اساسا، باید موازنهای بین تفسییرپذیری و انعطافپذیری مدل وجود داشته باشد. این موضوع همیشه هنگام پیادهسازی مدلهای یادگیری ماشین در نظر گرفته میشود.
آمادهسازی دادهها
گام بعدی آمادهسازی دادهها است. این جریان کاری از پروژهای به پروژه دیگر متفاوت است، در مثال این مطلب، از جریان کاری زیر استفاده خواهد شد.
- ایمپورت کردن دادهها
- نگاه کلی به آنها
- پاکسازی دادهها
- بخشبندی دادهها
۱. ایمپورت کردن دادهها
کار با ایمپورت کردن دادهها (+) آغاز میشود. در اینجا از کتابخانه Tidyverse استفاده خواهد شد. این کتابخانه از جمله مواردی است که دانشمندان دادهای که با R کار میکنند باید با آن آشنا باشند.
1library(tidyverse)
2
3# setting the working directory
4path_loc <- "C:/Users/Jonathan/Desktop/post"
5setwd(path_loc)
6
7# reading in the data
8df <- read_csv("Telco Data.csv")
در قطعه کد بالا، «مسیر» (PATH) در آغاز کار روی دایرکتوری سیستم کاربر تنظیم شده. نکتهای که باید به آن توجه کرد آن است که متغیر path_loc را باید به دایرکتوری در حال کار، یعنی جایی که کد و مجموعه داده قرار میگیرند تغییر داد. از آنجا که این یک فایل CSV است، تابع read_csv برای خواندن دادهها در یک دیتافریم df مورد استفاده قرار گرفته.
۲. نگاه کلی به دادهها
پس از آنکه دادهها ایمپورت شدند، بهتر است نگاه کلی به آنها انداخته شود. در همین راستا، ابعاد و نام ستونهای ویژگیها مورد بررسی قرار میگیرند.
1# dimensions of the data
2dim_desc(df)
3
4# names of the data
5names(df)
6> # dimensions of the data
7> dim_desc(df)
8[1] "[7,043 x 21]"
9>
10> # names of the data
11> names(df)
12 [1] "customerID" "gender" "SeniorCitizen" "Partner" "Dependents" "tenure"
13 [7] "PhoneService" "MultipleLines" "InternetService" "OnlineSecurity" "OnlineBackup" "DeviceProtection"
14[13] "TechSupport" "StreamingTV" "StreamingMovies" "Contract" "PaperlessBilling" "PaymentMethod"
میتوان مشاهده کرد که ۲۱ ویژگی و ۷۰۴۳ سطر وجود دارد. ویژگیهای متنوعی مانند TotalCharges و tenure موجود هستند. خروجی که مقرر شده پیشبینی شود، «churn» (رویگردانی) است. در این راستا، دیگر تابعی که مورد استفاده قرار میگیرد glimpse است که امکان بررسی سریع ویژگیها را همراه با جزئیات بیشتر میدهد.
1# taking a look at the data
2glimpse(df)
3> glimpse(df)
4Observations: 7,043
5Variables: 21
6$ customerID "7590-VHVEG", "5575-GNVDE", "3668-QPYBK", "7795-CFOCW", "9237-HQITU", "9305-CDSKC", "1452-K...
7$ gender "Female", "Male", "Male", "Male", "Female", "Female", "Male", "Female", "Female", "Male", "...
8$ SeniorCitizen 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1...
9$ Partner "Yes", "No", "No", "No", "No", "No", "No", "No", "Yes", "No", "Yes", "No", "Yes", "No", "No...
10$ Dependents "No", "No", "No", "No", "No", "No", "Yes", "No", "No", "Yes", "Yes", "No", "No", "No", "No"...
11$ tenure 1, 34, 2, 45, 2, 8, 22, 10, 28, 62, 13, 16, 58, 49, 25, 69, 52, 71, 10, 21, 1, 12, 1, 58, 4...
12$ PhoneService "No", "Yes", "Yes", "No", "Yes", "Yes", "Yes", "No", "Yes", "Yes", "Yes", "Yes", "Yes", "Ye...
13$ MultipleLines "No phone service", "No", "No", "No phone service", "No", "Yes", "Yes", "No phone service",...
14$ InternetService "DSL", "DSL", "DSL", "DSL", "Fiber optic", "Fiber optic", "Fiber optic", "DSL", "Fiber opti...
15$ OnlineSecurity "No", "Yes", "Yes", "Yes", "No", "No", "No", "Yes", "No", "Yes", "Yes", "No internet servic...
16$ OnlineBackup "Yes", "No", "Yes", "No", "No", "No", "Yes", "No", "No", "Yes", "No", "No internet service"...
17$ DeviceProtection "No", "Yes", "No", "Yes", "No", "Yes", "No", "No", "Yes", "No", "No", "No internet service"...
18$ TechSupport "No", "No", "No", "Yes", "No", "No", "No", "No", "Yes", "No", "No", "No internet service", ...
19$ StreamingTV "No", "No", "No", "No", "No", "Yes", "Yes", "No", "Yes", "No", "No", "No internet service",...
20$ StreamingMovies "No", "No", "No", "No", "No", "Yes", "No", "No", "Yes", "No", "No", "No internet service", ...
21$ Contract "Month-to-month", "One year", "Month-to-month", "One year", "Month-to-month", "Month-to-mon...
22$ PaperlessBilling "Yes", "No", "Yes", "No", "Yes", "Yes", "Yes", "No", "Yes", "No", "Yes", "No", "No", "Yes",...
23$ PaymentMethod "Electronic check", "Mailed check", "Mailed check", "Bank transfer (automatic)", "Electroni...
24$ MonthlyCharges 29.85, 56.95, 53.85, 42.30, 70.70, 99.65, 89.10, 29.75, 104.80, 56.15, 49.95, 18.95, 100.35...
25$ TotalCharges 29.85, 1889.50, 108.15, 1840.75, 151.65, 820.50, 1949.40, 301.90, 3046.05, 3487.95, 587.45,...
26$ Churn "No", "No", "Yes", "No", "Yes", "Yes", "No", "No", "Yes", "No", "No", "No", "No", "Yes", "N...
۳. پاکسازی دادهها
پیش از آنکه از مدل رگرسیون لجستیک استفاده شود، نیاز به انجام پاکسازیهای کوچکی است.
این کار با تغییر دادن متغیرهای «کاراکتر» (character) به «فاکتور» (factor) آغاز میشود.
1# changing character variables to factors
2df <- df %>% mutate_if(is.character, as.factor)
در این کد از تابع mutate_if بسته dplyr (+) برای تغییر متغیر کاراکترها به نوع فاکتورها استفاده میشود. %>% تحت عنوان متغیر پایپ شناخته شده است. این متغیر از کتابخانه magrittr (+) میآید که بخشی از Tidyverse محسوب میشود. ایده اصلی این عملگر آن است که کد را خواناتر کند. اکنون میتوان متغیر SeniorCitizen را از «صحیح» (integer) به «فاکتور» (factor) تغییر داد.
1# changing SeniorCitizen variable to factor
2df$SeniorCitizen <- as.factor(df$SeniorCitizen)
سپس، «مقادیر ناموجود» (missing values) مورد بررسی قرار میگیرند. این کار با تابع map از کتابخانه purrr (+) انجام میشود. purrr نیز یکی از کتابخانههای مجموعه بستههای Tidyverse است.
1# looking for missing values
2df %>% map(~ sum(is.na(.)))
3> df %>% map(~ sum(is.na(.)))
4$`customerID`
5[1] 0
6
7$gender
8[1] 0
9
10$SeniorCitizen
11[1] 0
12
13$Partner
14[1] 0
15
16$Dependents
17[1] 0
18
19$tenure
20[1] 0
21
22$PhoneService
23[1] 0
24
25$MultipleLines
26[1] 0
27
28$InternetService
29[1] 0
30
31$OnlineSecurity
32[1] 0
33
34$OnlineBackup
35[1] 0
36
37$DeviceProtection
38[1] 0
39
40$TechSupport
41[1] 0
42
43$StreamingTV
44[1] 0
45
46$StreamingMovies
47[1] 0
48
49$Contract
50[1] 0
51
52$PaperlessBilling
53[1] 0
54
55$PaymentMethod
56[1] 0
57
58$MonthlyCharges
59[1] 0
60
61$TotalCharges
62[1] 11
63
64$Churn
65[1] 0
میتوان مشاهده کرد که TotalCharges به تعداد ۱۱ مقدار ناموجود دارد. برای جایگزینی این مقادیر ناموجود، میتوان آنها را با «میانه» (median) جایگزین کرد.
1# imputing with the median
2df <- df %>%
3 mutate(TotalCharges = replace(TotalCharges,
4 is.na(TotalCharges),
5 median(TotalCharges, na.rm = T)))
البته این علمیترین و بهترین رویکرد برای جایگزین مقادیر ناموجود نیست. راهکارهای متعدد و بهتری موجود هستند که امکان بررسی آنها در این مطلب وجود ندارد. در این گام، آخرین چیزی که باید حذف کرد، ویژگی CustomerID است. این ویژگی، یک شناساگر یکتا برای هر مشتری است، و احتمالا هیچ اطلاعات مفیدی در اختیار مدل قرار نمیدهد.
1# removing customerID; doesn't add any value to the model
2df <- df %>% select(-customerID)
۴. بخشبندی دادهها
برای حصول اطمینان از اینکه مدل دچار بیشبرازش نمیشود، دادهها به دستههای «تست» (Test) و «آموزش» (Train) تقسیم میشوند. به این کار «اعتبارسنجی متقابل» (Cross Validation) گفته میشود. مدل روی یک مجموعه آموزش تحت آموزش قرار میگیرد و سپس کارایی آن با استفاده از مجموعه تست ارزیابی میشود.
به طور تصادفی ٪۷۵ از دادهها برای مجموعه داده آموزش انتخاب میشوند و ٪۲۵ دادهها برای تست خواهند بود. آزمودن نسبت درصدهای گوناگون برای مقایسه نتایج، به علاقمندان توصیه میشود (مثلا نسبت ٪۸۰ به ٪۲۰ و ٪۶۰ به ٪۴۰ برای مجموعههای آموزش و تست). برای تقسیم دادهها، از بسته Caret (+) استفاده میشود. اکنون کار با ایمپورت کردن Caret آغاز و یک seed تنظیم میشود تا قابلیت تولید مجدد نتایج مورد بررسی قرار بگیرد.
1library(caret)
2
3# selecting random seed to reproduce results
4set.seed(5)
سپس، از تابع createDataPartition برای انتخاب ٪۷۵ از دادهها برای استفاده در مجموعه آموزش استفاده میشود. این امر منجر به انتخاب یک نمونه تصادفی از ٪۷۵ سطرها میشود.
1# sampling 75% of the rows
2inTrain <- createDataPartition(y = df$Churn, p=0.75, list=FALSE)
در نهایت، دیتافریمهای آموزش و تست با استفاده از نمونه سطرها از بالا ساخته میشوند.
1# train/test split; 75%/25%
2train <- df[inTrain,]
3test <- df[-inTrain,]
روشهای دیگری مانند «اعتبارسنجی متقابل K-fold» وجود دارد. برای کسب اطلاعات بیشتر پیرامون چگونگی پیادهسازی k-fold در Caret، کافی است دستور (”help(“createFolds در کنسول R وارد شود.
برازش مدل
اکنون که مدل به دو بخش دادههای تست و آموزش تقسیم بندی شد، زمان برازش مدل فرا رسیده است. برای پیادهسازی رگرسیون لجستیک، از تابع glm مربوط به «مدلهای خطی عمومی شده» (Generalized Linear Models | GLM) استفاده میشود.
انواع متفاوتی از GLMها وجود دارند که رگرسیون لجستیک نیز از این جمله است. برای تعیین اینکه قصد اجرای رگرسیون لجستیک دودویی وجود دارد، از آرگومان family=binomial استفاده میشود. در ادامه کد کامل برازش مدل رگرسیون لجستیک آورده شده است.
1# fitting the model
2fit <- glm(Churn~., data=train, family=binomial)
در بخش بعدی، پیشبینی انجام و مدل ارزیابی میشود.
انجام پیشبینی
اکنون که مدل برازش شد، زمان بررسی چگونگی عملکرد آن فرا رسیده است. برای انجام این کار، پیشبینیها با استفاده از مجموعه داده test انجام میشوند. این مجموعه داده به مدل fit از بخش قبل پاس داده میشود. برای پیشبینی احتمالها ”type=”response تعیین میشود.
1# making predictions
2churn.probs <- predict(fit, test, type="response")
3head(churn.probs)
4> head(churn.probs)
5 1 2 3 4 5 6
60.32756804 0.77302887 0.56592677 0.20112771 0.05152568 0.15085976
اکنون، این احتمالها به پاسخهای دودویی تبدیل میشوند، زیرا متغیر Churn که قصد پیشبینی آن وجود دارد، «بلی» یا «خیر» است. این مورد با استفاده از تابع contrasts به سرعت انجام میشود.
1# Looking at the response encoding
2contrasts(df$Churn)
3> contrasts(df$Churn)
4 Yes
5No 0
6Yes 1
با توجه به نتایج، میتوان مشاهده کرد که Yes با استفاده از ۱ رمزنگاری شده. حالا که رمزنگاری پاسخ مشخص است، میتوان نتایج پیشبینی شده را به پاسخهای Yes و No تبدیل کرد. آستانه پاسخ روی 0.5 تنظیم میشود، بنابراین، اگر یک احتمال پیشبینی شده بالاتر از ۰.۵ باشد، میتوان پاسخ را به Yes تبدیل کرد. گام نهایی، تبدیل کردن پاسخهای کاراکتری به «انواع فاکتور» (Factor Types) است. بدین ترتیب، رمزنگاری برای مدل رگرسیون لجستیک صحیح است.
1# converting probabilities to "Yes" and "No"
2glm.pred = rep("No", length(churn.probs))
3glm.pred[churn.probs > 0.5] = "Yes"
4glm.pred <- as.factor(glm.pred)
بعدا نگاهی عمیقتر به آستانه میشود، بنابراین جای نگرانی پیرامون چرایی قرار دادن این مقدار برابر با ۰.۵ وجود ندارد. در حال حاضر این مقدار با توجه به هدف این مثال، برابر با این عدد قرار داده شده است. بخش مهمی از انجام پیشبینی «ارزیابی» (Evaluating) و «اعتبارسنجی» (Validating) مدل انجام پیشبینی است. اکنون نگاهی همراه با جزئیات به برخی از سنجههای ارزیابی انداخته و از یک روش دقیق با نام «اعتبارسنجی متقابل k-fold» برای اعتبارسنجی مدل استفاده میشود.
ارزیابی مدل
پس از آنکه پیشبینی انجام شد، زمان ارزیابی مدل فرا میرسد. یک ابزار مناسب برای انجام سریع این کار، استفاده از تابع confusionMatrix از Caret است.
همچون نتایج واقعی، از test$Churn به آرایه پیشبینیهای glm.pred خوراک داده میشود. در نهایت، کلاس مثبت با استفاده از ”positive=”Yes به صورت «Yes» تعیین میشود.
1# creating a confusion matrix
2confusionMatrix(glm.pred, test$Churn, positive = "Yes")
3> confusionMatrix(glm.pred, test$Churn, positive = "Yes")
4Confusion Matrix and Statistics
5
6 Reference
7Prediction No Yes
8 No 1165 205
9 Yes 128 262
10
11 Accuracy : 0.8108
12 95% CI : (0.7917, 0.8288)
13 No Information Rate : 0.7347
14 P-Value [Acc > NIR] : 4.239e-14
15
16 Kappa : 0.4877
17 Mcnemar's Test P-Value : 3.117e-05
18
19 Sensitivity : 0.5610
20 Specificity : 0.9010
21 Pos Pred Value : 0.6718
22 Neg Pred Value : 0.8504
23 Prevalence : 0.2653
24 Detection Rate : 0.1489
25 Detection Prevalence : 0.2216
26 Balanced Accuracy : 0.7310
27
28 'Positive' Class : Yes
این تابع، «ماتریس درهمریختگی» (Confusion Matrix) و دیگر موارد آماری را تولید میکند. یک ماتریس در همریختگی، نشان میدهد که چه تعداد پیشبینی صحیح و غلط برای هر کلاس انجام شده. در ادامه چشماندازی از ماتریس درهمریختگی برای مدل موجود، ارائه شده است.
میتوان مشاهده کرد که مدل به درستی «۱۱۶۵» بار، «No» را پیشبینی کرده و ۲۰۵ بار در حالیکه پاسخ صحیح «Yes» بوده، مدل به غلط «No» پیشبینی کرده است. به همین ترتیب، ۲۶۲ بار هنگامی که پاسخ صحیح «Yes» بوده به درستی پیشبینی کرده و ۱۲۸ بار به اشتباه پاسخ را «No» پیشبینی کرده است. صحت کلی برابر با ۸۱٪ است. یک مدل پایه ساده، پیشبینی دسته اکثریت محسوب میشود که در این مثال «No» است. اگر فقط کلاس اکثریت پیشبینی شود، صحت ٪۷۳ میشود. ۱۷۶۰ مشاهده در مجموعه تست و ۱۲۹۳ مورد «No» وجود دارد. اگر ۱۲۹۳ تقسیم بر ۱۷۶۰ شود، صحت به ۷۳٪ میرسد.
از دیگر سنجههای مفید میتوان به «حساسیت» (Sensitivity) و «ویژگی» (Specificity) اشاره کرد. از آنجا که کلاسها اندکی نامتوازن هستند، $$\text{~73%=
دیگر سنجه مفید، «ویژگی» (Specificity) است که نرخ «منفی صحیح» را نشان میدهد. این مقدار سنجهای از میزان پیشبینی صحیح کلاس منفی است. در ادامه، چگونگی انجام این کار شرح داده شده.
و سنجه دیگری که در این راستا مفید به شمار میآید، «ناحیه زیر منحنی مشخصه عملکرد سیستم» (Area Under the Receiver Operating Characteristic | ROC) است که AUC نیز نامیده میشود. در این روش که برای اولین بار در طول جنگ جهانی دوم برای تحلیل سیگنالهای رادارها پیادهسازی شد، ROC نمودار نرخ مثبت صحیح و نرخ مثبت غلط بود. اکنون به مدل اصلی که ۰.۵ در آن به عنوان آستانه «Yes» (یا مثبت) پیشبینیها در نظر گرفته شده بازگشته و پیرامون آن صحبت میشود. حقیقتا توجیه خوبی برای انتخاب ۰.۵ وجود ندارد. ROC ابزار خوبی است، زیرا TPR را در قیاس با FPR از آنجا که آستانه متنوع است، ترسیم میکند. میتوان نموداری از منحنی ROC را با استفاده از کتابخانه ROCR ترسیم کرد. کد کامل به زبان R برای این کار، در ادامه آمده است.
1library(ROCR)
2# need to create prediction object from ROCR
3pr <- prediction(churn.probs, test$Churn)
4
5# plotting ROC curve
6prf <- performance(pr, measure = "tpr", x.measure = "fpr")
7plot(prf)
چنانچه پیشتر بیان شد، دیگر سنجه مفید ناحیه زیر منحنی ROC است. AUC میتواند مقداری بین ۰ و ۱ داشته باشد که در آن ۱ بهترین مقدار است. این راهکاری مناسب برای تبدیل ROC به یک عدد یکتا به منظور ارزیابی مدل است. کد R برای ارزیابی مدل در ادامه آمده.
1# AUC value
2auc <- performance(pr, measure = "auc")
3auc <- auc@y.values[[1]]
4auc
5> auc
6[1] 0.8481338
مدل دارای AUC برابر با 0.85 است که بسیار خوب به حساب میآید. اگر فقط حدسهای تصادفی زده شود، ROC یک خط ۴۵ درجه میشود. این مقدار در واقع یعنی AUC برابر با 0.5 است. عملکرد مدل موجود فراتر از حدسهای تصادفی است و این یعنی دستکم مدل اندکی ارزش ایجاد میکند.
اعتبارسنجی متقابل K-fold
اکنون که مدل آموزش دید، تست و ارزیابی شد، زمان آن رسیده که با دقتی بیشتر ارزیابی شود. از همین رو، مجموعه داده را به دو بخش دادههای تست و آموزش تقسیم کرده تا بتوان مدل را مورد ارزیابی قرار داد.
این کار راهی مناسب برای آزمودن مدل و پیشگیری از «بیشبرازش» (Overfitting) است. و رویکرد بهتر برای تقسیم دادهها روش «اعتبارسنجی متقابل K-Fold» یا «K-fold Cross Validation» است.
در این روش ارزیابی مدل، دادهها به طور تصادفی با انتخاب تعداد مشخصی «Fold» به مجموعههای تست و آموزش تقسیم میشوند. در مثال بالا، تعداد foldها برابر با k = ۴ است. پس از آنکه مدل روی هر fold اجرا شد، میانگین سنجه ارزیابی از هر یک محاسبه میشود. بنابراین، میانگین هر یک از چهار مقدار ROC با یکدیگر محاسبه میشوند. این راهکاری مناسب برای امتحان کردن و پیشگیری از بیشبرازش در مدل است. یک تعداد متداول برای foldها ۱۰ است، بنابراین در اینجا از این عدد استفاده میشود. همچنین، این فرآیند سه بار تکرار میشود تا دقت فنی رویکرد اندکی افزایش یابد. کد کامل نوشته شده به زبان R برای این کار در ادامه آمده است.
1# setting a seed for reproduceability
2set.seed(10)
3
4# changing the positive class to "Yes"
5df$Churn <- as.character(df$Churn)
6df$Churn[df$Churn == "No"] <- "Y"
7df$Churn[df$Churn == "Yes"] <- "N"
8df$Churn[df$Churn == "Y"] <- "Yes"
9df$Churn[df$Churn == "N"] <- "No"
10
11# train control
12fitControl <- trainControl(## 10-fold CV
13 method = "repeatedcv",
14 number = 10,
15 ## repeated 3 times
16 repeats = 3,
17 classProbs = TRUE,
18 summaryFunction = twoClassSummary)
19
20# logistic regression model
21logreg <- train(Churn ~., df,
22 method = "glm",
23 family = "binomial",
24 trControl = fitControl,
25 metric = "ROC")
26logreg
27> logreg
28Generalized Linear Model
29
307043 samples
31 19 predictor
32 2 classes: 'No', 'Yes'
33
34No pre-processing
35Resampling: Cross-Validated (10 fold, repeated 3 times)
36Summary of sample sizes: 6338, 6339, 6338, 6339, 6339, 6339, ...
37Resampling results:
38
39 ROC Sens Spec
40 0.8455546 0.5500297 0.89602
در قطعه کد بالا، کار با تنظیم کردن تنظیمات k-fold CV با استفاده از تابع trainControl آغاز میشود. همه ورودیها کاملا سر راست هستند. همانطور که پیشتر بیان شد، از ۱۰ fold که سه بار تکرار میشوند استفاده خواهد شد. در گام بعدی مدل آموزش داده میشود. همانطور که پیشتر این کار انجام شد، از خانواده «binomial» از متد «glm» برای انجام آن استفاده میشود. برای ارزیابی مدل، از «ROC» استفاده میشود. مدل در واقع AUC را گزارش میدهد، اما روشی که باید آن را در تابع train تعیین کرد، استفاده از ”metric=”ROC است.
دیگر نکتهای که ممکن است توجهات را به خود جلب کند، تغییر دادن کلاس مثبت به «Yes»، درست پیش از کد برای تابع trainControl است. این کار با هدف مقایسه حساسیت و ویژگی برای نتایج پیشین انجام میشود. این مورد صرفا یک تغییر کوچک در تابع است و نیاز نیست بیش از اندازه روی آن وقت گذاشت. نتایج مشابه آنچه هستند که پیشتر حاصل شد. همچون قبل، AUC برابر با ۰.۸۵ است. این مورد در خروجی به عنوان ROC ارائه شده، در حالیکه در حقیقت AUC است. «نرخ مثبت صحیح» (true positive rate) (همان حساسیت یا sensitivity) برابر با ۰.۵۵ و نرخ منفی صحیح (true negative rate) (همان ویژگی یا specificity) برابر با ۰.۹۰ است.
تاثیر کسبوکار
تا این لحظه از اعتبارسنجی متقابل k-fold و رگرسیون لجستیک برای پیشبینی رویگردانی مشتریان استفاده شده است. همچنین، نگاهی به سنجههای مفید ارزیابی مانند AUC، حساسیت و ویژگی انداخته شد. همه این موارد خوب است، اما حالا چه؟ اگر دادهکاوی با این نتایج نزد مدیرعامل برود و نتایج را به تنهایی ارائه کند او خواهد گفت: «خب که چی؟». هدف نهایی از توسعه این مدل، نشان دادن تاثیر کسبوکار است. در این مثال، این کار منجر به «صرفهجویی در هزینهها» میشود.
در ادامه، چگونگی صرفهجویی در هزینهها به صورت گام به گام تشریح خواهد شد. در این راستا مفروضاتی پیرامون هزینههای گوناگون در نظر گرفته میشود. فرض میشود که «هزینه جلب مشتری» (customer acquisition cost) در صنعت مخابرات تقریبا ۳۰۰ دلار است.
اگر پیشبینی شود که یک مشتری رویگردان نیست، اما او این کار را در واقعیت انجام دهد (منفی غلط یا false negative)، سازمان باید ۳۰۰ دلار برای به دست آوردن جایگزینی برای مشتری از دست رفته هزینه کند. اکنون، فرض میشود که به دست آوردن مشتری جدید، پنج برابر پرهزینهتر از حفظ مشتری کنونی است. بنابراین اگر سازمان پیشبینی کند که مشتری رویگردانی خواهد کرد، باید ۶۰ دلار برای آن هزینه شود. گاهی، مشتریانی که رویگردانی خواهند کرد به درستی پیشبینی میشوند (مثبت صحیح یا True Positive | TP)، و گاهی به اشتباه پیشبینی میشود که مشتری ممکن است رویگردانی داشته باشد (مثبت غلط یا False Positive | FP). در هر دو شرایط، ۶۰ دلار برای باقی ماندن مشتری هزینه خواهد شد. در نهایت، سناریو این است که به درستی پیشبینی شود که مشتری رویگردان نیست (منفی صحیح یا True Negative | TN). در این سناریو هیچ پولی خرج نخواهد شد. اینها مشتریان خوشحالی هستند که به درستی به عنوان خوشحال تعیین شدهاند. در ادامه خلاصهای از هزینهها بیان شده است.
- FN (پیشبینی اینکه یک مشتری رویگردان نیست، اما مشتری در حقیقت رویگردان است): ۳۰۰ دلار
- TP (پیشبینی اینکه یک مشتری رویگردان است و آن مشتری در واقعیت نیز رویگردان باشد): ۶۰ دلار
- FP (پیشبینی اینکه یک مشتری رویگردان است، در حالیکه واقعا رویگردان نیست): ۶۰ دلار
- TN (پیشبینی اینکه یک مشتری رویگردان نیست، و در حقیقت نیز رویگردان نباشد): ۰ دلار
اگر تعداد هر نوع پیشبینی (FP ،TP، FP و TN) در هزینه مربوط به هر یک از آنها ضرب و مجموع آنها محاسبه شود، میتوان هزینه مربوط به مدل را به دست آورد. معادله انجام این محاسبات مطابق فرمول زیر است.
(Cost = FN($300) + TP($60) + FP($60) + TN($0
به عنوان مثال، فرض میشود که تعداد پیشبینیهای هر مورد به شرح زیر است:
- FN = 10
- TP = 5
- FP = 5
- TN = 5
10*($300) + 5*($60) + 5*($60) + 5*($0) = $3600
بنابراین، هزینه کلی ۳۶۰۰ دلار خواهد بود. اکنون، این ارزیابی هزینه روی مدل اعمال خواهد شد. کار با برازش مدل و انجام پیشبینی به شکل احتمال انجام میشود.
1# fitting the logistic regression model
2fit <- glm(Churn~., data=train, family=binomial)
3
4# making predictions
5churn.probs <- predict(fit, test, type="response")
سپس، یک بردار آستانه و یک بردار هزینه ساخته خواهد شد.
1# threshold vector
2thresh <- seq(0.1,1.0, length = 10)
3
4#cost vector
5cost = rep(0,length(thresh))
بردار آستانه، مقدار آستانه را برای هر مدل نگهداری میکند. پیشتر از ۰.۵ به عنوان آستانه استفاده شد، اما اکنون، نگاهی به آستانهها مختلف با افزایش پلکانی ۰.۱ بین ۰ و ۱ (برای مثال ۰.۱، ۰.۲، ۰.۳، ۰.۴، ۰.۵، ...، ۰.۹ و ۱.۰) انداخته میشود. بردار هزینه، برداری به طول ۱۰ با مقادیر صفر در آغاز است. این بردار با حلقهای از طریق تابع تکمیل و هر آستانه در حین پیش رفتن محاسبه میشود. برای ارزیابی هزینهها، از معادله هزینهای مشابه با آنچه پیشتر بیان شد، استفاده میشود. اکنون، یک حلقه for برای انجام پیشبینی با استفاده از آستانههای مختلف مورد استفاده قرار میگیرد و هزینه هر مورد محاسبه میشود.
1# cost as a function of threshold
2for (i in 1:length(thresh)){
3
4 glm.pred = rep("No", length(churn.probs))
5 glm.pred[churn.probs > thresh[i]] = "Yes"
6 glm.pred <- as.factor(glm.pred)
7 x <- confusionMatrix(glm.pred, test$Churn, positive = "Yes")
8 TN <- x$table[1]/1760
9 FP <- x$table[2]/1760
10 FN <- x$table[3]/1760
11 TP <- x$table[4]/1760
12 cost[i] = FN*300 + TP*60 + FP*60 + TN*0
13}
به جای استفاده از تعداد کل هر خروجی برای FN ،FP ،TN و TP، از درصد به جای آن استفاده خواهد شد. به همین دلیل است که مقادیر که از ماتریس درهمریختگی دریافت و بر ۱۷۶۰ تقسیم شدهاند. ۱۷۶۰ مشاهده در مجموعه تست وجود دارد، و بنابراین عددی که برای مخرج کسر استفاده شد، از اینجا آمده است. با انجام چنین کاری، «هزینه به ازای هر مشتری» (Cost Per Customer) محاسبه میشود. اکنون، فرض میشود که شرکت در حال حاضر از چیزی استفاده میکند که دادهکاو آن را «مدل ساده» نامیده است و مقدار پیشفرض آستانه آن ۰.۵ است. میتوان گامی جلوتر رفت و مدل را برازش کرد، پیشبینی انجام داد و هزینه را محاسبه کرد. به این مدل cost_simple گفته میشود.
1# simple model - assume threshold is 0.5
2glm.pred = rep("No", length(churn.probs))
3glm.pred[churn.probs > 0.5] = "Yes"
4glm.pred <- as.factor(glm.pred)
5
6x <- confusionMatrix(glm.pred, test$Churn, positive = "Yes")
7TN <- x$table[1]/1760
8FP <- x$table[2]/1760
9FN <- x$table[3]/1760
10TP <- x$table[4]/1760
11cost_simple = FN*300 + TP*60 + FP*60 + TN*0
در نهایت، میتوان کل نتایج را در یک دیتافریم قرار داد و نمودار آنها را ترسیم کرد.
1# putting results in a dataframe for plotting
2dat <- data.frame(
3 model = c(rep("optimized",10),"simple"),
4 cost_per_customer = c(cost,cost_simple),
5 threshold = c(thresh,0.5)
6)
7
8# plotting
9ggplot(dat, aes(x = threshold, y = cost_per_customer, group = model, colour = model)) +
10 geom_line() +
11 geom_point()
با نگاهی به نتایج، میتوان فهمید که حداقل هزینه به ازای هر مشتری در آستانه ۰.۲ حدود ۴۰.۰۰ دلار است. مدل «ساده»، که شرکت در حال حاضر پیادهسازی کرده، در آستانه ۰.۵۰ حدود ۴۸.۰۰ دلار به ازای هر مشتری هزینه دارد. اگر فرض شود که شرکت در حال حاضر تقریبا ۵۰۰٬۰۰۰ مشتری دارد، جابهجایی بین مدل ساده به مدل بهینه، ۴ میلیون دلار صرفهجویی در هزینهها را به همراه دارد.
1# cost savings of optimized model (threshold = 0.2) compared to baseline model (threshold = 0.5)
2savings_per_customer = cost_simple - min(cost)
3
4total_savings = 500000*savings_per_customer
5
6total_savings
7> total_savings
8[1] 4107955
چنین صرفهجویی در هزینهها بسته به سایز کسبوکار میتواند تاثیر کسبوکار قابل توجهی داشته باشد.
نتیجهگیری
در این مطلب، یک مدل یادگیری ماشین برای پیشبینی رویگردانی مشتریان ارائه شده است. مراحل زیر به این منظور انجام شده:
- زمینه کسبوکار
- رگرسیون لوجستیک
- آمادهسازی دادهها
- برازش مدل
- انجام پیشبینی
- تاثیر کسبوکار
در نهایت، یک مدل رگرسیون لجستیک بهینه شده برای مساله رویگردانی مشتریان (ریزش مشتریان) حاصل شد. با این فرض که شرکت از مدل رگرسیون لوجستیک با مقدار آستانه پیشفرض ۰.۵ استفاده میکند، میتوان فهمید که مقدار بهینه واقعی ۰.۲ است. این کار هزینه به ازای مشتری را از ۴۸.۰۰ دلار به ۴۰.۰۰ دلار کاهش میکند. با یک پایگاه مشتریان ۵۰۰۰۰۰ نفری، این کار منجر به صرفهجویی سالانه ۴ میلیون دلار میشود. اگرچه مساله مطرح شده، یک مثال کاملا فرضی بود، اما بسیار شبیه به چیزی است که در جهان واقعی به وقوع میپیوندد.
اگر نوشته بالا برای شما مفید بوده، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای برنامه نویسی پایتون (Python)
- مجموعه آموزشهای آمار، احتمالات و دادهکاوی
- مجموعه آموزشهای یادگیری ماشین و بازشناسی الگو
- مجموعه آموزشهای شبکههای عصبی مصنوعی
- مجموعه آموزشهای هوش محاسباتی
- آموزش برنامهنویسی R و نرمافزار R Studio
- مجموعه آموزشهای برنامه نویسی متلب (MATLAB)
^^