Press "Enter" to skip to content

掌握蒙特卡洛:如何通过模拟提升机器学习模型

通过模拟技术增强预测算法的概率方法应用

“蒙特卡洛的轮盘桌” by 埃德华·蒙克(1892年)

一位科学家玩纸牌如何永远改变了统计学的游戏

在1945年这个动荡的年份,世界正被第二次世界大战的最后阵痛所紧紧抓住,一局接一局的纸牌游戏悄然引发了计算领域的一次进步。这并不是一局普通的游戏,而是导致了蒙特卡洛方法的诞生(1)。玩家是谁?没有别人,正是科学家斯坦尼斯劳·乌拉姆,他同时也深陷于曼哈顿计划(2)。乌拉姆在病中疗养期间发现自己迷上了纸牌游戏。游戏中复杂的概率引起了他的兴趣,他意识到反复模拟这个游戏可以很好地近似这些概率(3)。这是一个灵光乍现的时刻,类似于牛顿的苹果,但是换成了纸牌。乌拉姆随后与他的同事约翰·冯·诺伊曼讨论了这些想法,他们共同将蒙特卡洛方法形式化,以蒙地卡罗赌场命名,(如上面展示的埃德华·蒙克的著名绘画中所描绘的),在那里赌注高企且机会统治一切——就像这个方法本身一样。

快进到现在,蒙特卡洛方法已经成为机器学习领域的一张王牌,包括在强化学习、贝叶斯滤波和复杂模型的优化中的应用(4)。其鲁棒性和多功能性确保了它在诞生七十多年后仍然具有持续的相关性。从乌拉姆的纸牌游戏到今天的复杂人工智能应用,蒙特卡洛方法仍然是模拟和近似处理复杂系统的强大工具。

通过概率模拟玩好你的牌

在数据科学和机器学习的复杂世界中,蒙特卡洛模拟就像进行精心计算的赌注。这种统计技术使我们能够在面对不确定性时进行战略性的押注,对复杂的确定性问题进行概率化解读。在本文中,我们将揭开蒙特卡洛模拟的神秘面纱,并探讨它在统计学和机器学习中的强大应用。

我们将首先深入探讨蒙特卡洛模拟背后的理论,阐明使这种技术成为问题解决的强大工具的原理。接着,我们将通过Python进行一些实际应用,演示蒙特卡洛模拟如何在实践中实现。

随后,我们将探讨如何使用蒙特卡洛模拟来优化机器学习模型。我们将重点关注超参数调优这一常常具有挑战性的任务,为您提供在这个复杂领域中导航的实用工具。

所以,请下注,让我们开始吧!

理解蒙特卡洛模拟

蒙特卡洛模拟对于数学家和数据科学家来说是一种无价的技术。这些模拟提供了一种方法,用于在广泛而复杂的可能性中导航,制定明智的假设,并逐步优化选择,直到找到最合适的解决方案。

它的工作原理就是这样:我们生成大量的随机场景,遵循一定的预定义过程,然后审查这些场景以估计各种结果的概率。这里有一个类比来让这个更清楚:把每个场景都看作流行的Hasbro棋盘游戏“克鲁”。对于那些不熟悉的人,克鲁是一种侦探风格的游戏,玩家在一座大宅院中移动,收集证据以推断犯罪的细节——谁、什么和何地。每个回合或提问都会排除潜在的答案,并使玩家们接近揭示真正犯罪情景的真相。类似地,蒙特卡洛研究中的每个模拟都提供了有助于解决我们复杂问题的见解。

在机器学习领域,这些“场景”可以代表不同的模型配置、不同的超参数集、将数据集分割为训练集和测试集的不同方法等等。通过评估这些场景的结果,我们可以获得有关机器学习算法行为的宝贵见解,从而能够对其进行优化的明智决策。

飞镖游戏

为了理解蒙特卡罗模拟,想象一下你正在玩飞镖游戏。但是,你被蒙上了眼睛,并且随机地向一个大方形飞镖靶投掷飞镖,而不是瞄准特定目标。在这个方形内部,有一个圆形目标。你的目标是估计圆的周长与直径的比值,也就是π的值。

听起来不可能,对吧?但是这里有一个诀窍:圆的面积与方形的面积的比值是π/4。因此,如果你投掷大量的飞镖,落在圆内的飞镖与总飞镖数的比值应该接近π/4。将这个比值乘以4,就可以得到π的估计值!

随机猜测 vs. 蒙特卡罗

为了展示蒙特卡罗模拟的威力,让我们将其与一个更简单的方法进行比较,可能是最简单的方法:随机猜测。

