深度学习笔记1——CNN识别黑白手写数字

news2024/11/18 14:38:04

文章目录

  • 摘要
  • 手写数字数据集(MNIST)
  • 卷积神经网络(Convolution Neural Network, CNN)
    • 模型架构搭建
    • Softmax函数和CrossEntropy损失函数
    • Adam 优化器
  • 构造数据迭代器
  • 训练、验证、测试模型
  • 训练结果可视化

摘要

本文将介绍CNN的开山之作——LeNet-5卷积神经网络;并且实现导入MNIST手写数字识别数据集,对LeNet-5模型进行训练、验证和测试,最后对训练过程的损失、准确率变化进行可视化。

  • 参考文献:
    1. LeNet-5:《Gradient-Based Learning Applied to Document Recognition》
    2. Adam:《Adam: A Method for Stochastic Optimization》
  • 数据集(MNIST):THE MNIST DATABASE
  • 完整代码(Github):MNIST_LeNet-5_PyTorch.py

手写数字数据集(MNIST)

MNIST数据集是28x28尺寸的单通道手写数字数据集,由60000张训练+10000张测试图片组成,示例图片如下:
在这里插入图片描述
官网下载的数据集源文件,解压后由4个ubyte文件组成,如下:

文件名文件大小说明
t10k-images-idx3-ubyte7,657KB测试10000张图片矩阵数据
t10k-labels-idx1-ubyte10KB测试10000张图片对应标签数据
train-images-idx3-ubyte45,938KB训练60000张图片矩阵数据
train-labels-idx1-ubyte59KB训练60000张图片对应标签数据

这种类型文件可以通过structnumpy模块进行读取,案例如下:

import struct
import numpy as np

def load_byte(file, cache='>IIII', dtp=np.uint8):
    """ 
    读取 ubyte 格式数据
    Args:
        file (str): 文件路径的字符串
        cache (str): 缓存字符
        dtp (type): 矩阵类型

    Returns:
        np.array
    """
    iter_num = cache.count('I') * 4
    with open(file, 'rb') as f:
        magic = struct.unpack(cache, f.read(iter_num))
        data = np.fromfile(f, dtype=dtp)
    return data


# 读取出来的均是Numpy矩阵,可以通过dtype指定矩阵类型
train_data = load_byte("train-images-idx3-ubyte")  # shape(47040000,)
test_data = load_byte("t10k-images-idx3-ubyte")  # shape(60000,)
train_label = load_byte("train-labels-idx1-ubyte", ">II")  # shape(7840000,)
test_label = load_byte("t10k-labels-idx1-ubyte", ">II")  # shape(10000,)

根据struct解包后,转换成numpy的矩阵格式,可以根据dtype传参转换成整数或者浮点数类型的矩阵。

卷积神经网络(Convolution Neural Network, CNN)

CNN是Yann Lecun等人于1998年投稿的《Gradient-Based Learning Applied to Document Recognition》中首次提出使用神经网络架构,其网络结构名称为LeNet-5,用于识别32x32手写数字黑白图像。

网络中采用了Conv2D卷积+Subsampliing下采样的组合提取图像特征,最后采用MLP(Multi-Layer Perceptrons)多层感知机的形式,将卷积+下采样得到的特征通过三个线性层映射到输出的10类上。整体结构如下图:
在这里插入图片描述

模型架构搭建

在PyTorch框架中,可以采用MaxPool2d代替Subsampling实现下采样操作,即Conv2d+MaxPool2d的组合。由于文中提出的模型结构输入图片是32x32,而MNIST数据集图片是28x28,因此需要对第一个Conv2d卷积层进行调整,输入通道为1,添加一个padding,使得后续的输出能够适应LeNet-5结构输出。搭建PyTorch代码如下:

import torch
import torch.nn as nn
import torchsummary


