Python数据挖掘之基础分类模型_K最近邻分类器(KNN)_决策树
文章目录
- K最近邻分类器(KNN)
- KNN改进
- KNeighborsClassifier
- 决策树
- 基本概念
- 构建过程
- 典型算法
- 应用场景
- 优缺点
- 简单例子
- 方法
- 信息增益
- 案例
K最近邻分类器(KNN)
K最近邻是一种很简单却又很有效的分类模型。
已知训练集D={(x,y)}和待分类的一条测试数据对象x′。KNN模型从训练集发现和x′最相似的k个数据对象作为其近邻。根据这k个近邻对象的类别标签进行决策,按照少数服从多数的原则将x′分配近邻中数量最多的类。
KNN模型有两个关键因素:数据的距离或相似性度量方法和参数k的选取。
对于数值型的数据我们常采用欧式距离,或者余弦相似度。
对于参数k的选择,过大的k值或过小的k值都是不可取的,通常需要通过实验比选的方法选择最优的k值。
KNN改进
KNeighborsClassifier
分类 | 名称 | 默认值 | 说明 |
---|---|---|---|
主要参数 | n_neighbors | 5 | 邻居个数 kkk,控制投票时参考的近邻数。 |
weights | 'uniform' | 权重策略:'uniform' 表示所有邻居权重相等;'distance' 表示距离近的邻居权重大;也可传入自定义函数。 | |
algorithm | 'auto' | 搜索近邻的算法:'auto' 自动选择,'ball_tree' 、'kd_tree' 、'brute' 分别对应不同的实现。 | |
metric | 'minkowski' | 距离度量方法,默认 Minkowski 距离;当 p=2 时等价于欧氏距离。 | |
p | 2 | Minkowski 距离中的参数 ppp,常用 p=1p=1p=1 (曼哈顿距离),p=2p=2p=2 (欧几里得距离)。 | |
属性 | classes_ | - | 训练后存储所有类别标签。 |
n_features_in_ | - | 输入特征的个数。 | |
n_samples_fit_ | - | 训练样本数。 | |
主要方法 | fit(X, y) | - | 使用训练数据 X 和标签 y 拟合模型。 |
predict(X) | - | 对测试数据 X 进行预测。 | |
predict_proba(X) | - | 返回每个测试样本属于各类别的概率分布。 | |
kneighbors(X, n_neighbors, return_distance) | - | 返回 X 的最近邻信息,可选是否返回距离。 | |
score(X, y) | - | 返回预测的准确率。 |
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier# 1. 加载数据
iris = load_iris()
X, y = iris.data, iris.target# 2. 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)# 3. 定义KNN分类器
knn = KNeighborsClassifier(n_neighbors=3, weights='distance', metric='minkowski', p=2)# 4. 训练模型
knn.fit(X_train, y_train)# 5. 预测
y_pred = knn.predict(X_test)# 6. 评估
print("预测结果:", y_pred)
print("模型准确率:", knn.score(X_test, y_test))
import numpy as np
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import Normalizer
from sklearn.pipeline import Pipeline
from sklearn.neighbors import KNeighborsClassifier# 1) 自定义权重:把“余弦距离” -> “相似度权重”
# 余弦距离 d = 1 - cos_sim,因此相似度 = 1 - d = cos_sim
def cosine_weights(distances: np.ndarray) -> np.ndarray:# 将负的数值(数值误差)截断为0sims = 1.0 - distancessims = np.maximum(sims, 0.0)# 可选:为避免全部为0的行(极端情况),加一个很小的epsilonreturn sims + 1e-12# 2) 数据
X, y = load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y
)# 3) Pipeline:先做L2归一化,再用KNN(metric='cosine' + 自定义weights)
knn_cosine = Pipeline([("norm", Normalizer(norm="l2")),("knn", KNeighborsClassifier(n_neighbors=5,algorithm="brute", # 余弦度量需要 brute(蛮力)metric="cosine", # 余弦“距离” = 1 - 余弦相似度weights=cosine_weights # 用相似度作为权重))
])# 4) 训练与评估
knn_cosine.fit(X_train, y_train)
acc = knn_cosine.score(X_test, y_test)
y_pred = knn_cosine.predict(X_test)print("测试集准确率:", acc)
print("前10个预测:", y_pred[:10])为什么要 Normalizer? 余弦相似度只关心方向,常配合 L2 归一化以弱化量纲和尺度差异。为何 algorithm='brute'? 余弦度量不适配 KDTree/BallTree,使用暴力搜索更稳妥。权重函数是否需要归一化? 不必。KNeighborsClassifier 会按你提供的权重直接做加权投票,归一化与否不影响决策边界(但在概率估计上会有缩放差异)。上例只是加了极小量防止边界极端情况。与 'distance' 的区别? 'distance' 是用 1/距离 加权;这里我们直接用 余弦相似度(越相似权重越大),更契合你的“相似度加权投票”设定。
决策树
基本概念
决策树是一种树形结构的分类与回归模型。它的结构由以下部分组成:
根节点(Root Node):表示决策的起点,一般对应整个训练集。
内部节点(Internal Node):对应某个特征(属性),根据该特征的不同取值分裂数据。
分支(Branch):表示一个特征的某个取值。
叶子节点(Leaf Node):表示分类结果(类别)或预测值(回归任务)。
决策过程相当于从根节点出发,根据特征值沿着分支逐步走向叶子节点,最终得到预测结果。
构建过程
决策树的构建本质上是一个递归划分数据集的过程,主要步骤如下:
特征选择
在当前数据集下,选择一个最优特征作为节点的划分依据。
常用的选择标准包括:信息增益、信息增益率、基尼指数等。
节点划分
根据所选特征的取值,将数据划分成若干子集,并生成对应的子节点。
递归构建
对每个子节点重复上述过程,直到满足停止条件(如节点数据属于同一类,或没有特征可分)。
剪枝处理(Pruning)
为防止过拟合,可以对决策树进行简化。
预剪枝:在构建过程中提前停止分裂,如节点样本数过少时不再分裂。
后剪枝:先生成完整树,再删除对分类贡献不大的分支。
典型算法
ID3算法(J. Ross Quinlan 提出,1986年)
特征选择标准:信息增益(Entropy 减少量)。
缺点:倾向于选择取值较多的特征,容易过拟合。
C4.5算法(对ID3的改进)
特征选择标准:信息增益率(信息增益除以特征熵)。
引入处理连续属性的方法和剪枝机制。
支持缺失值和多类问题。
CART算法(Classification And Regression Tree)
可用于分类和回归。
分类时使用 基尼指数(Gini Index) 作为特征选择标准;
回归时使用 平方误差最小化 作为划分标准。
CART 决策树是二叉树,每个节点最多分裂成两个子节点。
应用场景
分类任务:如垃圾邮件识别、信用评级、疾病诊断。
回归任务:如房价预测、销量预测。
特征选择:决策树能够自动挑选信息量大的特征,常作为特征工程工具。
集成学习基础模型:随机森林、梯度提升树(GBDT)、XGBoost 等都基于决策树。
优缺点
优点
易于理解和可视化,模型结果解释性强。
能处理数值型和分类型特征。
可用于分类和回归。
训练速度较快,不需要大量参数调整。
缺点
容易过拟合(需剪枝或结合集成方法)。
对噪声和异常值敏感。
不稳定性:数据的微小变化可能导致树结构完全不同。
简单例子
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier, plot_tree
import matplotlib.pyplot as plt# 1. 加载数据
iris = load_iris()
X, y = iris.data, iris.target# 2. 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)# 3. 训练决策树(使用CART算法)
clf = DecisionTreeClassifier(criterion='gini', max_depth=3, random_state=42)
clf.fit(X_train, y_train)# 4. 模型评估
print("模型准确率:", clf.score(X_test, y_test))# 5. 可视化决策树
plt.figure(figsize=(12, 8))
plot_tree(clf, filled=True, feature_names=iris.feature_names, class_names=iris.target_names)
plt.show()
方法
两种常见构建方式:
A) ID3 / C4.5(多路划分,信息增益/增益率)
B) CART(Gini/平方误差,二叉划分)
预测、连续/类别特征处理、常见停止条件与(可选)剪枝的伪代码。
# 构建决策树
FUNCTION BUILD_TREE_ID3_C45(D, FEATURES, criterion, max_depth, min_leaf):# D: 当前训练数据集# FEATURES: 可选的特征集合# criterion: "entropy" (信息增益, ID3) 或 "gain_ratio" (信息增益率, C4.5)# max_depth: 最大深度限制# min_leaf: 叶子节点最少样本数IF ALL_SAME_CLASS(D) OR max_depth == 0 OR FEATURES EMPTY:# 如果数据全属一个类别,或达到最大深度,或没有可分特征RETURN LEAF( MAJORITY_CLASS(D) ) # 返回叶子,类别取多数类best_f, best_split = SELECT_FEATURE_ID3_C45(D, FEATURES, criterion, min_leaf)IF best_f IS NONE:# 如果找不到合适特征(信息增益很小)RETURN LEAF( MAJORITY_CLASS(D))node = NODE(feature=best_f) # 创建节点FOR EACH key IN best_split.keys: # key 表示某个分支(特征取值或阈值)Dk = SUBSET(D, best_f, key) # 按该条件划分子数据集IF SIZE(Dk) < min_leaf:# 样本过少时,直接生成叶子,避免过拟合node.children[key] = LEAF( MAJORITY_CLASS(D) )ELSE:# 递归构建子树node.children[key] = BUILD_TREE_ID3_C45(Dk, FEATURES \ {best_f}, criterion, max_depth-1, min_leaf)RETURN node# 特征选择
FUNCTION SELECT_FEATURE_ID3_C45(D, FEATURES, criterion, min_leaf):best_score = -INF; best_f = NONE; best_split = NONEFOR EACH f IN FEATURES:splits = CANDIDATE_SPLITS(D, f) # 连续特征:阈值二分;离散特征:所有取值score = GAIN_OR_RATIO(D, splits, criterion)IF score > best_score:best_score = score; best_f = f; best_split = splitsIF best_score <= 0: RETURN (NONE, NONE) # 无有效提升RETURN (best_f, best_split)# 信息度量:信息增益 or 信息增益率
FUNCTION GAIN_OR_RATIO(D, splits, criterion):IG = ENTROPY(D) - Σ_k (|Dk|/|D|) * ENTROPY(Dk)IF criterion == "entropy": RETURN IGSplitInfo = - Σ_k (|Dk|/|D|) * LOG2(|Dk|/|D|)IF SplitInfo == 0: RETURN 0RETURN IG / SplitInfo # C4.5 用增益率FUNCTION ENTROPY(D):p = CLASS_FREQ(D)/|D|RETURN - Σ_c p_c * LOG2(p_c)# 预测:从根走到叶子
FUNCTION PREDICT_ID3_C45(tree, x):node = treeWHILE node IS NOT LEAF:f = node.featurekey = ROUTE_KEY(f, x[f], node) # 连续:比较阈值;离散:直接匹配IF key NOT IN node.children: # 如果遇到没见过的值RETURN MAJORITY_CLASS_AT(node)node = node.children[key]RETURN node.label # 返回叶子类别
# 构建 CART 树
FUNCTION BUILD_TREE_CART(D, FEATURES, task, max_depth, min_leaf, min_imp_dec):# task: "classification" 或 "regression"IF STOP(D, task, max_depth, min_leaf):RETURN LEAF( VALUE(D, task) ) # 分类: 多数类;回归: 平均值best = FIND_BEST_SPLIT_CART(D, FEATURES, task, min_leaf)IF best IS NONE OR best.imp_dec < min_imp_dec:# 如果没有合适分裂,或提升不足RETURN LEAF( VALUE(D, task) )node = NODE(feature=best.f, threshold=best.t) # 创建节点,二叉分裂node.left = BUILD_TREE_CART(best.left, FEATURES, task, max_depth-1, min_leaf, min_imp_dec)node.right = BUILD_TREE_CART(best.right, FEATURES, task, max_depth-1, min_leaf, min_imp_dec)RETURN node# 寻找最佳划分
FUNCTION FIND_BEST_SPLIT_CART(D, FEATURES, task, min_leaf):base = IMPURITY(D, task) # 当前不纯度best = NONEFOR EACH f IN FEATURES:FOR EACH t IN CANDIDATE_THRESHOLDS(D, f): # 连续特征:候选阈值L = { (x,y) | x[f] ≤ t }; R = { (x,y) | x[f] > t }IF SIZE(L) < min_leaf OR SIZE(R) < min_leaf: CONTINUEdec = base - [ (|L|/|D|)*IMPURITY(L,task) + (|R|/|D|)*IMPURITY(R,task) ]IF best IS NONE OR dec > best.imp_dec:best = (f=f, t=t, left=L, right=R, imp_dec=dec)RETURN best# 不纯度计算
FUNCTION IMPURITY(D, task):IF task == "classification": RETURN GINI(D)ELSE: RETURN MSE(D)FUNCTION GINI(D):p = CLASS_FREQ(D)/|D|RETURN 1 - Σ_c p_c^2 # Gini指数FUNCTION MSE(D):ȳ = MEAN(y in D)RETURN MEAN( (y - ȳ)^2 ) # 回归时的方差# 叶子节点取值
FUNCTION VALUE(D, task):IF task == "classification": RETURN MAJORITY_CLASS(D)ELSE: RETURN MEAN(y in D)# 停止条件
FUNCTION STOP(D, task, max_depth, min_leaf):IF max_depth == 0: RETURN TRUEIF SIZE(D) ≤ min_leaf: RETURN TRUEIF task == "classification" AND ALL_SAME_CLASS(D): RETURN TRUEIF task == "regression" AND VAR(y in D) == 0: RETURN TRUERETURN FALSE# 预测
FUNCTION PREDICT_CART(tree, x):node = treeWHILE node IS NOT LEAF:IF x[node.feature] ≤ node.threshold: node = node.leftELSE: node = node.rightRETURN node.value
开始│▼
检查数据集 D│├─ 所有样本属于同一类别? ──► 是 ──► 生成叶子节点(该类别)│ ├─ 达到最大深度? ──► 是 ──► 生成叶子节点(多数类/均值)│├─ 特征集合为空? ──► 是 ──► 生成叶子节点(多数类/均值)│▼ 否
选择最优特征│▼
按照该特征划分数据集│├─ 子集为空? ──► 是 ──► 生成叶子节点(父节点多数类/均值)│▼ 否
为每个子集递归调用【构建树】│▼
合并得到当前节点│▼
结束(返回根节点)
信息增益
![在这
信息度量│├── 信息熵 (Entropy)│ ↓│ 衡量样本集合的混乱度│ - 纯净度高 → 熵低│ - 类别均匀 → 熵高│├── 信息增益 (Information Gain)│ ↓│ 划分前后熵的减少量│ = 原始熵 - 划分后加权熵│ → 用于 ID3 特征选择│└── 信息增益率 (Gain Ratio)↓= 信息增益 / 分裂信息解决信息增益偏好取值多的缺陷→ 用于 C4.5 特征选择
案例
# 安装依赖(如本地未安装)
# pip install scikit-learn matplotlibfrom sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.tree import DecisionTreeClassifier, plot_tree, export_text
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
import matplotlib.pyplot as plt# 1) 加载数据
iris = load_iris()
X, y = iris.data, iris.target
feature_names = iris.feature_names
class_names = iris.target_names# 2) 划分训练/测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y
)# 3) 定义并训练决策树(CART:criterion='gini';也可用 'entropy')
clf = DecisionTreeClassifier(criterion='gini', # 分类不纯度:'gini' 或 'entropy'max_depth=3, # 预剪枝:限制树深度,防止过拟合min_samples_leaf=2, # 叶子最少样本数random_state=42
)
clf.fit(X_train, y_train)# 4) 评估:在测试集上
y_pred = clf.predict(X_test)
print("Test Accuracy:", accuracy_score(y_test, y_pred))
print("\nClassification Report:\n", classification_report(y_test, y_pred, target_names=class_names))
print("Confusion Matrix:\n", confusion_matrix(y_test, y_pred))# 5) 交叉验证(可选)
cv_scores = cross_val_score(clf, X, y, cv=5)
print("\n5-Fold CV Accuracy: mean=%.3f, std=%.3f" % (cv_scores.mean(), cv_scores.std()))# 6) 特征重要性
print("\nFeature Importances:")
for f, imp in zip(feature_names, clf.feature_importances_):print(f"{f}: {imp:.3f}")# 7) 文本形式查看树结构
print("\nText Tree:\n")
print(export_text(clf, feature_names=feature_names))# 8) 可视化决策树(需要图形环境)
plt.figure(figsize=(10, 6))
plot_tree(clf,feature_names=feature_names,class_names=class_names,filled=True,rounded=True
)
plt.title("Decision Tree (Iris)")
plt.tight_layout()
plt.show()想要更强的拟合:提高 max_depth、减小 min_samples_leaf;想要更稳健:反之。换成信息熵:把 criterion='gini' 改为 criterion='entropy'。有类别型特征时,通常先做 OneHotEncoder,再喂给 DecisionTreeClassifier(可用 ColumnTransformer + Pipeline 组合)。