当你运行下面的代码来进行随机和蒙特卡罗两种情况的比较时,每次都会得到不同的预测结果。这是可以预料的,因为飞镖是随机投掷的。这是蒙特卡罗模拟的一个关键特征:它们本质上是随机的。但尽管有这种随机性,当正确使用时,它们可以提供非常准确的估计值。因此,虽然你的图形可能与我的不完全相同,但它们会传达相同的信息。

在第一组可视化图表(图1a图1f)中,我们对π的值进行了一系列随机猜测,每次都基于不同的猜测值生成一个圆。让我们将这种“随机性”推向正确的方向,并假设虽然我们不能记住π的确切值,但我们知道它介于2和4之间。从生成的图中可以看出,圆的大小根据猜测的不同而变化,展示了这种方法的不准确性(这也不应该令人惊讶)。每个图中的绿色圆代表单位圆,也就是我们试图估计的“真实”圆,蓝色圆是基于我们的随机猜测生成的。

#对π的随机猜测#在运行此代码之前,请确保已安装必要的软件包。#您可以在终端上使用“pip install”安装它们,如果使用conda环境,则可以使用“conda install”#导入必要的库import randomimport plotly.graph_objects as goimport numpy as np#要进行的猜测次数。调整此参数以获得更多的猜测和随后的绘图num_guesses = 6#生成单位圆的坐标#我们使用np.linspace在0到2*pi的范围内生成均匀间隔的数字。#这些数字表示单位圆中的角度.theta = np.linspace(0, 2*np.pi, 100)#单位圆的x和y坐标分别是这些角度的余弦和正弦.unit_circle_x = np.cos(theta)unit_circle_y = np.sin(theta)#我们将对π的值进行一系列猜测for i in range(num_guesses):    #对π进行随机猜测,范围为2到4    pi_guess = random.uniform(2, 4)    #根据猜测生成圆的坐标    #圆的半径是猜测的π值除以4.    radius = pi_guess / 4    #圆的x和y坐标分别是半径乘以角度的余弦和正弦。    circle_x = radius * np.cos(theta)    circle_y = radius * np.sin(theta)    #创建圆的散点图    fig = go.Figure()    #将圆添加到图表中    #我们使用一个Scatter跟踪器,模式为'lines',来绘制圆。    fig.add_trace(go.Scatter(        x = circle_x,        y = circle_y,        mode='lines',        line=dict(            color='blue',            width=3        ),        name='估计的圆'    ))    #将单位圆添加到图表中    fig.add_trace(go.Scatter(        x = unit_circle_x,        y = unit_circle_y,        mode='lines',        line=dict(            color='green',            width=3        ),        name='单位圆'    ))    #更新图表的布局    #我们将标题设置为包含猜测的π值,并调整大小和轴范围以正确显示圆。    fig.update_layout(        title=f"图1{chr(97 + i)}:随机猜测π:{pi_guess}",        width=600,        height=600,        xaxis=dict(            constrain="domain",            range=[-1, 1]        ),        yaxis=dict(            scaleanchor="x",            scaleratio=1,            range=[-1, 1]        )    )    #显示图表    fig.show()

掌握蒙特卡洛:如何通过模拟提升机器学习模型 四海 第2张

掌握蒙特卡洛:如何通过模拟提升机器学习模型 四海 第3张

掌握蒙特卡洛:如何通过模拟提升机器学习模型 四海 第4张

掌握蒙特卡洛:如何通过模拟提升机器学习模型 四海 第5张

掌握蒙特卡洛:如何通过模拟提升机器学习模型 四海 第6张

Figures 1a-1f: Random Estimation of Pi

你可能会注意到一些奇怪的现象:在随机猜测的方法中,有时猜测结果更接近π的真实值,但圆却离单位圆更远。这种明显的矛盾是因为我们在看圆的周长,而不是它们的半径或面积。两个圆之间的视觉差距代表了基于猜测估计圆周长的误差,而不是整个圆。

在第二组可视化图表(图 2a图 2f)中,我们使用蒙特卡洛方法来估计π的值。我们不再进行随机猜测,而是将大量的飞镖投向一个正方形,并计算落在内切于该正方形的圆内的飞镖数量。由图表可见,估计的π值更准确,因为圆的大小更接近实际的单位圆。绿色点表示落在单位圆内的飞镖,红色点表示落在单位圆外的飞镖。

