尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

从零实现机器学习经典算法:线性回归、K-Means与决策树

从零实现机器学习经典算法:线性回归、K-Means与决策树 在实际机器学习项目中很多开发者会遇到一个典型困境虽然能调用sklearn的fit和predict完成模型训练但面对线性回归的损失函数、决策树的信息增益、K-Means的迭代过程等核心原理时却感到模糊不清。这种“会用但不懂”的状态在模型调优、排查异常结果或进行算法选型时会带来巨大障碍。真正的精通意味着你能从数学直觉和代码实现两个层面理解算法如何工作、为何有效、以及何时会失效。本文将以 Python 为实践语言带你从零开始不依赖高级封装亲手实现线性回归、K-Means聚类、决策树这几个最具代表性的经典算法。我们将聚焦于算法最核心的机制通过代码揭示其内部运作并讨论每个算法在实际应用中的关键参数、常见陷阱和性能边界。无论你是希望夯实基础的机器学习初学者还是想深入理解模型黑盒的工程实践者这篇内容都将提供一条从“入门”到“精通”的清晰路径。1. 理解机器学习算法的核心从“调用”到“实现”在直接动手写代码之前我们需要建立一个正确的认知框架机器学习算法不是魔法黑盒而是一系列基于数学优化和统计学习的可执行步骤。从“调用API”到“亲手实现”是理解这一点的关键跨越。1.1 算法学习的三个层次通常我们对一个算法的掌握可以分为三个层次应用层知道在sklearn中导入哪个类如何调用fit和predict并调整几个常见参数如max_depth对于决策树。这是项目快速上手的必备技能。原理层理解算法背后的目标函数如线性回归的最小化均方误差、优化方法如梯度下降以及核心概念如信息增益、轮廓系数。这决定了你能否进行有效的模型选择和调参。实现层能够用基础编程语言如 Python NumPy将算法的数学步骤转化为实际运行的代码。这是检验你是否真正“吃透”一个算法的终极标准它能让你深刻理解算法的计算复杂度、对数据的假设以及可能失败的边界条件。本文的目标是带领你达到第三层。我们将暂时抛开sklearn的高级封装从最原始的数学公式和编程逻辑出发。1.2 环境与工具准备最小化依赖为了聚焦于算法本身我们只需要最基础的科学计算环境。请确保你的 Python 环境已安装以下库# 使用 pip 进行安装 pip install numpy matplotlib scikit-learn各库的作用如下NumPy提供高效的数组操作和线性代数计算是我们实现算法的数学基础。Matplotlib用于数据可视化和结果展示帮助直观理解算法行为。scikit-learn我们暂时不会用它来调用算法但会用它来生成模拟数据、分割数据集并在最后与我们手写的算法结果进行对比验证这是一个极好的 sanity check。你可以通过以下代码片段快速验证环境import numpy as np import matplotlib.pyplot as plt from sklearn import datasets print(fNumPy version: {np.__version__}) # 生成一个简单的数据集用于后续测试 X, y datasets.make_regression(n_samples100, n_features1, noise10, random_state42) plt.scatter(X, y) plt.title(Sample Data for Verification) plt.show()如果上述代码能成功运行并显示散点图说明你的环境已就绪。2. 线性回归从最小二乘法到梯度下降线性回归是理解机器学习优化思想的绝佳起点。它的目标是找到一条直线或超平面使得所有数据点到该直线的垂直距离误差的平方和最小。2.1 数学模型与损失函数对于简单线性回归y w * x b其中w是权重斜率b是偏置截距。给定一组数据(x_i, y_i)我们定义损失函数为均方误差MSELoss(w, b) (1/n) * Σ(y_i - (w*x_i b))^2我们的任务就是找到使Loss(w, b)最小的w和b。方法一解析解最小二乘法对于线性回归损失函数是凸函数可以直接通过求导数为零的点得到全局最优解。其矩阵形式的解为θ (X^T * X)^(-1) * X^T * y其中X是增加了全为1的列对应偏置b的特征矩阵θ是包含[w, b]的参数向量。import numpy as np class LinearRegressionOLS: 使用最小二乘法解析解实现线性回归 def __init__(self): self.weights None # 存储训练得到的参数θ def fit(self, X, y): # 为X添加一列全1用于计算偏置项b X_b np.c_[np.ones((X.shape[0], 1)), X] # 应用解析解公式使用np.linalg.pinv求伪逆以增强数值稳定性 self.weights np.linalg.pinv(X_b.T.dot(X_b)).dot(X_b.T).dot(y) return self def predict(self, X): X_b np.c_[np.ones((X.shape[0], 1)), X] return X_b.dot(self.weights)关键解释np.c_用于按列连接数组这里为特征矩阵添加了一列偏置项。np.linalg.pinv计算矩阵的 Moore-Penrose 伪逆。即使X^T * X不可逆如特征共线时pinv也能给出一个合理的解比inv更稳健。这种方法在小数据集上非常快速准确但当特征维度很高10^4或样本量极大时计算逆矩阵会非常昂贵甚至不可行。方法二数值解梯度下降梯度下降是一种迭代优化算法通过不断沿损失函数梯度反方向更新参数逐步逼近最小值。参数更新规则为θ θ - learning_rate * ∇Loss(θ)对于 MSE 损失梯度∇Loss(θ)有解析形式。class LinearRegressionGD: 使用批量梯度下降实现线性回归 def __init__(self, learning_rate0.01, n_iters1000): self.lr learning_rate self.n_iters n_iters self.weights None self.loss_history [] # 记录每次迭代的损失值用于监控 def fit(self, X, y): n_samples, n_features X.shape # 初始化参数权重w随机偏置b初始为0 self.weights np.random.randn(n_features 1) X_b np.c_[np.ones((n_samples, 1)), X] y y.reshape(-1, 1) # 梯度下降迭代 for i in range(self.n_iters): # 计算预测值 y_pred X_b.dot(self.weights).reshape(-1, 1) # 计算误差 error y_pred - y # 计算梯度 (1/n) * X_b^T * error gradients (2 / n_samples) * X_b.T.dot(error).flatten() # 更新参数 self.weights - self.lr * gradients # 记录当前损失 loss np.mean(error ** 2) self.loss_history.append(loss) return self def predict(self, X): X_b np.c_[np.ones((X.shape[0], 1)), X] return X_b.dot(self.weights)2.2 实现验证与关键参数讨论现在我们用模拟数据来验证我们手写的两个线性回归实现。# 生成数据 from sklearn.model_selection import train_test_split X, y datasets.make_regression(n_samples200, n_features1, noise15, random_state42) X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42) # 使用解析解模型 model_ols LinearRegressionOLS() model_ols.fit(X_train, y_train) y_pred_ols model_ols.predict(X_test) mse_ols np.mean((y_test - y_pred_ols) ** 2) print(fOLS Model - Weights: {model_ols.weights}, Test MSE: {mse_ols:.2f}) # 使用梯度下降模型 model_gd LinearRegressionGD(learning_rate0.1, n_iters500) model_gd.fit(X_train, y_train) y_pred_gd model_gd.predict(X_test) mse_gd np.mean((y_test - y_pred_gd) ** 2) print(fGD Model - Weights: {model_gd.weights}, Test MSE: {mse_gd:.2f}) # 绘制损失下降曲线 plt.plot(range(len(model_gd.loss_history)), model_gd.loss_history) plt.xlabel(Iteration) plt.ylabel(Loss (MSE)) plt.title(Gradient Descent Loss Convergence) plt.show()关键参数与常见坑学习率 (learning_rate)这是梯度下降最重要的超参数。值太大损失函数会震荡甚至发散损失值爆炸式增长。值太小收敛速度极慢可能需要非常多的迭代次数。调试建议通常从 0.01、0.1、1 等数量级开始尝试并务必绘制损失曲线观察。如果曲线震荡调小学习率如果曲线下降太慢可适当调大。迭代次数 (n_iters)需要足够多次迭代以确保收敛。可以通过设置一个很小的阈值如损失变化小于1e-6作为早停条件而不是固定迭代次数。特征缩放上述代码未进行特征缩放。如果特征量纲差异巨大如x1范围是 0-1x2范围是 10000-100000梯度下降的收敛路径会变得非常曲折甚至难以收敛。最佳实践是在使用梯度下降前对特征进行标准化零均值、单位方差处理。初始化我们使用了随机初始化。对于线性回归由于损失函数是凸的初始化点不影响最终结果但会影响到达最优点的迭代步数。3. K-Means 聚类无监督学习中的迭代优化聚类是一种典型的无监督学习目标是将数据点分组使得同一组簇内的点彼此相似不同组间的点不相似。K-Means 以其简单高效成为最常用的聚类算法之一。3.1 算法流程与核心思想K-Means 的目标是最小化每个点到其所属簇中心的距离平方和。算法通过交替执行以下两个步骤直至收敛分配步骤将每个数据点分配到距离其最近的簇中心。更新步骤重新计算每个簇中所有点的均值作为新的簇中心。其数学目标是最小化J Σ Σ ||x - μ_k||^2其中内层求和针对属于簇k的所有点x。class KMeansManual: 手动实现 K-Means 聚类算法 def __init__(self, n_clusters3, max_iters300, tol1e-4): self.n_clusters n_clusters self.max_iters max_iters self.tol tol # 容忍度中心点移动小于此值则认为收敛 self.centroids None self.labels None self.inertia_ None # 保存最终的簇内误差平方和 def _init_centroids(self, X): 随机初始化簇中心从数据点中随机选择K个 n_samples X.shape[0] random_indices np.random.choice(n_samples, self.n_clusters, replaceFalse) centroids X[random_indices] return centroids def _compute_distance(self, X, centroids): 计算每个点到每个簇中心的距离 (欧氏距离平方) n_samples X.shape[0] n_clusters centroids.shape[0] distances np.zeros((n_samples, n_clusters)) for k in range(n_clusters): # 利用广播机制计算差值的平方和 distances[:, k] np.sum((X - centroids[k]) ** 2, axis1) return distances def fit(self, X): n_samples, n_features X.shape # 1. 初始化簇中心 self.centroids self._init_centroids(X) for i in range(self.max_iters): # 2. 分配步骤计算距离并分配标签 distances self._compute_distance(X, self.centroids) self.labels np.argmin(distances, axis1) # 3. 更新步骤计算新的簇中心 new_centroids np.zeros((self.n_clusters, n_features)) for k in range(self.n_clusters): # 找出属于当前簇k的所有点 cluster_points X[self.labels k] if len(cluster_points) 0: new_centroids[k] cluster_points.mean(axis0) else: # 如果某个簇没有点则重新随机初始化该中心 new_centroids[k] X[np.random.randint(0, n_samples)] # 4. 检查收敛中心点移动是否小于容忍度 centroid_shift np.sqrt(np.sum((new_centroids - self.centroids) ** 2, axis1)).sum() if centroid_shift self.tol: print(fConverged at iteration {i}) break self.centroids new_centroids # 计算最终的簇内误差平方和 distances self._compute_distance(X, self.centroids) self.inertia_ np.sum(distances[np.arange(n_samples), self.labels]) return self def predict(self, X): 对新数据点预测所属簇 distances self._compute_distance(X, self.centroids) return np.argmin(distances, axis1)3.2 算法验证与 K 值选择困境我们使用经典的鸢尾花Iris数据集进行演示但只使用其中两个特征以便可视化。# 加载并准备数据 from sklearn.datasets import load_iris iris load_iris() X_iris iris.data[:, :2] # 只取前两个特征 (萼片长度和宽度) y_iris iris.target # 真实标签仅用于对比算法本身不知道 # 使用手写 K-Means kmeans KMeansManual(n_clusters3, max_iters100) kmeans.fit(X_iris) labels kmeans.labels centroids kmeans.centroids # 可视化聚类结果 plt.figure(figsize(12, 5)) plt.subplot(1, 2, 1) plt.scatter(X_iris[:, 0], X_iris[:, 1], cy_iris, cmapviridis, edgecolork, s50) plt.scatter(centroids[:, 0], centroids[:, 1], cred, markerX, s200, labelCentroids (Manual)) plt.title(Ground Truth (Iris Species)) plt.xlabel(Sepal Length) plt.ylabel(Sepal Width) plt.subplot(1, 2, 2) plt.scatter(X_iris[:, 0], X_iris[:, 1], clabels, cmapviridis, edgecolork, s50) plt.scatter(centroids[:, 0], centroids[:, 1], cred, markerX, s200, labelCentroids (Manual)) plt.title(K-Means Clustering Result (Manual)) plt.xlabel(Sepal Length) plt.ylabel(Sepal Width) plt.legend() plt.show() print(f簇内误差平方和 (Inertia): {kmeans.inertia_:.2f})K-Means 的核心挑战与调参K 值的选择这是 K-Means 最大的挑战。我们如何知道数据中天然存在几个簇肘部法则绘制不同 K 值对应的簇内误差平方和Inertia。随着 K 增大Inertia 会下降。选择 Inertia 下降速度突然变缓的点像肘部弯曲作为合理的 K 值。inertias [] K_range range(1, 10) for k in K_range: km KMeansManual(n_clustersk) km.fit(X_iris) inertias.append(km.inertia_) plt.plot(K_range, inertias, bo-) plt.xlabel(Number of clusters (K)) plt.ylabel(Inertia) plt.title(Elbow Method for Optimal K) plt.show()轮廓系数更高级的方法同时考虑簇内的凝聚度和簇间的分离度值越接近 1 越好。初始化的敏感性随机初始化可能导致不同的局部最优解。生产环境中通常采用K-Means初始化策略它使初始中心点彼此远离能有效提升收敛速度和结果稳定性。对异常值和簇形状的假设K-Means 使用欧氏距离因此天然假设簇是凸形的、各向同性的在各个方向方差相近且对异常值敏感。对于非球形簇或方差差异大的簇效果可能不佳此时可考虑 DBSCAN 或高斯混合模型 GMM。数据标准化与梯度下降一样如果特征量纲不同距离计算会被量级大的特征主导。在聚类前必须进行特征标准化。4. 决策树基于信息论的规则构建决策树通过一系列 if-else 规则对数据进行划分目标是使划分后子集的“纯度”越来越高。理解决策树的关键在于理解其用于选择划分特征的准则。4.1 核心概念信息增益与基尼不纯度决策树学习的关键是在每一个节点选择哪个特征进行分割以及分割点在哪里。常用的准则有两个信息增益基于信息熵。熵表示随机变量的不确定性熵越大不确定性越高。信息增益 父节点的熵 - 子节点的加权平均熵。我们选择信息增益最大的特征进行分割。基尼不纯度衡量一个随机选中的样本在子集中被分错的可能性。基尼不纯度越小集合纯度越高。CART 树默认使用基尼系数。我们以实现分类树CART为例使用基尼不纯度作为划分标准。class Node: 决策树节点类 def __init__(self, feature_indexNone, thresholdNone, leftNone, rightNone, valueNone): self.feature_index feature_index # 用于分割的特征索引 self.threshold threshold # 分割阈值 self.left left # 左子树 ( threshold) self.right right # 右子树 ( threshold) self.value value # 如果是叶节点存储预测的类别 class DecisionTreeClassifierManual: 手动实现决策树分类器 (CART) def __init__(self, max_depth5, min_samples_split2): self.max_depth max_depth self.min_samples_split min_samples_split self.root None def _gini(self, y): 计算基尼不纯度 m y.size if m 0: return 0 # 计算每个类别的比例 p np.bincount(y) / m # 基尼不纯度 1 - Σ(p_i^2) return 1 - np.sum(p ** 2) def _best_split(self, X, y): 寻找最佳分割特征和阈值 m, n X.shape if m 1: # 样本数不足以分割 return None, None # 计算父节点的基尼不纯度 parent_gini self._gini(y) best_gini float(inf) best_feature, best_threshold None, None # 遍历所有特征 for feature_idx in range(n): # 获取该特征的所有唯一值作为候选阈值 thresholds np.unique(X[:, feature_idx]) for threshold in thresholds: # 根据阈值划分左右子集 left_mask X[:, feature_idx] threshold right_mask ~left_mask if np.sum(left_mask) 0 or np.sum(right_mask) 0: continue # 分割无效 # 计算加权平均基尼不纯度 gini_left self._gini(y[left_mask]) gini_right self._gini(y[right_mask]) n_left, n_right np.sum(left_mask), np.sum(right_mask) weighted_gini (n_left / m) * gini_left (n_right / m) * gini_right # 如果找到了更优的分割 if weighted_gini best_gini: best_gini weighted_gini best_feature feature_idx best_threshold threshold # 计算信息增益基尼减少量 info_gain parent_gini - best_gini # 如果信息增益非常小则不分割 if info_gain 1e-7: return None, None return best_feature, best_threshold def _build_tree(self, X, y, depth0): 递归构建决策树 num_samples, num_features X.shape num_classes len(np.unique(y)) # 终止条件 if (depth self.max_depth or num_samples self.min_samples_split or num_classes 1): # 创建叶节点值为最常见的类别 most_common_class np.argmax(np.bincount(y)) return Node(valuemost_common_class) # 寻找最佳分割 feature_idx, threshold self._best_split(X, y) if feature_idx is None: # 无法找到有效分割 most_common_class np.argmax(np.bincount(y)) return Node(valuemost_common_class) # 根据最佳分割划分数据 left_mask X[:, feature_idx] threshold right_mask ~left_mask # 递归构建左右子树 left_subtree self._build_tree(X[left_mask], y[left_mask], depth 1) right_subtree self._build_tree(X[right_mask], y[right_mask], depth 1) # 返回当前节点 return Node(feature_indexfeature_idx, thresholdthreshold, leftleft_subtree, rightright_subtree) def fit(self, X, y): self.root self._build_tree(X, y) return self def _predict_single(self, x, node): 对单个样本进行预测 if node.value is not None: # 到达叶节点 return node.value # 根据特征值和阈值决定走向左子树还是右子树 if x[node.feature_index] node.threshold: return self._predict_single(x, node.left) else: return self._predict_single(x, node.right) def predict(self, X): return np.array([self._predict_single(x, self.root) for x in X])4.2 决策树实战过拟合与剪枝我们使用一个简单的二维非线性分类数据集来演示决策树的工作方式及其核心问题。# 生成月亮形数据集 from sklearn.datasets import make_moons X_moons, y_moons make_moons(n_samples200, noise0.2, random_state42) # 训练一个深度较大的树容易过拟合 tree_deep DecisionTreeClassifierManual(max_depth10) tree_deep.fit(X_moons, y_moons) # 训练一个深度较小的树可能欠拟合 tree_shallow DecisionTreeClassifierManual(max_depth3) tree_shallow.fit(X_moons, y_moons) # 可视化决策边界 def plot_decision_boundary(clf, X, y, title): x_min, x_max X[:, 0].min() - 0.5, X[:, 0].max() 0.5 y_min, y_max X[:, 1].min() - 0.5, X[:, 1].max() 0.5 xx, yy np.meshgrid(np.arange(x_min, x_max, 0.02), np.arange(y_min, y_max, 0.02)) Z clf.predict(np.c_[xx.ravel(), yy.ravel()]) Z Z.reshape(xx.shape) plt.contourf(xx, yy, Z, alpha0.3, cmapcoolwarm) plt.scatter(X[:, 0], X[:, 1], cy, edgecolork, cmapcoolwarm) plt.title(title) plt.xlabel(Feature 1) plt.ylabel(Feature 2) plt.figure(figsize(12, 5)) plt.subplot(1, 2, 1) plot_decision_boundary(tree_shallow, X_moons, y_moons, Decision Tree (Max Depth3)) plt.subplot(1, 2, 2) plot_decision_boundary(tree_deep, X_moons, y_moons, Decision Tree (Max Depth10) - Potential Overfitting) plt.show()决策树的关键问题与调优过拟合这是决策树最显著的问题。如果不加限制树会一直生长直到每个叶节点只包含一个样本在训练集上达到 100% 准确率但会学习到噪声和异常点泛化能力极差。控制过拟合的主要手段是剪枝。预剪枝在构建树的过程中提前停止。通过max_depth最大深度、min_samples_split节点最小分裂样本数、min_samples_leaf叶节点最小样本数等参数控制。后剪枝先构建一棵完整的树然后自底向上考察非叶节点。如果将其替换为叶节点能提升验证集性能则进行剪枝。我们手写的简易版本只实现了预剪枝。不稳定性数据的微小变化可能导致生成完全不同的树。这是因为决策树在顶层选择特征时信息增益或基尼系数的差异可能很小随机性会影响选择。这也是集成方法如随机森林被广泛使用的原因——通过构建多棵树并投票来降低方差。特征重要性决策树可以天然地评估特征重要性。一个特征被用于分割的次数越多或者其带来的不纯度下降越多它通常就越重要。这为特征选择提供了依据。处理连续值和缺失值我们的简易实现假设特征是连续的并通过遍历所有唯一值寻找阈值。工业级实现会使用更高效的二分查找。对于缺失值常见策略是使用替代分割或将该样本分配到所有子节点并赋予权重。5. 从手动实现到生产实践常见问题排查与最佳实践手动实现算法让我们洞悉了其内核但在实际生产项目中我们几乎总是使用sklearn这样的成熟库。理解底层原理能帮助我们在使用高级 API 时做出正确的决策和高效的排查。5.1 模型不工作通用排查清单当你训练好的模型表现不佳时可以按以下顺序排查问题现象可能原因检查方式处理建议线性回归损失不下降或爆炸1. 学习率过大或过小2. 特征未标准化3. 迭代次数不足绘制损失曲线打印前几次迭代的权重变化检查特征最大值/最小值。调整学习率对特征进行StandardScaler标准化增加迭代次数或添加早停。K-Means 结果每次运行都不一样1. 随机初始化导致局部最优2. K 值选择不当固定随机种子 (random_state)运行多次取平均或最佳结果绘制肘部曲线。使用KMeans初始化sklearn默认尝试不同的 K 值考虑使用轮廓系数。决策树在训练集完美但测试集很差过拟合查看树的最大深度和叶节点样本数绘制学习曲线训练/测试得分 vs 树深度。增加min_samples_split和min_samples_leaf减小max_depth使用交叉验证调参。所有模型性能都像随机猜测1. 特征与标签无关2. 数据泄露或预处理错误3. 评估指标用错计算特征与标签的相关系数检查训练/测试集划分是否正确确认标签编码无误。进行特征工程检查数据流水线使用正确的评估指标如分类用准确率/F1回归用MSE/R2。5.2 算法选型速查指南不同的算法有其固有的优势和假设。选择不当是项目失败的主要原因之一。算法核心优势主要假设/局限典型应用场景线性回归简单、可解释性强、计算快、可得到参数置信区间。假设线性关系、对异常值敏感、特征需独立。房价预测、销售额预估、任何假设输入输出呈线性关系的场景。逻辑回归输出概率、可解释性强、不易过拟合。仍是线性模型决策边界线性、需要大量样本。二分类问题如垃圾邮件识别、广告点击预测。决策树非参数、可处理数值和类别特征、无需特征缩放、可视化强。极易过拟合、不稳定、对不平衡数据敏感。需要解释规则如信贷风控、数据包含复杂 if-else 逻辑。随机森林高准确率、抗过拟合、可评估特征重要性、并行化容易。计算和存储开销大、可解释性比单棵树差。大多数分类和回归任务的首选基准模型尤其是表格数据。K-Means简单、高效、可扩展性强。需指定K、对异常值和非球形簇敏感、依赖初始化。客户分群、图像压缩、异常检测将远离簇的点视为异常。神经网络拟合能力极强、可处理图像/文本等非结构化数据。需要大量数据和算力、超参数多、黑盒模型。计算机视觉、自然语言处理、复杂模式识别。5.3 生产环境最佳实践永远从基线模型开始不要一开始就使用最复杂的模型。先用线性回归或逻辑回归建立一个性能基线。这能帮你快速验证数据流水线并了解问题的难度。数据预处理是重中之重包括处理缺失值、异常值、类别特征编码、特征缩放对基于距离的模型如 K-Means、SVM 和基于梯度的模型至关重要、以及特征工程。大部分性能提升来源于更好的数据而非更复杂的模型。系统化评估与验证务必使用训练集/验证集/测试集的分割。使用交叉验证来更稳健地评估模型性能和调参。选择与业务目标一致的评估指标例如在正负例极不平衡时准确率是无效的应关注精确率、召回率或 F1 分数。理解模型的局限性知道你的模型在什么情况下会失败。例如线性回归无法捕捉非线性关系K-Means 无法发现流形形状的簇深度很浅的决策树可能欠拟合。可解释性与监控尤其是在金融、医疗等领域模型的可解释性可能比微小的精度提升更重要。同时模型上线后需要持续监控其性能因为数据分布可能会随时间漂移。通过从零实现这些经典算法你获得的不仅是对其数学本质的深刻理解更是一种“算法思维”——能够将复杂的数学优化问题分解为可迭代、可编程的步骤。这种能力是应对未来更复杂模型如梯度提升树、神经网络的坚实基础。下一步你可以尝试实现随机森林通过组合多棵决策树、支持向量机SVM的核技巧甚至是一个简单的多层感知机MLP从而将这种“从原理到实现”的学习方法应用到更广阔的机器学习领域。
返回列表