深度学习的特点:

使用多层神经网络,能够自动提取数据的多层次特征。
适合处理非结构化数据,如图像、音频、文本等。
依赖大量数据和计算资源,训练时间较长。
模型复杂,通常被视为“黑箱”,解释性较差。

一、神经网络基础

1.1、神经网络构成

人工神经网络(Artificial Neural Network,ANN)简称神经网络(NN),是一种模仿生物神经网

络结构和功能的计算模型。

神经元中的信息逐层传递(一般称为 前向传播 forward),上一层神经元的输出作为下一层神经元

的输入,每个连接都会有一个权重。

1.2、激活函数

激活函数必须是非线性函数,也正是激活函数的存在为神经网络引入了非线性,使得神经网络能够

学习和表示复杂的非线性关系。

1.2.1、阶跃函数Binary Step

阶跃函数的导数恒为0

# 激活函数

# 阶跃函数
def step_function0(x):
    if x > 0:
        return 1
    else:
        return 0

import numpy as np

def step_function(x):
    return np.array(x > 0 , dtype = int)

if __name__ == '__main__':
    x = np.array([0, 1, 2, 3, 4, 5, -1, -2, -3, -4, -5])
    x1 = np.array([[1,2,3],[-1,-2,-3]])
    print(step_function(x))
    print(step_function(x1))

[0 1 1 1 1 1 0 0 0 0 0]
[[1 1 1]
 [0 0 0]]
1.2.2、Sigmoid函数

Sigmoid(也叫 Logistic 函数)是平滑的、可微的,能将任意输入映射到区间(0,1)。常用于二分类

的输出层。但因其涉及指数运算,计算量相对较高。

Sigmoid 的输入在[-6,6]之外时,其输出值变化很小,可能导致信息丢失。

Sigmoid 的导数范围为(0,0.25),梯度较小。当输入在[-6,6]之外时,导数接近0,此时网络参数的

更新将会极其缓慢。使用Sigmoid作为激活函数,可能出现梯度消失,在逐层反向传播时,梯度

会呈指数级衰减。

import numpy as np
def sigmoid(x):
    return 1 / (1 + np.exp(-x))
x = np.array([0, 1, 2, 3, 4, 5, -1, -2, -3, -4, -5])
print(sigmoid(x))

[0.5        0.73105858 0.88079708 0.95257413 0.98201379 0.99330715
 0.26894142 0.11920292 0.04742587 0.01798621 0.00669285]
1.2.3、Tanh双曲正切函数

Tanh(双曲正切)将输入映射到区间(-1,1)。其关于原点中心对称。常用在隐藏层。输入在[-3,3]之

外时,Tanh的输出值变化很小,此时其导数接近0。Tanh的输出以0为中心,且其梯度相较于

Sigmoid 更大,收敛速度相对更快。但同样也存在梯度消失现象。

import numpy as np
x = np.array([0, 1, 2, 3, 4, 5, -1, -2, -3, -4, -5])
print(np.tanh(x))

[ 0.          0.76159416  0.96402758  0.99505475  0.9993293   0.9999092
 -0.76159416 -0.96402758 -0.99505475 -0.9993293  -0.9999092 ]
1.2.4、ReLU函数Rectified Linear Unit,修正线性单元

ReLU(Rectified Linear Unit,修正线性单元)会将小于0的输入转换为0,大于等于0的输入保持

不变。ReLU 作为激活函数不存在梯度消失。当输入小于0时,ReLU 的输出为0,这意味着在神

经网络中,ReLU激活的节点只有部分是“活跃”的,这种稀疏性有助于减少计算量提高模型效率。

ReLU的输出始终为0。这意味着神经元可能永远不会被激活,从而导致“神经元死亡”问题。这会影

响模型的学习能力,特别是如果大量的神经元都变成了“死神经元”。为解决此问题,可使用Leaky

ReLU来代替ReLU作为激活函数。

import numpy as np
# relu
def relu(x):
    return np.maximum(0, x)
x = np.array([0, 1, 2, 3, 4, 5, -1, -2, -3, -4, -5])
print(relu(x))

[0 1 2 3 4 5 0 0 0 0 0]
1.2.5、softmax函数

Softmax 的分母是指数之和,是为了保证输出是一个平滑、可微、非负且总和为1的概率分布,同

时放大不同类别之间的差异。

