深度学习(四):线性回归

引入

  线性回归属于单层神经网络,它们设计的概念、技术同样适用于大多数的深度学习模型。本文首先以线性回归为例,介绍大多数深度学习模型的基本要素和表示方法。
  关于线性回归的基本概念可以参照:
  https://blog.csdn.net/weixin_44575152/article/details/100640051
  代码已上传至github:
  https://github.com/InkiInki/Python/blob/master/Python1/deepLearning/LinearRegression.py
  https://github.com/InkiInki/Python/blob/master/Python1/deepLearning/TorchLinearRegression.py

1 基本要素

  以一个简单的房屋价格预测作为例子来解释线性回归的基本要素,其目标为预测一栋房屋的出售价格,且假设房屋价格( y y )只取决于房屋面积( x 1 x_1 )和房龄( x 2 x_2 )两个因素,从而探讨价格与这两个因素之间的关系。
  若了解基本要素,可前往表示方法。

1.1 模型定义

  设房屋的面积为 x 1 x_1 ,房龄为为 x 2 x_2 ,出售价格为 y y ,则输出与输入的线性关系如下:
y ^ = x 1 w 1 + x 2 w 2 + b (1) \hat{y}=x_1w_1+x_2w_2+b \tag{1} 其中 w 1 w_1 w 2 w_2 是权重(weight), b b 是偏差(bias),且均为标量。 y ^ \hat{y} 是真实价格 y y 的预测,通常允许它们之间有一定误差。

1.2 模型训练

  通过数据来寻找特定的模型参数值,使模型在数据上的误差尽可能下的过程称为模型训练(model training)。

1.2.1 训练数据

  在收集的关于房屋的数据中,一栋房屋的数据被称为一个样本(sample),首先给出以下符号表:

符号 意义
n n 样本总数
x i j , i [ 1.. n ] , j [ 1..2 ] x_{ij}, i\in[1..n], j\in[1..2] i i 个样本的第 j j 个属性
w j , j [ 1..2 ] w_j, j\in[1..2] j j 个属性所对应的权重
b b 偏差
y i , i [ 1.. n ] y_i, i\in[1..n] 第i个样本的真实标签
y ^ i , i [ 1.. n ] \hat{y}_i, i\in[1..n] i i 个样本的预测标签

  对于第 i i 个样本,其线性回归的预测表达式为:
y ^ i = j = 1 2 ( x i j w j ) + b (2) \hat{y}_i=\sum \limits_{j=1}^{2}(x_{ij}w_j)+b \tag{2}

1.2.2 损失函数

  在模型训练中,需要衡量 y ^ i \hat{y}_i y i y_i 之间的误差。通常会选取一个非负数作为误差,其越小代表误差越小。常用的选择为平方函数,则第 i i 个版本的误差表达式为,即损失函数(loss function):
i ( w 1 , w 2 , b ) = 1 2 ( y ^ i y i ) 2 (3) \ell_i(w_1, w_2, b) = \frac{1}{2}(\hat{y}_i-y_i)^2 \tag{3} 其中常数 1 2 \frac{1}{2} 的作用为:使平方项求导后的常数系数为1。这里使用的平方误差函数也称为平方损失(suqare loss)。
  通常,用训练集中所有样本误差的平均来衡量模型预测的质量,即:
( w 1 , w 2 , b ) = 1 n i = 1 n i ( w 1 , w 2 , b ) = 1 n i = 1 n 1 2 ( j = 1 2 ( x i j w j ) + b y i ) 2 (4) \ell(w_1, w_2, b)=\frac{1}{n}\sum_{i=1}^n\ell_i(w_1, w_2, b)= \frac{1}{n}\sum_{i=1}^n\frac{1}{2}(\sum \limits_{j=1}^{2}(x_{ij}w_j)+b - y_i)^2 \tag{4}

