通透!如何從頭構(gòu)建一個(gè)機(jī)器學(xué)習(xí)模型
大家好,我是小寒。
今天我將帶領(lǐng)大家一步步的來構(gòu)建一個(gè)機(jī)器學(xué)習(xí)模型。
我們將按照以下步驟開發(fā)客戶流失預(yù)測(cè)分類模型。
- 業(yè)務(wù)理解
- 數(shù)據(jù)收集和準(zhǔn)備
- 建立機(jī)器學(xué)習(xí)模型
- 模型優(yōu)化
- 模型部署
1.業(yè)務(wù)理解
在開發(fā)任何機(jī)器學(xué)習(xí)模型之前,我們必須了解為什么要開發(fā)該模型。
這里,我們以客戶流失預(yù)測(cè)為例。
在這種情況下,企業(yè)需要避免公司進(jìn)一步流失,并希望對(duì)流失概率高的客戶采取行動(dòng)。有了上述業(yè)務(wù)需求,所以需要開發(fā)一個(gè)客戶流失預(yù)測(cè)模型。
2.數(shù)據(jù)收集和準(zhǔn)備
數(shù)據(jù)采集
數(shù)據(jù)是任何機(jī)器學(xué)習(xí)項(xiàng)目的核心。沒有數(shù)據(jù),我們就無法訓(xùn)練機(jī)器學(xué)習(xí)模型。
在現(xiàn)實(shí)情況下,干凈的數(shù)據(jù)并不容易獲得。通常,我們需要通過應(yīng)用程序、調(diào)查和許多其他來源收集數(shù)據(jù),然后將其存儲(chǔ)在數(shù)據(jù)存儲(chǔ)中。
在我們的案例中,我們將使用來自 Kaggle 的電信客戶流失數(shù)據(jù)。它是有關(guān)電信行業(yè)客戶歷史的開源分類數(shù)據(jù),帶有流失標(biāo)簽。
https://www.kaggle.com/datasets/blastchar/telco-customer-churn
探索性數(shù)據(jù)分析 (EDA) 和數(shù)據(jù)清理
首先,我們加載數(shù)據(jù)集。
import pandas as pd
df = pd.read_csv('WA_Fn-UseC_-Telco-Customer-Churn.csv')
df.head()
接下來,我們將探索數(shù)據(jù)以了解我們的數(shù)據(jù)集。
以下是我們將為 EDA 流程執(zhí)行的一些操作。
- 檢查特征和匯總統(tǒng)計(jì)數(shù)據(jù)。
- 檢查特征中是否存在缺失值。
- 分析標(biāo)簽的分布(流失)。
- 為數(shù)值特征繪制直方圖,為分類特征繪制條形圖。
- 為數(shù)值特征繪制相關(guān)熱圖。
- 使用箱線圖識(shí)別分布和潛在異常值。
首先,我們將檢查特征和匯總統(tǒng)計(jì)數(shù)據(jù)。
df.info()
圖片
df.describe()
df.describe(exclude = 'number')
圖片
讓我們檢查一下缺失的數(shù)據(jù)。
df.isnull().sum()
可以看到,數(shù)據(jù)集不包含缺失數(shù)據(jù),因此我們不需要執(zhí)行任何缺失數(shù)據(jù)處理活動(dòng)。
然后,我們將檢查目標(biāo)變量以查看是否存在不平衡情況。
df['Churn'].value_counts()
圖片
存在輕微的不平衡,因?yàn)榕c無客戶流失的情況相比,只有接近 25% 的客戶流失發(fā)生。
讓我們?cè)倏纯雌渌卣鞯姆植记闆r,從數(shù)字特征開始。
import numpy as np
df['TotalCharges'] = df['TotalCharges'].replace('', np.nan)
df['TotalCharges'] = pd.to_numeric(df['TotalCharges'], errors='coerce').fillna(0)
df['SeniorCitizen'] = df['SeniorCitizen'].astype('str')
df['ChurnTarget'] = df['Churn'].apply(lambda x: 1 if x=='Yes' else 0)
num_features = df.select_dtypes('number').columns
df[num_features].hist(bins=15, figsize=(15, 6), layout=(2, 5))
圖片
我們還將提供除 customerID 之外的分類特征繪圖。
import matplotlib.pyplot as plt
# Plot distribution of categorical features
cat_features = df.drop('customerID', axis =1).select_dtypes(include='object').columns
plt.figure(figsize=(20, 20))
for i, col in enumerate(cat_features, 1):
plt.subplot(5, 4, i)
df[col].value_counts().plot(kind='bar')
plt.title(col)
圖片
然后我們將通過以下代碼看到數(shù)值特征之間的相關(guān)性。
import seaborn as sns
# Plot correlations between numerical features
plt.figure(figsize=(10, 8))
sns.heatmap(df[num_features].corr())
plt.title('Correlation Heatmap')
圖片
最后,我們將使用基于四分位距(IQR)的箱線圖檢查數(shù)值異常值。
# Plot box plots to identify outliers
plt.figure(figsize=(20, 15))
for i, col in enumerate(num_features, 1):
plt.subplot(4, 4, i)
sns.boxplot(y=df[col])
plt.title(col)
圖片
從上面的分析中,我們可以看出,我們不應(yīng)該解決缺失數(shù)據(jù)或異常值的問題。
下一步是對(duì)我們的機(jī)器學(xué)習(xí)模型進(jìn)行特征選擇,因?yàn)槲覀冎幌胍切┯绊戭A(yù)測(cè)且在業(yè)務(wù)中可行的特征。
特征選擇
特征選擇的方法有很多種,通常結(jié)合業(yè)務(wù)知識(shí)和技術(shù)應(yīng)用來完成。
但是,本教程將僅使用我們之前做過的相關(guān)性分析來進(jìn)行特征選擇。
首先,讓我們根據(jù)相關(guān)性分析選擇數(shù)值特征。
target = 'ChurnTarget'
num_features = df.select_dtypes(include=[np.number]).columns.drop(target)
# Calculate correlations
correlations = df[num_features].corrwith(df[target])
# Set a threshold for feature selection
threshold = 0.3
selected_num_features = correlations[abs(correlations) > threshold].index.tolist()
selected_cat_features=cat_features[:-1]
selected_features = []
selected_features.extend(selected_num_features)
selected_features.extend(selected_cat_features)
selected_features
你可以稍后嘗試調(diào)整閾值,看看特征選擇是否會(huì)影響模型的性能。
3.建立機(jī)器學(xué)習(xí)模型
選擇正確的模型
選擇合適的機(jī)器學(xué)習(xí)模型需要考慮很多因素,但始終取決于業(yè)務(wù)需求。
以下幾點(diǎn)需要記住:
- 用例問題。它是監(jiān)督式的還是無監(jiān)督式的?是分類式的還是回歸式的?用例問題將決定可以使用哪種模型。
- 數(shù)據(jù)特征。它是表格數(shù)據(jù)、文本還是圖像?數(shù)據(jù)集大小是大還是???根據(jù)數(shù)據(jù)集的不同,我們選擇的模型可能會(huì)有所不同。
- 模型的解釋難度如何?平衡可解釋性和性能對(duì)于業(yè)務(wù)至關(guān)重要。
經(jīng)驗(yàn)法則是,在開始復(fù)雜模型之前,最好先以較簡(jiǎn)單的模型作為基準(zhǔn)。
對(duì)于本教程,我們從邏輯回歸開始進(jìn)行模型開發(fā)。
分割數(shù)據(jù)
下一步是將數(shù)據(jù)拆分為訓(xùn)練、測(cè)試和驗(yàn)證集。
from sklearn.model_selection import train_test_split
target = 'ChurnTarget'
X = df[selected_features]
y = df[target]
cat_features = X.select_dtypes(include=['object']).columns.tolist()
num_features = X.select_dtypes(include=['number']).columns.tolist()
#Splitting data into Train, Validation, and Test Set
X_train_val, X_test, y_train_val, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)
X_train, X_val, y_train, y_val = train_test_split(X_train_val, y_train_val, test_size=0.25, random_state=42, stratify=y_train_val)
在上面的代碼中,我們將數(shù)據(jù)分成 60% 的訓(xùn)練數(shù)據(jù)集和 20% 的測(cè)試和驗(yàn)證集。
一旦我們有了數(shù)據(jù)集,我們就可以訓(xùn)練模型。
訓(xùn)練模型
如上所述,我們將使用訓(xùn)練數(shù)據(jù)訓(xùn)練 Logistic 回歸模型。
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder
from sklearn.linear_model import LogisticRegression
preprocessor = ColumnTransformer(
transformers=[
('num', 'passthrough', num_features),
('cat', OneHotEncoder(), cat_features)
])
pipeline = Pipeline(steps=[
('preprocessor', preprocessor),
('classifier', LogisticRegression(max_iter=1000))
])
# Train the logistic regression model
pipeline.fit(X_train, y_train)
模型評(píng)估
以下代碼顯示了所有基本分類指標(biāo)。
from sklearn.metrics import classification_report
# Evaluate on the validation set
y_val_pred = pipeline.predict(X_val)
print("Validation Classification Report:\n", classification_report(y_val, y_val_pred))
# Evaluate on the test set
y_test_pred = pipeline.predict(X_test)
print("Test Classification Report:\n", classification_report(y_test, y_test_pred))
從驗(yàn)證和測(cè)試數(shù)據(jù)中我們可以看出,流失率(1) 的召回率并不是最好的。這就是為什么我們可以優(yōu)化模型以獲得最佳結(jié)果。
圖片
4.模型優(yōu)化
優(yōu)化模型的一種方法是通過超參數(shù)優(yōu)化,它會(huì)測(cè)試這些模型超參數(shù)的所有組合,以根據(jù)指標(biāo)找到最佳組合。
每個(gè)模型都有一組超參數(shù),我們可以在訓(xùn)練之前設(shè)置它們。
from sklearn.model_selection import GridSearchCV
# Define the logistic regression model within a pipeline
pipeline = Pipeline(steps=[
('preprocessor', preprocessor),
('classifier', LogisticRegression(max_iter=1000))
])
# Define the hyperparameters for GridSearchCV
param_grid = {
'classifier__C': [0.1, 1, 10, 100],
'classifier__solver': ['lbfgs', 'liblinear']
}
# Perform Grid Search with cross-validation
grid_search = GridSearchCV(pipeline, param_grid, cv=5, scoring='recall')
grid_search.fit(X_train, y_train)
# Best hyperparameters
print("Best Hyperparameters:", grid_search.best_params_)
# Evaluate on the validation set
y_val_pred = grid_search.predict(X_val)
print("Validation Classification Report:\n", classification_report(y_val, y_val_pred))
# Evaluate on the test set
y_test_pred = grid_search.predict(X_test)
print("Test Classification Report:\n", classification_report(y_test, y_test_pred))
圖片
5.部署模型
我們已經(jīng)構(gòu)建了機(jī)器學(xué)習(xí)模型。有了模型之后,下一步就是將其部署到生產(chǎn)中。讓我們使用一個(gè)簡(jiǎn)單的 API 來模擬它。
首先,讓我們?cè)俅伍_發(fā)我們的模型并將其保存為 joblib 對(duì)象。
import joblib
best_params = {'classifier__C': 10, 'classifier__solver': 'liblinear'}
logreg_model = LogisticRegression(C=best_params['classifier__C'], solver=best_params['classifier__solver'], max_iter=1000)
preprocessor = ColumnTransformer(
transformers=[
('num', 'passthrough', num_features),
('cat', OneHotEncoder(), cat_features)])
pipeline = Pipeline(steps=[
('preprocessor', preprocessor),
('classifier', logreg_model)
])
pipeline.fit(X_train, y_train)
# Save the model
joblib.dump(pipeline, 'logreg_model.joblib')
一旦模型對(duì)象準(zhǔn)備就緒,我們將創(chuàng)建一個(gè)名為 app.py 的 Python 腳本,并將以下代碼放入腳本中。
from fastapi import FastAPI
from pydantic import BaseModel
import joblib
import numpy as np
# Load the logistic regression model pipeline
model = joblib.load('logreg_model.joblib')
# Define the input data for model
class CustomerData(BaseModel):
tenure: int
InternetService: str
OnlineSecurity: str
TechSupport: str
Contract: str
PaymentMethod: str
# Create FastAPI app
app = FastAPI()
# Define prediction endpoint
@app.post("/predict")
def predict(data: CustomerData):
input_data = {
'tenure': [data.tenure],
'InternetService': [data.InternetService],
'OnlineSecurity': [data.OnlineSecurity],
'TechSupport': [data.TechSupport],
'Contract': [data.Contract],
'PaymentMethod': [data.PaymentMethod]
}
import pandas as pd
input_df = pd.DataFrame(input_data)
# Make a prediction
prediction = model.predict(input_df)
# Return the prediction
return {"prediction": int(prediction[0])}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
在命令提示符或終端中,運(yùn)行以下代碼。
uvicorn app:app --reload
有了上面的代碼,我們已經(jīng)有一個(gè)用于接受數(shù)據(jù)和創(chuàng)建預(yù)測(cè)的 API。
讓我們?cè)谛陆K端中使用以下代碼嘗試一下。
curl -X POST "http://127.0.0.1:8000/predict" -H "Content-Type: application/json" -d "{\"tenure\": 72, \"InternetService\": \"Fiber optic\", \"OnlineSecurity\": \"Yes\", \"TechSupport\": \"Yes\", \"Contract\": \"Two year\", \"PaymentMethod\": \"Credit card (automatic)\"}"
如你所見,API 結(jié)果是一個(gè)預(yù)測(cè)值為 0(Not-Churn)的字典。你可以進(jìn)一步調(diào)整代碼以獲得所需的結(jié)果。