python打卡训练营打卡记录day51

复习日

作业:day43的时候我们安排大家对自己找的数据集用简单cnn训练,现在可以尝试下借助这几天的知识来实现精度的进一步提高

数据预处理

import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms, models
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image
import os
from sklearn.model_selection import train_test_split
from shutil import copyfiledata_root = "flowers"  # 数据集根目录
classes = ["daisy", "tulip", "rose", "sunflower", "dandelion"]  for folder in ["train", "val", "test"]:os.makedirs(os.path.join(data_root, folder), exist_ok=True)# 数据集划分
for cls in classes:cls_path = os.path.join(data_root, cls)if not os.path.isdir(cls_path):raise FileNotFoundError(f"类别文件夹{cls}不存在!请检查数据集路径。")imgs = [f for f in os.listdir(cls_path) if f.lower().endswith((".jpg", ".jpeg", ".png"))]if not imgs:raise ValueError(f"类别{cls}中没有图片文件!")# 划分数据集(测试集20%,验证集20% of 剩余数据,训练集60%)train_val, test = train_test_split(imgs, test_size=0.2, random_state=42)train, val = train_test_split(train_val, test_size=0.25, random_state=42)  # 0.8*0.25=0.2(验证集占比)# 复制到train/val/test下的类别子文件夹for split, imgs_list in zip(["train", "val", "test"], [train, val, test]):split_class_path = os.path.join(data_root, split, cls)os.makedirs(split_class_path, exist_ok=True)for img in imgs_list:src_path = os.path.join(cls_path, img)dst_path = os.path.join(split_class_path, img)copyfile(src_path, dst_path)# 设置中文字体支持
plt.rcParams["font.family"] = ["SimHei"]
plt.rcParams['axes.unicode_minus'] = False# 检查GPU是否可用
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"使用设备: {device}")# 训练集数据增强
train_transform = transforms.Compose([transforms.Resize((224, 224)),transforms.RandomCrop(224, padding=4),transforms.RandomHorizontalFlip(),transforms.ColorJitter(brightness=0.2, contrast=0.2),transforms.ToTensor(),transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225))
])# 测试集预处理
test_transform = transforms.Compose([transforms.Resize((224, 224)),transforms.ToTensor(),transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225))
])# 加载数据集
train_dataset = datasets.ImageFolder(root=os.path.join(data_root, "train"),  transform=train_transform
)val_dataset = datasets.ImageFolder(root=os.path.join(data_root, "val"),transform=test_transform
)test_dataset = datasets.ImageFolder(root=os.path.join(data_root, "test"),transform=test_transform
)# 创建数据加载器
batch_size = 32
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=2)
val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False, num_workers=2)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False, num_workers=2)# 获取类别名称
class_names = train_dataset.classes
print(f"检测到的类别: {class_names}")

通道注意力

