神经网络基本原理与数学基础
本文将从最基础的概念开始,逐步深入讲解神经网络的数学基础,包括线性代数、微积分在神经网络中的应用,以及神经网络的基本结构和工作原理。
文章目录
深度学习的核心是神经网络,而要真正理解神经网络,就必须掌握其背后的数学原理。本文将从最基础的概念开始,逐步深入讲解神经网络的数学基础,包括线性代数、微积分在神经网络中的应用,以及神经网络的基本结构和工作原理。
从生物神经元到人工神经元
在开始数学推导之前,我们先从直观的角度理解神经网络的起源。
生物神经元的启发
人脑中有大约860亿个神经元,它们通过突触相互连接,形成了一个复杂的网络。每个神经元接收来自其他神经元的信号,经过处理后,决定是否向下一个神经元传递信号。
这个过程给了我们启发:能否用数学模型来模拟这个过程?
人工神经元模型
1943年,McCulloch和Pitts提出了第一个人工神经元模型,也称为感知机。这个模型非常简单:
- 接收多个输入信号 x₁, x₂, …, xₙ
- 每个输入都有一个权重 w₁, w₂, …, wₙ
- 计算加权和:z = w₁x₁ + w₂x₂ + … + wₙxₙ + b
- 通过激活函数得到输出:y = f(z)
用数学公式表示就是:
y = f(Σwᵢxᵢ + b) = f(w^T·x + b)
其中,b是偏置项,f是激活函数。
下面用代码实现一个简单的神经元:
import numpy as np
class Neuron:
def __init__(self, n_inputs):
# 随机初始化权重和偏置
self.weights = np.random.randn(n_inputs)
self.bias = np.random.randn()
def forward(self, inputs):
"""前向传播"""
# 计算加权和
z = np.dot(self.weights, inputs) + self.bias
# 应用激活函数(这里使用sigmoid)
output = self.sigmoid(z)
return output
def sigmoid(self, z):
"""Sigmoid激活函数"""
return 1 / (1 + np.exp(-z))
# 创建一个有3个输入的神经元
neuron = Neuron(n_inputs=3)
inputs = np.array([1.0, 2.0, 3.0])
output = neuron.forward(inputs)
print(f"输入: {inputs}")
print(f"权重: {neuron.weights}")
print(f"偏置: {neuron.bias}")
print(f"输出: {output:.4f}")
神经网络的数学基础
要深入理解神经网络,需要掌握一些数学知识,主要包括线性代数和微积分。
线性代数基础
神经网络的计算本质上是大量的矩阵运算,因此线性代数是理解神经网络的关键。
向量和矩阵
在神经网络中:
- 输入数据通常表示为向量或矩阵
- 权重表示为矩阵
- 偏置表示为向量
例如,对于一个有3个输入、2个输出的层:
输入向量:x = [x₁, x₂, x₃]^T (3×1)
权重矩阵:W = [[w₁₁, w₁₂, w₁₃], [w₂₁, w₂₂, w₂₃]] (2×3)
偏置向量:b = [b₁, b₂]^T (2×1)
输出计算:z = W·x + b
import numpy as np
# 定义输入、权重和偏置
x = np.array([[1.0], [2.0], [3.0]]) # 3×1
W = np.array([[0.1, 0.2, 0.3], # 2×3
[0.4, 0.5, 0.6]])
b = np.array([[0.1], [0.2]]) # 2×1
# 计算输出
z = np.dot(W, x) + b
print("输入形状:", x.shape)
print("权重形状:", W.shape)
print("偏置形状:", b.shape)
print("输出形状:", z.shape)
print("输出值:\n", z)
批量处理
在实际应用中,我们通常一次处理多个样本(一个批次),这时输入变成了矩阵:
输入矩阵:X = [[x₁₁, x₁₂, x₁₃], [x₂₁, x₂₂, x₂₃], …] (batch_size × n_features)
输出计算:Z = X·W^T + b
注意这里权重矩阵需要转置。
# 批量处理示例
batch_size = 4
n_features = 3
n_outputs = 2
# 输入:4个样本,每个3个特征
X = np.random.randn(batch_size, n_features) # 4×3
W = np.random.randn(n_outputs, n_features) # 2×3
b = np.random.randn(n_outputs, 1) # 2×1
# 计算输出
Z = np.dot(X, W.T) + b.T # 4×2
print(f"批量输入形状: {X.shape}")
print(f"批量输出形状: {Z.shape}")
print(f"输出:\n{Z}")
微积分基础
神经网络的训练依赖于梯度下降算法,而梯度的计算需要用到微积分中的导数和链式法则。
导数的直观理解
导数表示函数在某一点的变化率。对于函数 y = f(x),导数 dy/dx 表示当 x 变化一个很小的量时,y 变化的速度。
在神经网络中,我们需要计算损失函数对每个参数的导数,这告诉我们如何调整参数才能减小损失。
import matplotlib.pyplot as plt
def f(x):
"""示例函数 f(x) = x²"""
return x ** 2
def df(x):
"""f(x)的导数 f'(x) = 2x"""
return 2 * x
# 绘制函数和切线
x = np.linspace(-3, 3, 100)
y = f(x)
x0 = 1.5
y0 = f(x0)
slope = df(x0)
# 切线方程:y - y0 = slope * (x - x0)
tangent_y = y0 + slope * (x - x0)
plt.figure(figsize=(10, 6))
plt.plot(x, y, label='f(x) = x²', linewidth=2)
plt.plot(x, tangent_y, 'r--', label=f'切线 (斜率={slope})', linewidth=2)
plt.scatter([x0], [y0], color='red', s=100, zorder=5)
plt.xlabel('x')
plt.ylabel('y')
plt.title('函数及其在x=1.5处的切线')
plt.legend()
plt.grid(True)
plt.show()
链式法则
链式法则是反向传播算法的核心。对于复合函数 y = f(g(x)),链式法则告诉我们:
dy/dx = (dy/dg) × (dg/dx)
在神经网络中,输出是输入经过多层变换得到的,链式法则让我们能够计算损失对每一层参数的梯度。
# 链式法则示例
def g(x):
"""内层函数 g(x) = 2x + 1"""
return 2 * x + 1
def f(g):
"""外层函数 f(g) = g²"""
return g ** 2
def dg_dx(x):
"""g对x的导数"""
return 2
def df_dg(g):
"""f对g的导数"""
return 2 * g
# 计算复合函数在x=3处的导数
x = 3
g_val = g(x)
f_val = f(g_val)
# 使用链式法则
dy_dx = df_dg(g_val) * dg_dx(x)
print(f"x = {x}")
print(f"g(x) = {g_val}")
print(f"f(g(x)) = {f_val}")
print(f"df/dx = {dy_dx}")
# 验证:直接计算 f(g(x)) = (2x+1)² = 4x² + 4x + 1
# 导数:8x + 4
print(f"直接计算导数: {8*x + 4}")
神经网络的基本结构
理解了单个神经元后,我们来看如何将多个神经元组织成网络。
层的概念
神经网络由多个层组成,每一层包含多个神经元。常见的层类型有:
- 输入层:接收原始数据
- 隐藏层:进行特征提取和变换
- 输出层:产生最终预测结果
全连接层
全连接层(也叫密集层)是最基本的层类型,层中的每个神经元都与上一层的所有神经元相连。
对于一个全连接层:
- 输入:x ∈ ℝⁿ
- 权重:W ∈ ℝᵐˣⁿ
- 偏置:b ∈ ℝᵐ
- 输出:y = f(Wx + b) ∈ ℝᵐ
class DenseLayer:
def __init__(self, n_inputs, n_neurons):
"""
全连接层
n_inputs: 输入特征数
n_neurons: 神经元数量
"""
# He初始化
self.weights = np.random.randn(n_neurons, n_inputs) * np.sqrt(2.0 / n_inputs)
self.biases = np.zeros((n_neurons, 1))
def forward(self, inputs):
"""
前向传播
inputs: shape (n_inputs, batch_size)
"""
self.inputs = inputs
self.z = np.dot(self.weights, inputs) + self.biases
return self.z
def __repr__(self):
return f"DenseLayer(输入={self.weights.shape[1]}, 输出={self.weights.shape[0]})"
# 创建一个简单的网络
layer1 = DenseLayer(n_inputs=3, n_neurons=4)
layer2 = DenseLayer(n_inputs=4, n_neurons=2)
print("网络结构:")
print(f" {layer1}")
print(f" {layer2}")
# 前向传播
x = np.random.randn(3, 1) # 单个样本
h1 = layer1.forward(x)
h2 = layer2.forward(h1)
print(f"\n输入形状: {x.shape}")
print(f"隐藏层输出形状: {h1.shape}")
print(f"最终输出形状: {h2.shape}")
激活函数的作用
如果神经网络只有线性变换,那么无论有多少层,整个网络都等价于一个线性变换。激活函数引入了非线性,使得神经网络能够学习复杂的非线性关系。
常用的激活函数有:
- Sigmoid:σ(x) = 1 / (1 + e⁻ˣ)
- Tanh:tanh(x) = (eˣ - e⁻ˣ) / (eˣ + e⁻ˣ)
- ReLU:ReLU(x) = max(0, x)
- Leaky ReLU:LeakyReLU(x) = max(αx, x)
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def tanh(x):
return np.tanh(x)
def relu(x):
return np.maximum(0, x)
def leaky_relu(x, alpha=0.01):
return np.where(x > 0, x, alpha * x)
# 绘制激活函数
x = np.linspace(-5, 5, 100)
plt.figure(figsize=(15, 10))
plt.subplot(2, 2, 1)
plt.plot(x, sigmoid(x), linewidth=2)
plt.title('Sigmoid')
plt.grid(True)
plt.axhline(y=0, color='k', linestyle='--', alpha=0.3)
plt.axvline(x=0, color='k', linestyle='--', alpha=0.3)
plt.subplot(2, 2, 2)
plt.plot(x, tanh(x), linewidth=2)
plt.title('Tanh')
plt.grid(True)
plt.axhline(y=0, color='k', linestyle='--', alpha=0.3)
plt.axvline(x=0, color='k', linestyle='--', alpha=0.3)
plt.subplot(2, 2, 3)
plt.plot(x, relu(x), linewidth=2)
plt.title('ReLU')
plt.grid(True)
plt.axhline(y=0, color='k', linestyle='--', alpha=0.3)
plt.axvline(x=0, color='k', linestyle='--', alpha=0.3)
plt.subplot(2, 2, 4)
plt.plot(x, leaky_relu(x), linewidth=2)
plt.title('Leaky ReLU')
plt.grid(True)
plt.axhline(y=0, color='k', linestyle='--', alpha=0.3)
plt.axvline(x=0, color='k', linestyle='--', alpha=0.3)
plt.tight_layout()
plt.show()
前向传播:从输入到输出
前向传播是神经网络进行预测的过程,数据从输入层流向输出层。
前向传播的数学表示
对于一个L层的神经网络,前向传播的过程可以表示为:
第1层:
- z⁽¹⁾ = W⁽¹⁾x + b⁽¹⁾
- a⁽¹⁾ = f⁽¹⁾(z⁽¹⁾)
第2层:
- z⁽²⁾ = W⁽²⁾a⁽¹⁾ + b⁽²⁾
- a⁽²⁾ = f⁽²⁾(z⁽²⁾)
…
第L层:
- z⁽ᴸ⁾ = W⁽ᴸ⁾a⁽ᴸ⁻¹⁾ + b⁽ᴸ⁾
- ŷ = a⁽ᴸ⁾ = f⁽ᴸ⁾(z⁽ᴸ⁾)
完整的前向传播实现
class NeuralNetwork:
def __init__(self, layer_sizes):
"""
初始化神经网络
layer_sizes: 列表,每个元素表示该层的神经元数量
例如 [3, 4, 2] 表示输入3个特征,隐藏层4个神经元,输出2个类别
"""
self.layer_sizes = layer_sizes
self.num_layers = len(layer_sizes)
self.weights = []
self.biases = []
# 初始化权重和偏置
for i in range(1, self.num_layers):
w = np.random.randn(layer_sizes[i], layer_sizes[i-1]) * np.sqrt(2.0 / layer_sizes[i-1])
b = np.zeros((layer_sizes[i], 1))
self.weights.append(w)
self.biases.append(b)
def relu(self, z):
return np.maximum(0, z)
def softmax(self, z):
"""Softmax激活函数,用于多分类输出层"""
exp_z = np.exp(z - np.max(z, axis=0, keepdims=True)) # 数值稳定性
return exp_z / np.sum(exp_z, axis=0, keepdims=True)
def forward(self, x):
"""
前向传播
x: 输入,shape (n_features, batch_size)
返回: 输出,shape (n_outputs, batch_size)
"""
a = x
self.activations = [x] # 保存每层的激活值,用于反向传播
self.zs = [] # 保存每层的z值
# 遍历每一层
for i in range(self.num_layers - 1):
z = np.dot(self.weights[i], a) + self.biases[i]
self.zs.append(z)
# 最后一层使用softmax,其他层使用ReLU
if i == self.num_layers - 2:
a = self.softmax(z)
else:
a = self.relu(z)
self.activations.append(a)
return a
def predict(self, x):
"""预测类别"""
output = self.forward(x)
return np.argmax(output, axis=0)
# 创建一个简单的网络
nn = NeuralNetwork([3, 5, 4, 2])
print("网络结构:")
for i, (w, b) in enumerate(zip(nn.weights, nn.biases)):
print(f" 层{i+1}: 权重形状={w.shape}, 偏置形状={b.shape}")
# 前向传播示例
x = np.random.randn(3, 10) # 10个样本,每个3个特征
output = nn.forward(x)
predictions = nn.predict(x)
print(f"\n输入形状: {x.shape}")
print(f"输出形状: {output.shape}")
print(f"预测类别: {predictions}")
print(f"输出概率(前3个样本):\n{output[:, :3]}")
损失函数:衡量预测的好坏
损失函数用来衡量模型预测值与真实值之间的差距。不同的任务使用不同的损失函数。
回归任务:均方误差
对于回归任务,常用均方误差(MSE):
L = (1/n) Σ(yᵢ - ŷᵢ)²
def mse_loss(y_true, y_pred):
"""均方误差损失"""
return np.mean((y_true - y_pred) ** 2)
def mse_loss_derivative(y_true, y_pred):
"""MSE损失的导数"""
return 2 * (y_pred - y_true) / y_true.shape[0]
分类任务:交叉熵损失
对于分类任务,常用交叉熵损失:
二分类:L = -[y·log(ŷ) + (1-y)·log(1-ŷ)]
多分类:L = -Σyᵢ·log(ŷᵢ)
def binary_cross_entropy(y_true, y_pred):
"""二分类交叉熵损失"""
epsilon = 1e-15 # 避免log(0)
y_pred = np.clip(y_pred, epsilon, 1 - epsilon)
return -np.mean(y_true * np.log(y_pred) + (1 - y_true) * np.log(1 - y_pred))
def categorical_cross_entropy(y_true, y_pred):
"""多分类交叉熵损失"""
epsilon = 1e-15
y_pred = np.clip(y_pred, epsilon, 1 - epsilon)
return -np.mean(np.sum(y_true * np.log(y_pred), axis=0))
# 示例
y_true = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]).T # 3个样本的one-hot编码
y_pred = np.array([[0.7, 0.2, 0.1], [0.1, 0.8, 0.1], [0.2, 0.1, 0.7]]).T
loss = categorical_cross_entropy(y_true, y_pred)
print(f"交叉熵损失: {loss:.4f}")
梯度下降:优化网络参数
有了损失函数,我们需要找到使损失最小的参数。梯度下降是最常用的优化算法。
梯度下降的原理
梯度下降的核心思想是:沿着梯度的反方向更新参数,逐步减小损失。
参数更新规则:
θ = θ - α·∇L(θ)
其中,α是学习率,∇L(θ)是损失函数对参数θ的梯度。
批量梯度下降的变体
- 批量梯度下降(BGD):使用全部数据计算梯度
- 随机梯度下降(SGD):每次使用一个样本计算梯度
- 小批量梯度下降(Mini-batch GD):每次使用一小批样本计算梯度
def gradient_descent_demo():
"""梯度下降可视化示例"""
# 定义一个简单的二次函数
def f(x):
return x**2 + 2*x + 1
def df(x):
return 2*x + 2
# 梯度下降
x = 5.0 # 初始点
learning_rate = 0.1
history = [x]
for i in range(20):
grad = df(x)
x = x - learning_rate * grad
history.append(x)
# 可视化
x_range = np.linspace(-6, 6, 100)
y_range = f(x_range)
plt.figure(figsize=(12, 6))
plt.plot(x_range, y_range, 'b-', linewidth=2, label='f(x) = x² + 2x + 1')
plt.plot(history, [f(x) for x in history], 'ro-', markersize=8, label='梯度下降路径')
plt.xlabel('x')
plt.ylabel('f(x)')
plt.title('梯度下降优化过程')
plt.legend()
plt.grid(True)
plt.show()
print(f"初始点: x = {history[0]:.4f}, f(x) = {f(history[0]):.4f}")
print(f"最终点: x = {history[-1]:.4f}, f(x) = {f(history[-1]):.4f}")
print(f"理论最优点: x = -1, f(x) = 0")
gradient_descent_demo()
神经网络的通用近似定理
神经网络为什么这么强大?通用近似定理给出了理论保证。
定理内容
通用近似定理指出:一个具有单个隐藏层的前馈神经网络,只要隐藏层有足够多的神经元,就可以以任意精度逼近任何连续函数。
这个定理告诉我们:
- 神经网络理论上可以学习任何函数
- 深度不是必需的,但深度网络通常更高效
- 关键是要有足够的神经元和合适的激活函数
# 用神经网络拟合复杂函数
def complex_function(x):
"""一个复杂的函数"""
return np.sin(x) + 0.5 * np.sin(3*x) + 0.3 * np.cos(5*x)
# 生成训练数据
x_train = np.linspace(-np.pi, np.pi, 100).reshape(-1, 1)
y_train = complex_function(x_train)
# 使用sklearn的MLPRegressor
from sklearn.neural_network import MLPRegressor
# 不同复杂度的网络
models = {
'小网络 (5个神经元)': MLPRegressor(hidden_layer_sizes=(5,), max_iter=5000, random_state=42),
'中网络 (20个神经元)': MLPRegressor(hidden_layer_sizes=(20,), max_iter=5000, random_state=42),
'大网络 (50个神经元)': MLPRegressor(hidden_layer_sizes=(50,), max_iter=5000, random_state=42),
}
plt.figure(figsize=(15, 5))
for idx, (name, model) in enumerate(models.items(), 1):
model.fit(x_train, y_train.ravel())
y_pred = model.predict(x_train)
plt.subplot(1, 3, idx)
plt.plot(x_train, y_train, 'b-', linewidth=2, label='真实函数')
plt.plot(x_train, y_pred, 'r--', linewidth=2, label='神经网络拟合')
plt.xlabel('x')
plt.ylabel('y')
plt.title(name)
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
总结
本文从生物神经元出发,介绍了人工神经元的数学模型,然后深入讲解了神经网络的数学基础,包括线性代数和微积分的应用。
我们学习了:
- 人工神经元模型:通过加权和与激活函数模拟生物神经元的行为
- 线性代数基础:向量、矩阵运算是神经网络计算的基础
- 微积分基础:导数和链式法则是反向传播的数学基础
- 神经网络结构:由输入层、隐藏层、输出层组成,通过全连接层连接
- 激活函数:引入非线性,使网络能学习复杂函数
- 前向传播:数据从输入流向输出的计算过程
- 损失函数:衡量预测与真实值的差距
- 梯度下降:通过迭代优化找到最优参数
- 通用近似定理:理论保证神经网络的强大能力
掌握这些数学基础后,我们就可以深入学习反向传播算法、各种优化技巧,以及更复杂的神经网络架构了。神经网络的本质是一个复杂的数学函数,通过大量的参数和非线性变换,它能够学习数据中的复杂模式,这就是深度学习的魅力所在。
更多推荐


所有评论(0)