XGB-2: Boosted Trees(提升树)简介

XGBoost代表“Extreme Gradient Boosting”,其中术语“Gradient Boosting”来源于Friedman的论文《Greedy Function Approximation: A Gradient Boosting Machine》。

梯度提升树已经存在一段时间,关于这个主题有很多资料。本部分将使用监督学习的元素,以一种自包含和原则性的方式解释提升树。认为这种解释更清晰、更正式,并激发了XGBoost中使用的模型形式的动机。

监督学习的要素

XGBoost用于监督学习问题,使用训练数据(具有多个特征) x i x_i xi来预测目标变量 y i y_i yi。在学习具体的树模型之前,首先回顾监督学习中的基本要素。

模型和参数

在监督学习中,模型通常指的是从输入 x i x_i xi得出预测 y i y_i yi的数学结构 y ^ i = ∑ j θ j x i j \hat{y}_i = \sum_j \theta_j x_{ij} y^i=jθjxij。一个常见的例子是线性模型,其中预测值表 x i x_i xi示为加权输入特征 x i x_i xi的线性组合。预测值可以有不同的解释,取决于具体的任务,即回归或分类。例如,它可以经过逻辑变换以获得 logistic 回归中正类别的概率,并且想要对输出进行排序时,它还可以用作排名分数。

参数parameters是需要从数据中学习的未确定部分。在线性回归问题中,参数是系数 θ \theta θ。通常,将 θ \theta θ表示参数(模型中有许多参数,这里的定义有些粗略)。

目标函数: 训练损失 + 正则化

通过对 y i y_i yi的巧妙选择,可以表达多种任务,例如回归、分类和排名。训练模型的任务是找到最适合训练数据 x i x_i xi和标签 y i y_i yi的最佳参数 θ \theta θ为了训练模型,需要定义目标函数objective function来衡量模型对训练数据拟合的好坏

目标函数的显著特征是由两部分组成: 训练损失和正则化项

obj ( θ ) = L ( θ ) + Ω ( θ ) \text{obj}(\theta) = L(\theta) + \Omega(\theta) obj(θ)=L(θ)+Ω(θ)

其中 L L L是训练损失函数, Ω \Omega Ω是正则化项。训练损失度量了模型在训练数据方面的预测能力。常见的 L L L选择是均方误差,表示为

L ( θ ) = ∑ i ( y i − y ^ i ) 2 L(\theta) = \sum_i (y_i-\hat{y}_i)^2 L(θ)=i(yiy^i)2

另一个常用的损失函数是 logistic 损失,用于 logistic 回归:

L ( θ ) = ∑ i [ y i ln ⁡ ( 1 + e − y ^ i ) + ( 1 − y i ) ln ⁡ ( 1 + e y ^ i ) ] L(\theta) = \sum_i[ y_i\ln (1+e^{-\hat{y}_i}) + (1-y_i)\ln (1+e^{\hat{y}_i})] L(θ)=i[yiln(1+ey^i)+(1yi)ln(1+ey^i)]

正则化项是通常会忘记添加的部分,正则化项控制着模型的复杂性,有助于避免过拟合。这听起来有点抽象,因此考虑以下问题。在下面的图片中,被要求在图像的左上角给定输入数据点的情况下直观地拟合一个阶跃函数。在三个解决方案中,哪一个是最佳拟合?

在这里插入图片描述

正确答案标记为红色,总的原则是希望得到一个简单而又有预测能力的模型。在机器学习中,这两者之间的权衡也被称为偏差-方差权衡bias-variance tradeoff

为什么介绍这个一般原则?

上面介绍的元素构成了监督学习的基本要素,它们是机器学习工具包的自然构建模块。例如,你应该能够描述梯度提升树和随机森林之间的差异和共同点。以形式化的方式理解这个过程还有助于我们理解正在学习的目标以及修剪和平滑等启发式方法背后的原因。

决策树集成Decision Tree Ensembles

现在已经介绍了监督学习的基本要素,下面介绍树模型。首先,首先了解XGBoost的模型选择:决策树集成decision tree ensembles。树集成模型由一组分类和回归树(CART)组成

下面是一个简单的CART示例,用于分类某个虚构的电脑游戏 X 是否会被某人喜欢。

