深度学习 Day12——P1实现mnist手写数字识别

news/2024/7/7 21:16:52
  • 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
  • 🍖 原作者:K同学啊 | 接辅导、项目定制

文章目录

  • 前言
  • 1 我的环境
  • 2 代码实现与执行结果
    • 2.1 前期准备
      • 2.1.1 引入库
      • 2.1.2 设置GPU(如果设备上支持GPU就使用GPU,否则使用CPU)
      • 2.1.3 导入数据
      • 2.1.4 查看数据
      • 2.1.5.数据可视化
    • 2.2 构建CNN网络模型
    • 2.3 训练模型
      • 2.3.1 训练模型
      • 2.3.2 编写训练函数
      • 2.3.3 编写测试函数
      • 2.3.4 正式训练
    • 2.4 结果可视化
  • 3 知识点详解
    • 3.1 MNIST手写数字数据集介绍
    • 3.2 Torch.NN简介
      • 3.2.1nn.Module模块概述
      • 3.2.2 Sequential类的概述
    • 3.3【Pytorch】model.train() 和 model.eval() 原理与用法
      • 3.3.1 两种模式
      • 3.3.2功能
      • 3.3.3 总结与对比
  • 总结


前言

本文将采用pytorch框架创建CNN网络,实现简单实现mnist手写数字识别。讲述实现代码与执行结果,并浅谈涉及知识点。
关键字:MNIST手写数字数据集介绍,Torch.NN简介,Pytorch】model.train() 和 model.eval() 原理与用法。

1 我的环境

  • 电脑系统:Windows 11
  • 语言环境:python 3.8.6
  • 编译器:pycharm2020.2.3
  • 深度学习环境:
    torch == 1.9.1+cu111
    torchvision == 0.10.1+cu111
  • 显卡:NVIDIA GeForce RTX 4070

2 代码实现与执行结果

2.1 前期准备

2.1.1 引入库

import torch
import torch.nn as nn
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号
plt.rcParams['figure.dpi'] = 100  # 分辨率
import torchvision
import numpy as np
from torchinfo import summary
import torch.nn.functional as F
import warnings

warnings.filterwarnings('ignore')  # 忽略一些warning内容,无需打印

2.1.2 设置GPU(如果设备上支持GPU就使用GPU,否则使用CPU)

"""前期准备-设置GPU-"""
# 如果设备上支持GPU就使用GPU,否则使用CPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)

输出

cuda

2.1.3 导入数据

'''前期工作-导入数据'''
train_ds = torchvision.datasets.MNIST('data', 
                                      train=True, 
                                      transform=torchvision.transforms.ToTensor(), # 将数据类型转化为Tensor
                                      download=True)

test_ds  = torchvision.datasets.MNIST('data', 
                                      train=False, 
                                      transform=torchvision.transforms.ToTensor(), # 将数据类型转化为Tensor
                                      download=True)
batch_size = 32

train_dl = torch.utils.data.DataLoader(train_ds, 
                                       batch_size=batch_size, 
                                       shuffle=True)

test_dl  = torch.utils.data.DataLoader(test_ds, 
                                       batch_size=batch_size)

2.1.4 查看数据

'''前期工作-查看数据'''                                       
# 取一个批次查看数据格式
# 数据的shape为:[batch_size, channel, height, weight]
# 其中batch_size为自己设定,channel,height和weight分别是图片的通道数,高度和宽度。
imgs, labels = next(iter(train_dl))  # 通过 iter(train_dl) 创建一个数据迭代器,然后使用 next(train_dl) 从训练数据加载一个批次的图像和对应的标签。
# images包含了一批图像,labels 包含了这些图像的类别标签。
print(imgs.shape)

输出

torch.Size([32, 1, 28, 28])

2.1.5.数据可视化

'''前期工作-数据可视化'''
# 指定图片大小,图像大小为20宽、5高的绘图(单位为英寸inch)
plt.figure(figsize=(20, 5))
for i, img in enumerate(imgs[:20]):
    # 维度缩减,去除所有维度为1的维度
    npimg = np.squeeze(img.numpy())
    # 将整个figure分成2行10列,绘制第i+1个子图。
    plt.subplot(2, 10, i + 1)
    plt.imshow(npimg, cmap=plt.cm.binary)  # 使用matlablib库cm子库中的binary颜色映射,讲图像渲染维黑白色
    plt.axis('off')  # 取消坐标轴显示
