numpy原生,pytorch原生以及面向对象的方式来实现简单的全连接神经网络

简述:使用两种数据集,多种方法,多向对比

  1. 分类任务使用手写数字数据集,小批量梯度下降法,全连接神经网络的输入层为784个神经元,隐藏层为100个神经元,输出层10个神经元。损失函数为交叉熵代价函数,激活函数为sigmoid函数。
  2. 回归任务使用自构随机数数据集,全连接神经网络的输入层为1000个神经元,隐藏层为100个神经元,输出层10个神经元。损失函数为均方误差代价函数,激活函数为y=x函数。

一、 回归任务使用自构随机数数据集

  • numpy实现
import numpy as np
import torch
x = np.random.randn(64, 1000)#正态分布
y = np.random.randn(10)
w1 = np.random.randn(1000, 100)
w2 = np.random.randn(100, 10)

lr = 0.000001
epoxhs = 500
for i in range(500):
    # forward
    h = x.dot(w1)
    h_relu = np.maximum(h, 0)
    y_pred = h_relu.dot(w2)
    
    # 计算损失
    loss = np.square(y_pred - y).sum()
    print(i, loss)

    # backward
    grad_y_pred = 2.0 * (y_pred - y)
    grad_w2 = h_relu.T.dot(grad_y_pred)  # w2的梯度
    grad_h_relu = grad_y_pred.dot(w2.T)
    grad_h = grad_h_relu.copy()
    grad_h[h<0] = 0
    grad_w1 = x.T.dot(grad_h) # w1的梯度

    # 更新参数
    w1 -= lr * grad_w1
    w2 -= lr * grad_w2

执行结果:
······
494 2.309283325916261e-05
495 2.2157265382087743e-05
496 2.1259504455147672e-05
497 2.0398317193778056e-05
498 1.9572198422014806e-05
499 1.8779967141503274e-05

  • pytorch实现
    由于pytorch对numpy进行了封装,所以很多属性方法同numpy类似
x = torch.randn(64, 1000)
y = torch.randn(10)
w1 = torch.randn(1000, 100,requires_grad = True)
w2 = torch.randn(100, 10,requires_grad = True)

lr = 0.000001
for i in range(500):
    # forward
    h = x.mm(w1)
    h_relu = h.clamp(min=0)#激活函数ReLU
    y_pred = h_relu.mm(w2)
    
    # 计算损失
    loss = (y_pred - y).pow(2).sum()
    print(i, loss)
#手动求导更新
#     # backward
#     grad_y_pred = 2.0 * (y_pred - y)
#     grad_w2 = h_relu.t().mm(grad_y_pred)  # w2的梯度
#     grad_h_relu = grad_y_pred.mm(w2.t())
#     grad_h = grad_h_relu.clone()
#     grad_h[h<0] = 0
#     grad_w1 = x.t().mm(grad_h) # w1的梯度

#     # 更新参数
#     w1 -= lr * grad_w1
#     w2 -= lr * grad_w2
#自动求导更新
    # backward
    loss.backward()

    # 更新参数
    w1.data.add_(-lr * w1.grad)
    w2.data.add_(-lr * w2.grad)
    w1.grad.zero_()
    w2.grad.zero_()

执行结果:
······
492 tensor(1.7772e-05, grad_fn=<SumBackward0>)
493 tensor(1.7666e-05, grad_fn=<SumBackward0>)
494 tensor(1.7485e-05, grad_fn=<SumBackward0>)
495 tensor(1.7217e-05, grad_fn=<SumBackward0>)
496 tensor(1.7055e-05, grad_fn=<SumBackward0>)
497 tensor(1.6938e-05, grad_fn=<SumBackward0>)
498 tensor(1.6785e-05, grad_fn=<SumBackward0>)
499 tensor(1.6566e-05, grad_fn=<SumBackward0>)

二、 分类任务使用手写数字数据集(小批量梯度下降)

  • 使用 nn.Module 重构
    相比于原来,封装整个框架
