机器学习模型自我代码复现:MLP多层感知机

根据模型的数学原理进行简单的代码自我复现以及使用测试,仅作自我学习用。模型原理此处不作过多赘述,仅罗列自己将要使用到的部分公式。

如文中或代码有错误或是不足之处,还望能不吝指正。

 本文侧重于使用numpy重新写出一个使用BP算法反向传播的MLP模型,故而不像其他文章那样加入图片演示正向传播与反相传播的原理或是某个特定函数的求导过程以及结论。

深度学习的一个典型例子就是将多层感知机(由输入到输出映射的数学函数)叠加在一起,由多个简单的函数符合在一起去拟合真实的目标函数;每一层隐藏层用以提取数据中的抽象特征,并且加入激活函数以拟合非线性函数。

前向传播:

X_{out} = f(W\cdot X_{in} + b)

其中,X_{in}为输入层/隐藏层/输出层的输入;W为这一层每个神经元的权重,b为神经元的偏置,f为激活函数,X_{out}为这一层的输出。

我们训练的目标,就是通过梯度下降的思想,求出Wb的梯度方向,在那个方向上,进行参数更新。

BP算法:输出层到隐含层

根据链式法则,\frac{\partial E}{\partial w_k} = \frac{\partial E}{\partial out} * \frac{\partial out}{\partial net} * \frac{\partial net}{\partial w_k}

其中,\frac{\partial E}{\partial out}为损失值Loss的逆向梯度,可以根据损失函数进求导进行计算;\frac{\partial out}{\partial net}为从输出层到隐含层的梯度,可以根据最后一个隐含层的激活函数求得;\frac{\partial net}{\partial w_k}为隐含层输出到自身某个权重参数w_k的梯度,因为net = w_1 * last_1 + w_2 * last_2 +...+w_k * last_k+...(其中last_k为上一层隐含层的输出),所以\frac{\partial net}{\partial w_k} =last_k

隐含层到隐含层:

\frac{\partial net_2}{\partial w_k} = \frac{\partial net_2}{\partial net_1}*\frac{\partial net_1}{\partial w_k}

其中,\frac{\partial net_2}{\partial net_1}为这一层的输出到上一层的梯度,可以通过激活函数求导获得;\frac{\partial net_1}{\partial w_k} =last_k,理由同上。

隐含层到输入层计算方法类似。

使用numpy实现的MLP:

import numpy as np

