【机器学习实战】18种核心算法数学推导与Python实现详解
1. 从“猜”到“算”:机器学习算法入门心法
很多朋友一听到“机器学习”、“算法”、“数学公式”,头就开始大了,感觉像是要重新翻开大学时的高等数学课本。别怕,我刚开始接触的时候也是这个感觉。但后来我发现,理解这些算法的核心,其实不需要你成为数学天才,关键在于转变一个思路:机器学习不是让机器去“猜”,而是教它如何“算”。我们平时做预测,比如看天气、猜股票,多少带点蒙的成分。但机器学习算法,是把我们人类“拍脑袋”的过程,变成一套可以计算、可以优化、可以复现的数学流程。
这篇文章就是为你准备的,如果你已经了解了一些Python基础,对数据分析有点兴趣,但又对那些黑盒模型感到困惑,想搞清楚“为什么这个参数调一下效果就好很多”,那你就来对地方了。我不会只给你扔一堆公式,也不会只让你调包跑通代码就完事。我们要做的是两件事:第一,亲手用数学推导,看看这个算法到底是怎么“想”问题的;第二,立刻用Python(主要是NumPy和一点点sklearn)把它实现出来,看看公式里的每一个符号,对应到代码里究竟是哪一行。这种从理论到落地的打通,才是你真正掌握一个算法、能在面试里侃侃而谈、能在项目里灵活调参的底气。
我将会带你深入18个最核心的算法,从最简单的线性回归,到有点绕的逻辑回归,再到强大的集成模型和聚类方法。每个算法,我们都遵循“生活例子引入 -> 核心思想白话解读 -> 关键公式分步推导 -> 手写Python代码实现 -> 对比sklearn验证”这条路径。放心,我们会避开最枯燥的纯理论证明,聚焦在那些直接影响你理解和调参的核心公式上。准备好了吗?让我们先从最基础,也最常用的线性回归家族开始。
2. 线性回归家族:预测的基石
线性回归绝对是机器学习世界的“Hello World”。它的思想直观到不行:找一条线(或者一个平面),让这条线尽可能穿过所有的数据点。但“尽可能穿过”这个说法太模糊了,数学家的严谨劲儿就体现在这里,他们把它转化成了一个可以求解的优化问题。
2.1 一元与多元线性回归:从直线到超平面
我们先从最简单的一元一次线性回归说起,就是 y = ax + b。假设你想根据房子面积(x)来预测房价(y)。我们怎么找到最合适的 a(斜率)和 b(截距)呢?机器学习的思路是:先定义一个标准,来衡量这条线“好不好”。这个标准就是损失函数,最常用的就是均方误差:把所有数据点的预测值 (a*x_i + b) 和真实值 y_i 的差的平方加起来,再求平均。公式就是:
L(a, b) = (1/n) * Σ(y_i - (a*x_i + b))^2
我们的目标就是找到一对 a 和 b,让这个 L(a, b) 的值最小。怎么找?这里就要用到一点微积分了,对 a 和 b 分别求偏导数,并令它们等于0。这个过程就是最小二乘法。推导下来,你会得到两个公式:
a = Σ((x_i - x_mean) * (y_i - y_mean)) / Σ((x_i - x_mean)^2)
b = y_mean - a * x_mean
看,a 就是 x 和 y 的协方差除以 x 的方差,b 就是让直线穿过 (x_mean, y_mean) 这个中心点。理解了这个,代码实现就水到渠成了。
import numpy as np
def simple_linear_regression(x, y):
"""
手动实现一元线性回归
"""
n = len(x)
x_mean, y_mean = np.mean(x), np.mean(y)
# 计算斜率 a
numerator = np.sum((x - x_mean) * (y - y_mean))
denominator = np.sum((x - x_mean) ** 2)
a = numerator / denominator
# 计算截距 b
b = y_mean - a * x_mean
return a, b
# 生成示例数据
np.random.seed(42)
x = np.random.rand(100) * 10
y = 2.5 * x + 1.0 + np.random.randn(100) * 2 # 加上一些噪声
a, b = simple_linear_regression(x, y)
print(f"手动计算: 斜率 a = {a:.4f}, 截距 b = {b:.4f}")
# 用sklearn验证一下
from sklearn.linear_model import LinearRegression
model = LinearRegression().fit(x.reshape(-1, 1), y)
print(f"Sklearn验证: 斜率 = {model.coef_[0]:.4f}, 截距 = {model.intercept_:.4f}")
运行上面这段代码,你会发现手动计算的结果和sklearn的结果几乎一模一样(可能因为浮点数计算有细微差别)。这种亲手验证的感觉,能极大地消除你对算法的陌生感。
现实世界的问题很少只有一个影响因素。房价怎么可能只由面积决定?还有地段、楼层、房龄等等。这时候就需要多元线性回归了,公式变成 y = w1*x1 + w2*x2 + ... + wn*xn + b。这里 w 是权重(系数),b 是偏置。为了写起来方便,我们通常用向量和矩阵的形式表示:Y = XW。其中 X 是包含了所有特征(外加一列1用于计算b)的矩阵,W 是包含所有权重和偏置的列向量。损失函数依然是均方误差,推导后可以得到权重向量的最优解公式(正规方程):
W = (X^T * X)^(-1) * X^T * Y
这个公式很美,直接给出了解析解。但它的计算复杂度较高,且要求 X^T * X 可逆。当特征非常多(比如上万维)或者样本量巨大时,我们更倾向于用梯度下降这种迭代方法去逼近最优解。梯度下降的核心思想就像下山:看看当前位置往哪个方向走(梯度方向),损失函数下降得最快,就朝那个方向迈一步(学习率)。我们会在后续的实战中详细实现它。
2.2 过拟合克星:岭回归与Lasso回归
当你用线性回归模型时,可能会遇到两个头疼的问题:一是特征之间相关性太高(多重共线性),导致权重估计不稳定,一点数据波动就让结果大变样;二是模型为了完美拟合训练数据,甚至把噪声都学进去了,这就是过拟合,在训练集上表现无敌,在新数据上一塌糊涂。
怎么诊断多重共线性?一个常用工具就是方差膨胀因子。它的思想很简单:把一个特征作为因变量,用其他所有特征去线性回归它,看拟合得好不好(R²)。如果拟合得太好,说明这个特征可以被其他特征线性表示,共线性就严重。VIF = 1 / (1 - R²)。通常,VIF大于10就值得警惕了。
为了解决过拟合和共线性,正则化技术登场了。它的核心是在损失函数里加一个“惩罚项”,告诉模型:“别光顾着拟合训练数据,也要让权重参数本身别太大、太复杂”。
岭回归 加的是L2惩罚项,即所有权重系数的平方和。损失函数变成:L = MSE + α * Σ(w_i^2)。这个 α 是控制惩罚力度的超参数。加了这个项之后,模型在优化时就会倾向于让权重 w 整体变小、更平滑,从而抑制过拟合。它的解也有解析形式:W = (X^T * X + α * I)^(-1) * X^T * Y。看,就是在原来 X^T * X 的基础上加了一个 α 乘以单位矩阵 I,这个操作保证了矩阵一定是可逆的,完美解决了共线性问题。
Lasso回归 则更激进一些,它加的是L1惩罚项,即所有权重系数的绝对值之和:L = MSE + α * Σ|w_i|。L1惩罚有一个神奇的特性:它倾向于让一部分不重要的特征的权重直接变成0。这就相当于在做特征选择,自动帮你筛选出最重要的特征。这对于特征成百上千的场景非常有用,能得到一个更简洁、可解释性更强的模型。
from sklearn.linear_model import Ridge, Lasso
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
# 假设我们有一个包含多个高度相关特征的数据集
# 这里用波士顿房价数据集简化演示
from sklearn.datasets import load_diabetes
data = load_diabetes()
X, y = data.data, data.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 标准化数据,这对正则化模型很重要
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 普通线性回归
lr = LinearRegression().fit(X_train_scaled, y_train)
print(f"线性回归训练集得分: {lr.score(X_train_scaled, y_train):.4f}")
print(f"线性回归测试集得分: {lr.score(X_test_scaled, y_test):.4f}")
# 岭回归
ridge = Ridge(alpha=1.0).fit(X_train_scaled, y_train)
print(f"\n岭回归(alpha=1)训练集得分: {ridge.score(X_train_scaled, y_train):.4f}")
print(f"岭回归(alpha=1)测试集得分: {ridge.score(X_test_scaled, y_test):.4f}")
print("岭回归系数绝对值之和:", np.sum(np.abs(ridge.coef_)))
# Lasso回归
lasso = Lasso(alpha=0.1).fit(X_train_scaled, y_train)
print(f"\nLasso回归(alpha=0.1)训练集得分: {lasso.score(X_train_scaled, y_train):.4f}")
print(f"Lasso回归(alpha=0.1)测试集得分: {lasso.score(X_test_scaled, y_test):.4f}")
print("Lasso回归非零系数个数:", np.sum(lasso.coef_ != 0))
print("Lasso回归系数:", lasso.coef_)
运行这段代码,你可能会发现,虽然线性回归在训练集上分数最高,但在测试集上,岭回归和Lasso回归往往能获得更好或相当的分数,这说明它们控制了过拟合。特别是Lasso,它给出的系数向量里会有很多0,清晰地告诉你哪些特征被模型认为不重要。
3. 逻辑回归:分类世界的“回归”算法
千万别被它的名字骗了!逻辑回归虽然叫“回归”,但它是个地地道道的分类算法,而且是二分类的扛把子。那它和回归有什么关系呢?关系在于,它用线性回归的思路去拟合了一个事件发生的概率。
3.1 Sigmoid函数与决策边界
线性回归的输出 z = w^T * x + b 的范围是负无穷到正无穷,这显然不能直接表示一个0到1之间的概率。于是,我们引入一个神奇的 Sigmoid函数(也叫Logistic函数):σ(z) = 1 / (1 + e^(-z))。这个函数长得很像一条拉长的“S”形曲线,它能把任何实数 z 平滑地映射到(0, 1)区间。这样,我们就可以把 σ(z) 解释为样本属于正类的概率:P(y=1|x) = σ(w^T * x + b)。
那么模型如何做决策呢?通常我们设定一个阈值,比如0.5。如果 P(y=1|x) > 0.5,就预测为正类(1),否则为负类(0)。从几何上看,P(y=1|x) = 0.5 对应着 z = w^T * x + b = 0,这正好是一个线性方程。逻辑回归的决策边界,本质上是一个超平面。这就是为什么逻辑回归是线性分类器。它只能处理线性可分的数据,对于复杂的非线性边界,我们需要借助特征工程或者核方法(这是后话)。
3.2 交叉熵损失与梯度下降
既然输出变成了概率,我们衡量模型好坏的损失函数也得换。均方误差在这里不太合适(会导致优化目标非凸,容易陷入局部最优)。逻辑回归使用的是交叉熵损失,它衡量的是预测概率分布与真实概率分布之间的差异。对于单个样本,损失函数是:
L = - [y * log(p) + (1-y) * log(1-p)]
其中 y 是真实标签(0或1),p 是预测为正类的概率。你可以直观理解一下:如果真实标签 y=1,那么损失就是 -log(p),预测概率 p 越接近1,损失越小;如果 y=0,损失就是 -log(1-p),预测概率 p 越接近0,损失越小。这个函数是凸函数,保证了我们能找到全局最优解。
有了损失函数,我们又要请出老朋友梯度下降来优化权重 w 和 b 了。经过求导(推导过程是理解逻辑回归的关键一步,建议手推一遍),我们可以得到参数更新的梯度公式,惊人地简洁:
∂L/∂w_j = (p - y) * x_j
∂L/∂b = (p - y)
看,梯度就等于预测误差 (p-y) 乘以对应的特征值 x_j。误差大,更新幅度就大;误差小,更新幅度就小。这非常符合直觉。
class LogisticRegressionManual:
"""手动实现逻辑回归(使用梯度下降)"""
def __init__(self, learning_rate=0.01, n_iters=1000):
self.lr = learning_rate
self.n_iters = n_iters
self.weights = None
self.bias = None
def _sigmoid(self, z):
# 防止数值溢出
z = np.clip(z, -500, 500)
return 1 / (1 + np.exp(-z))
def fit(self, X, y):
n_samples, n_features = X.shape
self.weights = np.zeros(n_features)
self.bias = 0
# 梯度下降
for _ in range(self.n_iters):
# 线性模型输出
linear_model = np.dot(X, self.weights) + self.bias
# 通过sigmoid得到概率预测
y_pred = self._sigmoid(linear_model)
# 计算梯度
dw = (1 / n_samples) * np.dot(X.T, (y_pred - y))
db = (1 / n_samples) * np.sum(y_pred - y)
# 更新参数
self.weights -= self.lr * dw
self.bias -= self.lr * db
def predict_proba(self, X):
linear_model = np.dot(X, self.weights) + self.bias
return self._sigmoid(linear_model)
def predict(self, X, threshold=0.5):
proba = self.predict_proba(X)
return (proba >= threshold).astype(int)
# 使用鸢尾花数据集做一个简单的二分类(两类)
from sklearn.datasets import load_iris
from sklearn.metrics import accuracy_score
iris = load_iris()
# 只取前两类(Setosa和Versicolor)和两个特征,方便可视化
X = iris.data[:100, :2] # 只取前100个样本,前两个特征
y = iris.target[:100]
# 手动实现的逻辑回归
manual_lr = LogisticRegressionManual(learning_rate=0.1, n_iters=3000)
manual_lr.fit(X, y)
y_pred_manual = manual_lr.predict(X)
print(f"手动实现逻辑回归准确率: {accuracy_score(y, y_pred_manual):.4f}")
# 使用sklearn验证
from sklearn.linear_model import LogisticRegression as SKLogisticRegression
sk_lr = SKLogisticRegression().fit(X, y)
y_pred_sk = sk_lr.predict(X)
print(f"Sklearn逻辑回归准确率: {accuracy_score(y, y_pred_sk):.4f}")
print(f"手动模型权重: {manual_lr.weights}, 偏置: {manual_lr.bias}")
print(f"Sklearn模型权重: {sk_lr.coef_}, 偏置: {sk_lr.intercept_}")
通过这个手写实现,你能清晰地看到从线性组合 z,到Sigmoid变换得到概率 p,再到用交叉熵损失计算梯度并更新参数的全过程。理解了这个,你就彻底搞懂了逻辑回归,而不再是一个只会调用 sklearn.linear_model.LogisticRegression 的调包侠。
4. 树模型与集成学习:从简单规则到强大预测
如果说线性模型和逻辑回归是在学习数据的“全局”模式,那么树模型则擅长捕捉数据的“局部”结构和交互效应。它的思想更贴近人类做决策的过程:通过一系列“如果...那么...”的问题,将数据层层细分。
4.1 决策树:if-else的自动化
决策树的核心是三个问题:1. 选择哪个特征进行分割?2. 在这个特征的什么值上进行分割?3. 什么时候停止分割?
这就引出了特征选择的准则。最常用的有信息增益(ID3算法)、信息增益率(C4.5算法)和基尼不纯度(CART算法)。我们以分类任务常用的基尼不纯度为例。对于一个数据集 D,其基尼值定义为:Gini(D) = 1 - Σ(p_i^2),其中 p_i 是第 i 类样本在 D 中出现的频率。基尼值越小,数据集的纯度越高。
假设我们根据特征 A 的某个取值 a 将数据集 D 分割成 D1 和 D2 两部分,那么这次分割的基尼指数为:Gini_index(D, A) = |D1|/|D| * Gini(D1) + |D2|/|D| * Gini(D2)。决策树算法会遍历所有特征和所有可能的分割点,选择那个使得分割后基尼指数最小的特征和分割点。这个过程递归进行,直到满足停止条件(如树达到最大深度、节点样本数过少、或纯度已经足够高)。
对于回归树,思路类似,只是将纯度衡量标准从基尼不纯度换成均方误差。选择分割点的标准是使得分割后两个子集的均方误差之和最小。
import numpy as np
from collections import Counter
class DecisionTreeClassifierManual:
"""极度简化的CART决策树分类器(仅用于演示核心原理)"""
def __init__(self, max_depth=5, min_samples_split=2):
self.max_depth = max_depth
self.min_samples_split = min_samples_split
self.tree = None
def _gini(self, y):
"""计算基尼不纯度"""
counter = Counter(y)
impurity = 1.0
for label in counter:
prob = counter[label] / len(y)
impurity -= prob ** 2
return impurity
def _best_split(self, X, y):
"""寻找最佳分割特征和阈值"""
best_gini = float('inf')
best_feature, best_threshold = None, None
n_samples, n_features = X.shape
for feature_idx in range(n_features):
feature_values = X[:, feature_idx]
unique_values = np.unique(feature_values)
# 尝试相邻值的中间值作为候选阈值
thresholds = (unique_values[:-1] + unique_values[1:]) / 2
for threshold in thresholds:
left_mask = feature_values <= threshold
right_mask = feature_values > threshold
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])
weighted_gini = (np.sum(left_mask) * gini_left + np.sum(right_mask) * gini_right) / n_samples
if weighted_gini < best_gini:
best_gini = weighted_gini
best_feature = feature_idx
best_threshold = threshold
return best_feature, best_threshold, best_gini
def _build_tree(self, X, y, depth=0):
"""递归构建树"""
n_samples, n_features = X.shape
n_labels = len(np.unique(y))
# 停止条件
if (depth >= self.max_depth or n_samples < self.min_samples_split or n_labels == 1):
leaf_value = Counter(y).most_common(1)[0][0] # 返回出现最多的类别
return leaf_value
# 寻找最佳分割
feature, threshold, gini = self._best_split(X, y)
if feature is None: # 无法找到有效分割
leaf_value = Counter(y).most_common(1)[0][0]
return leaf_value
# 递归构建左右子树
left_mask = X[:, feature] <= threshold
right_mask = X[:, feature] > threshold
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 {'feature': feature, 'threshold': threshold,
'left': left_subtree, 'right': right_subtree}
def fit(self, X, y):
self.tree = self._build_tree(X, y)
def _predict_one(self, x, node):
"""对单个样本进行预测"""
if not isinstance(node, dict): # 到达叶节点
return node
if x[node['feature']] <= node['threshold']:
return self._predict_one(x, node['left'])
else:
return self._predict_one(x, node['right'])
def predict(self, X):
return np.array([self._predict_one(x, self.tree) for x in X])
# 使用简单的合成数据测试
from sklearn.datasets import make_moons
X_moons, y_moons = make_moons(n_samples=200, noise=0.2, random_state=42)
tree = DecisionTreeClassifierManual(max_depth=3)
tree.fit(X_moons, y_moons)
y_pred_tree = tree.predict(X_moons)
print(f"手动决策树准确率: {accuracy_score(y_moons, y_pred_tree):.4f}")
这个实现非常简化(比如没有处理连续特征优化、缺失值等),但它清晰地展示了决策树如何通过递归地寻找最佳分割来构建模型。单棵决策树容易过拟合,对数据微小变化敏感,这就引出了更强大的集成方法。
4.2 随机森林与梯度提升:集众人之智
随机森林 是 Bagging 思想的杰出代表。它的智慧在于“三个随机”:随机抽取训练样本(有放回抽样,即Bootstrap)、随机抽取特征子集、构建多棵决策树。最终通过投票(分类)或平均(回归)得到结果。这种随机性保证了每棵树都不一样,且专注于数据的不同方面,将它们的结果综合起来,能有效降低单棵树的方差,提高模型的泛化能力和稳定性。数学上,这降低了总体模型的方差,而偏差与单棵树相近。
梯度提升 家族(包括Gradient Boosting, XGBoost, LightGBM, CatBoost)则采用了不同的策略 Boosting。它的思想是“知错就改,逐步完善”。先训练一个简单的模型(比如一棵很浅的树),然后去计算它的预测残差(真实值 - 预测值)。下一个模型不再去拟合原始标签 y,而是去拟合这个残差。这样,每一棵新树都在学习纠正前一棵树的错误。把所有树的预测加起来,就得到了最终的强预测器。XGBoost 在传统梯度提升的基础上,在目标函数中加入了正则化项来控制模型复杂度,并使用了二阶泰勒展开来更精确地逼近损失函数,同时运用了缓存访问、并行处理等工程优化,使其在效率和精度上都非常出色。
# 随机森林和XGBoost的sklearn快速对比
from sklearn.ensemble import RandomForestClassifier
from xgboost import XGBClassifier
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X_moons, y_moons, test_size=0.3, random_state=42)
# 随机森林
rf = RandomForestClassifier(n_estimators=100, max_depth=5, random_state=42)
rf.fit(X_train, y_train)
print(f"随机森林训练集准确率: {rf.score(X_train, y_train):.4f}")
print(f"随机森林测试集准确率: {rf.score(X_test, y_test):.4f}")
# XGBoost
xgb = XGBClassifier(n_estimators=100, max_depth=3, learning_rate=0.1, random_state=42)
xgb.fit(X_train, y_train)
print(f"\nXGBoost训练集准确率: {xgb.score(X_train, y_train):.4f}")
print(f"XGBoost测试集准确率: {xgb.score(X_test, y_test):.4f}")
在实际项目中,对于结构化数据,梯度提升树模型(尤其是XGBoost和LightGBM)往往是竞赛和业界的首选,因为它们通常能提供最高的预测精度。而随机森林由于它的并行特性和对超参数相对不敏感,也是一个非常稳健且易于使用的基线模型。
5. 无监督学习:发现数据的内在结构
之前我们讲的算法都需要有标签 y 来指导学习,这叫监督学习。但现实中,大量数据是没有标签的。无监督学习的目标就是从这些无标签数据中,发现内在的模式、结构或分布。
5.1 K-Means聚类:物以类聚
K-Means的想法非常直观:我想把数据分成K个组,那就先随机指定K个中心点(质心),然后把每个点分配给离它最近的中心点所在的组,接着重新计算每个组的中心点(取组内所有点的均值),然后再重新分配点……如此反复迭代,直到中心点不再发生大的变化。
它的优化目标是最小化簇内平方和:J = Σ Σ ||x - μ_i||^2,其中内层求和是对属于第 i 个簇的所有点 x 到其质心 μ_i 的距离平方求和。K-Means算法就是通过交替执行分配和更新两步来不断降低 J 的值。这里要注意,K-Means对初始质心的选择很敏感,可能陷入局部最优。通常我们会多次运行,选择结果最好的那次。另一个关键问题是K值的选择,肘部法则和轮廓系数是常用的方法。
5.2 PCA降维:抓住主要矛盾
当我们面对成百上千个特征时,不仅计算负担重,而且特征之间可能存在冗余(共线性)。主成分分析 就是一种强大的降维技术,它的目标是将高维数据投影到低维空间,同时尽可能保留原始数据中的变异信息。
PCA的数学基础是特征值分解。步骤是:1. 将数据中心化(减去均值);2. 计算协方差矩阵;3. 对协方差矩阵进行特征值分解,得到特征值和特征向量;4. 将特征值从大到小排序,选择前 k 个最大的特征值对应的特征向量,组成投影矩阵;5. 将原始数据乘以这个投影矩阵,就得到了 k 维的新数据。
这些特征向量被称为“主成分”,它们是新的坐标轴,第一个主成分方向是数据方差最大的方向,第二个主成分是与第一个正交且方差次大的方向,以此类推。通过PCA,我们能用少数几个“主成分”来近似表达原始数据,常用于数据可视化、去除噪声和加速后续模型训练。
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
from sklearn.datasets import load_iris
# 加载鸢尾花数据集(4个特征)
iris = load_iris()
X = iris.data
y = iris.target
# 使用PCA降维到2维,以便可视化
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)
print(f"原始数据形状: {X.shape}")
print(f"降维后数据形状: {X_pca.shape}")
print(f"各主成分解释的方差比例: {pca.explained_variance_ratio_}")
print(f"前两个主成分累计解释方差: {np.sum(pca.explained_variance_ratio_):.4f}")
# 可视化
plt.figure(figsize=(8, 6))
colors = ['navy', 'turquoise', 'darkorange']
for color, i, target_name in zip(colors, [0, 1, 2], iris.target_names):
plt.scatter(X_pca[y == i, 0], X_pca[y == i, 1], color=color, alpha=.8, lw=2, label=target_name)
plt.legend(loc='best', shadow=False, scatterpoints=1)
plt.title('PCA of IRIS dataset')
plt.xlabel('Principal Component 1')
plt.ylabel('Principal Component 2')
plt.show()
运行这段代码,你会看到原本4维的鸢尾花数据被投影到了2维平面上,并且三个类别依然能够被较好地区分开。explained_variance_ratio_ 告诉你每个主成分携带了多少原始信息。在实际应用中,我们常常选择累计解释方差达到95%或99%的前几个主成分,从而实现有效降维。
从线性回归的确定性预测,到逻辑回归的概率性分类,再到决策树的规则挖掘和集成学习的威力叠加,最后到无监督学习中数据内在结构的探索,这18个算法构成了机器学习一个坚实而丰富的工具箱。理解其背后的数学原理,能让你在模型调参、问题诊断和算法选型时不再盲目;而亲手实现它们,哪怕是最简化的版本,也能让你对这些工具产生真正的“手感”。机器学习的世界远不止于此,但掌握了这些核心算法,你就已经拥有了解决绝大多数实际问题的坚实基础。剩下的,就是在不断的项目实战中,去体会如何将这些工具灵活组合,去解决那些真正有趣的问题了。
更多推荐


所有评论(0)