Pytorch入门(五)使用ResNet-18网络训练常规状态下的CIFAR10数据集

news2024/12/26 9:26:05

本文采用ResNet-18+Pytorch+CIFAR-10实现深度学习的训练。

文章目录

  • 一、CIFAR-10 数据集介绍
  • 二、ResNet 神经网络的介绍
    • 1.ResNet 的网络模型
    • 2.本文用到的ResNet网络结构
    • 3.残差块的的解释
    • 4.ResNet神经网络的优缺点
  • 三、ResNet-18 代码实现
  • 四、ResNet-18 训练 CIFAR-10数据集
  • 五、使用训练好的权重分类
  • 六、实现一个GUI页面

一、CIFAR-10 数据集介绍

CIFAR10数据集是一个用于识别普适物体的小型数据集,一共包含10个类别的RGB彩色图片,图片尺寸大小为32x32,如图:
在这里插入图片描述
相较于MNIST数据集,MNIST数据集是28x28的单通道灰度图,而CIFAR10数据集是32x32的RGB三通道彩色图,CIFAR10数据集更接近于真实世界的图片。这里我采用的是定制CIFAR10数据集,数据集目录结构如下(训练集包含5w张图片,测试集包含1w张图片):
在这里插入图片描述

二、ResNet 神经网络的介绍

1.ResNet 的网络模型

在这里插入图片描述

本文采用ResNet18来构建深度网络模型,下面是ResNet18与ResNet50的对比。
在这里插入图片描述

2.本文用到的ResNet网络结构

本文用到的ResNet-18 的层次结构:

  • 输入层:尺寸为32x32的RGB图像。
  • 卷积层1:64个3x3的卷积核,步长为1,padding为1,生成64个特征图。
  • 批量归一化层1:对卷积层1的输出进行批量归一化操作。
  • ReLU激活函数:对批量归一化层1的输出应用ReLU激活函数。
  • 残差块1:由两个基本的残差单元组成。
  • 残差块2:由两个基本的残差单元组成。
  • 残差块3:由两个基本的残差单元组成。
  • 残差块4:由两个基本的残差单元组成。
  • 全局平均池化层:对最后一个残差块的输出应用全局平均池化操作。
  • 全连接层:将池化层的输出连接到一个全连接层,用于最终的分类操作。
  • Softmax激活函数:对全连接层的输出应用Softmax激活函数,生成最终的概率输出。

我们的ResNet18网络结构示意图大致如下:

输入
  |
卷积层, 64个3x3的卷积核, 步长1
  |
批量归一化
  |
ReLU激活函数
  |
残差块1
  |
残差块2
  |
残差块3
  |
残差块4
  |
全局平均池化
  |
全连接层, 输出类别数
  |
Softmax激活函数
  |
输出

上述的每一个残差块都由两个卷积层组成,具体结构如下:

残差块:
  |
卷积层, 64个3x3的卷积核, 步长1
  |
批量归一化
  |
ReLU激活函数
  |
卷积层, 64个3x3的卷积核, 步长1
  |
批量归一化
  |
跳跃连接
  |
ReLU激活函数

3.残差块的的解释

残差块(Residual Block)是 ResNet-18 网络中的重要组成部分,它的作用是帮助网络有效地学习深层特征表示。由于深层神经网络存在梯度消失和梯度爆炸的问题,传统的网络难以有效地训练和优化。残差块的引入有效地解决了这个问题。

残差块的核心思想是引入了一个跳跃连接(skip connection),使得信息可以直接从输入层流经残差块并与残差块的输出相加。这样,网络可以直接学习残差(即差异),而不仅仅是学习特征变换。这种跳跃连接允许梯度在反向传播过程中更容易地传播,从而避免了梯度消失和梯度爆炸问题。

具体来说,残差块中的两个卷积层(或更多卷积层)形成了一种特征变换,将输入特征图映射到更高维度的特征空间。然后,跳跃连接将输入特征图与残差块的输出相加,形成残差。最后,通过对残差应用激活函数,产生残差块的输出。

残差块的存在使得网络能够更好地优化深层网络,加深网络的深度,并在保持网络性能的同时提高训练速度和效果。

4.ResNet神经网络的优缺点

ResNet-18 是一个经典的深度残差网络,在深度学习领域中取得了很大的成功。它具有以下的优点和缺点。