import numpy as np
def softmax(x):
    return np.exp(x) / np.sum(np.exp(x))
x = np.array([0, 1, 2, 3, 4, 5, -1, -2, -3, -4, -5])
print(softmax(x))

[4.25926596e-03 1.15778853e-02 3.14719551e-02 8.55496437e-02
 2.32548042e-01 6.32131116e-01 1.56689638e-03 5.76428965e-04
 2.12056366e-04 7.80111772e-05 2.86987083e-05]
# 数据溢出的对策
def softmax(x):
    x = x - np.max(x)
    return np.exp(x) / np.sum(np.exp(x))

这个是为了防止数据溢出,如果x里的数据很大,再指数一下很容易就溢出了

# 考虑二维矩阵的输入
def softmax(x):
    if x.ndim == 2:
        x = x.T
        x = x - np.max(x,axis = 0)
        y = np.exp(x) / np.sum(np.exp(x),axis = 0)
        return y.T
    x = x - np.max(x)
    return np.exp(x) / np.sum(np.exp(x))

这个是考虑二维矩阵输入

1.2.6、其他常见激活函数

1.2.7 如何选择激活函数

1.3、神经网络的简单实现

深度神经网络由多个层(layer)组成,通常将其称之为 模型(Model)。整个模型接受原始输入

(特征),生成输出(预测),并包含一些参数。而在模型内部,每个单独的层都会接受一些输

入(由前一层提供),生成输出(到下一层的输入),并包含一组参数;层层向下传递,就可以得

到最终的输出值。

神经网络中的参数,就是每一层的权重和偏置。

1.3.1、三层神经网络

我们这里以一个三层神经网络为例,实现从输入到输出的处理计算,这个过程就是前向传播

(forward)

权重矩阵大小应该是输入神经元个数 × 输出神经元个数

偏置向量大小是(输出神经元个数,)

我们可以将神经网络的所有参数(每一层的权重w和偏置b),保存在一个字典network中,并定

义函数:
init_network():对参数进行初始化,每一个权重参数都是一个矩阵(二维),每一个偏置参数则是

一个数组(一维)

forward():前向传播,将输入信号转换为输出信号的处理操作。这里的激活函数,隐藏层用

sigmoid,输出层用 identity(恒等函数)。

import numpy as np
from common.functions import sigmoid,identity

# 初始化网络
def init_network():
    network = {}
    # 第一层参数
    network['W1'] = np.array([[0.1,0.3,0.5],[0.2,0.4,0.6]])
    network['b1'] = np.array([0.1,0.2,0.3])
    # 第二层参数
    network['W2'] = np.array([[0.1,0.4],[0.2,0.5],[0.3,0.6]])
    network['b2'] = np.array([0.1,0.2])
    # 第三层参数
    network['W3'] = np.array([[0.1,0.3],[0.2,0.4]])
    network['b3'] = np.array([0.1,0.2])
    return network
# 前向传播
def forward(network,X):
    w1,w2,w3 = network['W1'],network['W2'],network['W3']
    b1,b2,b3 = network['b1'],network['b2'],network['b3']
    a1 = X @ w1 + b1
    z1 = sigmoid(a1)
    a2 = z1 @ w2 + b2
    z2 = sigmoid(a2)
    a3 = z2 @ w3 + b3
    y = identity(a3)
    return y
# 主流程
x = np.array([1.0,0.5])
network = init_network()
y = forward(network,x)
print(y)

[0.31682708 0.69627909]

1.4、手写数字识别案例

数据集https://www.kaggle.com/competitions/digit-recognizer

这里,我们构建的也是一个三层神经网络,输入层应该有784个神经元,输出层有10个神经元(表

示0~9的分类结果);中间设置2个隐藏层,第一个隐藏层有50个神经元,第二个隐藏层有100个神

经元。这里的参数是需要学习得到的;我们假设已经学习完毕,直接从保存好的文件nn_sample中

进行读取即可。

import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler
import joblib
from common.functions import *

def get_data():
    data = pd.read_csv('../data/train.csv')
    X = data.drop(columns = 'label',axis = 1)
    y = data['label']
    x_train, x_test, y_train, y_test = train_test_split(X, y, test_size = 0.3, random_state = 42)
    scaler = MinMaxScaler()
    x_train = scaler.fit_transform(x_train)
    x_test = scaler.transform(x_test)
    return  x_test, y_test

