最简明扼要的AI神经网络入门

神经元(Neuron)

  • 输入x1,x2,x3x_1, x_2, x_3x1,x2,x3 是数据输入。
  • 权重和偏置:每个输入与对应的权重 w1,w2,w3w_1, w_2, w_3w1,w2,w3 相乘,再加上偏置 bbb
  • 线性组合:计算加权和 z=∑i=1nxiwi+bz = \sum_{i=1}^n x_i w_i + bz=i=1nxiwi+b
  • 激活函数:将 zzz 通过激活函数 f(z)f(z)f(z) 映射为最终输出 aaa
  • 批量大小Batch:在神经网络中,一个神经元通常会处理多个数据输入,而不是只处理一个数据输入。(多个不同样本的同种特征数据)
  • 神经元个数:在 输入层,神经元的个数通常等于输入数据的特征数,即每种特征对应一个神经元;在 隐藏层,神经元的个数通常是根据网络的复杂度和所需的表示能力来确定的。更多的神经元可以捕捉更复杂的特征关系,增强模型的表达能力。;在 输出层,神经元的个数通常与任务相关,例如,对于分类任务,输出层的神经元数可能与类别数相同;对于回归任务,输出层通常只有一个神经元。
    在这里插入图片描述
    z=∑i=1nxiwi+b z = \sum_{i=1}^n x_i w_i + b z=i=1nxiwi+b
    a=f(z) a = f(z) a=f(z)

激活函数(Activation function)

在这里插入图片描述

  1. Sigmoid
  • 公式: σ(x)=11+e−x\sigma(x) = \frac{1}{1 + e^{-x}}σ(x)=1+ex1
  • 特点:
    • 输出范围:(0,1)(0, 1)(0,1)
    • 常用于二分类问题的输出层。
    • 易受梯度消失问题影响(输入过大或过小时,梯度接近 0)。

  1. ReLU (Rectified Linear Unit)
  • 公式: ReLU(x)=max⁡(0,x)\text{ReLU}(x) = \max(0, x)ReLU(x)=max(0,x)
  • 特点:
    • 输出范围:[0,+∞)[0, +\infty)[0,+)
    • 计算简单,加速训练。
    • 解决了梯度消失问题,但可能导致“死神经元”(负值时梯度为 0)。

  1. tanh (双曲正切)
  • 公式: tanh⁡(x)=ex−e−xex+e−x\tanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}}tanh(x)=ex+exexex
  • 特点:
    • 输出范围:(−1,1)(-1, 1)(1,1)
    • 中心化(输出均值接近 0),有助于梯度稳定。
    • 梯度消失问题较 Sigmoid 轻微,但仍存在。

  1. Leaky ReLU
  • 公式: Leaky ReLU(x)=max⁡(0.1x,x)\text{Leaky ReLU}(x) = \max(0.1x, x)Leaky ReLU(x)=max(0.1x,x)
  • 特点:
    • 输出范围:(−∞,+∞)(-\infty, +\infty)(,+)
    • 避免 ReLU 的“死神经元”问题,负值时有小斜率(如 0.1)。
    • 提高模型鲁棒性,适合复杂任务。

把神经元组装成网络(Network)

  • 隐藏层的神经元既不直接接收外部输入(不像输入层),也不直接输出结果给外部(不像输出层)。它在输入和输出之间起到一个“中间处理”的作用,因此被称为“隐藏层”。
  • 一个神经网络的层数以及每一层中的神经元数量都是任意的
    在这里插入图片描述
  • 这个网络有两个输入,一个有两个神经元 (h1h_1h1h2h_2h2) 的隐藏层,以及一个有一个神经元 (o1o_1o1) 的输出层。要注意,o1o_1o1 的输入就是 h1h_1h1h2h_2h2 的输出,这样就组成了一个网络。

损失函数(Loss)

  • 损失:真实值与网络预测值之间的误差
  • 训练网络 = 最小化损失
  • 如何才能逐步地减少损失? 通过调整网络的权重和截距项,可以改变其预测值

梯度下降(Gradient Descent)

  • 是一种优化算法,用来最小化损失函数
  • 它通过沿着损失函数关于权重参数的负梯度方向更新参数,使损失逐步变小
    w1←w1−η∂L∂w1w_1 \leftarrow w_1 - \eta \frac{\partial L}{\partial w_1}w1w1ηw1L

η\etaη 是一个常数,被称为学习率,用于调整训练的速度。我们要做的就是用 w1w_1w1 减去η∂L∂w1\eta \frac{\partial L}{\partial w_1}ηw1L

  • 如果 ∂L∂w1\frac{\partial L}{\partial w_1}w1L 是正数,w1w_1w1 变小,LLL 会下降。
  • 如果 ∂L∂w1\frac{\partial L}{\partial w_1}w1L 是负数,w1w_1w1 会变大,LLL 会上升。

如果我们对网络中的每个权重和截距项都这样进行优化,损失就会不断下降,网络性能会不断上升。

