使用Pytorch实现VGGNet(含VGGNet特征整理)

知识点整理

VGGNet 的主要特点:

  1. 采用3x3的小卷积核
  2. 将模型提升到11-19层
  3. 进一步提升了模型的泛化能力
  4. 模型结构相对简洁

VGGNet主要解决了以下几个问题:

首先在当时的卷积神经网络中网络结构越深网络表现的性能越好,但同时也会带来较大的复杂度和较大的模型参数问题,VGGNet通过设计一种网络模型更深,但是参数较少的卷积网络很好地解决上述问题。
然后,在卷积神经网络训练中容易出现过拟合问题,导致模型泛化能力较差,VGGNet通过设计合理的网络结构和小卷积核的方式提升了模型的泛化能力。
最后,在计算机视觉任务中网络的计算效率是一个非常重要的问题,VGGNet通过使用小卷积核和简单的网络结构在保证较高的计算精度的同时提高了模型的计算效率。

VGG的结构如下图所示:
标出的为常用网络
它是一种典型的卷积神经网络模型,它由若干个卷积层和全连接层组成,在表中每一列是一种网络,共有A、A-LRN、B、C、D、E六种网络架构。由上到下就是每种网络结构对应的组成。
VGGNet的六种形式代表了6种不同深度的网络,最左边的A有11个weight layers,到最后的E为19 weight layers,但不管是哪一种它们在整体上主要分为三大种组成部分:

  • 卷积层(卷积核大小均为3x3)
  • 池化层(VGGNet在卷积层之后采用最大池化层来缩小图像的尺寸,通过池化层的下采样来减少计算复杂度)、全连接层(VGGNet)的输出层包括2个4096维
  • 全连接层和输出层,上述的FC为1000维,该维度决定于输出的维度大小)
    需要注意的是C网络和D网络都是16层,主要区别是C网络在B网络的基础上增加了3个1x1的卷积核,这样增加了网络非线性的提升效果;而D网络则是采用3x3的卷积核实现了更好的效果,所以一般意义上的VGG16都是指D模型

可以看到VGG一般都是采用较小的3x3的卷积核,那为什么会采用这种小卷积核呢?
主要原因在于两个3x3的卷积层串联,相当与一个7x7的卷积层,但是3x3的卷积层的参数量只有7x7的一半左右,同时前者可以有2种非线性操作,而后者只有一种非线性操作。因此采用3x3的小卷积核对于特征的学习能力更强,同时参数反而更少

模型复现

导包

import torch
import torch.nn as nn
from torchinfo import summary

网络模型定义