class Net(nn.Module):
    """ CNN 卷积网络在 MNIST 28x28 手写数字灰色图像上应用版本 """

    def __init__(self):
        super(Net, self).__init__()
        # 卷积层 #
        self.conv1 = nn.Conv2d(in_channels=1, out_channels=6,  # 输入通道由 3 调整为 1
                               kernel_size=5, stride=1, padding=2)  # padding 使得模型与原文提供的 32x32 结构保持不变
        self.conv2 = nn.Conv2d(in_channels=6, out_channels=16,
                               kernel_size=5, stride=1)
        # 池化层 #
        self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
        self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)
        # 全连接层 #
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)
        # 激活函数 #
        self.relu = nn.ReLU()

    def forward(self, x):
        # 卷积层 1 #
        out = self.conv1(x)
        out = self.relu(out)
        out = self.pool1(out)
        # 卷积层 2 #
        out = self.conv2(out)
        out = self.relu(out)
        out = self.pool2(out)
        # 全连接层 #
        out = out.view(out.size(0), -1)
        out = self.fc1(out)
        out = self.relu(out)
        out = self.fc2(out)
        out = self.relu(out)
        out = self.fc3(out)

        return out


model = Net()
torchsummary.summary(net, input_size=(1, 28, 28), device="cpu")  # 采用 keras 的方式顺序打印模型结构

可以调用torchsummary输出keras风格的模型结构表:

----------------------------------------------------------------
        Layer (type)               Output Shape         Param #
================================================================
            Conv2d-1            [-1, 6, 28, 28]             156
              ReLU-2            [-1, 6, 28, 28]               0
         MaxPool2d-3            [-1, 6, 14, 14]               0
            Conv2d-4           [-1, 16, 10, 10]           2,416
              ReLU-5           [-1, 16, 10, 10]               0
         MaxPool2d-6             [-1, 16, 5, 5]               0
            Linear-7                  [-1, 120]          48,120
              ReLU-8                  [-1, 120]               0
            Linear-9                   [-1, 84]          10,164
             ReLU-10                   [-1, 84]               0
           Linear-11                   [-1, 10]             850
================================================================
Total params: 61,706
Trainable params: 61,706
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.00
Forward/backward pass size (MB): 0.11
Params size (MB): 0.24
Estimated Total Size (MB): 0.35
----------------------------------------------------------------

Softmax函数和CrossEntropy损失函数

在实现多分类任务时,一般情况下会对最后输出概率的结果进行Softmax转换,目的是为了将线性层输出的结果进行一种类似”归一、标准化“的处理,为每个输出分类的结果赋予一个概率值,公式如下:

S o f t m a x ( z i ) = e z i ∑ c = 1 C e z c \rm{Softmax} (z_i) = \frac{ e ^ {z_i} } { \sum ^ C _ {c=1} e ^ {z_c} } Softmax(zi)=c=1Cezcezi

其中, C C C表示输出的类别总数量; z i ∈ R C z_i \in R^{C} ziRC表示第 i i i个类别的输出结果。

在损失函数上,最常见的是均方误差MSE(Mean Squared Error)和交叉熵(Cross Entropy)。在本文的MNIST手写数字识别任务,是属于多分类任务;因此为了使模型更好训练、收敛,采用了多分类交叉熵损失函数,具体公式如下:

C r o s s E n t r o p y ( y , y ^ ) = − ∑ i = 1 C y i log ⁡ ( y ^ i ) = − log ⁡ ( y ^ c ) \rm{CrossEntropy} (y, \hat{y}) = - \sum ^ {C} _ {i=1} y_i \log(\hat{y}_i) = - \log (\hat{y}_c) CrossEntropy(y,y^)=i=1Cyilog(y^i)=log(y^c)

其中, C C C表示输出的类别总数量; y ∈ R C y \in R^{C} yRC y ^ ∈ R C \hat{y} \in R^{C} y^RC表示原始类别概率和预测类别概率向量; y ^ c \hat{y}_c y^c表示当前图片原始类别标签对应的预测类别概率。在多分类任务中,每一条数据最后都会输出全部类别的概率,因此原始数据会进行独热编码(One-Hot)的转换,示例如下图:

在这里插入图片描述
因此计算交叉熵时,比如数字1的图片,对于0,2,3,4,5,6,7,8,9这些类别位置的概率均为0,因此只需要计算数字1类别的预测概率对数负数值即可,对每一条数据同上述操作。

Adam 优化器

