深度学习python实现

 

本文是看深度学习入门(基于python的理论与实现)这本书所做的学习笔记。本文忽略了神经网络的数学原理,着重于代码实现。

神经网络结构

神经网络结构由输入层,中间层,输出层组成。神经网络的基本构架如下图所示:

                                                                   深度学习python实现

实现一个简单的神经网络主要实现**函数,负责层与层之间的矩阵运算部分,输出函数这些部分的前向传播和误差后向传播,还有参数更新有损失函数部分。本文中后向传播backward() 函数返回的是参数梯度变化的值,公式的推导使用的是图计算。

**函数

神经网络的**函数主要有Sigmoid,ReLU函数等。

Sigmoid函数为:​深度学习python实现

ReLU函数为:​深度学习python实现

下图为几种**函数的比较:

 

深度学习python实现

 

Sigmoid函数的实现代码如下所示:

class Sigmoid:
    """
    Sigmoid**函数实现正反向传播
    """
    def __init__(self):
        self.out = None     # 反向传播需要
        
    def forward(self, x):
        """
        根据公式计算**函数的值
        """
        self.out = out = 1/(1 + np.exp(-x))
        return out
    
    def backward(self, dout):
        """
        dx = dout * y * (1-y)
        """
        return dout*self.out*(1-self.out)

ReLU函数 实现代码如下所示:

class Relu:
    """
    Relu函数正反向传递
    forward():
    backward():
    """
    def __init__(self):
        self.mask = None
    
    def forward(self, x):
        """
        当x>0时,返回x,x<=0时返回0
        x:为输入,是一个numpy类型
        """
        self.mask = (x <= 0)
        out = x.copy()  # 产生一个备份
        out[self.mask] = 0
        return out
    
    def backward(self, dout):
        """
        当原输入x > 0时,反向传播直接为dout
        当x <= 0 时,反响传播输出0
        """
        dout[self.mask] = 0
        return dout

矩阵运算Affine层

                                                                         深度学习python实现

Affine层的图计算如图所示:

 

深度学习python实现

其中(1) 式为:​深度学习python实现(2) 式为:​ 深度学习python实现

关于W 的梯度值的矩阵形状,即使是在mini-batch的训练下的大小也是两层节点之间的权重 W的数量,这是因为将多个样本的梯度加到了一起。因此在输出层误差反向传递时,各个节点的误差应该除以batch_size

深度学习python实现为后面网络的反向传递结果,看作一个已知的值。B是网络的偏置。

Affine层的代码实现如下所示。

class Affine:
    def __init__(self, w, b):
        self.W = w     # 网络之间的权重
        self.b = b     # 偏置
        self.x = None  # 输入
        self.dw = None # 关于x的梯度
        self.db = None # 关于B的梯度
        
    def forward(self, x):
        self.x = x
        return np.dot(x, self.W) + self.b
    
    def backward1(self, dout):
        dx = np.dot(dout, self.W.T)
        self.dw = np.dot(self.x.T, dout)
        self.db = np.sum(dout, axis=0)
        return dx
    
    def backward(self, dout):
        dx = np.dot(dout, self.W.T)
        if self.x.ndim == 1:
            self.dw = np.dot(self.x.reshape(len(self.x),1), dout.reshape(1, len(dout)))
            self.db = dout
        else:
            self.dw = np.dot(self.x.T, dout)
            self.db = np.sum(dout, axis=0)
        return dx

输出层设计

一般情况下,回归问题使用恒等函数,分类问题用softmax函数。

softmax函数 的形式为:

与softmax函数一起使用的损失函数是交叉熵误差 。关于softmax函数的实现如下所示。

class SoftmaxWithLoss():
    """softmax-with-loss前向后向传播实现,并计算交叉熵误差"""
    def __init__(self):
        self.loss = None # 误差
        self.y = None # 输出
        self.t = None # 监督数据
        
    def forward(self,x, t=None):
        """
        x:输入
        t:标签,监督数据
        """
        self.t = t
        self.y = softmax(x)
        self.loss = cross_entropy_error(self.y, t)
        return self.loss
    
    def backward(self):
        return (self.y - self.t)/len(self.y)

梯度参数更新

梯度参数的更新的方法有SGDMomentumAdaGradAdam 等。本文使用AdaGrad方法,实现如下所示(更新方式为所有参数一起更新):

class AdaGrad:
    def __init__(self, lr=0.01):
        self.lr = lr
        self.h = None
        
    def update(self, params, grads):
        if self.h is None:
            self.h = {}
            for key, val in params.items():
                self.h[key] = np.zeros_like(val)  # 给所有的梯度添加一个h,
        
        for key, val in params.items():
            self.h[key] += grads[key]**2
            params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)

初始化一个具体的神经网络