def init_network():
    # 从文件中加载已经训练好的模型
    network = joblib.load('../data/nn_sample')
    return network

def forward(network,X):
    w1,w2,w3 = network['W1'],network['W2'],network['W3']
    b1,b2,b3 = network['b1'],network['b2'],network['b3']
    a1 = X @ w1 + b1
    z1 = sigmoid(a1)
    a2 = z1 @ w2 + b2
    z2 = sigmoid(a2)
    a3 = z2 @ w3 + b3
    y = softmax(a3)
    return y

x_test, y_test = get_data()
network = init_network()
y_proba = forward(network,x_test)
y_pred = np.argmax(y_proba, axis = 1) # 这里因为x是有很多样本,所以y_proba是每个样本自己的预测概率
# 计算准确率
acc_cnt = np.sum(y_pred == y_test)
n = x_test.shape[0]
print(f'Accuracy:',acc_cnt / n)

Accuracy: 0.9362698412698413

这上面X的shape是(12600,784),这样计算量太大,意思就是一直有12600个样本在处理,我们分批量测试,一次处理100个

import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler
import joblib
from common.functions import *

def get_data():
    data = pd.read_csv('../data/train.csv')
    X = data.drop(columns = 'label',axis = 1)
    y = data['label']
    x_train, x_test, y_train, y_test = train_test_split(X, y, test_size = 0.3, random_state = 42)
    scaler = MinMaxScaler()
    x_train = scaler.fit_transform(x_train)
    x_test = scaler.transform(x_test)
    return  x_test, y_test

def init_network():
    # 从文件中加载已经训练好的模型
    network = joblib.load('../data/nn_sample')
    return network

def forward(network,X):
    w1,w2,w3 = network['W1'],network['W2'],network['W3']
    b1,b2,b3 = network['b1'],network['b2'],network['b3']
    a1 = X @ w1 + b1
    z1 = sigmoid(a1)
    a2 = z1 @ w2 + b2
    z2 = sigmoid(a2)
    a3 = z2 @ w3 + b3
    y = softmax(a3)
    return y

x_test, y_test = get_data()
network = init_network()

# 定义一些变量
batch_size = 100
n = x_test.shape[0]
acc_cnt = 0
# 循环迭代,分批预测,前向传播
for i in range(0,n,batch_size):
    # 取出当前批次
    x_batch = x_test[i:i+batch_size]
    y_batch = y_test[i:i+batch_size]
    y_proba = forward(network,x_batch)
    y_pred = np.argmax(y_proba, axis = 1)
    # 累计预测正确个数
    acc_cnt += np.sum(y_pred == y_batch)
print(f'Accuracy:', acc_cnt / n)

Accuracy: 0.9362698412698413

1.5、损失函数

神经网络中,需要以某个指标为线索来寻找最优权重参数;这个指标就是损失函数(loss

function)

1.5.1、均方误差MSE

均方误差(Mean Squared Error ,MSE),也称 L2 Loss,L2 Loss 对异常值敏感,遇到异常值时易发生梯度爆炸。

n是数据的“维度”。对于固定维度的网络,前面的系数n不重要,因此公式有时也可以写成这样,因

为求导之后把2约掉,这样会比较简洁

def mean_squared_error(y,t):
    return 0.5 * np.sum((y - t)**2)
1.5.2、交叉熵误差

交叉熵误差(Cross Entropy Error)也经常被用作损失函数

第一个if是转成二维方便后续处理,第二个if是判断是否是独热编码,如果是要转换成索引形式,

argmax是np里一个取最大值索引的方法

# 交叉熵误差
def cross_entropy_error(y,t):
    if y.ndim == 1:
        t = t.reshape(1,-1)
        y = y.reshape(1,-1)
    if t.size == y.size:
        t = np.argmax(t,axis = 1)
    n = y.shape[0]
    return -np.sum(np.log(y[np.arange(n),t] + 1e-7)) / n
1.5.3、拓展

1.6、数值微分

损失函数的值越小,代表我们选取的参数越适合;想要求得损失函数的最小值,最基本的想法就是

对函数求导,解出导数值为0的点,并判断它是否为极小值/最小值。然而,实际的函数直接求导不

容易得到解析解。这时可以用数值微分的方式来求某点处的导数,这在工程上应用非常广泛。

这个是前向差分