我们的训练过程是这样的:

  1. 从我们的数据集中选择一个样本,用随机梯度下降法进行优化——每次我们都只针对一个样本进行优化;
  2. 计算每个权重或截距项对损失的偏导(例如 ∂L∂w1\frac{\partial L}{\partial w_1}w1L∂L∂w2\frac{\partial L}{\partial w_2}w2L 等);
  3. 用更新等式更新每个权重和截距项;
  4. 重复第一步;

代码:一个完整的神经网络(Neuron Network)

数据

在这里插入图片描述

网络结构

在这里插入图片描述
输入层到隐藏层:
h1=σ(w1⋅weight+w2⋅height+b1) h_1 = \sigma\left(w_1 \cdot \text{weight} + w_2 \cdot \text{height} + b_1\right) h1=σ(w1weight+w2height+b1)
h2=σ(w3⋅weight+w4⋅height+b2) h_2 = \sigma\left(w_3 \cdot \text{weight} + w_4 \cdot \text{height} + b_2\right) h2=σ(w3weight+w4height+b2)

隐藏层到输出层:
o1=σ(w5⋅h1+w6⋅h2+b3) o_1 = \sigma\left(w_5 \cdot h_1 + w_6 \cdot h_2 + b_3\right) o1=σ(w5h1+w6h2+b3)

其中:

  • σ(⋅)\sigma(\cdot)σ() 表示激活函数(如 sigmoid 或 ReLU)。
  • wiw_iwi 表示权重。
  • bib_ibi 表示偏置。

完整代码

import numpy as np


def sigmoid(x):
    # Sigmoid激活函数: f(x) = 1 / (1 + e^(-x))
    # 将任意实数映射到(0,1)区间,常用于神经网络中
    return 1 / (1 + np.exp(-x))


def deriv_sigmoid(x):
    # Sigmoid函数的导数: f'(x) = f(x) * (1 - f(x))
    # 在反向传播中用于计算梯度
    fx = sigmoid(x)
    return fx * (1 - fx)


def mse_loss(y_true, y_pred):
    # 均方误差损失函数
    # y_true和y_pred是相同长度的numpy数组
    # 计算预测值与真实值差的平方的平均值
    return ((y_true - y_pred) ** 2).mean()


