关于作者
- 深耕领域:大语言模型开发 / RAG 知识库 / AI Agent 落地 / 模型微调
- 技术栈:Python | RAG (LangChain / Dify + Milvus) | FastAPI + Docker
- 工程能力:专注模型工程化部署、知识库构建与优化,擅长全流程解决方案
「让 AI 交互更智能,让技术落地更高效」
欢迎技术探讨与项目合作,解锁大模型与智能交互的无限可能!
KNN到随机森林:常用机器学习算法的直观理解与实战
引言:为什么需要这么多种算法?
在上一篇文章中,我们介绍了机器学习的基本概念,理解了什么是监督学习、损失函数和梯度下降。现在你可能会想:既然原理都差不多,为什么需要这么多种不同的算法?
想象一下,你要买一辆车。不同的场景下,你需要不同的车:城市通勤可能需要小巧灵活的小轿车;全家出游可能需要空间大的SUV;越野探险则需要底盘高的吉普车。没有哪种车是"最好的",只有最适合特定场景的。
机器学习算法也是一样的道理。每种算法都有它擅长的领域和局限性。KNN简单直观,但对大数据不友好;决策树容易解释,但容易过拟合;随机森林强大稳定,但有时候过于复杂难以解释。
今天,让我们深入理解每种算法的"脾性",学会在不同场景下选择最合适的武器。我会用大量的图解和代码示例,让你不仅"知道"这些算法是什么,更要"理解"它们为什么有效。
零、前置知识:上篇文章核心回顾
0.1 监督学习的核心框架
在深入算法之前,让我们快速回顾一下监督学习的核心框架。如果你对这部分还不太熟悉,建议先阅读《从猜数游戏到模型训练:机器学习核心概念的无痛入门》。
监督学习的本质是:根据输入数据 X X X 和对应的正确答案 y y y,学习一个从 X X X 到 y y y 的映射函数。训练时,模型预测 y ^ \hat{y} y^,计算预测误差 y − y ^ y - \hat{y} y−y^,然后通过优化算法(如梯度下降)调整模型参数,让误差越来越小。
0.2 分类与回归的区别
在开始算法讲解之前,我们需要区分两类最基本的任务:分类和回归。
分类是预测一个离散的类别标签。比如判断邮件是"垃圾"还是"正常"、图片是"猫"还是"狗"。输出是有限的几个类别。
回归是预测一个连续的值。比如预测房价(可以是100万、150万、203.5万)、预测明天的温度(可能是23.5度、24.2度)。
这两种任务虽然目标不同,但背后的学习原理是相通的——都是通过调整模型参数,让预测值尽可能接近真实值。
一、KNN:最直观也最"懒"的算法
1.1 "物以类聚"的数学表达
K近邻(K-Nearest Neighbors,简称KNN)是我见过的最符合直觉的机器学习算法之一。它的核心思想用一句话就能概括:如果你周围的邻居大多属于某个类别,那你也大概率属于这个类别。
这听起来像是一句废话,但正是这种朴素的直觉,构成了一个完整机器学习算法的核心。
让我用一个具体的例子来说明。假设我们在二维平面上有一些点,有红色和蓝色两类。现在有一个新的点(绿色)需要分类。
1.2 距离度量:如何定义"近"?
在KNN中,“近"是由距离决定的。最常用的距离是欧氏距离,也就是我们平常意义上"两点之间的直线距离”。
对于两个点 A ( x 1 , y 1 ) A(x_1, y_1) A(x1,y1) 和 B ( x 2 , y 2 ) B(x_2, y_2) B(x2,y2),欧氏距离是:
d ( A , B ) = ( x 1 − x 2 ) 2 + ( y 1 − y 2 ) 2 d(A, B) = \sqrt{(x_1 - x_2)^2 + (y_1 - y_2)^2} d(A,B)=(x1−x2)2+(y1−y2)2
除了欧氏距离,还有一些其他常用的距离度量:
曼哈顿距离:像在城市街区中开车,只能沿格子状的街道行驶,不能斜穿:
d ( A , B ) = ∣ x 1 − x 2 ∣ + ∣ y 1 − y 2 ∣ d(A, B) = |x_1 - x_2| + |y_1 - y_2| d(A,B)=∣x1−x2∣+∣y1−y2∣
余弦相似度:衡量两个向量方向的相似程度,与绝对距离无关:
cos ( θ ) = A ⋅ B ∣ A ∣ × ∣ B ∣ \cos(\theta) = \frac{A \cdot B}{|A| \times |B|} cos(θ)=∣A∣×∣B∣A⋅B
1.3 K值的选择:邻居数量的影响
KNN中的K代表"选择多少个最近邻居"来投票。K值的选择对结果有很大影响。
K值太小(比如K=1):模型会变得非常敏感,容易受到个别异常点的影响。这就像只听从离你最近的那个人的意见,风险很大。
K值太大:会考虑太多邻居,可能把一些不太相关的点也纳入考虑,导致决策边界变得模糊。
在实际应用中,通常通过交叉验证来选择最优的K值。常见的做法是尝试K=1, 3, 5, 7, 9, …等奇数(奇数是为了避免平票),选择验证集上表现最好的K值。
1.4 KNN的优缺点与适用场景
为什么KNN预测速度慢? 因为它没有显式的"训练"过程。训练时只是把数据存储起来,预测时需要计算待分类点与所有训练点的距离,时间复杂度是O(n)。
特征尺度为什么重要? 假设我们用两个特征预测房价:面积(几十到几百)和房间数(1到5)。面积的范围远大于房间数,如果不做标准化,距离计算会被面积主导。
1.5 KNN实战代码
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import classification_report, confusion_matrix
# 1. 生成模拟数据
# make_classification是sklearn提供的模拟数据生成函数
# n_samples=500: 生成500个样本
# n_features=2: 2个特征(方便可视化)
# n_informative=2: 2个有信息量的特征
# n_redundant=0: 没有冗余特征
# n_classes=2: 2个类别
X, y = make_classification(
n_samples=500,
n_features=2,
n_informative=2,
n_redundant=0,
n_classes=2,
random_state=42
)
# 2. 数据划分:80%训练,20%测试
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 3. 特征标准化(对KNN非常重要!)
# 因为KNN依赖距离计算,如果特征尺度不统一,
# 尺度大的特征会主导距离计算
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train) # 用训练集fit,transform训练集和测试集
X_test_scaled = scaler.transform(X_test)
# 4. 使用交叉验证选择最优K值
k_values = range(1, 31, 2) # 尝试K=1,3,5,...,29
cv_scores = []
for k in k_values:
# n_jobs=-1: 使用所有CPU核心加速
# cv=5: 5折交叉验证
knn = KNeighborsClassifier(n_neighbors=k, n_jobs=-1)
scores = cross_val_score(knn, X_train_scaled, y_train, cv=5, scoring='accuracy')
cv_scores.append(scores.mean())
print(f"K={k:2d}: 平均准确率 = {scores.mean():.4f} (+/- {scores.std()*2:.4f})")
# 找到最优K值
best_k = k_values[np.argmax(cv_scores)]
print(f"\n最优K值: {best_k}")
# 5. 用最优K值训练最终模型
final_knn = KNeighborsClassifier(n_neighbors=best_k)
final_knn.fit(X_train_scaled, y_train)
# 6. 在测试集上评估
y_pred = final_knn.predict(X_test_scaled)
print("\n分类报告:")
print(classification_report(y_test, y_pred))
print("\n混淆矩阵:")
print(confusion_matrix(y_test, y_pred))
# 7. 可视化决策边界
def plot_decision_boundary(model, X, y, title):
"""绘制决策边界的辅助函数"""
h = 0.02 # 网格步长
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
np.arange(y_min, y_max, h))
Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
plt.figure(figsize=(10, 8))
plt.contourf(xx, yy, Z, alpha=0.4)
plt.scatter(X[:, 0], X[:, 1], c=y, alpha=0.8)
plt.title(title)
plt.show()
plot_decision_boundary(final_knn, X_test_scaled, y_test,
f'KNN Decision Boundary (K={best_k})')
二、线性回归与逻辑回归:从猜数字到分类
2.1 线性回归:找到最佳拟合直线
线性回归是最基础也是最重要的回归算法。它的目标很简单:找到一条直线,使得所有数据点到这条直线的"距离之和"最小。
想象你在白纸上撒了一把米粒,现在要用一条直线来概括这些米粒的分布趋势。线性回归就是找到那条"最合适"的直线。
线性回归的数学形式是:
y = w 1 x 1 + w 2 x 2 + . . . + w n x n + b y = w_1 x_1 + w_2 x_2 + ... + w_n x_n + b y=w1x1+w2x2+...+wnxn+b
其中 w 1 , w 2 , . . . , w n w_1, w_2, ..., w_n w1,w2,...,wn 是权重, b b b 是偏置。对于只有一个特征的情况,简化为 y = w x + b y = wx + b y=wx+b。
2.2 梯度下降求解线性回归
在线性回归中,我们通过最小化均方误差(MSE)来找到最优参数:
M S E = 1 n ∑ i = 1 n ( y i − y ^ i ) 2 MSE = \frac{1}{n} \sum_{i=1}^{n}(y_i - \hat{y}_i)^2 MSE=n1i=1∑n(yi−y^i)2
2.3 逻辑回归:分类而非回归
逻辑回归的名字里有"回归",但实际上是一个分类算法。它之所以叫"回归",是因为它最初源于回归模型,后来被扩展用于分类。
逻辑回归的核心思想是:将线性回归的输出压缩到0-1之间,作为属于正类的概率。
这个压缩操作是通过Sigmoid函数完成的:
σ ( z ) = 1 1 + e − z \sigma(z) = \frac{1}{1 + e^{-z}} σ(z)=1+e−z1
为什么需要Sigmoid函数?
因为线性回归的输出是 ( − ∞ , + ∞ ) (-\infty, +\infty) (−∞,+∞) 的任意值,但我们需要的是概率,必须在 [ 0 , 1 ] [0, 1] [0,1] 范围内。Sigmoid函数恰好完成了这个映射: z z z 很大时输出接近1, z z z 很小时输出接近0。
2.4 线性回归与逻辑回归对比
三、决策树:自动生成if-else规则
3.1 用决策树理解世界
决策树是我最喜欢的算法之一,因为它模拟了人类做决策的思维方式。你可以把决策树理解为一连串的if-else规则,只不过这些规则不是人工设计的,而是算法从数据中自动学习出来的。
想象一个医生诊断病人的过程:医生会问"发烧吗?"如果发烧,再问"咳嗽吗?“如果咳嗽,再问"持续多久?”…这个不断提问、不断缩小范围的过程,就是决策树的核心思想。
3.2 信息增益:如何选择最佳分割特征?
决策树的核心问题之一是:每一步应该选择哪个特征来分割?
直觉告诉我们,应该选择那个"最能区分数据"的特征。比如在贷款审批中,"收入"比"最喜欢的颜色"更能区分申请人是否应该获批。
为了量化这个"区分能力",我们引入信息熵的概念。
熵衡量的是一个集合的"混乱程度"。如果集合中所有元素都属于同一类,熵为0(最有序);如果各类均匀分布,熵最大(最混乱)。
H ( S ) = − ∑ i = 1 c p i log 2 ( p i ) H(S) = -\sum_{i=1}^{c} p_i \log_2(p_i) H(S)=−i=1∑cpilog2(pi)
信息增益是分割前的熵减去分割后各子集熵的加权和。信息增益越大,说明这个分割让数据变得更纯净。
I G ( S , A ) = H ( S ) − ∑ v ∈ V a l u e s ( A ) ∣ S v ∣ ∣ S ∣ H ( S v ) IG(S, A) = H(S) - \sum_{v \in Values(A)} \frac{|S_v|}{|S|} H(S_v) IG(S,A)=H(S)−v∈Values(A)∑∣S∣∣Sv∣H(Sv)
3.3 决策树的三大问题与解决方案
问题1:树太深 → 过拟合
如果决策树无限生长,它可能会记住每一个训练样本的细节,包括噪声。这就像一个学生背下了教科书上的每一道题,但不会做新题。
解决方案:剪枝
- 预剪枝:设置树的最大深度、节点最小样本数等,提前停止生长
- 后剪枝:先让树充分生长,再从叶节点向上剪除
问题2:特征多时容易偏向取值多的特征
如果某个特征有100个取值(像身份证号),按它分割会得到很多单样本的节点,看起来信息增益很大,但实际上这是过拟合。
解决方案:使用信息增益率而非信息增益
G a i n R a t i o ( A ) = I G ( S , A ) S p l i t I n f o ( A ) GainRatio(A) = \frac{IG(S, A)}{SplitInfo(A)} GainRatio(A)=SplitInfo(A)IG(S,A)
其中 S p l i t I n f o ( A ) SplitInfo(A) SplitInfo(A) 是分割信息,惩罚取值多的特征。
问题3:连续值特征如何处理?
对于"面积 > 100㎡"这样的连续值条件,需要先离散化。常见做法是尝试所有可能的分割点,选择信息增益最大的。
3.4 决策树实战代码
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
import matplotlib.pyplot as plt
# 1. 加载鸢尾花数据集
# 经典数据集,包含3种鸢尾花的4个特征
iris = load_iris()
X, y = iris.data, iris.target
feature_names = iris.feature_names # ['sepal length (cm)', 'sepal width (cm)', ...]
target_names = iris.target_names # ['setosa', 'versicolor', 'virginica']
# 2. 数据划分
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# 3. 训练决策树(带预剪枝防止过拟合)
# criterion='entropy' 使用信息增益
# max_depth=4 限制树深度
# min_samples_split=5 内部节点最小样本数
# min_samples_leaf=2 叶节点最小样本数
tree_clf = DecisionTreeClassifier(
criterion='entropy', # 或 'gini'(基尼系数)
max_depth=4,
min_samples_split=5,
min_samples_leaf=2,
random_state=42
)
tree_clf.fit(X_train, y_train)
# 4. 评估
y_pred = tree_clf.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(f"测试集准确率: {accuracy:.4f}")
print("\n分类报告:")
print(classification_report(y_test, y_pred, target_names=target_names))
# 5. 可视化决策树
plt.figure(figsize=(20, 10))
plot_tree(
tree_clf,
feature_names=feature_names,
class_names=target_names,
filled=True, # 用颜色填充
rounded=True, # 圆角节点
fontsize=10
)
plt.title('Decision Tree for Iris Classification')
plt.tight_layout()
plt.savefig('decision_tree.png', dpi=150)
plt.show()
# 6. 特征重要性
print("\n特征重要性:")
for name, importance in zip(feature_names, tree_clf.feature_importances_):
print(f" {name}: {importance:.4f}")
四、集成学习:三个臭皮匠赛过诸葛亮
4.1 集成学习的核心思想
"三个臭皮匠赛过诸葛亮"这句话蕴含着深刻的智慧。在机器学习中,集成学习就是将多个模型的预测组合起来,得到一个更好的预测结果。
为什么集成能提升性能?关键在于多样性和减少过拟合。
想象你要决定是否投资一只股票。你会怎么做?你可能会咨询多个分析师,而不是只听一个人的意见。即使每个分析师都可能有偏见或犯错,综合多个意见往往能得到更可靠的判断。
4.2 Bagging vs Boosting:两种集成策略
集成学习主要有两种策略:Bagging和Boosting。
Bagging(Bootstrap Aggregating):并行训练多个模型,每个模型独立决策,最后投票。核心理念是"减少方差"。
Boosting:串行训练多个模型,每个模型专注于纠正前一个模型的错误。核心理念是"减少偏差"。
4.3 随机森林:Bagging的代表作
随机森林是Bagging策略最成功的实现之一。它的"随机"体现在两个地方:
- 行采样:每个决策树只用到一部分样本(有放回抽样)
- 列采样:每个决策树只用到一部分特征
为什么随机森林不容易过拟合?
因为每棵树只看到了一部分数据,而且只用到了一部分特征。这种"盲人摸象"式的学习,使得每棵树都是有偏的,但当很多有偏的树组合起来时,偏差会相互抵消,最终得到一个低方差(稳定)的预测。
4.4 XGBoost:Boosting的代表作
XGBoost(eXtreme Gradient Boosting)是Boosting策略的巅峰之作,在Kaggle等竞赛中几乎统治了表格数据任务长达数年。
XGBoost的核心思想是:每一棵新树都去学习前面所有树的预测误差,逐步减少残差。
XGBoost之所以强大,还因为它做了大量工程优化:
- 正则化:防止过拟合
- 缺失值处理:自动学习最优方向
- 特征并行:高效处理高维数据
- 缓存感知:优化内存访问
4.5 随机森林与XGBoost对比
4.6 集成学习实战代码
from sklearn.datasets import make_classification
from sklearn.model_selection import cross_val_score
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from xgboost import XGBClassifier
from sklearn.metrics import accuracy_score
import numpy as np
# 1. 生成模拟数据
X, y = make_classification(
n_samples=1000,
n_features=20,
n_informative=15,
n_redundant=5,
n_classes=2,
random_state=42
)
# 2. 对比三种算法
models = {
'RandomForest': RandomForestClassifier(n_estimators=100, random_state=42),
'GradientBoosting': GradientBoostingClassifier(n_estimators=100, random_state=42),
'XGBoost': XGBClassifier(n_estimators=100, random_state=42, use_label_encoder=False, eval_metric='logloss')
}
print("=" * 50)
print("集成学习算法对比")
print("=" * 50)
for name, model in models.items():
# 5折交叉验证评估
cv_scores = cross_val_score(model, X, y, cv=5, scoring='accuracy')
print(f"\n{name}:")
print(f" 交叉验证准确率: {cv_scores.mean():.4f} (+/- {cv_scores.std()*2:.4f})")
# 训练并测试
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
test_acc = accuracy_score(y_test, y_pred)
print(f" 测试集准确率: {test_acc:.4f}")
# 3. XGBoost调参示例
print("\n" + "=" * 50)
print("XGBoost超参数调优")
print("=" * 50)
# 常用XGBoost参数
param_grid = {
'n_estimators': [50, 100, 200],
'max_depth': [3, 5, 7],
'learning_rate': [0.01, 0.1, 0.2],
'subsample': [0.8, 1.0]
}
# 简单网格搜索(实际项目中建议用随机搜索或贝叶斯优化)
best_score = 0
best_params = {}
for n_est in param_grid['n_estimators']:
for max_d in param_grid['max_depth']:
for lr in param_grid['learning_rate']:
for ss in param_grid['subsample']:
xgb = XGBClassifier(
n_estimators=n_est,
max_depth=max_d,
learning_rate=lr,
subsample=ss,
random_state=42,
use_label_encoder=False,
eval_metric='logloss'
)
scores = cross_val_score(xgb, X, y, cv=3, scoring='accuracy')
mean_score = scores.mean()
if mean_score > best_score:
best_score = mean_score
best_params = {
'n_estimators': n_est,
'max_depth': max_d,
'learning_rate': lr,
'subsample': ss
}
print(f"\n最优参数: {best_params}")
print(f"最优交叉验证准确率: {best_score:.4f}")
五、实战项目:客户流失预测全流程
5.1 项目背景
让我用一个完整的实战项目来串联所有学到的算法。假设你在一家电信公司工作,老板让你预测哪些客户可能会流失(取消服务)。
这是一个经典的二分类问题:你需要根据客户的历史行为数据,预测他们是否会流失。
5.2 数据探索
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import make_classification
# 模拟电信客户数据(实际项目中用真实数据)
# make_classification生成模拟的二分类数据
X, y = make_classification(
n_samples=1000,
n_features=10,
n_informative=8,
n_redundant=2,
n_classes=2,
weights=[0.7, 0.3], # 70%不流失,30%流失(类别不平衡)
random_state=42
)
# 创建特征名
feature_names = [
'月消费', '在网时长', '投诉次数', '套餐等级',
'使用流量', '国际通话时长', '客服联系次数',
'欠费次数', '转套餐次数', '年龄'
]
df = pd.DataFrame(X, columns=feature_names)
df['流失'] = y
print("=" * 50)
print("数据概览")
print("=" * 50)
print(f"样本数: {len(df)}")
print(f"特征数: {len(feature_names)}")
print(f"\n流失分布:")
print(df['流失'].value_counts())
print(f"\n流失率: {df['流失'].mean()*100:.1f}%")
# 查看基本统计信息
print("\n" + "=" * 50)
print("特征统计")
print("=" * 50)
print(df.describe().round(2))
5.3 数据预处理
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
# 1. 处理类别不平衡
# 由于流失客户只占30%,我们需要处理类别不平衡问题
# 方法1: 过采样少数类(SMOTE)
# 方法2: 欠采样多数类
# 方法3: 调整分类阈值
# 2. 特征标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# 3. 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
X_scaled, y, test_size=0.2, random_state=42, stratify=y
)
print(f"训练集: {len(X_train)} 样本")
print(f"测试集: {len(X_test)} 样本")
print(f"训练集流失率: {y_train.mean()*100:.1f}%")
print(f"测试集流失率: {y_test.mean()*100:.1f}%")
5.4 多模型对比
from sklearn.neighbors import KNeighborsClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from xgboost import XGBClassifier
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score
# 定义模型
models = {
'KNN': KNeighborsClassifier(n_neighbors=5),
'逻辑回归': LogisticRegression(max_iter=1000),
'决策树': DecisionTreeClassifier(max_depth=5, random_state=42),
'随机森林': RandomForestClassifier(n_estimators=100, max_depth=5, random_state=42),
'梯度提升': GradientBoostingClassifier(n_estimators=100, max_depth=5, random_state=42),
'XGBoost': XGBClassifier(n_estimators=100, max_depth=5, random_state=42,
use_label_encoder=False, eval_metric='logloss')
}
# 训练和评估
print("=" * 80)
print("模型对比结果")
print("=" * 80)
print(f"{'模型':<15} {'准确率':<10} {'精确率':<10} {'召回率':<10} {'F1分数':<10} {'AUC':<10}")
print("-" * 80)
results = []
for name, model in models.items():
# 训练
model.fit(X_train, y_train)
# 预测
y_pred = model.predict(X_test)
y_prob = model.predict_proba(X_test)[:, 1] # 流失的概率
# 计算指标
acc = accuracy_score(y_test, y_pred)
prec = precision_score(y_test, y_pred)
rec = recall_score(y_test, y_pred)
f1 = f1_score(y_test, y_pred)
auc = roc_auc_score(y_test, y_prob)
results.append({
'model': name,
'accuracy': acc,
'precision': prec,
'recall': rec,
'f1': f1,
'auc': auc
})
print(f"{name:<15} {acc:.4f} {prec:.4f} {rec:.4f} {f1:.4f} {auc:.4f}")
# 找出最佳模型
best_model = max(results, key=lambda x: x['f1'])
print("\n" + "=" * 80)
print(f"最佳模型: {best_model['model']} (F1={best_model['f1']:.4f})")
5.5 业务解读
# 假设XGBoost是最佳模型,分析特征重要性
best_xgb = models['XGBoost']
feature_importance = pd.DataFrame({
'特征': feature_names,
'重要性': best_xgb.feature_importances_
}).sort_values('重要性', ascending=False)
print("\n" + "=" * 50)
print("特征重要性排名(业务解读)")
print("=" * 50)
for i, row in feature_importance.iterrows():
bar = "█" * int(row['重要性'] * 50)
print(f"{row['特征']:<12}: {row['重要性']:.4f} {bar}")
# 业务建议
print("\n" + "=" * 50)
print("业务建议")
print("=" * 50)
top3 = feature_importance.head(3)['特征'].tolist()
if '投诉次数' in top3:
print("• 重点关注投诉次数多的客户,及时跟进处理")
if '客服联系次数' in top3:
print("• 频繁联系客服的客户可能有问题,需主动关怀")
if '月消费' in top3:
print("• 高消费客户流失风险也高,提供VIP服务挽留")
if '在网时长' in top3:
print("• 新客户流失风险高,加强入网前3个月的服务")
六、总结与展望
六句话核心总结
第一句:KNN是最直观的算法,"近朱者赤近墨者黑"是其核心哲学。但预测速度慢,不适合大数据。
第二句:线性回归找最佳拟合直线,逻辑回归用Sigmoid将输出转为概率。前者用于回归,后者用于分类。
第三句:决策树自动学习if-else规则,信息增益帮助选择最优分割特征。但容易过拟合,需要剪枝。
第四句:集成学习将多个模型组合,“三个臭皮匠赛过诸葛亮”。Bagging减少方差,Boosting减少偏差。
第五句:随机森林是Bagging的代表作,通过数据采样和特征采样让每棵树独立学习。XGBoost是Boosting的巅峰,在表格数据上几乎无敌。
第六句:没有"最好的算法",只有"最适合的算法"。根据数据量、特征类型、任务需求选择合适的模型。
算法选择决策树
下篇预告
在下一篇文章中,我们将进入深度学习的世界,从神经元开始,一步步理解神经网络的工作原理。你将了解到:
- 人工神经元是如何模拟生物神经元的
- 激活函数为什么能带来非线性
- 前向传播和反向传播是如何工作的
- 如何用PyTorch实现第一个神经网络
敬请期待:《从神经元到神经网络:深度学习的本质》
参考资料
- Scikit-learn官方文档:https://scikit-learn.org/stable/
- XGBoost官方文档:https://xgboost.readthedocs.io/
- 机器学习实战:Peter Harrington
- Kaggle入门:https://www.kaggle.com/learn
如果觉得有帮助,欢迎转发给需要的朋友!
KNN到随机森林:常用机器学习算法的直观理解与实战
引言:为什么需要这么多种算法?
在上一篇文章中,我们介绍了机器学习的基本概念,理解了什么是监督学习、损失函数和梯度下降。现在你可能会想:既然原理都差不多,为什么需要这么多种不同的算法?
想象一下,你要买一辆车。不同的场景下,你需要不同的车:城市通勤可能需要小巧灵活的小轿车;全家出游可能需要空间大的SUV;越野探险则需要底盘高的吉普车。没有哪种车是"最好的",只有最适合特定场景的。
机器学习算法也是一样的道理。每种算法都有它擅长的领域和局限性。KNN简单直观,但对大数据不友好;决策树容易解释,但容易过拟合;随机森林强大稳定,但有时候过于复杂难以解释。
今天,让我们深入理解每种算法的"脾性",学会在不同场景下选择最合适的武器。我会用大量的图解和代码示例,让你不仅"知道"这些算法是什么,更要"理解"它们为什么有效。
零、前置知识:上篇文章核心回顾
0.1 监督学习的核心框架
在深入算法之前,让我们快速回顾一下监督学习的核心框架。如果你对这部分还不太熟悉,建议先阅读《从猜数游戏到模型训练:机器学习核心概念的无痛入门》。
监督学习的本质是:根据输入数据 X X X 和对应的正确答案 y y y,学习一个从 X X X 到 y y y 的映射函数。训练时,模型预测 y ^ \hat{y} y^,计算预测误差 y − y ^ y - \hat{y} y−y^,然后通过优化算法(如梯度下降)调整模型参数,让误差越来越小。
0.2 分类与回归的区别
在开始算法讲解之前,我们需要区分两类最基本的任务:分类和回归。
分类是预测一个离散的类别标签。比如判断邮件是"垃圾"还是"正常"、图片是"猫"还是"狗"。输出是有限的几个类别。
回归是预测一个连续的值。比如预测房价(可以是100万、150万、203.5万)、预测明天的温度(可能是23.5度、24.2度)。
这两种任务虽然目标不同,但背后的学习原理是相通的——都是通过调整模型参数,让预测值尽可能接近真实值。
一、KNN:最直观也最"懒"的算法
1.1 "物以类聚"的数学表达
K近邻(K-Nearest Neighbors,简称KNN)是我见过的最符合直觉的机器学习算法之一。它的核心思想用一句话就能概括:如果你周围的邻居大多属于某个类别,那你也大概率属于这个类别。
这听起来像是一句废话,但正是这种朴素的直觉,构成了一个完整机器学习算法的核心。
让我用一个具体的例子来说明。假设我们在二维平面上有一些点,有红色和蓝色两类。现在有一个新的点(绿色)需要分类。
1.2 距离度量:如何定义"近"?
在KNN中,“近"是由距离决定的。最常用的距离是欧氏距离,也就是我们平常意义上"两点之间的直线距离”。
对于两个点 A ( x 1 , y 1 ) A(x_1, y_1) A(x1,y1) 和 B ( x 2 , y 2 ) B(x_2, y_2) B(x2,y2),欧氏距离是:
d ( A , B ) = ( x 1 − x 2 ) 2 + ( y 1 − y 2 ) 2 d(A, B) = \sqrt{(x_1 - x_2)^2 + (y_1 - y_2)^2} d(A,B)=(x1−x2)2+(y1−y2)2
除了欧氏距离,还有一些其他常用的距离度量:
曼哈顿距离:像在城市街区中开车,只能沿格子状的街道行驶,不能斜穿:
d ( A , B ) = ∣ x 1 − x 2 ∣ + ∣ y 1 − y 2 ∣ d(A, B) = |x_1 - x_2| + |y_1 - y_2| d(A,B)=∣x1−x2∣+∣y1−y2∣
余弦相似度:衡量两个向量方向的相似程度,与绝对距离无关:
cos ( θ ) = A ⋅ B ∣ A ∣ × ∣ B ∣ \cos(\theta) = \frac{A \cdot B}{|A| \times |B|} cos(θ)=∣A∣×∣B∣A⋅B
1.3 K值的选择:邻居数量的影响
KNN中的K代表"选择多少个最近邻居"来投票。K值的选择对结果有很大影响。
K值太小(比如K=1):模型会变得非常敏感,容易受到个别异常点的影响。这就像只听从离你最近的那个人的意见,风险很大。
K值太大:会考虑太多邻居,可能把一些不太相关的点也纳入考虑,导致决策边界变得模糊。
在实际应用中,通常通过交叉验证来选择最优的K值。常见的做法是尝试K=1, 3, 5, 7, 9, …等奇数(奇数是为了避免平票),选择验证集上表现最好的K值。
1.4 KNN的优缺点与适用场景
为什么KNN预测速度慢? 因为它没有显式的"训练"过程。训练时只是把数据存储起来,预测时需要计算待分类点与所有训练点的距离,时间复杂度是O(n)。
特征尺度为什么重要? 假设我们用两个特征预测房价:面积(几十到几百)和房间数(1到5)。面积的范围远大于房间数,如果不做标准化,距离计算会被面积主导。
1.5 KNN实战代码
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import classification_report, confusion_matrix
# 1. 生成模拟数据
# make_classification是sklearn提供的模拟数据生成函数
# n_samples=500: 生成500个样本
# n_features=2: 2个特征(方便可视化)
# n_informative=2: 2个有信息量的特征
# n_redundant=0: 没有冗余特征
# n_classes=2: 2个类别
X, y = make_classification(
n_samples=500,
n_features=2,
n_informative=2,
n_redundant=0,
n_classes=2,
random_state=42
)
# 2. 数据划分:80%训练,20%测试
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 3. 特征标准化(对KNN非常重要!)
# 因为KNN依赖距离计算,如果特征尺度不统一,
# 尺度大的特征会主导距离计算
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train) # 用训练集fit,transform训练集和测试集
X_test_scaled = scaler.transform(X_test)
# 4. 使用交叉验证选择最优K值
k_values = range(1, 31, 2) # 尝试K=1,3,5,...,29
cv_scores = []
for k in k_values:
# n_jobs=-1: 使用所有CPU核心加速
# cv=5: 5折交叉验证
knn = KNeighborsClassifier(n_neighbors=k, n_jobs=-1)
scores = cross_val_score(knn, X_train_scaled, y_train, cv=5, scoring='accuracy')
cv_scores.append(scores.mean())
print(f"K={k:2d}: 平均准确率 = {scores.mean():.4f} (+/- {scores.std()*2:.4f})")
# 找到最优K值
best_k = k_values[np.argmax(cv_scores)]
print(f"\n最优K值: {best_k}")
# 5. 用最优K值训练最终模型
final_knn = KNeighborsClassifier(n_neighbors=best_k)
final_knn.fit(X_train_scaled, y_train)
# 6. 在测试集上评估
y_pred = final_knn.predict(X_test_scaled)
print("\n分类报告:")
print(classification_report(y_test, y_pred))
print("\n混淆矩阵:")
print(confusion_matrix(y_test, y_pred))
# 7. 可视化决策边界
def plot_decision_boundary(model, X, y, title):
"""绘制决策边界的辅助函数"""
h = 0.02 # 网格步长
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
np.arange(y_min, y_max, h))
Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
plt.figure(figsize=(10, 8))
plt.contourf(xx, yy, Z, alpha=0.4)
plt.scatter(X[:, 0], X[:, 1], c=y, alpha=0.8)
plt.title(title)
plt.show()
plot_decision_boundary(final_knn, X_test_scaled, y_test,
f'KNN Decision Boundary (K={best_k})')
二、线性回归与逻辑回归:从猜数字到分类
2.1 线性回归:找到最佳拟合直线
线性回归是最基础也是最重要的回归算法。它的目标很简单:找到一条直线,使得所有数据点到这条直线的"距离之和"最小。
想象你在白纸上撒了一把米粒,现在要用一条直线来概括这些米粒的分布趋势。线性回归就是找到那条"最合适"的直线。
线性回归的数学形式是:
y = w 1 x 1 + w 2 x 2 + . . . + w n x n + b y = w_1 x_1 + w_2 x_2 + ... + w_n x_n + b y=w1x1+w2x2+...+wnxn+b
其中 w 1 , w 2 , . . . , w n w_1, w_2, ..., w_n w1,w2,...,wn 是权重, b b b 是偏置。对于只有一个特征的情况,简化为 y = w x + b y = wx + b y=wx+b。
2.2 梯度下降求解线性回归
在线性回归中,我们通过最小化均方误差(MSE)来找到最优参数:
M S E = 1 n ∑ i = 1 n ( y i − y ^ i ) 2 MSE = \frac{1}{n} \sum_{i=1}^{n}(y_i - \hat{y}_i)^2 MSE=n1i=1∑n(yi−y^i)2
2.3 逻辑回归:分类而非回归
逻辑回归的名字里有"回归",但实际上是一个分类算法。它之所以叫"回归",是因为它最初源于回归模型,后来被扩展用于分类。
逻辑回归的核心思想是:将线性回归的输出压缩到0-1之间,作为属于正类的概率。
这个压缩操作是通过Sigmoid函数完成的:
σ ( z ) = 1 1 + e − z \sigma(z) = \frac{1}{1 + e^{-z}} σ(z)=1+e−z1
为什么需要Sigmoid函数?
因为线性回归的输出是 ( − ∞ , + ∞ ) (-\infty, +\infty) (−∞,+∞) 的任意值,但我们需要的是概率,必须在 [ 0 , 1 ] [0, 1] [0,1] 范围内。Sigmoid函数恰好完成了这个映射: z z z 很大时输出接近1, z z z 很小时输出接近0。
2.4 线性回归与逻辑回归对比
三、决策树:自动生成if-else规则
3.1 用决策树理解世界
决策树是我最喜欢的算法之一,因为它模拟了人类做决策的思维方式。你可以把决策树理解为一连串的if-else规则,只不过这些规则不是人工设计的,而是算法从数据中自动学习出来的。
想象一个医生诊断病人的过程:医生会问"发烧吗?"如果发烧,再问"咳嗽吗?“如果咳嗽,再问"持续多久?”…这个不断提问、不断缩小范围的过程,就是决策树的核心思想。
3.2 信息增益:如何选择最佳分割特征?
决策树的核心问题之一是:每一步应该选择哪个特征来分割?
直觉告诉我们,应该选择那个"最能区分数据"的特征。比如在贷款审批中,"收入"比"最喜欢的颜色"更能区分申请人是否应该获批。
为了量化这个"区分能力",我们引入信息熵的概念。
熵衡量的是一个集合的"混乱程度"。如果集合中所有元素都属于同一类,熵为0(最有序);如果各类均匀分布,熵最大(最混乱)。
H ( S ) = − ∑ i = 1 c p i log 2 ( p i ) H(S) = -\sum_{i=1}^{c} p_i \log_2(p_i) H(S)=−i=1∑cpilog2(pi)
信息增益是分割前的熵减去分割后各子集熵的加权和。信息增益越大,说明这个分割让数据变得更纯净。
I G ( S , A ) = H ( S ) − ∑ v ∈ V a l u e s ( A ) ∣ S v ∣ ∣ S ∣ H ( S v ) IG(S, A) = H(S) - \sum_{v \in Values(A)} \frac{|S_v|}{|S|} H(S_v) IG(S,A)=H(S)−v∈Values(A)∑∣S∣∣Sv∣H(Sv)
3.3 决策树的三大问题与解决方案
问题1:树太深 → 过拟合
如果决策树无限生长,它可能会记住每一个训练样本的细节,包括噪声。这就像一个学生背下了教科书上的每一道题,但不会做新题。
解决方案:剪枝
- 预剪枝:设置树的最大深度、节点最小样本数等,提前停止生长
- 后剪枝:先让树充分生长,再从叶节点向上剪除
问题2:特征多时容易偏向取值多的特征
如果某个特征有100个取值(像身份证号),按它分割会得到很多单样本的节点,看起来信息增益很大,但实际上这是过拟合。
解决方案:使用信息增益率而非信息增益
G a i n R a t i o ( A ) = I G ( S , A ) S p l i t I n f o ( A ) GainRatio(A) = \frac{IG(S, A)}{SplitInfo(A)} GainRatio(A)=SplitInfo(A)IG(S,A)
其中 S p l i t I n f o ( A ) SplitInfo(A) SplitInfo(A) 是分割信息,惩罚取值多的特征。
问题3:连续值特征如何处理?
对于"面积 > 100㎡"这样的连续值条件,需要先离散化。常见做法是尝试所有可能的分割点,选择信息增益最大的。
3.4 决策树实战代码
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
import matplotlib.pyplot as plt
# 1. 加载鸢尾花数据集
# 经典数据集,包含3种鸢尾花的4个特征
iris = load_iris()
X, y = iris.data, iris.target
feature_names = iris.feature_names # ['sepal length (cm)', 'sepal width (cm)', ...]
target_names = iris.target_names # ['setosa', 'versicolor', 'virginica']
# 2. 数据划分
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# 3. 训练决策树(带预剪枝防止过拟合)
# criterion='entropy' 使用信息增益
# max_depth=4 限制树深度
# min_samples_split=5 内部节点最小样本数
# min_samples_leaf=2 叶节点最小样本数
tree_clf = DecisionTreeClassifier(
criterion='entropy', # 或 'gini'(基尼系数)
max_depth=4,
min_samples_split=5,
min_samples_leaf=2,
random_state=42
)
tree_clf.fit(X_train, y_train)
# 4. 评估
y_pred = tree_clf.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(f"测试集准确率: {accuracy:.4f}")
print("\n分类报告:")
print(classification_report(y_test, y_pred, target_names=target_names))
# 5. 可视化决策树
plt.figure(figsize=(20, 10))
plot_tree(
tree_clf,
feature_names=feature_names,
class_names=target_names,
filled=True, # 用颜色填充
rounded=True, # 圆角节点
fontsize=10
)
plt.title('Decision Tree for Iris Classification')
plt.tight_layout()
plt.savefig('decision_tree.png', dpi=150)
plt.show()
# 6. 特征重要性
print("\n特征重要性:")
for name, importance in zip(feature_names, tree_clf.feature_importances_):
print(f" {name}: {importance:.4f}")
四、集成学习:三个臭皮匠赛过诸葛亮
4.1 集成学习的核心思想
"三个臭皮匠赛过诸葛亮"这句话蕴含着深刻的智慧。在机器学习中,集成学习就是将多个模型的预测组合起来,得到一个更好的预测结果。
为什么集成能提升性能?关键在于多样性和减少过拟合。
想象你要决定是否投资一只股票。你会怎么做?你可能会咨询多个分析师,而不是只听一个人的意见。即使每个分析师都可能有偏见或犯错,综合多个意见往往能得到更可靠的判断。
4.2 Bagging vs Boosting:两种集成策略
集成学习主要有两种策略:Bagging和Boosting。
Bagging(Bootstrap Aggregating):并行训练多个模型,每个模型独立决策,最后投票。核心理念是"减少方差"。
Boosting:串行训练多个模型,每个模型专注于纠正前一个模型的错误。核心理念是"减少偏差"。
4.3 随机森林:Bagging的代表作
随机森林是Bagging策略最成功的实现之一。它的"随机"体现在两个地方:
- 行采样:每个决策树只用到一部分样本(有放回抽样)
- 列采样:每个决策树只用到一部分特征
为什么随机森林不容易过拟合?
因为每棵树只看到了一部分数据,而且只用到了一部分特征。这种"盲人摸象"式的学习,使得每棵树都是有偏的,但当很多有偏的树组合起来时,偏差会相互抵消,最终得到一个低方差(稳定)的预测。
4.4 XGBoost:Boosting的代表作
XGBoost(eXtreme Gradient Boosting)是Boosting策略的巅峰之作,在Kaggle等竞赛中几乎统治了表格数据任务长达数年。
XGBoost的核心思想是:每一棵新树都去学习前面所有树的预测误差,逐步减少残差。
XGBoost之所以强大,还因为它做了大量工程优化:
- 正则化:防止过拟合
- 缺失值处理:自动学习最优方向
- 特征并行:高效处理高维数据
- 缓存感知:优化内存访问
4.5 随机森林与XGBoost对比
4.6 集成学习实战代码
from sklearn.datasets import make_classification
from sklearn.model_selection import cross_val_score
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from xgboost import XGBClassifier
from sklearn.metrics import accuracy_score
import numpy as np
# 1. 生成模拟数据
X, y = make_classification(
n_samples=1000,
n_features=20,
n_informative=15,
n_redundant=5,
n_classes=2,
random_state=42
)
# 2. 对比三种算法
models = {
'RandomForest': RandomForestClassifier(n_estimators=100, random_state=42),
'GradientBoosting': GradientBoostingClassifier(n_estimators=100, random_state=42),
'XGBoost': XGBClassifier(n_estimators=100, random_state=42, use_label_encoder=False, eval_metric='logloss')
}
print("=" * 50)
print("集成学习算法对比")
print("=" * 50)
for name, model in models.items():
# 5折交叉验证评估
cv_scores = cross_val_score(model, X, y, cv=5, scoring='accuracy')
print(f"\n{name}:")
print(f" 交叉验证准确率: {cv_scores.mean():.4f} (+/- {cv_scores.std()*2:.4f})")
# 训练并测试
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
test_acc = accuracy_score(y_test, y_pred)
print(f" 测试集准确率: {test_acc:.4f}")
# 3. XGBoost调参示例
print("\n" + "=" * 50)
print("XGBoost超参数调优")
print("=" * 50)
# 常用XGBoost参数
param_grid = {
'n_estimators': [50, 100, 200],
'max_depth': [3, 5, 7],
'learning_rate': [0.01, 0.1, 0.2],
'subsample': [0.8, 1.0]
}
# 简单网格搜索(实际项目中建议用随机搜索或贝叶斯优化)
best_score = 0
best_params = {}
for n_est in param_grid['n_estimators']:
for max_d in param_grid['max_depth']:
for lr in param_grid['learning_rate']:
for ss in param_grid['subsample']:
xgb = XGBClassifier(
n_estimators=n_est,
max_depth=max_d,
learning_rate=lr,
subsample=ss,
random_state=42,
use_label_encoder=False,
eval_metric='logloss'
)
scores = cross_val_score(xgb, X, y, cv=3, scoring='accuracy')
mean_score = scores.mean()
if mean_score > best_score:
best_score = mean_score
best_params = {
'n_estimators': n_est,
'max_depth': max_d,
'learning_rate': lr,
'subsample': ss
}
print(f"\n最优参数: {best_params}")
print(f"最优交叉验证准确率: {best_score:.4f}")
五、实战项目:客户流失预测全流程
5.1 项目背景
让我用一个完整的实战项目来串联所有学到的算法。假设你在一家电信公司工作,老板让你预测哪些客户可能会流失(取消服务)。
这是一个经典的二分类问题:你需要根据客户的历史行为数据,预测他们是否会流失。
5.2 数据探索
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import make_classification
# 模拟电信客户数据(实际项目中用真实数据)
# make_classification生成模拟的二分类数据
X, y = make_classification(
n_samples=1000,
n_features=10,
n_informative=8,
n_redundant=2,
n_classes=2,
weights=[0.7, 0.3], # 70%不流失,30%流失(类别不平衡)
random_state=42
)
# 创建特征名
feature_names = [
'月消费', '在网时长', '投诉次数', '套餐等级',
'使用流量', '国际通话时长', '客服联系次数',
'欠费次数', '转套餐次数', '年龄'
]
df = pd.DataFrame(X, columns=feature_names)
df['流失'] = y
print("=" * 50)
print("数据概览")
print("=" * 50)
print(f"样本数: {len(df)}")
print(f"特征数: {len(feature_names)}")
print(f"\n流失分布:")
print(df['流失'].value_counts())
print(f"\n流失率: {df['流失'].mean()*100:.1f}%")
# 查看基本统计信息
print("\n" + "=" * 50)
print("特征统计")
print("=" * 50)
print(df.describe().round(2))
5.3 数据预处理
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
# 1. 处理类别不平衡
# 由于流失客户只占30%,我们需要处理类别不平衡问题
# 方法1: 过采样少数类(SMOTE)
# 方法2: 欠采样多数类
# 方法3: 调整分类阈值
# 2. 特征标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# 3. 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
X_scaled, y, test_size=0.2, random_state=42, stratify=y
)
print(f"训练集: {len(X_train)} 样本")
print(f"测试集: {len(X_test)} 样本")
print(f"训练集流失率: {y_train.mean()*100:.1f}%")
print(f"测试集流失率: {y_test.mean()*100:.1f}%")
5.4 多模型对比
from sklearn.neighbors import KNeighborsClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from xgboost import XGBClassifier
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score
# 定义模型
models = {
'KNN': KNeighborsClassifier(n_neighbors=5),
'逻辑回归': LogisticRegression(max_iter=1000),
'决策树': DecisionTreeClassifier(max_depth=5, random_state=42),
'随机森林': RandomForestClassifier(n_estimators=100, max_depth=5, random_state=42),
'梯度提升': GradientBoostingClassifier(n_estimators=100, max_depth=5, random_state=42),
'XGBoost': XGBClassifier(n_estimators=100, max_depth=5, random_state=42,
use_label_encoder=False, eval_metric='logloss')
}
# 训练和评估
print("=" * 80)
print("模型对比结果")
print("=" * 80)
print(f"{'模型':<15} {'准确率':<10} {'精确率':<10} {'召回率':<10} {'F1分数':<10} {'AUC':<10}")
print("-" * 80)
results = []
for name, model in models.items():
# 训练
model.fit(X_train, y_train)
# 预测
y_pred = model.predict(X_test)
y_prob = model.predict_proba(X_test)[:, 1] # 流失的概率
# 计算指标
acc = accuracy_score(y_test, y_pred)
prec = precision_score(y_test, y_pred)
rec = recall_score(y_test, y_pred)
f1 = f1_score(y_test, y_pred)
auc = roc_auc_score(y_test, y_prob)
results.append({
'model': name,
'accuracy': acc,
'precision': prec,
'recall': rec,
'f1': f1,
'auc': auc
})
print(f"{name:<15} {acc:.4f} {prec:.4f} {rec:.4f} {f1:.4f} {auc:.4f}")
# 找出最佳模型
best_model = max(results, key=lambda x: x['f1'])
print("\n" + "=" * 80)
print(f"最佳模型: {best_model['model']} (F1={best_model['f1']:.4f})")
5.5 业务解读
# 假设XGBoost是最佳模型,分析特征重要性
best_xgb = models['XGBoost']
feature_importance = pd.DataFrame({
'特征': feature_names,
'重要性': best_xgb.feature_importances_
}).sort_values('重要性', ascending=False)
print("\n" + "=" * 50)
print("特征重要性排名(业务解读)")
print("=" * 50)
for i, row in feature_importance.iterrows():
bar = "█" * int(row['重要性'] * 50)
print(f"{row['特征']:<12}: {row['重要性']:.4f} {bar}")
# 业务建议
print("\n" + "=" * 50)
print("业务建议")
print("=" * 50)
top3 = feature_importance.head(3)['特征'].tolist()
if '投诉次数' in top3:
print("• 重点关注投诉次数多的客户,及时跟进处理")
if '客服联系次数' in top3:
print("• 频繁联系客服的客户可能有问题,需主动关怀")
if '月消费' in top3:
print("• 高消费客户流失风险也高,提供VIP服务挽留")
if '在网时长' in top3:
print("• 新客户流失风险高,加强入网前3个月的服务")
六、总结与展望
六句话核心总结
第一句:KNN是最直观的算法,"近朱者赤近墨者黑"是其核心哲学。但预测速度慢,不适合大数据。
第二句:线性回归找最佳拟合直线,逻辑回归用Sigmoid将输出转为概率。前者用于回归,后者用于分类。
第三句:决策树自动学习if-else规则,信息增益帮助选择最优分割特征。但容易过拟合,需要剪枝。
第四句:集成学习将多个模型组合,“三个臭皮匠赛过诸葛亮”。Bagging减少方差,Boosting减少偏差。
第五句:随机森林是Bagging的代表作,通过数据采样和特征采样让每棵树独立学习。XGBoost是Boosting的巅峰,在表格数据上几乎无敌。
第六句:没有"最好的算法",只有"最适合的算法"。根据数据量、特征类型、任务需求选择合适的模型。
算法选择决策树
下篇预告
在下一篇文章中,我们将进入深度学习的世界,从神经元开始,一步步理解神经网络的工作原理。你将了解到:
- 人工神经元是如何模拟生物神经元的
- 激活函数为什么能带来非线性
- 前向传播和反向传播是如何工作的
- 如何用PyTorch实现第一个神经网络
敬请期待:《从神经元到神经网络:深度学习的本质》
参考资料
- Scikit-learn官方文档:https://scikit-learn.org/stable/
- XGBoost官方文档:https://xgboost.readthedocs.io/
- 机器学习实战:Peter Harrington
- Kaggle入门:https://www.kaggle.com/learn
如果觉得有帮助,欢迎转发给需要的朋友!

6万+

被折叠的 条评论
为什么被折叠?



