深度学习--梯度下降算法(持续更新)

梯度下降(Gradient Descent,GD)算法


一.梯度下降

(一)基础

1.梯度

▽ f = ( ∂ f ∂ x , ∂ f ∂ y , ∂ f ∂ z ) \triangledown f=(\frac{\partial f}{\partial x},\frac{\partial f}{\partial y},\frac{\partial f}{\partial z}) f=(xf,yf,zf)
 

2.梯度下降

  • 梯度下降是最小化风险函数和损失函数的一种常用方法。梯度的方向实际就是函数在此点上升最快的方向,而往往需要的是损失函数的最小化,故有负号。
  • 在实际工作中,得到一组使得损失函数达到全局最小值是一种理想情况,更一般的情况是使得模型的准确度达到可接受范围的某个极小值。
     

3.梯度下降算法

θ 1 = θ 0 − α ▽ J ( θ 0 ) \theta_{1}=\theta_{0}-\alpha \triangledown J(\theta_{0}) θ1=θ0αJ(θ0)
其中, θ 1 \theta_{1} θ1 θ 1 \theta_{1} θ1表示位置始末, α \alpha α表示步长或者说学习率, J ( θ ) J(\theta) J(θ)表示 θ \theta θ的一个函数

算法实际步骤:

  • 用随机值初始化权重和偏差
  • 把输入传入网络,得到输出值
  • 计算预测值和真实值之间的误差
  • 对每一个产生误差的神经元,调整相应的(权重)值以减小误差
  • 重复迭代,直至得到网络权重的最佳值

 
代码实现:
 

(二)分类

1.批量梯度下降(Batch Gradient Descent,BGD)

θ t + 1 = θ t − α ▽ J t ( θ ) \theta_{t+1}=\theta_{t}-\alpha \triangledown J_{t}(\theta) θt+1=θtαJt(θ)
假如以MSE作为损失函数,有
y i ^ = ∑ j = 0 m w j x i , j ( w 0 = b , x i , 0 = 1 ) J ( w ) = 1 2 n ∑ i = 1 n ( y i ^ − y i ) 2 ▽ J ( w j ) = ∂ J ( w ) ∂ w j = 1 n ∑ i = 1 n ( y i ^ − y i ) x i , j w j , t + 1 = w j , t − α ▽ J t ( w j ) , t = 1 , 2 , . . . \hat{y_{i}}=\sum_{j=0}^{m} w_{j} x_{i,j} (w_{0}=b, x_{i,0}=1)\\ J(w)= \frac {1} {2n} \sum_{i=1}^{n} ( \hat{y_{i}} -y_{i})^{2}\\ \triangledown J(w_{j})=\frac{\partial J(w)}{\partial w_{j}}=\frac{1}{n} \sum_{i=1}^{n} ( \hat{y_{i}} -y_{i})x_{i,j}\\ w_{j,t+1}=w_{j,t}-\alpha \triangledown J_{t}(w_{j}),t=1,2,... yi^=j=0mwjxi,j(w0=b,xi,0=1)J(w)=2n1i=1n(yi^yi)2J(wj)=wjJ(w)=n1i=1n(yi^yi)xi,jwj,t+1=wj,tαJt(wj),t=1,2,...
其中,j为数据的特征数,m为特征总数,i为数据的样本数,n为样本总数,t为迭代次数epoch, y i ^ \hat{y_{i}} yi^为预测值, y i y_{i} yi为实际值, J ( w ) J(w) J(w)代价函数
 
(1)优点:下降方向为总体平均梯度,得到全局最优解
(2)缺点:需要计算整个数据样本集,速度会比较慢
(3)特点:

  • 对于参数的更新,所有样本都有贡献,因此计算得到的是最大梯度,一次更新的幅度较大
  • 样本不多的情况下,收敛速度会很快
     

2.小批量梯度下降(Mini-batch Gradient Descent,MBGD)