# 蒙特卡洛方法估计π值# 导入所需的库import randomimport mathimport plotly.graph_objects as goimport plotly.io as pioimport numpy as np# 我们将模拟将飞镖投向飞镖板以估计π值。让我们投掷10,000次飞镖.num_darts = 10000# 用于记录落在圆内的飞镖数量.darts_in_circle = 0# 我们将存储落在圆内和圆外的飞镖的坐标.x_coords_in, y_coords_in, x_coords_out, y_coords_out = [], [], [], []# 让我们在模拟过程中生成6个图表。因此,我们将每1,666次飞镖(10,000除以6)创建一个新的图表.num_figures = 6darts_per_figure = num_darts // num_figures# 创建一个单位圆以与我们的估计进行比较。在这里,我们使用极坐标并转换为笛卡尔坐标.theta = np.linspace(0, 2*np.pi, 100)unit_circle_x = np.cos(theta)unit_circle_y = np.sin(theta)# 我们开始投掷飞镖(模拟在一个1x1的正方形内随机生成点,并检查它们是否落在一个四分之一的圆内)。for i in range(num_darts):    # 在-1和1之间生成随机的x、y坐标。    x, y = random.uniform(-1, 1), random.uniform(-1, 1)        # 如果一个飞镖(点)距离原点(0,0)的距离小于等于1,那么它在圆内。    if math.sqrt(x**2 + y**2) <= 1:        darts_in_circle += 1        x_coords_in.append(x)        y_coords_in.append(y)    else:        x_coords_out.append(x)        y_coords_out.append(y)    # 每1,666次飞镖后,让我们看看我们的估计与真实的单位圆相比如何。    if (i + 1) % darts_per_figure == 0:        # 我们通过计算落在圆内的飞镖占总飞镖数的比例来估计π的值。        pi_estimate = 4 * darts_in_circle / (i + 1)        # 现在我们根据估计创建一个圆,以便与单位圆进行视觉比较。        estimated_circle_radius = pi_estimate / 4        estimated_circle_x = estimated_circle_radius * np.cos(theta)        estimated_circle_y = estimated_circle_radius * np.sin(theta)        # 使用Plotly绘制结果。        fig = go.Figure()        # 将落在圆内和圆外的飞镖添加到图表中。        fig.add_trace(go.Scattergl(x=x_coords_in, y=y_coords_in, mode='markers', name='飞镖落在圆内', marker=dict(color='green', size=4, opacity=0.8)))        fig.add_trace(go.Scattergl(x=x_coords_out, y=y_coords_out, mode='markers', name='飞镖落在圆外', marker=dict(color='red', size=4, opacity=0.8)))        # 将真实的单位圆和我们的估计圆添加到图表中。        fig.add_trace(go.Scatter(x=unit_circle_x, y=unit_circle_y, mode='lines', name='单位圆', line=dict(color='green', width=3)))        fig.add_trace(go.Scatter(x=estimated_circle_x, y=estimated_circle_y, mode='lines', name='估计圆', line=dict(color='blue', width=3)))        # 自定义图表布局。        fig.update_layout(title=f"图 {chr(97 + (i + 1) // darts_per_figure - 1)}:投掷飞镖次数:{(i + 1)},估计的π值:{pi_estimate}", width=600, height=600, xaxis=dict(constrain="domain", range=[-1, 1]), yaxis=dict(scaleanchor="x", scaleratio=1, range=[-1, 1]), legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01))        # 显示图表。        fig.show()        # 将图表保存为PNG图像文件。        pio.write_image(fig, f"fig2{chr(97 + (i + 1) // darts_per_figure - 1)}.png")

掌握蒙特卡洛:如何通过模拟提升机器学习模型 四海 第8张

掌握蒙特卡洛:如何通过模拟提升机器学习模型 四海 第9张

掌握蒙特卡洛:如何通过模拟提升机器学习模型 四海 第10张

掌握蒙特卡洛:如何通过模拟提升机器学习模型 四海 第11张

掌握蒙特卡洛:如何通过模拟提升机器学习模型 四海 第12张

Figure 2a-2f:π的蒙特卡洛估计

蒙特卡洛方法中,π的估计是基于“飞镖”落在圆内的比例与投掷的总飞镖数之间的关系。所得到的估计π值用于绘制一个圆。如果蒙特卡洛估计不准确,圆的尺寸也将不正确。估计圆与单位圆之间的间隙宽度可以提示蒙特卡洛估计的准确性。

然而,由于蒙特卡洛方法在“飞镖”数增加时可以生成更准确的估计,所以估计圆应该会随着投掷的“飞镖”数越来越接近单位圆。因此,虽然两种方法在估计不准确时都会显示出间隙,但随着“飞镖”数的增加,蒙特卡洛方法的间隙应该更加一致地减小。