class OurNeuralNetwork:
    '''
    一个简单的神经网络,包含:
    - 2个输入节点
    - 一个有2个神经元(h1, h2)的隐藏层
    - 一个有1个神经元(o1)的输出层
    这个网络可以解决简单的二分类问题
    '''

    def __init__(self):
        # 初始化网络参数

        # 权重初始化 - 使用正态分布随机初始化
        # w1, w2: 连接输入层到第一个隐藏神经元h1的权重
        self.w1 = np.random.normal()
        self.w2 = np.random.normal()
        # w3, w4: 连接输入层到第二个隐藏神经元h2的权重
        self.w3 = np.random.normal()
        self.w4 = np.random.normal()
        # w5, w6: 连接隐藏层到输出神经元o1的权重
        self.w5 = np.random.normal()
        self.w6 = np.random.normal()

        # 偏置项初始化 - 也使用正态分布随机初始化
        # b1: 第一个隐藏神经元h1的偏置
        self.b1 = np.random.normal()
        # b2: 第二个隐藏神经元h2的偏置
        self.b2 = np.random.normal()
        # b3: 输出神经元o1的偏置
        self.b3 = np.random.normal()

    def feedforward(self, x):
        '''
        前向传播函数
        参数x是一个包含2个元素的numpy数组,表示输入特征
        返回网络的预测输出(一个0到1之间的值)
        '''
        # 计算隐藏层第一个神经元的输出
        # h1 = sigmoid(w1*x1 + w2*x2 + b1)
        h1 = sigmoid(self.w1 * x[0] + self.w2 * x[1] + self.b1)

        # 计算隐藏层第二个神经元的输出
        # h2 = sigmoid(w3*x1 + w4*x2 + b2)
        h2 = sigmoid(self.w3 * x[0] + self.w4 * x[1] + self.b2)

        # 计算输出层神经元的输出
        # o1 = sigmoid(w5*h1 + w6*h2 + b3)
        o1 = sigmoid(self.w5 * h1 + self.w6 * h2 + self.b3)

        return o1

    def train(self, data, all_y_trues):
        '''
        训练神经网络
        参数:
        - data: 形状为(n x 2)的numpy数组,n为数据集中样本数量
        - all_y_trues: 包含n个元素的numpy数组,对应data中每个样本的真实标签
        '''
        # 设置学习率 - 控制每次参数更新的步长
        learn_rate = 0.1
        # 设置训练轮数 - 整个数据集会被遍历的次数
        epochs = 1000

        # 开始训练过程
        for epoch in range(epochs):
            # 遍历数据集中的每个样本
            for x, y_true in zip(data, all_y_trues):
                # ------ 前向传播 ------
                # 计算隐藏层神经元h1的输入和激活值
                sum_h1 = self.w1 * x[0] + self.w2 * x[1] + self.b1
                h1 = sigmoid(sum_h1)

                # 计算隐藏层神经元h2的输入和激活值
                sum_h2 = self.w3 * x[0] + self.w4 * x[1] + self.b2
                h2 = sigmoid(sum_h2)

                # 计算输出层神经元o1的输入和激活值(预测值)
                sum_o1 = self.w5 * h1 + self.w6 * h2 + self.b3
                o1 = sigmoid(sum_o1)
                y_pred = o1

                # ------ 反向传播计算梯度 ------
                # 命名规则: d_L_d_w1 表示"损失L对权重w1的偏导数"

                # 损失函数对预测值的偏导数: -2(y_true - y_pred)
                d_L_d_ypred = -2 * (y_true - y_pred)

                # 输出神经元o1的相关偏导数
                # 预测值对w5的偏导数
                d_ypred_d_w5 = h1 * deriv_sigmoid(sum_o1)
                # 预测值对w6的偏导数
                d_ypred_d_w6 = h2 * deriv_sigmoid(sum_o1)
                # 预测值对b3的偏导数
                d_ypred_d_b3 = deriv_sigmoid(sum_o1)

                # 预测值对隐藏层神经元输出的偏导数
                d_ypred_d_h1 = self.w5 * deriv_sigmoid(sum_o1)
                d_ypred_d_h2 = self.w6 * deriv_sigmoid(sum_o1)

                # 隐藏层神经元h1的相关偏导数
                # h1对w1的偏导数
                d_h1_d_w1 = x[0] * deriv_sigmoid(sum_h1)
                # h1对w2的偏导数
                d_h1_d_w2 = x[1] * deriv_sigmoid(sum_h1)
                # h1对b1的偏导数
                d_h1_d_b1 = deriv_sigmoid(sum_h1)

                # 隐藏层神经元h2的相关偏导数
                # h2对w3的偏导数
                d_h2_d_w3 = x[0] * deriv_sigmoid(sum_h2)
                # h2对w4的偏导数
                d_h2_d_w4 = x[1] * deriv_sigmoid(sum_h2)
                # h2对b2的偏导数
                d_h2_d_b2 = deriv_sigmoid(sum_h2)

                # ------ 使用梯度下降更新权重和偏置 ------
                # 隐藏层神经元h1相关参数更新
                # 应用链式法则计算复合函数的导数: dL/dw1 = dL/dy_pred * dy_pred/dh1 * dh1/dw1
                self.w1 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w1
                self.w2 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w2
                self.b1 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_b1

                # 隐藏层神经元h2相关参数更新
                self.w3 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_w3
                self.w4 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_w4
                self.b2 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_b2

                # 输出层神经元o1相关参数更新
                # 对于输出层参数,链式法则更简单: dL/dw5 = dL/dy_pred * dy_pred/dw5
                self.w5 -= learn_rate * d_L_d_ypred * d_ypred_d_w5
                self.w6 -= learn_rate * d_L_d_ypred * d_ypred_d_w6
                self.b3 -= learn_rate * d_L_d_ypred * d_ypred_d_b3

            # ------ 每隔10个epoch计算并打印一次总损失 ------
            if epoch % 10 == 0:
                # 对所有样本进行预测
                y_preds = np.apply_along_axis(self.feedforward, 1, data)
                # 计算总损失
                loss = mse_loss(all_y_trues, y_preds)
                print("Epoch %d loss: %.3f" % (epoch, loss))


# 定义训练数据集
# 每个样本有两个特征,可以理解为计算机视角的[体重, 身高]等特征
data = np.array([
    [-2, -1],  # Alice
    [25, 6],  # Bob
    [17, 4],  # Charlie
    [-15, -6],  # Diana
])

# 定义标签: 1表示女性, 0表示男性
all_y_trues = np.array([
    1,  # Alice - 女性
    0,  # Bob - 男性
    0,  # Charlie - 男性
    1,  # Diana - 女性
])

# 创建神经网络实例
network = OurNeuralNetwork()
# 训练神经网络
network.train(data, all_y_trues)

# 使用训练好的网络进行预测
# 预测两个新样本
emily = np.array([-7, -3])  # 特征类似于女性样本
frank = np.array([20, 2])  # 特征类似于男性样本
# 输出预测结果: 接近1表示预测为女性,接近0表示预测为男性
print("Emily: %.3f" % network.feedforward(emily))  # 预期输出接近1 - 女性
print("Frank: %.3f" % network.feedforward(frank))  # 预期输出接近0 - 男性

其他的一些适合纯小白的代码案例

CNN

import torch
import torch.nn as nn
import torch.optim as optim

from torchvision import datasets, transforms