每次更新使用k个样本,一定程度的反应样本的分布情况
θ t + 1 = θ t − α k ∑ i = k ( t − 1 ) + 1 k t ▽ J i , t ( θ ) \theta_{t+1}=\theta_{t}-\frac{\alpha}{k} \sum_{i=k(t-1)+1}^{kt} \triangledown J_{i,t}(\theta) θt+1=θtkαi=k(t1)+1ktJi,t(θ)
假如以MSE作为损失函数,有
y i ^ = ∑ j = 0 m w j x i , j ( w 0 = b , x i , 0 = 1 ) J i ( w ) = 1 2 ( y i ^ − y i ) 2 ▽ J i ( w j ) = ∂ J i ( w ) ∂ w j = ( y i ^ − y i ) x i , j w j , t + 1 = w j , t − α k ∑ i = k ( t − 1 ) + 1 k t ▽ J i , t ( w j ) , t = 1 , 2 , 3 , . . . , [ n k ] \hat{y_{i}}=\sum_{j=0}^{m} w_{j} x_{i,j} (w_{0}=b, x_{i,0}=1)\\ J_{i}(w)= \frac {1} {2} ( \hat{y_{i}} -y_{i})^{2}\\ \triangledown J_{i}(w_{j})=\frac{\partial J_{i}(w)}{\partial w_{j}}=( \hat{y_{i}} -y_{i})x_{i,j}\\ w_{j,t+1}=w_{j,t}- \frac{\alpha}{k} \sum_{i=k(t-1)+1}^{kt}\triangledown J_{i,t}(w_{j}),t=1,2,3,...,[\frac{n}{k}] yi^=j=0mwjxi,j(w0=b,xi,0=1)Ji(w)=21(yi^yi)2Ji(wj)=wjJi(w)=(yi^yi)xi,jwj,t+1=wj,tkαi=k(t1)+1ktJi,t(wj),t=1,2,3,...,[kn]
其中,j为数据的特征数,m为特征总数,i为数据的样本数,n为样本总数,t为迭代次数epoch,k为批样本数量batch size, y i ^ \hat{y_{i}} yi^为预测值, y i y_{i} yi为实际值, J i ( w ) J_{i}(w) Ji(w)损失函数
 
(1)优点:保证了训练速度,又能保证最后收敛的准确度
(2)缺点:选择合适的学习率比较困难
 

3.随机梯度下降(Stochastic Gradient Descent,SGD)

每次迭代更新参数,只使用一个随机样本p
θ t + 1 = θ t − α ▽ J i = p , t ( θ ) , p ∈ [ 1 , 2 , . . . , n ] \theta_{t+1}=\theta_{t}-\alpha \triangledown J_{i=p,t}(\theta),p \in [1,2,...,n] θt+1=θtαJi=p,t(θ),p[1,2,...,n]
假如以MSE作为损失函数,有
y i ^ = ∑ j = 0 m w j x i , j ( w 0 = b , x i , 0 = 1 ) J i ( w ) = 1 2 ( y i ^ − y i ) 2 ▽ J i ( w j ) = ∂ J i ( w ) ∂ w j = ( y i ^ − y i ) x i , j w j , t + 1 = w j , t − α ▽ J i = p , t ( w j ) , 其 中 p ∈ [ 1 , 2 , . . . , n ] , t = 1 , 2 , . . . \hat{y_{i}}=\sum_{j=0}^{m} w_{j} x_{i,j} (w_{0}=b, x_{i,0}=1)\\ J_{i}(w)= \frac {1} {2} ( \hat{y_{i}} -y_{i})^{2}\\ \triangledown J_{i}(w_{j})=\frac{\partial J_{i}(w)}{\partial w_{j}}=( \hat{y_{i}} -y_{i})x_{i,j}\\ w_{j,t+1}=w_{j,t}- \alpha \triangledown J_{i=p,t}(w_{j}),其中p \in [1,2,...,n],t=1,2,... yi^=j=0mwjxi,j(w0=b,xi,0=1)Ji(w)=21(yi^yi)2Ji(wj)=wjJi(w)=(yi^yi)xi,jwj,t+1=wj,tαJi=p,t(wj),p[1,2,...,n],t=1,2,...
其中,j为数据的特征数,m为特征总数,i为数据的样本数,n为样本总数,t为迭代次数epoch,p为随机样本次序, y i ^ \hat{y_{i}} yi^为预测值, y i y_{i} yi为实际值, J i ( w ) J_{i}(w) Ji(w)损失函数
 
(1)优点:仅需要计算一个样本的梯度,训练速度很快
(2)缺点:容易从一个局部最优跳到另一个局部最优,准确度下降
(3)特点:

  • 样本较多的情况下,收敛速度快
  • 每次更新使用一个样本来近似所有样本,因此计算得的是近似的梯度,甚至存在干扰,陷入局部最优解中
     
