作业实战:实现浅层神经网络

1. 浅层神经网络的基本概念

1.1 什么是浅层神经网络

浅层神经网络(Shallow Neural Network)是指具有一个隐藏层的神经网络模型。相比于单神经元网络,浅层神经网络具有更强的表达能力,可以解决非线性可分的问题。

1.2 浅层神经网络的结构

浅层神经网络的结构包括:

  • 输入层:接收输入特征
  • 隐藏层:提取输入数据的特征表示
  • 输出层:产生最终输出
  • 权重和偏置:连接不同层的参数
  • 激活函数:对线性组合结果进行非线性变换

1.3 浅层神经网络的数学表达式

对于一个具有一个隐藏层的浅层神经网络,数学表达式为:

# 隐藏层
z1 = W1*x + b1
a1 = f(z1)

# 输出层
z2 = W2*a1 + b2
y = g(z2)

其中,W1b1是输入层到隐藏层的权重和偏置,W2b2是隐藏层到输出层的权重和偏置,fg是激活函数。

2. 浅层神经网络的实现步骤

2.1 数据准备

首先,我们需要准备用于训练和测试的数据。

2.2 初始化参数

初始化网络的权重和偏置。

2.3 前向传播

计算网络的前向传播过程,得到预测输出。

2.4 计算损失

计算预测输出与真实标签之间的损失。

2.5 反向传播

计算损失函数对权重和偏置的梯度。

2.6 参数更新

使用梯度下降法更新权重和偏置。

2.7 模型评估

在测试集上评估模型的性能。

3. 实战案例:实现浅层神经网络解决非线性分类问题

3.1 问题描述

我们将实现一个浅层神经网络,用于解决非线性可分的二分类问题。具体来说,我们将创建一个非线性可分的数据集,然后训练浅层神经网络对数据进行分类。

3.2 数据准备

首先,我们创建一个非线性可分的二分类数据集:

import numpy as np
import matplotlib.pyplot as plt

# 设置随机种子,确保结果可复现
np.random.seed(42)

# 创建非线性可分的数据集
def create_nonlinear_dataset(n_samples=200):
    # 创建两类数据
    class0 = []
    class1 = []
    
    # 创建环形数据集
    for i in range(n_samples):
        # 为Class 0创建内部点
        r = np.random.uniform(0, 1.5)
        theta = np.random.uniform(0, 2*np.pi)
        x = r * np.cos(theta)
        y = r * np.sin(theta)
        class0.append([x, y])
        
        # 为Class 1创建外部点
        r = np.random.uniform(2, 3.5)
        theta = np.random.uniform(0, 2*np.pi)
        x = r * np.cos(theta)
        y = r * np.sin(theta)
        class1.append([x, y])
    
    # 转换为numpy数组
    class0 = np.array(class0)
    class1 = np.array(class1)
    
    # 创建标签
    labels0 = np.zeros((n_samples, 1))
    labels1 = np.ones((n_samples, 1))
    
    # 合并数据和标签
    X = np.vstack((class0, class1))
    y = np.vstack((labels0, labels1))
    
    # 打乱数据
    indices = np.random.permutation(2*n_samples)
    X = X[indices]
    y = y[indices]
    
    return X, y

# 创建数据集
X, y = create_nonlinear_dataset()

# 可视化数据集
plt.scatter(X[y.flatten() == 0][:, 0], X[y.flatten() == 0][:, 1], color='blue', label='Class 0')
plt.scatter(X[y.flatten() == 1][:, 0], X[y.flatten() == 1][:, 1], color='red', label='Class 1')
plt.title('Nonlinear Classification Dataset')
plt.xlabel('Feature 1')
plt.ylabel('Feature 2')
plt.legend()
plt.show()

3.3 浅层神经网络的实现

现在,我们实现一个浅层神经网络:

class ShallowNeuralNetwork:
    def __init__(self, input_size, hidden_size, output_size):
        """
        初始化浅层神经网络
        
        参数:
        input_size: 输入特征的维度
        hidden_size: 隐藏层神经元的数量
        output_size: 输出层神经元的数量
        """
        # 初始化权重和偏置
        self.W1 = np.random.randn(input_size, hidden_size) * 0.01
        self.b1 = np.zeros((1, hidden_size))
        self.W2 = np.random.randn(hidden_size, output_size) * 0.01
        self.b2 = np.zeros((1, output_size))
    
    def sigmoid(self, z):
        """
        sigmoid激活函数
        
        参数:
        z: 线性组合结果
        
        返回:
        激活后的值
        """
        return 1 / (1 + np.exp(-z))
    
    def sigmoid_derivative(self, z):
        """
        sigmoid激活函数的导数
        
        参数:
        z: 线性组合结果
        
        返回:
        sigmoid的导数
        """
        return self.sigmoid(z) * (1 - self.sigmoid(z))
    
    def relu(self, z):
        """
        ReLU激活函数
        
        参数:
        z: 线性组合结果
        
        返回:
        激活后的值
        """
        return np.maximum(0, z)
    
    def relu_derivative(self, z):
        """
        ReLU激活函数的导数
        
        参数:
        z: 线性组合结果
        
        返回:
        ReLU的导数
        """
        return np.where(z > 0, 1, 0)
    
    def forward(self, X, activation='relu'):
        """
        前向传播
        
        参数:
        X: 输入数据,形状为(m, n),其中m是样本数,n是特征数
        activation: 隐藏层的激活函数,可选值为'relu'或'sigmoid'
        
        返回:
        预测输出
        """
        # 计算隐藏层的线性组合
        self.Z1 = np.dot(X, self.W1) + self.b1
        
        # 应用隐藏层的激活函数
        if activation == 'relu':
            self.A1 = self.relu(self.Z1)
        elif activation == 'sigmoid':
            self.A1 = self.sigmoid(self.Z1)
        else:
            raise ValueError("激活函数必须是'relu'或'sigmoid'")
        
        # 计算输出层的线性组合
        self.Z2 = np.dot(self.A1, self.W2) + self.b2
        
        # 应用输出层的激活函数(sigmoid用于二分类)
        self.A2 = self.sigmoid(self.Z2)
        
        return self.A2
    
    def compute_loss(self, y, a):
        """
        计算损失
        
        参数:
        y: 真实标签
        a: 预测输出
        
        返回:
        损失值
        """
        m = y.shape[0]
        # 计算交叉熵损失
        loss = -np.mean(y * np.log(a) + (1 - y) * np.log(1 - a))
        return loss
    
    def backward(self, X, y, a, activation='relu'):
        """
        反向传播
        
        参数:
        X: 输入数据
        y: 真实标签
        a: 预测输出
        activation: 隐藏层的激活函数
        
        返回:
        权重和偏置的梯度
        """
        m = X.shape[0]
        
        # 计算输出层的梯度
        dZ2 = a - y
        # 计算输出层权重的梯度
        dW2 = (1/m) * np.dot(self.A1.T, dZ2)
        # 计算输出层偏置的梯度
        db2 = (1/m) * np.sum(dZ2, axis=0, keepdims=True)
        
        # 计算隐藏层的梯度
        dA1 = np.dot(dZ2, self.W2.T)
        if activation == 'relu':
            dZ1 = dA1 * self.relu_derivative(self.Z1)
        elif activation == 'sigmoid':
            dZ1 = dA1 * self.sigmoid_derivative(self.Z1)
        else:
            raise ValueError("激活函数必须是'relu'或'sigmoid'")
        
        # 计算隐藏层权重的梯度
        dW1 = (1/m) * np.dot(X.T, dZ1)
        # 计算隐藏层偏置的梯度
        db1 = (1/m) * np.sum(dZ1, axis=0, keepdims=True)
        
        return dW1, db1, dW2, db2
    
    def update_parameters(self, dW1, db1, dW2, db2, learning_rate):
        """
        更新参数
        
        参数:
        dW1: 隐藏层权重的梯度
        db1: 隐藏层偏置的梯度
        dW2: 输出层权重的梯度
        db2: 输出层偏置的梯度
        learning_rate: 学习率
        """
        self.W1 -= learning_rate * dW1
        self.b1 -= learning_rate * db1
        self.W2 -= learning_rate * dW2
        self.b2 -= learning_rate * db2
    
    def train(self, X, y, epochs=10000, learning_rate=0.01, activation='relu', print_interval=1000):
        """
        训练模型
        
        参数:
        X: 输入数据
        y: 真实标签
        epochs: 训练轮数
        learning_rate: 学习率
        activation: 隐藏层的激活函数
        print_interval: 打印间隔
        """
        losses = []
        
        for epoch in range(epochs):
            # 前向传播
            a = self.forward(X, activation)
            
            # 计算损失
            loss = self.compute_loss(y, a)
            losses.append(loss)
            
            # 反向传播
            dW1, db1, dW2, db2 = self.backward(X, y, a, activation)
            
            # 更新参数
            self.update_parameters(dW1, db1, dW2, db2, learning_rate)
            
            # 打印损失
            if epoch % print_interval == 0:
                print(f'Epoch {epoch}, Loss: {loss:.4f}')
        
        return losses
    
    def predict(self, X, activation='relu', threshold=0.5):
        """
        预测
        
        参数:
        X: 输入数据
        activation: 隐藏层的激活函数
        threshold: 阈值
        
        返回:
        预测标签
        """
        a = self.forward(X, activation)
        return (a > threshold).astype(int)
    
    def evaluate(self, X, y, activation='relu'):
        """
        评估模型
        
        参数:
        X: 输入数据
        y: 真实标签
        activation: 隐藏层的激活函数
        
        返回:
        准确率
        """
        predictions = self.predict(X, activation)
        accuracy = np.mean(predictions == y)
        return accuracy