def load_mnist(batch_size):
    # 初始化MNIST数据集加载器的函数

    # 超参数定义部分
    # 输入图像的尺寸是28x28像素,因此输入尺寸为28
    input_size = 28
    # MNIST数据集包含10个不同的类别(数字0-9)
    num_classes = 10
    # 训练模型时将完整遍历数据集的次数设为3次
    num_epochs = 3
    # 指定每个批次处理的数据量为64(这里直接使用函数参数batch_size更灵活)
    # 注意:下面代码中实际使用的batch_size会以函数参数为准

    # 加载MNIST训练集
    # 参数说明:
    # root: 数据存储的根目录
    # train: 设为True表示加载训练数据集
    # transform: 将图像数据转化为PyTorch的Tensor格式
    # download: 如果数据不存在则自动下载
    train_dataset = datasets.MNIST(
        root="../data",
        train=True,
        transform=transforms.ToTensor(),
        download=True,
    )

    # 加载MNIST测试集
    # 参数与训练集相似,只是train设为False来加载测试数据
    test_dataset = datasets.MNIST(
        root="../data", train=False, transform=transforms.ToTensor()
    )

    # 使用DataLoader创建数据加载器,它会在训练时按批次提供数据
    # shuffle=True表示在每次训练之前都会随机打乱数据顺序,有助于训练过程的稳定性和泛化能力
    train_loader = torch.utils.data.DataLoader(
        dataset=train_dataset, batch_size=batch_size, shuffle=True
    )

    # 同样为测试集创建数据加载器
    test_loader = torch.utils.data.DataLoader(
        dataset=test_dataset, batch_size=batch_size, shuffle=True
    )

    # 函数最后返回训练和测试数据加载器,方便后续模型训练和评估使用
    return train_loader, test_loader


# 定义一个卷积神经网络(CNN)类,继承自nn.Module
class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()  # 调用父类的构造函数初始化
        # 定义第一个卷积层块
        self.conv1 = nn.Sequential(  # Sequential表示顺序容器
            nn.Conv2d(
                in_channels=1,  # 输入通道数,灰度图是1通道
                out_channels=16,  # 输出通道数(特征图数)为16
                kernel_size=5,  # 卷积核大小为5x5
                stride=1,  # 卷积步长为1
                padding=2,  # 填充为2,使输出与输入大小相同
            ),  # 卷积后输出的特征图大小为 (16, 28, 28)
            nn.ReLU(),  # ReLU激活函数,使非线性
            nn.MaxPool2d(
                kernel_size=2
            ),  # 最大池化,池化核大小为2x2,输出大小变为 (16, 14, 14)
        )
        # 定义第二个卷积层块
        self.conv2 = nn.Sequential(  # 输入特征图大小为 (16, 14, 14)
            nn.Conv2d(
                16, 32, 5, 1, 2
            ),  # 输入通道16,输出通道32,卷积核5x5,步长1,填充2
            nn.ReLU(),  # ReLU激活函数
            nn.Conv2d(32, 32, 5, 1, 2),  # 再次卷积,保持输入输出通道数相同
            nn.ReLU(),  # ReLU激活函数
            nn.MaxPool2d(2),  # 最大池化,池化核2x2,输出大小变为 (32, 7, 7)
        )
        # 定义第三个卷积层模块:继续深化特征学习
        self.conv3 = nn.Sequential(
            # 上一层输出是32个特征图,每个大小为14x14像素
            nn.Conv2d(
                in_channels=32,  # 接收来自上一层的32个特征图
                out_channels=64,  # 本层输出将增强到64个特征图,有助于捕捉更复杂的特征
                kernel_size=5,  # 使用5x5的卷积核探索局部特征
                stride=1,  # 步长为1,意味着卷积核在特征图上逐像素移动
                padding=2,  # 填充2个像素,保持输出特征图尺寸与输入相同,这里是(14, 14)
            ),
            nn.ReLU(),  # 应用ReLU激活函数,增加网络的非线性表达能力,激活输出特征
            # 注意:此处未再进行池化操作,保持空间尺寸为(64, 14, 14),更多关注特征的丰富而非下采样
        )

        # 定义全连接层
        self.out = nn.Linear(
            64 * 7 * 7, 10
        )  # 全连接层,将64*7*7个节点连接到10个输出节点(类别数为10)

    # 定义前向传播过程
    def forward(self, x):
        x = self.conv1(x)  # 输入数据经过第一个卷积层块
        x = self.conv2(x)  # 然后经过第二个卷积层块
        x = self.conv3(x)  # 再经过第三个卷积层块
        x = x.view(
            x.size(0), -1
        )  # 将多维的特征图展平成一维向量,形状为 (batch_size, 64*7*7)
        output = self.out(x)  # 输入到全连接层,得到最终的输出
        return output  # 返回输出结果