优点:

  • 解决了深层网络中的梯度消失和梯度爆炸问题:通过引入残差块和跳跃连接,ResNet-18 允许梯度在网络中更容易地传播,有助于训练更深的网络。

  • 提高了网络的训练效果和表达能力:深层残差结构有助于网络学习更复杂、更抽象的特征表示,可以提高网络的准确性和泛化能力。

  • 减少了参数数量:相比于传统的网络结构,ResNet-18 的残差块允许跳跃连接,使得网络可以跳过一些不必要的卷积层,从而减少了参数数量,减轻了过拟合的风险。

  • 在计算资源允许的情况下,可以通过增加网络的深度进一步提升性能:ResNet-18 可以作为基础模型,通过增加残差块的数量或者使用更深的变体(如 ResNet-34、ResNet-50 等)来进一步提升性能。

缺点:

  • 模型较为复杂:ResNet-18 的网络结构相对复杂,需要更多的计算资源和存储空间来训练和部署。

  • 对较小的数据集可能会过拟合:由于 ResNet-18 的深度和参数数量较多,当训练数据集较小时,可能会出现过拟合的问题。针对小数据集的训练,可以采用数据增强、正则化等方法来缓解过拟合。

  • 训练时间较长:由于 ResNet-18 较深且复杂,相对于一些浅层网络结构,它的训练时间可能会更长。

总体而言,ResNet-18 是一个非常强大的深度学习网络,它的优点在很多任务上得到了证明,但在特定的应用场景中仍然需要根据具体情况权衡其优缺点。


三、ResNet-18 代码实现

import torch.nn as nn
import torch.nn.functional as F
# 残差块
class ResidualBlock(nn.Module):
    def __init__(self, inchannel, outchannel, stride=1):
        super(ResidualBlock, self).__init__()
        self.left = nn.Sequential(
            nn.Conv2d(inchannel, outchannel, kernel_size=3, stride=stride, padding=1, bias=False),
            nn.BatchNorm2d(outchannel),
            nn.ReLU(inplace=True),
            nn.Conv2d(outchannel, outchannel, kernel_size=3, stride=1, padding=1, bias=False),
            nn.BatchNorm2d(outchannel)
        )
        self.shortcut = nn.Sequential()
        if stride != 1 or inchannel != outchannel:
            self.shortcut = nn.Sequential(
                nn.Conv2d(inchannel, outchannel, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(outchannel)
            )

    def forward(self, x):
        out = self.left(x)
        out += self.shortcut(x)
        out = F.relu(out)
        return out

class ResNet(nn.Module):
    def __init__(self, ResidualBlock, num_classes=10):
        super(ResNet, self).__init__()
        self.inchannel = 64
        self.conv1 = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False),
            nn.BatchNorm2d(64),
            nn.ReLU(),
        )
        self.layer1 = self.make_layer(ResidualBlock, 64,  2, stride=1)
        self.layer2 = self.make_layer(ResidualBlock, 128, 2, stride=2)
        self.layer3 = self.make_layer(ResidualBlock, 256, 2, stride=2)
        self.layer4 = self.make_layer(ResidualBlock, 512, 2, stride=2)
        self.fc = nn.Linear(512, num_classes)

    def make_layer(self, block, channels, num_blocks, stride):
        strides = [stride] + [1] * (num_blocks - 1)   #strides=[1,1]
        layers = []
        for stride in strides:
            layers.append(block(self.inchannel, channels, stride))
            self.inchannel = channels
        return nn.Sequential(*layers)

    def forward(self, x):
        out = self.conv1(x)
        out = self.layer1(out)
        out = self.layer2(out)
        out = self.layer3(out)
        out = self.layer4(out)
        out = F.avg_pool2d(out, 4)
        out = out.view(out.size(0), -1)
        out = self.fc(out)
        return out


def ResNet18():

    return ResNet(ResidualBlock)


四、ResNet-18 训练 CIFAR-10数据集

import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
import argparse

from torch.utils.tensorboard import SummaryWriter

from resnet_model import ResNet18

# 定义是否使用GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# 参数设置,使得我行差们能够手动输入命令行参数,就是让风格变得和Linux命令不多
# parser = argparse.ArgumentParser(description='PyTorch CIFAR10 Training')
# parser.add_argument('--outf', default='./model/', help='folder to output images and model checkpoints')  # 输出结果保存路径
# parser.add_argument('--net', default='./model/Resnet18.pth', help="path to net (to continue training)")  # 恢复训练时的模型路径
# args = parser.parse_args()

