跳转至

09 关于机器学习,你需要了解的基本概念(二)

你好,我是独行。

上一节课我们了解了机器学习的基本概念,学习了线性回归和逻辑回归,相信你对机器学习有了初步理解,这节课我们继续讲解机器学习的经典算法,先从决策树开始。

经典算法

决策树

决策树是一种常用的机器学习算法,用于分类和回归任务。通过从数据中学习决策规则来预测目标变量的值。想象你在玩一个“是或否”的猜谜游戏,每次你只能问一个问题,对方只能回答是或否,你的目标就是用最少的问题猜出对方心中的答案。在实际应用中,决策树有很多场景,比如客户分类、信用评分、医疗诊断等等,下面我举一个简单的例子。

我们要根据天气情况、温度和风速来决定进行什么活动,比如宅在家还是出去玩,我们准备一些简单的数据集。

  • 天气状况:晴天(0)、阴天(1)、雨天(2)
  • 温度:低(0)、中(1)、高(2)
  • 风速:无风(0)、微风(1)、强风(2)

活动:

  • 去野餐(0)
  • 去博物馆(1)
  • 在家看书(2)

我们看一下使用sklearn库提供的决策树算法和模型的示例代码。

from sklearn.tree import DecisionTreeClassifier, plot_tree
import matplotlib.pyplot as plt
import numpy as np

# 创建数据集
X = np.array([
    [0, 2, 0],  # 晴天,高温,无风
    [1, 1, 1],  # 阴天,中温,微风
    [2, 0, 2],  # 雨天,低温,强风
    # ... 添加更多样本以增加模型的准确性
])
y = np.array([0, 1, 2])  # 分别对应去野餐、去博物馆、在家看书

# 初始化决策树模型,设置最大深度为5
clf = DecisionTreeClassifier(max_depth=5, random_state=42)

# 训练模型
clf.fit(X, y)

# 可视化决策树
plt.figure(figsize=(20, 10))
plot_tree(clf, filled=True, feature_names=["天气状况", "温度", "风速"], class_names=["去野餐", "去博物馆", "在家看书"], rounded=True, fontsize=12)
plt.show()

程序运行结果如下:
图片

我来简单解释下这几个信息指标。

  • Gini指数:一个衡量节点纯度的指标,用于分类问题。一个节点的Gini指数越低,表示这个节点包含的样本越倾向于属于同一个类别,0表示所有样本都属于同一类别,即完全纯净。
  • samples:表示到达这个节点的样本数量。这个数值提供了关于树的分支如何基于数据集进行分割的直观理解,也可以帮助我们评估决策树各个部分的数据覆盖范围。
  • value:表示这个节点每个类别的样本数量。对于二分类问题,它可能显示为[value1, value2],代表了属于第一类和第二类的样本数量。这个数组提供了节点分类分布的具体信息,有助于我们了解在每个决策节点处数据是如何被分割的。
  • class:代表了在当前节点样本中占多数的类别。如果一个节点是叶节点,那么这个类别就是这个节点的预测类别。即使在非叶节点,class也可以显示该节点样本的主要类别。

通过这些信息,我们不仅可以理解决策树是如何根据特征分割数据的,还可以评估树的深度、每个节点的决策质量,以及模型是否可能出现过拟合,比如某些叶节点只有很少的样本,具体原因我一会儿就会讲到。这些指标也是调整模型参数来优化模型性能的重要依据,比如树的最大深度、节点分割的最小样本数等。

随机森林

随机森林是一种流行的机器学习算法,属于集成学习家族。简单来说,随机森林通过构建多个决策树来进行预测,其基本思想是集体智慧——单个模型可能有限,但多个模型集合起来可以作出更好的判断。随机森林算法的关键在于它的随机性。

  1. 样本随机性:每棵树训练的数据通过从原始数据中进行随机抽样得到,这种方法称为自助采样。
  2. 特征随机性:在分裂决策树的节点时,算法会从所有特征中随机选取一部分特征,然后只在这些随机选取的特征中寻找最优分裂特征。

这种随机性使随机森林模型具有很高的准确性,同时也能防止模型过拟合。这里我解释下什么叫过拟合?过拟合是机器学习领域一个非常重要的概念,简单来讲就是因为训练数据或者模型参数导致模型缺乏泛化能力。