def accuracy(predictions, labels):
    """
    计算模型预测的准确率,即预测正确的样本数占总样本数的比例。
    - predictions: 这是一个来自PyTorch的张量(tensor),包含了模型对每个样本的预测得分。每个样本对应一列,每列中的数值表示该样本属于各个类别的可能性大小。
    - labels: 同样是PyTorch张量,但这里存储的是每个样本的真实类别标签。它的形状应与predictions相对应,确保一一匹配。
    返回值说明:
    - rights: 一个整数,代表预测正确的样本数量。通过比较预测类别和实际类别得到。
    - total: 一个整数,表示总共有多少个样本参与了这次准确率的计算。
    """
    # 使用torch.max找到predictions中每行的最大值及对应的索引,索引即为预测的类别
    pred = torch.max(predictions.data, 1)[1]

    # 将预测类别pred与实际类别labels进行元素级比较,eq函数会返回一个布尔型张量,True表示预测正确,False表示预测错误。
    # 使用sum函数累计预测正确的数量(True被视为1,False被视为0)
    rights = pred.eq(labels.data.view_as(pred)).sum()

    # 返回预测正确的数量和总样本数
    return rights, len(labels)


def train_model(
        net, train_loader, test_loader, num_epochs, batch_size, learning_rate=0.001
):
    """
    参数:
    net (nn.Module): 待训练的神经网络
    train_loader (DataLoader): 训练数据加载器
    test_loader (DataLoader): 测试数据加载器
    num_epochs (int): 训练轮数
    batch_size (int): 每批数据大小
    learning_rate (float): 学习率,默认值为0.001
    """

    # 定义损失函数
    criterion = nn.CrossEntropyLoss()

    # 定义优化器,这里使用Adam优化算法
    optimizer = optim.Adam(net.parameters(), lr=learning_rate)

    # 开始训练循环
    for epoch in range(num_epochs):
        # 保存当前epoch的结果
        train_rights = []

        # 针对训练数据加载器中的每一个批进行循环
        for batch_idx, (data, target) in enumerate(train_loader):
            net.train()  # 将模型设置为训练模式
            output = net(data)  # 前向传播,获取模型输出
            loss = criterion(output, target)  # 计算损失
            optimizer.zero_grad()  # 清空梯度
            loss.backward()  # 反向传播,计算梯度
            optimizer.step()  # 更新参数
            right = accuracy(output, target)  # 计算准确率
            train_rights.append(right)  # 保存每批的准确率

            # 每经过100个批次,进行一次验证
            if batch_idx % 100 == 0:
                net.eval()  # 将模型设置为评估模式
                val_rights = []  # 保存验证结果

                # 针对验证数据加载器中的每一个批进行循环
                for data, target in test_loader:
                    output = net(data)  # 前向传播,获取模型输出
                    right = accuracy(output, target)  # 计算准确率
                    val_rights.append(right)  # 保存每批的准确率

                # 计算训练集和验证集的准确率
                # 计算训练集和验证集的总正确预测数以及总样本数
                # 对于每个批次,train_rights和val_rights列表分别存储了该批次的正确预测数和总样本数
                # tup[0]表示正确预测数,tup[1]表示总样本数
                train_total_correct = sum(
                    [tup[0] for tup in train_rights]
                )  # 训练集总正确预测数
                train_total_samples = sum(
                    [tup[1] for tup in train_rights]
                )  # 训练集总样本数
                val_total_correct = sum(
                    [tup[0] for tup in val_rights]
                )  # 验证集总正确预测数
                val_total_samples = sum(
                    [tup[1] for tup in val_rights]
                )  # 验证集总样本数

                # 使用元组存储这些值,方便后续计算准确率
                train_r = (train_total_correct, train_total_samples)
                val_r = (val_total_correct, val_total_samples)

                # 打印当前训练进度和性能指标,帮助初学者直观了解训练情况
                # epoch: 当前正在进行的训练轮次
                # [batch_idx * batch_size/{len(train_loader.dataset)}: 表示已完成的样本数/总样本数
                # ({:.0f}%): 完成百分比,计算已完成的批次占总批次的比例
                # 损失: {:.6f}: 当前批次的平均损失值,衡量模型预测错误的程度
                # 训练集准确率: {:.2f}%: 根据train_r计算得出,表示训练集上模型预测正确的百分比
                # 测试集正确率: {:.2f}%: 根据val_r计算得出,表示验证集上模型预测正确的百分比

                print(
                    "当前epoch: {} [{}/{} ({:.0f}%)]\t损失: {:.6f}\t训练集准确率: {:.2f}%\t测试集正确率: {:.2f}%".format(
                        epoch,  # 当前训练轮数
                        batch_idx * batch_size,  # 当前批次结束时已处理的样本数
                        len(train_loader.dataset),  # 总样本数
                        100.0 * batch_idx / len(train_loader),  # 完成的百分比
                        loss.data,  # 当前批次的损失值
                        100.0 * train_r[0] / train_r[1],  # 训练集准确率
                        100.0 * val_r[0] / val_r[1],  # 验证集准确率
                    )
                )