1.2.3 优化算法

  当模型和损失函数的形式较为简单,上述误差最小化问题可以直接用公式表达出来,这类解叫做解析解(analytical solution)。目前的内容,可参照:
  https://blog.csdn.net/weixin_44575152/article/details/100640051
  然而大多数时候深度学习模型并没有解析解,只能通过优化算法有限次迭代模型参数来尽可能降低损失函数的值,这类解叫做数值解(numerical solution)。
  在求解数值解的优化算法中,小批量随机梯度下降(mini-batch stochastic gradient descent)在深度学习中被广泛使用。其算法步骤如下:
  1)选取一组模型参数的初始值,如随机选取;
  2)对参数进行多次迭代,使每次迭代都尽可能降低损失函数的值;
  2.1)在每次迭代中,先随机均匀采样一个由固定数目训练集样本所组成的小批量(mini-batch) B \mathcal{B}
  2.2)求小批量数据样本的平均损失有关模型参数的梯度;
  2.3)用此结果与预先设定的一个正数的乘积作为模型参数在本次迭代的减小量。

  在这里讨论的线性回归模型中,每个参数将如下迭代:
w 1 w 1 η B i B i ( w 1 , w 2 , b ) w 1 (5) w_1 \leftarrow w_1 - \frac{ \eta}{ |\mathcal{B}|} \sum_{i \in |\mathcal{B}|} \frac{ \partial \ell_i (w_1, w_2, b)}{ \partial w_1} \tag{5}

w 2 w 2 η B i B i ( w 1 , w 2 , b ) w 2 (6) w_2 \leftarrow w_2 - \frac{ \eta}{ |\mathcal{B}|} \sum_{i \in |\mathcal{B}|} \frac{ \partial \ell_i (w_1, w_2, b)}{ \partial w_2} \tag{6}

b b η B i B i ( w 1 , w 2 , b ) b (7) b \leftarrow b- \frac{ \eta}{ |\mathcal{B}|} \sum_{i \in |\mathcal{B}|} \frac{ \partial \ell_i (w_1, w_2, b)}{ \partial b} \tag{7} 其中, η \eta 称为学习率(learning rate),且取正数; B \mathcal{B} 称为每个小批量的样本个数(batch size)。强调的是,这里的 η \eta B |\mathcal{B}| 为认为设定,因此称为超参数(hyperparameter),通常通过“调参获取”,少数情况下可以通过模型学习得出。

1.3 模型预测

  训练完成后,将模型参数 w 1 , w 2 , b w_1, w_2, b 在算法停止时的值分别记作 w ^ 1 , w ^ 2 , b ^ \hat{w}_1, \hat{w}_2, \hat{b} 。当时这里只是最优解 w 1 , w 2 , b w_1^*, w_2^*, b^* 的一个近似。然后就可以利用 w ^ 1 , w ^ 2 , b ^ \hat{w}_1, \hat{w}_2, \hat{b} 对任一一栋房屋的价格进行预测。

2 表示方法

   接下来阐述线性回归与神经网络的联系,以及线性回归的矢量计算表达式。
  若了解表示方法,可前往具体实现。

2.1 神经网络图

  当前房屋模型对应的神经网络图(隐去了模型参数权重和偏差)如下:
\label{123456}
  图片来源:李沐、Aston Zhang等老师的这本《动手学深度学习》一书
  上图中,输入的个数叫做特征数或特征向量维度;输出为 y ^ = o \hat{y}=o

2.2 矢量计算表达式

  先看向量相加,按元素逐一相加的情况:

import torch
from time import time

def linear_regression():
    a = torch.ones(1000)
    b = torch.ones(1000)
    
    start_time = time()
    c = torch.zeros(1000)
    for i in range(1000):
        c[i] = a[i] + b[i]
    print(time() - start_time)

if __name__ == "__main__":
    linear_regression()

  运行结果:

0.017923593521118164

  再看矢量相加的情况:

import torch
from time import time

def linear_regression():
    a = torch.ones(1000)
    b = torch.ones(1000)
    
    start_time = time()
    c = a + b
    print(time() - start_time)