这里泛化能力可以理解为通用能力。我们训练模型的目的是希望模型解决通用问题。比如训练一个模型,用来识别一个照片中有没有狗。喂了1000张狗的照片进行训练,结果识别的时候只有尾巴直直的狗可以被识别出来。原因是有一个参数描述尾巴是直的还是弯的,这个参数的存在使模型额外识别狗的某一个特征,失去了泛化能力,导致了过拟合问题。

前面我们讲的决策树为什么会出现过拟合呢?如果决策条件中有一个非常不通用的条件,分类后这个分支节点只有一个样本,说明这个决策没有通用性(泛化能力),那么这个决策树模型就会存在过拟合问题。

随机森林怎么解决决策树过拟合的问题呢?我们先来看下随机森林的工作原理。

  1. 从原始数据集中随机抽样选取多个子集。
  2. 对每个子集训练一个决策树。
  3. 每棵树独立进行预测。
  4. 最终预测结果是所有的树预测结果的投票或平均。

简易代码如下:

import matplotlib.pyplot as plt
from sklearn.tree import plot_tree
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris

# 加载数据集
iris = load_iris()
X, y = iris.data, iris.target

# 训练随机森林模型
rf = RandomForestClassifier(n_estimators=3, random_state=42) # 使用3棵树以便于可视化
rf.fit(X, y)

# 绘制随机森林中的决策树
fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(20, 5), dpi=100)
for index in range(0, 3):
    plot_tree(rf.estimators_[index], 
              feature_names=iris.feature_names, 
              class_names=iris.target_names, 
              filled=True, 
              ax=axes[index])

    axes[index].set_title(f'Tree {index + 1}')

plt.tight_layout()
plt.show()

程序运行结果如下:

图片

这3棵树会通过投票的方式来配合使用。具体来说,对于分类问题,每棵树在接收到一个输入样本后都会独立做出一个预测。最终,整个随机森林的预测结果是基于所有树的预测结果进行多数投票得到的。也就是说,被最多棵树预测为某一类别的类别将成为随机森林的最终预测结果。

具体步骤如下:

  1. 预测:对于每个输入样本,随机森林中的每棵树都会独立做出自己的预测。
  2. 投票:收集所有树对每个样本的预测结果,并对这些结果进行计数。
  3. 决定最终预测结果:对于每个样本,被预测次数最多的类别将成为随机森林的最终预测结果。

在实际应用过程中,我们可以构造更多的决策树进行预测,比如100,甚至更多,这样可以使模型更准确更稳定。

支持向量机

支持向量机(Support Vector Machine,简称SVM)是一种强大的分类算法,在数据科学和机器学习领域广泛应用。SVM的核心思想是,找到一个最优的决策边界,或者称为“超平面”,这个边界能够以最大的间隔将不同类别的数据分开。这里有几个关键点需要好好理解一下。

  1. 超平面:在二维空间中,这个边界就是一条线;在三维空间中,是一个平面;而在更高维度的空间中,我们称之为“超平面”。这个超平面的任务就是尽可能准确地分隔开不同类别的数据点。
  2. 最大间隔:SVM不仅仅寻找一个能够将数据分类的边界,它寻找的是能够以最大间隔分开数据的边界。这个间隔是指不同类别的数据点到这个边界的最近距离,SVM试图使这个距离尽可能大。直观上,这样的边界更能抵抗数据中的小变动,提高模型的泛化能力。
  3. 支持向量:决定这个最优超平面位置的几个关键数据点被称为支持向量。它们是最靠近决策边界的点,实际上这个最大间隔的边界就是通过这些点来确定的。
  4. 核技巧:当数据不是线性可分时,也就是说无法通过一个直线或平面来分隔,SVM可以利用所谓的核技巧将数据映射到一个更高维的空间,在这个空间中数据可能是线性可分的。这使得SVM在处理非线性数据时非常强大。

简单来说,你可以将SVM想象成一个尽可能在不同类别间画一条粗的、清晰的界线,而这条界线是由距离它最近的几个点(支持向量)决定的。这种方法使得分类决策不仅仅依赖于数据的分布,而且具有很好的泛化能力,能够应对未见过的新数据。

我们简单看下示例代码:

from sklearn import datasets
from sklearn.svm import SVC
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
import numpy as np

# 生成模拟数据
X, y = datasets.make_blobs(n_samples=50, centers=2, random_state=6)

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

# 创建 SVM 模型
model = SVC(kernel='linear')
model.fit(X_train, y_train)