if __name__ == "__main__":
    # 加载MNIST数据集,每个批次64个样本
    train_loader, test_loader = load_mnist(batch_size=64)

    # 实例化一个卷积神经网络模型
    net = CNN()

    # 调用函数训练模型,传入网络模型、训练数据加载器、验证数据加载器、训练轮数和批次大小
    train_model(net, train_loader, test_loader, num_epochs=2, batch_size=64)

simpleclassifier

import torch
import torch.nn as nn
import torch.optim as optim

from torchvision import datasets, transforms


def load_mnist(batch_size):
    # 初始化MNIST数据集加载器的函数

    # 超参数定义部分
    # 输入图像的尺寸是28x28像素,因此输入尺寸为28
    input_size = 28
    # MNIST数据集包含10个不同的类别(数字0-9)
    num_classes = 10
    # 训练模型时将完整遍历数据集的次数设为3次
    num_epochs = 3
    # 指定每个批次处理的数据量为64(这里直接使用函数参数batch_size更灵活)
    # 注意:下面代码中实际使用的batch_size会以函数参数为准

    # 加载MNIST训练集
    # 参数说明:
    # root: 数据存储的根目录
    # train: 设为True表示加载训练数据集
    # transform: 将图像数据转化为PyTorch的Tensor格式
    # download: 如果数据不存在则自动下载
    train_dataset = datasets.MNIST(
        root="../data",
        train=True,
        transform=transforms.ToTensor(),
        download=True,
    )

    # 加载MNIST测试集
    # 参数与训练集相似,只是train设为False来加载测试数据
    test_dataset = datasets.MNIST(
        root="../data", train=False, transform=transforms.ToTensor()
    )

    # 使用DataLoader创建数据加载器,它会在训练时按批次提供数据
    # shuffle=True表示在每次训练之前都会随机打乱数据顺序,有助于训练过程的稳定性和泛化能力
    train_loader = torch.utils.data.DataLoader(
        dataset=train_dataset, batch_size=batch_size, shuffle=True
    )

    # 同样为测试集创建数据加载器
    test_loader = torch.utils.data.DataLoader(
        dataset=test_dataset, batch_size=batch_size, shuffle=True
    )

    # 函数最后返回训练和测试数据加载器,方便后续模型训练和评估使用
    return train_loader, test_loader


# 定义一个卷积神经网络(CNN)类,继承自nn.Module
class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()  # 调用父类的构造函数初始化
        # 定义第一个卷积层块
        self.conv1 = nn.Sequential(  # Sequential表示顺序容器
            nn.Conv2d(
                in_channels=1,  # 输入通道数,灰度图是1通道
                out_channels=16,  # 输出通道数(特征图数)为16
                kernel_size=5,  # 卷积核大小为5x5
                stride=1,  # 卷积步长为1
                padding=2,  # 填充为2,使输出与输入大小相同
            ),  # 卷积后输出的特征图大小为 (16, 28, 28)
            nn.ReLU(),  # ReLU激活函数,使非线性
            nn.MaxPool2d(
                kernel_size=2
            ),  # 最大池化,池化核大小为2x2,输出大小变为 (16, 14, 14)
        )
        # 定义第二个卷积层块
        self.conv2 = nn.Sequential(  # 输入特征图大小为 (16, 14, 14)
            nn.Conv2d(
                16, 32, 5, 1, 2
            ),  # 输入通道16,输出通道32,卷积核5x5,步长1,填充2
            nn.ReLU(),  # ReLU激活函数
            nn.Conv2d(32, 32, 5, 1, 2),  # 再次卷积,保持输入输出通道数相同
            nn.ReLU(),  # ReLU激活函数
            nn.MaxPool2d(2),  # 最大池化,池化核2x2,输出大小变为 (32, 7, 7)
        )
        # 定义第三个卷积层模块:继续深化特征学习
        self.conv3 = nn.Sequential(
            # 上一层输出是32个特征图,每个大小为14x14像素
            nn.Conv2d(
                in_channels=32,  # 接收来自上一层的32个特征图
                out_channels=64,  # 本层输出将增强到64个特征图,有助于捕捉更复杂的特征
                kernel_size=5,  # 使用5x5的卷积核探索局部特征
                stride=1,  # 步长为1,意味着卷积核在特征图上逐像素移动
                padding=2,  # 填充2个像素,保持输出特征图尺寸与输入相同,这里是(14, 14)
            ),
            nn.ReLU(),  # 应用ReLU激活函数,增加网络的非线性表达能力,激活输出特征
            # 注意:此处未再进行池化操作,保持空间尺寸为(64, 14, 14),更多关注特征的丰富而非下采样
        )

        # 定义全连接层
        self.out = nn.Linear(
            64 * 7 * 7, 10
        )  # 全连接层,将64*7*7个节点连接到10个输出节点(类别数为10)

    # 定义前向传播过程
    def forward(self, x):
        x = self.conv1(x)  # 输入数据经过第一个卷积层块
        x = self.conv2(x)  # 然后经过第二个卷积层块
        x = self.conv3(x)  # 再经过第三个卷积层块
        x = x.view(
            x.size(0), -1
        )  # 将多维的特征图展平成一维向量,形状为 (batch_size, 64*7*7)
        output = self.out(x)  # 输入到全连接层,得到最终的输出
        return output  # 返回输出结果