if __name__ == "__main__":
    linear_regression()

  运行结果:

0.0

  几乎不耗时间,这是矢量计算的优势。

  线性回归矢量计算表达式为:
y ^ = X w + b (8) \boldsymbol{ \hat{y}}= \boldsymbol{X w} + b \tag{8} 其中模型输出 y ^ R n × 1 \boldsymbol{ \hat{y}} \in \mathbf{R}^{n×1} ,批量样本 X R n × d \boldsymbol{X} \in \mathbf{R}^{n×d} ,权重 ω R n × 1 \boldsymbol{ \omega} \in \mathbf{R}^{n×1} ,偏差 b R b \in \mathbf{R} 。设模型参数 θ = [ w 1 , w 2 , b ] \boldsymbol{ \theta} = [ w_1, w_2, b] ,则重写损失函数为:
( θ ) = 1 2 n ( y ^ y ) T ( y ^ y ) (9) \ell (\boldsymbol{ \theta}) = \frac{1}{2n} ( \boldsymbol{ \hat{y}} - \boldsymbol{y})^T ( \boldsymbol{ \hat{y}} - \boldsymbol{y}) \tag{9}
  小批量随机梯度下降的迭代步骤相应的为:
θ θ η B i B θ i ( θ ) (10) \boldsymbol{ \theta} \leftarrow \boldsymbol{ \theta} - \frac{ \eta}{|\boldsymbol{ \mathcal{B}}|} \sum_{i \in |\boldsymbol{ \mathcal{B}}|} \nabla_{\boldsymbol{ \theta}} \ell_i (\boldsymbol{ \theta}) \tag{10} 其中
θ i ( θ ) = [ i ( w 1 , w 2 , b ) w 1 i ( w 1 , w 2 , b ) w 2 i ( w 1 , w 2 , b ) b ] = [ x i 1 x i 2 1 ] ( y ^ i y i ) (11) \nabla_{\boldsymbol{ \theta}} \ell_i (\boldsymbol{ \theta}) = \left[ \begin{matrix} \frac{ \partial \ell_i (w_1, w_2, b)}{ \partial w_1}\\ \frac{ \partial \ell_i (w_1, w_2, b)}{ \partial w_2}\\ \frac{ \partial \ell_i (w_1, w_2, b)}{ \partial b} \end{matrix} \right] = \left[ \begin{matrix} x_{i1}\\ x_{i2}\\ 1 \end{matrix} \right] (\hat{y}_i - y_i) \tag{11}

3 具体实现

  为了深入了解深度学习是如何工作的,首先介绍只利用Tensor和autograd来实现一个线性回归的训练。
  首先导入如下模块:

import torch
import numpy as np
import random
from IPython import display
from matplotlib import pyplot as plt

3.1 生成数据集

  生成数据集的参数如下:

  • 样本数 n = 1000 n = 1000
  • 特征数 = 2 = 2
  • 权重 w = [ 2 , 3.4 ] T \boldsymbol{w} = [2, -3.4]^T
  • 偏差 b = 4.2 b = 4.2
  • 随机噪声 ϵ \epsilon ~ N ( 0 , 0.01 2 ) N(0, {0.01}^2)

  则生成函数为
y = X w + b + ϵ (12) \boldsymbol{y} = \boldsymbol{Xw} + b + \epsilon \tag{12}
  数据生成代码如下:

class LinearRegression():
    
    def _init_(self, num_instances=1000, num_attributions=2, true_w=[2, -3.4], 
               true_b=4.2, epsilon=[0, 0.01], batch_size=10):
        self.num_instances = num_instances    #实例个数
        self.num_attributions = num_attributions    #属性大小
        self.true_w = true_w    #真实w值
        self.true_b = true_b    #真实b值
        self.epsilon = epsilon    #默认噪声
        self.batch_size = batch_size    #批次大小

    def generate_data_set(self):
        datas= torch.from_numpy(np.random.normal(0, 1, (self.num_instances, self.num_attributions)))    #生成给定大小的初始数据
        labels = self.true_w[0] * datas[:, 0] + self.true_w[1] * datas[:, 1] + self.true_b    #计算标签
        labels += torch.from_numpy(np.random.normal(self.epsilon[0], self.epsilon[1], size=labels.size()))    #添加噪声
        #self.set_figsize()
        #plt.scatter(datas[:, 1].numpy(), labels.numpy(), s=2)
        #plt.show()
        return datas, labels
     
    def set_figsize(self, figsize=(6, 4)):
        display.set_matplotlib_formats('svg')    #输出为矢量图
        plt.rcParams['figure.figsize'] = figsize
    
if __name__ == "__main__":
    if __name__ == "__main__":
    test = LinearRegression()
    test._init_()
    datas, labels = test.generate_data_set()

  绘图结果示例:
在这里插入图片描述

图1 生成数据集示例

3.2 读取数据

  在模型训练时,需要遍历数据集并不断读取小批量数据样本,即每次返回批量大小个随机样本的特征和标签,添加代码如下代码如下:

class LinearRegression():
	...
	def data_iter(self, datas, labels):
	    num_instances = len(datas)    #数据长度
	    indices = list(range(num_instances))    #索引生成
	    random.shuffle(indices)    #索引乱序
	    for i in range(0, num_instances, self.batch_size):
	        j = torch.LongTensor(indices[i : min(i + self.batch_size, num_instances)])    #以batch_size为间隔选取乱序后的索引;min(i + batch_size, num_instances)为避免最后一次不足一个batch_size的情况
	        yield datas.index_select(0, j), labels.index_select(0, j)    #选取对应索引的数据和标签返回,但不等同于return;0代表在以行为基准的维度进行选取

if __name__ == "__main__":
    test = LinearRegression()
    test._init_()
    datas, labels = test.generate_data_set()
    for x, y in test.data_iter(datas, labels):
        print(x)
        print(y)
        break    #只输出第一个批次

  运行结果示例:

tensor([[-0.1381, -2.6687],
        [-0.8824,  0.2013],
        [-0.7474, -0.6727],
        [-0.4402,  0.1414],
        [ 0.0178,  0.2415],
        [-0.0757,  0.0203],
        [-1.7888,  0.1679],
        [ 0.7078, -0.9608],
        [ 0.5318, -1.9371],
        [ 1.5580, -0.5262]], dtype=torch.float64)
tensor([13.2180,  1.3351,  5.3345,  0.9475,  2.6992,  3.3062,  0.8293,  8.3481,
        10.6143,  9.5403], dtype=torch.float64)

3.3 初始化模型参数

  我们将权重初始化为均值为0、标准差为0.01的正态随机数,偏差初始化为0,且需要这些参数求梯度来迭代参数的值,故于__init__()修改如下:

	...
	def _init_(self, num_instances=1000, num_attributions=2, true_w=[2, -3.4], 
               true_b=4.2, epsilon=[0, 0.01], w=[0, 0.01]):
        ...
        self.w = torch.tensor(np.random.normal(w[0], w[1], (self.num_attributions, 1)), requires_grad=True)
        self.b = torch.zeros(1, requires_grad=True)
	...

3.4 定义模型

  以下实现线性回归的矢量计算表达式:

	...
	def linreg(self, datas, w, b):
        return torch.mm(datas, w) + b    #mm用于矩阵乘法

3.5 定义损失函数

  使用平方损失来定义线性回归的损失函数。在实现时,需要把真实值 y y 变成预测值 y ^ \hat{y} 的形状,添加代码如下:

	...
	def squared_loss(self, y_hat, y):
        return (y_hat - y.view(y_hat.size())) ** 2 / 2    #使y_hat与y同形再计算