class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_ratio=16):super(ChannelAttention, self).__init__()self.avg_pool = nn.AdaptiveAvgPool2d(1)self.fc = nn.Sequential(nn.Linear(in_channels, in_channels // reduction_ratio, bias=False),nn.ReLU(inplace=True),nn.Linear(in_channels // reduction_ratio, in_channels, bias=False),nn.Sigmoid())def forward(self, x):batch_size, channels, _, _ = x.size()avg_pool_output = self.avg_pool(x).view(batch_size, channels)channel_weights = self.fc(avg_pool_output).view(batch_size, channels, 1, 1)return x * channel_weights

空间注意力

class SpatialAttention(nn.Module):"""空间注意力模块"""def __init__(self, kernel_size=7):super(SpatialAttention, self).__init__()self.conv = nn.Conv2d(2, 1, kernel_size, padding=kernel_size//2, bias=False)self.sigmoid = nn.Sigmoid()def forward(self, x):# 沿通道维度计算均值和最大值avg_out = torch.mean(x, dim=1, keepdim=True)max_out, _ = torch.max(x, dim=1, keepdim=True)# 拼接均值和最大值特征concat = torch.cat([avg_out, max_out], dim=1)# 卷积操作生成空间注意力图spatial_att = self.conv(concat)spatial_att = self.sigmoid(spatial_att)# 应用空间注意力return x * spatial_att

CBAM注意力

class CBAM(nn.Module):"""CBAM注意力模块:结合通道注意力和空间注意力"""def __init__(self, in_channels, reduction_ratio=16, kernel_size=7):super(CBAM, self).__init__()self.channel_attention = ChannelAttention(in_channels, reduction_ratio)self.spatial_attention = SpatialAttention(kernel_size)def forward(self, x):# 先应用通道注意力x = self.channel_attention(x)# 再应用空间注意力x = self.spatial_attention(x)return x

定义带CBAM的ResNet18模型

class FlowerCNN(nn.Module):def __init__(self, num_classes=5):super(FlowerCNN, self).__init__()# 加载预训练ResNet18resnet = models.resnet18(pretrained=True)# 构建特征提取器,在每个残差块阶段后插入CBAM模块self.features = nn.Sequential(resnet.conv1,resnet.bn1,resnet.relu,resnet.maxpool,resnet.layer1,              # 输出通道64CBAM(64),                   # CBAM模块(64通道)resnet.layer2,              # 输出通道128CBAM(128),                  # CBAM模块(128通道)resnet.layer3,              # 输出通道256CBAM(256),                  # CBAM模块(256通道)resnet.layer4,              # 输出通道512CBAM(512)                   # CBAM模块(512通道))self.gap = nn.AdaptiveAvgPool2d(1)# 自定义分类头self.fc = nn.Sequential(nn.Flatten(),nn.Linear(512, 512),nn.ReLU(),nn.Dropout(0.5),nn.Linear(512, num_classes))def forward(self, x):x = self.features(x)x = self.gap(x) x = self.fc(x)return x

初始化模型

model = FlowerCNN(num_classes=5).to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', patience=3)def train_model(model, train_loader, val_loader, epochs=10):best_val_acc = 0.0train_loss_history = []val_loss_history = []train_acc_history = []val_acc_history = []for epoch in range(epochs):model.train()running_loss = 0.0correct = 0total = 0for batch_idx, (data, target) in enumerate(train_loader):data, target = data.to(device), target.to(device)optimizer.zero_grad()outputs = model(data)loss = criterion(outputs, target)loss.backward()optimizer.step()running_loss += loss.item()_, predicted = torch.max(outputs.data, 1)total += target.size(0)correct += (predicted == target).sum().item()if (batch_idx+1) % 50 == 0:print(f"Epoch [{epoch+1}/{epochs}] Batch {batch_idx+1}/{len(train_loader)} "f"Loss: {loss.item():.4f} Acc: {(100*correct/total):.2f}%")epoch_train_loss = running_loss / len(train_loader)epoch_train_acc = 100. * correct / total# 验证集评估model.eval()val_loss = 0.0val_correct = 0val_total = 0with torch.no_grad():for data, target in val_loader:data, target = data.to(device), target.to(device)outputs = model(data)val_loss += criterion(outputs, target).item()_, predicted = torch.max(outputs.data, 1)val_total += target.size(0)val_correct += (predicted == target).sum().item()epoch_val_loss = val_loss / len(val_loader)epoch_val_acc = 100. * val_correct / val_totalscheduler.step(epoch_val_loss)train_loss_history.append(epoch_train_loss)val_loss_history.append(epoch_val_loss)train_acc_history.append(epoch_train_acc)val_acc_history.append(epoch_val_acc)print(f"Epoch {epoch+1} 完成 | 训练损失: {epoch_train_loss:.4f} 验证准确率: {epoch_val_acc:.2f}%")if epoch_val_acc > best_val_acc:torch.save(model.state_dict(), "best_flower_model.pth")best_val_acc = epoch_val_accprint("保存最佳模型...")# 绘制训练曲线plt.figure(figsize=(12, 4))plt.subplot(1, 2, 1)plt.plot(train_loss_history, label='训练损失')plt.plot(val_loss_history, label='验证损失')plt.title('损失曲线')plt.xlabel('Epoch')plt.ylabel('损失值')plt.legend()plt.subplot(1, 2, 2)plt.plot(train_acc_history, label='训练准确率')plt.plot(val_acc_history, label='验证准确率')plt.title('准确率曲线')plt.xlabel('Epoch')plt.ylabel('准确率 (%)')plt.legend()plt.tight_layout()plt.show()return best_val_acc

训练模型

print("开始训练...")
final_acc = train_model(model, train_loader, val_loader, epochs=15)
print(f"训练完成!最佳验证准确率: {final_acc:.2f}%")from torch.nn import functional as F
import cv2 
import numpy as np
import torchvision.transforms as transformsclass GradCAM:def __init__(self, model, target_layer_name="features.10.1.conv2"):"""target_layer_name说明:- features.10 对应resnet.layer4(索引10)- .1.conv2 对应layer4中第二个残差块的第二个卷积层"""self.model = model.eval()self.target_layer_name = target_layer_nameself.gradients = Noneself.activations = Nonefor name, module in model.named_modules():if name == target_layer_name:module.register_forward_hook(self.forward_hook)module.register_backward_hook(self.backward_hook)breakdef forward_hook(self, module, input, output):self.activations = output.detach()def backward_hook(self, module, grad_input, grad_output):self.gradients = grad_output[0].detach()def generate(self, input_image, target_class=None):outputs = self.model(input_image)if target_class is None:target_class = torch.argmax(outputs, dim=1).item()self.model.zero_grad()one_hot = torch.zeros_like(outputs)one_hot[0, target_class] = 1outputs.backward(gradient=one_hot)gradients = self.gradientsactivations = self.activationsweights = torch.mean(gradients, dim=(2, 3))cam = torch.sum(activations[0] * weights[0][:, None, None], dim=0)cam = F.relu(cam)cam = (cam - cam.min()) / (cam.max() - cam.min() + 1e-8)cam = F.interpolate(cam.unsqueeze(0).unsqueeze(0),size=(224, 224),mode='bilinear', align_corners=False).squeeze()return cam.cpu().numpy(), target_classdef visualize_gradcam(img_path, model, class_names, alpha=0.6):img = Image.open(img_path).convert("RGB")img = img.resize((224, 224))img_np = np.array(img) / 255.0transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize(mean=(0.485, 0.456, 0.406),std=(0.229, 0.224, 0.225))])input_tensor = transform(img).unsqueeze(0).to(device)grad_cam = GradCAM(model, target_layer_name="features.10.1.conv2")heatmap, pred_class = grad_cam.generate(input_tensor)heatmap = np.uint8(255 * heatmap)heatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET)heatmap = heatmap / 255.0heatmap_rgb = heatmap[:, :, ::-1]superimposed = cv2.addWeighted(img_np, 1 - alpha, heatmap, alpha, 0)plt.figure(figsize=(12, 4))plt.subplot(1, 3, 1)plt.imshow(img_np)plt.title(f"原始图像\n真实类别: {img_path.split('/')[-2]}")plt.axis('off')plt.subplot(1, 3, 2)plt.imshow(heatmap_rgb)plt.title(f"Grad-CAM热力图\n预测类别: {class_names[pred_class]}")plt.axis('off')plt.subplot(1, 3, 3)plt.imshow(superimposed)plt.title("叠加热力图")plt.axis('off')plt.tight_layout()plt.show()
开始训练...
Epoch [1/15] Batch 50/81 Loss: 0.6559 Acc: 70.81%
Epoch 1 完成 | 训练损失: 0.7685 验证准确率: 62.54%
保存最佳模型...
Epoch [2/15] Batch 50/81 Loss: 0.4877 Acc: 79.75%
Epoch 2 完成 | 训练损失: 0.5815 验证准确率: 72.83%
保存最佳模型...
Epoch [3/15] Batch 50/81 Loss: 0.4116 Acc: 82.88%
Epoch 3 完成 | 训练损失: 0.4738 验证准确率: 83.24%
保存最佳模型...
Epoch [4/15] Batch 50/81 Loss: 0.3755 Acc: 85.00%
Epoch 4 完成 | 训练损失: 0.4515 验证准确率: 82.31%
Epoch [5/15] Batch 50/81 Loss: 0.6060 Acc: 85.81%
Epoch 5 完成 | 训练损失: 0.3845 验证准确率: 75.84%
Epoch [6/15] Batch 50/81 Loss: 0.4477 Acc: 86.94%
Epoch 6 完成 | 训练损失: 0.3705 验证准确率: 82.77%
Epoch [7/15] Batch 50/81 Loss: 0.3701 Acc: 89.38%
Epoch 7 完成 | 训练损失: 0.3345 验证准确率: 84.97%
保存最佳模型...
Epoch [8/15] Batch 50/81 Loss: 0.2666 Acc: 89.75%
Epoch 8 完成 | 训练损失: 0.3281 验证准确率: 83.93%
Epoch [9/15] Batch 50/81 Loss: 0.1533 Acc: 89.44%
Epoch 9 完成 | 训练损失: 0.3294 验证准确率: 83.47%
Epoch [10/15] Batch 50/81 Loss: 0.2991 Acc: 90.94%
Epoch 10 完成 | 训练损失: 0.2643 验证准确率: 83.82%
Epoch [11/15] Batch 50/81 Loss: 0.4048 Acc: 90.94%
Epoch 11 完成 | 训练损失: 0.2640 验证准确率: 89.25%
保存最佳模型...
Epoch [12/15] Batch 50/81 Loss: 0.1055 Acc: 92.50%
Epoch 12 完成 | 训练损失: 0.2396 验证准确率: 81.62%
Epoch [13/15] Batch 50/81 Loss: 0.3020 Acc: 92.81%
Epoch 13 完成 | 训练损失: 0.2298 验证准确率: 83.24%
Epoch [14/15] Batch 50/81 Loss: 0.1166 Acc: 92.69%
Epoch 14 完成 | 训练损失: 0.2228 验证准确率: 86.47%
Epoch [15/15] Batch 50/81 Loss: 0.1193 Acc: 93.38%
Epoch 15 完成 | 训练损失: 0.2004 验证准确率: 85.43%

