梯度下降算法可视化


一、机器学习
机器学习用一句话概括就是“根据数据找函数”。具体相关内容可以访问链: https://www.cnblogs.com/subconscious/p/4107357.html 。个人觉得讲的通俗易懂,适合新手和初级玩家。

举个例子,假设女生择偶时主要根据“高富帅”来决定是否要在一起,那么先通过采访或者问卷调查得到一大批数据样本,每个样本为不同女生的择偶标准,这时我们就可以对这些样本进行统计分析,找出通用的规律,我们可以将其表示为:

这样我们男生就可以知道绝大数女性择偶标准了,可以将自己的情况输入到这棵决策树中,看看自己是“pass”还是”ok”。此处建议:如果身高没希望了,就整整颜值,如果这两个都没希望,那么就好好提高自己的能力,提高收入。
再比如下图所示的离散数据点,能否根据历史数据得到一个能表征内部关系的函数,这样当有新的x时,便可以知道y。

二、神经网络
人工神经网络(Artificial Neural Networks,简写为ANNs)也简称为神经网络(NNs)或称作连接模型(Connection Model),它是一种模仿动物神经网络行为特征,进行分布式并行信息处理的算法数学模型。这种网络依靠系统的复杂程度,通过调整内部大量节点之间相互连接的关系,从而达到处理信息的目的。本质上讲就是通过线性加权和非线性函数转换来逼近真实值。详情可以访问链接 https://blog.csdn.net/illikang/article/details/82019945。