# 绘制数据点和分类边界
plt.figure(figsize=(8, 6))
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')

# 绘制决策边界
ax = plt.gca()
xlim = ax.get_xlim()
ylim = ax.get_ylim()

# 创建网格点
xx = np.linspace(xlim[0], xlim[1], 30)
yy = np.linspace(ylim[0], ylim[1], 30)
YY, XX = np.meshgrid(yy, xx)
xy = np.vstack([XX.ravel(), YY.ravel()]).T
Z = model.decision_function(xy).reshape(XX.shape)

# 绘制决策边界和间隔
ax.contour(XX, YY, Z, colors='k', levels=[-1, 0, 1], alpha=0.5, linestyles=['--', '-', '--'])
plt.scatter(model.support_vectors_[:, 0], model.support_vectors_[:, 1], s=100, linewidth=1, facecolors='none', edgecolors='k')
plt.title("支持向量机分类示例")
plt.xlabel("特征1")
plt.ylabel("特征2")
plt.show()

程序运行结果如下:

图片

这个例子比较简单,我们使用一个线性核的SVM模型来分类模拟生成的数据点。数据点用橙色和黄色表示,分别代表两个不同的类别。

图中的黑色实线表示决策边界,这是SVM找到的最佳超平面,用于将两类数据分开。虚线表示决策边界的边缘,这些边缘之间的区域是模型的间隔。支持向量在图中已经用黑色圆圈圈出,它们是离决策边界最近的几个点,对于确定决策边界至关重要。

这个例子通过直线就可以完成分类,假设这些点在一个平面上混合在一起,那么通过直线是无法进行分类的,这时候我们需要进行升维,再加一个维度,在三维空间里,通过一个面进行分类。在实际应用中,SVM可以通过选择不同的核函数来处理更复杂的数据集。

神经网络

神经网络应该说是目前最火的机器学习算法模型,现在主流的大部分大模型都是基于深度神经网络的。主要设计思路是模仿人的大脑,由许多小的、处理信息的单位组成,这些单位就是神经元,各神经元之间彼此连接,每个神经元可以向其他神经元发送和接收信号。通过这种方式,神经网络能够执行各种复杂的计算任务,比如图像和语音识别、自然语言处理以及许多其他类型的机器学习任务。

一个简单的神经网络包含三层:输入层、隐藏层和输出层。一般情况下,一个神经网络输入层和输出层仅有一层,隐藏层可以有多层,不过也有特殊情况,我们后面再讲。

  • 输入层:接收原始数据输入,例如图片的像素值或者一段文本的编码。
  • 隐藏层:处理输入数据,可以有一个或多个隐藏层。隐藏层的神经元会对输入数据进行加权和,应用激活函数,这个过程可以捕捉输入数据中的复杂模式和关系。
  • 输出层:根据隐藏层的处理结果,输出一个值或一组值,代表了神经网络的最终预测结果。

我们来看一个示例。

import matplotlib.pyplot as plt

# 创建一个简单的神经网络图,并调整文字标签的位置
def plot_neural_network_adjusted():
    fig, ax = plt.subplots(figsize=(10, 6))  # 创建绘图对象

    # 输入层、隐藏层、输出层的神经元数量
    input_neurons = 3
    hidden_neurons = 4
    output_neurons = 2

    # 绘制神经元
    layer_names = ['输入层', '隐藏层', '输出层']
    for layer, neurons in enumerate([input_neurons, hidden_neurons, output_neurons]):
        for neuron in range(neurons):
            circle = plt.Circle((layer*2, neuron*1.5 - neurons*0.75 + 0.75), 0.5, color='skyblue', ec='black', lw=1.5, zorder=4)
            ax.add_artist(circle)

    # 绘制连接线
    for input_neuron in range(input_neurons):
        for hidden_neuron in range(hidden_neurons):
            line = plt.Line2D([0*2, 1*2], [input_neuron*1.5 - input_neurons*0.75 + 0.75, hidden_neuron*1.5 - hidden_neurons*0.75 + 0.75], c='gray', lw=1, zorder=1)
            ax.add_artist(line)
    for hidden_neuron in range(hidden_neurons):
        for output_neuron in range(output_neurons):
            line = plt.Line2D([1*2, 2*2], [hidden_neuron*1.5 - hidden_neurons*0.75 + 0.75, output_neuron*1.5 - output_neurons*0.75 + 0.75], c='gray', lw=1, zorder=1)
            ax.add_artist(line)

    # 设置图参数
    ax.set_xlim(-1, 5)
    ax.set_ylim(-2, max(input_neurons, hidden_neurons, output_neurons)*1.5)
    plt.axis('off')  # 不显示坐标轴

    # 调整层名称的绘制位置,确保不被遮挡
    for i, name in enumerate(layer_names):
        plt.text(i*2, max(input_neurons, hidden_neurons, output_neurons)*0.75 + 1, name, horizontalalignment='center', fontsize=14, zorder=5)

    plt.title("简单神经网络图解", fontsize=16)
    return fig