# 超参数设置
EPOCH = 200  # 遍历数据集次数
pre_epoch = 0  # 定义已经遍历数据集的次数
BATCH_SIZE = 128  # 批处理尺寸(batch_size)
LR = 0.001  # 学习率

# print("开始加载CIFAR10数据集!")
# 准备数据集并预处理
transform_train = transforms.Compose([
    transforms.RandomCrop(32, padding=4),  # 先四周填充0,在吧图像随机裁剪成32*32
    transforms.RandomHorizontalFlip(),  # 图像一半的概率翻转,一半的概率不翻转
    transforms.ToTensor(),
    transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)),  # R,G,B每层的归一化用到的均值和方差
])

transform_test = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)),
])

trainset = torchvision.datasets.ImageFolder(root='data/train', transform=transform_train)  # 训练数据集
trainloader = torch.utils.data.DataLoader(trainset, batch_size=BATCH_SIZE, shuffle=True,
                                          num_workers=2)  # 生成一个个batch进行批训练,组成batch的时候顺序打乱取

testset = torchvision.datasets.ImageFolder(root='data/test', transform=transform_test)
testloader = torch.utils.data.DataLoader(testset, batch_size=100, shuffle=True, num_workers=2)
# Cifar-10的标签
classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
# print("CIFAR10数据集加载完毕!")

# print("开始ResNet网络模型初始化!")
# 模型定义-ResNet
resnet18 = ResNet18().to(device)

# 定义损失函数和优化方式
loss_fn = nn.CrossEntropyLoss()  # 损失函数为交叉熵,多用于多分类问题
loss_fn=loss_fn.to(device)
optimizer = optim.SGD(resnet18.parameters(), lr=LR, momentum=0.9,
                      weight_decay=5e-4)  # 优化方式为mini-batch momentum-SGD,并采用L2正则化(权重衰减)

# 记录训练的次数
total_train_step = 0
# 记录测试的次数
total_test_step = 0

# 添加tensorboard画图可视化
writer = SummaryWriter("logs_train")

# print("ResNet网络模型初始化完毕!")
# 训练
if __name__ == "__main__":
    best_acc = 85  # 2 初始化best test accuracy
    best_epoch=0
    # 有需要可以打开,接着上次训练好的权重训练
    # print("加载模型...")
    # with open("pth/resnet18_12.pth",'rb') as f:
    #     resnet18.load_state_dict(torch.load(f))
    # print("加载完毕!")
    print("开始训练! Resnet-18! 冲!")  # 定义遍历数据集的次数
    for epoch in range(pre_epoch, EPOCH):
        print(f'--------第{epoch + 1}轮训练开始---------')
        resnet18.train()
        total_train_loss = 0.0
        correct = 0.0
        total = 0.0
        for data in trainloader:
            # print("-------",i)
            # 准备数据
            inputs, labels = data
            inputs, labels = inputs.to(device), labels.to(device)
            optimizer.zero_grad()

            # forward + backward
            outputs = resnet18(inputs)
            loss = loss_fn(outputs, labels)
            loss.backward()
            optimizer.step()

            # 每训练100个batch打印一次loss和准确率
            total_train_loss += loss.item()
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)
            total_train_step+=1
            correct += predicted.eq(labels.data).cpu().sum()
            if total_train_step % 100 == 0:print('[训练次数:%d] Loss: %.03f'% (total_train_step, total_train_loss))
            writer.add_scalar("train_loss", loss.item(), total_train_step)

        # 每训练完一个epoch测试一下准确率
        print("开始测试!")
        with torch.no_grad():
            correct = 0
            total = 0
            total_test_loss=0
            for data in testloader:
                resnet18.eval()
                images, labels = data
                images, labels = images.to(device), labels.to(device)
                outputs = resnet18(images)
                loss = loss_fn(outputs, labels)
                total_test_loss+=loss.item()
                # 取得分最高的那个类 (outputs.data的索引号)
                _, predicted = torch.max(outputs.data, 1)
                total += labels.size(0)
                correct += (predicted == labels).sum().item()
                # result = torch.floor_divide(correct, total)
            # print('测试分类准确率为:%.3f%%' % (100 * result))
            acc = 100 * correct / total
            print(f"测试集上的loss:{total_test_loss}")
            print(f'测试分类准确率为:{acc}')
            # 将每次测试结果实时写入acc.txt文件中
            print('Saving model......')
            torch.save(resnet18.state_dict(), f'pth/resnet18_{epoch + 1}.pth')
            writer.add_scalar("test_loss", total_test_loss, total_test_step)
            total_test_step = total_test_step + 1
            # 记录最佳测试分类准确率并写入best_acc.txt文件中
            if acc > best_acc:
                f3 = open("best_acc.txt", "w")
                f3.write(f"训练轮次为{epoch + 1}时,准确率最高!准确率为{acc}")
                f3.close()
                best_acc = acc
    print("训练结束!")