在神经网络模型训练过程中,都会基于随机梯度下降法(Single Gradient Descent, SGD)进行反馈传播。为了加快模型的收敛速度、减少训练时间,采用结合AdaGrad和RMSProp两种算法优点的Adam算法,详细原理后期再开专栏介绍。

构造数据迭代器

在PyTorch中,为了实现更快的训练过程,使用torch.utils.data.DataLoader构造批次数据迭代器对象;批次的原理是将数据合并生成一个新维度,从而实现批量训练。DataLoader可以传入torch.utils.data.Dataset对象,在torchvision.dataset中存在一个针对MNISTDataset对象,通过roottrain传参来快速构造训练、测试数据迭代器,代码如下:

from torchvision import transforms
from torchvision.datasets import MNIST
import torch.utils.data as data
import numpy as np

num_channels = 1  # 图像通道数
image_size = 28  # 图像尺寸
num_workers = 0  # 读取图片进程数
valid_split = .2  # 在训练集上划出验证集的尺寸0
# 训练配置
batch_size = 512  # 批次大小

'''++++++++++++++++++++++
@@@ 数据预处理
++++++++++++++++++++++'''

transform = transforms.Compose([
    transforms.ToTensor(),  # 转换为张量
])
# 读取图片为数据集
train_data = MNIST(root=data_path, train=True, transform=transform)
test_data = MNIST(root=data_path, train=False, transform=transform)
# 训练 data 划分成 train 和 validation
valid_size = int(len(train_data) * valid_split)
indices = np.arange(len(train_data))
np.random.shuffle(indices)
# 构造迭代器
train_db = data.DataLoader(dataset=train_data, batch_size=batch_size, sampler=indices[:-valid_size])
val_db = data.DataLoader(dataset=train_data, batch_size=batch_size, sampler=indices[-valid_size:])
test_db = data.DataLoader(dataset=test_data, batch_size=batch_size)
print('Train: (%i, %i, %i, %i)' % (len(indices[:-valid_size]), num_channels, image_size, image_size))
print('Valid: (%i, %i, %i, %i)' % (len(indices[-valid_size:]), num_channels, image_size, image_size))
print('Test: (%i, %i, %i, %i)' % (len(test_data), num_channels, image_size, image_size))

# 查看单个案例
for (x, y) in train_db:
	print(x.shape, y.shape)
	break
torch.Size([512, 1, 28, 28]) torch.Size([512])

需要注意,root传入的地址必须包含MNIST文件夹,结构如下:

MNIST.
├─raw
  └─t10k-images-idx3-ubyte
  └─t10k-labels-idx1-ubyte
  └─train-images-idx3-ubyte
  └─train-labels-idx1-ubyte

如果想要通过网络下载,可以在MNIST()中加入download=True传参,届时会自动从官网下载数据集的tar.gz压缩包并且自动解压。

训练、验证、测试模型

对于模型训练、验证和测试过程,这里是仿照keras进度条,使用tqdm进度条模块编写了训练过程的变化过程。在每次epoch训练的最后一个batch训练结束后对模型进行验证。完成训练和验证过程最后再对模型进行测试,该部分代码如下:

from tqdm import tqdm
import time

epochs = 20  # 周期
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')

'''+++++++++++++++++++++++
@@@ 模型训练和验证
+++++++++++++++++++++++'''

net.to(device)
criterion = nn.CrossEntropyLoss()  # 设置损失函数
optimizer = torch.optim.Adam(net.parameters())  # 配置优化器

# 初始化损失和准确率
train_history = {"loss": {"train": [[] for _ in range(epochs)], "val": [[] for _ in range(epochs)]},
                 "acc": {"train": [[] for _ in range(epochs)], "val": [[] for _ in range(epochs)]}}