cart.png

将一个家庭的成员分类到不同的叶子上,并为它们分配相应叶子上的分数。CART与决策树有些不同,决策树中叶子只包含决策值。在CART中,每个叶子都关联有一个实际分数,使得能够进行超越分类的更丰富的解释。这也允许采用一个有原则的、统一的优化方法。

通常,单一树在实践中不足以使用。实际上使用的是集成模型,它将多个树的预测值相加在一起

在这里插入图片描述

这是一个包含两棵树的树集成的示例。每棵树的预测分数相加以获得最终分数。这两棵树试图互补。从数学上讲,可以将模型写成以下形式:

y ^ i = ∑ k = 1 K f k ( x i ) , f k ∈ F \hat{y}_i = \sum_{k=1}^K f_k(x_i), f_k \in \mathcal{F} y^i=k=1Kfk(xi),fkF

其中, K K K是树的数量, f k f_k fk 是特征空间 F F F 中的函数, F F F是所有可能的 CART 的集合。要优化的目标函数由以下公式给出:

obj ( θ ) = ∑ i n l ( y i , y ^ i ) + ∑ k = 1 K ω ( f k ) \text{obj}(\theta) = \sum_i^n l(y_i, \hat{y}_i) + \sum_{k=1}^K \omega(f_k) obj(θ)=inl(yi,y^i)+k=1Kω(fk)

其中, ω ( f k ) \omega(f_k) ω(fk)是树的复杂性。

现在有个问题:随机森林中使用的是什么模型?树集成!因此,随机森林和梯度提升树实际上是相同的模型;它们的区别在于训练的方式不同。这意味着,如果你为树集成编写一个预测服务,只需要编写一个,它应该同时适用于随机森林和梯度提升树。(有关实际示例,请参见 Treelite)这就是为什么监督学习元素如此强大的一个例子。

Tree Boosting

上述介绍了树集成模型,现在介绍训练:应该如何学习这些树呢?答案总是相同的,对于所有监督学习模型:定义一个目标函数并优化它!

设以下为目标函数(记住它总是需要包含训练损失和正则化):

obj = ∑ i = 1 n l ( y i , y ^ i ( t ) ) + ∑ i = 1 t ω ( f i ) \text{obj} = \sum_{i=1}^n l(y_i, \hat{y}_i^{(t)}) + \sum_{i=1}^t\omega(f_i) obj=i=1nl(yi,y^i(t))+i=1tω(fi)

加性训练Additive Training

第一个问题我们要问的是:树的参数是什么?你会发现我们需要学习的是那些包含树的结构和叶子得分的函数 f i f_i fi 。学习树的结构比传统的优化问题更难,传统优化问题中可以简单地取梯度。一次性学习所有的树是不可行的。相反,使用一种加法策略:固定已经学到的,一次添加一棵新树。将第 t t t步的预测值写为 y ^ i ( t ) \hat{y}_i^{(t)} y^i(t)。然后有

y ^ i ( 0 ) = 0 y ^ i ( 1 ) = f 1 ( x i ) = y ^ i ( 0 ) + f 1 ( x i ) y ^ i ( 2 ) = f 1 ( x i ) + f 2 ( x i ) = y ^ i ( 1 ) + f 2 ( x i ) … y ^ i ( t ) = ∑ k = 1 t f k ( x i ) = y ^ i ( t − 1 ) + f t ( x i ) \begin{split}\hat{y}_i^{(0)} &= 0\\ \hat{y}_i^{(1)} &= f_1(x_i) = \hat{y}_i^{(0)} + f_1(x_i)\\ \hat{y}_i^{(2)} &= f_1(x_i) + f_2(x_i)= \hat{y}_i^{(1)} + f_2(x_i)\\ &\dots\\ \hat{y}_i^{(t)} &= \sum_{k=1}^t f_k(x_i)= \hat{y}_i^{(t-1)} + f_t(x_i)\end{split} y^i(0)y^i(1)y^i(2)y^i(t)=0=f1(xi)=y^i(0)+f1(xi)=f1(xi)+f2(xi)=y^i(1)+f2(xi)=k=1tfk(xi)=y^i(t1)+ft(xi)

