基于 resnet 对 CIFAR-10 图片进行分类和网络微调

news2024/11/24 3:57:07

本文基于李沐老师的 实战 Kaggle 比赛:图像分类 (CIFAR-10)

文章目录

  • 数据格式
  • 数据加载
  • 标签转化
  • 划分数据集
  • 查看数据
  • 搭建网络
  • 主训练函数
  • 网络训练

数据格式

CIFAR-10 下载地址:https://www.kaggle.com/competitions/cifar-10/data
下拉到最下面有一个 Download All 选线,即可下载所有的文件,下载完成之后,完成解压。解压出来的东西如下:
在这里插入图片描述
1、test 文件夹中存放的为验证集的数据,比较多,解压可能需要消耗一点时间。
2、train 文件夹中存放的为训练集的数据。
3、sampleSubmission.csv 为你网络训练完后,测得验证机图片对应的标号填入的文件。在 kaggle 的比赛提交的结果就是这个。
4、trainLabels.csv 为训练集图片和其对应的标号对应关系。
数据没有为我们划分训练集和验证集,所以需要我们自己在 train 中划分出部分数据作为训练数据集。

数据加载

mport os
import math
import pandas as pd
import numpy as np
import torch
from torch import nn
import torchvision
from torchvision import transforms
import torchvision.models as models
from torch.utils.data import Dataset, DataLoader
from PIL import Image
import matplotlib.pyplot as plt
from tqdm import tqdm

class Cifar10Dataset(Dataset):
    def __init__(self, data_info, file_path, class_to_num, mode='train', transforms=transforms.ToTensor()):
        self.file_path = file_path
        self.mode = mode
        self.data_info = data_info
        self.data_len = len(self.data_info.index)
        self.transforms = transforms
    def __len__(self):
        # 返回当前数据集总的长度
        return self.data_len
    
    def __getitem__(self, index):
        # 根据输入的索引获取到当前图片的路径,并打开图片
        img_path = ""
        label = -1
        if self.mode == "train" or self.mode == "valid":
            # 读取训练集图片的路径
            img_path = self.file_path + "/train/" + str(self.data_info['id'][index]) + ".png"
            # 读取对应的标签,并将其转换为数字
            str_label = self.data_info['label'][index]
            label = class_to_num[str_label]
        else:
            img_path = self.file_path + "/test/" + str(self.data_info['id'][index]) + ".png"
        img_orin = Image.open(img_path)  
        
        # 对图像做增强处理
        img = self.transforms(img_orin) 
        return img, label

一般情况下我们需要针对训练集和验证集创建不同的 Dataset 加载方式,也可以使用逻辑单元来实现一个 Dataset 公用。
1、继承 Dataset 的作用:是 pytorch 中数据加载的默认方式类,我们继承它,实现其中的一些类方法之后,就可以使用 pytorch 自带的数据加载器的方法,在后面根据 batch_size 和 collate_fn 在 DataLoader 中加载我们的数据。
2、实现 __len__ (self)函数的作用:在后面我们使用 pytorch 中 DataLoader 来创建数据迭代器的时候,需要获取训练集呀、验证集呀和测试集合的长度,这样我们才可以根据 batch_size 来划分数据。
3、实现 __getitem__ (self, index) 函数的作用:输入一个索引值, 我们就可以获得对应索引的训练数据和对应的标号,主要是为了可以下面这样获得数据:Cifar10Dataset(index),在其中我们可以自定义我们自己的数据处理方式,需要注意的是,我们是根据一个 index 来获取 一个训练数据和一个对应的标号, 所以返回值为 img, label,在后面我们获取 batch_size 的大小的数据的时候,其中数据的存放方式为: ( img, label), ( img, label), .....( img, label),然而我们训练的时候 batch_size 的输入应该为 ( img, img, ..., img), ( lable, label, ..., label),所以单纯的依靠 __getitem__ (self, index) 的功能是不足的,依靠的是下面要将的 collate_fn 函数。

标签转化

# 定义文件的路径
root_path = "../data/cifar-10"

data_info = pd.read_csv(os.path.join(root_path, "trainLabels.csv"))

# 制造类别:字符串和int数值的转换
# 把label文件去重,排个序
str_labels = sorted(list(set(data_info['label'])))
n_classes = len(str_labels)
class_to_num = dict(zip(str_labels, range(n_classes)))
# 数字转成对应label,方便最后预测的时候使用
num_to_class = {v : k for k, v in class_to_num.items()}
print(class_to_num, "\n", num_to_class)

