决定给这个系列起一个响亮的名字:明月机器学习系列
我们从简单的线性模型入手,讲解了梯度下降是如何学习其中的参数的。而本篇主要是讲怎么从零开始,使用Python实现梯度下降算法。
最简单的梯度下降算法
承接上一篇文章,过两个点(1,2)和(2,1),训练一个最简单的线性模型:y = ax + b,其在参数a和参数b上的梯度为:
代入其参数更新迭代公式如下:
假设学习率为0.1。实现代码不复杂,完整如下:
import numpy as np
# 使用梯度下降算法训练模型:y = ax + b
# 给定两个点(1,2)和(2,1),过这两个点拟合一条曲线
points = [(1,2), (2,1)] # 样本数据
learning_rate = 0.1 # 学习率
epochs = 100 # 迭代次数
w = (0, 0) # 初始权重
def cal_loss(a, b):
"""计算损失"""
sum_loss = [(a*x+b-y)**2 for x, y in points]
return sum(sum_loss) / len(points)
def gradient_a(a, b):
"""损失函数在a方向上的梯度:损失函数对a求偏导"""
loss_grad = [2*(a*x+b-y)*x for x,y in points]
return sum(loss_grad)
def gradient_b(a, b):
"""损失函数在b方向上的梯度:损失函数对b求偏导"""
loss_grad = [2*(a*x+b-y) for x,y in points]
return sum(loss_grad)
def update(a, b):
"""更新权重"""
return (a - learning_rate * gradient_a(a, b),
b - learning_rate * gradient_b(a, b))
for i in range(epochs):
w = update(w[0], w[1])
loss = cal_loss(w[0], w[1])
if i % 10 == 9:
print("a=%.3f, b=%.3f, loss=%.3f" % (w[0], w[1], loss))
运行所得结果如下:
a=0.800, b=0.600, loss=1.800
a=0.440, b=0.480, loss=1.296
a=0.512, b=0.624, loss=1.166
a=0.426, b=0.667, loss=1.092
a=0.400, b=0.745, loss=1.028
。。。。。。
a=-0.916, b=2.865, loss=0.004
参数a和b越来越接近模型:y = -x + 3 这条直线。该程序支持多个点,只需要修改对应的points的定义即可。
多特征的梯度下降算法推导
上面已经实现了一个简单的梯度下降算法,但是只能对y = ax + b这样简单的情况,如果多一个特征就必须修改源码。对于y = ax + b,我们标准化一一下:
其中x0=1,也就是我们所理解的一个特征,可以理解为两个特征,只是其中一个特征的值一直都是1(对应线性模型中的常数项,任何数值乘以1都等于其本身)。写成更加通用的矩阵形式如下:
这就是通用的线性模型了。对于有m个特征的模型:
展开如下:
输入一组特征值(x0, x1, ..., xn),会得到一个预测值y,我们还是定义该模型的损失函数为:
其中n为样本数量。把预测值代入展开如下:
对于第i个参数ai计算梯度:
代入参数的梯度下降迭代更新公式:
从第t次迭代到第t+1次迭代的公式如上。
支持多特征的梯度下降算法实现
完整代码实现如下:
import numpy as np
# 使用梯度下降算法训练模型:Y = AX
# 支持多个特征
# points = [(1,1), (2,2), (4,2)] # 样本数据: y = ax + b
points = [(1,1,1), (1,2,2), (2,2,3)] # 样本数据: y = ax1 + bx2 + c
learning_rate = 0.01 # 学习率,注意对于[(1,1), (2,2), (4,2)],如果学习率为0.1,将不再收敛
epochs = 500 # 迭代次数
def parse_points(points):
"""生成特征集合目标集"""
return np.array([np.append(row[:-1], 1) for row in points]), np.array([row[-1] for row in points])
def init_w(n):
"""初始化权重"""
return np.zeros(n)
def cal_loss(w):
"""计算损失"""
sum_loss = [(sum(w*x)-y)**2 for x, y in zip(train_X, train_Y)]
return sum(sum_loss) / len(points)
def gradient(w, i):
"""计算损失函数在第i个权重上的梯度"""
loss_grad = [2*(sum(w*x) - y)*x[i] for x, y in zip(train_X, train_Y)]
grad = sum(loss_grad)
return grad
def update(w):
"""更新权重"""
new_w = [wi - learning_rate * gradient(w, i) for i, wi in zip(range(len(w)), w)]
return np.array(new_w)
train_X, train_Y = parse_points(points)
print('特征字段:', train_X)
print('目标字段:', train_Y)
w = init_w(len(train_X[0]))
print('初始权重:', w)
for i in range(epochs):
w = update(w)
loss = cal_loss(w)
if i % 10 == 9:
print(w, loss)
这是一个相对通用的梯度下降算法了。
后续可以优化的点:
ps:这是第三篇关于梯度下降的文章了感觉要写到通俗易懂也是不容易。