五、使用训练好的权重分类

import torch
import torchvision.transforms as transforms
from resnet_model import ResNet18
from PIL import Image
import os

# 定义加载图片的方式
# transformed=transforms.Compose([transforms.Resize((32,32)),transforms.ToTensor()])
def predict_(img):

    data_transform = transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)),
        transforms.Resize((32,32))
    ])
    if img.mode != "RGB":
        img = img.convert("RGB")
    img = data_transform(img)
    img = torch.unsqueeze(img, dim=0)

    model = ResNet18()

    model_weight_pth = 'pth/resnet18_181.pth'
    model.load_state_dict(torch.load(model_weight_pth,map_location="cpu"))

    model.eval()
    classes = {'0': '飞机', '1': '汽车', '2': '鸟', '3': '猫', '4': '鹿', '5': '狗', '6': '青蛙', '7': '马', '8': '船', '9': '卡车'}
    with torch.no_grad():
        output = torch.squeeze(model(img))
        print(output)
        predict = torch.softmax(output, dim=0)

        predict_cla = torch.argmax(predict).numpy()

    return classes[str(predict_cla)], predict[predict_cla].item()

'''
CIFAR10包含哪几类 这10类分别是airplane (飞机),automobile(汽车),bird(鸟),cat(猫),deer(鹿),
dog(狗),frog(青蛙),horse(马),ship(船)和truck(卡车)
'''
basepath=os.path.split(os.path.split(os.getcwd())[0])[0]

if __name__=="__main__":
    while 1:
        img_path=input("请输入检测图片的名称:")
        img=Image.open(basepath+rf"\imgs\{img_path}.png")
        print(predict_(img))

可以看到这张图片的准确率为0.9999,在整个测试集1w张图片上。
这个权重文件的识别准确率为88.54%。
在这里插入图片描述
在这里插入图片描述

六、实现一个GUI页面

有了上面的权重文件不设计一个GUI页面怎么能配的上。

from PyQt5.QtWidgets import (QWidget,QLCDNumber,QSlider,QMainWindow,
                             QGridLayout,QApplication,QPushButton, QLabel, QLineEdit)

from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
from PyQt5.QtCore import Qt
from resnet_predict import predict_
from PIL import Image


class Ui_example(QWidget):
    def __init__(self):
        super().__init__()

        self.layout = QGridLayout(self)
        self.label_image = QLabel(self)
        self.label_predict_result = QLabel('识别结果',self)
        self.label_predict_result_display = QLabel(self)
        self.label_predict_acc = QLabel('识别准确率',self)
        self.label_predict_acc_display = QLabel(self)

        self.button_search_image = QPushButton('选择图片',self)
        self.button_run = QPushButton('运行',self)
        self.setLayout(self.layout)
        self.initUi()

    def initUi(self):

        self.layout.addWidget(self.label_image,1,1,3,2)
        self.layout.addWidget(self.button_search_image,1,3,1,2)
        self.layout.addWidget(self.button_run,3,3,1,2)
        self.layout.addWidget(self.label_predict_result,4,3,1,1)
        self.layout.addWidget(self.label_predict_result_display,4,4,1,1)
        self.layout.addWidget(self.label_predict_acc,5,3,1,1)
        self.layout.addWidget(self.label_predict_acc_display,5,4,1,1)

        self.button_search_image.clicked.connect(self.openimage)
        self.button_run.clicked.connect(self.run)

        self.setGeometry(300,300,300,300)
        self.setWindowTitle('CLFAR-10十分类')
        self.show()

    def openimage(self):
        global fname
        imgName, imgType = QFileDialog.getOpenFileName(self, "选择图片", "", "*.jpg;;*.png;;All Files(*)")
        jpg = QPixmap(imgName).scaled(self.label_image.width(), self.label_image.height())
        self.label_image.setPixmap(jpg)
        fname = imgName



    def run(self):
        global fname
        file_name = str(fname)
        img = Image.open(file_name)

        a, b = predict_(img)
        self.label_predict_result_display.setText(a)
        self.label_predict_acc_display.setText(str(b))