本文初始化一个四层的神经网络,初始代码如下所示。

class LayerNet:
    def __init__(self, input_size, hide, out_size,lr = 0.001):
        """hide:隐藏层,表示为一个元组()"""
        self.params = {}
        
        # 参数初始化,ReLU的权重初始化为标准差为 根号(2/n)。n为前一层的节点数
        self.params["W1"] = np.sqrt(2)*np.random.randn(input_size, hide[0])/np.sqrt(input_size)
        self.params["b1"] = np.zeros(hide[0])
        
        self.params["W2"] = np.sqrt(2)*np.random.randn(hide[0], hide[1])/np.sqrt(hide[0])
        self.params["b2"] = np.zeros(hide[1])
    
        self.params["W3"] = np.sqrt(2)*np.random.randn(hide[1], out_size)/np.sqrt(hide[1])
        self.params["b3"] = np.zeros(out_size)
        
        #生成层
        self.Layer = OrderedDict()    # 有序字典
        self.Layer["Affine1"] = Affine(self.params["W1"], self.params["b1"])
        self.Layer["Relu1"] = Relu()
        
        self.Layer["Affine2"] = Affine(self.params["W2"], self.params["b2"])
        self.Layer["Relu2"] = Relu()
        
        self.Layer["Affine3"] = Affine(self.params["W3"], self.params["b3"])
        
        self.lastLayer = SoftmaxWithLoss()
        
        self.gradchange = AdaGrad(lr)  # 梯度更新
        
    def predict(self, x, t):
        for layer in self.Layer.values():
            x = layer.forward(x)
        return x
    
    def loss(self, x,t):
        y = self.predict(x, t)
        return self.lastLayer.forward(y, t)
    
    def accuracy(self,x, t):
        y = self.predict(x,t)
        mask = np.argmax(y, axis=1)
        t = np.argmax(t, axis = 1)
        return np.sum(mask == t)/float(len(t))
    
    def gradient(self, x, t):
        # 前向传播
        self.loss(x, t)
        
        # 后向传播
        dout = self.lastLayer.backward()
        
        layers = list(self.Layer.values())
        layers.reverse()
        for layer in layers:
            dout = layer.backward(dout)
            
        grad = {}
        grad["W1"] = self.Layer["Affine1"].dw
        grad["b1"] = self.Layer["Affine1"].db
        grad["W2"] = self.Layer["Affine2"].dw
        grad["b2"] = self.Layer["Affine2"].db
        grad["W3"] = self.Layer["Affine3"].dw
        grad["b3"] = self.Layer["Affine3"].db
        return grad
    
    def update_gradient(self,x, t):
        grad = self.gradient(x, t)
        self.gradchange.update(self.params, grad)

运行

本文采用的数据为sk-learn的digits dataset,是8X8的格式。图片如下图所示。

 

深度学习python实现

from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
import numpy as py
​
data, target = load_digits(return_X_y=True)   # 导入数据
target1 = np.array(pd.get_dummies(target))    # 把数字2形式转换为向量形式[0,0,1,0,0,0...]
X_train, X_test, y_train, y_test = train_test_split(data, target1, test_size=0.3, random_state=0)
​
network = LayerNet(64,(1000, 1000), 10, 0.0001)   # 初始化网络
iters_num = 1000                     # 迭代次数
train_size = len(X_train)
batch_size = 25                     # mini_batch的大小
iters_per_epoch = max(train_size//batch_size, 1)    # 每经过一个epoch的迭代次数
train_acc_list = []             # 对于每次epoch迭代,训练数据集的精度
test_acc_list = []              # 对于每次epoch迭代,测试数据集的精度
​
for i in range(iters_num):
    # 分裂数据,提取簇
    batch_mask = np.random.choice(train_size, batch_size)
    batch_X = X_train[batch_mask]
    batch_y = y_train[batch_mask]
        
    network.update_gradient(batch_X, batch_y)
    
    if i % iters_per_epoch == 0:
        train_acc_list.append(network.accuracy(X_train, y_train))
        test_acc_list.append(network.accuracy(X_test, y_test))

绘制学习曲线

绘制学习曲线的代码如下所示。

# 学习曲线
plt.figure()
plt.title("learn-curves")
plt.xlabel("Training epoch")
plt.ylabel("Score")
train_scores_std = np.std(train_acc_list)
test_scores_std = np.std(test_acc_list)
plt.grid()
plt.plot(np.arange(len(train_acc_list)), train_acc_list, 'o-', color="r",
         label="Training score")
plt.plot(np.arange(len(test_acc_list)), test_acc_list, 'o-', color="g",
         label="test score")
​
plt.legend(loc="best")

用上面的参数,学习曲线如下图所示。

 

深度学习python实现