线性回归2:数据迭代、模型定义与训练循环的完整拆解

16 阅读4分钟

构造数据迭代器:

  1. 传入参数:batch_size(一批数据的大小),features(特征值矩阵X),labels(标签矩阵即真实值y)
  2. 前三行作用:生成个数为features行数的列表,用shuffle打乱成随机数列表
  3. 第一个for循环:每次循环步长是一批数据的个数,循环100次
  • 从上一步的乱序列表中,按顺序取出一批,如果是最后一批且不够一个列表大小,就取到列表的末尾。
  • 将一批的数据作为一行,形成张量。
  • 将该张量的元素作为索引,取出对应的features和labels,暂停,等待下一次被调用就从暂停的地方继续
def data_iter(batch_size,features,labels):                     #数据迭代器
    num_examples = len(features)                              #features的行数
    indices = list(range(num_examples))                    #生成从0-999的随机数
    random.shuffle(indices)                                #将原始列表打乱
    for i in range(0,num_examples,batch_size):             #从0开始,999结束,步长是batch_size
        batch_indices = torch.tensor(
            indices[i:min(i+batch_size,num_examples)])      #如果是最后一批,不够一个batch_size,就取列表的末尾
        yield features[batch_indices],labels[batch_indices]    
        #取出当前批次的特征数据和标签数据,暂停一下,把数据递出去,等下一次被调用时,从暂停的地方继续走
batch_size = 10
for X,y in data_iter(batch_size,features,labels):            #测试第一批数据的形状是否正确
    print(X,'\n',y)
    break         
  1. w是权重,由一组正态分布的随机数组成,以0为对称轴,0.01为标准差,形状是1行2列,开启梯度追踪
  2. b是偏置,初始化为全0矩阵,开启梯度追踪
w = torch.normal(0,0.01,size=(2,1),requires_grad = True)       #w是权重
b = torch.zeros(1,requires_grad = True)                        #b是偏置,初始化为0

线性回归函数:

  1. 传入参数:特征值矩阵X,权重矩阵w,偏置矩阵b
  2. 返回值:Xb+w 即特征值*权重+偏置
def linreg(X,w,b):                                      #返回线性回归的预测值
    return torch.matmul(X,w) + b

均方误差函数:

  1. 传入参数:预测值y_hat,即linreg函数的返回值和真实值y,即labels值
  2. 返回值:返回预测值-真实值的平方除以2(均方误差),其中真实值y矩阵的形状应转换成与y_hat矩阵一样
def squared_loss(y_hat,y):                                 #y_hat是linreg()函数的返回值
    return (y_hat - y.reshape(y_hat.shape))**2 / 2        #y.reshape(y_hat.shape)强制把y的形状转换成y_hat一致,真实值-预测值
    #均方损失                                           #y是data_iter返回的labels值

小批量梯度随机下降:

  1. 传入参数:params存储w,b的参数列表,lr学习率
  2. 执行过程:
  • 临时关闭梯度追踪
  • 分别把w,b从列表中拿出来
  • param新值=param旧值-学习率*(这一小批样本的平均梯度)
  • 将上一批的梯度矩阵清零
def sgd(params,lr,batch_size):      #小批量梯度随机下降,params是一个存储w,b的参数列表,lr是学习率
    with torch.no_grad():           #临时关闭梯度追踪
        for param in params:        #把w,b分别一次次拿出来
            param -= lr * param.grad / batch_size   #param新值=param旧值-学习率*(这一小批样本的平均梯度)
            param.grad.zero_()                         #将上一批的梯度矩阵清零
  1. 设置学习率0.03,扫描次数3,将线性回归函数重命名net,损失函数loss
  2. 循环扫描3次:从data_iter函数中把数据一批批取出来,得到均方误差l,算出总和梯度,执行sgd更新参数,关闭梯度追踪,生成预测值与真实值之间的均方误差,最后把每一次循环的平均损失输出
lr = 0.03
num_epochs = 3                           
net = linreg
loss = squared_loss
for epoch in range(num_epochs):                             #把整个数据集完整扫描3遍
    for X,y in data_iter(batch_size,features,labels):     #从data_iter函数中把数据一批批取出来
        l = loss(net(X,w,b),y)
        l.sum().backward()                         #l.sum()形成标量用于计算backward(),算出损失对w,b的总和梯度
        sgd([w,b],lr,batch_size)                   #sgd把总和梯度除以batch_size变成平均梯度
    with torch.no_grad():                           #关闭梯度追踪
        train_l = loss(net(features,w,b),labels)    #net()对整个1000个样本做线性回归预测,loss()生成预测值和真实值的均方误差
        print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')        #把每一个循环次数里面的平均损失打印出来

输出内容:

  • w估计误差:将预测权重w转换成真实权重true_w的形状,计算差值,形成估计误差
  • b估计误差:真实偏置-模型学到的偏置之间的误差
print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')     #将预测权重w转换成真实权重true_w的形状,计算差值,形成估计误差
print(f'b的估计误差: {true_b - b}')                          #真实偏置-模型学到的偏置之间的误差