预测π:概率的力量

蒙特卡洛模拟之所以如此强大,是因为它们能够利用随机性来解决确定性问题。通过生成大量的随机场景并分析结果,我们可以估计不同结果的概率,甚至对于难以通过分析方法解决的复杂问题也可以做到这一点。

在估计π的情况下,蒙特卡洛方法使我们能够做出非常准确的估计,即使我们只是随机地投掷飞镖。正如前面所讨论的,我们投掷的飞镖越多,我们的估计就越准确。这是大数定律的演示,它是概率论中的一个基本概念,它指出从大量试验中获得的结果的平均值应该接近于期望值,并且随着进行更多的试验,会越来越接近期望值。让我们看看我们在Figures 2a-2f中展示的六个例子是否符合这个规律,通过绘制投掷的飞镖数与蒙特卡洛估计的π值与真实π值之间的差异。一般来说,我们的图表(Figure 2g)应该是负的。以下是完成这个任务的代码:

# 计算真实π和估计π之间的差异
diff_pi = [abs(estimate - math.pi) for estimate in pi_estimates]
# 创建投掷飞镖数与π差异的图表(Figure 2g)
fig2g = go.Figure(data=go.Scatter(x=num_darts_thrown, y=diff_pi, mode='lines'))
# 添加标题和标签到图表
fig2g.update_layout(
    title="Fig2g: 投掷飞镖数与估计π的差异",
    xaxis_title="投掷飞镖数",
    yaxis_title="π的差异",
)
# 显示图表
fig2g.show()
# 将图表保存为png
pio.write_image(fig2g, "fig2g.png")

掌握蒙特卡洛:如何通过模拟提升机器学习模型 四海 第14张

请注意,即使只有6个示例,总体模式仍然符合预期:投掷的飞镖越多(场景越多),估计值和真实值之间的差异越小,因此预测越好。

假设我们投掷了1,000,000枚飞镖,并允许自己进行500次预测。换句话说,在1,000,000次投掷飞镖的模拟过程中,在500个均匀间隔的时间点记录估计值和实际值之间的差异。我们不想生成500个额外的图形,让我们直接跳到我们想要确认的内容:随着投掷的飞镖数量增加,我们预测的pi值与实际pi值之间的差异是否确实变小。我们将使用散点图(图2h):

#500个蒙特卡洛场景;1,000,000枚投掷的飞镖
import random
import math
import plotly.graph_objects as go
import numpy as np

# 总共要投掷的飞镖数(1M)
num_darts = 1000000
darts_in_circle = 0

# 要记录的场景数(500)
num_scenarios = 500
darts_per_scenario = num_darts // num_scenarios

# 用于存储每个场景数据的列表
darts_thrown_list = []
pi_diff_list = []

# 投掷一定数量的飞镖
for i in range(num_darts):
    # 生成介于-1和1之间的随机x,y坐标
    x, y = random.uniform(-1, 1), random.uniform(-1, 1)
    
    # 检查飞镖是否在圆内
    # 如果飞镖距离原点(0,0)的距离小于等于1,则飞镖在圆内
    if math.sqrt(x**2 + y**2) <= 1:
        darts_in_circle += 1
    
    # 如果是记录场景的时候
    if (i + 1) % darts_per_scenario == 0:
        # 使用蒙特卡洛方法估计pi值
        # 估计值是圆内飞镖数的四倍除以总飞镖数
        pi_estimate = 4 * darts_in_circle / (i + 1)
        
        # 记录已投掷的飞镖数和估计值与实际pi值之间的差异
        darts_thrown_list.append((i + 1) / 1000)  # 除以1000以以千为单位显示
        pi_diff_list.append(abs(pi_estimate - math.pi))

# 创建散点图
fig = go.Figure(data=go.Scattergl(x=darts_thrown_list, y=pi_diff_list, mode='markers'))

# 更新图形的布局
fig.update_layout(
    title="图2h:估计值和实际pi值之间的差异与投掷的飞镖数量(以千为单位)",
    xaxis_title="投掷的飞镖数量(以千为单位)",
    yaxis_title="估计值和实际pi值之间的差异",
)

# 显示图形
fig.show()

# 将图形保存为png
pio.write_image(fig2h, "fig2h.png")

掌握蒙特卡洛:如何通过模拟提升机器学习模型 四海 第15张

蒙特卡洛模拟和超参数调优:一个成功的组合

你可能会想到这一点:“蒙特卡洛是一种有趣的统计工具,但它如何应用于机器学习?”简短的回答是:有很多方式。蒙特卡洛模拟在机器学习中的许多应用之一是在超参数调优领域。