训练完成!最佳验证准确率: 89.25%

选择训练图像

test_image_path = "flowers/tulip/100930342_92e8746431_n.jpg"  
visualize_gradcam(test_image_path, model, class_names)

day43简单cnn模型训练结果

准确率69.94%

对比分析

(一)损失曲线分析

1. 改进模型(ResNet18 + CBAM + GAP)

  • 训练损失:随 Epoch 推进持续下降(最终稳定在 0.2 左右),说明预训练骨干网 + 注意力机制有效拟合数据模式,模型学习能力强。
  • 验证损失:前期快速下降(Epoch 1-3),中期小幅震荡(Epoch 4-12),后期趋于平稳。震荡源于 CBAM 动态调整注意力区域,短期影响泛化性,但整体趋势验证模型未过度拟合。

2. 简单 CNN 模型

  • 训练损失:快速下降后陷入平缓(长期维持在 1 左右),反映模型复杂度不足,难以挖掘深层特征。
  • 验证损失:始终高于训练损失,且与训练损失差距小,典型 “欠拟合”—— 模型未充分学习数据模式,泛化性极差。

(二)准确率曲线分析

1. 改进模型(ResNet18 + CBAM + GAP)

  • 训练准确率:稳步攀升至 93%+,体现模型对训练数据的强拟合能力,CBAM 注意力有效聚焦关键特征(如花瓣、花蕊)。
  • 验证准确率:最高达 89.25%(Epoch 11),虽有波动但整体趋势向上。波动因注意力机制对复杂场景(如花朵密集、背景干扰)的动态适应,验证模型具备一定泛化性。