def accuracy(predictions, labels):
    """
    计算模型预测的准确率,即预测正确的样本数占总样本数的比例。
    - predictions: 这是一个来自PyTorch的张量(tensor),包含了模型对每个样本的预测得分。每个样本对应一列,每列中的数值表示该样本属于各个类别的可能性大小。
    - labels: 同样是PyTorch张量,但这里存储的是每个样本的真实类别标签。它的形状应与predictions相对应,确保一一匹配。
    返回值说明:
    - rights: 一个整数,代表预测正确的样本数量。通过比较预测类别和实际类别得到。
    - total: 一个整数,表示总共有多少个样本参与了这次准确率的计算。
    """
    # 使用torch.max找到predictions中每行的最大值及对应的索引,索引即为预测的类别
    pred = torch.max(predictions.data, 1)[1]

    # 将预测类别pred与实际类别labels进行元素级比较,eq函数会返回一个布尔型张量,True表示预测正确,False表示预测错误。
    # 使用sum函数累计预测正确的数量(True被视为1,False被视为0)
    rights = pred.eq(labels.data.view_as(pred)).sum()

    # 返回预测正确的数量和总样本数
    return rights, len(labels)


def train_model(
        net, train_loader, test_loader, num_epochs, batch_size, learning_rate=0.001
):
    """
    参数:
    net (nn.Module): 待训练的神经网络
    train_loader (DataLoader): 训练数据加载器
    test_loader (DataLoader): 测试数据加载器
    num_epochs (int): 训练轮数
    batch_size (int): 每批数据大小
    learning_rate (float): 学习率,默认值为0.001
    """

    # 定义损失函数
    criterion = nn.CrossEntropyLoss()

    # 定义优化器,这里使用Adam优化算法
    optimizer = optim.Adam(net.parameters(), lr=learning_rate)

    # 开始训练循环
    for epoch in range(num_epochs):
        # 保存当前epoch的结果
        train_rights = []

        # 针对训练数据加载器中的每一个批进行循环
        for batch_idx, (data, target) in enumerate(train_loader):
            net.train()  # 将模型设置为训练模式
            output = net(data)  # 前向传播,获取模型输出
            loss = criterion(output, target)  # 计算损失
            optimizer.zero_grad()  # 清空梯度
            loss.backward()  # 反向传播,计算梯度
            optimizer.step()  # 更新参数
            right = accuracy(output, target)  # 计算准确率
            train_rights.append(right)  # 保存每批的准确率

            # 每经过100个批次,进行一次验证
            if batch_idx % 100 == 0:
                net.eval()  # 将模型设置为评估模式
                val_rights = []  # 保存验证结果

                # 针对验证数据加载器中的每一个批进行循环
                for data, target in test_loader:
                    output = net(data)  # 前向传播,获取模型输出
                    right = accuracy(output, target)  # 计算准确率
                    val_rights.append(right)  # 保存每批的准确率

                # 计算训练集和验证集的准确率
                # 计算训练集和验证集的总正确预测数以及总样本数
                # 对于每个批次,train_rights和val_rights列表分别存储了该批次的正确预测数和总样本数
                # tup[0]表示正确预测数,tup[1]表示总样本数
                train_total_correct = sum(
                    [tup[0] for tup in train_rights]
                )  # 训练集总正确预测数
                train_total_samples = sum(
                    [tup[1] for tup in train_rights]
                )  # 训练集总样本数
                val_total_correct = sum(
                    [tup[0] for tup in val_rights]
                )  # 验证集总正确预测数
                val_total_samples = sum(
                    [tup[1] for tup in val_rights]
                )  # 验证集总样本数

                # 使用元组存储这些值,方便后续计算准确率
                train_r = (train_total_correct, train_total_samples)
                val_r = (val_total_correct, val_total_samples)

                # 打印当前训练进度和性能指标,帮助初学者直观了解训练情况
                # epoch: 当前正在进行的训练轮次
                # [batch_idx * batch_size/{len(train_loader.dataset)}: 表示已完成的样本数/总样本数
                # ({:.0f}%): 完成百分比,计算已完成的批次占总批次的比例
                # 损失: {:.6f}: 当前批次的平均损失值,衡量模型预测错误的程度
                # 训练集准确率: {:.2f}%: 根据train_r计算得出,表示训练集上模型预测正确的百分比
                # 测试集正确率: {:.2f}%: 根据val_r计算得出,表示验证集上模型预测正确的百分比

                print(
                    "当前epoch: {} [{}/{} ({:.0f}%)]\t损失: {:.6f}\t训练集准确率: {:.2f}%\t测试集正确率: {:.2f}%".format(
                        epoch,  # 当前训练轮数
                        batch_idx * batch_size,  # 当前批次结束时已处理的样本数
                        len(train_loader.dataset),  # 总样本数
                        100.0 * batch_idx / len(train_loader),  # 完成的百分比
                        loss.data,  # 当前批次的损失值
                        100.0 * train_r[0] / train_r[1],  # 训练集准确率
                        100.0 * val_r[0] / val_r[1],  # 验证集准确率
                    )
                )