plt.show()  # 如果你使用的是Pycharm编译器,请加上这行代码

在这里插入图片描述

2.2 构建CNN网络模型

在这里插入图片描述

"""构建CNN网络"""
num_classes = 10  # 图片的类别数


class Model(nn.Module):
    def __init__(self):
        super().__init__()
        # 特征提取网络
        self.conv1 = nn.Conv2d(1, 32, kernel_size=3)  # 第一层卷积,卷积核大小为3*3
        self.pool1 = nn.MaxPool2d(2)  # 设置池化层,池化核大小为2*2
        self.conv2 = nn.Conv2d(32, 64, kernel_size=3)  # 第二层卷积,卷积核大小为3*3
        self.pool2 = nn.MaxPool2d(2)

        # 分类网络
        self.fc1 = nn.Linear(1600, 64)
        self.fc2 = nn.Linear(64, num_classes)

    # 前向传播
    def forward(self, x):
        x = self.pool1(F.relu(self.conv1(x)))
        x = self.pool2(F.relu(self.conv2(x)))

        x = torch.flatten(x, start_dim=1)

        x = F.relu(self.fc1(x))
        x = self.fc2(x)

        return x


# 将模型转移到GPU中(我们模型运行均在GPU中进行)
model = Model().to(device)
summary(model)

输出

=================================================================
Layer (type:depth-idx)                   Param #
=================================================================
Model                                    --
├─Conv2d: 1-1                            320
├─MaxPool2d: 1-2                         --
├─Conv2d: 1-3                            18,496
├─MaxPool2d: 1-4                         --
├─Linear: 1-5                            102,464
├─Linear: 1-6                            650
=================================================================
Total params: 121,930
Trainable params: 121,930
Non-trainable params: 0
=================================================================

2.3 训练模型

2.3.1 训练模型

"""训练模型--设置超参数"""
loss_fn = nn.CrossEntropyLoss()  # 创建损失函数,计算实际输出和真实相差多少,交叉熵损失函数,事实上,它就是做图片分类任务时常用的损失函数
learn_rate = 1e-2  # 学习率
opt = torch.optim.SGD(model.parameters(), lr=learn_rate)  # 作用是定义优化器,用来训练时候优化模型参数;其中,SGD表示随机梯度下降,用于控制实际输出y与真实y之间的相差有多大

2.3.2 编写训练函数

"""训练模型--编写训练函数"""
# 训练循环
def train(dataloader, model, loss_fn, optimizer):
    size = len(dataloader.dataset)  # 训练集的大小,一共60000张图片
    num_batches = len(dataloader)  # 批次数目,1875(60000/32)

    train_loss, train_acc = 0, 0  # 初始化训练损失和正确率

    for X, y in dataloader:  # 加载数据加载器,得到里面的 X(图片数据)和 y(真实标签)
        X, y = X.to(device), y.to(device) # 用于将数据存到显卡

        # 计算预测误差
        pred = model(X)  # 网络输出
        loss = loss_fn(pred, y)  # 计算网络输出和真实值之间的差距,targets为真实值,计算二者差值即为损失

        # 反向传播
        optimizer.zero_grad()  # 清空过往梯度
        loss.backward()  # 反向传播,计算当前梯度
        optimizer.step()  # 根据梯度更新网络参数

        # 记录acc与loss
        train_acc += (pred.argmax(1) == y).type(torch.float).sum().item()
        train_loss += loss.item()

    train_acc /= size
    train_loss /= num_batches

    return train_acc, train_loss

2.3.3 编写测试函数

"""训练模型--编写测试函数"""
# 测试函数和训练函数大致相同,但是由于不进行梯度下降对网络权重进行更新,所以不需要传入优化器
def test(dataloader, model, loss_fn):
    size = len(dataloader.dataset)  # 测试集的大小,一共10000张图片
    num_batches = len(dataloader)  # 批次数目,313(10000/32=312.5,向上取整)
    test_loss, test_acc = 0, 0

    # 当不进行训练时,停止梯度更新,节省计算内存消耗
    with torch.no_grad(): # 测试时模型参数不用更新,所以 no_grad,整个模型参数正向推就ok,不反向更新参数
        for imgs, target in dataloader:
            imgs, target = imgs.to(device), target.to(device)

            # 计算loss
            target_pred = model(imgs)
            loss = loss_fn(target_pred, target)

            test_loss += loss.item()
            test_acc += (target_pred.argmax(1) == target).type(torch.float).sum().item()#统计预测正确的个数

    test_acc /= size
    test_loss /= num_batches

    return test_acc, test_loss