在 trainLabels.csv 中存放的标号为字符串,在训练的时候,我们一般使用为数字,这样就需要手动的转化。
1、class_to_num :存放的为从 string -> int 的标号的转化过程,在制造训练和测试迭代器的时候使用。
2、num_to_class :存放的为从 int -> string 的标号转化过程,在制造验证数据的最后结果和绘图展示的时候会用到。

划分数据集

# 在train数据及中划分出验证数据集
valid_ratio = 0.1
train_data_info = data_info.iloc[ : int(len(data_info.index) * (1 - valid_ratio)), :]
valid_data_info = data_info.iloc[int(len(data_info.index) * (1 - valid_ratio)):, :]
valid_data_info.index -= int(len(data_info.index) * (1 - valid_ratio))

#制造测试集的数据信息
test_data_info = None
test_csv_path = os.path.join(root_path,'sampleSubmission.csv')
test_data_info = pd.read_csv(test_csv_path)
   
#定义train、valid、test数据增强手段
train_data_transforms = transforms.Compose([
   # 图片很小,放大一下有利于后面的操作
   transforms.Resize(40),
   # 随机裁剪出⼀个⾼度和宽度均为40像素的正⽅形图像,
   # ⽣成⼀个⾯积为原始图像⾯积0.64到1倍的⼩正⽅形,
   # 然后将其缩放为⾼度和宽度均为32像素的正⽅形
   transforms.RandomResizedCrop(32, scale=(0.64, 1.0), ratio=(1.0, 1.0)),
   # 水平翻转
   torchvision.transforms.RandomHorizontalFlip(),
   # 变成张量
   transforms.ToTensor(),
   # 标准化图像的每个通道,数据来源于imagenet图片中的均值和方差
   torchvision.transforms.Normalize([0.4914, 0.4822, 0.4465],[0.2023, 0.1994, 0.2010])
])

test_data_transforms = transforms.Compose([
   # 变成张量
   transforms.ToTensor(),
   # 标准化图像的每个通道
   torchvision.transforms.Normalize([0.4914, 0.4822, 0.4465],[0.2023, 0.1994, 0.2010])
])
#分别创造train、valid、test 数据加载器
train_dataset = Cifar10Dataset(train_data_info, root_path, class_to_num, mode="train", transforms=train_data_transforms)
valid_dataset = Cifar10Dataset(valid_data_info, root_path, class_to_num, mode="valid", transforms=test_data_transforms)
test_dataset = Cifar10Dataset(test_data_info, root_path, class_to_num, mode="test", transforms=test_data_transforms)

#创建数据迭代器
batch_size = 256
num_workers = 0
train_iter = DataLoader(train_dataset, batch_size=batch_size, shuffle=False, num_workers=num_workers, drop_last=True)
valid_iter = DataLoader(valid_dataset, batch_size=batch_size, shuffle=False, num_workers=num_workers, drop_last=True)
test_iter = DataLoader(test_dataset, batch_size=batch_size, shuffle=False, num_workers=num_workers, drop_last=True)

1、valid_ratio: 为我们打算从原始的训练数据中划分一部分作为验证机的比列,一般训练数据集和验证的比例为 1:9,也可以更改,随情况而定。
2、test_data_info:在我们训练完成自己的网络之后,我们需要使用验证集的数据来验证我们的网络,也可以才用 batch_size 的方式来加快验证,所以这里也创建了一个 test_data_info。
3、train_data_transforms 和 test_data_transforms :分别对应着训练集的数据增强手段和验证、测试集的数据增强手段。
为什么训练集的数据增强手段要多余验证集的数据增强手段?
因为,一般训练的时候,我们希望网络更具有鲁棒性,减少过拟合,采用多种数据增强手段,可以在不改变数据总体分布情况的条件下,增加训练数据的泛化性,使得网络能够真的提取关键信息。
在验证的时候,我们一般仅仅需要网络获得当前数据的标号,所以一般情况下是不做过多的数据增强手段的。测试的时候也是相同的原理。
4、DataLoader 的作用:根据输入的 batch_size 的大小,从 dataset 批次性的创建迭代器,shuffle 是读取数据的时候,是否随机选取读取的 index,num_workers 是数据加载的时候所开启的线程,一般情况下读取数据的速度需要满足网络训练的速度,drop_last 是最后一个 batch_size 不够的情况下,丢弃掉。
其实其中还可以输入一个 collate_fn 的参数,此参数的作用,是将 ( img, label), ( img, label), .....( img, label) 换成网络可接受的输入形状:( img, img, ..., img), ( lable, label, ..., label),那为什么我们没有写自己的 collate_fn 函数呢,因为针对图片分类的问题, label 为一个维度,pytorch 官方默认的 collate_fn 函数可以帮我们处理,但是针对目标检测的数据,label 通常由 anchors 生成,造成了 anchor 是多维的,这个时候就需要我们在其中传递我们自己写的 collate_fn 函数。

