前言
提醒:
文章内容为方便作者自己后日复习与查阅而进行的书写与发布,其中引用内容都会使用链接表明出处(如有侵权问题,请及时联系)。
其中内容多为一次书写,缺少检查与订正,如有问题或其他拓展及意见建议,欢迎评论区讨论交流。
文章目录
- 前言
- 数据集MNIST
- CNN
-
-
- [1. 卷积层(Convolutional Layer)](#1. 卷积层(Convolutional Layer))
- [2. 激活函数(Activation Function)](#2. 激活函数(Activation Function))
- [3. 池化层(Pooling Layer)](#3. 池化层(Pooling Layer))
- [4. 全连接层(Fully Connected Layer)](#4. 全连接层(Fully Connected Layer))
- [5. 卷积神经网络的典型结构](#5. 卷积神经网络的典型结构)
- [6. 卷积神经网络的训练](#6. 卷积神经网络的训练)
- [7. CNN 训练的优化:梯度下降法](#7. CNN 训练的优化:梯度下降法)
-
- 代码分析
-
- [def init(self,hidden_channel_size_1,hidden_channel_size_2,num_classes=10):](#def init(self,hidden_channel_size_1,hidden_channel_size_2,num_classes=10):)
-
-
- [1 第一层卷积](#1 第一层卷积)
- [2 第二层卷积](#2 第二层卷积)
- [3 全连接层](#3 全连接层)
-
- [def forward(self, x):](#def forward(self, x):)
- [criterion = nn.CrossEntropyLoss()](#criterion = nn.CrossEntropyLoss())
- [optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)](#optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate))
- 训练过程
- 测试模型
数据集MNIST
MNIST(Modified National Institute of Standards and Technology)数据集是一个广泛使用的图像数据集,主要用于训练和测试图像处理系统,尤其是机器学习和计算机视觉领域的算法。MNIST 是手写数字的一个标准化数据集,常用作分类任务的基准数据集。
数据集概述
***MNIST 数据集包含了 28x28 像素的手写数字图像,且图像是灰度图(即每个像素值在 0 到 255 之间)***数据集包含两部分:
-
训练集(Training set):
- 包含 60,000 张图像。
- 每张图像对应一个标签,表示该图像所表示的数字(0 到 9)。
-
测试集(Test set):
- 包含 10,000 张图像。
- 用于测试模型的泛化能力。
每张图像的标签为 0 到 9 的一个数字,表示图像中手写数字的类别。
数据集的结构
- 图像大小: 每张图像的尺寸为 28x28 像素。
- 灰度级: 每个像素的灰度值介于 0(黑色)到 255(白色)之间。
- 数据类型: 图像数据通常会被转换为浮点数(归一化为 0 到 1 之间),以便输入到神经网络中进行训练。
数据集的格式
MNIST 数据集的每张图像都由 28x28 的灰度值构成,并且对应一个标签。数据通常以以下格式存储:
- 每张图像是一个 28x28 的矩阵。
- 每个标签是一个数字(0 到 9),表示图像中的数字。
使用 MNIST 数据集
MNIST 数据集是机器学习和深度学习初学者常用的入门数据集,适用于各种分类算法的实验和研究。因为它的任务相对简单,所以它常常作为测试新算法的基准。常见的算法包括:
- 传统的机器学习算法(如支持向量机 SVM、k近邻 KNN 等)。
- 深度学习模型(如卷积神经网络 CNN)。
例子:如何加载 MNIST 数据集
使用 PyTorch,可以通过 torchvision.datasets.MNIST
来直接加载 MNIST 数据集。
数据集的应用
- 机器学习: 因为 MNIST 是一个简单且具有代表性的分类问题,它非常适合用来评估和比较不同的机器学习算法。许多研究者和开发者使用它来验证新提出的算法的有效性。
- 深度学习: 在深度学习领域,MNIST 数据集常常用来作为卷积神经网络(CNN)和其他深度学习模型的初步测试集,帮助开发者快速验证他们的模型效果。
数据集的挑战
尽管 MNIST 数据集非常经典,但它也逐渐暴露出了一些局限性。随着计算机视觉技术的进步,MNIST 的问题变得相对简单,许多现代模型可以达到接近 100% 的准确率。因此,很多研究者现在更倾向于使用更复杂的图像数据集,如 CIFAR-10、CIFAR-100、ImageNet 等。
数据集介绍
使用 PyTorch,可以通过 torchvision.datasets.MNIST 来直接加载 MNIST 数据集:
python
# MNIST dataset
import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms
train_dataset = torchvision.datasets.MNIST(root='data',
train=True,
download=True,
transform=transforms.ToTensor())
test_dataset = torchvision.datasets.MNIST(root='data',
train=False,
transform=transforms.ToTensor(),
download=True)
# Data loader
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=False)
1. 导入必要的库
python
import torch
import torchvision
import torchvision.transforms as transforms
- torch:PyTorch的核心库,提供了多维数组(Tensor)等基础设施。
- torchvision:一个专门用于计算机视觉任务的库,提供了常用的数据集、模型和图像处理工具。
- torchvision.transforms:用于数据预处理和数据增强的模块。
2. 加载MNIST数据集
python
train_dataset = torchvision.datasets.MNIST(root='data',
train=True,
download=True,
transform=transforms.ToTensor())
-
torchvision.datasets.MNIST
:这是一个用于加载MNIST数据集的类。MNIST数据集包含手写数字的图像,通常用于图像分类任务。 -
参数说明:
root='data'
:指定数据集存储的根目录。如果该目录不存在,数据集将下载到此位置。train=True
:加载训练集。如果设置为False
,则加载测试集。download=True
:如果在指定的root
路径下找不到数据集,将自动下载数据集。transform=transforms.ToTensor()
:应用于数据集的转换操作。这里使用了transforms.ToTensor()
,它将加载的PIL图像转换为PyTorch的Tensor格式,同时将像素值从[0, 255]范围归一化到[0, 1]范围。
3. 加载测试集
python
test_dataset = torchvision.datasets.MNIST(root='data',
train=False,
transform=transforms.ToTensor(),
download=True)
- 这段代码与加载训练集的代码类似,但
train=False
表示加载的是测试集。其他参数相同。测试集用于模型评估,而训练集用于模型训练。
4. 创建数据加载器
python
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
batch_size=batch_size,
shuffle=True)
-
torch.utils.data.DataLoader
:这是PyTorch提供的一个工具,用于批量加载数据集,便于在训练和测试模型时使用。 -
参数说明:
dataset=train_dataset
:指定要加载的数据集,这里是训练集。batch_size=batch_size
:每个批次加载的数据样本数量。batch_size
是一个变量,通常在代码的其他地方定义。合理的批量大小可以提高训练效率,并有助于模型的收敛。shuffle=True
:在每个epoch开始前打乱数据集的顺序,增加训练的随机性,有助于提升模型的泛化能力。
python
test_loader = torch.utils.data.DataLoader(dataset=test_dataset,
batch_size=batch_size,
shuffle=False)
- 这段代码用于加载测试集,参数与训练集加载器相似。这里的
shuffle=False
表示在测试时不打乱数据集。测试集的顺序通常是重要的,以便在评估模型性能时保持一致。
数据集实例
python
# MNIST dataset
import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import numpy as np
train_dataset = torchvision.datasets.MNIST(root='data',
train=True,
download=True,
transform=transforms.ToTensor())
test_dataset = torchvision.datasets.MNIST(root='data',
train=False,
transform=transforms.ToTensor(),
download=True)
# Data loader
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
batch_size=100,
shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset,
batch_size=100,
shuffle=False)
images, labels = zip(*[(train_dataset[i][0], train_dataset[i][1]) for i in range(10)])
# 设置绘图
fig, axes = plt.subplots(2, 5, figsize=(10, 5))
# 展示图像
for i, ax in enumerate(axes.flat):
# 获取图像数据和标签
img = images[i].numpy().squeeze() # 移除多余的维度
label = labels[i]
# 显示图像
ax.imshow(img, cmap='gray')
ax.set_title(f'Label: {label}')
ax.axis('off') # 不显示坐标轴
plt.tight_layout()
plt.show()
运行结果为:
CNN
卷积神经网络(Convolutional Neural Networks,CNN)是一种深度学习模型,广泛应用于计算机视觉、语音识别等任务。CNN 通过利用局部连接和权重共享,显著降低了模型参数的数量,从而提高了训练效率并防止过拟合。CNN 的核心是卷积操作,并结合了池化层和全连接层等其他组件。
1. 卷积层(Convolutional Layer)
卷积层是 CNN 的核心,它负责从输入数据中提取特征。其基本操作是 卷积(convolution),通过卷积核(或滤波器)对输入数据进行滑动和局部特征提取。
数学公式:
设输入图像为一个二维矩阵 I I I,卷积核(滤波器)为 K K K,卷积操作输出为特征图 O O O。对于一个输入图像 I I I 和一个卷积核 K K K,卷积的计算公式为:
O ( i , j ) = ( I ∗ K ) ( i , j ) = ∑ m ∑ n I ( i + m , j + n ) K ( m , n ) O(i, j) = (I * K)(i, j) = \sum_m \sum_n I(i+m, j+n) K(m, n) O(i,j)=(I∗K)(i,j)=m∑n∑I(i+m,j+n)K(m,n)
其中:
- I ( i , j ) I(i, j) I(i,j) 是输入图像 I I I 在位置 ( i , j ) (i, j) (i,j) 的像素值;
- K ( m , n ) K(m, n) K(m,n) 是卷积核 K K K 在位置 ( m , n ) (m, n) (m,n) 的权重;
- O ( i , j ) O(i, j) O(i,j) 是输出特征图在位置 ( i , j ) (i, j) (i,j) 的值。
这里的操作是卷积核在输入图像上滑动并计算加权和。通过卷积层,CNN 能够提取局部特征(例如边缘、纹理、颜色等),并保留空间层次信息。
卷积的步骤:
- 卷积核从图像的左上角开始,逐步向右滑动(水平),然后向下滑动(垂直)。
- 在每个位置,卷积核与输入图像的局部区域进行加权求和,输出一个新的像素值,生成新的特征图。
- 通过多个卷积核,CNN 可以从不同的角度提取不同类型的特征。
2. 激活函数(Activation Function)
卷积操作后的输出通常会经过非线性激活函数,常用的激活函数包括 ReLU(Rectified Linear Unit)。ReLU 的数学公式如下:
ReLU ( x ) = max ( 0 , x ) \text{ReLU}(x) = \max(0, x) ReLU(x)=max(0,x)
ReLU 激活函数帮助网络引入非线性,使其能够学习复杂的特征。它的优点是计算简单且收敛速度较快。
3. 池化层(Pooling Layer)
池化层用于降低特征图的空间维度(宽度和高度),同时保持重要特征。池化层通常有两种操作:最大池化 (Max Pooling)和 平均池化(Average Pooling)。在最大池化中,取池化窗口内的最大值;在平均池化中,取池化窗口内的平均值。
最大池化:
给定池化窗口大小为 2 × 2 2 \times 2 2×2,池化操作可以表示为:
O ( i , j ) = max m , n ∈ P ( i , j ) I ( m , n ) O(i, j) = \max_{m, n \in P(i,j)} I(m, n) O(i,j)=m,n∈P(i,j)maxI(m,n)
其中, P ( i , j ) P(i,j) P(i,j) 是以 ( i , j ) (i, j) (i,j) 为中心的池化窗口。
池化操作的作用是减小特征图的尺寸,从而降低计算量并减少模型的参数量。
4. 全连接层(Fully Connected Layer)
在卷积和池化层后,CNN 通常包含一个或多个全连接层(FC),其作用是将高层次的特征组合成最终的分类结果。全连接层的每个神经元与前一层的所有神经元都有连接。
全连接层的计算公式为:
y = f ( W x + b ) y = f(Wx + b) y=f(Wx+b)
其中:
- x x x 是上一层的输出(例如,经过卷积层和池化层后的特征图展平后形成的向量);
- W W W 是权重矩阵;
- b b b 是偏置项;
- f f f 是激活函数(通常使用 ReLU 或 Sigmoid)。
全连接层的作用是进行分类或回归任务。对于分类任务,通常会在全连接层后使用 Softmax 激活函数,得到每个类别的概率分布。
5. 卷积神经网络的典型结构
一个典型的 CNN 网络结构包括以下几部分:
- 卷积层(Convolutional Layer):提取局部特征。
- 激活函数(ReLU):引入非线性。
- 池化层(Pooling Layer):降维,减少计算量。
- 全连接层(Fully Connected Layer):将高层特征映射到最终的输出(如分类标签)。
- Softmax 层:将输出转换为概率分布(用于分类任务)。
6. 卷积神经网络的训练
CNN 的训练过程类似于其他神经网络。主要包括以下几个步骤:
- 前向传播:输入图像通过卷积层、激活函数、池化层、全连接层,最终输出预测结果。
- 计算损失:通过比较预测结果与实际标签,计算损失函数(如交叉熵损失)。
- 反向传播:通过反向传播算法计算梯度,并利用梯度下降算法更新网络权重。
损失函数 L L L 通常是交叉熵损失(用于分类任务):
L = − ∑ i = 1 N y i log ( y ^ i ) L = - \sum_{i=1}^{N} y_i \log(\hat{y}_i) L=−i=1∑Nyilog(y^i)
其中:
- y i y_i yi 是实际标签的 one-hot 编码;
- y ^ i \hat{y}_i y^i 是网络的预测输出。
7. CNN 训练的优化:梯度下降法
在训练过程中,通常使用 梯度下降(Gradient Descent)及其变种(如 Adam、SGD 等)来优化网络的参数。通过计算损失函数对权重的梯度,更新网络中的参数。梯度下降的更新公式为:
W = W − η ∂ L ∂ W W = W - \eta \frac{\partial L}{\partial W} W=W−η∂W∂L
其中:
- W W W 是权重;
- η \eta η 是学习率;
- ∂ L ∂ W \frac{\partial L}{\partial W} ∂W∂L 是损失函数对权重的梯度。
代码分析
python
import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms
# try running the following code, if you see errors on Windows OS
# import os
# os.environ['KMP_DUPLICATE_LIB_OK']='TRUE'
# Device configuration
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# Hyper-parameters
hidden_channel_size_1 = 16
hidden_channel_size_2 = 32
num_epochs = 5
batch_size = 100
learning_rate = 0.001
# MNIST dataset
train_dataset = torchvision.datasets.MNIST(root='data',
train=True,
download=True,
transform=transforms.ToTensor())
test_dataset = torchvision.datasets.MNIST(root='data',
train=False,
transform=transforms.ToTensor(),
download=True)
# Data loader
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=False)
len(train_dataset),len(test_dataset)
# CNN
class CNeuralNet(nn.Module):
def __init__(self,hidden_channel_size_1,hidden_channel_size_2,num_classes=10):
super(CNeuralNet, self).__init__()
self.layer1 = nn.Sequential(
nn.Conv2d(1, hidden_channel_size_1, kernel_size=5, stride=1, padding=2),
nn.BatchNorm2d(hidden_channel_size_1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2))
self.layer2 = nn.Sequential(
nn.Conv2d(hidden_channel_size_1, hidden_channel_size_2, kernel_size=5, stride=1, padding=2),
nn.BatchNorm2d(hidden_channel_size_2),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2))
self.fc = nn.Linear(7*7*hidden_channel_size_2, num_classes) # originally 28*28, after two MaxPool2d(2), becomes 7*7
def forward(self, x):
out = self.layer1(x)
out = self.layer2(out)
out = out.reshape(out.size(0), -1)
out = self.fc(out)
return out
model = CNeuralNet(hidden_channel_size_1,hidden_channel_size_2).to(device)
# Loss and optimizer
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
# Train the model
total_step = len(train_loader)
for epoch in range(num_epochs):
for i, (images, labels) in enumerate(train_loader):
images = images.to(device)
labels = labels.to(device)
# Forward pass
outputs = model(images)
loss = criterion(outputs, labels)
# Backward and optimize
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (i+1) % 100 == 0:
print ('Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}'
.format(epoch+1, num_epochs, i+1, total_step, loss.item()))
# Test the model
model.eval() # eval mode (batchnorm uses moving mean/variance instead of mini-batch mean/variance)
with torch.no_grad():
correct = 0
total = 0
for images, labels in test_loader:
images = images.to(device)
labels = labels.to(device)
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print('Test Accuracy of the model on the 10000 test images: {} %'.format(100 * correct / total))
运行结果:
def init(self,hidden_channel_size_1,hidden_channel_size_2,num_classes=10):
python
def __init__(self,hidden_channel_size_1,hidden_channel_size_2,num_classes=10):
super(CNeuralNet, self).__init__()
self.layer1 = nn.Sequential(
nn.Conv2d(1, hidden_channel_size_1, kernel_size=5, stride=1, padding=2),
nn.BatchNorm2d(hidden_channel_size_1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2))
self.layer2 = nn.Sequential(
nn.Conv2d(hidden_channel_size_1, hidden_channel_size_2, kernel_size=5, stride=1, padding=2),
nn.BatchNorm2d(hidden_channel_size_2),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2))
self.fc = nn.Linear(7*7*hidden_channel_size_2, num_classes) # originally 28*28, after two MaxPool2d(2), becomes 7*7
1 第一层卷积
python
self.layer1 = nn.Sequential(
nn.Conv2d(1, hidden_channel_size_1, kernel_size=5, stride=1, padding=2),
nn.BatchNorm2d(hidden_channel_size_1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2))
-
卷积层 :
nn.Conv2d(1, hidden_channel_size_1, kernel_size=5, stride=1, padding=2)
输入通道数为 1(灰度图像),输出通道数为
hidden_channel_size_1
(即 16),卷积核大小为 5x5,步幅为 1,填充为 2。这意味着卷积后的输出维度保持为 28x28。卷积操作的数学公式为:
O i , j = ∑ m , n I i + m , j + n ⋅ K m , n O_{i,j} = \sum_{m,n} I_{i+m, j+n} \cdot K_{m,n} Oi,j=m,n∑Ii+m,j+n⋅Km,n
其中:
- I I I 是输入图像矩阵, K K K 是卷积核, O O O 是输出特征图。
-
批量归一化 :
nn.BatchNorm2d(hidden_channel_size_1)
用于规范化输出,保持均值接近 0,方差接近 1,帮助加速训练并防止梯度消失。
-
ReLU 激活函数 :
nn.ReLU()
引入非线性,ReLU 的公式为:
ReLU ( x ) = max ( 0 , x ) \text{ReLU}(x) = \max(0, x) ReLU(x)=max(0,x)
-
最大池化 :
nn.MaxPool2d(kernel_size=2, stride=2)
对特征图进行池化操作,使用 2x2 的池化窗口,步幅为 2,这将特征图尺寸减半。即,28x28 的特征图变为 14x14。
2 第二层卷积
python
self.layer2 = nn.Sequential(
nn.Conv2d(hidden_channel_size_1, hidden_channel_size_2, kernel_size=5, stride=1, padding=2),
nn.BatchNorm2d(hidden_channel_size_2),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2))
- 输入特征图大小为 14x14,卷积核大小为 5x5,步幅为 1,填充为 2,因此输出特征图的大小仍然是 14x14。
- 执行最大池化后,特征图尺寸将减半,变为 7x7。
3 全连接层
python
self.fc = nn.Linear(7*7*hidden_channel_size_2, num_classes)
- 在经过两次池化后,特征图的尺寸为 7x7,每个卷积层有
hidden_channel_size_2
个通道(即 32)。因此,全连接层的输入大小是 7 × 7 × 32 = 1568 7 \times 7 \times 32 = 1568 7×7×32=1568。 - 最终输出为
num_classes
,即 10(代表数字 0-9 的分类)。
def forward(self, x):
前向传播
python
def forward(self, x):
out = self.layer1(x)
out = self.layer2(out)
out = out.reshape(out.size(0), -1) # Flatten the output from convolution layers
out = self.fc(out)
return out
out.reshape(out.size(0), -1)
:将卷积层的输出展平为一维向量,准备进入全连接层。- 最终输出是一个大小为
(batch_size, num_classes)
的张量,表示每个输入图像属于每个类别的得分。
criterion = nn.CrossEntropyLoss()
损失函数
python
criterion = nn.CrossEntropyLoss()
- 交叉熵损失:用于分类任务,特别是多类别分类问题。公式为:
L = − ∑ i = 1 C y i log ( y ^ i ) L = -\sum_{i=1}^{C} y_i \log(\hat{y}_i) L=−i=1∑Cyilog(y^i)
其中:
- C C C 是类别数(这里是 10)。
- y i y_i yi 是实际类别的标签,通常是 one-hot 编码。
- y ^ i \hat{y}_i y^i 是模型预测的类别概率,通常通过 softmax 激活函数得到。
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
优化器
python
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
-
Adam 优化器 :一种自适应的优化算法,结合了 梯度下降 和 自适应学习率。
更新公式如下:
θ t = θ t − 1 − η m t v t + ϵ \theta_t = \theta_{t-1} - \eta \frac{m_t}{\sqrt{v_t} + \epsilon} θt=θt−1−ηvt +ϵmt
其中:
- θ \theta θ 是网络参数(权重)。
- m t m_t mt 和 v t v_t vt 分别是梯度的均值和方差估计。
- η \eta η 是学习率, ϵ \epsilon ϵ 是为了避免除零的常数。
训练过程
python
for epoch in range(num_epochs):
for i, (images, labels) in enumerate(train_loader):
images = images.to(device)
labels = labels.to(device)
outputs = model(images) # 前向传播
loss = criterion(outputs, labels) # 计算损失
optimizer.zero_grad() # 清除之前的梯度
loss.backward() # 反向传播
optimizer.step() # 更新参数
- 前向传播:输入图像通过卷积层和全连接层,生成输出。
- 计算损失:使用交叉熵损失函数计算预测值与真实标签之间的误差。
- 反向传播:计算梯度并更新网络的参数。
测试模型
python
model.eval() # 设定为评估模式,关闭 dropout 和 batchnorm
with torch.no_grad():
correct = 0
total = 0
for images, labels in test_loader:
images = images.to(device)
labels = labels.to(device)
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
- 评估模式 :在评估阶段,
model.eval()
会让 BatchNorm 使用训练过程中累计的均值和方差。 - 计算准确率 :通过
torch.max()
获取模型预测的类别索引,然后与实际标签进行比较,计算正确预测的数量,最终计算模型的准确率。
总结
这段代码实现了一个典型的卷积神经网络,包含两个卷积层和一个全连接层,通过训练数据不断优化网络参数,最终在测试数据上进行评估。主要数学公式包括:
- 卷积操作:通过卷积核提取局部特征。
- ReLU 激活函数:引入非线性变换。
- 交叉熵损失:用于多类别分类问题。
- 梯度下降优化:通过 Adam 优化器更新模型参数。