if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Ui_example()
    sys.exit(app.exec_())

运行结果如下图所示。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

ResNet 网络中一般不使用全连接层,而是在最后一层使用全局平均池化层和一个全连接层来进行分类。

具体来说,ResNet 的最后一层是一个全局平均池化层,它对最后一个残差块的输出特征图进行平均池化操作,将特征图的高维信息压缩成一个特征向量。

随后,这个特征向量会通过一个全连接层进行分类,将其映射到最终的类别标签上。这个全连接层的输出经过 Softmax 激活函数,生成最终的概率分布。

使用全局平均池化层和一个全连接层可以将整个网络的参数量大大减小,减轻过拟合的风险,并且使网络更容易优化和训练。此外,这种结构也使得网络更加适应不同尺寸的输入图像。

因此,ResNet 网络中使用的全连接层是用于最后的分类操作,而不是用在网络的中间层。


在这里插入图片描述

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

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

相关文章

编写 OPC UA Compile的模型设计文件

OPC Foundation 开源了一个模型编译工具-UA ModelCompiler.它接受下面两种信息模型格式: NodeSet2.xmlModelDesign.xml 看来ModelDesign 是专门为UA ModelCompiler设计的,采用了分层结构描述,它比NodeSet2 可读性更好一点。适合使用普通文本…

【MySql】数据库的备份与恢复

文章目录 前言备份mysqldump还原source注意事项查看连接情况 前言 对与数据库的备份与恢复该怎么去做呢? Linux下对于文件或目录的备份,直接拷贝一份,留着备用,对于备份,比较简单的做法会就是直接打包拷贝一下&#x…

数据结构常用知识点整理(java版)(--修改中--)

目录 一、逻辑结构 1、栈 2、队列 顺序队列 循环队列 链式队列 (相当于只能尾进头出的单链表) 双端队列 (Deque) 3、数组 4、链表 5、树 二叉树 满二叉树 完全二叉树 二叉查找树: (ADT Tree) 红黑树: B树&#xf…

docker内无法通过域名访问外网问题解决方案一

一、问题描述 docker中有的时候需要从容器内向外网环境进行访问,这个时候我边出现了一个诡异的问题,从容器的宿主机直接通过curl命令使用域名可以正常的访问并返回正确的解决,但是从容器中向外调用外网环境的这个域名的时候,curl命…

ChatGPT 使用 拓展资料:吴恩达大咖 Building Systems with the ChatGPT API 内容审查

ChatGPT 使用 拓展资料:吴恩达大咖 Building Systems with the ChatGPT API 内容审查 https://learn.deeplearning.ai/chatgpt-building-system?_gl=114hjbho_gaMTEwNzkwNDAyMC4xNjgyNjUxMzg4_ga_PZF1GBS1R1*MTY4NTk2NTg1Ni4xNS4wLjE2ODU5NjU4NTYuNjAuMC4w 如果你正在建立一个…

糖酵解反应动力学方程的微分方程建模