class MLP_np:
    def __init__(self,act='sigmoid',loss='MSE',norm_initialize=True):
        """
        初始化,初始化以下内容:
        self.weights:各层的权重,以[ch_in,ch_out]实现
        self.biases:各层的偏置
        self.outs:各层输出,训练时保存正向传播的结果,反向传播时使用
        self.act:激活函数,目前只支持sigmoid,relu,tanh
        self.loss:损失函数,此处只支持MSE
        norm_initialize:是否使用正态分布随机初始权重
        """
        self.sizes = [784,30,30,10] #各层神经元个数,此处暂时不将sizes作为参数
        
        #各层的权重,形状为[784,30][30,30][30,10]
        self.weights = []
        for i in range(len(self.sizes)-1):
            if norm_initialize:
                np.random.seed(i)
                self.weights.append(np.random.normal(0,1,(self.sizes[i],self.sizes[i+1])))
            else:
                self.weights.append(np.random.randn(self.sizes[i],self.sizes[i+1]))
        #初始化各层偏置
        if norm_initialize:
            np.random.seed(i)
            self.biases = [np.random.normal(0,1,(i,)) for i in self.sizes[1:]]
        else:
            self.biases = [np.random.randn(i) for i in self.sizes[1:]]
        
        #各层的输出
        self.outs = []
        self.act=act
        self.loss = loss
        #加入sigmoid函数
        
    def _activation(self,z,act='sigmoid'):
        """
        正向传播的激活函数
        """
        if act == 'sigmoid':
            return 1.0/(1.0+np.exp(-z))
        elif act == 'relu':
            return np.maximum(z,0)
        elif act == 'tanh':
            return (np.exp(z)-np.exp(-z))/(np.exp(z)+np.exp(-z))
        else:
            raise ValueError("必须是sigmoid,relu或是tanh")
    
    def _d_activation(self,z,act='sigmoid'):
        """
        激活函数求导,反向传播
        """
        if act == 'sigmoid':
            return z*(1-z)
        elif act == 'relu':
            z = np.maximum(z,0)
            z[z>0] = 1
            return z
        elif act == 'tanh':
            return 1-self._activation(z,act='tanh')**2
        else:
            raise ValueError("必须是sigmoid,relu或是tanh")
        
    def _loss(self,pred,label,loss='MSE'):
        """
        损失函数
        """
        if loss == "MSE":
            return ((pred-label)**2)/2
        else:
            raise ValueError("暂时不支持其他")
    
    def _loss_back(self,pred,label,loss='MSE'):
        """
        计算损失函数逆向的求导
        此处没有考虑“在多分类问题之后加入softmax层”的情况
        ,所以MSE与交叉熵的损失函数不会有“softmax”的内容
        """
        if loss == "MSE":
            return (pred-label)
        else:
            raise ValueError("暂时不支持其他")
        
    def forward(self,x,train=True):
        """
        前向传播
        x:输入
        train:是否正在训练,True时将其存档
        """
        self.outs.clear()
        for i in range(len(self.sizes)-1):
            w = self.weights[i]
            b = self.biases[i]
            
            z = np.dot(x,w)+b
            #激活函数
            x = self._activation(z)
            if train:
                self.outs.append(x)
        
        return x
    
    def backward(self,x,y,learning_rate = 0.01):
        """
        后向传播,全量梯度下降
        x:输入
        y:输出
        learning_rate:学习率
        """
        self.forward(x)
        #将个各层的梯度方向保存起来
        nabla_w = [np.zeros(w.shape) for w in self.weights]
        nabla_b = [np.zeros(b.shape) for b in self.biases]
        #输出层到隐含层
        total_to_out = self._loss_back(self.outs[-1],y,self.loss)
        out_to_net = self._d_activation(self.outs[-1],self.act)
        net_to_w = self.outs[-2]
        delta = total_to_out*out_to_net
        nabla_w[-1] = np.dot(net_to_w.T,delta)
        nabla_b[-1] = np.sum(delta,axis=0)
        #隐含层到隐含层
        net2_to_net1 = self._d_activation(self.outs[-2],self.act)
        delta = np.dot(delta,self.weights[-1].T)*net2_to_net1
        nabla_w[-2] = np.dot(self.outs[-3].T,delta)
        nabla_b[-2] = np.sum(delta,axis=0)
        #隐含层到输入层
        net1_to_input = self._d_activation(self.outs[-3],self.act)
        delta = np.dot(delta,self.weights[-2].T)*net1_to_input
        nabla_w[-3] = np.dot(x.T,delta)
        nabla_b[-3] = np.sum(delta,axis=0)
        
        #更新
        for i in range(len(nabla_b)):
            self.weights[i] -= learning_rate*nabla_w[i]
            self.biases[i] -= learning_rate*nabla_b[i]
            
    def calc_loss(self,pred,label):
        """
        计算损失函数
        pred:预测值
        label:真实值
        """
        return self._loss(pred,label,self.loss)
    
    def pred(self,x):
        """
        使用模型预测,进行一次正向传播
        x:输入
        """
        y = self.forward(x,train=False)
        return y

使用MNIST数据集进行测试:

from torchvision import datasets
from torchvision import transforms
from torch.utils.data import DataLoader
from visdom import Visdom

train_loader = DataLoader(
    datasets.MNIST('mnist_data',train=True,download=False,
                              transform = transforms.Compose(
                                  [transforms.ToTensor()
                                  ]
                              )),batch_size=512,shuffle=True
)

test_loader = DataLoader(
    datasets.MNIST('mnist_data',train=False,download=False,
                              transform = transforms.Compose(
                                  [transforms.ToTensor()
                                  ]
                              )),batch_size=512,shuffle=True
)

myMLP = MLP_np()
viz = Visdom()

global_step=0
test_loss = []
test_acc = []
for epoch in range(100):
    for batch_size,(x,y) in enumerate(train_loader):
        global_step+=1
        x = np.array(x).reshape(x.shape[0],28*28)
        y_idx = np.array(y)
        y = np.eye(10)[y_idx]
        myMLP.backward(x,y)
        pred = myMLP.pred(x)
        #print(np.sum(myMLP.calc_loss(pred,y)))
        if global_step%50==0:
            viz.line([np.mean(myMLP.calc_loss(pred,y))],[global_step],win="train_loss",update="append")
    if epoch % 10 == 0:
        test_loss_epoch = 0
        test_acc_epoch = 0
        for batch_size,(x,y) in enumerate(test_loader):
            x = np.array(x).reshape(x.shape[0],28*28)
            y_idx = np.array(y)
            y_true = np.eye(10)[y_idx]
            pred = myMLP.pred(x)
            test_loss_epoch += np.sum(myMLP.calc_loss(pred,y_true))/10000 #测试集大小
            pred = np.argmax(myMLP.pred(x),axis=1)
            test_acc_epoch += np.sum(np.equal(pred,np.array(y)))/10000
        test_loss.append(test_loss_epoch)
        test_acc.append(test_acc_epoch)
        print(epoch,"test_loss",test_loss_epoch)
        print(epoch,"test_acc",test_acc_epoch)