3.1.随机平均梯度下降(Averaged Stochastic Gradient Descent,ASGD,SAG)

假如以MSE作为损失函数,只使用一个随机样本p更新梯度,有
y i , t ^ = ∑ j = 0 m w j , t x i , j ( w 0 = b , x i , 0 = 1 ) J i , t ( w ) = 1 2 ( y i , t ^ − y i ) 2 ▽ J i , t ( w j ) = ∂ J i , t ( w ) ∂ w j = ( y i , t ^ − y i ) x i , j 初 始 化 : ▽ J i , t = 1 ( w j ) = ( ∑ j = 0 m w j , t = 1 x i , j − y i ) x i , j w j , t + 1 = w j , t − α n [ ▽ J i = p , t ( w j ) + ∑ i = 1 n ▽ J i ≠ p , t − 1 ( w j ) ] 其 中 p ∈ [ 1 , 2 , . . . , n ] , t = 1 , 2 , . . . \hat{y_{i,t}}=\sum_{j=0}^{m} w_{j,t} x_{i,j} (w_{0}=b, x_{i,0}=1)\\ J_{i,t}(w)= \frac {1} {2} ( \hat{y_{i,t}} -y_{i})^{2}\\ \triangledown J_{i,t}(w_{j})=\frac{\partial J_{i,t}(w)}{\partial w_{j}}=( \hat{y_{i,t}} -y_{i})x_{i,j}\\ 初始化:\triangledown J_{i,t=1}(w_{j})=(\sum_{j=0}^{m} w_{j,t=1} x_{i,j}-y_{i})x_{i,j}\\ w_{j,t+1}=w_{j,t}- \frac{\alpha}{n} [\triangledown J_{i=p,t}(w_{j})+\sum_{i=1}^{n}\triangledown J_{i\neq p,t-1}(w_{j})]\\ 其中p \in [1,2,...,n],t=1,2,... yi,t^=j=0mwj,txi,j(w0=b,xi,0=1)Ji,t(w)=21(yi,t^yi)2Ji,t(wj)=wjJi,t(w)=(yi,t^yi)xi,jJi,t=1(wj)=(j=0mwj,t=1xi,jyi)xi,jwj,t+1=wj,tnα[Ji=p,t(wj)+i=1nJi=p,t1(wj)]p[1,2,...,n],t=1,2,...
其中,j为数据的特征数,m为特征总数,i为数据的样本数,n为样本总数,t为迭代次数epoch,k为批样本数量batch size, y i ^ \hat{y_{i}} yi^为预测值, y i y_{i} yi为实际值, J i ( w ) J_{i}(w) Ji(w)损失函数
 
在SGD方法中,虽然避开了运算成本大的问题,但对于大数据训练而言,SGD效果常不尽如人意,因为每一轮梯度更新都完全与上一轮的数据和梯度无关。随机平均梯度算法克服了这个问题,在内存中为每一个样本都维护一个旧的梯度,随机选择第p个样本来更新此样本的梯度,其他样本的梯度保持不变,然后求得所有梯度的平均值,进而更新了参数。
 

3.2 带动量的随机梯度下降(SGD with Momentum)(SGDM)
3.3 SGDW
3.4 SGDWM
3.5 Cyclical LR
3.6 SGDR

二.梯度下降优化算法

(一)补充理论知识

1.指数加权平均(Exponentially weighted average)

V t = β V t − 1 + ( 1 − β ) θ t V_{t}=\beta V_{t-1}+(1-\beta)\theta _{t} Vt=βVt1+(1β)θt
 

2.带偏差修正的指数加权平均(Bias correction in exponentially weighted average)

V t = 1 1 − β t [ β V t − 1 + ( 1 − β ) θ t ] ( V 0 = 0 ) V_{t}=\frac{1}{1-\beta ^{t}}[\beta V_{t-1}+(1-\beta)\theta _{t}]\\ (V_{0}=0) Vt=1βt1[βVt1+(1β)θt](V0=0)

3.指数加权移动平均(exponentially weighted moving average)

主要是对前期数据进行修正,当 t → ∞ t\to \infty t时, β t → 0 \beta ^{t}\to0 βt0,即对后期数据影响不大
 