fig = plot_neural_network_adjusted()
plt.show()

图片

神经网络之所以强,是因为它是非线性的,它可以理解非常复杂的逻辑关系。另外,在深度神经网络中,不同的层可以学习不同的特征,较低的层可能学习简单的特征,较高的层则可以学到更复杂的概念。这种从简单到复杂的学习过程使得神经网络非常适合处理复杂的数据结构。当然,还有一些其他概念,如激活函数、前向传播、反向传播、梯度下降等,我们通过一个例子来说明这些概念。

假设你在做一道菜,而神经网络就像是你的厨房,厨房里有各种炊具和调料,代表着神经网络的各个组成部分。

  1. 食材:输入数据,这是你要做菜所需的原材料,类比神经网络的输入数据。
  2. 调料:激活函数,赋予了食材不同的特性和味道。
  3. 烹饪过程:前向传播,这是你根据菜谱和经验,按照一定的步骤和方法进行的烹饪过程。在前向传播中,神经网络逐层处理输入数据,通过各种操作和激活函数的作用,逐渐提取并组合数据的特征,最终得到输出结果。
  4. 味道调整:训练过程,在烹饪过程中,你会尝试不同的调料和烹饪技巧,不断调整菜的味道,直到达到满意的效果。而在神经网络的训练过程中,我们通过反向传播算法来调整网络参数,使得网络的输出尽可能接近真实标签,达到最佳的预测效果。
  5. 品尝和反馈:反向传播,在烹饪中,你会尝试做好的菜品,并根据味道来调整调料的用量和烹饪方法。而在神经网络中,反向传播就像是品尝和反馈过程,通过计算模型输出与真实标签之间的差距(损失函数),并利用链式法则逆向传播这个误差,以调整每一层的参数,使得网络的输出更接近真实标签。
  6. 调整火候:梯度下降,在烹饪过程中,你还会根据实际情况调整火候,使菜肴烹饪得更加均匀和完美。而在神经网络的训练中,梯度下降就像是调整火候,它是一种优化算法,通过不断沿着梯度的反方向调整参数,逐步降低损失函数,使得网络的预测效果逐渐提升,达到最优的训练效果。
  7. 评价口感:损失函数,在烹饪过程中,你可能会根据菜肴的味道、口感等因素来评价菜品的好坏。而在神经网络中,损失函数就像是评价口感的标准,它衡量了模型的输出与真实标签之间的差距,即模型的预测效果,损失函数越小表示模型的预测越接近真实标签。

通过这个示例,我们基本把常见的几个关于神经网络的概念都解释清楚了,技术相关的细节我们会在后面的课程中继续学习。

小结

这节课我们只是进行概念性的介绍,目的在于认识基本的机器学习算法,对于初学者而言,理解这些基本的概念至关重要,通过这两节课的学习,我们了解了一些比较常见的机器学习算法,通过一些简单的代码示例,理解了各个算法的实际应用案例。虽然有些抽象,但是慢慢看还是能够理解的。建议你深入研究下sklearn这个库,里面包含这类场景的机器学习算法,然后自己动手敲一下这些示例代码感受一下。

思考题

实际上我们上面讲的神经网络之所以强大,就是因为有激活函数,使神经网络呈现为非线性的,那么你可以思考一下,为什么激活函数可以使神经网络呈非线性?如果没有激活函数,神经网络会出现什么问题?欢迎你把你思考后的结果分享到评论区,也欢迎你把这节课的内容分享给其他朋友,我们下节课再见!