st = time.time()
for epoch in range(epochs):
    with tqdm(total=len(train_db), desc=f'Epoch {epoch + 1}/{epochs}') as pbar:
        for step, (x, y) in enumerate(train_db):
            net.train()  # 标记模型开始训练,此时权重可变
            x, y = x.to(device), y.to(device)  # 转移张量至 GPU
            output = net(x)  # 将 x 送进模型进行推导

            # 计算损失
            loss = criterion(output, y)  # 计算交叉熵损失
            optimizer.zero_grad()  # 清空梯度
            loss.backward()  # 反向传播
            optimizer.step()  # 一步随机梯度下降算法

            # 计算准确率
            prediction = torch.softmax(output, dim=1).argmax(dim=1)  # 将预测值转换成标签

            # 记录损失和准确率
            train_history["loss"]["train"][epoch].append(loss.item())
            train_history["acc"]["train"][epoch].append(((prediction == y).sum() / y.shape[0]).item())

            # 进度条状态更新
            pbar.update(1)
            pbar.set_postfix({"loss": "%.4f" % np.mean(train_history["loss"]["train"][epoch]),
                              "acc": "%.2f%%" % (np.mean(train_history["acc"]["train"][epoch]) * 100)})

        # 每一个 epoch 训练结束后进行验证
        net.eval()  # 标记模型开始验证,此时权重不可变
        for x, y in val_db:
            x, y = x.to(device), y.to(device)
            output = net(x)
            loss_val = criterion(output, y).item() / len(val_db)

            prediction = torch.softmax(output, dim=1).argmax(dim=-1)
            # 记录验证损失和准确率
            train_history["loss"]["val"][epoch].append(loss_val)
            train_history["acc"]["val"][epoch].append(((prediction == y).sum() / y.shape[0]).item())

            # 更新进度条
            pbar.set_postfix({"loss": "%.4f" % np.mean(train_history["loss"]["train"][epoch]),
                              "acc": "%.2f%%" % (np.mean(train_history["acc"]["train"][epoch]) * 100),
                              'val_loss': "%.4f" % np.mean(train_history["loss"]["val"][epoch]),
                              "val_acc": "%.2f%%" % (np.mean(train_history["acc"]["val"][epoch]) * 100)})

et = time.time()
time.sleep(0.1)
print('Time Taken: %d seconds' % (et - st))  # 69

'''+++++++++++++++++++
@@@ 模型测试
+++++++++++++++++++'''

print('Test data in model...')
correct, total, loss = 0, 0, 0
per_time = []  # 计算每个
net.eval()
with tqdm(total=len(test_db)) as pbar:
    for step, (x, y) in enumerate(test_db):
        x, y = x.to(device), y.to(device)
        st = time.perf_counter()
        output = net(x)
        torch.cuda.synchronize()
        et = time.perf_counter()
        per_time.append(et - st)
        loss += float(criterion(output, y)) / len(test_db)
        prediction = torch.softmax(output, dim=1).argmax(dim=1)
        correct += int((prediction == y).sum())
        total += y.shape[0]

        pbar.update(1)
        pbar.set_postfix({'loss': '%.4f' % loss,
                          'accuracy': '%.2f%%' % (correct / total * 100),
                          'per_time': '%.4fs' % (et - st)})

可以发现,计算损失时不需要对输出概率进行Softmax转换,因为nn.CrossEntropyLoss会自动在内部进行Softmax转换,因此只需要在计算准确率时进行处理即可,一定程度上也能减少模型推理时间(大雾)。

训练过程中命令行窗口将输出以下进度条记录形式:

Epoch 1/20: 100%|██████████| 94/94 [00:03<00:00, 26.21it/s, loss=1.0145, acc=68.89%, val_loss=0.0165, val_acc=88.14%]
Epoch 2/20: 100%|██████████| 94/94 [00:03<00:00, 27.80it/s, loss=0.2832, acc=91.37%, val_loss=0.0095, val_acc=93.23%]
Epoch 3/20: 100%|██████████| 94/94 [00:03<00:00, 28.02it/s, loss=0.1792, acc=94.60%, val_loss=0.0064, val_acc=95.41%]
Epoch 4/20: 100%|██████████| 94/94 [00:03<00:00, 27.47it/s, loss=0.1261, acc=96.10%, val_loss=0.0051, val_acc=96.21%]
Epoch 5/20: 100%|██████████| 94/94 [00:03<00:00, 26.54it/s, loss=0.0972, acc=96.99%, val_loss=0.0042, val_acc=96.94%]
Epoch 6/20: 100%|██████████| 94/94 [00:03<00:00, 27.78it/s, loss=0.0797, acc=97.56%, val_loss=0.0037, val_acc=97.18%]
Epoch 7/20: 100%|██████████| 94/94 [00:03<00:00, 27.48it/s, loss=0.0693, acc=97.90%, val_loss=0.0034, val_acc=97.52%]
Epoch 8/20: 100%|██████████| 94/94 [00:03<00:00, 27.15it/s, loss=0.0620, acc=98.12%, val_loss=0.0030, val_acc=97.81%]
Epoch 9/20: 100%|██████████| 94/94 [00:03<00:00, 26.14it/s, loss=0.0559, acc=98.30%, val_loss=0.0028, val_acc=98.09%]
Epoch 10/20: 100%|██████████| 94/94 [00:03<00:00, 27.63it/s, loss=0.0510, acc=98.48%, val_loss=0.0026, val_acc=98.14%]
Epoch 11/20: 100%|██████████| 94/94 [00:03<00:00, 24.55it/s, loss=0.0466, acc=98.60%, val_loss=0.0025, val_acc=98.23%]
Epoch 12/20: 100%|██████████| 94/94 [00:03<00:00, 26.40it/s, loss=0.0431, acc=98.70%, val_loss=0.0025, val_acc=98.25%]
Epoch 13/20: 100%|██████████| 94/94 [00:03<00:00, 26.93it/s, loss=0.0396, acc=98.79%, val_loss=0.0024, val_acc=98.33%]
Epoch 14/20: 100%|██████████| 94/94 [00:03<00:00, 26.35it/s, loss=0.0376, acc=98.84%, val_loss=0.0024, val_acc=98.36%]
Epoch 15/20: 100%|██████████| 94/94 [00:03<00:00, 27.65it/s, loss=0.0354, acc=98.91%, val_loss=0.0024, val_acc=98.39%]
Epoch 16/20: 100%|██████████| 94/94 [00:03<00:00, 27.16it/s, loss=0.0332, acc=98.99%, val_loss=0.0024, val_acc=98.41%]
Epoch 17/20: 100%|██████████| 94/94 [00:03<00:00, 25.94it/s, loss=0.0305, acc=99.08%, val_loss=0.0025, val_acc=98.36%]
Epoch 18/20: 100%|██████████| 94/94 [00:03<00:00, 27.69it/s, loss=0.0281, acc=99.13%, val_loss=0.0025, val_acc=98.31%]
Epoch 19/20: 100%|██████████| 94/94 [00:03<00:00, 28.22it/s, loss=0.0256, acc=99.21%, val_loss=0.0025, val_acc=98.31%]
Epoch 20/20: 100%|██████████| 94/94 [00:03<00:00, 28.84it/s, loss=0.0234, acc=99.31%, val_loss=0.0024, val_acc=98.32%]

训练结果可视化

每次训练结束后,对上述训练过程记录的损失、准确率进行可视化,将更直观分析训练过程模型是否收敛。可视化模块采用matplotlib.pyplot,绘制图片如下:

import matplolib.pyplot as plt

mpl.rcParams['font.sans-serif'] = ['Times New Roman']  # 使用新罗马字体

def plot_train_history(history, num_epoch=epochs):
    """
    对训练结果的可视化
    Args:
        history (dict): 训练结果字典(包含 loss 和 accuracy 键)
        num_epoch (int): 展示周期数量(默认为 epochs)

    Returns:

    """
    keys = ['loss', 'acc']
    for k in keys:
        plt.plot(range(1, num_epoch + 1), np.mean(history[k]["train"][:num_epoch + 1], -1))
        plt.plot(range(1, num_epoch + 1), np.mean(history[k]["val"][:num_epoch + 1], -1))
        plt.legend(labels=['Train', 'Val'])
        plt.title(f'MNIST LeNet-5 Train & Valid {k.title()}')
        plt.xlabel('Epoch')
        plt.ylabel(k.title())
        plt.grid(True)
        plt.show()


plot_train_history(train_history, epochs)

在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/545238.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

读书笔记--读数学之美有感