4.Nesterov加速算法

5.牛顿法

(二)动量优化算法

引入物理学中的动量思想,加速梯度下降,梯度下降在不变的维度上,参数更新变快,梯度有所改变时,更新参数变慢,这样就能够加快收敛并且减少动荡
 

1.动量momentum

参数更新时在一定程度上保留之前更新的方向
v t + 1 = β v t + α ▽ J ( θ t ) θ t + 1 = θ t − v t + 1 v_{t+1}=\beta v_{t}+\alpha \triangledown J(\theta_{t})\\ \theta_{t+1}=\theta_{t}-v_{t+1} vt+1=βvt+αJ(θt)θt+1=θtvt+1
 
(1)特点:

  • 在梯度方向改变时,momentum能够降低参数更新速度,从而减少震荡
  • 在梯度方向相同时,momentum可以加速参数更新,从而加速收敛
     

2.加速梯度Nesterov Accelerated Gradient(NAG)

momentum的改进,在梯度更新时做一个矫正,具体做法就是在当前的梯度J上添加上一时刻的动量
v t + 1 = β v t − α ▽ J ( θ t + β v t ) θ t + 1 = θ t + v t + 1 v_{t+1}=\beta v_{t}-\alpha \triangledown J(\theta_{t}+ \beta v_{t})\\ \theta_{t+1}=\theta_{t}+v_{t+1} vt+1=βvtαJ(θt+βvt)θt+1=θt+vt+1
缺点:
这个算法会导致运行速度巨慢无比,比momentum要慢两倍,因此在实际实现过程中几乎没人直接用这个算法,而都是采用了变形版本。
 