以下代码是中心差分,这个更精确,因为它用到了两边的点(x+h 和 x−h),误差项会相互抵消。

# 数值微分
def numerical_diff(f,x):
    h = 1e-4
    return (f(x+h) - f(x-h))/(2 * h)
1.6.1、tangentline案例
import numpy as np
import matplotlib.pyplot as plt
from common.gradient import numerical_diff

def f(x):
    return 0.01 * x ** 2 + 0.1 * x

def tangent_line(f,x0):
    # 利用数值微分计算当前点的导数值
    a = numerical_diff(f, x0)
    b = f(x0) - a * x0
    return lambda x: a * x + b

x0 = 5.0
tf = tangent_line(f, x0)

x = np.arange(0.0,20.0,0.1)
y1 = f(x)
y2 = tf(x)
plt.plot(x,y1)
plt.plot(x,y2)
plt.show()

1.6.2、利用中心差分计算梯度

x为向量,i就是在那个索引的位置

下面的enumerate意思是i是索引号,x是向量

# 利用数值微分计算梯度,x为向量
def _numerical_gradient(f,x):
    h = 1e-4
    grad = np.zeros_like(x)
    for i in range(x.size):
        tmp = x[i]
        x[i] = tmp + h
        fxh1 = f(x)
        x[i] = tmp - h
        fxh2 = f(x)
        grad[i] = (fxh1 - fxh2)/(2*h)
        x[i] = tmp
    return grad

def numerical_gradient(f,X):
    if X.ndim == 1:
        return _numerical_gradient(f,X)
    else:
        grad = np.zeros_like(X)
        for i,x in enumerate(X):
            grad[i] = _numerical_gradient(f,x)
        return grad
1.6.3、神经网络的梯度计算

在神经网络的学习中,梯度的计算非常重要。神经网络中的梯度,指的就是损失函数关于权重参数

的梯度。

import numpy as np
from common.functions import *
from common.gradient import numerical_gradient

class SimpleNet:
    def __init__(self):
        self.W = np.random.randn(2,3)
        self.b = np.zeros(3)
    def forward(self,X):
        a = X @ self.W + self.b
        y = softmax(a)
        return y
    def loss(self, x, t):
        y = self.forward(x)
        loss_value = cross_entropy_error(y, t)
        return loss_value

if __name__ == '__main__':
    x = np.array([0.6,0.9])
    t = np.array([0,0,1])
    net = SimpleNet()
    loss_f = lambda w : net.loss(x,t)
    grad_w = numerical_gradient(loss_f,net.W)
    print(grad_w)


[[ 0.04865014  0.28398957 -0.33263971]
 [ 0.07297521  0.42598435 -0.49895956]]

1.7、梯度下降法

沿着目标函数(如损失函数)的负梯度方向逐步调整参数,从而逼近函数的最小值。

def gradient_descent(f,init_x,lr=0.01,step_num=100):
    x = init_x
    x_history = []
    for i in range(step_num):
        x_history.append(x.copy())
        grad = numerical_gradient(f,x)
        x -= lr * grad
    return x,np.array(x_history)

.copy() 是为了保留每次更新前的真实轨迹。

1.7.1、求最小值案例

目标函数:f(x1,x2) = x1 ** 2 + x2 ** 2

import numpy as np
import matplotlib.pyplot as plt
from common.gradient import  gradient_descent

# 目标函数:f(x1,x2) = x1 ** 2 + x2 ** 2
def f(x):
    return x[0] ** 2 + x[1] ** 2

init_x = np.array([-3.0,4.0])

lr = 0.1
num_iters = 20

x,x_history = gradient_descent(f,init_x,lr,num_iters)
print(x)
print(x_history)

plt.scatter(x_history[:,0],x_history[:,1])
plt.plot([-5,5],[0,0],'--b')
plt.plot([0,0],[-5,5],'--b')
plt.xlabel('x1')
plt.ylabel('x2')
plt.show()

[-0.03458765  0.04611686]
[[-3.          4.        ]
 [-2.4         3.2       ]
 [-1.92        2.56      ]
 [-1.536       2.048     ]
 [-1.2288      1.6384    ]
 [-0.98304     1.31072   ]
 [-0.786432    1.048576  ]
 [-0.6291456   0.8388608 ]
 [-0.50331648  0.67108864]
 [-0.40265318  0.53687091]
 [-0.32212255  0.42949673]
 [-0.25769804  0.34359738]
 [-0.20615843  0.27487791]
 [-0.16492674  0.21990233]
 [-0.1319414   0.17592186]
 [-0.10555312  0.14073749]
 [-0.08444249  0.11258999]
 [-0.06755399  0.09007199]
 [-0.0540432   0.07205759]
 [-0.04323456  0.05764608]]

