在当今的人工智能领域,神经网络已经成为解决复杂问题的核心技术之一。从图像识别到自然语言处理,再到强化学习,神经网络的身影无处不在。然而,对于许多初学者来说,神经网络似乎是一个神秘而复杂的黑盒子。本文将带你用基础的 Python 代码构建一个简单的神经网络,揭开它的神秘面纱,让你真正理解神经网络的工作原理。
一、神经网络的基本原理
在深入了解代码之前,我们需要先回顾一下神经网络的基本原理。神经网络是由大量的神经元(或节点)组成的,这些神经元按照一定的层次结构连接在一起。每一层的神经元接收来自前一层神经元的输入,并通过一个激活函数生成输出,传递给下一层。
一个典型的神经网络包括:
-
输入层:接收外部数据。
-
隐藏层:对输入数据进行处理和变换。
-
输出层:生成最终的预测结果。
神经网络的学习过程主要通过调整神经元之间的连接权重来实现。通过反向传播算法(Backpropagation),网络可以根据预测结果与真实值之间的误差,逐步调整权重,使误差最小化。
二、构建神经网络的 Python 环境
在开始编写代码之前,我们需要准备好 Python 环境。本文使用的工具是 Python 3.8 及以上版本,并且需要安装以下库:
-
NumPy:用于高效的数值计算。
-
Matplotlib:用于可视化数据和训练过程。
你可以通过以下命令安装这些库:
pip install numpy matplotlib
三、用基础 Python 代码构建神经网络
1. 数据准备
为了方便理解,我们先构建一个简单的数据集,用于训练神经网络。假设我们有一个二分类问题,数据集由二维特征和对应的标签组成。
import numpy as np
# 构造简单的二分类数据集
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]]) # 输入数据
y = np.array([0, 1, 1, 0]) # 对应的标签 (异或问题)
2. 初始化神经网络结构
我们定义一个简单的神经网络,包含一个输入层、一个隐藏层和一个输出层。隐藏层的神经元数量可以自行设置,这里我们选择 4 个神经元。
# 神经网络参数
input_size = 2 # 输入层神经元数量
hidden_size = 4 # 隐藏层神经元数量
output_size = 1 # 输出层神经元数量
# 初始化权重和偏置
np.random.seed(42) # 设置随机种子,保证结果可复现
weights_input_hidden = np.random.randn(input_size, hidden_size) # 输入层到隐藏层的权重
bias_hidden = np.zeros((1, hidden_size)) # 隐藏层偏置
weights_hidden_output = np.random.randn(hidden_size, output_size) # 隐藏层到输出层的权重
bias_output = np.zeros((1, output_size)) # 输出层偏置
3. 激活函数及其导数
激活函数是神经网络中的关键组件,它为网络引入了非线性特性。这里我们使用 Sigmoid 激活函数及其导数。
def sigmoid(x):
"""Sigmoid 激活函数"""
return 1 / (1 + np.exp(-x))
def sigmoid_derivative(x):
"""Sigmoid 激活函数的导数"""
return x * (1 - x)
4. 前向传播
前向传播是神经网络的核心过程之一,它通过输入数据计算每一层的输出。
def forward_propagation(X):
"""前向传播过程"""
# 输入层到隐藏层的计算
hidden_layer_input = np.dot(X, weights_input_hidden) + bias_hidden
hidden_layer_output = sigmoid(hidden_layer_input)
# 隐藏层到输出层的计算
output_layer_input = np.dot(hidden_layer_output, weights_hidden_output) + bias_output
output_layer_output = sigmoid(output_layer_input)
return hidden_layer_output, output_layer_output
5. 损失函数
为了衡量神经网络的预测结果与真实值之间的差距,我们需要定义一个损失函数。这里我们使用均方误差(MSE)作为损失函数。
def compute_loss(y_true, y_pred):
"""计算均方误差损失"""
return ((y_true - y_pred) ** 2).mean()
6. 反向传播
反向传播是神经网络训练的核心过程,它通过计算损失函数对权重的梯度,调整权重以最小化损失。
def backward_propagation(X, y, hidden_layer_output, output_layer_output):
"""反向传播过程"""
# 计算输出层的误差
output_error = y - output_layer_output
output_delta = output_error * sigmoid_derivative(output_layer_output)
# 计算隐藏层的误差
hidden_error = output_delta.dot(weights_hidden_output.T)
hidden_delta = hidden_error * sigmoid_derivative(hidden_layer_output)
# 计算权重和偏置的梯度
weights_hidden_output_gradient = hidden_layer_output.T.dot(output_delta)
bias_output_gradient = np.sum(output_delta, axis=0, keepdims=True)
weights_input_hidden_gradient = X.T.dot(hidden_delta)
bias_hidden_gradient = np.sum(hidden_delta, axis=0, keepdims=True)
return weights_hidden_output_gradient, bias_output_gradient, weights_input_hidden_gradient, bias_hidden_gradient
7. 权重更新
根据反向传播计算的梯度,我们使用梯度下降算法更新权重和偏置。
def update_weights(learning_rate, weights_hidden_output_gradient, bias_output_gradient,
weights_input_hidden_gradient, bias_hidden_gradient):
"""更新权重和偏置"""
global weights_hidden_output, bias_output, weights_input_hidden, bias_hidden
weights_hidden_output += learning_rate * weights_hidden_output_gradient
bias_output += learning_rate * bias_output_gradient
weights_input_hidden += learning_rate * weights_input_hidden_gradient
bias_hidden += learning_rate * bias_hidden_gradient
8. 训练神经网络
将上述过程整合在一起,我们可以训练神经网络。
# 超参数设置
learning_rate = 0.1 # 学习率
epochs = 10000 # 训练轮数
# 训练过程
for epoch in range(epochs):
# 前向传播
hidden_layer_output, output_layer_output = forward_propagation(X)
# 计算损失
loss = compute_loss(y, output_layer_output)
# 反向传播
weights_hidden_output_gradient, bias_output_gradient, weights_input_hidden_gradient, bias_hidden_gradient = (
backward_propagation(X, y, hidden_layer_output, output_layer_output)
)
# 更新权重
update_weights(learning_rate, weights_hidden_output_gradient, bias_output_gradient,
weights_input_hidden_gradient, bias_hidden_gradient)
# 打印训练过程
if (epoch + 1) % 1000 == 0:
print(f"Epoch {epoch + 1}, Loss: {loss}")
9. 测试神经网络
训练完成后,我们可以使用训练好的神经网络进行预测。
# 测试神经网络
hidden_layer_output, output_layer_output = forward_propagation(X)
predictions = (output_layer_output > 0.5).astype(int) # 将输出转换为二分类结果
print("Predictions:")
print(predictions)
print("Actual Labels:")
print(y)
四、结果分析与可视化
通过上述代码,我们可以看到神经网络成功地学习了 XOR 问题的模式。为了更直观地观察训练过程,我们可以绘制损失函数随训练轮数的变化曲线。
import matplotlib.pyplot as plt
# 记录每一轮的损失
loss_history = []
# 在训练过程中记录损失
for epoch in range(epochs):
# ... (省略部分代码)
loss_history.append(loss)
# 绘制损失曲线
plt.plot(range(epochs), loss_history)
plt.xlabel("Epochs")
plt.ylabel("Loss")
plt.title("Loss Curve")
plt.show()
完整代码:以下是一个完整可运行的手搓神经网络实现代码,包含训练和测试流程:
import numpy as np
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# 激活函数实现
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def relu(x):
return np.maximum(0, x)
def softmax(x):
exps = np.exp(x - np.max(x, axis=1, keepdims=True))
return exps / exps.sum(axis=1, keepdims=True)
# 网络层类
class DenseLayer:
def __init__(self, input_size, output_size, activation='relu'):
self.weights = np.random.randn(input_size, output_size) * 0.01
self.bias = np.zeros((1, output_size))
self.activation = activation
def forward(self, inputs):
self.inputs = inputs # 保存输入用于反向传播
self.z = np.dot(inputs, self.weights) + self.bias
if self.activation == 'relu':
self.output = relu(self.z)
elif self.activation == 'sigmoid':
self.output = sigmoid(self.z)
elif self.activation == 'softmax':
self.output = softmax(self.z)
return self.output
# 神经网络类
class NeuralNetwork:
def __init__(self, layers, learning_rate=0.1):
self.layers = layers
self.lr = learning_rate
def forward(self, X):
output = X
for layer in self.layers:
output = layer.forward(output)
return output
def backward(self, y_true):
m = y_true.shape[0]
layers = self.layers
# 计算输出层梯度
output_layer = layers[-1]
if output_layer.activation == 'softmax':
dZ = (output_layer.output - y_true) / m
else:
dZ = (output_layer.output - y_true) * output_layer.output * (1 - output_layer.output)
for i in reversed(range(len(layers))):
layer = layers[i]
if i == 0:
inputs = self.X
else:
inputs = layers[i-1].output
dW = np.dot(inputs.T, dZ)
db = np.sum(dZ, axis=0, keepdims=True)
# 更新参数
layer.weights -= self.lr * dW
layer.bias -= self.lr * db
# 计算前一层的梯度
if i > 0:
dZ = np.dot(dZ, layer.weights.T)
if layers[i-1].activation == 'relu':
dZ *= (layers[i-1].z > 0)
# 生成数据集
X, y = make_classification(n_samples=1000, n_features=20, n_classes=2, n_informative=15)
y = y.reshape(-1, 1)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 数据归一化
X_train = (X_train - np.mean(X_train, axis=0)) / np.std(X_train, axis=0)
X_test = (X_test - np.mean(X_test, axis=0)) / np.std(X_test, axis=0)
# 构建网络
network = NeuralNetwork([
DenseLayer(20, 64, 'relu'),
DenseLayer(64, 32, 'relu'),
DenseLayer(32, 1, 'sigmoid')
], learning_rate=0.01)
# 训练参数
epochs = 500
batch_size = 32
# 训练循环
for epoch in range(epochs):
# 随机小批量
indices = np.random.permutation(X_train.shape[0])
X_shuffled = X_train[indices]
y_shuffled = y_train[indices]
for i in range(0, X_train.shape[0], batch_size):
X_batch = X_shuffled[i:i+batch_size]
y_batch = y_shuffled[i:i+batch_size]
# 前向传播
output = network.forward(X_batch)
# 反向传播
network.X = X_batch # 保存输入数据用于反向传播
network.backward(y_batch)
# 每个epoch计算损失和准确率
if epoch % 50 == 0:
train_pred = network.forward(X_train)
train_loss = np.mean(np.square(train_pred - y_train))
train_acc = accuracy_score(y_train, (train_pred > 0.5).astype(int))
test_pred = network.forward(X_test)
test_acc = accuracy_score(y_test, (test_pred > 0.5).astype(int))
print(f"Epoch {epoch} | Train Loss: {train_loss:.4f} | "
f"Train Acc: {train_acc:.2f} | Test Acc: {test_acc:.2f}")
# 最终评估
final_pred = network.forward(X_test)
final_pred = (final_pred > 0.5).astype(int)
print("\nFinal Test Accuracy:", accuracy_score(y_test, final_pred))
运行结果示例:
Epoch 0 | Train Loss: 0.2478 | Train Acc: 0.51 | Test Acc: 0.54
Epoch 50 | Train Loss: 0.0913 | Train Acc: 0.91 | Test Acc: 0.89
Epoch 100 | Train Loss: 0.0562 | Train Acc: 0.95 | Test Acc: 0.92
...
Final Test Accuracy: 0.935
注意事项:
- 学习率可根据具体任务调整(0.001-0.1)
- 网络深度和宽度可根据需要修改
- 增加epoch数可以进一步提升精度
- 对于多分类任务需要修改输出层为softmax和交叉熵损失