3.6 定义优化

  以下实现小批量随机梯度下降算法,通过不断迭代模型参数来优化损失函数。这里自动求梯度模块计算得来的梯度是一个批量样本的梯度和,将用它来除以批量大小得到平均值。
  将__init()修改如下:

	...
	def _init_(self, num_instances=1000, num_attributions=2, true_w=[2, -3.4], 
    	...
        self.lr = lr    #学习率
	...

  优化函数如下:

def sgd(self, params):
        for param in params:
            param.data -= self.lr * param.grad / self.batch_size

3.7 训练模型

  在驯良中,将多次迭代训练模型。在每次迭代中,将根据当前读取的小批量数据样本,通过反向函数backward计算小批量随机梯度,并调用优化算法sgd迭代模型参数。
  默认设置中,batch_size为10,每次小批量的损失l的形状为(10, 1)。由于l不是标量,所有可以调用sum()将其求和得到一个标量,再使用l.backward()得到该变量油管模型参数的梯度,并在每次更新完参数后将参数的梯度清零。
  在一个迭代周期中,将完整遍历一遍data_iter函数,并对训练集中所有样本都使用一次(假设样本数能够被批量大小整除)。修改__init__()如下:

	...
	def _init_(self, num_instances=1000, num_attributions=2, true_w=[2, -3.4], 
               true_b=4.2, epsilon=[0, 0.01], w=[0, 0.01], lr=0.03, num_epochs=3):
        ...
        self.num_epochs = num_epochs
	...

  训练模型代码添加如下:

if __name__ == "__main__":
    test = LinearRegression()
    test._init_()
    datas, labels = test.generate_data_set()
    #每一次迭代中,均会使用训练集中的所有样本,假设如前。
    for epoch in range(test.num_epochs):
        for x, y in test.data_iter(datas, labels):
            l = test.squared_loss(test.linreg(x, test.w, test.b), y).sum()    #小批量的损失
            l.backward()     #求梯度
            test.sgd([test.w, test.b])
            test.w.grad.data.zero_()    #梯度清空
            test.b.grad.data.zero_()
        train_l = test.squared_loss(test.linreg(datas, test.w, test.b), labels)    #总损失
        print("Epoch %d, loss %f" % (epoch + 1, train_l.mean())))
    print("W: ", test.w, ", b: ", test.b)

  运行结果如下:

Epoch 1, loss 0.035907
Epoch 2, loss 0.000133
Epoch 3, loss 0.000050
W:  tensor([[ 1.9997],
        [-3.3995]], dtype=torch.float64, requires_grad=True) , b:  tensor([4.1996], requires_grad=True)

  可以发现w和b与实际情况很接近。

3.8 完整代码

import torch
import numpy as np
import random
from IPython import display
from matplotlib import pyplot as plt
from numpy import dtype

class LinearRegression():
    
    def _init_(self, num_instances=1000, num_attributions=2, true_w=[2, -3.4], 
               true_b=4.2, epsilon=[0, 0.01], batch_size=10, w=[0, 0.01], lr=0.03,
               num_epochs=3):
        self.num_instances = num_instances
        self.num_attributions = num_attributions
        self.true_w = true_w
        self.true_b = true_b
        self.epsilon = epsilon
        self.batch_size = batch_size
        self.w = torch.tensor(np.random.normal(w[0], w[1], (self.num_attributions, 1)), requires_grad=True)
        self.b = torch.zeros(1, requires_grad=True)
        self.lr = lr
        self.num_epochs = num_epochs

    def generate_data_set(self):
        datas= torch.from_numpy(np.random.normal(0, 1, (self.num_instances, self.num_attributions)))
        labels = self.true_w[0] * datas[:, 0] + self.true_w[1] * datas[:, 1] + self.true_b
        labels += torch.from_numpy(np.random.normal(self.epsilon[0], self.epsilon[1], size=labels.size()))
        #self.set_figsize()
        #plt.scatter(datas[:, 1].numpy(), labels.numpy(), s=2)
        #plt.show()
        return datas, labels
     
    def set_figsize(self, figsize=(6, 4)):
        display.set_matplotlib_formats('svg')
        plt.rcParams['figure.figsize'] = figsize
         
    def data_iter(self, datas, labels):
        num_instances = len(datas)
        indices = list(range(num_instances))
        random.shuffle(indices)
        for i in range(0, num_instances, self.batch_size):
            j = torch.LongTensor(indices[i : min(i + self.batch_size, num_instances)])
            yield datas.index_select(0, j), labels.index_select(0, j)
            
    def linreg(self, datas, w, b):
        return torch.mm(datas, w) + b
    
    def squared_loss(self, y_hat, y):
        return (y_hat - y.view(y_hat.size())) ** 2 / 2
    
    def sgd(self, params):
        for param in params:
            param.data -= self.lr * param.grad / self.batch_size

