当前位置 主页 > 网站技术 > 代码类 >

    numpy实现神经网络反向传播算法的步骤

    栏目:代码类 时间:2019-12-24 21:08

    一、任务

    实现一个4 层的全连接网络实现二分类任务,网络输入节点数为2,隐藏层的节点数设计为:25,50,25,输出层2 个节点,分别表示属于类别1 的概率和类别2 的概率,如图所示。我们并没有采用Softmax 函数将网络输出概率值之和进行约束,而是直接利用均方差误差函数计算与One-hot 编码的真实标签之间的误差,所有的网络激活函数全部采用Sigmoid 函数,这些设计都是为了能直接利用梯度推导公式。

    二、数据集

    通过scikit-learn 库提供的便捷工具生成2000 个线性不可分的2 分类数据集,数据的特征长度为2,采样出的数据分布如图 所示,所有的红色点为一类,所有的蓝色点为一类,可以看到数据的分布呈月牙状,并且是是线性不可分的,无法用线性网络获得较好效果。为了测试网络的性能,按照7: 3比例切分训练集和测试集,其中2000 ∗ 0 3 =600个样本点用于测试,不参与训练,剩下的1400 个点用于网络的训练。 

    import matplotlib.pyplot as plt
    import seaborn as sns #要注意的是一旦导入了seaborn,matplotlib的默认作图风格就会被覆盖成seaborn的格式
    from sklearn.datasets import make_moons
    from sklearn.model_selection import train_test_split
    N_SAMPLES = 2000 # 采样点数
    TEST_SIZE = 0.3 # 测试数量比率
    # 利用工具函数直接生成数据集
    X, y = make_moons(n_samples = N_SAMPLES, noise=0.2, random_state=100)
    # 将2000 个点按着7:3 分割为训练集和测试集
    X_train, X_test, y_train, y_test = train_test_split(X, y,
    test_size=TEST_SIZE, random_state=42)
    print(X.shape, y.shape)
    # 绘制数据集的分布,X 为2D 坐标,y 为数据点的标签
    def make_plot(X, y, plot_name, file_name=None, XX=None, YY=None, preds=None,dark=False):
      if (dark):
        plt.style.use('dark_background')
      else:
        sns.set_style("whitegrid")
      plt.figure(figsize=(16,12))
      axes = plt.gca()
      axes.set(xlabel="$x_1$", ylabel="$x_2$")
      plt.title(plot_name, fontsize=30)
      plt.subplots_adjust(left=0.20)
      plt.subplots_adjust(right=0.80)
      if(XX is not None and YY is not None and preds is not None):
        plt.contourf(XX, YY, preds.reshape(XX.shape), 25, alpha = 1,cmap=plt.cm.Spectral)
        plt.contour(XX, YY, preds.reshape(XX.shape), levels=[.5],cmap="Greys", vmin=0, vmax=.6)
      # 绘制散点图,根据标签区分颜色
      plt.scatter(X[:, 0], X[:, 1], c=y.ravel(), s=40, cmap=plt.cm.Spectral,edgecolors='none')
      plt.savefig('dataset.svg')
      plt.close()
    # 调用make_plot 函数绘制数据的分布,其中X 为2D 坐标,y 为标签
    make_plot(X, y, "Classification Dataset Visualization ")
    plt.show()

    三、网络层

    通过新建类Layer 实现一个网络层,需要传入网络层的数据节点数,输出节点数,激活函数类型等参数,权值weights 和偏置张量bias 在初始化时根据输入、输出节点数自动生成并初始化:

    class Layer:
      # 全连接网络层
      def __init__(self, n_input, n_neurons, activation=None, weights=None,
             bias=None):
        """
        :param int n_input: 输入节点数
        :param int n_neurons: 输出节点数
        :param str activation: 激活函数类型
        :param weights: 权值张量,默认类内部生成
        :param bias: 偏置,默认类内部生成
        """
     
        # 通过正态分布初始化网络权值,初始化非常重要,不合适的初始化将导致网络不收敛
        self.weights = weights if weights is not None else
        np.random.randn(n_input, n_neurons) * np.sqrt(1 / n_neurons)
        self.bias = bias if bias is not None else np.random.rand(n_neurons) *0.1
        self.activation = activation # 激活函数类型,如'sigmoid'
        self.last_activation = None # 激活函数的输出值o
        self.error = None # 用于计算当前层的delta 变量的中间变量
        self.delta = None # 记录当前层的delta 变量,用于计算梯度
     
      def activate(self, x):
        # 前向传播
        r = np.dot(x, self.weights) + self.bias # X@W+b
        # 通过激活函数,得到全连接层的输出o
        self.last_activation = self._apply_activation(r)
        return self.last_activation
      # 其中self._apply_activation 实现了不同的激活函数的前向计算过程:
      def _apply_activation(self, r):
     
        # 计算激活函数的输出
        if self.activation is None:
          return r # 无激活函数,直接返回
        # ReLU 激活函数
        elif self.activation == 'relu':
          return np.maximum(r, 0)
        # tanh
        elif self.activation == 'tanh':
          return np.tanh(r)
        # sigmoid
        elif self.activation == 'sigmoid':
          return 1 / (1 + np.exp(-r))
        return r
     
      # 针对于不同的激活函数,它们的导数计算实现如下:
      def apply_activation_derivative(self, r):
     
        # 计算激活函数的导数
        # 无激活函数,导数为1
        if self.activation is None:
          return np.ones_like(r)
        # ReLU 函数的导数实现
        elif self.activation == 'relu':
          grad = np.array(r, copy=True)
          grad[r > 0] = 1.
          grad[r <= 0] = 0.
          return grad
        # tanh 函数的导数实现
        elif self.activation == 'tanh':
          return 1 - r ** 2
        # Sigmoid 函数的导数实现
        elif self.activation == 'sigmoid':
          return r * (1 - r)
        return r