查看数据

# 查看创建的迭代器是否可以正确的读取数据

def im_convert(tensor):
    image = tensor.to("cpu").clone().detach()
    image = image.numpy().squeeze()
    image = image.transpose(1,2,0)
    image = image.clip(0, 1)
    return image

# 创建画布
# fig = plt.figure(figsize=(20, 12))
# trainDataIter = iter(train_iter)
# validDataIter = iter(valid_iter)
# testDataIter = iter(test_iter)

# img, label = trainDataIter.next()

# # 显示网络真正加载的图片
# for i in range(8):
#     ax = fig.add_subplot(4,4,i+1,xticks=[],yticks=[])
#     ax.set_title(num_to_class[int(label[i])])
#     plt.imshow(im_convert(img[i]))

# #  显示数据增强之前的图片
# for i in range(8, 16):
#     ax = fig.add_subplot(4,4,i+1,xticks=[],yticks=[])
#     ax.set_title(num_to_class[int(label[i - 8])])
#     img = plt.imread(root_path + "/train/" + str(train_data_info['id'][i - 8]) + ".png")
#     plt.imshow(img)
# plt.show()

# img, label = validDataIter.next()

# # 显示网络真正加载的图片
# for i in range(8):
#     ax = fig.add_subplot(4,4,i+1,xticks=[],yticks=[])
#     ax.set_title(num_to_class[int(label[i])])
#     plt.imshow(im_convert(img[i]))

# #  显示数据增强之前的图片
# for i in range(8, 16):
#     ax = fig.add_subplot(4,4,i+1,xticks=[],yticks=[])
#     ax.set_title(num_to_class[int(label[i - 8])])
#     img = plt.imread(root_path + "/train/" + str(valid_data_info['id'][i - 8]) + ".png")
#     plt.imshow(img)
# plt.show()

# # 测试验证集
# img, label = testDataIter.next()
# # 显示网络真正加载的图片
# for i in range(8):
#     ax = fig.add_subplot(4,4,i+1,xticks=[],yticks=[])
#     plt.imshow(im_convert(img[i]))
# #  显示数据增强之前的图片
# for i in range(8, 16):
#     ax = fig.add_subplot(4,4,i+1,xticks=[],yticks=[])
#     img = plt.imread(root_path + "/test/" + str(test_data_info['id'][i - 8]) + ".png")
#     plt.imshow(img)
# plt.show()

从创建的迭代器中,拿出数据增强前后的图片现实,查看其中的不同,也是查看数据增强手段是否正确,trainDataIter 等是否编码正确。

搭建网络

# 下面开始定义自己的网络,
def get_resnet(num_classes, num_block=18, is_pretrained=False):
    net = None
    if num_block == 18:
        net = models.resnet18(pretrained=is_pretrained)
    elif num_block == 34:
        net = models.resnet34(pretrained=is_pretrained)
    elif num_block == 50:
        net = models.resnet50(pretrained=is_pretrained)
    elif num_block == 101:
        net = models.resnet101(pretrained=is_pretrained)
    elif num_block == 152:
        net = models.resnet152(pretrained=is_pretrained)
    else:
        raise "No model fit you num_blocks"
    net.fc = nn.Sequential(  # 替换最后一层
        nn.Linear(net.fc.in_features, num_classes)
    )
    return net

这里我们没有自定义网络,而是从预训练模型中加载了不同的 resnet 网络来当作训练网络。其中

net.fc = nn.Sequential(  # 替换最后一层
        nn.Linear(net.fc.in_features, num_classes)
    )

因为原始的 resnet 是做 1000 类别的分类,我们这里只需要做 10 类的分类,所以我们需要将最后一层换掉,重新设置需要分类的分类数目。

主训练函数