超参数是我们(人类)在设置机器学习算法时调整的旋钮和控制器。它们控制着算法的行为,这些行为关键地不是从数据中学习得到的。例如,在决策树中,树的最大深度是一个超参数。在神经网络中,学习率和隐藏层的数量是超参数。

选择正确的超参数可以使模型的表现从差到优秀。但是我们如何知道要选择哪些超参数?这就是蒙特卡洛模拟发挥作用的地方。

传统上,机器学习实践者使用网格搜索或随机搜索等方法来调整超参数。这些方法涉及指定每个超参数的可能值集,并为每个超参数可能的组合训练和评估模型。这可能会消耗大量的计算资源和时间,特别是当需要调整许多超参数或每个超参数可以采用大范围的可能值时。

蒙特卡洛模拟提供了一种更高效的替代方案。我们可以根据某种概率分布从超参数空间中进行随机采样,而不是穷举搜索所有可能的超参数组合。这样可以更高效地探索超参数空间,并更快地找到良好的超参数组合。

在下一节中,我们将使用一个真实的数据集来演示如何在实践中使用蒙特卡罗模拟进行超参数调优。让我们开始吧!

蒙特卡罗模拟用于超参数调优

实验的核心:心脏病数据集

在机器学习领域,数据是推动我们模型的生命线。在我们探索蒙特卡罗模拟在超参数调优中的应用时,让我们看一下一个与心脏密切相关的数据集。心脏病数据集(CC BY 4.0)来自UCI机器学习库,是一组来自患者的医疗记录,其中一些患者患有心脏病。

该数据集包含14个属性,包括年龄、性别、胸痛类型、静息血压、胆固醇水平、空腹血糖等。目标变量是心脏病的存在与否,使其成为一个二分类任务。由于混合了分类和数值特征,它是一个用于演示超参数调优的有趣数据集。

首先,让我们看一下我们的数据集,以了解我们将要处理的数据 – 这总是一个很好的起点。

#导入必要的库import pandas as pdfrom sklearn.model_selection import train_test_splitfrom sklearn.preprocessing import StandardScaler, OneHotEncoderfrom sklearn.compose import ColumnTransformerfrom sklearn.pipeline import Pipelinefrom sklearn.linear_model import LogisticRegressionfrom sklearn.model_selection import GridSearchCVfrom sklearn.metrics import roc_auc_scoreimport numpy as npimport plotly.graph_objects as go#加载数据集#数据集可在UCI机器学习库中获取#它是一个关于心脏病的数据集,包含各种患者测量数据url = "https://archive.ics.uci.edu/ml/machine-learning-databases/heart-disease/processed.cleveland.data"#为数据框定义列名column_names = ["age", "sex", "cp", "trestbps", "chol", "fbs", "restecg", "thalach", "exang", "oldpeak", "slope", "ca", "thal", "target"]#将数据集加载到pandas数据框中#我们指定列名,并告诉pandas将'?'视为NaNdf = pd.read_csv(url, names=column_names, na_values="?")#打印数据框的前几行#这将快速给我们一个数据概览print(df.head())

这显示了我们数据集中所有列的前四个值。如果您已经加载了正确的csv文件并按照我的命名方式命名了列,您的输出将看起来像图3

图3:我们数据集的前4行数据

设置脉搏:数据预处理

在我们可以将心脏病数据集用于超参数调优之前,我们需要对数据进行预处理。这涉及到几个步骤:

  1. 处理缺失值:数据集中的一些记录有缺失值。我们需要决定如何处理这些缺失值,是通过删除记录、填充缺失值还是其他方法。
  2. 编码分类变量:许多机器学习算法要求输入数据为数字形式。我们需要将分类变量转换为数字格式。
  3. 归一化数值特征:当数值特征处于相似的范围时,机器学习算法通常表现更好。我们将应用归一化来调整这些特征的范围。

让我们从处理缺失值开始。在我们的心脏病数据集中,’ca’和 ‘thal’列中有一些缺失值。我们将用各自列的中位数填充这些缺失值。这是一种处理缺失数据的常见策略,因为它不会对数据的分布产生重大影响。

接下来,我们将对分类变量进行编码。在我们的数据集中,’cp’、’restecg’、’slope’、’ca’和 ‘thal’列是分类变量。我们将使用标签编码将这些分类变量转换为数值变量。标签编码将每个列中的每个唯一类别分配给不同的整数。