推导过程:
θ t ′ = θ t + β v t \theta'_{t}=\theta_{t}+ \beta v_{t} θt=θt+βvt,得
θ t + 1 = θ t + β v t − α ▽ J ( θ t + β v t ) \theta_{t+1}=\theta_{t}+\beta v_{t}-\alpha \triangledown J(\theta_{t}+ \beta v_{t}) θt+1=θt+βvtαJ(θt+βvt)
⇒ θ t + 1 ′ − β v t + 1 = θ t ′ − α ▽ J ( θ t ′ ) \Rightarrow\theta'_{t+1}-\beta v_{t+1}=\theta'_{t}-\alpha \triangledown J(\theta'_{t}) θt+1βvt+1=θtαJ(θt)
⇒ θ t + 1 ′ = θ t ′ + β [ β v t − α ▽ J ( θ t + β v t ) ] − α ▽ J ( θ t ′ ) \Rightarrow\theta'_{t+1}=\theta'_{t}+\beta[\beta v_{t}-\alpha \triangledown J(\theta_{t}+ \beta v_{t})]-\alpha \triangledown J(\theta'_{t}) θt+1=θt+β[βvtαJ(θt+βvt)]αJ(θt)
⇒ θ t + 1 ′ = θ t ′ + β 2 v t − α ( 1 + β ) ▽ J ( θ t ′ ) \Rightarrow\theta'_{t+1}=\theta'_{t}+\beta^{2}v_{t}-\alpha(1+\beta) \triangledown J(\theta'_{t}) θt+1=θt+β2vtα(1+β)J(θt)
⇒ θ t + 1 = θ t + β 2 v t − α ( 1 + β ) ▽ J ( θ t ) \Rightarrow\theta_{t+1}=\theta_{t}+\beta^{2}v_{t}-\alpha(1+\beta) \triangledown J(\theta_{t}) θt+1=θt+β2vtα(1+β)J(θt)
 
变形版本:
v t + 1 = β 2 v t − α ( 1 + β ) ▽ J ( θ t ) θ t + 1 = θ t + v t + 1 v_{t+1}=\beta^{2}v_{t}-\alpha(1+\beta) \triangledown J(\theta_{t})\\ \theta_{t+1}=\theta_{t}+v_{t+1} vt+1=β2vtα(1+β)J(θt)θt+1=θt+vt+1


在这里插入图片描述

请添加图片描述
Momentum:(蓝色)先更新梯度,然后在原来动量方向迈一大步
NAG:(绿色)先在原来动量方向(棕)迈一大步,然后算梯度(红),得到矫正之后的绿色线
 

(三)传播优化算法

1.弹性传播Resilient propagation(Rprop)

2.均方根传播Root Mean Square propagation(RMSprop)

Adadelta的一个特例,当ρ=0.5时,E就变为了求梯度平方和的平均数;如果再求根,就变成了RMS(均方根)
g t = ▽ J ( θ t ) G t = β G t − 1 + ( 1 − β ) g t 2 θ t + 1 = θ t − α G t + ε g t g_{t}=\triangledown J(\theta_{t})\\ G_{t}=\beta G_{t-1}+(1-\beta)g_{t}^{2}\\ \theta_{t+1}=\theta_{t}-\frac{\alpha}{\sqrt{G_{t}+\varepsilon}} g_{t} gt=J(θt)Gt=βGt1+(1β)gt2θt+1=θtGt+ε αgt
 
(1)特点:·RMSprop算是AdaGrad的一种发展,Adadelta的变体,效果趋于二者之间·适合处理非平稳目标,对RNN效果很好
 

(四)自适应学习率算法

1.自适应梯度Adaptive Gradient(AdaGrad)

g t = ▽ J ( θ t ) G t = G t − 1 + g t 2 θ t + 1 = θ t − α G t + ε g t g_{t}=\triangledown J(\theta_{t})\\ G_{t}=G_{t-1}+g_{t}^{2}\\ \theta_{t+1}=\theta_{t}-\frac{\alpha}{\sqrt{G_{t}+\varepsilon}} g_{t} gt=J(θt)Gt=Gt1+gt2θt+1=θtGt+ε αgt
其中 α \alpha α为学习率,一般取 0.01 0.01 0.01 ε \varepsilon ε是为了防止分母为0,一般取 1 0 − 7 10^{-7} 107
 
(1)优点:前期在参数空间更为平缓的方向,会取得更大的进步;对于梯度较大的参数,学习率会变得较小。而对于梯度较小的参数,则效果相反。这样就可以使得参数在平缓的地方下降的稍微快些,不至于徘徊不前。
(2)缺点:使得学习率过早,过量地减少;由于是累积梯度的平方,后期会导致梯度消失
 

2.Adadelta

AdaGrad的一个扩展
初 始 化 : E ( g 2 ) t = 0 = 0 , E ( h 2 ) t = 0 = 0 g t = ▽ J ( θ t ) E ( g 2 ) t = β E ( g 2 ) t − 1 + ( 1 − β ) g t 2 h t = E ( h 2 ) t − 1 + ε E ( g 2 ) t + ε g t E ( h 2 ) t = β E ( h 2 ) t − 1 + ( 1 − β ) h t 2 θ t + 1 = θ t − h t 初始化:E(g^{2})_{t=0}=0,E(h^{2})_{t=0}=0\\ g_{t}=\triangledown J(\theta_{t})\\ E(g^{2})_{t}=\beta E(g^{2})_{t-1}+(1-\beta) g_{t}^{2}\\ h_{t}= \frac {\sqrt {E(h^{2})_{t-1}+\varepsilon} }{\sqrt{E(g^{2})_{t}+\varepsilon }} g_{t}\\ E(h^{2})_{t}=\beta E(h^{2})_{t-1}+(1-\beta) h_{t}^{2}\\ \theta_{t+1}=\theta_{t}-h_{t} E(g2)t=0=0,E(h2)t=0=0gt=J(θt)E(g2)t=βE(g2)t1+(1β)gt2ht=E(g2)t+ε E(h2)t1+ε gtE(h2)t=βE(h2)t1+(1β)ht2θt+1=θtht
其中 ε \varepsilon ε是为了防止分母为0,一般取 1 0 − 6 10^{-6} 106
 
(1)特点:

  • 训练前中期,加速效果不错,很快
  • 训练后期。反复在局部最小值附近抖动
  • 不用依赖于全局学习率,手工设置一个全局学习率不会影响最终结果
     

3.自适应动量估计Adaptive Momentum Estimation(Adam)

初 始 化 : m 0 = 0 , v 0 = 0 g t = ▽ J ( θ t ) m t = 1 1 − β 1 t [ β 1 m t − 1 + ( 1 − β 1 ) g t ] v t = 1 1 − β 2 t [ β 2 v t − 1 + ( 1 − β 2 ) g t 2 ] θ t + 1 = θ t − α v t + ε m t 初始化:m_{0}=0,v_{0}=0\\ g_{t}=\triangledown J(\theta_{t})\\ m_{t}=\frac{1}{1-\beta_{1} ^{t}}[\beta_{1} m_{t-1}+(1-\beta_{1})g_{t}]\\ v_{t}=\frac{1}{1-\beta_{2} ^{t}}[\beta_{2} v_{t-1}+(1-\beta_{2})g_{t}^{2}]\\ \theta_{t+1}=\theta_{t}-\frac{\alpha}{\sqrt{v_{t}}+\varepsilon} m_{t} m0=0,v0=0gt=J(θt)mt=1β1t1[β1mt1+(1β1)gt]vt=1β2t1[β2vt1+(1β2)gt2]θt+1=θtvt +εαmt
其中 α \alpha α为学习率,一般取 0.001 0.001 0.001 β 1 \beta_{1} β1 β 2 \beta_{2} β2为平滑常数或衰减速率,一般分别取 0.9 0.9 0.9 0.999 0.999 0.999 ε \varepsilon ε是为了防止分母为0,一般取 1 0 − 8 10^{-8} 108
 
(1)说明:

  • Adam也同样需要求梯度平方和v(也就是RMSPropz中的G)
  • 使用新的变量m对梯度进行平滑的更新
  • 会对梯度的梯度平方和都进行有保留的更新
  • 会对梯度的梯度平方和都进行有保留的更新

(2)特点:

  • 参数比较平稳
  • 善于处理稀疏梯度,善于处理非平稳目标
  • 为不同的参数计算不同的自适应学习率
  • 也适用于大多非凸优化问题——适用于大数据集和高维空间
     
3.1 Adamax

Adam加入学习率上限
 

3.2 AdamW

Adam加入权重衰减
 

3.3 AMSGrad
3.4 Nadam

Adam中引入Nesterov加速效果
 

3.5 SparseAdam

针对稀疏(sparse)张量的Adam
 

3.6 AdaBound

(五)其他

1.SWATS

2.RAdam

3.Lookahead

4.Nesterov accelerated gradient (NAG)

方法对比

算法 优点 缺点 适用情况
批量梯度下降 目标函数为凸函数时,可以找到全局最优值 收敛速度慢,需要用到全部数据,内存消耗大 不适用于大数据集,不能在线更新模型
随机梯度下降 避免冗余数据的干扰,收敛速度加快,能够在线学习 更新值的方差较大,收敛过程会产生波动,可能落入极小值,选择合适的学习率比较困难 适用于需要在线更新的模型,适用于大规模训练样本情况
小批量梯度下降 降低更新值的方差,收敛较为稳定 选择合适的学习率比较困难
Momentum 能够在相关方向加速SGD,抑制振荡,从而加快收敛 需要人工设定学习率 适用于有可靠的初始化参数
Nesterov 梯度在大的跳跃后,进行计算对当前梯度进行校正 需要人工设定学习率
Adagrad 不需要对每个学习率手工地调节 仍依赖于人工设置一个全局学习率,学习率设置过大,对梯度的调节太大,中后期,梯度接近于0,使得训练提前结束 需要快速收敛,训练复杂网络时:适合处理稀疏梯度
Adadelta 不需要预设一个默认学习率,训练初中期,加速效果不错,很快,可以避免参数更新时两边单位不统一的问题 训练后期,反复在局部最小值附近抖动 需要快速收敛,训练复杂网络时
RMSprop 解决Adagrad激进的学习率缩减问题 依然依赖于全局学习率 需要快速收敛,训练复杂网络时;适合处理非平稳目标-对于RNN效果很好
Adam 对内存需求较小,为不同的参数计算不同的自适应学习率 需要快速收敛,训练复杂网络时;善于处理稀疏梯度和处理非平稳目标的优点,也适用于大多非凸优化-适用于大数据集和高维空间
  • 对于稀疏数据,尽量使用学习率可自适应的优化方法
  • SGD通常训练时间更长,但是结果更可靠
  • 如果在意更快的收敛,推荐使用学习率自适应的优化方法
  • Adadelta,RMSprop,Adam是比较相近的算法

以上未完待更新,仅供个人学习,侵权联系删除,如有错误或不足之处可指出,以便改进。

猜你喜欢

转载自blog.csdn.net/abc31431415926/article/details/127968430