import datetime
# 定义自己的主训练函数
def train_Cifar10(net, device, train_iter, valid_iter, num_epochs, learning_rate, weight_decay, lr_period, lr_decay):
    print("training on: ", device)
    
    # 定义自己的优化器
    optm = torch.optim.SGD(net.parameters(), lr=learning_rate, weight_decay=weight_decay, momentum=0.9)
    
    # 定义自己学习率的下降公式, 每经过lr_period个epoch,学习率lr=lr*lr_decay
    scheduler = torch.optim.lr_scheduler.StepLR(optm, lr_period, lr_decay)
    
    # 定义自己的损失函数
    loss = nn.CrossEntropyLoss()
    
    # 开始 epochs 的训练
    for epoch in range(num_epochs):
        train_loss = []
        train_acc = []
        valid_loss = []
        valid_acc = []
        
        # 让网络进入训练模式,防止后面在验证集上进入评价模式
        net.train()
        pmgressbar_train = tqdm(train_iter, desc=f'Train epoch {epoch + 1 } / {num_epochs}', postfix=dict, mininterval=0.3)
        for imgs, labels in pmgressbar_train:
            with torch.no_grad():
                # 数据放到 gpu 设备上
                imgs = imgs.to(device)
                labels = labels.to(device)
            
            # 送入网络获得输出
            preds = net(imgs)
            
            # 优化器梯度清零
            optm.zero_grad()
            
            # 计算损失
            l = loss(preds, labels)
            
            # 反向传播
            l.backward()
            
            # 使用优化器更新权重
            optm.step()
            
            # 计算损失和计算准确率
            acc = (preds.argmax(dim=-1) == labels).float().mean().item()
            train_loss.append(l.item())
            train_acc.append(acc)
            
            train_loss_mean = sum(train_loss) / len(train_loss)
            
            pmgressbar_train.set_postfix(**{'train_loss' : train_loss_mean,
                                            'train_acc'  : acc})
            pmgressbar_train.update()

        pmgressbar_train.close()
        
        # 每个epoch训练完,更新一下学习率
        scheduler.step()

        train_loss_mean = sum(train_loss) / len(train_loss)
        train_acc_mean  = sum(train_acc) / len(train_acc)
        print(f'the {epoch+1} epoch, loss = {train_loss_mean:.5f}, train_acc = {train_acc_mean:.5f}')
        
        if ((epoch % 5) == 0 or epoch == (num_epochs - 1)):
            # 网络进入验证模式
            net.eval()
            pmgressbar_valid = tqdm(valid_iter, desc=f'Test epoch {epoch + 1 } / {num_epochs}', postfix=dict, mininterval=0.3)
            for imgs, labels in pmgressbar_valid:
                with torch.no_grad():
                    # 数据放到 gpu 设备上
                    imgs = imgs.to(device)
                    labels = labels.to(device)
                # 获得网络的输出
                preds = net(imgs)
                
                # 获得精度和验证机损失
                valid_l = loss(preds, labels)
                valid_loss.append(valid_l.item())
                acc = (preds.argmax(dim=-1)==labels).float().mean().item()
                valid_acc.append(acc)
              
                valid_loss_mean = sum(valid_loss) / len(valid_loss)
                pmgressbar_valid.set_postfix(**{'valid_loss_mean' : valid_loss_mean,
                                                'valid_acc_mean'  : acc})
            pmgressbar_valid.update()
            valid_loss_mean = sum(valid_loss) / len(valid_loss)
            valid_acc_mean  = sum(valid_acc) / len(valid_acc)
            print(f'the {epoch+1} epoch, loss = {valid_loss_mean:.5f}, valid_acc = {valid_acc_mean:.5f}')
            pmgressbar_valid.close()
    
    # 所有epoch全部跑完, 保存权重
    path = "../data/" + datetime.datetime.now().strftime('%Y_%m_%d_%H_%M_%S') + '_renet_params.pth'
    torch.save({'model': net.state_dict()}, path)
    print('Training completed!!')

# 定义测试函数
def test_Cifar10(net, device, test_iter, file):
    net.eval()
    all_preds_labels = []
    for imgs, _ in tqdm(test_iter):
        # 送入网络获得预测输出
        preds = net(imgs)
        # 对预测的最后一个维度计算最大值的位置
        preds_labels = preds.argmax(dim=-1).cpu.numpy().tolist()
        # 记录下所有的预测的标签
        all_preds_labels.append([num_to_class(preds_label)  for preds_label in preds_labels ])
    
    print('all test img numbers is :', len(all_preds_labels))
    
    return all_preds_labels