2.3.4 正式训练

"""训练模型--正式训练"""
epochs = 5
train_loss = []
train_acc = []
test_loss = []
test_acc = []

for epoch in range(epochs):
    model.train()
    epoch_train_acc, epoch_train_loss = train(train_dl, model, loss_fn, opt)

    model.eval()
    epoch_test_acc, epoch_test_loss = test(test_dl, model, loss_fn)

    train_acc.append(epoch_train_acc)
    train_loss.append(epoch_train_loss)
    test_acc.append(epoch_test_acc)
    test_loss.append(epoch_test_loss)

    template = ('Epoch:{:2d}, Train_acc:{:.1f}%, Train_loss:{:.3f}, Test_acc:{:.1f}%,Test_loss:{:.3f}')
    print(template.format(epoch + 1, epoch_train_acc * 100, epoch_train_loss, epoch_test_acc * 100, epoch_test_loss))
print('Done')

输出

Epoch: 1, Train_acc:79.5%, Train_loss:0.709, Test_acc:92.7%,Test_loss:0.243
Epoch: 2, Train_acc:94.3%, Train_loss:0.188, Test_acc:96.2%,Test_loss:0.125
Epoch: 3, Train_acc:96.4%, Train_loss:0.121, Test_acc:97.3%,Test_loss:0.088
Epoch: 4, Train_acc:97.2%, Train_loss:0.093, Test_acc:97.6%,Test_loss:0.077
Epoch: 5, Train_acc:97.6%, Train_loss:0.078, Test_acc:98.0%,Test_loss:0.064
Done

2.4 结果可视化

"""训练模型--结果可视化"""
epochs_range = range(epochs)

plt.figure(figsize=(12, 3))
plt.subplot(1, 2, 1)

