1.梯度下降法
是一种基于搜索的最优化方法,作用是最小化一个损失函数。
但不是所有的函数都有唯一的极值点。
解决方案:多次运行,随机初始化点
梯度下降法的初始点也是一个超参数
线性回归法的损失函数具有唯一的最优解。
模拟实现梯度下降法
1 import numpy as np 2 import matplotlib.pyplot as plt 3 4 plot_x=np.linspace(-1, 6,141) 5 plot_y=(plot_x-2.5)**2-1 6 7 def dJ(theta): 8 return 2*(theta-2.5) 9 10 def J(theta): 11 try: 12 return (theta-2.5)**2-1 13 except: 14 return float('inf') 15 16 #eta为学习速率,n_iters规定最大循环次数 17 def gradient_descent(initial_theta, eta, n_iters=1e4, epsilon=1e-8): 18 19 theta = initial_theta 20 theta_history.append(initial_theta) 21 i_iter=0 #记录当前是第几次循环 22 23 while i_iter<n_iters: 24 gradient = dJ(theta) #计算梯度 25 last_theta = theta #更新theta 26 theta = theta - eta * gradient 27 theta_history.append(theta) 28 29 if (abs(J(theta) - J(last_theta)) < epsilon): 30 break 31 32 i_iter+=1 33 34 def plot_theta_history(): 35 plt.plot(plot_x, J(plot_x)) 36 plt.plot(np.array(theta_history),J(np.array(theta_history)),color='r',marker='+') 37 plt.show() 38 39 eta=0.1 40 theta_history=[] 41 gradient_descent(0, eta) 42 plot_theta_history()
当学习速率eta=0.1和eta=0.8时,相应的图像如下:
2.线性回归中的梯度下降法
因为多元线性回归的正规方程解复杂度较高,采用梯度下降法解决。
推导如下:
使用梯度下降法求解线性回归参数:
1 import numpy as np 2 import matplotlib.pyplot as plt 3 4 np.random.seed(666) 5 x=2*np.random.random(size=100) 6 y=x*3.+4.+np.random.normal(size=100) #(100,) 7 8 X=x.reshape(-1,1) #(100,1) 9 10 plt.scatter(x,y) 11 12 def J(theta,X_b,y): 13 try: 14 return np.sum((y-X_b.dot(theta))**2)/len(X_b) 15 except: 16 return float('inf') 17 18 def dJ(theta,X_b,y): 19 res=np.empty(len(theta)) 20 res[0]=np.sum(X_b.dot(theta)-y) 21 for i in range(1,len(theta)): 22 res[i]=(X_b.dot(theta)-y).dot(X_b[:,i]) 23 24 return res*2/len(X_b) 25 26 def gradient_descent(X_b, y, initial_theta, eta, n_iters=1e4, epsilon=1e-8): 27 theta = initial_theta 28 cur_iter = 0 29 30 while cur_iter < n_iters: 31 gradient = dJ(theta, X_b, y) 32 last_theta = theta 33 theta = theta - eta * gradient 34 if (abs(J(theta, X_b, y) - J(last_theta, X_b, y)) < epsilon): 35 break 36 37 cur_iter += 1 38 return theta 39 40 X_b=np.hstack([np.ones((len(X),1)),X]) 41 initial_theta=np.zeros(X_b.shape[1]) 42 eta=0.01 43 44 theta=gradient_descent(X_b, y, initial_theta, eta) 45 print(theta) #对应截距和斜率(和调用LinearRegression中的fit函数拟合出的截距和斜率一致) 46 47 plt.plot(X,theta[0]+theta[1]*X) 48 plt.show()
线性回归中梯度下降法的向量化
对上面的推导进行优化:
将上面for循环实现的dJ函数修改如下:
1 def dJ(theta,X_b,y): 2 return X_b.T.dot(X_b.dot(theta)-y)*2.0/len(X_b)
Tip:使用梯度下降之前要进行数据归一化。
3.随机梯度下降法
批量梯度下降法中,每一次计算过程都要将样本中所有信息进行批量计算,如果样本量很大,计算梯度就很耗时。于是引出随机梯度下降法,每次只取一行作为搜索的方向。
eta随着循环次数的增加,学习率降低。
手写随机梯度下降法
1 import numpy as np 2 3 m=100000 4 x=np.random.normal(size=m) 5 X=x.reshape(-1,1) 6 y=x*4.+3.+np.random.normal(0,3,size=m) 7 8 def dJ_sgd(theta,X_b_i,y_i): 9 return X_b_i.T.dot(X_b_i.dot(theta)-y_i)*2.0 10 11 def sgd(X_b, y, initial_theta, n_iters): 12 t0=5 13 t1=50 14 def learning_rate(t): 15 return t0/(t+t1) 16 17 theta=initial_theta 18 for cur_iter in range(n_iters): 19 rand_i=np.random.randint(len(X_b)) #随机获得一个索引 20 gradient=dJ_sgd(theta, X_b[rand_i], y[rand_i]) 21 theta=theta-learning_rate(cur_iter)*gradient 22 return theta 23 24 X_b=np.hstack([np.ones((len(X),1)),X]) 25 initial_theta=np.zeros(X_b.shape[1]) 26 eta=0.01 27 28 theta=sgd(X_b, y, initial_theta, n_iters=len(X_b)//3) #只检测了三分之一个样本就可以达到比较好的结果 29 print(theta) #对应截距和斜率
scikit-learn中的随机梯度下降法
1 import numpy as np 2 from sklearn import datasets 3 4 #波士顿房产数据 5 boston=datasets.load_boston() 6 7 X=boston.data 8 y=boston.target 9 10 X=X[y<50.0] #去除掉采集样本拥有的上限点 11 y=y[y<50.0] 12 13 #拆分数据集 14 from sklearn.model_selection import train_test_split 15 X_train,X_test,y_train,y_test=train_test_split(X,y,random_state=666) 16 17 #归一化处理 18 from sklearn.preprocessing import StandardScaler 19 standardScaler=StandardScaler() 20 standardScaler.fit(X_train) 21 22 X_train_standard=standardScaler.transform(X_train) 23 X_test_standard=standardScaler.transform(X_test) 24 25 26 from sklearn.linear_model import SGDRegressor 27 sgd_reg=SGDRegressor(n_iter_no_change=100) #次数默认为5 28 sgd_reg.fit(X_train_standard,y_train) 29 print(sgd_reg.score(X_test_standard,y_test))
4.关于梯度的调试
1 import numpy as np 2 3 np.random.seed(666) 4 X=np.random.random(size=(1000,10)) 5 6 true_theta=np.arange(1,12,dtype=float) 7 8 X_b=np.hstack((np.ones((len(X),1)),X)) 9 y=X_b.dot(true_theta) + np.random.normal(size=1000) 10 11 def J(theta,X_b,y): 12 try: 13 return np.sum((y-X_b.dot(theta))**2)/len(X_b) 14 except: 15 return float('inf') 16 17 #只适用于当前损失函数J(相当于使用了求导公式,所以只适用于当前函数) 18 def dJ_math(theta,X_b,y): 19 return X_b.T.dot(X_b.dot(theta)-y)*2.0/len(y) 20 21 #适用于任何损失函数(相当于用定义求导,所以适用于任何函数) 22 def dJ_debug(theta,X_b,y,epsilon=0.01): 23 res=np.empty(len(theta)) 24 for i in range(len(theta)): 25 theta_1=theta.copy() 26 theta_1[i]+=epsilon 27 28 theta_2=theta.copy() 29 theta_2[i]-=epsilon 30 res[i]=(J(theta_1,X_b,y)-J(theta_2,X_b,y))/(2*epsilon) 31 return res 32 33 def gradient_descent(dJ,X_b, y, initial_theta, eta, n_iters=1e4, epsilon=1e-8): 34 theta = initial_theta 35 cur_iter = 0 36 37 while cur_iter < n_iters: 38 gradient = dJ(theta, X_b, y) 39 last_theta = theta 40 theta = theta - eta * gradient 41 if (abs(J(theta, X_b, y) - J(last_theta, X_b, y)) < epsilon): 42 break 43 44 cur_iter += 1 45 return theta 46 47 initial_theta=np.zeros(X_b.shape[1]) 48 eta=0.01 49 theta=gradient_descent(dJ_math, X_b, y, initial_theta, eta) 50 print(theta) 51 52 theta=gradient_descent(dJ_debug, X_b, y, initial_theta, eta) 53 print(theta)
dJ_debug方法比dJ_math方法要慢得多,通过这个方式可以先得到正确的结果,然后再推导公式求解梯度公式的数学解,最后将解和debug的解比较,从而判断我们推导的公式是否正确。