接下来要问的是:在每一步想要哪棵树?一个自然的想法是添加那棵优化我们目标的树。

obj ( t ) = ∑ i = 1 n l ( y i , y ^ i ( t ) ) + ∑ i = 1 t ω ( f i ) = ∑ i = 1 n l ( y i , y ^ i ( t − 1 ) + f t ( x i ) ) + ω ( f t ) + c o n s t a n t \begin{split}\text{obj}^{(t)} & = \sum_{i=1}^n l(y_i, \hat{y}_i^{(t)}) + \sum_{i=1}^t\omega(f_i) \\ & = \sum_{i=1}^n l(y_i, \hat{y}_i^{(t-1)} + f_t(x_i)) + \omega(f_t) + \mathrm{constant}\end{split} obj(t)=i=1nl(yi,y^i(t))+i=1tω(fi)=i=1nl(yi,y^i(t1)+ft(xi))+ω(ft)+constant

MSE 的形式很友好,有一个一阶项(通常称为残差)和一个二次项。对于其他感兴趣的损失(例如 logistic 损失),要获得这样一个好看的形式就不那么容易。因此,在一般情况下,将损失函数进行泰勒展开,展开到二阶项:

obj ( t ) = ∑ i = 1 n [ l ( y i , y ^ i ( t − 1 ) ) + g i f t ( x i ) + 1 2 h i f t 2 ( x i ) ] + ω ( f t ) + c o n s t a n t \text{obj}^{(t)} = \sum_{i=1}^n [l(y_i, \hat{y}_i^{(t-1)}) + g_i f_t(x_i) + \frac{1}{2} h_i f_t^2(x_i)] + \omega(f_t) + \mathrm{constant} obj(t)=i=1n[l(yi,y^i(t1))+gift(xi)+21hift2(xi)]+ω(ft)+constant

其中 g i g_i gi h i h_i hi定义如下:

g i = ∂ y ^ i ( t − 1 ) l ( y i , y ^ i ( t − 1 ) ) h i = ∂ y ^ i ( t − 1 ) 2 l ( y i , y ^ i ( t − 1 ) ) \begin{split}g_i &= \partial_{\hat{y}_i^{(t-1)}} l(y_i, \hat{y}_i^{(t-1)})\\ h_i &= \partial_{\hat{y}_i^{(t-1)}}^2 l(y_i, \hat{y}_i^{(t-1)})\end{split} gihi=y^i(t1)l(yi,y^i(t1))=y^i(t1)2l(yi,y^i(t1))

在去除所有常数之后,第 t t t 步的具体目标函数变为:

∑ i = 1 n [ g i f t ( x i ) + 1 2 h i f t 2 ( x i ) ] + ω ( f t ) \sum_{i=1}^n [g_i f_t(x_i) + \frac{1}{2} h_i f_t^2(x_i)] + \omega(f_t) i=1n[gift(xi)+21hift2(xi)]+ω(ft)

这成为新树的优化目标,这一定义的一个重要优势是目标函数的值仅取决于 g i g_i gi h i h_i hi 。这就是 XGBoost 支持自定义损失函数的方式。可以使用完全相同的求解器来优化每个损失函数,包括 logistic 回归和成对排序,该求解器将 g i g_i gi h i h_i hi 作为输入!

模型复杂度Model Complexity

上面已经介绍了训练步骤,下面介绍正则化项 regularization term!需要定义树的复杂性 ω ( f ) \omega(f) ω(f)。为了做到这一点,首先细化对树 f ( x ) f(x) f(x) 的定义:

f t ( x ) = w q ( x ) , w ∈ R T , q : R d → { 1 , 2 , ⋯ , T } . f_t(x) = w_{q(x)}, w \in R^T, q:R^d\rightarrow \{1,2,\cdots,T\} . ft(x)=wq(x),wRT,q:Rd{1,2,,T}.

在这里, w w w是叶子上的分数向量, q q q是将每个数据点分配给相应叶子的函数, T T T是叶子的数量。在XGBoost中,将树的复杂性定义为:

ω ( f ) = γ T + 1 2 λ ∑ j = 1 T w j 2 \omega(f) = \gamma T + \frac{1}{2}\lambda \sum_{j=1}^T w_j^2 ω(f)=γT+21λj=1Twj2