import torch
import numpy as np
class Mnist_Logistic(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.weights1 = torch.nn.Parameter(torch.randn(784,100))#第一层权重,正态分布随机数
        self.weights2 = torch.nn.Parameter(torch.randn(100,10))
        self.bias1 = torch.nn.Parameter(torch.randn(100))#第一层偏置值
        self.bias2 = torch.nn.Parameter(torch.randn(10))
        
    def forward(self,x):    #前向传播函数,不可改名
        x = x@self.weights1+self.bias1     #@==*
        x =  torch.sigmoid(x)  #激活函数
        return x @self.weights2+self.bias2

    def fit(self,x,y,lr,epoxhs,bs):
        for i in range(epoxhs):
            start = 0
            end = bs
            while end<=x.shape[0]:
                xb = x[start:end]
                yb = y[start:end]
                start = end
                end +=bs
                pred = self(xb)
                loss = torch.nn.functional.cross_entropy(pred,yb)#分类任务,交叉熵代价函数
                loss.backward()
                with torch.no_grad():
                    for p in self.parameters(): #更新每一个参数
                        p -=p.grad*lr
                    self.zero_grad()
        return loss
        
#共用部分,下面将不再重复
lr = 0.0001#学习率
bs = 64 #小批量的样本数
epoxhs = 30 #迭代轮数
train_X, test_X, train_y, test_y = np.load('./mnist.npy', allow_pickle=True)
x_train = train_X.reshape(60000, 28*28).astype(np.float32)
x_test = test_X.reshape(10000, 28*28).astype(np.float32)
x_train, y_train, x_test, y_test = map(torch.tensor, (x_train, train_y, x_test, test_y))#转换类型ndarray->tonser
x,y = x_train, y_train

#实例化模型
model = Mnist_Logistic()       
model.fit(x,y,lr,epoxhs,bs)

执行结果:
tensor(5.3542, grad_fn=<NllLossBackward>)

  • 使用 nn.Linear 重构
    PyTorch 的 nn.Linear 类建立一个线性层,以替代手动定义和初始化 self.weights 和 self.bias、计算 xb @ self.weights + self.bias 等工作。
import torch

class Mnist_Logistic(torch.nn.Module):
    def __init__(self,in_feartures,hid_feartures,out_feartures):
        super().__init__()
        self.layer1 = nn.Linear(in_feartures,hid_feartures)  #第一个线性层
        self.layer2 = nn.Linear(hid_feartures,out_feartures)
        
    def forward(self,x):#前向传播函数
        x = self.layer1(x)
        x = torch.sigmoid(x)
        return self.layer2(x)

	#与上面重复
    def fit(self,x,y,lr,epoxhs,bs):
        for i in range(epoxhs):
            start = 0
            end = bs
            while end<=x.shape[0]:
                xb = x[start:end]
                yb = y[start:end]
                start = end
                end +=bs
                pred = self(xb)
                loss = torch.nn.functional.cross_entropy(pred,yb)#分类任务
                loss.backward()
                with torch.no_grad():
                    for p in self.parameters():
                        p -=p.grad*lr

                    self.zero_grad()
        return loss
    
modle = Mnist_Logistic(784,100,10)
modle.fit(x,y,lr,epoxhs,bs)

执行结果:
tensor(0.8863, grad_fn=<NllLossBackward>)

  • 使用 optim 重构(完整包含交叉熵,L2,早停策略以及测试集的预测)
    我们可以使用优化器中的 step 方法来执行前向步骤,而不是手动更新参数
import numpy as np
import torch
from torch import nn
class Mnist_Logistic(torch.nn.Module):
    def __init__(self,in_feartures,hid_feartures,out_feartures):
        super().__init__()
        self.layer1 = nn.Linear(in_feartures,hid_feartures)#第一层
        self.layer2 = nn.Linear(hid_feartures,out_feartures)#第二层
        self.patience = 20  #耐心,损失累积不减的最大次数
    def forward(self,x):#前向传播函数
        x = self.layer1(x)
        x = torch.sigmoid(x)
        return self.layer2(x)

    
    def fit(self,x,y,lr,epoxhs,bs):
        opt = torch.optim.SGD(self.parameters(), lr=lr)
        x_val,y_val = x[0:500],y[0:500] #验证集
        train_x,train_y = x[500:],y[500:] #训练集
        worse_times =0  #损失连续不减少的次数
        best_para = 0  #最优参数
        best_step = 0  #最好的训练步长
        min_loss = 0   #最小损失
        for i in range(epoxhs):
            start = 0
            end = bs
            while end<=train_x.shape[0]:
                xb = train_x[start:end]
                yb = train_y[start:end]
                start = end
                end +=bs
                #训练集预测结果
                pred = self(xb)
                #引入L2正则化项
               	sum_1 = sum([(p**2).sum() for p in self.parameters() ])
                L2 = torch.pow(sum_1,0.5)
                loss = torch.nn.functional.cross_entropy(pred,yb)+L2#分类任务,交叉熵
                loss.backward()
                #更新参数
                opt.step()
                opt.zero_grad()
                #初始化将第一次的参数,损失,步长赋值给最优解
                if start ==0 and i==0:
                    best_para = self.parameters()
                    best_step = [i,start//bs]
                    min_loss = loss
                #每10次进行一次验证
                if end % (bs*10) == 0:
                    pred_val = self(x_val)
                    loss_val = torch.nn.functional.cross_entropy(pred_val,y_val)+L2
                    #如果当前损失小,更新参数
                    if loss_val < loss:
                        best_para = self.parameters()
                        worse_times =0
                        best_step = [i,start//bs]
                        min_loss = loss_val
                    #否则不更新,错误次数加一
                    else:
                        worse_times +=1
                #达到耐心值,提前终止
                if worse_times == self.patience:
                    return best_para,best_step,min_loss
        #返回最优参数,w,b,最优步长,最小损失
        return best_para,best_step,min_loss
    
    
lr = 0.0001   #学习率
bs = 64        #小批量样本数
epoxhs = 100   #迭代轮数
#导入数据
train_X, test_X, train_y, test_y = np.load('./mnist.npy', allow_pickle=True)
x_train = train_X.reshape(60000, 28*28).astype(np.float32)
x_test = test_X.reshape(10000, 28*28).astype(np.float32)
#转换tensor类型
x_train, y_train, x_test, y_test = map(torch.tensor, (x_train, train_y, x_test, test_y))
x,y = x_train, y_train 
#实例化模型
modle = Mnist_Logistic(784,100,10)
best_para,best_step,min_loss =modle.fit(x,y,lr,epoxhs,bs)
    
para_list = [i for i in best_para]#保存w.b
#开始预测验证集
w1,b1,w2,b2 = para_list[0],para_list[1],para_list[2],para_list[3]
pred_test_y = torch.softmax(torch.sigmoid(x_test @ w1.T +b1) @w2.T +b2)  #返回预测的各类概率
from sklearn.metrics import accuracy_score

print(y_test)
pred_test_y = torch.argmax(pred_test_y,axis = 1) #返回预测的类别
print(pred_test_y)
accuracy_score(y_test,pred_test_y) #计算准确率

执行结果:
tensor([7, 2, 1, …, 4, 5, 6]) #test
tensor([7, 2, 1, …, 4, 5, 6]) #predict
0.9124 #accuracy

  • 使用 Dataset 重构
    PyTorch 有一个抽象的 Dataset 类。 数据集可以是具有len函数(由 Python 的标准len函数调用)和具有getitem函数作为对其进行索引的一种方法。 PyTorch 的 TensorDataset 是一个数据集包装张量。 通过定义索引的长度和方式,这也为我们提供了沿张量的一维进行迭代,索引和切片的方法。 这将使我们在训练的同一行中更容易访问自变量和因变量。
from torch.utils.data import TensorDataset
train_ds = TensorDataset(x_train, y_train)#将x,y合并为同一个tensordataset对象,更方便切片
xb,yb = train_ds[i*bs : i*bs+bs]
  • 使用 DataLoader 进行重构
    Pytorch 的DataLoader负责批次管理。 您可以从任何Dataset创建一个DataLoader。 DataLoader使迭代迭代变得更加容易。 不必使用train_ds[ibs : ibs+bs],DataLoader 会自动为我们提供每个小批量。
from torch.utils.data import DataLoader

train_ds = TensorDataset(x_train, y_train)#合并x,y
train_dl = DataLoader(train_ds, batch_size=bs)#小批量自动迭代

for xb,yb in train_dl:
    pred = model(xb)

最终优化结果:(无范数,无早停)

opt = torch.optim.SGD(self.parameters(), lr=lr)
for epoch in range(epochs):
    for xb, yb in train_dl:
        pred = model(xb)
        loss = loss_func(pred, yb)

        loss.backward()
        opt.step()
        opt.zero_grad()

可以看出每一次重构都是在前一次的基础上进行简化步骤,不断优化,所以一般线性层重构,并使用step来自动更新参数。至于优化模型的方法,还可以引入正则化项(L1,L2)和提前终止策略,dropout,数据增强等。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值