# 获取当前的GPU, 没有的话使用CPU
def get_device():
    return 'cuda' if torch.cuda.is_available() else 'cpu'  

train_Cifar10: 是主训练函数,其中 torch.optim.lr_scheduler.StepLR 是一种阶梯性的学习率下降公式,每经过 lr_period 个 epoch,学习率 lr=lr*lr_decay。
test_Cifar10 :是测试函数,输入数据集,获取网络预测结果,再将预测的结果转换为 string 类型,写入对应的 csv 文件中。

网络训练

# 开始建立训练参数,并开始训练

# 获取训练的设备
device = get_device()

# 获取网络模型
train_net = get_resnet(10, num_block=18, is_pretrained=True)
train_net = train_net.to(device)
# 训练的超参数
num_epochs, learning_rate, weight_decay, lr_period, lr_decay = 50, 2e-4, 5e-4, 4, 0.9
train_Cifar10(train_net, device, train_iter, valid_iter, num_epochs, learning_rate, weight_decay, lr_period, lr_decay)

训练的时候将会现实如下的训练进度
在这里插入图片描述
这是正常训练的结果,后面使用 test_Cifar10 函数将验证得到的数据写入 csv 文件即可。

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

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

相关文章

JavaScript中的Event(事件)详解

Event 对象 Event 对象代表事件的状态,比如事件在其中发生的元素、键盘按键的状态、鼠标的位置、鼠标按钮的状态。 事件通常与函数结合使用,函数不会在事件发生前被执行! 事件句柄 (Event Handlers) HTML 4.0 的新特性之一是能够使 HTML 事…

Maven uber-jar(带依赖的打包插件)maven-shade-plugin

文章目录 最基础的 maven-shade-plugin 使用生成可执行的 Jar 包 和 常用的资源转换类包名重命名打包时排除依赖与其他常用打包插件比较 本文是对 maven-shade-plugin 常用配置的介绍,更详细的学习请参照 Apache Maven Shade Plugin 官方文档 通过使用 maven-shade…

BetaFlight飞控启动运行过程简介疑问跟踪

BetaFlight飞控启动&运行过程简介疑问跟踪 1. 源由2. 【已解存疑】问题一:6.1 Why desiredPeriodCycles is so important to Betaflight task?3. 【已解】问题二:6.2 What root cause has made gyro task to been overrun, so scheduler has to ski…

轮式机械臂小车实现语音控制

