机器学习系列(十六)——随机梯度下降Stochastic Gradient Descent

随机梯度下降法Stochastic Gradient Descent

在之前的梯度下降法解决线性回归问题中,梯度向量的每一项都要有所有样本参与运算,因此也称作批量梯度下降法Batch Gradient Descent。但这显然带来一个问题,如果样本量m非常大,计算梯度是非常耗费时间的。于是提出了随机梯度下降法,虽然随机梯度下降法每次不一定朝着损失函数减小的方向更不能保证沿着减小速度最快的方向,当然也不能保证一定来到最优值位置(收敛不稳定),但是在绝大多数问题中经过一定的轮数,往往还是能来到最优解附近。对于样本量巨大的情况,我们愿意用精度来换取一定的时间。
由于随机梯度下降的特性,在使用随机梯度下降法时学习率\eta一般是变化的,随着训练轮数i\_iters的增加,\eta一般要越来越小,这样往往能得到更稳定的结果,下式是一种学习率随着训练轮数变化的公式:
\eta=\frac{a}{i\_iters+b}

于是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开源

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容