if __name__ == "__main__":
    # 加载MNIST数据集,每个批次64个样本
    train_loader, test_loader = load_mnist(batch_size=64)

    # 实例化一个卷积神经网络模型
    net = CNN()

    # 调用函数训练模型,传入网络模型、训练数据加载器、验证数据加载器、训练轮数和批次大小
    train_model(net, train_loader, test_loader, num_epochs=2, batch_size=64)

decision_tree

from sklearn.tree import DecisionTreeClassifier, plot_tree
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import font_manager

font_path = "C:\\Windows\\Fonts\\simsun.ttc"  # 替换为您系统中的中文字体路径
font_prop = font_manager.FontProperties(fname=font_path)

plt.rcParams["font.family"] = font_prop.get_name()

# 如果还需要更改字体的大小、样式等
plt.rcParams["font.size"] = 12  # 修改为您需要的大小

# 创建数据集
X = np.array(
    [
        [0, 2, 0],  # 晴天,高温,无风
        [1, 1, 1],  # 阴天,中温,微风
        [2, 0, 2],  # 雨天,低温,强风
        # ... 添加更多样本以增加模型的准确性
    ]
)
y = np.array([0, 1, 2])  # 分别对应去野餐、去博物馆、在家看书

# 初始化决策树模型,设置最大深度为5
clf = DecisionTreeClassifier(max_depth=5, random_state=42)

# 训练模型
clf.fit(X, y)

# 可视化决策树
plt.figure(figsize=(20, 10))
plot_tree(
    clf,
    filled=True,
    feature_names=["天气状况", "温度", "风速"],
    class_names=["去野餐", "去博物馆", "在家看书"],
    rounded=True,
    fontsize=12,
)
plt.show()

svm

from sklearn import datasets
from sklearn.svm import SVC
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
import numpy as np

from matplotlib import font_manager

# 设置字体路径为系统中支持中文的字体
font_path = "C:\\Windows\\Fonts\\simsun.ttc"  # 替换为您系统中的中文字体路径
font_prop = font_manager.FontProperties(fname=font_path)

plt.rcParams["font.family"] = font_prop.get_name()
from matplotlib import rcParams

# 或者直接指定负号字符为普通减号字符
rcParams["axes.unicode_minus"] = False

# 继续绘制图形代码


# 生成模拟数据
X, y = datasets.make_blobs(n_samples=50, centers=2, random_state=6)

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42
)

# 创建 SVM 模型
model = SVC(kernel="linear")
model.fit(X_train, y_train)

# 绘制数据点和分类边界
plt.figure(figsize=(8, 6))
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap="autumn")

# 绘制决策边界
ax = plt.gca()
xlim = ax.get_xlim()
ylim = ax.get_ylim()

# 创建网格点
xx = np.linspace(xlim[0], xlim[1], 30)
yy = np.linspace(ylim[0], ylim[1], 30)
YY, XX = np.meshgrid(yy, xx)
xy = np.vstack([XX.ravel(), YY.ravel()]).T
Z = model.decision_function(xy).reshape(XX.shape)

# 绘制决策边界和间隔
ax.contour(
    XX, YY, Z, colors="k", levels=[-1, 0, 1], alpha=0.5, linestyles=["--", "-", "--"]
)
plt.scatter(
    model.support_vectors_[:, 0],
    model.support_vectors_[:, 1],
    s=100,
    linewidth=1,
    facecolors="none",
    edgecolors="k",
)
plt.title("支持向量机分类示例")
plt.xlabel("特征1")
plt.ylabel("特征2")
plt.show()

randomforest

import matplotlib.pyplot as plt
from sklearn.tree import plot_tree
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris

# 加载数据集
iris = load_iris()
X, y = iris.data, iris.target

# 训练随机森林模型
rf = RandomForestClassifier(n_estimators=3, random_state=42)  # 使用3棵树以便于可视化
rf.fit(X, y)

# 绘制随机森林中的决策树
fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(20, 5), dpi=100)
for index in range(0, 3):
    plot_tree(
        rf.estimators_[index],
        feature_names=iris.feature_names,
        class_names=iris.target_names,
        filled=True,
        ax=axes[index],
    )

    axes[index].set_title(f"Tree {index + 1}")

plt.tight_layout()
plt.show()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

frostmelody

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值