2. 简单 CNN 模型

  • 训练准确率:缓慢爬坡至 70%+,因模型结构简单(如仅含基础卷积、池化),无法提取细粒度特征(如郁金香与玫瑰的花瓣差异 )。
  • 验证准确率:始终低于训练集且波动大,最高仅 69.94%,暴露模型对未见过数据的弱适应能力,实际应用价值低。

(三)GradCAM 热力图对比

1. 改进模型

  • 样本说明:输入为郁金香(tulip)图像,模型正确分类,热力图覆盖花朵集中区域。
  • 优势体现
    • 聚焦性:高亮区域精准覆盖郁金香主体,CBAM 引导模型关注与类别强相关的视觉特征(如花瓣形状、颜色分布 )。
    • 解释性:叠加热力图清晰展示 “模型依据花朵区域判断类别”,验证注意力机制的有效性,为分类结果提供可解释依据。

2. 简单 CNN 模型

  • 样本说明:同一张郁金香图像,简单 CNN 虽最终正确分类,但热力图激活区域分散、模糊
  • 缺陷暴露
    • 特征利用低效:模型依赖全局特征 “碰运气”,未聚焦关键区分区域(如花瓣细节 ),遇到相似类别(如郁金香 vs 风信子 )极易误判。
    • 解释性差:热力图无法说明分类依据,实际应用中难排查错误原因,可靠性低。