当然,有很多方法可以定义复杂性,但这个方法在实践中效果很好。正则化是大多数树模型处理得不够仔细或干脆忽略的一部分。这是因为传统的树学习方法只强调改善不纯度,而复杂性控制则留给了启发式方法。通过正式定义,可以更好地理解正在学习的内容,并获得在实际应用中表现良好的模型。

结构分The Structure Score

在重新构建树模型后,可以将以第 t t t个树为例的目标值写为:

obj ( t ) ≈ ∑ i = 1 n [ g i w q ( x i ) + 1 2 h i w q ( x i ) 2 ] + γ T + 1 2 λ ∑ j = 1 T w j 2 = ∑ j = 1 T [ ( ∑ i ∈ I j g i ) w j + 1 2 ( ∑ i ∈ I j h i + λ ) w j 2 ] + γ T \begin{split}\text{obj}^{(t)} &\approx \sum_{i=1}^n [g_i w_{q(x_i)} + \frac{1}{2} h_i w_{q(x_i)}^2] + \gamma T + \frac{1}{2}\lambda \sum_{j=1}^T w_j^2\\ &= \sum^T_{j=1} [(\sum_{i\in I_j} g_i) w_j + \frac{1}{2} (\sum_{i\in I_j} h_i + \lambda) w_j^2 ] + \gamma T\end{split} obj(t)i=1n[giwq(xi)+21hiwq(xi)2]+γT+21λj=1Twj2=j=1T[(iIjgi)wj+21(iIjhi+λ)wj2]+γT

其中 I j = { i ∣ q ( x i ) = j } I_j = \{i|q(x_i)=j\} Ij={iq(xi)=j}是分配给第 j j j个叶子的数据点的索引集。请注意,在第二行中,更改了求和的索引,因为同一叶子上的所有数据点得分相同。可以通过定义 G j = ∑ i ∈ I j g i G_j = \sum_{i\in I_j} g_i Gj=iIjgi H j = ∑ i ∈ I j h i H_j = \sum_{i\in I_j} h_i Hj=iIjhi进一步压缩表达式:

obj ( t ) = ∑ j = 1 T [ G j w j + 1 2 ( H j + λ ) w j 2 ] + γ T \text{obj}^{(t)} = \sum^T_{j=1} [G_jw_j + \frac{1}{2} (H_j+\lambda) w_j^2] +\gamma T obj(t)=j=1T[Gjwj+21(Hj+λ)wj2]+γT

在这个方程中, w j w_j wj 相对于彼此是独立的,形式 G j w j + 1 2 ( H j + λ ) w j 2 G_jw_j+\frac{1}{2}(H_j+\lambda)w_j^2 Gjwj+21(Hj+λ)wj2是二次的,对于给定的结构 q ( x ) q(x) q(x),可以得到的最佳 w j w_j wj和最佳目标减少是:

w j ∗ = − G j H j + λ obj ∗ = − 1 2 ∑ j = 1 T G j 2 H j + λ + γ T \begin{split}w_j^\ast &= -\frac{G_j}{H_j+\lambda}\\ \text{obj}^\ast &= -\frac{1}{2} \sum_{j=1}^T \frac{G_j^2}{H_j+\lambda} + \gamma T\end{split} wjobj=Hj+λGj=21j=1THj+λGj2+γT

最后一个方程衡量了树结构 q ( x ) q(x) q(x)有多好。

在这里插入图片描述

如果这听起来有点复杂,可以看一下图片,看看得分是如何计算的。基本上,对于给定的树结构,我们将统计信息 g i g_i gi h i h_i hi 推送到它们所属的叶子节点,将统计信息相加,并使用该公式计算树的质量。这个得分类似于决策树中的不纯度度量,只是它还考虑了模型的复杂性。

学习树结构

现在有了一种衡量树质量的方法,理想情况下,会枚举所有可能的树并选择最佳的树。实际上这是不可行的,因此将尝试一次优化树的一级。具体而言,尝试将一个叶子节点分成两个叶子节点,它的得分是

