12-梯度与梯度下降算法(末尾有图)
文章目录
求梯度
先回忆求梯度的公式:
假如
L
=
f
(
x
,
y
,
z
)
L = f(x,y,z)
L=f(x,y,z)
(
∂
L
∂
x
,
∂
L
∂
y
,
∂
L
∂
z
.
.
.
)
(\frac{\partial L}{\partial x},\frac{\partial L}{\partial y},\frac{\partial L}{\partial z}...)
(∂x∂L,∂y∂L,∂z∂L...)
分别对x, y, z 求导:
∂
L
∂
x
=
f
(
x
+
h
,
y
,
z
)
−
f
(
x
−
h
,
y
,
z
)
2
h
\frac{\partial L}{\partial x}= \frac{f(x+h,y,z)-f(x-h,y,z)}{2h}
∂x∂L=2hf(x+h,y,z)−f(x−h,y,z)
∂ L ∂ y = f ( x , y + h , z ) − f ( x , y − h , z ) 2 h ∂ L ∂ z = f ( x , y , z + h ) − f ( x , y , z − h ) 2 h \frac{\partial L}{\partial y}= \frac{f(x,y+h,z)-f(x,y-h,z)}{2h} \\\frac{\partial L}{\partial z}= \frac{f(x,y,z+h)-f(x,y,z-h)}{2h} \\ ∂y∂L=2hf(x,y+h,z)−f(x,y−h,z)∂z∂L=2hf(x,y,z+h)−f(x,y,z−h)
接下来用python实现如何求梯度:
- 首先定义一个 L 函数,假设为:
f(x,y,z)=x**2 + y**2 + z**2
# 这里 x是个数组,可以代表x, y, z, ...
f = lambda x: np.sum(x**2)
- 接下来开始求
f
在x0
处的梯度,我们希望有一个函数,只要传入参数f, x0就能返回所要的结果,因此定义一个函数:numerical_gradient(f, x0)
def numerical_gradient(f, x0):
h = 1e-4
#产生一个与x0维度(与变量x维度一样)相同的数组,作为最后要返回的梯度
grad = np.zeros_like(x0)
for i in range(x0.size):
temp_x = x[i]
x[i] = temp_x + h
fx1 = f(x) #求 f(x+h)
x[i] = temp_x - h
fx2 = f(x) #求 f(x-h)
grad[i] = (fx1 - fx2)/(2 * h)
x[i] = temp_x # 还原值
return grad #返回求得的梯度
代码说明:
- 第7行的
x.size
可以得到 x的维度,所以循环是为了求多个维度的导数值 - 第11行,每一个维度都需要求
x[i]+h
,x[i]-h
, 再求完梯度后必须要把x值恢复到最初的状态,但x[i]+h
,x[i]-h
会导致丢失x[i]
的初值,所以第8行使用了中间变量保存初值。
接下来就可以求梯度了,例如求 f 在 (1,3,5)点处的梯度:
x0 = np.array([1.0,3.0,5.0]) #注意必须要用浮点数
print(numerical_gradient(f, x0))
#输出:
[ 2. 6. 10.]
求最小值——梯度下降法
根据梯度的含义可知,梯度就是一个向量,它的方向指向函数增加最快的地方,因此,它的负方向就指向函数下降的地方,由此引出了梯度下降算法,首先确定一个初始点,然后在该点求梯度,接着沿着梯度的方向前进一小段距离,然后继续求梯度,继续沿梯度方向前进;如此循环往复,直到下降到最低点。
用公式表示为:
x
1
=
x
1
−
η
∂
f
∂
x
1
x
2
=
x
2
−
η
∂
f
∂
x
2
.
.
.
x
n
=
x
n
−
η
∂
f
∂
x
n
x_1 = x_1 - \eta \frac{\partial f}{\partial x_1}\\x_2 = x_2 - \eta \frac{\partial f}{\partial x_2}\\.\\.\\.\\x_n = x_n - \eta \frac{\partial f}{\partial x_n}
x1=x1−η∂x1∂fx2=x2−η∂x2∂f...xn=xn−η∂xn∂f
n表示函数 f
的维度,
η
\eta
η 表示学习率,实际上就是用来控制每次前进一小段距离的长度,不能太小,不然要计算梯度的次数太多,耗费时间;也不能太大,避免跨过了最值点的位置。
根据上面的公式,用python代码实现梯度下降算法如下:
def gradient_descent(f, x0, lr=0.01, step_num=100):
x = x0
for i in range(step_num):
x -= lr * numerical_gradient(f, x)
return x
代码说明:
x0
表示最初的位置lr
(即learn
)表示学习率step_num
表示要前进的次数,越大当然可以越接近最值位置,但循环次数太多就要耗费时间了
接下来求一下 f=x**2 + y**2
取得最小值时应该在哪个位置。
gradient_descent(f, np.array(10.0, 10.0)) #注意用浮点数
#输出:
array([1.32619556, 1.32619556])
我们知道,在 (0,0) 位置 f 会取到最小值,现在求得的在大约 (1.3,1.3)的位置,说明点已经从最初的(10,10)的位置前进到了 (1.3,1.3)的位置。这个结果离实际最低点还有一段距离,说明前进的次数(即循环次数)不够,或者每次前进距离(即学习率)太短了。
把循环次数提高到500次看看:
gradient_descent(f, np.array(10.0, 10.0),step_num=500)
#输出
array([0.00041024, 0.00041024])
这个结果已经很接近了
再看看提高学习率到0.1,但循环次数不变的情况:
gradient_descent(f, np.array(10.0, 10.0),lr=0.1)
#输出
array([2.03703598e-09, 2.03703598e-09])
这个值更小了,再将学习率提高到 0.99看看:
gradient_descent(f, np.array(10.0, 10.0),lr=0.99)
#输出
array([1.32619556, 1.32619556])
结果变大了,这说明学习率不能设的太大
弄个可视化图看看前进的过程:
import matplotlib.pyplot as plt
import numpy as np
plt.rcParams['font.family'] = 'SimHei'
plt.rcParams['font.sans-serif'] = 'FangSong'
def gradient_descent(f, x0, lr=0.01, step_num=100):
x = x0
fig = plt.figure()
ax = fig.add_subplot(111)
for i in range(step_num):
x -= lr * numerical_gradient(f, x)
if i % 10 == 0 and i!=0:
ax.scatter(x[0], x[1], s=2000/i) #s表示绘制点的面积
plt.xlabel('x轴',fontdict=dict(fontsize=15))
plt.ylabel('y轴',fontdict=dict(fontsize=15))
plt.show()
return x
gradient_descent(y, np.array([10.,10.]),lr=0.01, step_num=500)
注意从右上角往左下角移动,可以看到越接近结果的地方每次前进的距离越短,说明梯度值变小了
本文来自博客园,作者:aJream,转载请记得标明出处:https://www.cnblogs.com/ajream/p/15383574.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人