根据我上一篇关于波士顿房价猜测一文能够知道,假如运用梯度下降法,需要将所有的样本对梯度的贡献取平均,根据梯度更新参数。

但是面对海量样本的数据集,假如每次核算都运用悉数的样原本核算丢失函数和梯度,功能会很差,也便是核算的会慢。

随机梯度下降法(SGD)

为了解决功能差的问题,咱们引入了随机梯度下降法(SGD)对其进行优化,改进如下: 反正参数每次只沿着梯度反方向更新一点点,那么方向大差不差即可,所以咱们每次只从总数据集中随机抽取一部分数据来代表全体,基于这部分数据来核算梯度和丢失函数来更新参数,这便是随机梯度下降法。

关于此次优化,咱们首要对上文中的代码进行了两部分改进,这儿为了方便我直接把改进前的代码仿制过来:

  • 数据处理部分
  • 练习进程部分

改进前的代码:

import numpy as np
from matplotlib import pyplot as plt
def load_data():
    # 从文件导入数据
    datafile = 'housing.data'
    data = np.fromfile(datafile, sep=' ')
    print(data.shape)
    # 每条数据包含14项,其间前面13项是影响要素,第14项是相应的房子价格中位数
    feature_names = ['CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV']
    feature_num = len(feature_names)
    # 将原始数据进行reshape, 变为[N, 14]这样的形状
    data = data.reshape([data.shape[0] // feature_num, feature_num])
    print(data.shape)
    # 将原数据集拆分成练习集和测验集
    # 这儿运用80%的数据做练习,20%的数据做测验
    # 测验集和练习集有必要是没有交集的
    ratio = 0.8
    offset = int(data.shape[0] * ratio)
    data_slice = data[:offset]
    # 核算train数据集的最大值、最小值平和均值
    maxinums, mininums, avgs = data_slice.max(axis=0), data_slice.min(axis=0), data_slice.sum(axis=0) / data_slice.shape[0]
    # 对数据进行归一化处理
    for i in range(feature_num):
        # print(maxinums[i], mininums[i], avgs[i])
        data[:, i] = (data[:, i] - avgs[i]) / (maxinums[i] - mininums[i])
    # 练习集和测验集的区分份额
    # ratio = 0.8
    train_data = data[:offset]
    test_data = data[offset:]
    return train_data, test_data
class NetWork(object):
    def __init__(self, num_of_weights):
        # 随机发生w的初始值
        # 为了坚持程序每次运转成果的一致性,此处设置了固定的随机数种子
        np.random.seed(0)
        self.w = np.random.randn(num_of_weights, 1)
        self.b = 0
    def forward(self, x):
        z = np.dot(x, self.w) + self.b
        return z
    def loss(self, z, y):
        error = z - y
        cost = error * error
        cost = np.mean(cost)
        return cost
    def gradient(self, x, y):
        z = self.forward(x)
        gradient_w = (z - y) * x
        gradient_w = np.mean(gradient_w, axis=0)  # axis=0表明把每一行做相加然后再除以总的行数
        gradient_w = gradient_w[:, np.newaxis]
        gradient_b = (z - y)
        gradient_b = np.mean(gradient_b)
        # 此处b是一个数值,所以能够直接用np.mean得到一个标量(scalar)
        return gradient_w, gradient_b
    def update(self, gradient_w, gradient_b, eta=0.01):    # eta代表学习率,是操控每次参数值变动的大小,即移动步长,又称为学习率
        self.w = self.w - eta * gradient_w                 # 相减: 参数向梯度的反方向移动
        self.b = self.b - eta * gradient_b
    def train(self, x, y, iterations=1000, eta=0.01):
        losses = []
        for i in range(iterations):
            # 四步法
            z = self.forward(x)
            L = self.loss(z, y)
            gradient_w, gradient_b = self.gradient(x, y)
            self.update(gradient_w, gradient_b, eta)
            losses.append(L)
            if (i + 1) % 10 == 0:
                print('iter {}, loss {}'.format(i, L))
        return losses
# 获取数据
train_data, test_data = load_data()
print(train_data.shape)
x = train_data[:, :-1]
y = train_data[:, -1:]
# 创建网络
net = NetWork(13)
num_iterations = 2000
# 发动练习
losses = net.train(x, y, iterations=num_iterations, eta=0.01)
# 画出丢失函数的改动趋势
plot_x = np.arange(num_iterations)
plot_y = np.array(losses)
plt.plot(plot_x, plot_y)
plt.show()

在修正之前咱们先介绍几个变量:

  • min-batch: 每次迭代的时分抽取出来的一批数据被称为一个min-batch。
  • batch_size: 一个min-batch所包含的样本数量。
  • epoch: 按mini-batch逐次取出样本,当将整个样本集遍历一次后,即完结了一轮的练习,称为一个epoch。

数据处理部分修正

  1. 拆分数据批次 在上述代码中数据处理部分,咱们将总的数据集依照8:2的份额进行了练习集和测验集的分配。 练习集train_data中总共包含了506 * 0.8 = 404条数据集(这儿取整)。 假如将==batch_size=10==,那么咱们将取练习集中的前10个样本作为第一个mini-batch,核算梯度和丢失函数并更新网络参数。代码如下:
train_data1 = train_data[0:10]
print(train_data1.shape)
# 输出(10, 14)
net = NetWork(13)
x = train_data1[:, :-1]
y = train_data1[:, -1]
loss = net.train(x, y, iterations=1, eta=0.01)
print(loss)
# 输出 [0.9001866101467376]

同理,再取出样本10-19作为第二个mini-batch核算梯度并更新参数,依次类推,直到完结一轮的练习,再根据==num_epoches==的轮数中止或许再来一轮。

留意: 假如batch_size=10,那下述程序将train_data分成404/10 + 1 = 41个mini_batch。前40个mini_batch,每个均含有10个样本,最终一个mini_batch只含有4个样本。

batch_size = 10
n = len(train_data)
mini_batches = [train_data[k: k + batch_size] for k in range(0, n, batch_size)]
# 这儿运用了列表生成式,在列表内部运用for循环依次将tran_data分割成n / batch_size个mini_batch
print('总的mini_batches是:', len(mini_batches))
print('第一个mini_batch维度是', mini_batches[0].shape)
print('最终一个mini_batch维度是', mini_batches[-1].shape)
# 输出
# 总的mini_batches是:41
# 第一个mini_batch维度是(10, 14)
# 最终一个mini_batch维度是(4,14)
  1. 随机抽取mini_batch的实现 在上述进程中,咱们抽取mini_batch的方式是按次序逐渐取出mini_batch,而在随机梯度下降法中,咱们需要随机抽取一部分样本代表总体。 所以咱们运用np.random.shuffle来打乱mini_batches中的mini_batch次序。举个二维数组的比如:
import numpy as np
# 新建一个array
a = np.arange(1, 13).reshape([6, 2])
np.random.shuffle(a)
print(a)
print(a)
# 以下为输出成果
[[ 1  2]
 [ 3  4]
 [ 5  6]
 [ 7  8]
 [ 9 10]
 [11 12]]
[[11 12]
 [ 9 10]
 [ 1  2]
 [ 3  4]
 [ 7  8]
 [ 5  6]]

屡次运转上面的代码能够发现,shuffle之后的array每次都不一样,二维的数组默许只改动第0维的元素次序,也便是[1,2]和[3,4]这样的次序,这正好契合咱们的需求。 注:随机的好处在于防止样本次序对练习进程的影响(人和模型一样都更注重最近的样本),只要在特定情况下才会有意组织样本的练习次序

练习进程部分的修正

加入多轮和多批次练习的双层循环

  1. 第一层循环,代表样本调集要被练习遍历的次数,即“epoch”。
 for epoch_id in range(num_epoches):
  1. 第二层循环,代表每次循环时,样本调集被拆分成的多个批次,需要悉数履行练习,称为“iter(iteration)”
for iter_id, mini_batch in enumerate(mini_batches):

这儿运用了enumerate枚举法,iter_id代表索引值,mini_batch代表每一次索引的数据。 3. 两层练习内是经典的四步 前向核算-> 核算丢失-> 核算梯度-> 更新参数 深度学习的一招鲜:两层循环+四个步骤

完整代码实现

import pandas as pd
import numpy as np
import torch.nn as nn
from torch.nn import Linear
import torch.nn.functional as F
from matplotlib import pyplot as plt
def load_data():
    # 从文件导入数据
    datafile = 'housing.data'
    data = np.fromfile(datafile, sep=' ')
    # 每条数据包含14项,其间前面13项是影响要素,第14项是相应的房子价格中位数
    feature_names = ['CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT',
                     'MEDV']
    feature_num = len(feature_names)
    # 将原始数据进行reshape, 变为[N, 14]这样的形状
    data = data.reshape([data.shape[0] // feature_num, feature_num])
    # 将原数据集拆分成练习集和测验集
    # 这儿运用80%的数据做练习,20%的数据做测验
    # 测验集和练习集有必要是没有交集的
    ratio = 0.8
    offset = int(data.shape[0] * ratio)
    data_slice = data[:offset]
    # 核算train数据集的最大值、最小值平和均值
    maxinums, mininums, avgs = data_slice.max(axis=0), data_slice.min(axis=0), data_slice.sum(axis=0) / \
                               data_slice.shape[0]
    # 对数据进行归一化处理
    for i in range(feature_num):
        # print(maxinums[i], mininums[i], avgs[i])
        data[:, i] = (data[:, i] - avgs[i]) / (maxinums[i] - mininums[i])
    # 练习集和测验集的区分份额
    # ratio = 0.8
    train_data = data[:offset]
    test_data = data[offset:]
    return train_data, test_data
class NetWork(object):
    def __init__(self, num_of_weights):
        # 随机发生w的初始值
        # 为了坚持程序每次运转成果的一致性,此处设置了固定的随机数种子
        np.random.seed(0)
        self.w = np.random.randn(num_of_weights, 1)
        self.b = 0
    def forward(self, x):
        z = np.dot(x, self.w) + self.b
        return z
    def loss(self, z, y):
        error = z - y
        cost = error * error
        cost = np.mean(cost)
        return cost
    def gradient(self, x, y):
        z = self.forward(x)
        gradient_w = (z - y) * x
        gradient_w = np.mean(gradient_w, axis=0)  # axis=0表明把每一行做相加然后再除以总的行数
        gradient_w = gradient_w[:, np.newaxis]
        gradient_b = (z - y)
        gradient_b = np.mean(gradient_b)
        # 此处b是一个数值,所以能够直接用np.mean得到一个标量(scalar)
        return gradient_w, gradient_b
    def update(self, gradient_w, gradient_b, eta=0.01):  # eta代表学习率,是操控每次参数值变动的大小,即移动步长,又称为学习率
        self.w = self.w - eta * gradient_w  # 相减: 参数向梯度的反方向移动
        self.b = self.b - eta * gradient_b
    def train(self, training_data, num_epoches, batch_size, eta):
        n = len(training_data)
        losses = []
        for epoch_id in range(num_epoches):
            # 在每轮迭代开始之前,将练习数据的次序随机的打乱
            # 然后在按每次取batch_size条数据的方式取出
            np.random.shuffle(training_data)
            # 将练习数据进行拆分,每个mini_batch包含batch_size条的数据
            mini_batches = [training_data[k: k+batch_size] for k in range(0, n, batch_size)]  # 这儿运用列表生成式,将training_data分为n/batch_size个mini_batch
            for iter_id, mini_batch in enumerate(mini_batches):
                # print(self.w.shape)
                # print(self.b)
                x = mini_batch[:, :-1]
                y = mini_batch[:, -1:]
                a = self.forward(x)
                loss = self.loss(a, y)
                gradient_w, gradient_b = self.gradient(x, y)
                self.update(gradient_w, gradient_b, eta)
                losses.append(loss)
                print('Epoch {:3d} / iter {:3d}, loss={:4f}'.format(epoch_id + 1, iter_id + 1, loss))
        return losses
# 获取数据
train_data, test_data = load_data()
# 创建网络
net = NetWork(13)
# 发动练习
losses = net.train(train_data, num_epoches=50, batch_size=100, eta=0.1)
# 画出丢失函数的改动趋势
plot_x = np.arange(len(losses))
plot_y = np.array(losses)
plt.plot(plot_x, plot_y)
plt.show()

成果如下:

波士顿房价预测—随机梯度下降法优化

丢失函数改动趋势可视化

波士顿房价预测—随机梯度下降法优化
随机梯度下降加快了练习的进程,但是因为每次仅仅基于少量样本核算梯度丢失和更新参数,所以丢失下降曲线会呈现震动,但是这无伤大雅。 留意:本事例因为房价猜测的数据量过少,所以难以感受到随机梯度下降带来的功能提高。咱们以后能够在数据集大的事例上运用随机梯度下降法来对比功能的提高。