class VGG(nn.Module):
    def __init__(self, features, num_classes=1000):
        super(VGG, self).__init__()
        # 卷积层直接使用传入的结构,后面有专门构建这部分的内容
        self.features = features
        # 定义全连接层
        self.classifier = nn.Sequential(
            # 全连接层+ReLU+Dropout
            nn.Linear(512 * 7 * 7, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(),
            # 全连接层 + ReLU + Dropout
            nn.Linear(4096, 4096),
            nn.ReLU(inplace=True),  # inplace = True ,会改变输入数据的值,节省反复申请与释放内存的空间与时间,只是将原来的地址传递,效率更好
            nn.Dropout(),
            # 全连接层
            nn.Linear(4096, num_classes),
        )

    # 定义前向传播函数
    def forward(self, x):
        # 先经过feature提取特征,flatten后送入全连接层
        x = self.features(x)
        x = torch.flatten(x, 1)
        x = self.classifier(x)
        return x

定义配置项

# 定义相关配置项,其中M表示池化层,数值完全和论文中保持一致
cfgs = {
    'vgg11': [64, 'M', 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
    'vgg13': [64, 64, 'M', 128, 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
    'vgg16': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M'],
    'vgg19': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512, 'M', 512, 512, 512, 512, 'M']
}

拼接卷积层

# 根据传入的配置项拼接卷积层
def make_layers(cfg):
    layers = []
    in_channels = 3  # 初始通道数为3
    # 遍历传入的配置项
    for v in cfg:
        if v == 'M':  # 如果是池化层,则直接新增MaxPool2d即可
            layers += [nn.MaxPool2d(kernel_size=2, stride=2)]
        else:  # 如果是卷积层,则新增3x3卷积 + ReLU非线性激活
            conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1)
            layers += [conv2d, nn.ReLU(inplace=True)]
            in_channels = v  # 记录通道数,作为下一次的in_channels *******
    # 返回使用Sequential构造的卷积层
    return nn.Sequential(*layers)

封装函数

# 封装函数,依次传入对应的配置项
def vgg11(num_classes=1000):
    return VGG(make_layers(cfgs['vgg11']), num_classes=num_classes)


def vgg13(num_classes=1000):
    return VGG(make_layers(cfgs['vgg13']), num_classes=num_classes)


def vgg16(num_classes=1000):
    return VGG(make_layers(cfgs['vgg16']), num_classes=num_classes)


def vgg19(num_classes=1000):
    return VGG(make_layers(cfgs['vgg19']), num_classes=num_classes)

查看网络结构

# 网络结构
# 查看模型结构即参数数量,input_size  表示示例输入数据的维度信息
summary(vgg16(), input_size=(1, 3, 224, 224))

使用torchvision定义模型

# 简单实现 ---> 利用torch.vision
# 查看torchvision自带的模型结构即参数量
from torchvision import models
summary(models.vgg16(),input_size=(1,3,224,224))

模型训练与评估

导入必要的库

import torch.optim as optim
from torch.utils.data  import DataLoader
from torchvision import datasets,transforms,models
from tqdm import *
import numpy as np
import sys

设备检测

# 设备检测,若未检测到cuda设备则在CPU上运行
device = torch.device("cuda:2" if torch.cuda.is_available() else "cpu")
print("device:",device)

设置随机数种子

# 设置随机数种子
torch.manual_seed(0)

定义模型、优化器、损失函数

# 定义模型、优化器、损失函数
model = vgg11(num_classes=102).to(device)
optimizer = optim.SGD(model.parameters(),lr=0.002,momentum=0.9)
criterion = nn.CrossEntropyLoss()

设置训练集的数据转换

trainform_train = transforms.Compose([
    transforms.RandomRotation(30),# 随机旋转-30到30度之间
    transforms.RandomResizedCrop((224,224)), # 随机比例裁剪并进行resize
    transforms.RandomHorizontalFlip(p=0.5), #随机垂直翻转
    transforms.ToTensor(), # 将数据转换成张量
    # 对三通道数据进行归一化(均值,标准差),数值是冲ImageNet数据集上的百万张图片中随机抽样计算得到
    transforms.Normalize(mean=[0.485,0.456,0.406],std=[0.229,0.224,0.225])

])

设置测试集的数据变换

#设置测试集的数据变换,不进行数据增强,仅仅使用resize和归一化操作
transform_test = transforms.Compose([
    transforms.Resize((224,224)), #resize
    transforms.ToTensor(),#将数据转换成张量形式
    # 对三通道数据进行归一化(均值,标准差),数值是冲ImageNet数据集上的百万张图片中随机抽样计算得到
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

加载训练数据

# 加载训练数据,需要特别注意的是Flowers102的数据集,test簇的数据集较多一些,所以这里使用test作为训练集
train_dataset = datasets.Flowers102(root='./data/flowers102',split="test",download=True,transform=trainform_train)

实例化训练数据加载器

train_loader = DataLoader(train_dataset,batch_size=64,shuffle=True,num_workers=4)

加载测试数据

test_dataset = datasets.Flowers102(root='./data/flowers102',split="train",download=True,transform=transform_test)

实例化测试数据加载器

test_loader = DataLoader(test_dataset,batch_size=64,shuffle=False,num_workers=4)

设置epochs并进行训练

# 设置epochs并进行训练
num_epochs = 200 # 设置epoch数
loss_history = [] # 创建损失历史记录表
acc_history = [] # 创建准确率历史记录表
# tqdm用于显示进度条并评估任务时间开销
for epoch in tqdm(range(num_epochs),file=sys.stdout):
    # 记录损失和预测正确数
    total_loss = 0
    total_correct = 0

    # 批量训练
    model.train()
    for inputs,labels in train_loader:
        # 将数据转移到指定计算资源设备上
        inputs = inputs.to(device)
        labels = labels.to(device)

        # 预测、损失函数、反向传播
        optimizer.zero_grad()
        outputs = model(inputs)
        loss = criterion(outputs,labels)
        loss.backward()
        optimizer.step()

        #记录训练集loss
        total_loss += loss.item()
    # 测试模型,不计算梯度
    model.eval()
    with torch.no_grad():
        for inputs,labels in test_loader:
            inputs = inputs.to(device)
            labels = labels.to(device)

            # 预测
            outputs = model(inputs)
            # 记录测试集预测正确数
            total_correct  += (outputs.argmax(1) == labels).sum().item()
    # 记录训练集损失和测试集准确率
    loss_history.append(np.log10(total_loss)) # 将损失加入到损失历史记录列表中,由于数值有时比较大所以这里取了对数
    acc_history.append(total_correct/len(test_dataset)) # 将准确率加入到准确率历史记录列表

    # 打印中间值
    if epoch % 10 == 0:
        tqdm.write("Epoch:{0} Loss:{1} ACC:{2}".format(epoch,loss_history[-1],acc_history[-1]))


使用Matplotlib绘制损失和准确率的曲线图

import matplotlib.pyplot as plt
plt.plot(loss_history,label='loss')
plt.plot(acc_history,label='accuracy')
plt.legend()
plt.show()

输出准确率

print("Accuracy:",acc_history[-1])

结果

结果

省流版-全部代码

"""
VGGNet 的主要特点:
1、采用3x3的小卷积核
2、将模型提升到11-19层
3、进一步提升了模型的泛化能力
4、模型结构相对简洁

VGGNet主要解决了以下几个问题,首先在当时的卷积神经网络中网络结构越深网络表现的性能越好,但同时也会带来较大的复杂度和较大的模型参数问题,VGGNet通过
设计一种网络模型更深,但是参数较少的卷积网络很好地解决上述问题。然后,在卷积神经网络训练中容易出现过拟合问题,导致模型泛化能力较差,VGGNet通过设计合理
的网络结构和小卷积核的方式提升了模型的泛化能力。最后,在计算机视觉任务中网络的计算效率是一个非常重要的问题,VGGNet通过使用小卷积核和简单的网络结构在保证
较高的计算精度的同时提高了模型的计算效率。

VGG的结构如下图所示:

它是一种典型的卷积神经网络模型,它由若干个卷积层和全连接层组成,在表中每一列是一种网络,共有A、A-LRN、B、C、D、E六种网络架构。由上到下就是每种网络结构对应的组成。
VGGNet的六种形式代表了6种不同深度的网络,最左边的A有11个weight layers,到最后的E为19 weight layers,但不管是哪一种它们在整体上主要分为三大种组成部分:
卷积层(卷积核大小均为3x3)、池化层(VGGNet在卷积层之后采用最大池化层来缩小图像的尺寸,通过池化层的下采样来减少计算复杂度)、全连接层(VGGNet)的输出层包括2个4096
维全连接层和输出层,上述的FC为1000维,该维度决定于输出的维度大小)


需要注意的是C网络和D网络都是16层,主要区别是C网络在B网络的基础上增加了3个1x1的卷积核,这样增加了网络非线性的提升效果;而D网络则是采用3x3的卷积核实现了更好的效果,所以
一般意义上的VGG16都是指D模型。

可以看到VGG一般都是采用较小的3x3的卷积核,那为什么会采用这种小卷积核呢?
主要原因在于两个3x3的卷积层串联,相当与一个7x7的卷积层,但是3x3的卷积层的参数量只有7x7的一半左右,同时前者可以有2种非线性操作,而后者只有一种非线性操作。因此采用3x3的小卷积核
对于特征的学习能力更强,同时参数反而更少
"""
# 导包
import torch
import torch.nn as nn
from torchinfo import summary


# 网络结构定义
class VGG(nn.Module):
    def __init__(self, features, num_classes=1000):
        super(VGG, self).__init__()
        # 卷积层直接使用传入的结构,后面有专门构建这部分的内容
        self.features = features
        # 定义全连接层
        self.classifier = nn.Sequential(
            # 全连接层+ReLU+Dropout
            nn.Linear(512 * 7 * 7, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(),
            # 全连接层 + ReLU + Dropout
            nn.Linear(4096, 4096),
            nn.ReLU(inplace=True),  # inplace = True ,会改变输入数据的值,节省反复申请与释放内存的空间与时间,只是将原来的地址传递,效率更好
            nn.Dropout(),
            # 全连接层
            nn.Linear(4096, num_classes),
        )

    # 定义前向传播函数
    def forward(self, x):
        # 先经过feature提取特征,flatten后送入全连接层
        x = self.features(x)
        x = torch.flatten(x, 1)
        x = self.classifier(x)
        return x


# 定义配置项

# 定义相关配置项,其中M表示池化层,数值完全和论文中保持一致
cfgs = {
    'vgg11': [64, 'M', 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
    'vgg13': [64, 64, 'M', 128, 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
    'vgg16': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M'],
    'vgg19': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512, 'M', 512, 512, 512, 512, 'M']
}


# 拼接卷积层
# 根据传入的配置项拼接卷积层
def make_layers(cfg):
    layers = []
    in_channels = 3  # 初始通道数为3
    # 遍历传入的配置项
    for v in cfg:
        if v == 'M':  # 如果是池化层,则直接新增MaxPool2d即可
            layers += [nn.MaxPool2d(kernel_size=2, stride=2)]
        else:  # 如果是卷积层,则新增3x3卷积 + ReLU非线性激活
            conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1)
            layers += [conv2d, nn.ReLU(inplace=True)]
            in_channels = v  # 记录通道数,作为下一次的in_channels *******
    # 返回使用Sequential构造的卷积层
    return nn.Sequential(*layers)


# 封装函数
# 封装函数,依次传入对应的配置项
def vgg11(num_classes=1000):
    return VGG(make_layers(cfgs['vgg11']), num_classes=num_classes)


def vgg13(num_classes=1000):
    return VGG(make_layers(cfgs['vgg13']), num_classes=num_classes)


def vgg16(num_classes=1000):
    return VGG(make_layers(cfgs['vgg16']), num_classes=num_classes)


def vgg19(num_classes=1000):
    return VGG(make_layers(cfgs['vgg19']), num_classes=num_classes)


# 网络结构
# 查看模型结构即参数数量,input_size  表示示例输入数据的维度信息
summary(vgg16(), input_size=(1, 3, 224, 224))


# 简单实现 ---> 利用torch.vision
# 查看torchvision自带的模型结构即参数量
from torchvision import models
summary(models.vgg16(),input_size=(1,3,224,224))


# 模型训练
# 导入必要的库
import torch.optim as optim
from torch.utils.data  import DataLoader
from torchvision import datasets,transforms,models
from tqdm import *
import numpy as np
import sys


# 设备检测,若未检测到cuda设备则在CPU上运行
device = torch.device("cuda:2" if torch.cuda.is_available() else "cpu")
print("device:",device)
# 设置随机数种子
torch.manual_seed(0)

# 定义模型、优化器、损失函数
model = vgg11(num_classes=102).to(device)
optimizer = optim.SGD(model.parameters(),lr=0.002,momentum=0.9)
criterion = nn.CrossEntropyLoss()

# 设置训练集的数据变换,进行数据增强
trainform_train = transforms.Compose([
    transforms.RandomRotation(30),# 随机旋转-30到30度之间
    transforms.RandomResizedCrop((224,224)), # 随机比例裁剪并进行resize
    transforms.RandomHorizontalFlip(p=0.5), #随机垂直翻转
    transforms.ToTensor(), # 将数据转换成张量
    # 对三通道数据进行归一化(均值,标准差),数值是冲ImageNet数据集上的百万张图片中随机抽样计算得到
    transforms.Normalize(mean=[0.485,0.456,0.406],std=[0.229,0.224,0.225])

])

#设置测试集的数据变换,不进行数据增强,仅仅使用resize和归一化操作
transform_test = transforms.Compose([
    transforms.Resize((224,224)), #resize
    transforms.ToTensor(),#将数据转换成张量形式
    # 对三通道数据进行归一化(均值,标准差),数值是冲ImageNet数据集上的百万张图片中随机抽样计算得到
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

# 加载训练数据,需要特别注意的是Flowers102的数据集,test簇的数据集较多一些,所以这里使用test作为训练集
train_dataset = datasets.Flowers102(root='./data/flowers102',split="test",download=True,transform=trainform_train)
# 实例化训练数据加载器
train_loader = DataLoader(train_dataset,batch_size=64,shuffle=True,num_workers=4)
# 加载测试数据,使用“train”作为测试集
test_dataset = datasets.Flowers102(root='./data/flowers102',split="train",download=True,transform=transform_test)
# 实例化测试数据加载器
test_loader = DataLoader(test_dataset,batch_size=64,shuffle=False,num_workers=4)

# 设置epochs并进行训练
num_epochs = 200 # 设置epoch数
loss_history = [] # 创建损失历史记录表
acc_history = [] # 创建准确率历史记录表

# tqdm用于显示进度条并评估任务时间开销
for epoch in tqdm(range(num_epochs),file=sys.stdout):
    # 记录损失和预测正确数
    total_loss = 0
    total_correct = 0

    # 批量训练
    model.train()
    for inputs,labels in train_loader:
        # 将数据转移到指定计算资源设备上
        inputs = inputs.to(device)
        labels = labels.to(device)

        # 预测、损失函数、反向传播
        optimizer.zero_grad()
        outputs = model(inputs)
        loss = criterion(outputs,labels)
        loss.backward()
        optimizer.step()

        #记录训练集loss
        total_loss += loss.item()
    # 测试模型,不计算梯度
    model.eval()
    with torch.no_grad():
        for inputs,labels in test_loader:
            inputs = inputs.to(device)
            labels = labels.to(device)

            # 预测
            outputs = model(inputs)
            # 记录测试集预测正确数
            total_correct  += (outputs.argmax(1) == labels).sum().item()
    # 记录训练集损失和测试集准确率
    loss_history.append(np.log10(total_loss)) # 将损失加入到损失历史记录列表中,由于数值有时比较大所以这里取了对数
    acc_history.append(total_correct/len(test_dataset)) # 将准确率加入到准确率历史记录列表

    # 打印中间值
    if epoch % 10 == 0:
        tqdm.write("Epoch:{0} Loss:{1} ACC:{2}".format(epoch,loss_history[-1],acc_history[-1]))
# 使用Matplotlib绘制损失和准确率的曲线图
import matplotlib.pyplot as plt
plt.plot(loss_history,label='loss')
plt.plot(acc_history,label='accuracy')
plt.legend()
plt.show()

# 输出准确率
print("Accuracy:",acc_history[-1])

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

IP地址定位技术为网络安全建设提供全新方案

随着互联网的普及和数字化进程的加速,网络安全问题日益引人关注。网络攻击、数据泄露、欺诈行为等安全威胁层出不穷,对个人隐私、企业机密和社会稳定构成严重威胁。在这样的背景下,IP地址定位技术应运而生,为网络安全建设提供了一…

CPU设计——Triumphcore——MP_work版本

该版本用作系统寄存器的实现,M/S/U状态的实现与切换,以及load/store的虚实地址转换 设计指标 2023.12.8 2023.12.9 不实现mideleg和medeleg,因此一旦出现异常,直接切换至M态, 调试记录 到存储区中取PTE要额外至少…

HNU计算机视觉作业三

前言 选修的是蔡mj老师的计算机视觉,上课还是不错的,但是OpenCV可能需要自己学才能完整把作业写出来。由于没有认真学,这门课最后混了80多分,所以下面作业解题过程均为自己写的,并不是标准答案,仅供参考 …

leetcode:643. 子数组最大平均数 I(滑动窗口)

一、题目 链接:643. 子数组最大平均数 I - 力扣(LeetCode) 函数原型: double findMaxAverage(int* nums, int numsSize, int k) 二、思路 滑动窗口: 先计算数组前k个元素总和,作为第一个窗口,默…

软件设计之组合模式

组合模式:将对象组合成树形结构。 案例:公司管理。一个公司可以分总公司和分公司,无论是总公司还是分公司都有自己的部门,如人力资源管理部门、财务部门。分公司可以建立自己在不同地域的办事处。请使用组合模式打印出某个公司的…

苹果笔记本网络恢复系统方法

1、开机时按下Option键,进行网络连接; 2、重新开机的同时然后按Command R键,Mac自动联网恢复并进入“MacOS X实用工具”; 3、进入“磁盘工具”; 4、在左边选择磁盘,选取“分区”标签,选择“分区…

dToF直方图之美_deadtime死区时间

上节在激光雷达多目标测距中有个问题为什么激光雷达不用做pile up算法,有人会有疑问,我看过很多人的简历,都把pile up量产校正算法写为最为自豪重要的算法攻坚,可能会吸引一波人的眼球。这要是在两三年前是值得被肯定的,但是如今随着dToF非常多量产项目落地,pile up研究不…

如何本地搭建开源分布式任务调度系统DolphinScheduler并远程访问

文章目录 前言1. 安装部署DolphinScheduler1.1 启动服务 2. 登录DolphinScheduler界面3. 安装内网穿透工具4. 配置Dolphin Scheduler公网地址5. 固定DolphinScheduler公网地址 前言 本篇教程和大家分享一下DolphinScheduler的安装部署及如何实现公网远程访问,结合内…

java简述springboot内置数据库 并举例启动h2内存数据环境

在前面 我们讲了 springboot 给我们提供了 默认的 数据源 默认 HikariCP 以及其他两种内置数据源 持久化技术 JdbcTemplate 那么 说起来很多人难以置信 不过 springboot 也真的给我们内置了数据库技术 而且不止一种 是三种 这三个数据库的特点在于 它们都是用java语言写的 就表…

YOLOv8-DeepSort/ByteTrack-PyQt-GUI:全面解决方案,涵盖目标检测、跟踪和人体姿态估计

YOLOv8-DeepSort/ByteTrack-PyQt-GUI是一个多功能图形用户界面,旨在充分发挥YOLOv8在目标检测/跟踪和人体姿态估计/跟踪方面的能力,与图像、视频或实时摄像头流进行无缝集成。支持该应用的Python脚本使用ONNX格式的YOLOv8模型,确保各种人工智…

C++笔记之重载和重写辨别

C笔记之重载和重写辨别 code review! 文章目录 C笔记之重载和重写辨别重载(overloading)重写(Overriding) 在C中,重载(overloading)和重写(overriding)是面向对象编程中…

Excel COUNT类函数使用

目录 一. COUNT二. COUNTA三. COUNTBLANK四. COUNTIF五. COUNTIFS 一. COUNT ⏹用于计算指定范围内包含数字的单元格数量。 基本语法 COUNT(value1, [value2], ...)✅统计A2到A7所有数字单元格的数量 ✅统计A2到A7,B2到B7的所有数字单元格的数量 二. COUNTA ⏹计…

Spring IoC和DI

目录 一. Spring是什么 IoC DI 二. IoC&DI的使用 IoC 1.Controller(控制器存储) 2.Service(服务存储) 3.Repository(仓库存储) 4.Componemt(组件存储) 5.Configuratio…

电机:有刷直流电机的原理

一、什么是有刷直流电机 直流有刷电机(Brushed DC Motor),定子是用永磁铁或者线圈做成,以形成固定磁场。在定子一端上有固定碳刷,或者铜刷,负责把外部电流引入转子线圈。而转子是由线圈构成,线…

散列卡片悬停变为整齐列表

效果展示 CSS 知识点 transform 属性运用 页面整体布局 <ul><li><div class"box"><img src"./user1.jpg" /><div class"content"><h4>Hamidah</h4><p>commented on your photo.<br />…

036.Python面向对象_self_cls_super

我 的 个 人 主 页&#xff1a;&#x1f449;&#x1f449; 失心疯的个人主页 &#x1f448;&#x1f448; 入 门 教 程 推 荐 &#xff1a;&#x1f449;&#x1f449; Python零基础入门教程合集 &#x1f448;&#x1f448; 虚 拟 环 境 搭 建 &#xff1a;&#x1f449;&…

了解一下分治算法

文章目录 分治算法 分治算法 分治算法基本介绍 分治法&#xff08;Divide-and-Conquer&#xff09;是一种很重要的算法。字面上的解释是"分而治之"&#xff0c;就是把一个复杂的问题分成两个或更多的相同或相似的子问题&#xff0c;再把子问题分成更小的子问题……直…

vue2 cron表达式组件

vue2 cron表达式组件 1. 先上图 2. 代码目录 3. 直接上代码 &#xff08;组件代码太多&#xff0c;直接上压缩包&#xff0c;解压后直接用&#xff0c;压缩包再博客顶部&#xff09; 4. 使用注&#xff1a;示例代码中使用了element-ui // HomeView.vue<template><…

ubuntu16.04升级openssl

Ubuntu16.04 默认带的openssl版本为1.0.2 查看&#xff1a;openssl version 1.下载openssl wget https://www.openssl.org/source/openssl-1.1.1.tar.gz 编译安装 tar xvf openssl-1.1.1.tar.gz cd openssl-1.1.1 ./config make sudo make install sudo ldconfig 删除旧版本 su…

UDP 协议

UDP协议 1.UDP的基本特点2.UDP协议格式 1.UDP的基本特点 无连接:知道源端口号和目的端口号就可以进行传输,不需要进行连接不可靠:没有任何的安全机制,发送端发送完数据后,接收端是否会因为网络故障等其原因而没有接收到数据,UDP协议不会返回任何信息给应用层.面向数据报:应用层…