1. 功能说明 本文实例将实现语音控制R214e样机运动(前进、后退、左转、右转、拿起、放下)的功能。 2. 电子硬件 在这个示例中,我们采用了以下硬件,请大家参考: 主控板 Basra主控板(兼容Arduino Uno&#x…

港科夜闻|推进湾区产学研融合发展,香港科大(广州)—广州市属国企校企合作专题交流会圆满举行...

关注并星标 每周阅读港科夜闻 建立新视野 开启新思维 1、推进湾区产学研融合发展,香港科大(广州)—广州市属国企校企合作专题交流会圆满举行。本次交流会由香港科大(广州)、广州市人民政府国有资产监督管理委员会主办,越秀集团协办,旨在充分发…

springboot+jsp网上图书商城销售系统java

开发环境 开发语言:Java 框架:springboot 技术:JSP JDK版本:JDK1.8 服务器:tomcat7 数据库:mysql 数据库工具:Navicat11 开发软件:eclipse/myeclipse/idea Maven包:Maven…

ubuntu 20.04虚拟内存

ubuntu 20.04虚拟内存 1. 查看自己的虚拟内存 使用top命令或者使用free命令 2. 创建虚拟内存配置文件 # 新建文件夹 cd ~ mkdir swap cd swap# bs 为块的大小,count 创建多少个块 sudo dd if/dev/zero ofswapfile bs1M count2048# 修改权限 sudo chmod 0600 …

系统分析师:六、企业信息化战略与实施 练习题

目录 1、十二五的七大国家战略新兴产业体系 2、标准化的三个方面 3、信息的属性 4、电子政务描述 5、数据产品管理 6、企业门户 7、企业应用集成 8、电子商务标准体系 9、电子商务标准范畴 10、知识管理显性和隐形知识 11、决策支持系统 12、CRM解决方案 13、CRM核…

用Vue写教务系统学生管理

文章目录 一.首先创建新的Demo二.在APP里面绑定DemoStudent三.源码附上四.效果图&#xff08;新增记录还未实现&#xff09; 一.首先创建新的Demo 二.在APP里面绑定DemoStudent <template><img alt"Vue logo" src"./assets/logo.png"><!--…

华为OD机试真题 Java 实现【最小的调整次数】【2023Q1 100分】

一、题目描述 有一个特异性的双端队列&#xff0c;该队列可以从头部或尾部添加数据&#xff0c;但是只能从头部移出数据。 小A依次执行2n个指令往队列中添加数据和移出数据。 其中n个指令是添加数据 (可能从头部添加、也可能从尾部添加)&#xff0c;依次添加1到n&#xff0c…

超稳定ChatGPT镜像网站,小白适用,赶紧收藏【持续更新中】

&#x1f482;作者简介&#xff1a; THUNDER王&#xff0c;一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学会计学专业大二本科在读&#xff0c;同时任汉硕云&#xff08;广东&#xff09;科技有限公司ABAP开发顾问。在学习工作中&#xff0c;我通常使用偏后…

无代码开发引路,重塑企业数字生产力

互联网时代&#xff0c;信息的传递与共享成为企业运营的核心环节。传统的软件开发模式已经不能满足企业数字化转型的需要&#xff0c;而无代码开发平台可以在不改变底层技术架构的前提下&#xff0c;提供一种高效、低成本、灵活、易用的软件开发解决方案。相较于传统代码编写&a…

[山海关crypto 训练营 day13]

日常鼓励自己&#xff1a;世界上只有想不通的人&#xff0c;没有走不通的路。 [长安杯 2021]checkin 题目代码和数据 from Crypto.Util.number import * from secret import flag p getPrime(1024) q getPrime(16) n p*q m bytes_to_long(flag) for i in range(1,p-q):m…

事务AOP

事务&AOP 事务管理 在数据库中我们已经学过事务了。 事务是一组操作的集合&#xff0c;它是不可再分的工作单位。事务会把所有的操作作为一个整体&#xff0c;一起向数据库提交或者撤销操作请求。所以这组操作要么是同时成功&#xff0c;要么同时失败。 事务的操作主要…

【共用体和枚举】

共用体 一种构造类型的数据结构 共用体和结构体类似&#xff0c;也是一种构造类型的数据结构。 既然是构造类型的&#xff0c;就需要先定义出类型&#xff0c;然后用类型定义变量。 定义共用体类型的方法和结构体非常相似&#xff0c;把struct 改成union 就可以了。 在进行某…

美颜SDK的应用与优化:一次全面探究

在直播过程中&#xff0c;美颜技术可以帮助主播实现更好的视觉效果&#xff0c;从而吸引更多的关注和粉丝。因此&#xff0c;直播美颜SDK的应用和优化已经成为了直播行业中的一个重要研究方向。 一、直播美颜SDK的应用 1. 美颜滤镜 美颜滤镜是直播美颜SDK中最常用的一种技术…

【Mybatis】Mybatis处理一对多、多对多关系映射-四

唠嗑部分 上篇文章我们说了Mybatis中ORM映射的几种方式&#xff0c;相关文章&#xff1a; 【Mybatis】简单入门及工具类封装-一 【Mybatis】如何实现ORM映射-二 【Mybatis】Mybatis的动态SQL、缓存机制-三 这篇文章我们来说说Mybatis如何处理一对一、一对多、多对多的关系映射…

Linux下网络编程(2)——socket的函数们

accept()函数 服务器调用 listen()函数之后&#xff0c;就会进入到监听状态&#xff0c;等待客户端的连接请求&#xff0c;使用 accept()函数获取客户端的连接请求并建立连接。函数原型如下所示&#xff1a; int accept(int sockfd, struct sockaddr *addr, socklen_t *addrle…

RK3588光电载荷处理板研制进展

本来就是一个很小众的市场&#xff0c;但是偶尔也会有同行询问&#xff0c;这儿就简单汇报一下后期的进展 板子已经开发完成&#xff0c;并有幸得到了两个订单&#xff0c;虽然量不是很大&#xff0c;但是也很开心由于一段时间的努力和付出&#xff0c;将该设备应用在了国防事业…

【Linux】Linux文件目录结构

Linux文件目录结构 在 Linux 中&#xff0c;其文件目录结构是一颗类似于多叉树的结构&#xff0c;所有目录都在 / &#xff08;根目录&#xff09;下面&#xff0c;每个非叶节点代表一个目录&#xff0c;叶节点代表文件。 一般结构如下所示&#xff1a; usr :“Unix Software …