当前位置: 首页 > java >正文

传统神经网络实现-----手写数字识别(MNIST)项目

完整代码:

# import torch
# print(torch.__version__)#1.X     1、验证安装的开发环境是否正确,'''
MNIST包含70,000张手写数字图像: 60,000张用于训练,10,000张用于测试。
图像是灰度的,28x28像素的,并且居中的,以减少预处理和加快运行。
'''
import torch
from torch import nn    #导入神经网络模块,
from torch.utils.data import DataLoader #数据包管理工具,打包数据,
from torchvision import datasets #封装了很多与图像相关的模型,及数据集
from torchvision.transforms import ToTensor #数据转换,张量,将其他类型的数据转换为tensor张量,numpy array,dataframe'''下载训练数据集(包含训练图片+标签)'''
training_data = datasets.MNIST( #跳转到函数的内部源代码,pycharm 按下ctrl +鼠标点击root="data",#表示下载的手写数字 到哪个路径。60000train=True,#读取下载后的数据 中的 训练集download=True,#如果你之前已经下载过了,就不用再下载transform=ToTensor(),   #张量,图片是不能直接传入神经网络模型
)   #对于pytorch库能够识别的数据一般是tensor张量.
print(len(training_data))
# datasets.MNIST的参数:
#   root(string): 表示数据集的根目录,
#   train(bool, optional): 如果为True,则从training.pt创建数据集,否则从test.pt创建数据集
#   download(bool, optional): 如果为True,则从internet下载数据集并将其放入根目录。如果数据集已下载,则不会再次下载
#   transform(callable, optional): 接收PIL图片并返回转换后版本图片的转换函数'''下载测试数据集(包含训练图片+标签) '''
test_data = datasets.MNIST(root="data",train=False,download=True,transform=ToTensor(),#Tensor是在深度学习中提出并广泛应用的数据类型,它与深度学习框架(如 PyTorch、TensorFlow)紧密集成,方便进行神经网络的训练和推理。
)#NumPy 数组只能在CPU上运行。Tensor可以在GPU上运行,这在深度学习应用中可以显著提高计算速度。
print(len(test_data))# '''展示手写字图片,把训练数据集中的前59000张图片展示一下'''
# from matplotlib import pyplot as plt
# figure = plt.figure()
# for i in range(9):#
#     img, label = training_data[i+59000]#提取第59000张图片
#
#     figure.add_subplot(3, 3, i+1)#图像窗口中创建多个小窗口,小窗口用于显示图片
#     plt.title(label)
#     plt.axis("off")  # plt.show(I)#显示矢量,
#     plt.imshow(img.squeeze(), cmap="gray")  #plt.imshow()将NumPy数组data中的数据显示为图像,并在图形窗口中显示该图像
#     a = img.squeeze() # img.squeeze()从张量img中去掉维度为1的。如果该维度的大小不为1则张量不会改变。#cmap="gray"表示使用灰度色彩映射来显示图像。这意味着图像将以灰度模式显示
# plt.show()'''创建数据DataLoader(数据加载器)batch_size:将数据集分成多份,每一份为batch_size个数据。优点:可以减少内存的使用,提高训练速度。
'''
train_dataloader = DataLoader(training_data, batch_size=64)#64张图片为一个包,1、损失函数2、GPU一次性接受的图片个数
test_dataloader = DataLoader(test_data, batch_size=64)
for X, y in test_dataloader:#X是表示打包好的每一个数据包print(f"Shape of X [N, C, H, W]: {X.shape}")#print(f"Shape of y: {y.shape} {y.dtype}")break'''判断当前设备是否支持GPU,其中mps是苹果m系列芯片的GPU。'''#返回cuda,mps。CPU   m1 ,m2  集显CPU+GPU  RTX3060,
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
print(f"Using {device} device")#字符串的格式化。  CUDA驱动软件的功能:pytorch能够去执行cuda的命令,cuda通过GPU指令集去控制GPU
#神经网络的模型也需要传入到GPU,1个batchsize的数据集也需要传入到GPU,才可以进行训练。''' 定义神经网络  类的继承这种方式'''
class NeuralNetwork(nn.Module):#通过调用类的形式来使用神经网络,神经网络的模型,nn.moduledef __init__(self):#python基础关于类,self类自己本身super().__init__()#继承的父类初始化self.flatten = nn.Flatten()#展开,创建一个展开对象flattenself.hidden1 = nn.Linear(28*28, 128)#第1个参数:有多少个神经元传入进来,第2个参数:有多少个数据传出去前一层神经元的个数,当前本层神经元个数self.hidden2 = nn.Linear(128, 256)#为什么你要用128self.out = nn.Linear(256, 10)#输出必需和标签的类别相同,输入必须是上一层的神经元个数def forward(self, x):   #前向传播,你得告诉它  数据的流向。是神经网络层连接起来,函数名称不能改。当你调用forward函数的时候,传入进来的图像数据x = self.flatten.forward(x)     #图像进行展开  self.flatten.forwardx = self.hidden1.forward(x)x = torch.relu(x) #激活函数,torch使用的relu函数 relu,tanhx = self.hidden2.forward(x)x = torch.relu(x)x = self.out.forward(x)return xmodel = NeuralNetwork().to(device)#把刚刚创建的模型传入到Gpu
print(model)def train(dataloader, model, loss_fn, optimizer):model.train()#告诉模型,我要开始训练,模型中w进行随机化操作,已经更新w。在训练过程中,w会被修改的
#pytorch提供2种方式来切换训练和测试的模式,分别是:model.train() 和 model.eval()。
# 一般用法是:在训练开始之前写上model.trian(),在测试时写上 model.eval() 。batch_size_num = 1  #统计 训练的batch数量for X, y in dataloader:                 #其中batch为每一个数据的编号X, y = X.to(device), y.to(device)   #把训练数据集和标签传入cpu或GPUpred = model(X)             #.forward可以被省略,父类中已经对此功能进行了设置。自动初始化 w权值loss = loss_fn(pred, y)             #通过交叉熵损失函数计算损失值loss# Backpropagation 进来一个batch的数据,计算一次梯度,更新一次网络optimizer.zero_grad()               #梯度值清零loss.backward()                     #反向传播计算得到每个参数的梯度值woptimizer.step()                    #根据梯度更新网络w参数loss_value = loss.item()                  #从tensor数据中提取数据出来,tensor获取损失值if batch_size_num %100  ==0:print(f"loss: {loss_value:>7f}  [number:{batch_size_num}]")batch_size_num += 1def test(dataloader, model, loss_fn):size = len(dataloader.dataset)#10000num_batches = len(dataloader)#打包的数量model.eval()    #测试,w就不能再更新。test_loss, correct = 0, 0   #with torch.no_grad():   #一个上下文管理器,关闭梯度计算。当你确认不会调用Tensor.backward()的时候。这可以减少计算所用内存消耗。for X, y in dataloader:X, y = X.to(device), y.to(device)  #送到GPUpred = model.forward(X)test_loss += loss_fn(pred, y).item() #test_loss是会自动累加每一个批次的损失值correct += (pred.argmax(1) == y).type(torch.float).sum().item()a = (pred.argmax(1) == y)  #dim=1表示每一行中的最大值对应的索引号,dim=0表示每一列中的最大值对应的索引号b = (pred.argmax(1) == y).type(torch.float)test_loss /= num_batches  #能来衡量模型测试的好坏。correct /= size  #平均的正确率print(f"Test result: \n Accuracy: {(100*correct)}%, Avg loss: {test_loss}")loss_fn = nn.CrossEntropyLoss() #创建交叉熵损失函数对象,因为手写字识别中一共有10个数字,输出会有10个结果
# L1Loss:L1损失,也称为平均绝对误差(Mean Absolute Error, MAE)。它计算预测值与真实值之间的绝对差值的平均值。
# NLLLoss:负对数似然损失(Negative Log Likelihood Loss)。它用于多分类问题,通常与LogSoftmax输出层配合使用。
# NLLLoss2d:这是NLLLoss的一个特殊版本,用于处理2D图像数据。在最新版本的PyTorch中,这个损失函数可能已经被整合到NLLLoss中,通过指定reduction参数来实现同样的功能。
# PoissonNLLLoss:泊松负对数似然损失,用于泊松回归问题。
# GaussianNLLLoss:高斯负对数似然损失,用于高斯分布(正态分布)的回归问题。
# KLDivLoss:Kullback-Leibler散度损失,用于度量两个概率分布之间的差异。
# MSELoss:均方误差损失(Mean Squared Error Loss),计算预测值与真实值之间差值的平方的平均值。
# BCELoss:二元交叉熵损失(Binary Cross Entropy Loss),用于二分类问题。
# BCEWithLogitsLoss:结合了Sigmoid激活函数和二元交叉熵损失的损失函数,用于提高数值稳定性。
# HingeEmbeddingLoss:铰链嵌入损失,用于学习非线性嵌入或半监督学习。
# MultiLabelMarginLoss:多标签边际损失,用于多标签分类问题。
# SmoothL1Loss:平滑L1损失,是L1损失和L2损失(MSE)的结合,旨在避免梯度爆炸问题。
# HuberLoss:Huber损失,与SmoothL1Loss类似,但有一个可调的参数来控制L1和L2损失之间的平衡。
# SoftMarginLoss:软边际损失,用于二分类问题,可以看作是Hinge损失的一种软化版本。
# CrossEntropyLoss:交叉熵损失,用于多分类问题。它结合了LogSoftmax和NLLLoss的功能。
# MultiLabelSoftMarginLoss:多标签软边际损失,用于多标签二分类问题。
# CosineEmbeddingLoss:余弦嵌入损失,用于学习非线性嵌入,通过余弦相似度来度量样本之间的相似性。
# MarginRankingLoss:边际排序损失,用于排序问题,如学习到排序的嵌入空间。
# MultiMarginLoss:多边际损失,用于多分类问题,旨在优化分类边界的边际。
# TripletMarginLoss:三元组边际损失,用于学习嵌入空间中的距离度量,通常用于人脸识别或图像检索等任务。
# TripletMarginWithDistanceLoss:这是TripletMarginLoss的一个变体,允许使用自定义的距离函数。
# CTCLoss:连接时序分类损失(Connectionist Temporal Classification Loss),用于序列到序列的学习问题,特别是当输出序列的长度不固定时(如语音识别)。#一会改成adam优化器   梯度下降
optimizer = torch.optim.Adam(model.parameters(), lr=0.005)#创建一个优化器,SGD为随机梯度下降算法
# #params:要训练的参数,一般我们传入的都是model.parameters()。
# #lr:learning_rate学习率,也就是步长。#loss表示模型训练后的输出结果与 样本标签的差距。如果差距越小,就表示模型训练越好,越逼近于真实的模型。# train(train_dataloader, model, loss_fn, optimizer)#训练1次完整的数据,多轮训练,
# test(test_dataloader, model, loss_fn)epochs = 10 #到底选择多少呢?
for t in range(epochs):print(f"Epoch {t+1}\n-------------------------------")train(train_dataloader, model, loss_fn, optimizer)#10次训练
print("Done!")
test(test_dataloader, model, loss_fn)# # #分析sigmiod,relu
# # # sgd,Adam

按代码模块进行解析:

第一部分:环境验证与数据加载
# import torch
# print(torch.__version__)#1.X     1、验证安装的开发环境是否正确,
  • 注释掉了,用于检查 PyTorch 是否安装成功。


import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor

  • 导入 PyTorch 核心库及相关模块:

    • nn:构建神经网络。

    • DataLoader:批量加载数据。

    • datasets:内置数据集(如 MNIST)。

    • ToTensor:将图片转为 Tensor 格式。


training_data = datasets.MNIST(root="data",train=True,download=True,transform=ToTensor(),
)

  • 下载 训练集(60,000 张图):

    • root="data":保存到本地 data/ 文件夹。

    • transform=ToTensor():将图片转为 Tensor(灰度值归一化到 [0, 1])。


test_data = datasets.MNIST(root="data",train=False,download=True,transform=ToTensor(),
)

  • 下载 测试集(10,000 张图)。


train_dataloader = DataLoader(training_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)

  • 使用 DataLoader 将数据打包成 批次(每批 64 张图),方便训练。


 第二部分:设备选择与模型定义

device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"

  • 自动选择设备:

    • 优先使用 NVIDIA GPU(cuda);

    • 其次 Apple 芯片(mps);

    • 最后回退到 CPU。


class NeuralNetwork(nn.Module):def __init__(self):super().__init__()self.flatten = nn.Flatten()self.hidden1 = nn.Linear(28*28, 128)self.hidden2 = nn.Linear(128, 256)self.out = nn.Linear(256, 10)def forward(self, x):x = self.flatten(x)x = torch.relu(self.hidden1(x))x = torch.relu(self.hidden2(x))x = self.out(x)return x

  • 定义一个 三层全连接神经网络

    • 输入层:28×28 = 784 像素;

    • 隐藏层1:128 个神经元;

    • 隐藏层2:256 个神经元;

    • 输出层:10 个类别(0~9 数字);

    • 激活函数:ReLU。


model = NeuralNetwork().to(device)

  • 将模型迁移到 GPU(或 CPU)。


第三部分:训练与测试函数

def train(dataloader, model, loss_fn, optimizer):model.train()for batch, (X, y) in enumerate(dataloader):X, y = X.to(device), y.to(device)pred = model(X)loss = loss_fn(pred, y)optimizer.zero_grad()loss.backward()optimizer.step()if batch % 100 == 0:print(f"loss: {loss.item():.7f} [batch {batch}]")

  • 训练函数

    • 每个批次前向传播 → 计算损失 → 反向传播 → 更新权重;

    • 每 100 个批次打印一次损失值。


def test(dataloader, model, loss_fn):model.eval()test_loss, correct = 0, 0with torch.no_grad():for X, y in dataloader:X, y = X.to(device), y.to(device)pred = model(X)test_loss += loss_fn(pred, y).item()correct += (pred.argmax(1) == y).type(torch.float).sum().item()test_loss /= len(dataloader)correct /= len(dataloader.dataset)print(f"Test Accuracy: {100*correct:.2f}%, Avg loss: {test_loss:.4f}")

  • 测试函数

    • 不更新权重(model.eval());

    • 计算整体损失与准确率。


第四部分:损失函数与优化器

loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.005)

  • 使用 交叉熵损失(适合多分类);

  • 使用 Adam 优化器(比 SGD 更稳定)。


第五部分:训练循环

epochs = 10
for t in range(epochs):print(f"Epoch {t+1}\n-------------------------------")train(train_dataloader, model, loss_fn, optimizer)
print("Done!")
test(test_dataloader, model, loss_fn)

  • 训练 10 轮

  • 每轮遍历一次完整训练集;

  • 最后测试模型性能。

代码模块具体逐行逐句解释:

class NeuralNetwork(nn.Module):def __init__(self):super().__init__()self.flatten = nn.Flatten()self.hidden1 = nn.Linear(28*28, 128)self.hidden2 = nn.Linear(128, 256)self.out = nn.Linear(256, 10)def forward(self, x):x = self.flatten(x)x = torch.relu(self.hidden1(x))x = torch.relu(self.hidden2(x))x = self.out(x)return x
第一部分:类定义与初始化 (__init__ 方法)

class NeuralNetwork(nn.Module):

作用:声明一个继承自 PyTorch 基类 nn.Module 的新类
含义:所有自定义神经网络必须继承此类才能享受 PyTorch 的训练/推理功能(如 model.train(), model.eval()

super().__init__()

作用:调用父类 nn.Module 的构造函数
重要性:初始化模块的必要内部结构(如参数注册器),不可省略

self.flatten = nn.Flatten()

作用:创建一个展平层对象
功能详解:将多维输入(如图像 [B, H, W])压缩为一维向量 [B, H×W]
典型场景:连接卷积层和非全连接层时的过渡操作

self.hidden1 = nn.Linear(28*28, 128)

作用:定义第一个全连接层(又称密集层)
参数解析

  • in_features=28*28=784:输入特征数(对应 28×28 图像的像素总数)

  • out_features=128:本层神经元数量
    内部机制:自动创建权重矩阵 W₁ (形状 784×128) 和偏置向量 b₁ (长度 128)

self.hidden2 = nn.Linear(128, 256)

作用:定义第二个全连接层
参数解析

  • in_features=128:前一层的输出特征数

  • out_features=256:本层神经元数量
    内部机制:自动创建权重矩阵 W₂ (形状 128×256) 和偏置向量 b₂ (长度 256)

self.out = nn.Linear(256, 10)

作用:定义输出层
特殊设计

  • out_features=10:对应分类任务的类别数(如 MNIST 手写数字识别)

  • 输出未经过激活函数(直接输出 logits),配合交叉熵损失函数使用


第二部分:前向传播 (forward 方法)

def forward(self, x):

作用:定义数据的前向传播路径
关键性质:每次调用 model(input) 时会自动执行此方法

x = self.flatten(x)

作用:展平输入张量
示例

  • 输入形状 [batch_size, 28, 28] → 输出形状 [batch_size, 784]
    必要性:全连接层只能接受一维特征向量

x = torch.relu(self.hidden1(x))

作用:通过第一隐藏层并进行 ReLU 激活
计算过程

  1. 线性变换:x = W₁·x + b₁

  2. ReLU 激活:x[x<0]=0(保留正值,引入非线性)
    设计理由:解决线性模型无法拟合复杂模式的问题

x = torch.relu(self.hidden2(x))

作用:通过第二隐藏层并进行 ReLU 激活
计算过程

  1. 线性变换:x = W₂·x + b₂

  2. ReLU 激活:同上
    效果:进一步提取高阶特征,增加模型表达能力

x = self.out(x)

作用:通过输出层生成最终结果
注意:此处不添加激活函数
原因:分类任务通常在损失函数中结合 LogSoftmax(如 CrossEntropyLoss),logits 更灵活可控

return x

作用:返回模型输出
输出形式:原始 logits(未归一化的概率分数)
后续处理:通常会接入软最大值函数(Softmax)进行概率转换,或直接用于计算损失

def train(dataloader, model, loss_fn, optimizer):model.train()for batch, (X, y) in enumerate(dataloader):X, y = X.to(device), y.to(device)pred = model(X)loss = loss_fn(pred, y)optimizer.zero_grad()loss.backward()optimizer.step()if batch % 100 == 0:print(f"loss: {loss.item():.7f} [batch {batch}]")

第1行:model.train()

功能:将模型设置为 训练模式
底层逻辑

  • 激活模型中所有适用于训练的特殊组件。

    • 示例nn.Dropout(p=0.5) 在训练时会随机屏蔽50%的神经元,而在推理模式(model.eval())下无效。

  • 确保模型处于可学习状态(参数注册钩子启用)。
    关键性:若省略此步,模型可能因未启用必要层(如 Dropout)导致性能下降或错误。


第2行:for batch, (X, y) in enumerate(dataloader):

功能:遍历数据集的一个完整周期(Epoch)
参数解析

  • dataloader:PyTorch 的 DataLoader 对象,负责按批次加载数据。

  • enumerate():同时获取当前批次的索引 batch 和数据 (X, y)
    典型输出

    • X: 输入特征张量,形状为 [batch_size × input_dim](如图像数据为 [B, C, H, W])。

    • y: 目标标签张量,形状为 [batch_size × output_dim](分类任务通常为 one-hot 编码或类别索引)。
      设计目的:通过迭代实现 mini-batch SGD(随机梯度下降),逐步优化模型参数。


第3-4行:X, y = X.to(device), y.to(device)

功能:将数据迁移到指定计算设备(CPU/GPU)
底层逻辑

  • device 通常是预定义的变量(如 torch.device('cuda')),表示可用的硬件资源。

  • .to(device) 方法执行以下操作:

    • 数据搬运:将张量从 CPU 内存复制到 GPU 显存(若 device='cuda')。

    • 类型匹配:自动转换数据类型以匹配模型参数的类型(如 float32)。
      重要性:确保模型与数据在同一设备上运算,否则会抛出 RuntimeError: ... not on the same device
      注意:此操作仅影响张量的存储位置,不改变其数值内容。


第5行:pred = model(X)

功能:执行前向传播(Forward Propagation)
计算流程

  1. 输入 X 经模型各层依次变换(如线性层、激活函数、归一化层等)。

  2. 输出 pred 是模型对输入 X 的原始预测值(Logits),尚未应用任何激活函数。
    维度示例

  • 输入形状:[batch_size, input_dim] → 输出形状:[batch_size, num_classes](分类任务)。
    注意:此处保持线性输出,供损失函数后续处理。


第6行:loss = loss_fn(pred, y)

功能:计算当前批次的损失值
核心机制

  • loss_fn 是预定义的损失函数(如 nn.CrossEntropyLoss()nn.MSELoss())。

  • 对比模型输出 pred 与真实标签 y,量化预测误差。
    数学本质

    • 分类任务:交叉熵损失 L = -Σ(y * log(softmax(pred)))

    • 回归任务:均方误差 L = ||pred - y||²_2
      作用:为反向传播提供优化目标,指导参数更新方向。


第7行:optimizer.zero_grad()

功能:清空优化器的梯度缓存
底层逻辑

  • PyTorch 采用 累积梯度 策略,每次调用 loss.backward() 会将新梯度累加到现有梯度上。

  • 此操作将所有可训练参数的梯度置零,防止跨批次梯度混合。
    必要性:若不执行此步,梯度会指数级增长,导致参数更新异常剧烈(如爆炸性梯度)。
    内部实现:遍历模型的所有可训练参数,执行 param.grad = None


第8行:loss.backward()

功能:执行反向传播(Backward Propagation)
计算流程

  1. 根据链式法则自动计算损失对每个参数的梯度。

  2. 梯度存储在 param.grad 属性中(仅存在于 requires_grad=True 的参数)。
    技术核心:利用自动微分系统高效计算复杂计算图的梯度。
    注意:此操作 不会立即更新参数,仅计算梯度。


第9行:optimizer.step()

功能:根据梯度更新模型参数
执行过程

  1. 优化器(如 SGD、Adam)按照预设规则(学习率、动量等)更新参数。

    • SGD 示例param = param - lr * param.grad

  2. 完成一次参数更新后,梯度会被自动清零(部分优化器除外)。
    效果:使模型向损失降低的方向调整参数。
    注意:此操作是参数更新的唯一入口,必须在 zero_grad() 之后调用。


第10-11行:if batch % 100 == 0: print(f"loss: {loss.item():.7f} [batch {batch}]")

功能:定期打印训练进度
实现细节

  • batch % 100 == 0:每处理 100 个批次打印一次日志。

  • loss.item():将张量转换为 Python 标量(浮点数),用于格式化输出。
    输出示例loss: 0.1234567 [batch 100]
    用途:监控训练稳定性,辅助调试(如发现 NaN 或爆炸性损失)。

http://www.xdnf.cn/news/20295.html

相关文章:

  • 状压 dp --- 棋盘覆盖问题
  • 使用smb协议同步远程文件失败
  • javaweb(【概述和安装】【tomeat的使用】【servlet入门】).
  • SQL工具30年演进史:从Oracle到Navicat、DBeaver,再到Web原生SQLynx
  • 【开题答辩全过程】以 智能商品数据分析系统为例,包含答辩的问题和答案
  • 商密保护密码:非公知性鉴定的攻防之道
  • 介电常数何解?
  • 苍穹外卖 day03
  • 数字时代的 “安全刚需”:为什么销售管理企业都在做手机号码脱敏
  • 小学爱国教育主题班会PPT课件模板
  • MySql的事务机制
  • 让语言模型自我进化:探索 Self-Refine 的迭代反馈机制
  • 均匀圆形阵抗干扰MATLAB仿真实录与特点解读
  • 结合机器学习的Backtrader跨市场交易策略研究
  • Linux进程死锁
  • SpringBoot 中 ThreadLocal 的妙用:原理、实战与避坑指南
  • 2025年度全球人工智能驱动的营销技术格局透视:探索领先的GEO优化公司
  • 一笔成形,秒绘标准图!Pen Kit重构“自然书写”体验
  • 为什么后端接口不能直接返回数据库实体?聊聊 Product 到 ProductDetailVo 的转换逻辑
  • 轨迹文件缺少时间
  • 【HEMCO第一期】用户教程
  • 3-8〔OSCP ◈ 研记〕❘ WEB应用攻击▸REST API枚举
  • Java IO 流深度剖析:原理、家族体系与实战应用
  • 【问题解决】mac笔记本遇到鼠标无法点击键盘可响应处理办法?(Command+Option+P+R)
  • 监管罚单背后,金融机构合规管理迎大考!智慧赋能或是破局关键
  • 数据库基础操作命令总结
  • 基于单片机智能家居环境检测系统/室内环境检测设计
  • 【Python - 类库 - requests】(01)使用“requests“库的基本介绍...
  • 行业了解07:政府/公共部门
  • TVS防护静电二极管选型需要注意哪些参数?-ASIM阿赛姆