深度学习入门阅读笔记(2-4章)

202 阅读4分钟

本书重点,主要是实现了一个简单的两层神经网络,并且用它跑了一下mnist数据集

神经网络

激活函数

  • 一般是指这样的函数 h(x)={0(x<=0)1(x>0)h(x)=\begin{cases} 0 & (x<=0) \\ 1&(x>0) \\ \end{cases} ,当输入的值超过0时,就输出1,认为是被激活了。
import numpy as  np  
import matplotlib.pyplot as plt  
  
def step_function(x):  
    return np.array(x>0,dtype=int)  
  
x = np.arange(-5.0,5.0,0.1)  
y = step_function(x)  
plt.plot(x,y)  
plt.ylim(-0.1,1.1)  
plt.show()

Sigmoid函数

h(x)=1(1+exp(x))(exp(x)=ex)h(x)= \frac{1}{(1+ exp(-x))} \quad (exp(-x)=e^{-x})

import numpy as  np  
import matplotlib.pyplot as plt  
  
def sigmoid(x):  
    return 1 / (1 + np.exp(-x))  
  
x = np.arange(-5.0,5.0,0.1)  
y = sigmoid(x);  
plt.plot(x,y)  
plt.ylim(-0.1,1.1)  
plt.show()
  • 绘制图形比较后可知,Sigmoid函数的曲线更加的平滑。与阶跃函数相比,它输出可以是小数,而不仅仅是0/1。
  • 二者都是 非线性函数。在神经网络的 激活函数中,必须使用 非线性函数线性函数的问题在于,增加层数的时候,总是存在与它等效的 无隐藏层的神经网络。举例: 假如用y=axy=ax作为激活函数,选择三层,等效的运算是y(x)=aaaxy(x)=a*a*a*x ,此时,令c=a3,y=cxc=a^3,y=c*x是等价的

三层神经网络的实现

  • 第一层的加权和AA可以用以下式子表示,其中XX是输入信号,W代表了权重,B称为偏置值。 A(1)=XW(1)+B(1)A^{(1)}=XW^{(1)}+B^{(1)}
import numpy as  np  
import matplotlib.pyplot as plt  
def sigmoid(x):  
    return 1 / (1 + np.exp(-x))  
  
X = np.array([1.0,0.5])  
W1 = np.array([[0.1,0.3,0.5],[0.2,0.4,0.6]])  
B1 = np.array([0.1,0.2,0.3])  
  
A1 = np.dot(X,W1) + B1  
Z1 = sigmoid(A1)  
  
W2 = np.array([[0.1,0.4],[0.2,0.5],[0.3,0.6]])  
B2 = np.array([0.1,0.2])  
A2 = np.dot(Z1,W2) + B2  
Z2 = sigmoid(A2)  
  
W3 = np.array([[0.1,0.3],[0.2,0.4]])  
B3 = np.array([0.1,0.2])  
A3 = np.dot(Z2,W3) + B3  
Y = A3  
print(Y)

输出层的设计

  • 一般来说,回归问题用恒等函数,分类函数使用softmax函数

Softmax函数

  • softmax函数: yk=exp(ak)i=1nexp(ai)y_k=\frac{exp(a_k)}{\sum_{i=1}^{n}exp(a_i)} 其中,exp(x)exp(x)是表示exe^x的一个指数函数
  • 然而,这个形式的softmax函数很容易溢出,采用分母分子同时乘上一个很小的数的形式来优化yk=Cexp(ak)Ci=1nexp(ai)=exp(ak+logC)i=1nexp(ai+logC)=exp(ak+logC)i=1nexp(ai+logC)y_k=\frac{C*exp(a_k)}{C*\sum_{i=1}^{n}exp(a_i)}=\frac{exp(a_k+logC)}{\sum_{i=1}^{n}exp(a_i+logC)}=\frac{exp(a_k+logC^`)}{\sum_{i=1}^{n}exp(a_i+logC^`)}此处的CC^` 可以是任意常数,一般取最大值的负数
  • 改进后的softmax函数如下:
import numpy as  np  
import matplotlib.pyplot as plt  
  
def softmax(a):  
    mx = np.max(a)  
    exp_a = np.exp(a-mx) #为了防止溢出  
    sum_exp_a = np.sum(exp_a)  
    return exp_a / sum_exp_a  

性质

  • Softmax函数的输出是 0.0~1.0之间的实数,并且softmax函数的输出值 总和为1.,可以把它解释成概率。

神经网络的学习

  • 神经网络的特征就是可以 从数据中学习,也就是由数据自动决定权重参数的值

数据驱动

  • 人可以直接使用经验识别出“5”的数字,而机器不行。我们需要从图像中 提取特征量,再用机器学习技术学习特征量的模式。提取特征值后,可以用SVM,KNN等分类器进行学习。
  • 在机器学习中,数据分为 训练数据测试数据。我们需要在训练数据中寻找最优的参数,然后在 测试数据上正确地评估模型。

损失函数

  • 用于评估当前的神经网络对 监督数据在多大程度上不拟合损失函数越小越拟合
  • 一般使用 均方误差交叉熵误差

均方误差

  • E=12k(yktk)2E=\frac{1}{2} \sum_k({y_k-t_k})^2 其中,yk是神经网络的输出,tk是监督的数据,k是数据的维度y_k是神经网络的输出,t_k是监督的数据,k是数据的维度

交叉熵误差

  • E=ktklog(yk)E = -\sum_k{t_k*log(y_k)}

批处理的损失函数

  • 对于多组数据来说,我们要把单个的函数值全部都加起来E=1Nnktnklog(ynk)E = -\frac{1}{N}\sum_n\sum_kt_{nk}log(y_nk)
def cross_entropy_error(y,t):  
    if y.ndim == 1:  
        t = t.reshape(1,t.size)  
        y = y.reshape(1,y.size)  
    batch_size = y.shape[0]  
    return -np.sum(np.log(y+1e-7)) / batch_size

mini-batch

  • 对于MNIST数据集来说,有60000个图像,全部计算损失函数耗费较大。通常选取 一批小数据来进行训练,
# coding: utf-8  
import sys, os  
sys.path.append(os.pardir)  # 親ディレクトリのファイルをインポートするための設定  
import numpy as np  
from dataset.mnist import load_mnist  
from PIL import Image  
  
  
  
  
(x_train, t_train), (x_test, t_test) = \  
    load_mnist(normalize=True,one_hot_label=True)  
  
train_size = x_train.shape[0]  
batch_size = 10  
batch_mask = np.random.choice(train_size, batch_size)  
print(batch_mask)  
x_train = x_train[batch_mask]  
print(x_train.shape)  
t_train = t_train[batch_mask]  
print(t_train.shape)
  • 上述代码完成了功能:批量的 随机的取出了单位为10的训练集。

识别精度

  • 识别精度=正确分类的样本样本总数识别精度=\frac{正确分类的样本}{样本总数}通常不用它作为评估神经网络的学习好坏,而是用 损失函数,原因是因为该函数大部分情况下 不可微,没有办法利用梯度评估下一步如何该调整参数。

梯度

  • 回顾一下梯度的定义——由 偏导数构成的向量,比如说二元函数的梯度(fx,fy)(\frac{\partial f}{\partial {x}},\frac{\partial f}{\partial y})
  • 在某点的偏导数,用以下式子计算fx(x0,y0)=limh0f(x0+h)f(x0h)2h\frac{\partial f}{\partial x}{|}(x_0,y_0)=\lim_{{h}\to {0} } \frac{f({x_0+h})-f({x_0-h})}{2*h}
import numpy as np         
      def function_2(x):  
         return x[0]**2 + x[1]**2  
       
      def numrcial_gradient(f,x):  
         h = 1e-4  
         grad = np.zeros_like(x)  
       
         for idx in range(x.size):  
             tmp = x[idx]  
             x[idx] = tmp + h  
             fxh1 = f(x)  
             x[idx] = tmp - h  
             fxh2 = f(x)  
             grad[idx] = (fxh1 - fxh2) / (2*h)  
             x[idx] = tmp #还原值  
         return grad  
      grad = numrcial_gradient(function_2,np.array([3.0,4.0]))  
      print(grad)
      # 结果是[6. 8.] 该代码显示了如何接受一个函数和它对应的点,返回对应点的梯度值
  • 重要性质:梯度指向函数值减小的最快的方向,梯度的方向 不一定指向最小值,但沿着梯度的方向能最大限度地减小损失函数的值。

梯度法

  • 函数的取值从当前函数梯度前进一定距离,重新求梯度,再沿着梯度前行。像这样不断沿着梯度前行,来减少损失函数的值的方法称为 梯度法
  • 用数学公式表达梯度法,如下:x0=x0ηfx0x_0=x_0 -\eta * \frac{\partial f}{\partial{x_0}}
  • 该式子中的 η\eta 表示更新量,称为 学习率。学习率决定再一次学习中,应该学习多少,再多大程度上更新参数
  • 学习率需要 事先设定,过小/过大的效果都不会太好

梯度下降法的实现

  • 梯度下降法是指利用梯度法来寻找函数最小值的方法
          # 利用梯度下降法,寻找f(x,y)=x^2 + y^2 的极小值
          import numpy as np  
           
          def function_2(x):  
             return x[0]**2 + x[1]**2  
           
          def numrcial_gradient(f,x):  
             h = 1e-4  
             grad = np.zeros_like(x)  
           
             for idx in range(x.size):  
                 tmp = x[idx]  
                 x[idx] = tmp + h  
                 fxh1 = f(x)  
                 x[idx] = tmp - h  
                 fxh2 = f(x)  
                 grad[idx] = (fxh1 - fxh2) / (2*h)  
                 x[idx] = tmp #还原值  
             return grad  
          def gradient_descent(f,init_x,lr=0.01,step_num=100):  
             # f是要优化的函数,init_x是初始值,lr是learning rate,step_num是要学习的次数  
             # 梯度下降法就是要找到 极小值的点  
             x = init_x  
             for i in range(step_num):  
                 grad = numrcial_gradient(f,x)  
                 print("i is"+str(i)+"x is"+str(x))  
                 x-= lr * grad  
             return x  
           
          init_x = np.array([10.0,10.0])  
          x = gradient_descent(function_2,init_x=init_x,lr=0.01,step_num=200)  
          print(x)
          # 结果是:[0.17587947 0.17587947],只要把学习次数调高一点,就会越来越靠近[0,0]这个点
  • 学习率这种无法通过学习调整的参数,需要人为进行设置的参数称为超参数

神经网路的梯度

  • 在神经网络中,损失函数就是梯度中的函数,参数就是指 神经网络中的 权重参数
  • WW来表示这个权重参数矩阵,用LL来表示损失函数 W=(w11w12w13w21w22w23)W=\begin{pmatrix} w_{11} & w_{12} & w_{13} \\ w_{21} & w_{22} & w_{23} \end{pmatrix}
import numpy as np  
import matplotlib.pyplot as plt  
  
  
def softmax(a):  
    mx = np.max(a)  
    exp_a = np.exp(a - mx)  # 为了防止溢出  
    sum_exp_a = np.sum(exp_a)  
    return exp_a / sum_exp_a  
  
  
# 利用梯度下降法,寻找f(x,y)=x^2 + y^2 的极小值  
import numpy as np  
  
  
def function_2(x):  
    return x[0] ** 2 + x[1] ** 2  
  
  
def numerical_gradient(f, x):  
    h = 1e-4  # 0.0001  
    grad = np.zeros_like(x)  
  
    it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])  
    while not it.finished:  
        idx = it.multi_index  
        tmp_val = x[idx]  
        x[idx] = tmp_val + h  
        fxh1 = f(x)  # f(x+h)  
  
        x[idx] = tmp_val - h  
        fxh2 = f(x)  # f(x-h)  
        grad[idx] = (fxh1 - fxh2) / (2 * h)  
  
        x[idx] = tmp_val  # 値を元に戻す  
        it.iternext()  
  
    return grad  
def cross_entropy_error(y,t):  
    if y.ndim == 1:  
        t = t.reshape(1,t.size)  
        y = y.reshape(1,y.size)  
    batch_size = y.shape[0]  
    return -np.sum(np.log(y+1e-7)) / batch_size  
  
  
# 实现一个基本的神经网络  
class simpleNet:  
    def __init__(self):  
        self.W = np.random.randn(2,3) #用高斯分布进行初始化  
  
    def predict(self,x):  
        return np.dot(x,self.W)  
  
    def loss(self,x,t):  
        z = self.predict(x)  
        y = softmax(z)  
        loss = cross_entropy_error(y,t)  
        return loss  
  
x = np.array([0.6, 0.9])  
t = np.array([0, 0, 1])  
  
net = simpleNet()  
  
f = lambda w: net.loss(x, t)  
dW = numerical_gradient(f, net.W)  
print(dW)
  • 该段代码实现了初始化一个大小为 232*3的神经网络,并且设置了其初始权重。然后使用[[#交叉熵误差]] 作为损失函数

两层神经网络的实现

初始化

# coding: utf-8  
import sys, os  
sys.path.append(os.pardir)   
from common.functions import *  
from common.gradient import numerical_gradient  
import numpy as np  
  
  
class TwoLayerNet:  
  
    def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):   
        self.params = {}  
        self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)  
        self.params['b1'] = np.zeros(hidden_size)  
        self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)  
        self.params['b2'] = np.zeros(output_size)  
  
    def predict(self, x):  
        W1, W2 = self.params['W1'], self.params['W2']  
        b1, b2 = self.params['b1'], self.params['b2']  
      
        a1 = np.dot(x, W1) + b1  
        z1 = sigmoid(a1)  
        a2 = np.dot(z1, W2) + b2  
        y = softmax(a2)  
          
        return y  
          
    def loss(self, x, t):  
        y = self.predict(x)  
          
        return cross_entropy_error(y, t)  
      
    def accuracy(self, x, t):  
        y = self.predict(x)  
        y = np.argmax(y, axis=1)  
        t = np.argmax(t, axis=1)  
          
        accuracy = np.sum(y == t) / float(x.shape[0])  
        return accuracy  
          
    def numerical_gradient(self, x, t):  
        loss_W = lambda W: self.loss(x, t)  
          
        grads = {}  
        grads['W1'] = numerical_gradient(loss_W, self.params['W1'])  
        grads['b1'] = numerical_gradient(loss_W, self.params['b1'])  
        grads['W2'] = numerical_gradient(loss_W, self.params['W2'])  
        grads['b2'] = numerical_gradient(loss_W, self.params['b2'])  
          
        return grads  
          
    def gradient(self, x, t):  
        W1, W2 = self.params['W1'], self.params['W2']  
        b1, b2 = self.params['b1'], self.params['b2']  
        grads = {}  
          
        batch_num = x.shape[0]  
          
        # forward  
        a1 = np.dot(x, W1) + b1  
        z1 = sigmoid(a1)  
        a2 = np.dot(z1, W2) + b2  
        y = softmax(a2)  
          
        # backward  
        dy = (y - t) / batch_num  
        grads['W2'] = np.dot(z1.T, dy)  
        grads['b2'] = np.sum(dy, axis=0)  
          
        dz1 = np.dot(dy, W2.T)  
        da1 = sigmoid_grad(a1) * dz1  
        grads['W1'] = np.dot(x.T, da1)  
        grads['b1'] = np.sum(da1, axis=0)  
  
        return grads
  • 上述代码完成了一个神经网络的初始化, 权重参数为符合高斯分布的数字进行初始化,偏置值为0.输入神经元大小为784(28 * 28图像大小),输出神经元大小为10(0~9)个数字

梯度下降法寻找最佳参数

import numpy as np  
import sys, os  
sys.path.append(os.pardir)  
import matplotlib.pyplot as plt  
from dataset.mnist import load_mnist  
from two_layer_net import TwoLayerNet  
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True,one_hot_label=True)  
  
train_loss_list = []  
iters_num = 10000  
train_size = x_train.shape[0]  
batch_size =100  
learning_rate =0.1  
#初始化网络  
network = TwoLayerNet(input_size=784,hidden_size=50,output_size=10)  
  
for i in range(iters_num):  
    # 随机获取100个数据  
    batch_mask = np.random.choice(train_size,batch_size)  
    x_batch = x_train[batch_mask]  
    t_batch = t_train[batch_mask]  
    # 计算梯度  
    # grad = network.numerical_gradient(x_batch,t_batch)  
    grad = network.gradient(x_batch,t_batch)  
    # 根据梯度与学习率更改梯度  
    for key in ('W1','b1','W2','b2'):  
        network.params[key] -= learning_rate * grad[key]  
    loss  = network.loss(x_batch,t_batch)  
    print("第"+str(i)+"次训练的loss是")  
    print(loss)  
    train_loss_list.append(loss)  
plt.plot(train_loss_list)  
plt.show()
  • Pasted image 20240403232547.png
  • 可见,损失函数随着步数的增加逐渐减小
  • 在神经网络的学习中,必须确定你的模型是否会 过拟合,也就是模型能正确 识别训练集,但是不能正确地识别 测试集

epoch

  • epoch是指——当整个训练集都被使用过时,此时的 训练次数。比如有1000个数据,每次mini-batch训练要用100个数据,此时的epoch次数就是10.
import numpy as np  
import sys, os  
sys.path.append(os.pardir)  
import matplotlib.pyplot as plt  
from dataset.mnist import load_mnist  
from two_layer_net import TwoLayerNet  
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True,one_hot_label=True)  
  
train_loss_list = []  
train_acc_list = []  
test_acc_list = []  
  
iters_num = 10000  
train_size = x_train.shape[0]  
batch_size =100  
learning_rate =0.1  
iter_per_epoch = max(train_size/batch_size,1)  
#初始化网络  
network = TwoLayerNet(input_size=784,hidden_size=50,output_size=10)  
  
for i in range(iters_num):  
    # 随机获取100个数据  
    batch_mask = np.random.choice(train_size,batch_size)  
    x_batch = x_train[batch_mask]  
    t_batch = t_train[batch_mask]  
    # 计算梯度  
    # grad = network.numerical_gradient(x_batch,t_batch)  
    grad = network.gradient(x_batch,t_batch)  
    # 根据梯度与学习率更改梯度  
    for key in ('W1','b1','W2','b2'):  
        network.params[key] -= learning_rate * grad[key]  
    loss  = network.loss(x_batch,t_batch)  
    train_loss_list.append(loss)  
    if i % iter_per_epoch == 0:  
        train_acc = network.accuracy(x_train,t_train)  
        test_acc = network.accuracy(x_test,t_test)  
        train_acc_list.append(train_acc)  
        test_acc_list.append(test_acc)  
x = np.arange(len(train_acc_list))  
plt.plot(x, train_acc_list, label='train acc')  
plt.plot(x, test_acc_list, label='test acc', linestyle='--')  
plt.show()
  • 以上代码每一个epoch就检查每一个节点的准确率,可以发现在训练集和测试集中,二者表现差不多,可以说没有发生 过拟合