总结

改进模型通过 “预训练骨干网 + 注意力机制 + 全局池化” 组合,解决了简单 CNN 的三大痛点:

  1. 特征提取弱:精准聚焦花朵关键区域,挖掘细粒度特征(如花瓣纹理、颜色渐变 )。
  2. 泛化性不足:训练 / 验证曲线趋势验证模型具备学习复杂模式的能力,适配真实场景干扰(如背景复杂、花朵密集 )。
  3. 解释性差:GradCAM 热力图清晰展示分类依据,为模型可靠性提供可视化支撑。

@浙大疏锦行

 

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

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

相关文章

网络安全:OWASP防护守则

目录 一、OWASP十大WEB弱点防护守则 二、防护守则 1、权限控制失效 2、加密失误 3、注入 4、不安全设计 5、安全配置缺陷 6、易受攻击和过时的组件 7、身份认证和会话管理失效 8、缺乏完整性校验 9、缺乏监控与日志记录 10、服务端请求伪造 三、核心防护原则总结 …

Dagster 实现数据质量自动化:6大维度检查与最佳实践

在当今数据驱动的世界中,数据质量的重要性不言而喻。数据质量测试是确保数据准确、完整、一致和可靠的关键步骤。本文将深入探讨数据质量测试的六大维度,并提供相关的检查方法和最佳实践。 什么是数据质量测试? 数据质量测试涉及评估数据以确…

计算机视觉之三维重建(深入浅出SfM与SLAM核心算法)—— 2. 摄像机标定

文章目录 1. 前置知识1.1. 非齐次线性方程组求解1.1.1. 传统求解方法1.1.2. 奇异值分解法1.1.3. 牛顿法或者梯度下降法 1.2. 齐次线性方程组的最小二乘解1.3. 非线性方程组的最小二乘解 2. 相机标定2.1. 相机内参数求解2.1.1. 求解 u 0 u_0 u0​ 和 v 0 v_0 v0​2.1.2. 求解 …

SQLLL

595-big-countries https://leetcode.com/problems/big-countries/description/ 面积最大的国家 -- select name, population, area from World where area > 3000000 or population > 25000000596-classes-with-at-least-5-students https://leetcode.com/problems/…

MySQL中触发器详解 触发器在自动化任务中的应用场景

触发器是mysql中与表关联的数据库对象,能在特定操作(如insert、update、delete)发生时自动执行预定义sql逻辑。其核心用途包括:1. 维护数据一致性,如订单插入后自动减少库存;2. 记录审计日志,如…

MySQL 8.0的数据库root用户默认无法远程登录,需要修改root的远程授权

mysql> grant all privileges on . to ‘root’‘%’; ERROR 1410 (42000): You are not allowed to create a user with GRANT mysql> use mysql; Reading table information for completion of table and column names You can turn off this feature to get a quick…

​​MPI + OpenMP 环境配置指南(Windows/Linux)​

—— 让你的并行计算飞起来 🚀 1. 简介​​ ​​MPI (Message Passing Interface)​​:用于多机分布式并行计算(进程级并行)。​​OpenMP​​:用于单机多线程并行计算(线程级并行)。​​混合编…

新闻类鸿蒙应用功耗危机以及优化方案

🔋 ​​一、功耗痛点:新闻类应用成“续航杀手”​​ ​​后台进程失控​​ ​​高频刷新​​:未适配应用(如网易新闻、百度客户端)默认每30秒后台刷新内容,触发CPU持续唤醒,单设备日均耗电增加1…

【小工具】-Doxygen01

