import numpy as np
class net:
def __init__(self, size):
self.w = np.random.rand(size)
self.b = 0
self.for_y = 0
# 记录forward数据
self.loss = 10
# 记录loss大小
self.grad_w = 1
# 记录w 权重的梯度
self.grad_b = 1
# 记录b 偏置的梯度
def forward(self, rx):
self.for_y = rx*self.w+self.b
# 简单的线性方程:y=kx+b
def Loss(self, ry):
losser = (ry - self.for_y)**2
self.loss = 0.5*losser.mean()
# 采用MSE 均方差 乘以0.5只是便于求导无其他意义
def backward(self, rx, ry, rate):
# 反向传播
grad_w = (self.for_y - ry) * rx
grad_b = (self.for_y - ry)
# 记录w 和b 的梯度
self.grad_w = grad_w.mean()
self.grad_b = grad_b.mean()
# 取得w和b的梯度的均值
self.w = self.w - rate*self.grad_w
self.b = self.b - rate*self.grad_b
# 根据一定的学习率rate 更新 w 权重 和 b偏置
# 这里采用的梯度下降的方法是 BGD 批量梯度下降法
def zero(self):
self.grad_w = 0
self.grad_b = 0
# 梯度清零
def replay(self, size):
self.w = np.random.rand(size)
# 重置w权重
def pridect(self, nx):
return nx*self.w.mean()+self.b
# 预测
x = np.array([1, 3, 5, 20, 30, 40, 50])
y = np.array([5, 11, 17, 62, 92, 122, 152])
# 训练集
nx = np.array([0, -1, 10])
ny = np.array([2, -1, 32])
# 测试集
n = net(len(x))
# 初始化网络
ac = 0
# 记录正确率
while ac < 0.8:
# 当正确率高于0.8时退出
count = 0
# 记录正确的个数
n.forward(x)
# 向前传播
n.Loss(y)
# 计算损失
print("loss:", n.loss)
n.zero()
# 梯度清零
n.backward(x, y, 0.002)
# 反向传播 0.001的学习率
if last_loss == n.loss:
n.replay(len(x))
# 如果loss值不变了 就刷新w权重
# 至于loss不变的原因还有待学习
last_loss = n.loss
py = n.pridect(nx)
# 进行测试预测
result = py - ny
# result保存的是预测的数据跟实际数据的差值
print(result)
for i in result:
if abs(i) < 0.1:
# 当差值小于0.1视为正确 count++
count += 1
ac = count/len(result)
# 输出ac值
print("准确率:", count/len(result))
# 输出w 和 b
print(n.w.mean(), n.b)
以上代码是我用python写的一个简单的线性回归模型,因为本来数据量也不多,所以我用的是BGD批量梯度下降的方法,损失函数用的是传统的MES均方差。
运行效果:
结论:
其实这是一个y=3x+2的直线回归的程序。而程序运行的结果是w=3.00,b=2.08与正确答案很是接近了。所以我们这个程序还是算成功的,尽管可能有点不规范。
总结:
什么是损失函数?
损失函数(loss function)就是一种用来衡量模型的预测值与真实值的差异程度的运算函数。线性回归任务常用的损失函数是MSE(均方差损失),分类任务常用的损失函数是Crossentropy(交叉熵损失),当然还有其他的损失函数。损失函数用在合适的任务上,模型效果会更好。
什么叫做梯度下降以及为什么需要梯度下降?
首先,我们思考一个问题,我们是不是需要把损失函数loss也就是损失调整到最小值?这样才能说明我们的模型的预测效果很好,至少在训练集上很好。
那么问题是不是就变换到求一个(损失)函数的最小值的问题了?求一个函数的最小值,我们首先想到的就是求导,当导函数为0的时候,就会有极值的出现。而损失函数是由w(一组权重数组)和b组成的,所以一般是有多个变量的,那么就是各个变量的偏导同时为0的时候,就会有极值出现。
那么我们为什么不直接写一个程序快速地算出让导数,偏导数都为0的点呢?其实在机器学习里面,损失函数在大多情况都是不可逆的函数。何为不可逆的函数?简单来说,就是知道x求y容易,知道y求x难,甚至不可能。而我们根据导数,偏导为0,来求点坐标,无异于就是知道y求x,故在大部分情况下是不可能求出的。
所以这个时候提出了一种方法---梯度下降(盲人下山)。
梯度是一个向量(矢量),表示函数在该点处的方向导数沿着该方向取得最大值,即函数在该点处沿着该方向(此梯度的方向)变化最快,变化率最大(为该梯度的模)。
我们知道函数如果沿着梯度的方向变化是最快的,换而言之也是最陡峭的,那如果函数沿着梯度的反方向变化就是最慢的,换而言之也就是最平坦的。如果我们每次都朝着平坦的方向走,函数就会慢慢收敛到一个点,这时候的梯度就会为0,也就是极值点的位置。
具象化就是w=w-rate*grad。这个就是代码里面更新权重矩阵的梯度下降式子。grad是梯度,rate是学习率,也可以称为步长,负号就是代表着朝着梯度的反方向走。
就举个简单的二次函数的例子,如图,我们取个(1,1)的点,该点的导数值为2,那么梯度就是(2,0)是表现为在X轴正方向上的一个箭头,如果我们的x继续朝着梯度所指的方向变化(正方向)的话,我们的导数值是不是会进一步变大?所以如果这个时候我们朝着梯度的反方向走,去减小我们的x,那么我们的导数值是不是会变小,也就是收敛?到一定程度后,我们就会收敛到0,这个时候就不用前后走了。而rate学习率就是我们每次走的步长,不能太大,不然你只会在梯度上反复左右横跳(一会是正方向的,一会又是负方向的),就达不到收敛的效果了。
梯度下降的方法有很多种:常见的有SGD,BGD,MBGD等不同任务可以选择合适的梯度下降的方法,也叫做优化器。
模型训练的一般流程?
数据集的获取,训练集和测试集的分割,训练集和测试集的数据归一化,模型设置,模型训练,模型保存,模型预测,模型评估。
数据的归一化,是有助于梯度下降的平稳实现的。一开始的数据可能是分布过于散乱,当我们把数据映射到一个小的区间,可以是[-1,1],那么数据整体的分布就比较集中了。
最后以上都是我个人学习的见解,如有错误,请提出您宝贵的建议!谢谢!