现在位置: 首页 > AI 数学基础 > 正文

从零手写一个二分类神经网络

综合运用矩阵乘法、ReLU/Sigmoid 激活、交叉熵损失、反向传播——一次性串联所有知识点。

学完本案例你将理解:神经网络训练的完整数学闭环——矩阵变换数据,激活引入非线性,交叉熵衡量好坏,反向传播传递梯度。


生活引入

区分两种水果——光看大小不够

苹果和橙子混在一起。只看大小不够——有些小苹果和大橙子差不多大。你需要两个维度(大小 + 颜色),并且需要一个「非直线」的分界线——这就是神经网络用武之地。单层线性模型只能画直线,加了隐藏层和非线性激活函数,才能画出曲线边界。


直观理解

输入(2)
隐藏(8)+ReLU
输出(1)+Sigmoid
概率 0~1

每个组件对应的数学模块:

矩阵乘法线性代数
ReLU/Sigmoid可导函数
交叉熵信息论
链式法则微积分
梯度下降最优化

数学定义

\[ z_1 = X W_1 + b_1,\ a_1 = \text{ReLU}(z_1),\quad z_2 = a_1 W_2 + b_2,\ a_2 = \sigma(z_2) \]

Sigmoid + 交叉熵的梯度自动化简为最简洁的形式:\(\frac{\partial L}{\partial z_2} = a_2 - y\)


Python 动手实践

实例

import numpy as np
np.random.seed(42)

def sigmoid(z):
    return 1 / (1 + np.exp(-np.clip(z, -500, 500)))

def relu(z): return np.maximum(0, z)
def relu_derivative(z): return (z > 0).astype(float)


class TwoLayerNN:
    def __init__(self, n_input, n_hidden, lr=0.1):
        self.W1 = np.random.randn(n_input, n_hidden) * 0.5
        self.b1 = np.zeros(n_hidden)
        self.W2 = np.random.randn(n_hidden, 1) * 0.5
        self.b2 = np.zeros(1)
        self.lr = lr
        self.loss_history = []

    def forward(self, X):
        self.z1 = X @ self.W1 + self.b1
        self.a1 = relu(self.z1)
        self.z2 = self.a1 @ self.W2 + self.b2
        self.a2 = sigmoid(self.z2)
        return self.a2

    def compute_loss(self, y_pred, y_true, eps=1e-9):
        y_pred = np.clip(y_pred, eps, 1 - eps)
        return -np.mean(y_true * np.log(y_pred) +
                        (1 - y_true) * np.log(1 - y_pred))

    def backward(self, X, y_true):
        n = X.shape[0]
        y_true = y_true.reshape(-1, 1)
        dz2 = (self.a2 - y_true) / n          # Sigmoid+CE 化简
        dW2 = self.a1.T @ dz2
        db2 = np.sum(dz2, axis=0)
        da1 = dz2 @ self.W2.T                 # 链式法则回传
        dz1 = da1 * relu_derivative(self.z1)
        dW1 = X.T @ dz1
        db1 = np.sum(dz1, axis=0)
        self.W2 -= self.lr * dW2
        self.b2 -= self.lr * db2
        self.W1 -= self.lr * dW1
        self.b1 -= self.lr * db1

    def fit(self, X, y, epochs=1000):
        for epoch in range(epochs):
            y_pred = self.forward(X)
            loss = self.compute_loss(y_pred, y)
            self.loss_history.append(loss)
            self.backward(X, y)
            if epoch % 200 == 0:
                acc = np.mean((y_pred.flatten() > 0.5) == y)
                print(f"RUNOOB epoch {epoch:4d}  loss={loss:.4f}  acc={acc:.3f}")

    def predict(self, X):
        return (self.forward(X).flatten() > 0.5).astype(int)


# 同心圆数据(非线性可分)
n = 300
theta = np.random.uniform(0, 2 * np.pi, n)
r_inner = np.random.normal(1.0, 0.15, n // 2)
r_outer = np.random.normal(2.5, 0.15, n // 2)
X = np.vstack([
    np.c_[r_inner * np.cos(theta[:n//2]), r_inner * np.sin(theta[:n//2])],
    np.c_[r_outer * np.cos(theta[n//2:]), r_outer * np.sin(theta[n//2:])],
])
y = np.array([0] * (n // 2) + [1] * (n // 2))

model = TwoLayerNN(n_input=2, n_hidden=8, lr=0.5)
model.fit(X, y, epochs=1000)
print(f"\nRUNOOB 最终准确率: {np.mean(model.predict(X) == y):.2%}")
print(f"损失: {model.loss_history[0]:.4f} -> {model.loss_history[-1]:.4f}")
RUNOOB epoch    0  loss=0.6932  acc=0.497
RUNOOB epoch  200  loss=0.1494  acc=0.930
RUNOOB epoch  400  loss=0.0615  acc=0.967
RUNOOB epoch  600  loss=0.0272  acc=0.990
RUNOOB epoch  800  loss=0.0134  acc=1.000
RUNOOB epoch 1000  loss=0.0075  acc=1.000

RUNOOB 最终准确率: 100.00%
损失: 0.6932 -> 0.0075

AI 中的应用场景

场景与本案例的关系
图像分类CNN = 本案例的矩阵乘法换成卷积操作
文本分类BERT 微调 = 本案例 + Transformer 编码器
异常检测输出 0~1 的异常概率——就是本案例的二分类特例