0、前言 参考帖子。 使用Doxygen Documentation Generator自动添加注释 Doxygen使用教程 代码注释规范之Doxygen 1、Doxygen介绍 Doxygen 是一个功能强大的开源文档生成工具,主要用于从源代码中自动提取注释并生成专业的 API 文档。它支持多种编程语言&#xff…

大模型Transformer触顶带来的“热潮退去”,稀疏注意力架构创新或是未来

1. 大模型退潮:裸泳者离场,创新者浮出水面 资本热潮逐渐冷却,大模型赛道正经历残酷洗牌。过去两年密集的“百模大战”,本质是商业模式的军备竞赛,用数据规模与参数数量掩盖技术同质化。当DeepSeek以61层精简架构挑战千…

Android编译时打印所有引用的so库路径

在app module build.gradle 最后添加脚本 tasks.whenTaskAdded { task -> println("test 11 task.name:"task.name) if (task.name.startsWith(merge) && task.name.endsWith(NativeLibs)) { task.doFirst { prin…

暴雨亮相2025中关村论坛数字金融与金融安全大会

6月10日,由中关村金融科技产业发展联盟与中关村互联网金融研究院主办的“2025中关村论坛系列活动——数字金融与金融安全大会”在中关村展示中心盛大召开。本次大会以“人工智能机遇:未来金融格局重塑及安全治理”为主题,汇聚政产学研各界精英…

mapstruct中的@Mapper注解详解

在MapStruct中,Mapper注解是核心注解之一,用于标记一个接口或抽象类为MapStruct的映射器(Mapper)。MapStruct会在编译时自动生成该接口的实现类,完成对象之间的属性映射。以下是对Mapper注解的详细解析: 1.…

uniapp+vue2+h5图片下载保存,微信浏览器、非微信浏览器

小程序端 onDown() {// 检查相册权限uni.authorize({scope: scope.writePhotosAlbum,success: () > {this.downloadImage();},fail: () > {uni.showToast({title: "请授权相册权限",icon: "none"});}}); }, downloadImage() {common.request(post, …

NumPy 与 OpenCV 版本兼容性深度解析:底层机制与解决方案

在计算机视觉项目中,NumPy 和 OpenCV 的兼容性问题常被低估,实则暗藏复杂的技术陷阱。下面从底层机制深入剖析核心兼容性问题及解决方案: 一、内存布局冲突:数组连续性陷阱 问题本质: OpenCV 的 C 内核要求 连续内存块…

基于SpringBoot利用死信队列解决RabbitMQ业务队列故障重试无效场景问题

基于SpringBoot利用死信队列解决RabbitMQ业务队列故障重试无效场景问题 解决方案项目实战1、生产者服务1.1、RabbitConfig定义相关交换机及死信队列等配置数据1.2、TestController测试接口Controller 2、消费者服务2.1 BusinessQueueConsumer业务队列监听器2.2 DeadLetterConsu…

西安java面试总结1

这是我第二次的面试。其实第一次也算不上面试,去了让我手写了几道题,三道算法题,一道SQL题,两道逻辑思维题,做完之后也没看我的解答,随便看了一眼简历,觉得我是大二的,大三还有课&am…

【redis】线程IO模型

Redis线程IO模型 总结:在redis5.0及之前,redis线程io模型是单线程。那么Redis单线程如何处理那么多的并发客户端连接的?原因两点:1)非阻塞io 2)多路复用(事件轮询) 以下&#xff0…

进程间通信详解(三):Linux进程信号深度解析

文章目录 一、Linux进程信号核心概念1.1 信号本质1.2 关键术语1.3 Linux 信号机制的核心流程: 二、信号产生机制全景2.1 通过终端按键产生信号2.1.1 基本操作 2.2 调用系统命令向进程发信号2.2.1 kill 命令:向指定进程发送信号2.2.2 killall 命令&#x…

C++ 日志系统实战第五步:日志器的设计

全是通俗易懂的讲解,如果你本节之前的知识都掌握清楚,那就速速来看我的项目笔记吧~ 本文项目代码编写收尾! 日志器类 (Logger) 设计(建造者模式) 日志器主要用于和前端交互。当我们需要使用日志系统打印 log 时&…