最后,我们将对数值特征进行归一化。归一化调整数值特征的尺度,使其都在一个相似的范围内。这可以帮助提高许多机器学习算法的性能。我们将使用标准缩放进行归一化,该方法将数据转换为均值为0,标准差为1。

下面是执行所有这些预处理步骤的Python代码:

# 预处理
# 导入所需的库
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import LabelEncoder
# 在数据集中找出缺失值
# 这将打印每列中缺失值的数量
print(df.isnull().sum())
# 使用列的中位数填充缺失值
# sklearn的SimpleImputer类提供了基本的缺失值填充策略
# 我们使用'strategy'参数设置为'median',将缺失值替换为每列的中位数
imputer = SimpleImputer(strategy='median')
# 将填充器应用到数据框中
# 结果是一个新的数据框,其中的缺失值已经被填充
df_filled = pd.DataFrame(imputer.fit_transform(df), columns=df.columns)
# 打印填充后的数据框的前几行
# 这样可以快速检查填充是否正确
print(df_filled.head())
# 在数据集中找出分类变量
# 这些是包含非数值数据的变量
categorical_vars = df_filled.select_dtypes(include='object').columns
# 对分类变量进行编码
# sklearn的LabelEncoder类将每个唯一字符串转换为唯一整数
encoder = LabelEncoder()
for var in categorical_vars:
    df_filled[var] = encoder.fit_transform(df_filled[var])
# 标准化数值特征
# sklearn的StandardScaler类通过去除均值并进行单位方差缩放来标准化特征
scaler = StandardScaler()
# 将标准化器应用到数据框中
# 结果是一个新的数据框,其中数值特征已经被标准化
df_normalized = pd.DataFrame(scaler.fit_transform(df_filled), columns=df_filled.columns)
# 打印标准化后的数据框的前几行
# 这样可以快速检查标准化是否正确
print(df_normalized.head())

第一个打印语句显示原始数据集中每列中的缺失值数量。在我们的例子中,’ca’和’thal’列有一些缺失值。

第二个打印语句显示在填充缺失值后数据集的前几行。如前所述,我们使用每列的中位数来填充缺失值。

第三个打印语句显示在对分类变量进行编码后数据集的前几行。在此步骤之后,数据集中的所有变量都是数值型的。

最后一个打印语句显示在标准化数值特征后数据集的前几行。在此步骤之后,数据集中的所有数值特征都具有相似的尺度。请检查输出结果是否类似于图4

图4:预处理打印输出结果

运行这段代码之后,我们得到了一个经过预处理的数据集,可以用于建模。

实现基本的机器学习模型

现在我们已经对数据进行了预处理,可以开始实现一个基本的机器学习模型了。这将作为我们的基准模型,稍后我们将通过超参数调优来改进它。

我们将使用一个简单的逻辑回归模型来完成这个任务。请注意,尽管它被称为“回归”,但实际上它是用于二分类问题的最流行的算法之一,就像我们在心脏病数据集中所面对的问题一样。它是一种线性模型,用于预测正类的概率。

在训练模型之后,我们将使用两个常见的评估指标来评估其性能:准确率和ROC-AUC。准确率是所有预测中正确预测的比例,而ROC-AUC(受试者工作特征曲线下面积)衡量了真正例率和假正例率之间的权衡。

但这与蒙特卡洛模拟有什么关系呢?嗯,像逻辑回归这样的机器学习模型有许多可以调优以提高性能的超参数。然而,找到最佳超参数组合可能就像大海捞针一样困难。这就是蒙特卡洛模拟的用武之地。通过随机抽样不同的超参数组合并评估它们的性能,我们可以估计好的超参数的概率分布,并对最佳超参数进行有根据的猜测,就像我们在飞镖投掷练习中选择更好的π值一样。

下面是实现和评估基本逻辑回归模型的Python代码:

# 逻辑回归模型 - 基线
# 导入所需的库
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, roc_auc_score
# 将标准化后的DataFrame中的'target'列替换为原始的'target'列
# 这样做是因为'target'列也被标准化了,而我们不希望这样
df_normalized['target'] = df['target']
# 将'target'列二值化
# 这样做是因为原始的'target'列包含值从0到4
# 我们想将问题简化为一个二分类问题:有心脏病或无心脏病
df_normalized['target'] = df_normalized['target'].apply(lambda x: 1 if x > 0 else 0)
# 将数据分割为训练集和测试集
# 'target'列是我们的标签,所以我们从特征(X)中删除它
# 我们使用20%的测试集,也就是80%的数据用于训练,20%用于测试
X = df_normalized.drop('target', axis=1)
y = df_normalized['target']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 实现一个基本的逻辑回归模型
# 逻辑回归是一个简单但强大的用于二分类问题的线性模型
model = LogisticRegression()
model.fit(X_train, y_train)
# 在测试集上进行预测
# 模型已经训练好了,所以我们现在可以用它来对未见过的数据进行预测
y_pred = model.predict(X_test)
# 评估模型
# 我们使用准确率(正确预测的比例)和ROC-AUC(模型区分类别的能力的度量)作为指标
accuracy = accuracy_score(y_test, y_pred)
roc_auc = roc_auc_score(y_test, y_pred)
# 打印性能指标
# 这些指标给出了模型的性能情况
print("基线模型准确率:" + f'{accuracy}')
print("基线模型ROC-AUC:" + f'{roc_auc}')