精选留言(8)
  • 张申傲 👍(10) 💬(3)

    第9讲打卡~ 思考题:激活函数可以使神经网络呈现非线性,是因为激活函数中引入了复合函数和非线性转换,简单来说就是针对每层网络,激活函数可以控制有哪些特征值、经过什么样的转换之后,再传递到下一层。如果没有激活函数,那么输入层的所有特征值都会按照固定的权重依次传递到隐藏层和输出层,经过换算之后,本质上就相当于只经历了一个线性的隐藏层。

    2024-06-19

  • 阿斯蒂芬 👍(1) 💬(1)

    老师这个做菜的比喻很妙啊,生动形象。

    2024-07-07

  • 朱俊帆 👍(0) 💬(1)

    做菜这个比喻太妙了,感觉我以后面试都可以这么讲

    2024-12-30

  • charlott 👍(0) 💬(1)

    老师您好,机器学习适合宽表数据集吗,尤其是在预测场景下,感觉目前的预测手段都是手写规则十分不便,如果使用机器学习的方式理论上是不是能达到更优良的效果?

    2024-06-27

  • 方梁 👍(0) 💬(1)

    如果没有激活函数,那么下层的节点和参数会几何级增长。

    2024-06-19

  • 石云升 👍(3) 💬(0)

    学习概念性东西,还是要结合一些实际案例来学会比较好。有了AI之后,这个事情变得很简单了。举例: 1.产品评论情感分析(使用SVM) 2.客户流失预测(使用随机森林) 3.产品推荐系统(使用神经网络) 任务:对用户提交的产品评论进行情感分析,判断评论是正面、负面还是中性。 为什么选择SVM: - 文本数据通常是高维的(每个单词都是一个维度) - SVM在处理高维稀疏数据方面表现出色 - 我们主要关注二分类(正面vs负面)或三分类问题 实施步骤: a) 数据预处理: - 收集大量带标签的产品评论 - 进行文本清洗(去除标点、转换为小写等) - 使用词袋模型或TF-IDF将文本转换为向量 b) 特征工程: - 选择最相关的特征(如最常见的1000个词) - 可能会使用词性标注或命名实体识别等技术增强特征 c) 模型训练: - 选择合适的核函数(如线性核或RBF核) - 使用网格搜索和交叉验证找到最佳参数 d) 模型评估: - 使用准确率、精确率、召回率和F1分数评估模型 - 分析错误分类的案例以进一步改进 e) 部署和监控: - 将模型集成到评论系统中 - 定期用新数据更新模型 任务:预测哪些客户可能在近期停止使用你的平台。 为什么选择随机森林: - 可以处理各种类型的特征(数值型、类别型) - 能处理大量特征,并提供特征重要性 - 对类别不平衡(流失客户通常是少数)有较好的处理能力 实施步骤: a) 数据收集和预处理: - 收集客户的历史行为数据(如购买频率、客户服务互动、浏览记录等) - 处理缺失值和异常值 - 对类别特征进行编码 b) 特征工程: - 创建新特征,如客户生命周期价值、最近一次购买到现在的时间等 - 标准化数值特征 c) 模型训练: - 设置合适的树的数量和深度 - 使用交叉验证来调整参数 d) 模型评估: - 使用 AUC-ROC 曲线评估模型性能 - 分析特征重要性,了解哪些因素最影响客户流失 e) 模型应用: - 对可能流失的客户进行预警 - 制定针对性的留存策略 任务:基于用户行为和产品特征,为用户推荐可能感兴趣的产品。 为什么选择神经网络: - 可以处理复杂的非线性关系 - 能够学习用户和产品的潜在特征 - 适合处理大规模数据和实时预测 实施步骤: a) 数据收集: - 用户历史行为数据(浏览、购买、评分等) - 产品特征数据(类别、价格、品牌等) - 用户特征数据(年龄、性别、位置等) b) 数据预处理: 将用户和产品ID转换为嵌入向量 对数值特征进行归一化 对类别特征进行one-hot c) 模型设计: d) 模型训练: e) 模型评估: f) 模型部署和更新:

    2024-08-28

  • 福禄妹妹 👍(1) 💬(0)

    激活函数进一步提高了网络的表现力。这是因为向网络添加了基于激活函数的“非线性” 表现力,通过非线性函数的叠加,可以表现更加复杂的东西。所以从另一个维度说,激活函数必须使用非线性的,比如ReLU

    2024-11-14

  • like life 👍(1) 💬(0)

    激活函数可以使神经网络呈非线性是因为在训练的过程中得益于自反馈的机制来不断调整参数从而导致每次得出的结果和特征都不一样,如果没有激活函数,神经网络则就是一个简单的预测模型

    2024-11-10