test_loss_epoch = 0
test_acc_epoch = 0
for batch_size,(x,y) in enumerate(test_loader):
    x = np.array(x).reshape(x.shape[0],28*28)
    y_idx = np.array(y)
    y_true = np.eye(10)[y_idx]
    pred = myMLP.pred(x)
    test_loss_epoch += np.sum(myMLP.calc_loss(pred,y_true))/10000 #测试集大小
    pred = np.argmax(myMLP.pred(x),axis=1)
    test_acc_epoch += np.sum(np.equal(pred,np.array(y)))/10000
test_loss.append(test_loss_epoch)
test_acc.append(test_acc_epoch)
print(epoch,"test_loss",test_loss_epoch)
print(epoch,"test_acc",test_acc_epoch)

如图所示为在训练集上的损失

99 test_loss 0.04836542739525751
99 test_acc 0.9388000000000002

 在测试集上的准确率为0.9389

使用pytorch实现MLP做对比:

由于pytorch使用的优化器不是每一次都选取所有样本进行梯度下降,故而需要增加每层神经元个数

import numpy as np
from torchvision import datasets
from torchvision import transforms
from torch.utils.data import DataLoader
from visdom import Visdom
import torch
from torch import nn
from torch.nn import functional as F

train_loader = DataLoader(
    datasets.MNIST('mnist_data',train=True,download=False,
                              transform = transforms.Compose(
                                  [transforms.ToTensor()
                                  ]
                              )),batch_size=512,shuffle=True
)

test_loader = DataLoader(
    datasets.MNIST('mnist_data',train=False,download=False,
                              transform = transforms.Compose(
                                  [transforms.ToTensor()
                                  ]
                              )),batch_size=512,shuffle=True
)

model = nn.Sequential(
    nn.Linear(784,200),
    nn.Sigmoid(),
    nn.Linear(200,200),
    nn.Sigmoid(),
    nn.Linear(200,10),
    nn.Sigmoid()
)

optim = torch.optim.Adam(model.parameters(),lr=0.01)
using_loss = nn.MSELoss()

viz = Visdom()
global_step=0
test_loss = []
test_acc = []
for epoch in range(100):
    for batch_idx,(x,y) in enumerate(train_loader):
        global_step+=1
        x.resize_([x.shape[0],28*28])
        y = F.one_hot(y).to(torch.float32)
        y_pred = model(x)
        loss = using_loss(y_pred,y)
        optim.zero_grad()
        loss.backward()
        optim.step()
        if global_step%50==0:
            viz.line([loss.item()],[global_step],win="train_loss_torch",update="append",opts = dict(title="pytorch训练的损失函数"))
    if epoch % 10 == 0:
        test_loss_epoch = 0
        test_acc_epoch = 0
        with torch.no_grad():
            for batch_idx,(x,y) in enumerate(test_loader):
                x.resize_([x.shape[0],28*28])
                y_onehot = F.one_hot(y).to(torch.float32)
                y_pred = model(x)
                test_loss_epoch += using_loss(y_pred,y_onehot).item()
                pred = np.argmax(np.array(y_pred),axis=1)
                test_acc_epoch += np.sum(np.equal(pred,np.array(y)))
        test_loss.append(test_loss_epoch)
        test_acc.append(test_acc_epoch/10000)
        print(epoch,"test_loss",test_loss_epoch)
        print(epoch,"test_acc",test_acc_epoch/10000)

90 test_loss 0.08563304203562438
90 test_acc 0.9749

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
mlp多层感知与深度学习密切相关。深度学习是一种机器学习的方法,它基于神经网络的多层结构进行建模和训练。多层感知(MLP)是最简单的神经网络结构之一,也是深度学习中常用的模型之一。 MLP由多个计算层组成,包括输入层、若干个隐层和输出层。每个隐层都由多个隐单元组成,而隐单元的个数是根据数据集的杂度来确定的。对于简单的数据集,可以选择较少的隐单元,而对于杂的数据集,可以选择更多的隐单元,甚至可以添加多个隐层。 例如,当我们使用MLP进行分类任务时,可以使用两个隐层,每个隐层都包含10个隐单元。这样的设置可以通过修改MLPClassifier类的hidden_layer_sizes属性来实。具体的代码如下所示: ``` mlp = MLPClassifier(solver='lbfgs', random_state=0, hidden_layer_sizes=[10, 10]) ``` 这个设置意味着我们有两个隐层,每个隐层都有10个隐单元。 因此,通过使用MLP多层感知,我们可以实深度学习中的模型构建和训练,以解决各种机器学习问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [深度学习简介与MLP多层感知](https://blog.csdn.net/qq_43355223/article/details/86593078)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值