掌握蒙特卡洛:如何通过模拟提升机器学习模型 四海 第18张

准确率为0.885,ROC-AUC得分为0.884,我们的基本逻辑回归模型为我们提供了一个坚实的基准,可以在其基础上改进。这些指标表明我们的模型在区分患有和不患有心脏病的患者方面表现得非常好。让我们看看能否使其变得更好。

在机器学习中,通过调整模型的超参数通常可以提高其性能。超参数是在学习过程开始之前设置的,不是从数据中学习得到的参数。例如,在逻辑回归中,正则化强度’C’和惩罚类型’l1’或’l2’就是超参数。

让我们使用网格搜索对逻辑回归模型进行超参数调优。我们将调优’C’和’penalty’两个超参数,并使用ROC-AUC作为评分指标。让我们看看能否超过基准模型的性能。

现在,让我们开始这一部分的Python代码。

# 网格搜索# 导入必要的库from sklearn.model_selection import GridSearchCV# 定义超参数及其取值范围# 'C'是正则化强度的倒数(较小的值指定更强的正则化)# 'penalty'指定惩罚的范数(l1或l2)hyperparameters = {'C': [0.001, 0.01, 0.1, 1, 10, 100, 1000],                    'penalty': ['l1', 'l2']}# 实现网格搜索# GridSearchCV是一种用于调优模型超参数的方法# 我们传入模型、要调优的超参数以及交叉验证的折数# 我们使用ROC-AUC作为评分指标grid_search = GridSearchCV(LogisticRegression(), hyperparameters, cv=5, scoring='roc_auc')grid_search.fit(X_train, y_train)# 获取最佳超参数# GridSearchCV已经找到了我们模型的最佳超参数,所以我们将其打印出来best_params = grid_search.best_params_print(f'最佳超参数:{best_params}')# 评估最佳模型# GridSearchCV还给出了最佳模型,所以我们可以使用它进行预测并评估其性能best_model = grid_search.best_estimator_y_pred_best = best_model.predict(X_test)accuracy_best = accuracy_score(y_test, y_pred_best)roc_auc_best = roc_auc_score(y_test, y_pred_best)# 打印最佳模型的性能指标# 这些指标可以告诉我们经过超参数调优后模型的性能如何print("网格搜索方法 " + f'最佳模型的准确率:{accuracy_best}')print("网格搜索方法 " + f'最佳模型的ROC-AUC得分:{roc_auc_best}')

掌握蒙特卡洛:如何通过模拟提升机器学习模型 四海 第19张

最佳超参数被找到为{‘C’: 0.1, ‘penalty’: ‘l2’},我们的网格搜索在最佳模型上的准确率为0.852,ROC-AUC得分为0.853。有趣的是,这个性能略低于我们的基准模型。这可能是因为我们的基准模型的超参数已经适合这个特定的数据集,或者这可能是训练集-测试集划分中固有的随机性的结果。无论如何,这是一个宝贵的提醒,更复杂的模型和技术并不总是更好。

然而,你可能也注意到我们的网格搜索只探索了相对较少的超参数组合。实际上,超参数的数量和潜在取值范围可能会更大,使得网格搜索在计算上变得昂贵甚至不可行。

这就是蒙特卡洛方法的用武之地。让我们看看这种更有引导性的方法是否能改善原始基准模型或基于网格搜索的模型的性能:

# 蒙特卡洛方法# 导入必要的库from sklearn.metrics import accuracy_score, roc_auc_scorefrom sklearn.linear_model import LogisticRegressionfrom sklearn.model_selection import train_test_splitimport numpy as np# 将数据分割为训练集和测试集X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)# 定义超参数的范围# 'C'是正则化强度的倒数(较小的值指定更强的正则化)# 'penalty'指定惩罚的范数(l1或l2)C_range = np.logspace(-3, 3, 7)penalty_options = ['l1', 'l2']# 初始化变量以存储最佳得分和超参数best_score = 0best_hyperparams = None# 执行蒙特卡洛模拟# 我们将进行1000次迭代。您可以尝试不同的次数以查看性能的变化。# 记住大数定律!for _ in range(1000):         # 从定义的范围中随机选择超参数    C = np.random.choice(C_range)    penalty = np.random.choice(penalty_options)        # 创建并评估带有这些超参数的模型    # 我们使用'liblinear'求解器,因为它支持L1和L2正则化    model = LogisticRegression(C=C, penalty=penalty, solver='liblinear')    model.fit(X_train, y_train)    y_pred = model.predict(X_test)        # 计算准确率和ROC-AUC    accuracy = accuracy_score(y_test, y_pred)    roc_auc = roc_auc_score(y_test, y_pred)        # 如果该模型的ROC-AUC是迄今为止最好的,存储其得分和超参数    if roc_auc > best_score:        best_score = roc_auc        best_hyperparams = {'C': C, 'penalty': penalty}# 打印最佳得分和超参数print("蒙特卡洛方法 " + f'最佳ROC-AUC:{best_score}')print("蒙特卡洛方法 " + f'最佳超参数:{best_hyperparams}')# 使用最佳超参数训练模型best_model = LogisticRegression(**best_hyperparams, solver='liblinear')best_model.fit(X_train, y_train)# 在测试集上进行预测y_pred = best_model.predict(X_test)# 计算并打印最佳模型的准确率accuracy = accuracy_score(y_test, y_pred)print("蒙特卡洛方法 " + f'最佳模型的准确率:{accuracy}')

掌握蒙特卡洛:如何通过模拟提升机器学习模型 四海 第20张

在蒙特卡洛方法中,我们发现最佳的ROC-AUC分数为0.9014,最佳超参数为{‘C’: 0.1, ‘penalty’: ‘l1’}。最佳模型的准确率为0.9016。

看起来蒙特卡洛刚刚从牌组中拿到了一张A牌 – 这是对基准模型和使用网格搜索调优的模型的改进。我鼓励您调整Python代码,看看它对性能的影响,记住讨论过的原则。尝试增加超参数空间来改进网格搜索方法,或者比较计算时间与蒙特卡洛方法。增加或减少我们的蒙特卡洛方法的迭代次数,看看它对性能的影响。

结论

蒙特卡洛方法,诞生于纸牌游戏,无疑改变了计算数学和数据科学的格局。它的力量在于其简单和多功能性,使我们能够相对轻松地解决复杂的高维问题。从用飞镖估计pi的值到调优机器学习模型的超参数,蒙特卡洛模拟已经证明是我们数据科学工具中不可或缺的工具。

在本文中,我们从蒙特卡洛方法的起源开始,经过其理论基础,进入到其在机器学习中的实际应用。我们看到它如何用于优化机器学习模型,并通过对真实数据集进行超参数调优的实际探索进行了实践。我们还将其与其他方法进行了比较,展示了其效率和有效性。

但蒙特卡洛的故事远未结束。随着我们继续推动机器学习和数据科学的边界,蒙特卡洛方法无疑将继续发挥关键作用。无论我们是开发复杂的人工智能应用程序,理解复杂数据,还是简单地玩一局纸牌游戏,蒙特卡洛方法都证明了模拟和近似在解决复杂问题中的强大作用。

在我们继续前进的同时,让我们停下来欣赏这种方法的美 – 这种方法的根源可以追溯到简单的纸牌游戏,但它却有能力推动世界上一些最先进的计算。蒙特卡洛方法确实是一个高风险的机会和复杂性游戏,到目前为止,似乎庄家总是赢。所以,继续洗牌,继续打好你的牌,记住 – 在数据科学的游戏中,蒙特卡洛可能会成为你的王牌。

结束语

恭喜您完成了阅读!我们已经穿越了概率世界,与复杂模型搏斗,并对蒙特卡洛模拟的力量有了新的认识。我们看到它们在实际应用中的表现,将复杂问题简化为可管理的组成部分,甚至为机器学习任务优化超参数。

如果您像我一样喜欢深入研究机器学习问题解决的复杂性,请关注我在VoAGI和LinkedIn上的动态。让我们一起以聪明的解决方案逐渐驾驭AI的迷宫。

在我们下一次的统计冒险之前,请继续探索,继续学习,继续模拟!在您的数据科学和机器学习之旅中,愿运气永远与您同在。

注:除非另有说明,所有图片均由作者提供。

Leave a Reply

Your email address will not be published. Required fields are marked *