3.4 训练浅层神经网络

现在,我们使用准备好的数据训练浅层神经网络:

# 创建浅层神经网络
nn = ShallowNeuralNetwork(input_size=2, hidden_size=4, output_size=1)

# 训练模型
losses = nn.train(X, y, epochs=20000, learning_rate=0.01, activation='relu', print_interval=2000)

# 可视化损失曲线
plt.plot(losses)
plt.title('Training Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.show()

# 评估模型
accuracy = nn.evaluate(X, y, activation='relu')
print(f'Training Accuracy: {accuracy:.4f}')

3.5 可视化决策边界

我们可以可视化浅层神经网络的决策边界:

# 可视化决策边界
def plot_decision_boundary(model, X, y, activation='relu'):
    # 设置网格范围
    x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    
    # 创建网格
    xx, yy = np.meshgrid(np.linspace(x_min, x_max, 100),
                         np.linspace(y_min, y_max, 100))
    
    # 预测网格点的标签
    grid = np.c_[xx.ravel(), yy.ravel()]
    predictions = model.predict(grid, activation)
    predictions = predictions.reshape(xx.shape)
    
    # 绘制决策边界
    plt.contourf(xx, yy, predictions, alpha=0.8)
    
    # 绘制数据点
    plt.scatter(X[y.flatten() == 0][:, 0], X[y.flatten() == 0][:, 1], color='blue', label='Class 0')
    plt.scatter(X[y.flatten() == 1][:, 0], X[y.flatten() == 1][:, 1], color='red', label='Class 1')
    
    plt.title('Decision Boundary')
    plt.xlabel('Feature 1')
    plt.ylabel('Feature 2')
    plt.legend()
    plt.show()

# 绘制决策边界
plot_decision_boundary(nn, X, y, activation='relu')

3.6 分析训练结果

通过训练浅层神经网络,我们可以:

  • 观察损失函数的下降过程
  • 了解模型的收敛情况
  • 可视化决策边界,理解模型的学习能力
  • 评估模型的准确率
  • 体会浅层神经网络解决非线性问题的能力

4. 浅层神经网络的超参数调优

4.1 隐藏层神经元数量的选择

隐藏层神经元数量的选择会影响模型的性能:

  • 过少的神经元:模型可能无法捕捉数据中的复杂模式
  • 过多的神经元:模型可能会过拟合训练数据

4.2 学习率的选择

学习率的选择会影响模型的训练速度和收敛性:

  • 过小的学习率:模型训练速度慢,可能陷入局部最优
  • 过大的学习率:模型可能无法收敛

4.3 训练轮数的选择

训练轮数的选择会影响模型的性能:

  • 过少的训练轮数:模型可能未充分学习数据中的模式
  • 过多的训练轮数:模型可能会过拟合训练数据

4.4 超参数调优实验

我们可以通过实验来找到最佳的超参数组合:

# 超参数调优
def tune_hyperparameters(X, y):
    # 定义超参数组合
    hidden_sizes = [2, 4, 8, 16]
    learning_rates = [0.001, 0.01, 0.1]
    activations = ['relu', 'sigmoid']
    
    best_accuracy = 0
    best_params = {}
    
    for hidden_size in hidden_sizes:
        for learning_rate in learning_rates:
            for activation in activations:
                print(f'\n测试超参数: hidden_size={hidden_size}, learning_rate={learning_rate}, activation={activation}')
                
                # 创建并训练模型
                nn = ShallowNeuralNetwork(input_size=2, hidden_size=hidden_size, output_size=1)
                nn.train(X, y, epochs=10000, learning_rate=learning_rate, activation=activation, print_interval=5000)
                
                # 评估模型
                accuracy = nn.evaluate(X, y, activation=activation)
                print(f'准确率: {accuracy:.4f}')
                
                # 更新最佳参数
                if accuracy > best_accuracy:
                    best_accuracy = accuracy
                    best_params = {
                        'hidden_size': hidden_size,
                        'learning_rate': learning_rate,
                        'activation': activation
                    }
    
    print(f'\n最佳超参数: {best_params}')
    print(f'最佳准确率: {best_accuracy:.4f}')
    
    return best_params

# 运行超参数调优
best_params = tune_hyperparameters(X, y)

# 使用最佳超参数训练模型
best_nn = ShallowNeuralNetwork(
    input_size=2, 
    hidden_size=best_params['hidden_size'], 
    output_size=1
)
best_nn.train(
    X, y, 
    epochs=20000, 
    learning_rate=best_params['learning_rate'], 
    activation=best_params['activation'], 
    print_interval=2000
)

# 评估模型
best_accuracy = best_nn.evaluate(X, y, activation=best_params['activation'])
print(f'最佳模型准确率: {best_accuracy:.4f}')

# 可视化决策边界
plot_decision_boundary(best_nn, X, y, activation=best_params['activation'])

5. 扩展:使用不同的损失函数

5.1 常见的损失函数

除了交叉熵损失函数外,常见的损失函数还包括:

  • 均方误差损失(MSE):适用于回归问题
  • 平均绝对误差损失(MAE):适用于回归问题
  • 铰链损失:适用于支持向量机

5.2 实现均方误差损失函数

def compute_mse_loss(self, y, a):
    """
    计算均方误差损失
    
    参数:
    y: 真实标签
    a: 预测输出
    
    返回:
    损失值
    """
    m = y.shape[0]
    loss = (1/(2*m)) * np.sum((a - y)**2)
    return loss

# 添加compute_mse_loss方法到ShallowNeuralNetwork类
ShallowNeuralNetwork.compute_mse_loss = compute_mse_loss

# 修改train方法,支持不同的损失函数
def train_with_loss(self, X, y, epochs=10000, learning_rate=0.01, activation='relu', loss_function='cross_entropy', print_interval=1000):
    """
    训练模型(支持不同的损失函数)
    
    参数:
    X: 输入数据
    y: 真实标签
    epochs: 训练轮数
    learning_rate: 学习率
    activation: 隐藏层的激活函数
    loss_function: 损失函数,可选值为'cross_entropy'或'mse'
    print_interval: 打印间隔
    """
    losses = []
    
    for epoch in range(epochs):
        # 前向传播
        a = self.forward(X, activation)
        
        # 计算损失
        if loss_function == 'cross_entropy':
            loss = self.compute_loss(y, a)
        elif loss_function == 'mse':
            loss = self.compute_mse_loss(y, a)
        else:
            raise ValueError("损失函数必须是'cross_entropy'或'mse'")
        
        losses.append(loss)
        
        # 反向传播
        dW1, db1, dW2, db2 = self.backward(X, y, a, activation)
        
        # 更新参数
        self.update_parameters(dW1, db1, dW2, db2, learning_rate)
        
        # 打印损失
        if epoch % print_interval == 0:
            print(f'Epoch {epoch}, Loss: {loss:.4f}')
    
    return losses

# 添加train_with_loss方法到ShallowNeuralNetwork类
ShallowNeuralNetwork.train_with_loss = train_with_loss

# 使用均方误差损失函数训练模型
nn_mse = ShallowNeuralNetwork(input_size=2, hidden_size=4, output_size=1)
losses_mse = nn_mse.train_with_loss(X, y, epochs=20000, learning_rate=0.01, activation='relu', loss_function='mse', print_interval=2000)

# 可视化损失曲线
plt.plot(losses, label='Cross Entropy')
plt.plot(losses_mse, label='MSE')
plt.title('Training Loss Comparison')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.show()

# 评估模型
accuracy_mse = nn_mse.evaluate(X, y, activation='relu')
print(f'Training Accuracy (MSE): {accuracy_mse:.4f}')

# 可视化决策边界
plot_decision_boundary(nn_mse, X, y, activation='relu')

6. 扩展:使用不同的优化算法

6.1 小批量梯度下降法

小批量梯度下降法是一种常用的优化算法,它每次使用一小批样本进行更新,可以提高训练速度和稳定性。

def create_minibatches(X, y, batch_size):
    """
    创建小批量数据
    
    参数:
    X: 输入数据
    y: 真实标签
    batch_size: 批量大小
    
    返回:
    小批量数据的列表
    """
    m = X.shape[0]
    minibatches = []
    
    # 打乱数据
    permutation = np.random.permutation(m)
    X_shuffled = X[permutation]
    y_shuffled = y[permutation]
    
    # 创建小批量
    for i in range(0, m, batch_size):
        end = i + batch_size
        if end > m:
            end = m
        minibatch_X = X_shuffled[i:end]
        minibatch_y = y_shuffled[i:end]
        minibatches.append((minibatch_X, minibatch_y))
    
    return minibatches

# 实现小批量梯度下降法
def train_minibatch(self, X, y, epochs=10000, batch_size=32, learning_rate=0.01, activation='relu', print_interval=1000):
    """
    使用小批量梯度下降法训练模型
    
    参数:
    X: 输入数据
    y: 真实标签
    epochs: 训练轮数
    batch_size: 批量大小
    learning_rate: 学习率
    activation: 隐藏层的激活函数
    print_interval: 打印间隔
    """
    losses = []
    m = X.shape[0]
    
    for epoch in range(epochs):
        epoch_loss = 0
        
        # 创建小批量
        minibatches = create_minibatches(X, y, batch_size)
        
        for minibatch_X, minibatch_y in minibatches:
            # 前向传播
            a = self.forward(minibatch_X, activation)
            
            # 计算损失
            loss = self.compute_loss(minibatch_y, a)
            epoch_loss += loss * len(minibatch_X) / m
            
            # 反向传播
            dW1, db1, dW2, db2 = self.backward(minibatch_X, minibatch_y, a, activation)
            
            # 更新参数
            self.update_parameters(dW1, db1, dW2, db2, learning_rate)
        
        losses.append(epoch_loss)
        
        # 打印损失
        if epoch % print_interval == 0:
            print(f'Epoch {epoch}, Loss: {epoch_loss:.4f}')
    
    return losses

# 添加train_minibatch方法到ShallowNeuralNetwork类
ShallowNeuralNetwork.train_minibatch = train_minibatch

# 使用小批量梯度下降法训练模型
nn_minibatch = ShallowNeuralNetwork(input_size=2, hidden_size=4, output_size=1)
losses_minibatch = nn_minibatch.train_minibatch(X, y, epochs=5000, batch_size=32, learning_rate=0.01, activation='relu', print_interval=500)

# 可视化损失曲线
plt.plot(losses, label='Full Batch')
plt.plot(losses_minibatch, label='Mini-batch')
plt.title('Training Loss Comparison')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.show()

# 评估模型
accuracy_minibatch = nn_minibatch.evaluate(X, y, activation='relu')
print(f'Training Accuracy (Mini-batch): {accuracy_minibatch:.4f}')

# 可视化决策边界
plot_decision_boundary(nn_minibatch, X, y, activation='relu')

7. 总结与展望

7.1 主要内容总结

本教程介绍了如何实现一个浅层神经网络,包括:

  • 浅层神经网络的基本概念和结构
  • 浅层神经网络的实现步骤
  • 实战案例:实现浅层神经网络解决非线性分类问题
  • 浅层神经网络的超参数调优
  • 扩展:使用不同的损失函数
  • 扩展:使用不同的优化算法

7.2 未来学习方向

通过本教程的学习,读者可以:

  • 掌握浅层神经网络的基本实现方法
  • 理解前向传播和反向传播的原理
  • 了解不同激活函数、损失函数和优化算法的效果
  • 学习超参数调优的方法
  • 为实现更复杂的神经网络模型打下基础

7.3 实践建议

在实践中,读者可以:

  • 尝试使用不同的数据集训练浅层神经网络
  • 调整网络结构,如增加隐藏层的数量,构建深层神经网络
  • 尝试实现其他类型的激活函数和损失函数
  • 探索更高级的优化算法,如动量梯度下降法和Adam优化算法
  • 学习如何使用深度学习框架(如TensorFlow和PyTorch)实现神经网络

通过不断的实践和探索,读者将能够更深入地理解神经网络的工作原理,为后续的深度学习学习打下坚实的基础。

« 上一篇 作业实战:实现一个单神经元网络 下一篇 » 多分类问题与Softmax函数