Bootstrap

从零开始:用Python手写神经网络

在当今的人工智能领域,神经网络已经成为解决复杂问题的核心技术之一。从图像识别到自然语言处理,再到强化学习,神经网络的身影无处不在。然而,对于许多初学者来说,神经网络似乎是一个神秘而复杂的黑盒子。本文将带你用基础的 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

注意事项:

  1. 学习率可根据具体任务调整(0.001-0.1)
  2. 网络深度和宽度可根据需要修改
  3. 增加epoch数可以进一步提升精度
  4. 对于多分类任务需要修改输出层为softmax和交叉熵损失

;