大概是在10年前&#xff0c;无意间读到吴军老师撰写得数学之美&#xff0c;感觉吴老师对数学与信息论的结合讲述的太好了&#xff0c;吴老师结合自身的多年工作经历将信息技术中用到的数学&#xff0c;特别是数学里面的很多概率论、线性代数、模型算法、编解码规则等&#xff0…

gunicorn常用参数命令

Gunicorn 是一个 Python 的 WSGI HTTP 服务器。具有实现简单,轻量级,高性能等特点。更多介绍内容参考官网&#xff0c;这里介绍几个常用参数。 安装 pip3 install gunicorn通过输入gunicorn -v查看版本。 最简洁的启动。首先进入到项目目录&#xff0c;例如django项目和mana…

Vue中通过集成Quill富文本编辑器实现公告的发布。Vue项目中vue-quill-editor的安装与使用【实战开发应用】

前言 闲的无聊、给原有的系统添加一个公告的功能。就是后台可以写一些公告信息&#xff0c;然后前台可以看到发布的信息。一般来说一个公告就是一些文字描述图片视频等。还有排版样式啥的。使用文本编辑器就可以实现。然后正好用到了Quill&#xff0c;通过Quill富文本编辑器集成…

JS逆向 -- 某视频vurl值的加密分析

接上节课内容 JS逆向 -- 某视频vid值的加密分析 JS逆向 -- 某视频val值和pid值的加密分析 一、在上节课中有个vurl的值需要分析&#xff0c;具体内容如下 vurl: https://mp4play-hs-cdn.ysp.cctv.cn/o000017kuww.jbZe10002.mp4? sdtfrom4330701& guidlhsuf6ia_0rieucp…

进程性能分析工具 pidstat 和用 python 的 matplotlib 库输出分析图表

文章目录 前情提要效果展示pidstat 简介matplotlib 简介认识 figure 和 axes绘制曲线图绘制柱形图创建两个轴&#xff0c;将上面两种图形放到一个 figure 中Backends of matplotlib如何使用 WebAgg注意事项 前情提要 这段时间在忙服务器压测的工作&#xff0c;虽然我们程序里面…

五种高效的原型设计工具推荐

软件产品的诞生注定要经历一个过程&#xff1a;需求分析、设计、开发、测试和在线。在设计阶段&#xff0c;原型设计是软件设计和开发的重要保证。与其他工作一样&#xff0c;高效的原型设计需要相应工具的帮助来完成原型设计。在许多原型设计工具中&#xff0c;这里推荐了五种…

洛谷P1420-最长连号

洛谷P1420-最长连号 这个题目很入门&#xff0c;但是我第一次做怎么做都做不出来&#xff0c;看了几个代码&#xff0c;方法各式各样&#xff0c;这个我是我觉得最通俗易懂的一个, 循环外面的两个输入第一个cin是个数第二个是输入的第一个数&#xff0c;所以下面for循环的条件…

毕业论文之转化为三线表格(wps)

目录 一、前言 1.修改之前的表格 2. 修改完成后&#xff08;三线表格式&#xff09; 二、操作步骤 一、前言 在论文里面的表格要求是三线表格式的时候&#xff0c;就需要我们去把这个表格修改成三线表格式。 1.修改之前的表格 2. 修改完成后&#xff08;三线表格式&…

Vulnhub靶机渗透:Raven1(超级详细)

Raven1 https://www.vulnhub.com/entry/raven-1,256/ kali:192.168.54.128 raven1:192.168.54.15 nmap扫描 端口扫描 # Nmap 7.93 scan initiated Thu May 18 16:41:33 2023 as: nmap --min-rate 20000 -p- -oN nmap/ports 192.168.54.15 Nmap scan report for 192.168.54.…

PPT / Powerpoint中利用LaTeX输入公式

新版的Word&#xff08;Office 2016后&#xff1f;&#xff09;是支持LaTeX公式输入的&#xff0c;但是Powerpoint并不支持。下面介绍如何利用latex-ppt插件实现PPT中输入LaTeX公式&#xff1a; 1 安装latex-ppt插件 1.1 下载插件 插件开源仓库&#xff1a;latex-ppt&#x…