1.8、模型训练相关概念

一个epoch是对应整个数据集n,一个iter是对应一个batch

1.9、SDG

实际操作时,一般会从训练数据中随机选择一个小批量数据(mini-batch),然后用梯度下降法迭

代多个轮次(iteration);这种“对随机选择的数据进行的梯度下降法”,被称作 随机梯度下降法

(stochastic gradient descent,SGD)。

2.0、两层神经网络类 

lambda的意思就是给我一个参数 (比如 W1、W2、b1 或 b2),我就把它代入当前网络中计算loss(x,t)

import numpy as np
from common.functions import  *
from common.gradient import numerical_gradient
class TwoLayerNet():
    def __init__(self, input_size, hidden_size, output_size,weight_init_std=0.01):
        self.params = {}
        self.params['W1'] = np.random.randn(input_size, hidden_size) * weight_init_std
        self.params['b1'] = np.zeros(hidden_size)
        self.params['W2'] = np.random.randn(hidden_size, output_size) * weight_init_std
        self.params['b2'] = np.zeros(output_size)
    def forward(self, X):
        W1,W2 = self.params['W1'], self.params['W2']
        b1,b2 = self.params['b1'], self.params['b2']
        a1 = X @ W1 + b1
        z1 = sigmoid(a1)
        a2 = z1 @ W2 + b2
        y = softmax(a2)
        return y
    def loss(self,x,t):
        y = self.forward(x)
        return cross_entropy_error(y,t)
    def accuracy(self,x,t):
        y_proba = self.forward(x)
        y_pred = np.argmax(y_proba, axis=1)
        acc = np.sum(y_pred == t) / x.shape[0]
        return acc
    def numerical_gradient(self, x, t):
        loss_f = lambda _: self.loss(x,t)
        grads = {}
        grads['W1'] = numerical_gradient(loss_f,self.params['W1'])
        grads['b1'] = numerical_gradient(loss_f,self.params['b1'])
        grads['W2'] = numerical_gradient(loss_f,self.params['W2'])
        grads['b2'] = numerical_gradient(loss_f,self.params['b2'])
        return grads

加入数据加载之后的模型训练和验证,完整版,这个代码可以运行,但是运行速度太慢了,主要慢在

grads = network.numerical_gradient(x_batch, t_batch)

这里,每次计算梯度都要调底层的数值计算梯度下降方法

batch_index = np.random.choice(n,batch_size)这句话的意思是从n个里面随机选取一个batch_size

这下面的代码,可以用以下解释

batch_index = np.random.choice(n,batch_size)
x_batch = x_train[batch_index]
t_batch = t_train[batch_index]

import numpy as np
import matplotlib.pyplot as plt
from two_layer_net import TwoLayerNet
from common.load_data import get_data

x_train, t_train, x_test, t_test = get_data()
print(x_train.shape,x_test.shape,t_train.shape,t_test.shape)

network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)

lr = 0.1
batch_size = 100
num_epochs = 10

n = x_train.shape[0]
iters_per_epoch = np.ceil(n / batch_size) # 每个epoch里的迭代次数
iters_num = int(iters_per_epoch * num_epochs)

train_loss_list = []
train_acc_list = []
test_acc_list = []

for i in range(iters_num):
    batch_index = np.random.choice(n,batch_size)
    x_batch = x_train[batch_index]
    t_batch = t_train[batch_index]
    grads = network.numerical_gradient(x_batch, t_batch)
    for key in network.params.keys():
        network.params[key] -= lr * grads[key]

    this_loss = network.loss(x_batch, t_batch)
    train_loss_list.append(this_loss)
    print('Loss:{}'.format(this_loss))
    if i % iters_per_epoch == 0:
        train_acc = network.accuracy(x_train, t_train)
        test_acc = network.loss(x_test, t_test)
        train_acc_list.append(train_acc)
        test_acc_list.append(test_acc)
        print(f'train_acc:{train_acc}, test_acc:{test_acc}')

更多推荐