plt.plot(epochs_range, train_acc, label='Training Accuracy')
plt.plot(epochs_range, test_acc, label='Test Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, train_loss, label='Training Loss')
plt.plot(epochs_range, test_loss, label='Test Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()

在这里插入图片描述

3 知识点详解

3.1 MNIST手写数字数据集介绍

MNIST手写数字数据集来源于是美国国家标准与技术研究所,是著名的公开数据集之一。数据集中的数字图片是由250个不同职业的人纯手写绘制,数据集获取的网址为:http://yann.lecun.com/exdb/mnist/(下载后需解压)。我们一般会采用(train_images, train_labels), (test_images, test_labels) = datasets.mnist.load_data()这行代码直接调用,这样就比较简单MNIST手写数字数据集中包含了70000张图片,其中60000张为训练数据,10000为测试数据,70000张图片均是2828,如果我们把每一张图片中的像素转换为向量,则得到长度为2828=784的向量。因此我们可以把训练集看成是一个[60000,784]的张量,第一个维度表示图片的索引,第二个维度表示每张图片中的像素点。而图片里的每个像素点的值介于0-1之间。
在pytorch下可以直接调用torchvision.datasets里面的MNIST数据集(这是官方写好的数据集类)

train = torchvision.datasets.MNIST(root='./data/',train=True, transform= transforms.ToTensor())

作用
从 torchvision 中加载 MNIST 数据集的训练集
参数

  • root=‘./data’: 数据集将被下载并保存在当前工作目录下的 ‘data’ 子目录中
  • train=True: 加载训练集
  • download=True: 如果数据集不存在,则下载数据集
  • transform=transforms.ToTensor(): #接收PIL图片并返回转换后版本图片的转换函数,这里为将图像转换为 PyTorch 的 Tensor 格式
    返回值为一个元组(train_data,train_target)(这个类使用的时候也有坑,必须用train[i]索引才能使用 transform功能),一般是与torch.utils.data.DataLoader配合使用
dataloader = DataLoader(train, batch_size=50,shuffle=True, num_workers=4)
for step, (x, y) in enumerate(dataloader):
 b_x = x.shape
 b_y = y.shape
 print 'Step: ', step, '| train_data的维度' ,b_x,'| train_target的维度',b_y

作用
创建一个 DataLoader 对象,用于对数据进行批量加载和处理
参数

  • trainset: 要加载的数据集
  • batch_size=4: 每个批次包含的图像样本数量
  • shuffle=True: 打乱数据,以便在每个 epoch 中随机访问样本
  • num_workers=4:并行处理数

3.2 Torch.NN简介

torch.nn 是 PyTorch 中用于构建神经网络的模块。它提供了一组类和函数,用于定义、训练和评估神经网络模型。
torch.nn 模块的核心是 nn.Module 类,它是所有神经网络模型的基类。在Containers中通过继承 nn.Module 类,您可以创建自己的神经网络模型,并定义模型的结构和操作。
以下是 torch.nn 模块中常用的一些类和函数:

  • nn.Linear: 线性层,用于定义全连接层,可以起到特征提取器的作用,最后一层的全连接层也可以认为是输出层,传入参数为输入特征数和输出特征数(输入特征数由特征提取网络计算得到,如果不会计算可以直接运行网络,报错中会提示输入特征数的大小,下方网络中第一个全连接层的输入特征数为1600。
  • nn.Conv2d: 二维卷积层,用于处理图像数据。
  • nn.ReLU: ReLU 激活函数。
  • nn.Sigmoid: Sigmoid 激活函数。
  • nn.Dropout: Dropout 层,用于正则化和防止过拟合。
  • nn.CrossEntropyLoss: 交叉熵损失函数,通常用于多类别分类问题。
  • nn.MSELoss: 均方误差损失函数,通常用于回归问题。
  • nn.Sequential: 顺序容器,用于按顺序组合多个层,在初始化阶段就设定好网络结构,不需要在前向传播中重新写一遍。
    使用 torch.nn 模块,您可以创建自定义的神经网络模型,并使用 PyTorch 提供的优化器(如 torch.optim)和损失函数来训练和优化模型。

3.2.1nn.Module模块概述

nn.Module类的基本定义
在定义自已的网络的时候,需要继承nn.Module类,并重新实现构造函数__init__()和forward这两个方法。在构造函数__init__()中使用super(Model, self).init()来调用父类的构造函数,forward方法是必须要重写的,它是实现模型的功能,实现各个层之间的连接关系的核心。
1.一般把网络中具有可学习参数的层(如全连接层、卷积层)放在构造函数__init__()中。
2.一般把不具有可学习参数的层(如ReLU、dropout)可放在构造函数中,也可不放在构造函数中(在forward中使用nn.functional来调用)。
示例1:将具有可学习参数层和不具有可学习参数层均放在构造函数中

import torch.nn as nn
import torch.nn.functional as F
class Model(nn.Module):
    def __init__(self):
        super(Model, self).__init__()   # 调用父类的构造函数
        self.con2v = nn.Conv2d(1, 3, 3, 1)
        self.relu = nn.ReLU()
        self.max_pooling = nn.MaxPool2d(2, 1)
    def forward(self, x):
        x = self.con2v(x)
        x = self.relu(x)
        x = self.max_pooling(x)
        return x
model = Model()
print(model)
'''  可看到输出的模型结构
Model(
  (con2v): Conv2d(1, 3, kernel_size=(3, 3), stride=(1, 1))
  (relu): ReLU()
  (max_pooling): MaxPool2d(kernel_size=2, stride=1, padding=0, dilation=1, ceil_mode=False)
)
'''

示例2:把不具有可学习参数的层不放在构造函数中(在forward中使用nn.functional调用)

import torch.nn as nn
import torch.nn.functional as F
class Model(nn.Module):
    def __init__(self):
        super(Model, self).__init__()   # 调用父类的构造函数
        self.con2v = nn.Conv2d(1, 3, 3, 1)
        self.max_pooling = nn.MaxPool2d(2, 1)
    def forward(self, x):
        x = self.con2v(x)
        x = F.relu(x)
        x = self.max_pooling(x)
        return x
model = Model()
print(model)
''' 可看到输出的模型结构
Model(
  (con2v): Conv2d(1, 3, kernel_size=(3, 3), stride=(1, 1))
  (max_pooling): MaxPool2d(kernel_size=2, stride=1, padding=0, dilation=1, ceil_mode=False)
)
'''

由此可看出不具有可学习参数层没有放在构造函数里面,那么这些层就不会出现在model中。
也就是在构造函数__init__()中只是定义了模型的结构,而在forward方法中实现了模型中的所有层的连接。
3.只要在nn.Module中定义了forward函数,backward函数就会被自动实现(利用Autograd)。而且一般不是显式的调用forward(layer.forward), 而是layer(input), 会自执行forward()。

3.2.2 Sequential类的概述

nn.Sequential的定义:一个有顺序容器,神经网络模块将按照构造函数中传递的顺序添加到该容器中。此外,也可以传入一个有序的模块字典。

Sequenrial类实现了整数索引,每一个层是没有名称的,默认是以0、1、2…这样的index来命名,可以使用model[index]这样的方式获取一个层,并不能够通过名称(如model[“Conv2d”])来获取层。
示例如下:

import torch.nn as nn
class Model(nn.Module):
    def __init__(self):
        super(Model, self).__init__()   # 调用父类的构造函数
        self.model1= nn.Sequential(
            nn.Conv2d(1, 3, 3, 1),
            nn.ReLU(),
            nn.Conv2d(3, 9, 3, 1),
            nn.MaxPool2d(2, 1)
        )
    def forward(self, x):
        x = self.struct(x)
        return x
model = Model()
print(model)
print(model.model1[2])  # 通过索引获取第几个层

'''  输出结果
Model(
  (model1): Sequential(
    (0): Conv2d(1, 3, kernel_size=(3, 3), stride=(1, 1))
    (1): ReLU()
    (2): Conv2d(3, 9, kernel_size=(3, 3), stride=(1, 1))
    (3): MaxPool2d(kernel_size=2, stride=1, padding=0, dilation=1, ceil_mode=False)
  )
)
Conv2d(3, 9, kernel_size=(3, 3), stride=(1, 1))
'''

Sequential的三种包装方式
示例如下:

import torch.nn as nn
from collections import OrderedDict
class Model(nn.Module):
    def __init__(self):
        super(Model, self).__init__()   # 调用父类的构造函数
        self.model1 = nn.Sequential(    # 方式一
            nn.Conv2d(1, 3, 3, 1),
            nn.ReLU()
        )
        self.model2 = nn.Sequential(   # 方式二
            OrderedDict([
                ('conv1', nn.Conv2d(1, 3, 3, 1)),
                ('relu1', nn.ReLU())
            ])
        )
        self.model3 = nn.Sequential()  # 方式三
        self.model3.add_module('conv1', nn.Conv2d(1, 3, 3, 1)),
        self.model3.add_module('relu1', nn.ReLU())
    def forward(self, x):
        x = self.model1(x)
        x = self.model2(x)
        x = self.model3(x)
        return x
model = Model()
print(model)

'''  输出结果
Model(
  (model1): Sequential(
    (0): Conv2d(1, 3, kernel_size=(3, 3), stride=(1, 1))
    (1): ReLU()
  )
  (model2): Sequential(
    (conv1): Conv2d(1, 3, kernel_size=(3, 3), stride=(1, 1))
    (relu1): ReLU()
  )
  (model3): Sequential(
    (conv1): Conv2d(1, 3, kernel_size=(3, 3), stride=(1, 1))
    (relu1): ReLU()
  )
)
'''

参考链接:
nn.Module模块概述
神经网络的基本框架的搭建-nn.Module

3.3【Pytorch】model.train() 和 model.eval() 原理与用法

3.3.1 两种模式

pytorch可以给我们提供两种方式来切换训练和评估(推断)的模式,分别是:model.train() 和 model.eval()。
一般用法是:在训练开始之前写上 model.trian() ,在测试时写上 model.eval() 。

3.3.2功能

1. model.train()
在使用 pytorch 构建神经网络的时候,训练过程中会在程序上方添加一句model.train(),作用是 启用 batch normalization 和 dropout 。
如果模型中有BN层(Batch Normalization)和 Dropout ,需要在 训练时 添加 model.train()。
model.train() 是保证 BN 层能够用到 每一批数据 的均值和方差。对于 Dropout,model.train() 是 随机取一部分 网络连接来训练更新参数。

2. model.eval()
model.eval()的作用是 不启用 Batch Normalization 和 Dropout。
如果模型中有 BN 层(Batch Normalization)和 Dropout,在 测试时 添加 model.eval()。
model.eval() 是保证 BN 层能够用 全部训练数据 的均值和方差,即测试过程中要保证 BN 层的均值和方差不变。对于 Dropout,model.eval() 是利用到了 所有 网络连接,即不进行随机舍弃神经元。

为什么测试时要用 model.eval() ?
训练完 train 样本后,生成的模型 model 要用来测试样本了。在 model(test) 之前,需要加上model.eval(),否则的话,有输入数据,即使不训练,它也会改变权值。这是 model 中含有 BN 层和 Dropout 所带来的的性质。

eval() 时,pytorch 会自动把 BN 和 DropOut 固定住,不会取平均,而是用训练好的值。
不然的话,一旦 test 的 batch_size 过小,很容易就会被 BN 层导致生成图片颜色失真极大。
eval() 在非训练的时候是需要加的,没有这句代码,一些网络层的值会发生变动,不会固定,你神经网络每一次生成的结果也是不固定的,生成质量可能好也可能不好。

也就是说,测试过程中使用model.eval(),这时神经网络会 沿用 batch normalization 的值,而并不使用 dropout。

3.3.3 总结与对比

如果模型中有 BN 层(Batch Normalization)和 Dropout,需要在训练时添加 model.train(),在测试时添加 model.eval()。
其中 model.train() 是保证 BN 层用每一批数据的均值和方差,而 model.eval() 是保证 BN 用全部训练数据的均值和方差;
而对于 Dropout,model.train() 是随机取一部分网络连接来训练更新参数,而 model.eval() 是利用到了所有网络连接。

参考链接:【Pytorch】model.train() 和 model.eval() 原理与用法

总结

通过本文的学习,了解了pytorch网络模型如何创建、训练与测试。


http://lihuaxi.xjx100.cn/news/1885184.html

相关文章

Spring框架中的五种常用设计模式

1、单例模式 Spring 的 Bean 默认是单例模式,通过 Spring 容器管理 Bean 的⽣命周期,保证每个 Bean 只被 创建⼀次,并在整个应⽤程序中重用。 2.工厂模式 Spring 使⽤⼯⼚模式通过 BeanFactory 和 ApplicationContext 创建并管理 Bean 对象…

第三十二章 控制到 XML 模式的映射 - %ListOfObjects

文章目录 第三十二章 控制到 XML 模式的映射 - %ListOfObjects%ArrayOfObjects 第三十二章 控制到 XML 模式的映射 - %ListOfObjects 本部分显示了从支持 XML 的类生成的 XML架构的一部分,该类包含定义为 %ListOfObjects 的属性。例如,考虑以下属性定义…

IPC之六:使用 POSIX 信号量解决经典的‘生产者-消费者问题‘

IPC 是 Linux 编程中一个重要的概念,IPC 有多种方式,本文主要介绍信号量(Semaphores),尽管信号量被认为是 IPC 的一种方式,但实际上通常把信号量用于进程间同步或者资源互斥,和共享内存(Shared Memory)配合使用,可以实现完美的进程间通信;Linux 既支持 UNIX SYSTEM V 的…

Mybatis之Mybatis的各种查询功能和特殊SQL的执行

学习的最大理由是想摆脱平庸,早一天就多一份人生的精彩;迟一天就多一天平庸的困扰。各位小伙伴,如果您: 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持,想组团高效学习… 想写博客但无从下手,急需…

代码随想录27期|Python|Day13|栈与队列|239. 滑动窗口最大值 (一刷至少需要理解思路)|347.前 K 个高频元素 (一刷至少需要理解思路)

239. 滑动窗口最大值 单调队列 滑动窗口中的队列一直保持出口大,入口小的顺序。(图:代码随想录) 1、每次有新的元素进入(也就是滑动窗口移动后),都需要先和入口的元素比较大小,如果…

运筹学经典问题(二):最短路问题

问题描述 给定一个图(有向图或无向图) G ( V , E ) G (V, E) G(V,E), V V V是图中点的集合, E E E是图中边的集合,图中每条边 ( i , j ) ∈ E (i, j) \in E (i,j)∈E都对应一个权重 c i j c_{ij} cij​(…

Altman作了多少恶?排挤首席科学家出GPT5开发、离间董事会、PUA员工

在山姆奥特曼(Sam Altman)被OpenAI董事会突然解职后的几天里,这个消息在科技圈引发轰动,该公司内部员工和许多科技界人士甚至将此举比作一场政变。 奥特曼被解雇后立即传出的说法是,OpenAI的广大员工都很喜欢他&#x…

系统日志分析

文章目录 系统日志分析1. Windows系统日志分析1.1 日志位置1.1.1 系统日志 System.evtx1.1.2 应用程序日志 Application.evtx1.1.3 安全日志 Security.evtx 1.2 打开日志审计策略功能1.3 查看系统日志1.4 事件日志分析1.4.1 日志清除记录——ID:11021.4.2 出站入站记…