G a i n = 1 2 [ G L 2 H L + λ + G R 2 H R + λ − ( G L + G R ) 2 H L + H R + λ ] − γ Gain = \frac{1}{2} \left[\frac{G_L^2}{H_L+\lambda}+\frac{G_R^2}{H_R+\lambda}-\frac{(G_L+G_R)^2}{H_L+H_R+\lambda}\right] - \gamma Gain=21[HL+λGL2+HR+λGR2HL+HR+λ(GL+GR)2]γ

这个公式可以分解为:

    1. 新左叶子上的分数
    1. 新右叶子上的分数
    1. 原始叶子上的分数
    1. 对额外叶子的正则化

可以看到这里一个重要的事实:如果增益小于 γ \gamma γ,最好不要添加该分支。这正是树模型中剪枝技术的原理!通过使用监督学习的原理,可以自然地理解这些技术为什么有效 。

对于实值数据,通常希望搜索最佳拆分。为了有效地进行此操作,按排序顺序放置所有实例,如下图所示。

split_find.png

从左到右的扫描足以计算所有可能的拆分解决方案的结构分数,可以有效地找到最佳拆分。

注意:

加性树学习的限制

由于枚举所有可能的树结构是不可行的,一次只添加一个分割。这种方法在大多数情况下都很有效,但有一些边缘情况由于这种方法而失败。对于这些边缘情况,训练会导致一个退化的模型,因为我们一次只考虑一个特征维度。参见《Can Gradient Boosting Learn Simple Arithmetic?》以获取示例。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/667289.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Chrono Engine学习总结】1-安装配置与程序运行

本文仅用于个人安装记录。 官方安装教程 https://api.projectchrono.org/8.0.0/tutorial_install_chrono.html Windows下安装 windows下安装就按照教程好了。采用cmake-gui进行配置,建议首次安装只安装核心模块。然后依此configure下irrlicht,sensor…

6款超好用的IDEA插件,开发必备!

今天给大家介绍几款开发必备的IDEA插件: JRebel 热部署插件,让你在修改完代码后,不用再重新启动,很实用!但是,不是免费的,需要大家继续发挥下自己的聪明才智才能happy的使用 Json Parser 厌倦…

Abp 从空白的WebApplication中添加EntityFrameworkCore生成数据库

书接上文:Abp 从空白WebApplication开始 开发环境:.NET6、Volo.Abp 数据库:Sqlite 说明:纯属个人强行入门。我个人觉得按照官网的操作不舒服,所以自己研究着来,请读者根据自己的需要进行参考。我能保证的…

Ubuntu+GPU搭建Stable-Diffusion教程

【前序】已经安装anaconda 1.git拉取项目到本地 执行git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui.git 进入项目目录下 cd stable-diffusion-webui/ 2. 安装对应Python依赖包 首先安装pytorch和torchvision,若是GPU环境的用户需要安装与cu…

2024年最新幻兽帕鲁服务器搭建教程

玩转幻兽帕鲁服务器,阿里云推出新手0基础一键部署幻兽帕鲁服务器教程,傻瓜式一键部署,3分钟即可成功创建一台Palworld专属服务器,成本仅需26元,阿里云服务器网aliyunfuwuqi.com分享2024年新版基于阿里云搭建幻兽帕鲁服…

深度解析源码,Spring 如何使用三级缓存解决循环依赖

目录 一. 前言 二. 基础知识 2.1. 什么是循环依赖? 2.2. 三级缓存 2.3. 原理执行流程 三. 源码解读 3.1. 代码入口 3.2. 第一层 3.3. 第二层 3.4. 第三层 3.5. 返回第二层 3.6. 返回第一层 四. 原理深度解读 4.1. 什么要有三级缓存? 4.2.…

【HarmonyOS 4.0 应用开发实战】ArkTS 快速入门

个人名片: 🐼作者简介:一名大三在校生,喜欢AI编程🎋 🐻‍❄️个人主页🥇:落798. 🐼个人WeChat:hmmwx53 🕊️系列专栏:🖼️…

python中[[]] * (n)和[[] for _ in range(n)]的区别

1、现象 刷leetcode207的时候碰到一个坑,用[[]] * (n)初始化二维数组,逻辑是正确的,但是结果始终不对。 2、原因 最后定位是初始化语句使用错误导致的,我使用的是[[]] * (n),应该使用[[] for _ in range(n)] 3、解…

