1. 十大基础算法全景图
1.1 为什么必须有“基础算法”这个概念
人工智能这个领域现在热闹到什么程度呢?随便打开一个招聘网站,AI相关岗位的薪资都很高;随便刷一条短视频,都能看到大模型、智能体的消息。但热闹归热闹,真正想进入这个领域、或者说想把AI用明白的人,很快就会撞上一堵墙:理论太多、框架太多、名词太多,根本不知道从哪下手。
我的建议一直很直接:别急着追大模型,先把基础算法吃透。原因很简单,今天所有看起来高大上的人工智能应用,包括大语言模型、自动驾驶、推荐系统、人脸识别,底层都是这些基础算法在支撑。你可以把基础算法理解为“内功”,把TensorFlow、PyTorch这些框架理解为“兵器”。兵器再锋利,内功不够,使出来的招式也是飘的。
那什么叫“基础算法”呢?业内比较公认的说法,是下面这十种:线性回归、逻辑回归、决策树、随机森林、支持向量机(SVM)、K近邻(KNN)、K-Means聚类、朴素贝叶斯、主成分分析(PCA)、AdaBoost。这十种算法覆盖了机器学习里最核心的两大分支——监督学习和无监督学习,也覆盖了分类、回归、聚类、降维这四类最常见的任务。不管你是做数据分析、算法工程师,还是想搞AI产品设计,这十个算法都是绕不过去的基本功。
我见过不少学生和转行的朋友,上来就报班学深度学习,学了一个月卷积神经网络、循环神经网络,结果连过拟合和欠拟合都说不清楚,连训练集和测试集为什么要分开都理解不了。这种基础不牢的情况,后期写论文、做项目、调模型的时候会非常痛苦。反过来,如果你把这十个算法真正弄明白了,再看深度学习的内容,会发现很多概念其实是相通的,只是表达方式变了而已。
1.2 十大算法分类与学习路线
这十个算法不是随便排在一起的,它们之间有清晰的内在逻辑。我习惯把它们分成四组来理解:
第一组是回归家族,包括线性回归和逻辑回归。线性回归解决的是“预测一个数值”的问题,比如预测房价、预测销量;逻辑回归解决的是“判断是A还是B”的问题,比如判断邮件是不是垃圾邮件、判断用户会不会流失。虽然名字里都带“回归”,但逻辑回归实际上是分类算法,这是新手最容易搞混的地方。
第二组是树模型家族,包括决策树和随机森林。决策树的特点是“可解释性强”,它能告诉你它为什么做出这个判断,就像医生根据症状一步步推断病情一样。随机森林则是把很多棵决策树组合在一起,用“投票”的方式做决定,准确率更高,但解释性会差一些。
第三组是基于距离和概率的算法,包括K近邻和朴素贝叶斯。K近邻的思路非常朴素:跟你最像的那群人是什么样子,你大概就是什么样子。朴素贝叶斯则是用概率论来做判断,它在文本分类领域表现非常好,垃圾邮件过滤的经典方案就是它。
第四组是优化和集成类算法,包括支持向量机、K-Means、PCA和AdaBoost。支持向量机擅长处理高维数据,在图像识别和文本分类上都曾大放异彩;K-Means是聚类算法,解决的是“无监督”的问题,也就是没有标准答案、需要自动分组的场景;PCA用于降维,可以把几百个特征压缩成几十个,同时保留大部分有效信息;AdaBoost则是集成学习的代表,把多个表现一般的模型组合成一个强模型。
我给新人的学习路线通常是这样的:先学线性回归和逻辑回归,把损失函数、梯度下降这些最核心的概念搞清楚;然后学决策树和随机森林,理解集成学习的思想;接着学K近邻和朴素贝叶斯这两个相对简单的算法,增强信心;再啃SVM和PCA这两个数学要求稍高的算法;最后学K-Means和AdaBoost。这个顺序由易到难,知识点之间有递进关系,学起来不会那么痛苦。
2. 回归家族:从线性回归到逻辑回归
2.1 线性回归:一切预测问题的起点
线性回归是机器学习里最基础、也最容易被低估的算法。它的数学形式非常简单,就是 y = wx + b,高中就学过的一次函数。但在机器学习语境下,这个公式的含义是:我们有一堆数据点,每个点有若干特征(x),还有一个真实的结果(y),我们需要找到一组参数(w和b),让预测值和真实值之间的差距尽可能小。
这个“让差距尽可能小”的过程,就是机器学习里最核心的概念之一:优化。我们需要定义一个损失函数,最常见的是均方误差(MSE),然后通过梯度下降法不断调整参数,把损失函数的值降下来。梯度下降这个思想贯穿了整个机器学习领域,包括深度学习的反向传播,本质上也是梯度下降的一种复杂应用。
实际操作中,线性回归有不少需要注意的坑。第一个坑是特征需要归一化。如果某个特征的数值范围是0到1,另一个是0到10000,那梯度下降的速度会非常慢,甚至不收敛。第二个坑是异常值影响很大。线性回归对异常值非常敏感,一个极端值就可能把回归线拉偏,所以在建模之前,先做异常值检测非常关键。第三个坑是多重共线性,也就是特征之间高度相关,这会导致参数估计不稳定。
我当年刚学的时候犯过一个特别蠢的错误:直接用Sklearn里的LinearRegression跑数据,也没看特征分布,结果模型效果极差。后来才发现数据里有几个离群点,把回归线带偏了。从那以后我养成了一个习惯,训练任何模型之前,先画图,用散点图看数据分布,这比任何高级技巧都管用。
用Python实现线性回归非常容易,Sklearn几行代码就搞定了:
from sklearn.linear_model import LinearRegression from sklearn.model_selection import train_test_split import numpy as np # 假设X是特征矩阵,y是目标值 X, y = np.random.rand(100, 3), np.random.rand(100) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) model = LinearRegression() model.fit(X_train, y_train) print("训练集评分:", model.score(X_train, y_train)) print("测试集评分:", model.score(X_test, y_test)) print("系数:", model.coef_) y_pred = model.predict(X_test)注意这里我用了train_test_split把数据分成了训练集和测试集,这是机器学习里最基本的规矩:不能用训练模型的数据来评估模型。如果你用训练集评分来炫耀模型多好,那基本等于考试前先看了答案再参加考试,分数再高也没有意义。评估模型一定要看它在没见过的数据上的表现,也就是测试集上的表现。
2.2 逻辑回归:披着回归外衣的分类器
逻辑回归这个名字很有迷惑性,很多初学者以为它是做回归的,其实它是最经典二分类算法。它的思路是这样的:在线性回归的基础上,加一个Sigmoid函数(也叫Logistic函数),把输出压缩到0到1之间,然后把这个值理解为“属于正类的概率”。如果概率大于0.5,就判为正类,否则判为负类。
逻辑回归的数学表达式是:P(y=1|x) = 1 / (1 + e^(-z)),其中z = wx + b。这个Sigmoid函数很有意思,它在z很大时趋近于1,z很小时趋近于0,在z=0附近有一个平滑的过渡。这个“平滑”很重要,它让模型不仅能给出分类结果,还能给出概率,这对很多业务场景很有价值。比如在风控领域,银行不仅要判断一笔交易是不是欺诈,还要知道欺诈的概率有多大,概率高的优先处理。
逻辑回归的损失函数和线性回归不一样,它用的是对数损失函数(Log Loss),也叫交叉熵损失。这也是一个贯穿深度学习的重要概念,后面学神经网络的时候会反复遇到它。
实操中,逻辑回归有几个优势让它至今仍然被广泛使用。第一是可解释性强,每个特征的系数都能直接说明“这个特征每增加一个单位,目标概率会怎么变化”,这在金融、医疗等需要解释原因的行业非常重要。第二是训练速度快,即使数据量很大,逻辑回归也能很快训练完。第三是对内存要求低,部署起来很方便。
但逻辑回归也有它的短板。它对特征之间关系的表达能力有限,如果数据里有复杂的非线性关系,逻辑回归的表现就不太够。这时候要么做特征工程,把非线性关系显式地构造出来,要么就直接换更复杂的模型,比如树模型或者神经网络。
给个简单的逻辑回归代码示例:
from sklearn.linear_model import LogisticRegression from sklearn.metrics import classification_report model = LogisticRegression() model.fit(X_train, y_train_binary) # y_train_binary是0/1标签 y_pred = model.predict(X_test) print(classification_report(y_test_binary, y_pred)) # 查看每个特征的系数,理解模型逻辑 for feature, coef in zip(feature_names, model.coef_[0]): print(f"{feature}: {coef:.4f}")一个我踩过的坑:逻辑回归默认带L2正则化,正则化系数C默认是1.0。在很多情况下默认值就能工作,但如果特征非常多或者数据量很小,需要手动调C值。C值越小,正则化越强,模型越简单,越不容易过拟合;C值越大,正则化越弱,模型越复杂。调参的时候可以试几个数量级,0.01、0.1、1、10、100,看验证集上哪个表现最好。
3. 树模型:从决策树到随机森林
3.1 决策树:人类决策过程的机器化
决策树是我个人非常喜欢的一种算法,因为它太符合人类的思维方式了。我们在生活中做决定时,往往是按条件一步步判断的:今天出门要不要带伞?先看天气,如果下雨就带,不下就不带。如果天气预报不准,再看云层厚度,云厚就带,云薄就不带。决策树就是把这种“如果有这种情况,那么走那条路”的判断过程形式化,用一棵倒着长的树来表示。
树模型的训练过程和人类学习的过程也很像:从根节点开始,每一次都找一个最优的特征和最优的切分点,把数据分成两组,然后在每一组里继续找下一个特征,继续切分,直到满足停止条件。这个“最优”怎么衡量呢?常见的有三种标准:信息增益(ID3算法用)、信息增益率(C4.5用)、基尼系数(CART用)。信息增益的概念来自信息论,核心思想是:切分之后,数据的“纯度”提升得越多,这个特征就越好。基尼系数的思路类似,也是衡量数据的不纯度。
这么多标准怎么选呢?实践中98%的情况下,用基尼系数就够了。决策树的优点是解释性极强,训练完之后你可以把整棵树画出来,每一步判断都清清楚楚,这在需要向业务方解释模型的场景下特别好用。它也不需要做特征归一化,数值型和类别型特征都能处理,这点比线性模型省事很多。
但决策树最大的问题在于容易过拟合。如果你不限制树的深度,它会一直生长,直到每一个叶子节点都只包含一个样本为止。这样训练集准确率100%,但测试集上表现一塌糊涂。控制过拟合的办法就是加约束:限制最大深度、限制叶子节点的最小样本数、设置分裂所需的最小样本数。
用Sklearn实现决策树:
from sklearn.tree import DecisionTreeClassifier from sklearn.tree import plot_tree import matplotlib.pyplot as plt model = DecisionTreeClassifier( max_depth=5, # 限制最大深度,防止过拟合 min_samples_split=10, # 内部节点至少需要10个样本才继续分裂 min_samples_leaf=5, # 叶子节点至少包含5个样本 random_state=42 ) model.fit(X_train, y_train) # 可视化树结构 plt.figure(figsize=(20, 10)) plot_tree(model, filled=True, feature_names=feature_names, class_names=['0', '1']) plt.show()这里需要特别强调一下max_depth这个参数。我在教学中发现,很多同学喜欢把max_depth设得很大,觉得树越深模型越准确,这是典型的误解。模型复杂度和模型效果之间不是简单的正比关系,而是倒U型关系。树太浅,欠拟合,学不到规律;树太深,过拟合,记住了太多噪声。找到中间那个平衡点,才是真正考验功夫的地方。
3.2 随机森林:一群树的智慧
接着上面的问题说,既然单棵决策树容易过拟合,那怎么办?一个非常朴素的想法是:一棵树容易犯错,那一百棵树一起投票,总该靠谱了吧?这就是随机森林的核心思想:用很多棵决策树组成的“森林”来集体决策,同时引入随机性,让每棵树都不完全一样。
随机性体现在两个地方,这也是“随机森林”这个“随机”二字的来源。第一是样本随机:训练每一棵树的时候,从原始数据中有放回地抽取一部分样本,这叫Bootstrap抽样。这样每一棵树用的数据都不完全一样,大家看到的视角不同,综合起来就更全面。第二是特征随机:在每个节点分裂时,不是从所有特征中找最优,而是随机抽一部分特征,再从这些特征中找最优。这样保证了每棵树的侧重点不同,减少了树与树之间的相关性。
为什么要刻意让树之间有差异呢?这个道理可以用一个生活场景来说。如果你要做一个重大决定,你会只听一个人的意见,还是听十个背景不同、经历不同的朋友一起商量?群体决策在大多数情况下都比个体决策更可靠,前提是这个群体内部要有足够多的不同观点。如果十个人都是同一个思维模式,那跟听一个人意见没什么区别。
随机森林在绝大多数表格数据上表现都相当好,被称为“没有超参数也能用”的算法,虽然这话有点夸张,但也说明了它的鲁棒性。它还能输出特征重要性排名,告诉你说这个模型主要靠哪些特征做判断,这个功能在业务分析中特别实用,可以用来发现影响结果的关键因素。
代码示例:
from sklearn.ensemble import RandomForestClassifier model = RandomForestClassifier( n_estimators=200, # 树的数量,通常100-500之间 max_depth=10, max_features='sqrt', # 每个节点随机抽取的特征数 random_state=42, n_jobs=-1 # 使用所有CPU核心,加速训练 ) model.fit(X_train, y_train) print("训练集准确率:", model.score(X_train, y_train)) print("测试集准确率:", model.score(X_test, y_test)) # 特征重要性 importance = model.feature_importances_ for i, imp in enumerate(importance): print(f"特征{i}: {imp:.4f}")这里n_estimators是个值得聊的参数。树的数量太少,比如50棵以下,模型的稳定性不够,每次运行结果差异比较大;但也不是越多越好,超过500棵之后,提升非常小,反而增加训练和预测的时间。折中来看,200到300棵是性价比最高的区间。
随机森林还有一个隐藏优势,它不需要太精细的调参,用默认参数往往就能达到不错的基线效果。做项目的时候,我经常拿随机森林的结果作为“及格线”,如果辛辛苦苦调出来的神经网络模型还不如随机森林,那说明问题不在模型复杂度上,而可能出在特征工程或数据质量上。
4. 距离与概率驱动的算法
4.1 K近邻:靠投票完成分类
K近邻(K-Nearest Neighbors,KNN)可能是机器学习里最直观的算法了,一句话就能说清楚:一个样本的类别,由离它最近的K个样本投票决定。比如说K取5,那就找到离这个待预测样本最近的5个已知样本,看这5个样本里哪一类占多数,就把它判为哪一类。
这个算法听起来简单到不像机器学习,但它背后有一个非常深刻的假设:相似的样本在空间中距离接近。这个假设在很多时候是成立的。比如你想判断一个人是不是经常运动,那你看看他身边最亲近的5个朋友是不是都在运动,大概率就能猜个八九不离十。
KNN有几个关键的细节需要注意。第一个是K值的选择。K太小,比如K=1,模型很容易受到单个噪声点的影响;K太大,比如K=100,会把太远的样本也拉进来投票,导致分类边界过于平滑。通常的做法是用交叉验证来选K,在3到20之间试。第二个是距离度量方式。最常用的是欧氏距离,但如果特征维度很高,欧氏距离的效果会变差,这时可以考虑余弦相似度或曼哈顿距离。第三个是特征归一化,这一点尤其重要。KNN是靠距离来判断相似度的,如果某个特征的数值范围特别大,它会主导整个距离计算,其他特征就形同虚设了。归一化到同一个量纲,才能保证每个特征公平地参与投票。
KNN最明显的缺点就是计算量大。因为它“懒惰学习”的特性——训练阶段几乎不做什么,只是把所有数据存下来,真正的工作都在预测阶段做——如果训练集有100万个样本,每预测一个新样本,都要计算它到100万个样本的距离,这个开销相当大。所以KNN适合中小规模的数据集,不适合大规模生产环境。
代码示例:
from sklearn.neighbors import KNeighborsClassifier from sklearn.preprocessing import StandardScaler from sklearn.model_selection import GridSearchCV # 先归一化,KNN对尺度敏感 scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test) # 用网格搜索找最优K param_grid = {'n_neighbors': range(3, 20)} grid = GridSearchCV( KNeighborsClassifier(), param_grid, cv=5, # 5折交叉验证 scoring='accuracy' ) grid.fit(X_train_scaled, y_train) print("最优K值:", grid.best_params_) print("交叉验证最佳准确率:", grid.best_score_) print("测试集准确率:", grid.score(X_test_scaled, y_test))这里的StandardScaler就是做归一化的标准做法,把每个特征变成均值为0、标准差为1的分布。我见过太多人用KNN不归一化,结果是数值范围大的特征完全主导了预测结果,模型准确率上不去还找不到原因,最后只能怪数据集不好。
4.2 朴素贝叶斯:用概率做判断
朴素贝叶斯是另一个看起来简单但非常强大的算法,它基于贝叶斯定理。贝叶斯定理用一句话说就是:当你获得新信息后,你的信念应该更新。放到分类问题里就是:先计算每个类别的先验概率(在所有样本中,这个类别占多少比例),然后根据当前样本的特征,计算后验概率,最后选择后验概率最大的类别。
“朴素”这个字眼是关键。它的意思是假设所有特征之间相互独立。这个假设在现实中几乎不可能完全成立——比如在文本分类里,“价格”和“便宜”两个词就不是独立的,它们经常一起出现。但神奇的是,即使这个假设被违背了,朴素贝叶斯在很多场景下依然表现得非常好,尤其是在文本分类、垃圾邮件过滤这类问题上。
朴素贝叶斯为什么面对“错误假设”还能表现好?一个解释是,虽然特征独立性假设不成立,但分类决策需要的往往是各类别后验概率的相对大小关系,而这种大小关系在特征相关性不是特别强的时候,依然能被准确估计。也就是说,它不一定能给出准确的概率,但能给出正确的排名。
朴素贝叶斯的优势非常突出:训练和预测速度极快、对高维数据表现好、在小样本场景下表现稳定、不容易过拟合。它需要调节的超参数也极少,基本上是“开箱即用”的算法。但它也有硬伤:假设特征独立,在特征强相关的场景下效果会打折扣;另外它只适用于离散特征或者需要把连续特征离散化的情况。
代码示例:
from sklearn.naive_bayes import MultinomialNB from sklearn.feature_extraction.text import CountVectorizer # 假设有一组文本数据和标签 texts = ["价格便宜质量好", "发给我价格吧", "质量真的很差", "这家店服务态度不错"] labels = [1, 1, 0, 1] # 1表示好评,0表示差评 # 文本向量化:把文本变成词频矩阵 vectorizer = CountVectorizer() X = vectorizer.fit_transform(texts) model = MultinomialNB() model.fit(X, labels) # 预测新文本 new_text = ["价格便宜吗"] X_new = vectorizer.transform(new_text) print("预测结果:", model.predict(X_new))这里的MultinomialNB是朴素贝叶斯的其中一个变体,适合处理离散计数的特征(比如词频)。还有高斯朴素贝叶斯GaussianNB,适合处理连续特征;伯努利朴素贝叶斯BernoulliNB,适合处理0/1二值特征。选哪个变体取决于数据形态,不要一股脑都用MultinomialNB。
5. 边界与聚类:SVM 与 K-Means
5.1 支持向量机:寻找最大边距的超平面
支持向量机(Support Vector Machine,SVM)在深度学习流行之前,是机器学习界的“当红花旦”。它解决的是分类问题,核心思想非常优美:在两类数据之间画一条分界线,让这条线离两边最近的数据点都尽可能远。这个“离两边最近的数据点”就是支持向量,这条线在二维空间叫线,在高维空间叫超平面。
为什么非要“最大化边距”呢?因为边距越大,模型的泛化能力通常越强,对新样本的分类就越稳健。你可以想象一下:如果你的分类边界紧贴着训练数据,那稍微来个之前没见过的新样本,很可能就落在了边界另一边,分类就错了;但如果边界离训练数据很远,两边都留有充足的“缓冲区”,新样本即使有点偏差,也大概率能被正确分类。
SVM另一个杀手锏是核函数(Kernel Function)。当数据在当前维度下线性不可分时,核函数可以把数据映射到更高维的空间,在高维空间里找到一个超平面把它们分开。最常见的核函数有线性核、多项式核、高斯径向基核(RBF)。RBF核在实践中最常用,因为它能处理绝大多数非线性问题,而且只有一个超参数gamma需要调节。
SVM的优点是:在高维空间表现优秀,即使特征数量远大于样本数量也能工作;泛化能力强,不容易过拟合。缺点是:训练时间较长,尤其在大数据集上;对参数(C和gamma)敏感,需要仔细调参;模型可解释性较弱。
用Sklearn实现SVM:
from sklearn.svm import SVC from sklearn.pipeline import Pipeline from sklearn.model_selection import GridSearchCV from sklearn.preprocessing import StandardScaler # 注意:SVM对特征尺度非常敏感,必须先归一化 pipeline = Pipeline([ ('scaler', StandardScaler()), ('svm', SVC()) ]) param_grid = { 'svm__C': [0.1, 1, 10, 100], 'svm__gamma': [0.01, 0.1, 1], 'svm__kernel': ['rbf'] } grid = GridSearchCV(pipeline, param_grid, cv=5, scoring='accuracy', n_jobs=-1) grid.fit(X_train, y_train) print("最优参数:", grid.best_params_) print("测试集准确率:", grid.score(X_test, y_test))这里的Pipeline(管道)是用来串联多个步骤的工具,它保证归一化操作是在每一折交叉验证内部独立进行的,避免了“数据泄露”。很多新手容易犯一个错误:先对整个数据集做归一化,再划分训练集和测试集做交叉验证。这会导致测试集的信息泄露到训练过程中,得到的评估结果虚高。
5.2 K-Means:无监督的自动分组
前面讲的所有算法都属于监督学习——训练数据里有“标准答案”,算法学习的是从特征到答案的映射。K-Means则是完全不同的逻辑,它是聚类算法,属于无监督学习:训练数据里没有任何标签,算法要自己发现数据中的结构,把相似的样本自动分成K组。
K-Means的工作过程是这样:先随机选K个点作为初始中心;然后计算每个样本到这K个中心的距离,把样本归到最近的那个中心所在的类;接着重新计算每个类的质心(组内所有样本的平均位置),把它作为新的聚类中心;然后重复“分配样本-更新中心”这个过程,直到聚类中心不再变化或达到最大迭代次数。
用生活化的例子来说,K-Means就像管理员给书分类:先随便放几个空书架的标签在房间里,然后把每本书放到离它最近的那个标签对应的书架上,全部放完后发现有些书架的书分布不太均匀,于是移动标签的位置,让它正好位于一堆书的“中心点”,再次重新分配书本,反复几次之后,每本书都会稳定地待在离自己最近的书架上了。
K-Means最大的难点在于选择K值。这个K值事先是不知道的,需要结合业务和算法来定。常用的方法是“肘部法则”:画一条“聚类数K vs 簇内平方和”的曲线,找到曲线的“肘部”位置,那个K值就是推荐值。还有一个更实用但计算量稍大的方法是轮廓系数法,轮廓系数越接近1,说明聚类效果越好。
from sklearn.cluster import KMeans import matplotlib.pyplot as plt # 用肘部法则选K inertia = [] K_range = range(1, 11) for k in K_range: kmeans = KMeans(n_clusters=k, random_state=42, n_init=10) kmeans.fit(X) inertia.append(kmeans.inertia_) plt.plot(K_range, inertia, 'bo-') plt.xlabel('K') plt.ylabel('Inertia') plt.title('肘部法则确定K值') plt.show() # 选定K后训练 kmeans = KMeans(n_clusters=3, random_state=42, n_init=10) labels = kmeans.fit_predict(X) print("聚类标签:", labels) print("聚类中心:", kmeans.cluster_centers_)这里有个n_init参数,意思是算法会从随机位置开始跑10次,取效果最好的那个结果。千万不要把n_init设成1,K-Means的初始中心选择是随机的,不同的初始点可能收敛到不同的局部最优解,多跑几次取最优是保证结果稳定的基本操作。Sklearn新版默认n_init=10,但有些老代码里没设这个参数,跑出来的结果每次都不一样,还以为是代码bug,其实只是初始中心随机性导致的。
K-Means也有明显的局限。第一,它假设每个簇是凸形的,对非凸形状的簇效果不好。第二,它基于距离计算,对异常值敏感。第三,它不适用于类别型特征,只能处理数值型特征。在很多实际业务中,K-Means主要作为探索性分析的工具,用来初步理解数据结构,而不是作为最终解决方案。
6. 特征工程与集成学习
6.1 PCA:降维的经典工具
主成分分析(Principal Component Analysis,PCA)严格来说不算一个“模型”,它是特征工程里最经典的降维工具。它的作用是在保留数据大部分信息的前提下,把高维数据压缩到低维。为什么需要降维?因为数据维度太高会带来很多问题:计算量增大、存储成本增高、模型容易过拟合,还会出现“维度灾难”——在高维空间里,样本之间的距离会趋于相似,很多基于距离的算法会完全失效。
PCA的原理用大白话讲是这样的:你要在n维空间里找一个新方向,使得所有样本投影到这个方向上之后,方差(数据的离散程度)最大。这个方向就是第一主成分。然后再找一个与它正交的方向,使剩余方差最大,这就是第二主成分。以此类推。因为方差大的方向承载的信息多,方差小的方向承载的往往是噪声,所以我们可以只保留前几个主成分,丢掉后面的,实现降维。
用PCA有几点必须注意。第一是数据必须先归一化,否则数值范围大的特征会主导主成分方向,PCA就不是提取“数据的结构”了,而是提取“数值范围的分布”。第二是PCA后得到的特征是原有特征的线性组合,可解释性会大打折扣。第三是PCA是无监督的方法,它不关心标签,所以降维后的特征不一定对分类任务最有帮助。如果是为了提升分类效果,可以考虑有监督的降维方法,比如LDA(线性判别分析)。
实操中PCA还有个有趣的玩法:用PCA做数据可视化。高维数据没法直接画图,但用PCA降到2维或3维之后,就可以用散点图直观地看数据的分布结构。我做过一个项目,把用户行为特征用PCA降维后画出来,发现不同群体在二维平面上分得很开,这个发现直接指导了后续的精细化运营策略。
代码示例:
from sklearn.decomposition import PCA from sklearn.preprocessing import StandardScaler # 先归一化 scaler = StandardScaler() X_scaled = scaler.fit_transform(X) # 查看每个主成分能解释多少方差 pca_full = PCA() pca_full.fit(X_scaled) explained_variance = pca_full.explained_variance_ratio_ # 累计解释方差比达到95%的维度数量 cumsum = 0 n_components = 0 for i, var in enumerate(explained_variance): cumsum += var n_components = i + 1 if cumsum >= 0.95: break print(f"保留95%信息需要 {n_components} 个主成分") # 降维 pca = PCA(n_components=n_components) X_pca = pca.fit_transform(X_scaled) print("降维前:", X_scaled.shape) print("降维后:", X_pca.shape)这里“累计解释方差比达到95%”是一个常用的经验准则,但它不是绝对的标准。在有些场景下,保留80%就够了;在另一些场景下(比如金融风控追求精确),可能需要保留99%以上。关键在于你要清楚自己降维的目的是什么。为了可视化,2到3个维度就够;为了加速训练,可以适当压缩;为了压缩存储,就需要在信息和空间之间权衡。
6.2 AdaBoost:把弱学习器变成强学习器
集成学习(Ensemble Learning)的思想在前面随机森林里已经提到过了,AdaBoost是集成学习里另一个经典代表。不同之处在于,随机森林是“并行”的——所有树同时构建,互相独立;AdaBoost是“串行”的——每一轮训练都关注上一轮犯过的错误,按顺序迭代。
AdaBoost的核心思路是这样的:先用原始数据训练一个弱学习器(通常是深度很浅的决策树,也叫决策树桩),然后增大被这个学习器分错的样本的权重,减少分对的样本的权重,再用调整权重后的数据训练下一个弱学习器。如此反复迭代,最后把这一堆弱学习器加权组合起来,权重大小取决于每个学习器的表现。
打个比方,AdaBoost就像老师在给学生出题:第一套题,大家考完了,发现有些知识点大家普遍容易错;第二套题就专门出这些容易错的知识点,让大家多练;练完后,那些被反复练熟的知识点在最终考试里的比重就会相应提高。通过这样有针对性的反复强化,整体水平就上来了。
这样做的好处是:即使每个弱学习器只有比随机猜测好一点点的能力,组合起来也能变成一个非常强的模型。但在实际应用时,AdaBoost对异常值和噪声比较敏感。因为异常值的样本会被赋予越来越高的权重,导致后续的学习器过度关注这些异常点。如果数据集里有比较多噪声,要谨慎使用AdaBoost,这种情况下随机森林或梯度提升树(Gradient Boosting)可能是更好的选择。
from sklearn.ensemble import AdaBoostClassifier from sklearn.tree import DecisionTreeClassifier # 基学习器用深度为1的决策树桩 base_estimator = DecisionTreeClassifier(max_depth=1) model = AdaBoostClassifier( estimator=base_estimator, # 新版Sklearn参数名是estimator n_estimators=100, learning_rate=1.0, random_state=42 ) model.fit(X_train, y_train) print("AdaBoost测试集准确率:", model.score(X_test, y_test)) # 查看每个弱学习器的权重 print("弱学习器权重:", model.estimator_weights_)learning_rate是AdaBoost另一个重要参数,控制每轮迭代对最终结果的贡献。learning_rate越小,模型学习越保守,需要更多的弱学习器;learning_rate太大,模型可能过于激进,导致波动。通常建议learning_rate设小一点(0.1到1之间),配合较多的n_estimators使用。
需要提醒一下,Sklearn 1.2版本之后,AdaBoostClassifier的base_estimator参数改名为estimator了,如果你用的是新版本还按旧参数名写,会直接报错。这种API变动是Sklearn每个大版本都可能出现的情况,遇到报错先检查一下版本号和API说明,别急着怀疑自己的代码逻辑。
7. 从传统算法到深度学习的升级路径
7.1 感知机到神经网络
前面讲的九个传统算法,如果都掌握了,再来看深度学习,你会发现很多东西是相通的。深度学习的基础单位——感知机(Perceptron),本质上就是逻辑回归的简化版:把输入特征加权求和,经过一个激活函数得到输出。多个感知机堆叠成多层结构,就是神经网络。
为什么多层结构能解决单层解决不了的问题呢?单层感知机本质上只能学习线性决策边界,就像逻辑回归只能画一条直线来分类。但如果把多个感知机分层连接起来,每一层都在学习上一层的特征组合,模型就能表达非常复杂的非线性关系。比如一个圆形分布的数据,单层感知机无论怎么画一条直线都分不开,但两层神经网络可以学出一个非线性边界把内外分开。
深度学习之所以后来居上,把传统算法甩在后面,靠的就是这种“多层抽象”的能力。第一层学的是边缘、纹理这些底层特征;第二层把边缘组合成形状、局部模式;第三层再把形状组合成更抽象的概念。在大语言模型里,这种层级抽象体现得更加充分:低层学到词法和语法,中层学到语义关系,高层学到跨句子的推理能力。
但这也带出了深度学习的代价:计算量巨大、需要海量数据、超参数多到让人头皮发麻。传统算法的好处恰恰是:数据量小也能用、可解释性强、训练速度快、对硬件要求低。我在实际项目里见过很多团队,一上来就想用深度神经网络,其实他们的数据和问题规模用随机森林或XGBoost解决就绰绰有余了,硬上深度模型反而效果不好。
选型的基本原则是这样的:数据量小、特征维度中等(几十到几百)、业务要解释模型逻辑时,优先传统算法。数据量大(百万级)、输入是图像或文本这类非结构化数据、计算资源充足时,深度神经网络是更好的选择。
7.2 CNN、RNN 与 Transformer 的基础概念
几个最重要的深度学习模型结构也需要了解一下。CNN(卷积神经网络)是专门为图像数据设计的,它的核心操作是“卷积”,本质上是在图像上滑动一个小窗口(比如3x3的网格),逐区域提取特征。这样就利用了图像的空间局部性——相邻像素之间的联系比远处像素更紧密。
RNN(循环神经网络)是为序列数据设计的,比如文字、语音、股票价格。它的核心特点是“记忆”:处理序列的每一步,网络不仅看当前的输入,还看前一步的记忆状态。这样就能捕捉时间上的依赖关系。但RNN有个出名的问题——梯度消失/梯度爆炸,导致它很难记住很早期的信息。为了解决这个问题,出现了LSTM(长短期记忆网络)和GRU(门控循环单元),通过引入“门”机制来控制信息的记忆和遗忘。
Transformer则是当前大语言模型的基础架构,它的核心机制叫“自注意力”(Self-Attention)。自注意力让序列中的每个位置都能直接关注到序列中所有其他位置,不管距离有多远。这种“全连接”的注意力机制解决了RNN无法处理长距离依赖的问题,而且整个计算过程可以完全并行化,训练效率远高于RNN。
理解了这些背景之后,你会发现“人工智能十大基础算法”不是孤立的知识点,它们是大模型时代的基石。比如:大模型在微调和推理过程中的很多优化问题,本质上还是梯度下降和损失函数的问题;Transformer里的注意力机制距离的计算方式和KNN的距离计算底层逻辑相通;PCA的思想被用在CLIP等模型的可解释性分析里。地基打得牢,后面盖高楼才有底气。
8. 新手常见问题与实操排坑
8.1 入门阶段最容易踩的五个理解误区
第一个误区是混淆“训练集准确率高”和“模型好”。我见过太多人拿着训练集准确率90%的模型欣喜若狂,结果在测试集上只有60%。这就是典型的过拟合。判断模型好坏,永远看它在未见过的数据上的表现。交叉验证的意义也在于此,它模拟的是“模型在没见过的数据上表现如何”这个最关键的问题。
第二个误区是不做特征工程,上来就套模型。很多初学者以为机器学习就是调包,拿到数据直接敲fit方法完事。其实特征工程在整个机器学习流程里的重要性远大于模型选择。同样的数据,认真做特征工程后可能用线性回归就能达到80%的准确率;不做特征工程,即使用随机森林可能也只有70%。
第三个误区是忽视数据不平衡问题。比如一个二分类任务,正类样本占比95%,负类只占5%。如果直接训练模型,模型只需要把所有样本都预测成正类,准确率就有95%。但这个模型完全没有实际价值。处理数据不平衡的方法有很多:过采样、欠采样、调整类别权重、使用F1分数等指标评估。核心思想是想办法让模型对少数类样本敏感。
第四个误区是归一化的时机搞错。归一化必须在划分训练集和测试集之后,只在训练集上计算归一化参数(均值和标准差),然后用训练集的参数去变换测试集。如果把所有数据放一起归一化再划分,会发生数据泄露。这个错误极其隐蔽但后果严重,会让评估结果虚高。
第五个误区是一次性把大招全用上。我理解新手的好奇心,但用网格搜索调参、用集成学习、堆特征、做交叉验证,这些手段不是越多越好。最佳实践是:先用最简单的模型和默认参数跑通全流程,确认没有bug,然后逐步增加复杂度。每增加一个复杂度,都要确保带来了明确的收益提升,否则就回退。
8.2 算法作业的三个核心排坑技巧
如果你是在校学生,正被“算法设计与分析实验”或者“人工智能大作业”折磨,这里有几个从实际教学和作业批改中总结出来的技巧。
第一,先画数据分布图,再写模型。这是我最强调的习惯。拿到任何数据集,第一步不是敲代码,而是用matplotlib或seaborn画几个关键图:各类别的分布、特征之间的相关性、是否存在明显的离群点。这些图会指导你后续所有决策:要不要归一化、特征是否要删掉、用什么模型合适。我批改作业时,一眼就能看出哪个学生是认真做了数据探索的,哪个是直接跑示例代码的。
第二,建立“基础模型-改进-调参”的标准流程。不要一上来就GridSearchCV,先跑一个baseline模型,把这个baseline的结果记录下来。然后尝试特征工程、模型改进,每一次改动都记录效果变化。最后再针对表现最好的模型调参。这样做的好处是,大作业报告好写,而且你能准确知道每一步改动带来多少提升——这个信息在面试聊项目时会很有价值。
第三,代码报错先读最后一行,再逆推。机器学习代码报错通常信息量很大,但新手容易一看红屏就慌。报错信息最后一行是错误类型的核心描述,先搞清楚类型是什么。如果是ValueError,通常是数据形状或取值有问题;如果是AttributeError,通常是API版本变了或参数名不对;如果是ConvergenceWarning,通常是模型没收敛,需要增大迭代次数或归一化数据。按这个思路排查,90%的问题几分钟就能定位。
最后分享一个我每年带新人都会说的经验:学这十个基础算法,不要贪快,每个算法至少完成三件事——一是用手写代码的方式实现一遍最朴素版本,不用任何机器学习库,只靠numpy和数学公式;二是用Sklearn实现一遍标准版本,跑通流程;三是找一个真实数据集应用一遍,把结果展示出来。手写实现是最痛苦但收获最大的步骤,它能逼你把数学公式真正看懂。等这十种算法都过完一遍,你会发现看任何新模型、新论文,都不会再觉得一头雾水了。