随机梯度下降法Stochastic Gradient Descent
在之前的梯度下降法解决线性回归问题中,梯度向量的每一项都要有所有样本参与运算,因此也称作批量梯度下降法Batch Gradient Descent。但这显然带来一个问题,如果样本量m非常大,计算梯度是非常耗费时间的。于是提出了随机梯度下降法,虽然随机梯度下降法每次不一定朝着损失函数减小的方向更不能保证沿着减小速度最快的方向,当然也不能保证一定来到最优值位置(收敛不稳定),但是在绝大多数问题中经过一定的轮数,往往还是能来到最优解附近。对于样本量巨大的情况,我们愿意用精度来换取一定的时间。
由于随机梯度下降的特性,在使用随机梯度下降法时学习率一般是变化的,随着训练轮数的增加,一般要越来越小,这样往往能得到更稳定的结果,下式是一种学习率随着训练轮数变化的公式:
于是a和b是其中的超参数,这种方法中经验上一般取a=5,b=50。接下来对比批量梯度下降法和随机梯度下降法的性能差异,首先生成模拟数据集:
import numpy as np
import matplotlib.pyplot as plt
m = 100000
x=np.random.normal(size=m)
X=x.reshape(-1,1)
y=4.*x+3.+np.random.normal(0,3,size=m)
生成的模拟数剧集包含100000个单特征数据,符合的回归方程是y=3+4x,加入了微小噪音以更加具有真实性。
'''定义损失函数和梯度计算以及训练过程'''
def J(theta, X_b, y):
try:
return np.sum((y - X_b.dot(theta))**2)/len(X_b)
except:
return float('inf')
def dJ(theta, X_b, y):
return X_b.T.dot(X_b.dot(theta)-y)*2/len(X_b)
def gradient_descent(X_b, y, initial_theta, eta, n_iters = 1e4, epsilon = 1e-5):
theta = initial_theta
i_iter = 0
while i_iter < n_iters:
gradient = dJ(theta, X_b, y)
last_theta = theta
theta = theta - eta * gradient#参数更新
if (abs(J(theta, X_b, y)-J(last_theta, X_b, y))<epsilon):
break
i_iter += 1
return theta
批量梯度下降性能如下:
批量梯度下降性能可见得到了很好的回归结果,与y=3+4x的标准解吻合。
为了使用随机梯度下降法,修改梯度函数和训练过程:
'''修改梯度函数'''
def dJ_sgd(theta, X_b_i, y_i):
return X_b_i.T.dot(X_b_i.dot(theta)-y_i)*2
def sgd(X_b,y,initial_theta,n_iters):
t0 = 5
t1 = 50
def learning_rate(t):
return t0/(t+t1)
theta = initial_theta
for cur_iter in range(n_iters):
rand_i = np.random.randint(len(X_b))
'''求一个单独的梯度值'''
gradient = dJ_sgd(theta,X_b[rand_i],y[rand_i])
theta = theta - learning_rate(cur_iter)*gradient
return theta
对于随机梯度下降法,只训练样本个数的三分之一次,其结果如下图所示:
随机梯度下降性能随机梯度下降法也得到了和标准解几乎吻合的结果,而且仅仅训练了样本个数的三分之一次,这比批量梯度下降法一次训练检查的样本还要少!正因如此时间上随机梯度下降要比批量梯度下降优秀很多。可见随机梯度下降法威力有多么大。不过实际中还是要考虑所有样本的,而且要将样本看多遍。
sklearn中的随机梯度下降
为了以后使用的方便,将自写的随机梯度下降法封装进我们的play_Ml模块下的LinearRegression.py:
def fit_sgd(self,X_train,y_train,n_iters=5,t0=5,t1=50):
assert X_train.shape[0] == y_train.shape[0],"must be the same!"
assert n_iters>=1,"must large than 1!"
def dJ_sgd(theta, X_b_i, y_i):
return X_b_i.T.dot(X_b_i.dot(theta)-y_i)*2
def sgd(X_b,y,initial_theta,n_iters,t0=5,t1=50):
def learning_rate(t):
return t0/(t+t1)
theta = initial_theta
m = len(X_b)
for cur_iter in range(n_iters):
'''保证每个样本都被考虑到'''
indexes = np.random.permutation(m)
X_b_new = X_b[indexes]
y_new = y[indexes]
for i in range(m):
gradient = dJ_sgd(theta,X_b_new[i],y_new[i])
theta = theta - learning_rate(cur_iter*m + i)*gradient
return theta
X_b = np.hstack([np.ones((len(X_train),1)),X_train])
initial_theta = np.zeros(X_b.shape[1])
self._theta = sgd(X_b, y_train, initial_theta, n_iters,t0,t1)
self.interception_ = self._theta[0]
self.coef_ = self._theta[1:]
return self
在boston房价数据上使用我们的随机梯度下降算法:
'''使用boston房产数据的所有特征'''
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
boston = datasets.load_boston()
X = boston.data
y = boston.target
X = X[y < 50.0]
y = y[y < 50.0]
需要注意的是,对于真实的boston房产数据,训练前最好进行数据的归一化:
from play_Ml.model_selection import train_test_split
X_train,X_test,y_train,y_test = train_test_split(X,y,seed=666)
'''对真实数据使用梯度下降法,要进行归一化处理'''
from sklearn.preprocessing import StandardScaler
standardScaler = StandardScaler()
standardScaler.fit(X_train)
X_train_standard = standardScaler.transform(X_train)
X_test_standard = standardScaler.transform(X_test)
from play_Ml.LinearRegression import LinearRegression
lin_reg = LinearRegression()
%time lin_reg.fit_sgd(X_train_standard,y_train,n_iters=2)
lin_reg.score(X_test_standard,y_test)
结果如下:
boston_sgd1可见score值并没有达到最优的0.81左右,可能是由于训练次数不足,增加训练轮数:
boston_sgd2此时已经达到最优的score值。
在理解了随机梯度下降法后,接下来使用sklearn中的随机梯度下降:
from sklearn.linear_model import SGDRegressor
sgd_reg = SGDRegressor()
%time sgd_reg.fit(X_train_standard,y_train)
sgd_reg.score(X_test_standard,y_test)
sklearn_sgd
sklearn很快地达到了最优的score值。
由此也可以更加深切体会到sklearn的方便和好用之处,实际上sklearn中的随机梯度实现方式和我们自己写的还是有区别的,sklearn中做了很多的优化,因此性能非常优秀,不过我们自写算法是为了理解原理,sklearn是一个开源框架,有兴趣的可以去sklearn官网和github上查看阅读sklearn算法源代码。
sklearn-github源码链接:sklearn-github开源
网友评论