Stable Diffusion 模型下载:ReV Animated

模型介绍 该模型能够创建 2.5D 类图像生成。此模型是检查点合并,这意味着它是其他模型的产物,以创建从原始模型派生的产品。 条目内容类型大模型基础模型SD 1.5来源CIVITAI作者s6yx文件名称revAnimated_v122EOL.safetensors文件大小5.13GB 生成案例 …

redis数据库设置对象的过期时间,到期后自动删除该条数据

redis数据库设置对象的过期时间,到期后自动删除该条数据!过期时间的事情经常发生。比如大家领到了一些购物券。这张购物券有一个过期时间。必须在某节点之前(某年某月末日)之前,使用掉,否则该券就会过期,无…

Aigtek高压放大器用途是什么呢

高压放大器在电子领域中扮演着至关重要的角色,其主要作用是将低电压信号放大到更高的电压水平。这种类型的放大器广泛用于各种应用中,以下是高压放大器的用途以及其关键作用的详细介绍。 1、科学研究和实验室应用: 高压放大器在科学研究和实验…

【Linux】Linux权限

Hello everybody!在介绍完Linux操作系统常用的基本指令后,咱们就要开始权限的讲解了。如果没有看过我之前文章的宝子们可以去看看呦!有比较系统的指令讲解! 回到正题,关于权限,实际上没有大家想象中的那么神秘&#x…

解决问题(Tensorflow框架):ImportError: cannot import name ‘merge‘ from ‘keras.layers‘

看了一圈解决方案,没有找到跟我这个相关的 这就是版本兼容性问题 说句最简单的,针对我这个问题 直接把merge删除点就完事了,因为新版的tensorflow框架这个里面不包含merge,所以直接删掉问题就解决了

Linux进程信号处理:深入理解与应用(2​​)

🎬慕斯主页:修仙—别有洞天 ♈️今日夜电波:its 6pm but I miss u already.—bbbluelee 0:01━━━━━━️💟──────── 3:18 🔄 ◀️…

ElementUI Form:Form表单

ElementUI安装与使用指南 Form表单 点击下载learnelementuispringboot项目源码 效果图 el-form.vue&#xff08;Form表单&#xff09;页面效果图 项目里 el-form.vue代码 <script> export default {name: el_form,data() {var checkAge (rule, value, callback…

计算机设计大赛 深度学习 opencv python 公式识别(图像识别 机器视觉)

文章目录 0 前言1 课题说明2 效果展示3 具体实现4 关键代码实现5 算法综合效果6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于深度学习的数学公式识别算法实现 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学…

中等题 ------ 链表

文章目录 1. 删除链表中的倒数第N个节点&#xff08;1&#xff09;栈&#xff08;2&#xff09;双指针&#xff08;快慢指针&#xff09; 2. 交换链表中的节点3. 两数相加4. 合并两个链表5. 旋转链表6. 奇偶链表7. 两两交换8. k 个一组翻转链表9. 分割链表10. 分隔链表11. 重排…

19.HarmonyOS App(JAVA)依赖布局DependentLayout使用方法

layout/ability_main.xml 显示位置不对&#xff1a;检查布局文件ohos:lef_of "id:tuzi",比如显示在兔子的左侧&#xff0c;这里就会显示不对。 需要id前没有$符号。改为&#xff1a; ohos:lef_of "$id:tuzi" <?xml version"1.0" encodi…

第六讲:文件操作

第六讲:文件操作 文件夹创建文件夹移动文件夹复制文件夹删除文件夹文件操作文件读取文件写入文件文件夹 创建文件夹 定义创建文件夹函数:chmk_path()定义一个函数 chmk_path(),这个函数的功能是创建文件夹。 首先需要导入操作系统接口模块——os 模块,这个模块中包含某些函…

前端小案例——滚动文本区域(HTML+CSS, 附源码)

一、前言 实现功能: 这个案例实现了一个具有滚动功能的文本区域&#xff0c;用于显示长文本内容&#xff0c;并且可以通过滚动条来查看完整的文本内容。 实现逻辑&#xff1a; 内容布局&#xff1a;在<body>中&#xff0c;使用<div>容器创建了一个类名为listen_t…