if __name__ == "__main__":
    test = LinearRegression()
    test._init_()
    datas, labels = test.generate_data_set()
    for epoch in range(test.num_epochs):
        for x, y in test.data_iter(datas, labels):
            l = test.squared_loss(test.linreg(x, test.w, test.b), y).sum()
            l.backward()
            test.sgd([test.w, test.b])
            test.w.grad.data.zero_()
            test.b.grad.data.zero_()
        train_l = test.squared_loss(test.linreg(datas, test.w, test.b), labels)
        print("Epoch %d, loss %f" % (epoch + 1, train_l.mean()))
    print("W: ", test.w, ", b: ", test.b)

4 torch实现

  本节介绍torch简洁实现线性回归。

4.1 生成数据集

  数据集生成代码不变,仅做少许改动:

import torch
import numpy as np
import random
from IPython import display
from matplotlib import pyplot as plt
from numpy import dtype

class TorchLinearRegression():
    
    class TorchLinearRegression(nn.Module):
    
    def _init_(self, num_instances=1000, num_attributions=2, true_w=[2, -3.4], 
               true_b=4.2, epsilon=[0, 0.01], batch_size=10, lr=0.03, num_epochs=3, weight=[0, 0.01], bias=0):
        self.num_instances = num_instances
        self.num_attributions = num_attributions
        self.true_w = true_w
        self.true_b = true_b
        self.epsilon = epsilon
        self.batch_size = batch_size
        self.lr = lr
        self.num_epochs = num_epochs
        self.weight = weight     #初始权重
        self.bias = bias     #初始偏差
        self.net = nn.Sequential(nn.Linear(test.num_attributions, 1))    #初始网络,随后讲解
        
    def generate_data_set(self):
    	#注意此处必须制定类型,否则会报错
        datas= torch.tensor(np.random.normal(0, 1, (self.num_instances, self.num_attributions)), dtype=torch.float)
        labels = self.true_w[0] * datas[:, 0] + self.true_w[1] * datas[:, 1] + self.true_b
        labels += torch.tensor(np.random.normal(self.epsilon[0], self.epsilon[1], size=labels.size()), dtype=torch.float)
        return datas, labels

4.2 读取数据

  torch提供了data包来读取数据。由于data常用作变量名,故将dataData代替。

import torch.utils.data as Data
...

if __name__ == "__main__":
    test = TorchLinearRegression()
    test._init_()
    datas, labels = test.generate_data_set()
    data_set = Data.TensorDataset(datas, labels)    #整合标签和数据
    data_iter = Data.DataLoader(data_set, test.batch_size, shuffle=True)    #shuffle=True表示乱序索引
    for x, y in data_iter:
        print(x)    #输出这里不予展示
        print(y)
        break

4.3 定义模型

  首先,导入torch.nn模块,该模块利用autograd定义了大量神经网络的层。其核心数据结构是Module,它是一个抽象概念,既可以表示神经网络中的某个层(layer),也可以表示一个包含很多层的神经网络。在实际应用中,最常见的做法是继承nn.Module,撰写自己的网络/层。
  首先做如下修改:

import torch.nn as nn
...
#注意继承的模块
class TorchLinearRegression(nn.Module):

  Sequential是一个有序的容器,可以很方便的搭建网络。网络层将按照在传入Sequential的顺序依次添加到计算图中。对于本次线性回归模型,有以下三种方式进行创建:

方式1:

if __name__ == "__main__":
    test = TorchLinearRegression()
    test._init_()
    net = nn.Sequential(
        nn.Linear(test.num_attributions, 1)
        #此处还可以传入其他层
        )    #这里num_attributions=2
    print(net)    #输出网络查看结构
    print(net[0])

  运行结果:

Sequential(
  (0): Linear(in_features=2, out_features=1, bias=True)
)
Linear(in_features=2, out_features=1, bias=True)

方式2:

if __name__ == "__main__":
    test = TorchLinearRegression()
    test._init_()
    net = nn.Sequential()
    net.add_module('linear', nn.Linear(test.num_attributions, 1))
    #net.add_module...
    print(net)
    print(net[0])

方式3:

if __name__ == "__main__":
    test = TorchLinearRegression()
    test._init_()
    from collections import OrderedDict
    net = nn.Sequential(OrderedDict([
        ('linear', nn.Linear(test.num_attributions, 1))
        #...
        ]))
    print(net)
    print(net[0])

  net.parameters()可以查看模型所有可学习参数,并返回一个生成器:

if __name__ == "__main__":
    test = TorchLinearRegression()
    test._init_()
    net = nn.Sequential(
        nn.Linear(test.num_attributions, 1)
        )
    for param in net.parameters():
        print(param)

  运行结果:

Parameter containing:
tensor([[0.1092, 0.5279]], requires_grad=True)
Parameter containing:
tensor([-0.4188], requires_grad=True)

  注意:torch.nn仅支持输入一个batch_size的样本,而不支持单个样本输入,如果只有单个样本,可使用input.unsqueeze(0)来添加一维。

3.4 初始化模型参数

  使用net前,需要初始化模型参数,如线性回归模型中的权重和偏差。PyTorch在init模块中提供了很多初始化方法,其中权重和偏差的初始化如下:

def initialize(self):
        from torch.nn import init
        
        init.normal_(self.net[0].weight, mean=self.weight[0], std=self.weight[1])
        init.constant_(self.net[0].bias, val=self.bias)

3.5 定义损失函数

  nn模块中提供了各种损失函数,这些损失函数可以看作一种特殊的层:

	loss = nn.MSELoss()

3.6 定义优化算法

  torch.optim模块提供了很多常用的优化算法,比如SGD(小批量随机梯度下降)、Adam和RMSProp等:

import torch.optim as optim
...
if __name__ == "__main__":
    test = TorchLinearRegression()
    test._init_()
    optimizer = optim.SGD(test.net.parameters(), lr=test.lr)
    print(optimizer)

  运行结果:

SGD (
Parameter Group 0
    dampening: 0
    lr: 0.03
    momentum: 0
    nesterov: False
    weight_decay: 0
)

  不同的子网络也可设置不同的学习率:

if __name__ == "__main__":
    test = TorchLinearRegression()
    test._init_()
    optimizer = optim.SGD([
        #若不指定,则默认使用最外层的学习率
        {'params': test.net[0].parameters(), 'lr': 0.01},
        #{...},
        ], lr=test.lr)
    print(optimizer)

  运行结果:

SGD (
Parameter Group 0
    dampening: 0
    lr: 0.01
    momentum: 0
    nesterov: False
    weight_decay: 0
)

  学习率也可动态调整:

if __name__ == "__main__":
    test = TorchLinearRegression()
    test._init_()
    optimizer = optim.SGD(test.net.parameters(), lr=test.lr)
    for param_group in optimizer.param_groups:
        param_group['lr'] *= 0.1
    print(optimizer)

  运行结果:

SGD (
Parameter Group 0
    dampening: 0
    lr: 0.003
    momentum: 0
    nesterov: False
    weight_decay: 0
)