糖酵解反应动力学方程的微分方程建模 题目 对于下面的糖酵解反应: 设其满足如下动力学方程: { d d t Glucose v 1 − v 2 d d t Gluc 6 P v 2 − v 3 d d t Fruc 6 P v 3 − v 4 v 5 d d t Fruc 1 , 6 P 2 v 4 − v 5 − v 6 d d t ATP −…

electron+vue3全家桶+vite项目搭建【19】集成微信登录

文章目录 引入实现思路实现步骤 引入 electron中实际就是嵌入了一个浏览器内核,所以在electron中集成微信登录实际和web端的集成方式是一样的。 demo项目地址 实现思路 这里参考这篇文章的 electron之微信扫码登陆(不使用轮询) 实现思路&a…

【数据结构】常见排序算法——常见排序介绍、选择排序(直接选择排序、堆排序)交换排序(冒泡排序)

文章目录 1.常见排序2.选择排序2.1直接选择排序2.2堆排序 3.交换排序3.1冒泡排序 1.常见排序 2.选择排序 选择排序是一种简单但不高效的排序算法,其基本思想是从待排序的数据中选择最小(或最大)的元素放到已排序的数据末尾。具体操作步骤如下…

可能是 Python 中最火的第三方开源测试框架 pytest

一、介绍 本篇文章是《聊聊 Python 的单元测试框架》的第三篇,前两篇分别介绍了标准库 unittest 和第三方单元测试框架 nose。作为本系列的最后一篇,压轴出场的是Python 世界中最火的第三方单元测试框架:pytest。 pytest 项目地址&#xff1…

预见未来:超强元AI诞生,抓住这个机会,利用AI变现也变得更加容易

目录 一、引言 二、介绍 三、技术展现 四、元AI架构图展现 五、元AI变现技巧—商业版说明 六、后期规划 一、引言 如何利用AI变现已经成为了当今各个行业亟需解决的问题。随着人工智能技术的快速发展和普及,越来越多的企业开始将其应用于产品研发、销售流程优化、客…

一学就会---移除链表相同元素

文章目录 题目描述思路一:思路二: 题目描述 给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val val 的节点,并返回 新的头节点 。 示例: 思路一: 要移除链表中值为val的结…

Lecture 3 N-gram Language Models

目录 Probabilities: Joint to Conditional 概率:联合概率到条件概率The Markov Assumption 马尔可夫假设Maximum Likelihood Estimation 最大似然估计Book-ending Sequences 序列的开头和结尾Problems with N-gram models N-gram模型的问题Smoothing 平滑处理In Pr…

Mujoco210 Ubuntu 22.04配置安装

.1 下载 1.1 解压 先是下载软件包 然后 mkdir ~/.mujoco缩包所在位置(一般在下载目录下)在终端打开,输入以下命令将压缩包解压到.mujoco文件夹中: tar -zxvf mujoco210-linux-x86_64.tar.gz -C ~/.mujoco1.2 许可问题 有说mu…

concrt140.dll丢失怎么修复?concrt140.dll丢失的最新修复教程

今天准备打开电脑软件时候,当打开我自己的软件后,弹出了一个对话框,内容是:由于找不到concrt140.dll,无法继续执行代码。重新安装程序可能会解决此问题。 我很纳闷,前几天还好好着呢。于是我上网上查了一下…

八、EGL实践

第一部分基础概念 1)引入 之前的OpenGLes应用的开发都是使用类GLSurfaceView ,然而GLSurfaceView 是内部已经实现了EGL的封装,也就是对Display,surface,context的管理。因此我们也很方便的利用GLSurfaceView.Rendere…

零基础入门网络安全/Web安全,收藏这一篇就够了

前言 由于我之前写了不少网络安全技术相关的文章和回答,不少读者朋友知道我是从事网络安全相关的工作,于是经常有人私信问我: 我刚入门网络安全,该怎么学?要学哪些东西?有哪些方向?怎么选&…

高频面试八股文用法篇(八) == 和 equals 的区别

目录 区别 如何对equals重写 为何重写equals方法就得重写hashCode方法 扩展延伸 1、使用HashSet存储自定义类对象时为什么要重写equals和hashCode方法? 2、HashMap为什么要同时重写hashCode和equals方法 区别 一、对象类型不同 1、equals():是超类…

第二章:MySQL环境搭建

第二章:MySQL环境搭建 2.1:MySQL的下载、安装、配置 MySQL的四大版本 MySQL Community Server社区版本:开源免费、自由下载,但不提供官方技术支持,适用于大多数普通用户。MySQL Enterprise Edition企业版本&#xff1…

SpringBoot个人博客系统(含源码+数据库)

一、作品设计理念 个人博客系统是一个让个人可以通过互联网自由表达、交流和分享的平台,是个人展示自己思想、感受和经验的品牌。设计理念对于任何一个个人博客系统来说都非常重要,它直接影响到用户的使用体验和网站的整体感觉。 好的设计理念应该着眼于…

小红书热搜榜TOP1,多巴胺时尚爆火,怎么抄作业?

今夏时尚,明媚与简约并存。要说今年夏天什么最火?多巴胺必须拥有姓名。无论男女、老少、人宠,都被这股快乐风带飞。 “多巴胺”有多火?就只是彩色穿搭吗?各大博主、品牌若想加入,要怎么玩?今儿&…