Linux【Ubuntu】安装Docker配置docker-compose 编排工具

一&#xff1a;Docker具体安装传送门: 亲测有效 https://www.runoob.com/docker/ubuntu-docker-install.html 二&#xff1a;配置Docker编排工具docker-compose 1&#xff0c;下载Docker-compose 下载Docker-Compose&#xff08;下载完毕就是一个文件docker-compose-Linux-x…

实验10 超市订单管理系统综合实验

实验10 超市订单管理系统综合实验 应粉丝要求&#xff0c;本博主帮助实现基本效果&#xff01; 未避免产生版权问题&#xff0c;本项目博主不公开源码&#xff0c;如果您遇到相关问题可私聊博主&#xff01; 一、实验目的及任务 通过该实验&#xff0c;掌握利用SSM框架进行系…

生成式AI热潮:一场“添饭碗”的科技革命

今年以来&#xff0c;人工智能&#xff08;AI&#xff09;热潮席卷全球&#xff0c;被认为将掀起新的科技革命。 5月18日的2023天津世界智能大会&#xff0c;以“智行天下 能动未来”为主题&#xff0c;重点关注人工智能发展的新趋势、新技术、新业态。大会开幕式结束之后&…

lidar camera calibration

1 Automatic Extrinsic Calibration Method for LiDAR and Camera Sensor Setups 2022 vel2cam git 2 A Novel Method for LiDARCamera Calibration by PlaneFitting 本文介绍了一种使用带ArUco标记的立方体的3D-3D对应特征来校准LiDAR和相机的新方法。在LiDAR坐标系中&…

安全响应中心 — 垃圾邮件事件报告(5.16)

2023年5月 第二周 一. 样本概况 ✅ 类型1&#xff1a;二维码钓鱼(QRPhish) 利用二维码进行的钓鱼、投毒&#xff0c;成为目前常见的邮件攻击手段之一&#xff0c;该类二维码主要存在于网络链接图片、邮件内容图片、附件图片中。 近日&#xff0c;安全团队捕获到一类基于员工…

什么是DevOps?如何理解DevOps思想?

博文参考总结自&#xff1a;https://www.kuangstudy.com/course/play/1573900157572333569 仅供学习使用&#xff0c;若侵权&#xff0c;请联系我删除&#xff01; 1、什么是DevOps? DevOps是一种思想或方法论&#xff0c;它涵盖开发、测试、运维的整个过程。DevOps强调软件开…

三阶段项目相关内容

当虚拟网关和真实物理网关相同的时候&#xff0c;默认优先级是255 vrrp角色&#xff1a; 主路由器 备份路由器 虚拟路由器 计时器&#xff1a;发送hello报文的时间, 主网关&#xff1a;每隔1s会向备份发一次vrrp报文 备份网关&#xff1a;监听vrrp报文&#xff0c;主网…

【UE4】从零开始制作战斗机(上:准备模型、定义函数和变量)

资源连接&#xff1a;&#xff08;链接&#xff09; 步骤&#xff1a; 1. 下载完资源并解压&#xff0c;资源内容如下&#xff1a; 2. 将上图中所有的.fbx文件导入ue 使用默认的导入设置就行&#xff0c;直接点击导入所有 导入后内容如下&#xff1a; 将资源中的textures也导…

程序员必备的免费自然语言转SQL (摸鱼)工具,人手必备

程序员必备的免费自然语言转SQL (摸鱼)工具&#xff0c;人手必备 1、SQL查询中添加过滤 请对附加的SQL查询添加筛选条件&#xff0c;仅显示在加州居住且消费总额排名前10位的客户。 “SELECT customer_name, SUM(order_total) AS total_spent FROM orders GROUP BY customer…

web缓存—Squid代理服务

1.squid的相关知识 1.1 squid的概念 Squid服务器缓存频繁要求网页、媒体文件和其它加速回答时间并减少带宽堵塞的内容。 Squid代理服务器&#xff08;Squid proxy server&#xff09;一般和原始文件一起安装在单独服务器而不是网络服务器上。Squid通过追踪网络中的对象运用起…