3.7 训练模型

  optim实例中的step可以用完迭代模型参数:

if __name__ == "__main__":
    test = TorchLinearRegression()
    test._init_()
    datas, labels = test.generate_data_set()
    data_set = Data.dataset.TensorDataset(datas, labels)
    data_iter = Data.DataLoader(data_set, test.batch_size, shuffle=True)
    loss = nn.MSELoss()
    
    optimizer = optim.SGD(test.net.parameters(), lr=test.lr)
    for epoch in range(1, test.num_epochs + 1):
        for x, y in data_iter:
            output = test.net(x)
            l = loss(output, y.view(-1, 1))
            optimizer.zero_grad()    #清除梯度
            l.backward()
            optimizer.step()
        print('Epoch %d, loss: %f' %(epoch, l.item()))
    print(test.true_w, test.net[0].weight)
    print(test.true_b, test.net[0].bias)

  运行结果:

Epoch 1, loss: 0.000442
Epoch 2, loss: 0.000061
Epoch 3, loss: 0.000140
[2, -3.4] Parameter containing:
tensor([[ 2.0001, -3.3987]], requires_grad=True)
4.2 Parameter containing:
tensor([4.1992], requires_grad=True)

3.8 torch实现完整代码

'''
@(#)test1.py
The class of test.
Author: Yu-Xuan Zhang
Email: [email protected]
Created on May 4, 2020
Last Modified on May 4, 2020

@author: inki
'''
import torch
import numpy as np
import random
import torch.utils.data as Data
import torch.nn as nn
import torch.optim as optim
from IPython import display
from matplotlib import pyplot as plt

class TorchLinearRegression(nn.Module):
    
    def _init_(self, num_instances=1000, num_attributions=2, true_w=[2, -3.4], 
               true_b=4.2, epsilon=[0, 0.01], batch_size=10, lr=0.03, num_epochs=3, weight=[0, 0.01], bias=0):
        self.num_instances = num_instances
        self.num_attributions = num_attributions
        self.true_w = true_w
        self.true_b = true_b
        self.epsilon = epsilon
        self.batch_size = batch_size
        self.lr = lr
        self.num_epochs = num_epochs
        self.weight = weight
        self.bias = bias
        self.net = nn.Sequential(nn.Linear(test.num_attributions, 1))

    def generate_data_set(self):
        datas= torch.tensor(np.random.normal(0, 1, (self.num_instances, self.num_attributions)), dtype=torch.float)
        labels = self.true_w[0] * datas[:, 0] + self.true_w[1] * datas[:, 1] + self.true_b
        labels += torch.tensor(np.random.normal(self.epsilon[0], self.epsilon[1], size=labels.size()), dtype=torch.float)
        return datas, labels
    
    def initialize(self):
        from torch.nn import init
        
        init.normal_(self.net[0].weight, mean=self.weight[0], std=self.weight[1])
        init.constant_(self.net[0].bias, val=self.bias)
    
if __name__ == "__main__":
    test = TorchLinearRegression()
    test._init_()
    datas, labels = test.generate_data_set()
    data_set = Data.dataset.TensorDataset(datas, labels)
    data_iter = Data.DataLoader(data_set, test.batch_size, shuffle=True)
    loss = nn.MSELoss()
    
    optimizer = optim.SGD(test.net.parameters(), lr=test.lr)
    for epoch in range(1, test.num_epochs + 1):
        for x, y in data_iter:
            output = test.net(x)
            l = loss(output, y.view(-1, 1))
            optimizer.zero_grad()
            l.backward()
            optimizer.step()
        print('Epoch %d, loss: %f' %(epoch, l.item()))
    print(test.true_w, test.net[0].weight)
    print(test.true_b, test.net[0].bias)
原创文章 35 获赞 44 访问量 8630

猜你喜欢

转载自blog.csdn.net/weixin_44575152/article/details/104564344