低维无法线性可分,通过映射到高维可以线性可分。(第二张三维图可以使用一个线性平面进行二分)。
![]() |
![]() |
梯度下降法简介
1.梯度方向是函数值变化最快的方向,沿着梯度的负方向可以最快收敛到极值点,即梯度下降法。梯度下降法是神经网络发展的基础。
2.BP算法 (链式法则)https://blog.csdn.net/qq_42570457/article/details/81454008
3.优化算法(批量梯度下降法、随机梯度下降法、自适应梯度下降法、动量梯度下降法等等)
例1:拟合线性函数 y=w1 x1+w2x2
步骤:
- 生成数据样本(添加一定的噪声)——->(X,Y)
- 随机初始化w1,w2
- 计算y值
- 计算损失函数
- 根据损失函数分别对w1和w2求偏导
- 对w1和w2进行更新
- 重复3-6,直到优化到满意解
不同优化算法表先表现
1、批量梯度下降算法(使用所有的样本来更新):当数据量较大时,速度比较慢,占用内存大,但能够代表整体。
(1)lr=0.0001 epoch=500(学习率较小时,收敛的比较慢,花费的时间较长)
![]() |
![]() |
(2)lr=0.01 epoch=100(学习率较大时,前期下降较快,后期不稳定,容易摆动)
![]() |
![]() |
2、随机梯度下降算法(只根据一个样本来更新参数):训练速度快,但容易陷入局部极小值,甚至很难收敛。
(1) lr=0.0001 epoch=1000
![]() |
![]() |
批量梯度下降算法和随机梯度下降算法是两个极端,一般会进行中和,选取小批量来更新。
3、自适应梯度下降算法(初始值学习率可以设置成较大值,随着迭代次数的增加逐渐减小,且每个参数有自己的学习率):刚开始更新比较快,越往后会因为学习率较小而导致收敛较慢将第一种方法的学习率设置为1时,迭代过程如下图所示,可以发现效果非常差:
![]() |
使用自适应梯度下降算法,设置lr=1,epoch=500
![]() |
![]() |
例2:求解损失函数极值:y=x2-y2
第一个例子是一种典型的极值点为最优点,但在实际的网络中,往往存在多个局部极值点或者鞍点,因此容易陷入到鞍点。设初始值为(10,0)。
使用批量梯度下降算法,lr=0.2 epoch=500
![]() |
![]() |
造成神经网络难以优化的一个重要(乃至主要)原因不是高维优化问题中有很多局部极值,而是存在大量鞍点。https://arxiv.org/pdf/1406.2572v1.pdf。可以使用动量梯度下降法等算法来解决。
使用批量梯度梯度下降算法,lr=1,epoch=500
![]() |
![]() |
4、动量梯度下降算法(使收敛更快,减少震荡。梯度方向不变时,收敛更快,梯度方向改变时,减小震荡。收敛更稳定;有能力跳出局部极值)
设置lr=0.01,epoch=500
![]() |
![]() |
以上无法跳出鞍点的原因是:初始值y=0,而dy=-2y,使得不管在什么时候,dy=0,因此y永远为0,从而使得迭代的轨迹在y=0这条线上徘徊。如果重新初始化x,y的值为(10,0.01),则:
lr=0.004,epoch=500:
![]() |
![]() |
添加随机扰动,设初始点为(10,0),lr=0.004,epoch=500:
![]() |
![]() |
除了以上将的优化算法外,还有很多梯度下降算法,目前常用的是Adam等。
最后附上以上可视化以及相关梯度下降算法的源代码:
import numpy as np import pandas as pd import matplotlib.pyplot as plt from matplotlib import animation from mpl_toolkits.mplot3d import Axes3D %matplotlib notebook
#拟合y=w1x1+w2x2 class Net: def __init__(self,n,best_w,input_size,lr): self.n=n self.input_size=input_size self.best_w=np.array(best_w).reshape(-1,1) self.w=np.array([-10.,10.]).reshape(-1,1) self.w1_history=[self.w[0].copy()[0]] self.w2_history=[self.w[1].copy()[0]] self.z_history=[] self.g_w1=[""] self.g_w2=[""] self.lr=lr self.w_sum=np.zeros((self.w.shape[0],self.w.shape[1])) self.old_gw=np.zeros((self.w.shape[0],self.w.shape[1])) self.get_data() def get_data(self): self.x_data=np.random.randn(self.n,self.input_size) noise=np.random.randn(self.n,1) self.y_data=self.x_data.dot(self.best_w)+0.2*noise def get_loss(self): return 0.5*np.square(self.out-self.y_data).sum() def forward(self): self.out=self.x_data.dot(self.w) def backward(self): g_l=self.out-self.y_data g_w=self.x_data.T.dot(g_l) self.w-=self.lr*g_w self.w1_history.append(self.w[0].copy()[0]) self.w2_history.append(self.w[1].copy()[0]) self.g_w1.append(g_w[0].copy()) self.g_w2.append(g_w[1].copy()) def Adagrad_backward(self): eps = 1e-6 g_l=self.out-self.y_data g_w=self.x_data.T.dot(g_l) self.w_sum+=g_w**2 self.w-=self.lr/np.sqrt(self.w_sum+eps)*g_w self.w1_history.append(self.w[0].copy()[0]) self.w2_history.append(self.w[1].copy()[0]) self.g_w1.append(g_w[0].copy()) self.g_w2.append(g_w[1].copy()) def Momentum_backward(self,gamma=0.01): g_l=self.out-self.y_data g_w=self.x_data.T.dot(g_l) G_w=gamma*self.old_gw+(1-gamma)*g_w self.old_gw=g_w self.w-=self.lr*G_w self.w1_history.append(self.w[0].copy()[0]) self.w2_history.append(self.w[1].copy()[0]) self.g_w1.append(G_w[0].copy()) self.g_w2.append(G_w[1].copy()) def SGD_backward(self): select_index=np.random.randint(0,self.n) g_l=self.out[[select_index]]-self.y_data[[select_index]] g_w=self.x_data[[select_index]].T.dot(g_l) self.w-=self.lr*g_w self.w1_history.append(self.w[0].copy()[0]) self.w2_history.append(self.w[1].copy()[0]) self.g_w1.append(g_w[0].copy()) self.g_w2.append(g_w[1].copy()) def surface_view(self): fig = plt.figure() ax3 = fig.gca(projection='3d') w1=np.linspace(-10,10,100) w2=np.linspace(-10,10,100) X,Y = np.meshgrid(w1,w2) y_data=np.concatenate([self.y_data for i in range(X.shape[1])],axis=1) Z=np.zeros((X.shape[0],X.shape[1])) for i in range(len(Y)): temp_X=np.concatenate((X[0],Y[i]),axis=0).reshape(-1,X.shape[1]) Z[i]=0.5*np.square(self.x_data.dot(temp_X)-y_data).sum(axis=0) ax3.plot_surface(X,Y,Z,cmap='rainbow',alpha=0.75) ax3_x,ax3_y,ax3_z=[],[],[] ax3.plot([],[],[],color="black",linewidth=3.0) gx_text=ax3.text(0,0,22000,"",fontsize=12) gy_text=ax3.text(0,0,20000,"",fontsize=12) loss_text=ax3.text(0,0,18000,"",fontsize=12) def view_update(num): ax3_x.append(self.w1_history[num]) ax3_y.append(self.w2_history[num]) ax3_z.append(self.z_history[num]) ax3.plot(ax3_x,ax3_y,ax3_z,color="black",linewidth=3.0) gx_text.set_text("g_w1={}".format(self.g_w1[num])) gy_text.set_text("g_w2={}".format(self.g_w2[num])) loss_text.set_text("loss={}".format(self.z_history[num])) return (ax3,gx_text,gy_text,loss_text) ani = animation.FuncAnimation(fig=fig,func=view_update,frames=np.arange(0,len(self.w1_history)),interval=80,blit=True) ax3.text(self.w1_history[0]+0.1, self.w2_history[0]+0.2, self.z_history[0]+0.2,'start', color='r') ax3.text(self.w1_history[-1]+0.1, self.w2_history[-1]+0.2, self.z_history[-1]+0.2,'end', color='r') ax3.set_xlabel("x") ax3.set_ylabel("y") ax3.set_zlabel("loss") ani.save('二元一次函数agd小0.0001.gif', writer='imagemagick', fps=10) plt.show() def view(self): w1=np.linspace(-10,10,100) w2=np.linspace(-10,10,100) X,Y = np.meshgrid(w1,w2) y_data=np.concatenate([self.y_data for i in range(X.shape[1])],axis=1) Z=np.zeros((X.shape[0],X.shape[1])) for i in range(len(Y)): temp_X=np.concatenate((X[0],Y[i]),axis=0).reshape(-1,X.shape[1]) Z[i]=0.5*np.square(self.x_data.dot(temp_X)-y_data).sum(axis=0) plt.figure(figsize=(8,6)) contour=plt.contour(X,Y,Z,6,colors='k',offset=-1) plt.clabel(contour,fontsize=10,colors='k') plt.plot(self.w1_history,self.w2_history,color="r") plt.text(self.w1_history[0]+0.1, self.w2_history[0]+0.2,'start', color='r') plt.text(self.w1_history[-1]+0.1, self.w2_history[-1]+0.2,'end', color='r') plt.scatter(self.best_w[0],self.best_w[1],marker="*",color="green") plt.xlabel('w1') plt.ylabel('w2') plt.show() def train(self): self.forward() loss=self.get_loss() self.z_history.append(loss) epoch=50 for i in range(epoch): self.Adagrad_backward() self.forward() loss=self.get_loss() self.z_history.append(loss) self.view() self.surface_view()
#求解损失函数x^2+y^2 class Net: def __init__(self,init_x,init_y,lr): self.x=init_x self.y=init_y self.lr=lr self.x_history=[init_x] self.y_history=[init_y] self.z_history=[init_x**2-init_y**2] self.g_x=[""] self.g_y=[""] self.x_sum=0 self.y_sum=0 self.old_gx=0 self.old_gy=0 def surface_view(self): fig = plt.figure() ax3 = fig.gca(projection='3d') x_data=np.arange(-10,10,0.5) y_data=np.arange(-10,10,0.5) X, Y = np.meshgrid(x_data, y_data) Z = X**2-Y**2 ax3.plot_surface(X,Y,Z,cmap='rainbow',alpha=0.75) ax3_x,ax3_y,ax3_z=[],[],[] ax3.plot([],[],[],color="black",linewidth=3.0) gx_text=ax3.text(0,0,210,"",fontsize=12) gy_text=ax3.text(0,0,190,"",fontsize=12) loss_text=ax3.text(0,0,170,"",fontsize=12) def view_update(num): ax3_x.append(self.x_history[num]) ax3_y.append(self.y_history[num]) ax3_z.append(self.z_history[num]) ax3.plot(ax3_x,ax3_y,ax3_z,color="black",linewidth=3.0) gx_text.set_text("g_x={}".format(self.g_x[num])) gy_text.set_text("g_y={}".format(self.g_y[num])) loss_text.set_text("loss={}".format(self.z_history[num])) return (ax3,gx_text,gy_text,loss_text) ani = animation.FuncAnimation(fig=fig,func=view_update,frames=np.arange(0,len(self.x_history)),interval=200,blit=True) ax3.text(self.x_history[0]+0.1, self.y_history[0]+0.2, self.z_history[0]+0.2,'start', color='r') ax3.text(self.x_history[-1]+0.1, self.y_history[-1]+0.2, self.z_history[-1]+0.2,'end', color='r') ax3.set_xlabel("x") ax3.set_ylabel("y") ax3.set_zlabel("loss") ax3.view_init(elev=44,azim=-95)#改变绘制图像的视角,即相机的位置,azim沿着z轴旋转,elev沿着y轴 ani.save('梯度下降算法小2.gif', writer='imagemagick', fps=10) plt.show() def view(self): plt.figure(figsize=(8,6)) x_data=np.arange(-10,10,0.5) y_data=np.arange(-10,10,0.5) X, Y = np.meshgrid(x_data, y_data) Z = X**2-Y**2 contour=plt.contour(X,Y,Z,6,colors='k',offset=-1) plt.clabel(contour,fontsize=10,colors='k') plt.plot(self.x_history,self.y_history,color="r") plt.text(self.x_history[0]+0.1, self.y_history[0]+0.2,'start', color='r') plt.text(self.x_history[-1]+0.1, self.y_history[-1]+0.2,'end', color='r') plt.xlabel('x') plt.ylabel('y') plt.show() def get_loss(self): return self.x**2-self.y**2 def backward(self): g_x=2*self.x g_y=-2*self.y self.x-=self.lr*g_x self.y-=self.lr*g_y self.x_history.append(self.x) self.y_history.append(self.y) self.g_x.append(g_x) self.g_y.append(g_y) def jump_min(self): g_x=2*self.x g_y=-2*self.y self.x-=self.lr*g_x self.y-=self.lr*g_y self.x_history.append(self.x) self.y_history.append(self.y) self.g_x.append(g_x) self.g_y.append(g_y) def Adagrad_backward(self): eps = 1e-6 g_x=2*self.x g_y=-2*self.y self.x_sum+=g_x**2 self.y_sum+=g_y**2 self.x-=self.lr/np.sqrt(self.x_sum+eps)*g_x self.y-=self.lr/np.sqrt(self.y_sum+eps)*g_y self.x_history.append(self.x) self.y_history.append(self.y) self.g_x.append(g_x) self.g_y.append(g_y) def RMSProp_backward(self,gamma=0.2): eps = 1e-6 g_x=2*self.x g_y=-2*self.y self.x_sum=gamma*self.x_sum+(1-gamma)*g_x**2 self.y_sum=gamma*self.y_sum+(1-gamma)*g_y**2 self.x-=self.lr/np.sqrt(self.x_sum+eps)*g_x self.y-=self.lr/np.sqrt(self.y_sum+eps)*g_y self.x_history.append(self.x) self.y_history.append(self.y) self.g_x.append(g_x) self.g_y.append(g_y) def Momentum_backward(self,gamma=0.9): g_x=2*self.x g_y=-2*self.y+0.01*np.random.rand() G_x=gamma*self.old_gx+g_x G_y=gamma*self.old_gy+g_y self.old_gx=g_x self.old_gy=g_y self.x-=self.lr*G_x self.y-=self.lr*G_y self.x_history.append(self.x) self.y_history.append(self.y) self.g_x.append(G_x) self.g_y.append(G_y) def train(self): for i in range(50): self.Momentum_backward() self.z_history.append(self.get_loss()) self.surface_view() self.view()
例2:拟合一元二次函数:y=2x^2+1
class Net:
def __init__(self,sample,input_size,hidden_size,out_size,lr):
self.sample=sample
self.input_size=input_size
self.hidden_size=hidden_size,
self.out_size=out_size
self.lr=lr
#权重初始化
self.w1=np.random.randn(input_size,hidden_size)
self.b1=np.zeros((1,hidden_size))
self.w2=np.random.randn(hidden_size,out_size)
self.b2=np.zeros((1,out_size))
#归一化初始化参数
self.x_mean=np.zeros((1,input_size))
self.x_std=np.ones((1,input_size))
self.y_mean=np.zeros((1,out_size))
self.y_std=np.ones((1,out_size))
#获取数据
def get_data(self):
self.x=np.linspace(-3,3,self.sample).reshape(-1,self.input_size)
noise=np.random.randn(self.x.shape[0],self.x.shape[1])
self.y=2*self.x**2+0.2*noise
#self.normalization()
#归一化
def normalization(self):
self.x_mean=self.x.mean(axis=0)
self.x_std=self.x.std(axis=0)
self.y_mean=self.y.mean(axis=0)
self.y_std=self.y.std(axis=0)
self.x=(self.x-self.x_mean)/(self.x_std+1e-5)
self.y=(self.y-self.y_mean)/(self.y_std+1e-5)
#激活函数
def Relu(self,input):
return np.maximum(input, 0)
#获取损失值
def get_loss(self):
return 0.5*np.square(self.out3 - self.y).sum()
#前向传播
def forward(self):
self.out1=self.x.dot(self.w1)+self.b1
self.out2=self.Relu(self.out1)
self.out3=self.out2.dot(self.w2)+self.b2
#批量梯度下降法
def backward(self):
gl=self.out3-self.y #(sample,out_size)
gw2=self.out2.T.dot(gl) #(h,o)
gb2=gl.sum(axis=0)
g_h_relu=gl.dot(self.w2.T)
g_h = g_h_relu.copy()
g_h[self.out1 < 0] = 0
gw1 = self.x.T.dot(g_h)
gb1=g_h.sum(axis=0)
self.w1-=self.lr*gw1
self.b1-=self.lr*gb1
self.w2-=self.lr*gw2
self.b2-=self.lr*gb2
#绘制图像
def plot(self):
plt.plot(self.x*self.x_std+self.x_mean,self.y*self.y_std+self.y_mean,label="true")
plt.plot(self.x*self.x_std+self.x_mean,self.out3*self.x_std+self.x_mean,label="predict")
plt.legend()
plt.show()
#训练模型
def train(self):
self.get_data()
self.forward()
epoch=300
loss=self.get_loss()
for i in range(epoch):
print("loss={:.6f}".format(loss))
self.backward()
self.forward()
epoch+=1
loss=self.get_loss()
self.plot()
拟合结果可视化:

2.总结
目前还没有实现正真的智能,有多少人工,就有多少智能。大家还是要注重基础理论知识的学习,尤其是数学。