【卷积神经网络详解与实例】8——经典CNN之VGG

1 开发背景

VGGNet是牛津大学视觉几何组(Visual Geometry Group)提出的模型,该模型在2014ImageNet图像分类与定位挑战赛 ILSVRC-2014中取得在分类任务第二,定位任务第一的优异成绩。其核心贡献在于系统性地探索了网络深度对性能的影响,并证明了通过堆叠非常小的卷积核(3x3)可以显著提升网络性能

论文地址:1409.1556] Very Deep Convolutional Networks for Large-Scale Image Recognitionhttps://arxiv.org/abs/1409.1556

论文详解:(51 封私信 / 60 条消息) 经典神经网络超详细解读(五)VGG网络(论文精读+网络详解+代码实战) - 知乎https://zhuanlan.zhihu.com/p/27715976701


2 网络结构

2.1 VGG的核心思想

  1. 采用更深的网络结构:相比 AlexNet(8 层),VGG 增加到了 16 或 19 层,提升了网络的表达能力。

  2. 使用小卷积核(3×3)和多个连续的卷积层:所有卷积层都使用 3×3 卷积核,而不是较大的 5×5 或 7×7,这样可以:

  • 每个卷积层后面通常跟着一个ReLU激活函数,多个卷积层可以增加非线性能力(两个 3×3 卷积相当于一个 5×5 卷积);

  • 与使用大尺寸卷积核的单一层相比,多个小卷积层的堆叠可以提供更好的特征提取能力

  • 减少参数数量(比单个 5×5 卷积的参数更少)。

这里说明一下为什么两个 3×3 卷积相当于一个 5×5 卷积,以及为什么参数数量变少了:

如上图所示,如果我们使用一个 5×5 的卷积核对一个 5×5 的图像进行卷积,最后可以得到一个 1×1 的输出,而使用 3×3 的卷积核进行两次连续的卷积,同样可以得到 1×1 的输出。同理,三个 3×3 的卷积相当于一个 7×7 的卷积。一个3×3卷积核在处理具有64个输入通道的特征图时,只有577(3×3×64+1=577,加的1是偏置项)个参数,而一个11x11的卷积核则有7745(11×11×64+1)个参数,7745>577×2。这种参数数量的显著差异意味着使用小卷积核可以大幅减少网络的参数量


2.2 VGG架构实验

上图是论文中六次实验的结果图。VGG网络的卷积层全部为3*3的卷积核,用conv3-xxx来表示,xxx表示通道数。

  • 第一组(A)就是个简单的卷积神经网络,没有啥花里胡哨的地方。

  • 第二组(A-LRN)在第一组的卷积神经网络的基础上加了LRN(LRN:局部响应归一化,一种正则化手段,现在已不是主流了,LRN是Alexnet中提出的方法,在Alexnet中有不错的表现 )

  • 第三组(B)在A的基础上加了两个conv3,即多加了两个3*3卷积核

  • 第四组(C)在B的基础上加了三个conv1,即多加了三个1*1卷积核

  • 第五组(D)在C的基础上把三个conv1换成了三个3*3卷积核

  • 第六组(E)在D的基础上又加了三个conv3,即多加了三个3*3卷积核

结论:

1.第一组和第二组进行对比,LRN在这里并没有很好的表现;

2.第四组和第五组进行对比,conv3比conv1好使;

3.统筹看这六组实验,会发现随着网络层数的加深,模型的表现会越来越好。

据此可简单总结:作者一共实验了6种网络结构,其中VGG16(D)和VGG19(E)分类效果最好(16、19层隐藏层),证明了增加网络深度能在一定程度上影响最终的性能。 两者没有本质的区别,只是网络的深度不一样。


2.3 VGG16架构

下面以VGG16为例做具体讲解。

VGG16是最常用和最经典的VGG变体之一。数字“16”指的是网络中包含权重参数的层数,即13个卷积层 + 3个全连接层 = 16层。池化层和激活层不计入此数。

具体参数如下:

阶段层类型输出尺寸 (C x H x W)参数配置 (核大小/步长/填充)输出通道数备注
输入Input Image3 x 224 x 224--假设输入图像尺寸为224x224
Block 1Conv3-6464 x 224 x 2243x3 / 1 / 164
Conv3-6464 x 224 x 2243x3 / 1 / 164
MaxPool64 x 112 x 1122x2 / 2-尺寸减半
Block 2Conv3-128128 x 112 x 1123x3 / 1 / 1128
Conv3-128128 x 112 x 1123x3 / 1 / 1128
MaxPool128 x 56 x 562x2 / 2-尺寸减半
Block 3Conv3-256256 x 56 x 563x3 / 1 / 1256
Conv3-256256 x 56 x 563x3 / 1 / 1256
Conv3-256256 x 56 x 563x3 / 1 / 1256
MaxPool256 x 28 x 282x2 / 2-尺寸减半
Block 4Conv3-512512 x 28 x 283x3 / 1 / 1512
Conv3-512512 x 28 x 283x3 / 1 / 1512
Conv3-512512 x 28 x 283x3 / 1 / 1512
MaxPool512 x 14 x 142x2 / 2-尺寸减半
Block 5Conv3-512512 x 14 x 143x3 / 1 / 1512
Conv3-512512 x 14 x 143x3 / 1 / 1512
Conv3-512512 x 14 x 143x3 / 1 / 1512
MaxPool512 x 7 x 72x2 / 2-尺寸减半
FCFlatten1 x 25088--将特征图展平为一维向量
FC-40964096-4096全连接层1
FC-40964096-4096全连接层2
FC-10001000-1000输出层 (Softmax)

关键点说明

  1. 卷积块(Conv Block): 每个Block由连续的2-3个3x3卷积层组成,通道数在Block内保持不变(如Block1都是64通道),在Block之间通过池化层后翻倍(64->128->256->512->512)。Block5的通道数没有继续翻倍,保持512。

  2. 池化层(MaxPool): 位于每个卷积块之后,负责降低空间维度(H, W减半),增加感受野,并一定程度上提供平移不变性。

  3. 全连接层(FC)

    • 在最后一个池化层(输出尺寸为512 x 7 x 7)之后,将特征图展平(Flat) 成一个长度为 512 * 7 * 7 = 25088 的一维向量。

    • 然后依次通过两个4096维的全连接层(通常使用ReLU激活和Dropout防止过拟合)。

    • 最后是一个1000维的全连接层(对应ImageNet的1000类),使用Softmax函数输出每个类别的预测概率。

  4. 激活函数: 所有卷积层和全连接层(除最后一层)之后都使用ReLU激活函数,引入非线性。

  5. 参数量: VGG16的总参数量约为138 million (1.38亿)。其中,绝大部分参数(约90%)集中在最后的三个全连接层(FC-4096: ~102M, FC-4096: ~102M, FC-1000: ~4M)。卷积层的参数量相对较少(约27M)。


3 VGG的优缺点

3.1 核心优点

  1. 设计简洁统一,易于理解与实现

    • 规则化结构:所有卷积层均使用 3×3核(除少量1×1卷积),所有池化层均使用 2×2最大池化,模块化设计清晰。

    • 配置单一:卷积层参数固定(stride=1, padding=1),池化层参数固定(stride=2),无需复杂调参。

    • 代码复用性高:相同结构的卷积块可循环实现,降低工程复杂度。

  2. 深度网络性能的强力证明

    • 深度提升效果显著:通过对比VGG11(11层)到VGG19(19层),证明增加深度能持续提升精度(ImageNet Top-5错误率从10.4%降至7.3%)。

    • 小卷积核的优越性

      相同感受野的情况下,堆叠小卷积核相比使用大卷积核具有更多的激活函数、更丰富的特征,更强的辨别能力。卷积后都伴有激活函数,可使决策函数更加具有辨别能力;此外,3x3比7x7就足以捕获细节特征的变化:3x3的9个格子,最中间的格子是一个感受野中心,可以捕获上下左右以及斜对角的特征变化;3个3x3堆叠近似一个7x7,网络深了两层且多出了两个非线性ReLU函数,网络容量更大,对于不同类别的区分能力更强。

  3. 强大的特征提取能力

    • 分层特征学习

      • 浅层(Block1-2)学习边缘、纹理等低级特征。

      • 中层(Block3-4)学习物体部件等中级特征。

      • 深层(Block5-FC)学习语义等高级特征。

    • 特征泛用性强:预训练模型(尤其VGG16/19)的卷积层特征被广泛用于迁移学习(如目标检测、图像分割)。


3.2 显著缺点

  1. 参数量巨大,计算成本高昂

    • 参数分布

      层类型参数量(VGG16)占比
      卷积层≈14.7M10.6%
      全连接层≈123.6M89.4%
      总计≈138.3M100%
    • 计算开销

      • 训练需大量GPU资源(如VGG19训练耗时约2-3周,单卡V100)。

      • 推理速度慢(224×224图像在V100上约100ms,远低于MobileNet)。

  2. 全连接层(FC)的致命缺陷

    • 参数冗余:FC层占90%参数,但贡献有限(后续研究表明可用全局平均池化替代)。

    • 空间限制:FC层要求固定输入尺寸(如224×224),需对图像裁剪/缩放,可能丢失信息。

    • 过拟合风险:FC层参数多,在小型数据集上易过拟合(需强正则化如Dropout)。

  3. 内存与存储瓶颈

    • 显存占用高:训练时需存储大量中间特征图(如Block5输出512×7×7,需≈10MB显存)。

    • 模型体积大:VGG16模型文件超500MB,不适合移动端/嵌入式部署。

  4. 用多个 3×3卷积代替大卷积,会增加网络深度,深度过大可能带来梯度消失或梯度爆炸问题。

    • 解决方案

      • 使用 Batch Normalization(BN) 进行归一化。

      • 采用 ResNet 残差连接,保证梯度有效传播。

  5. 结构僵化,缺乏创新性

    • 无多尺度处理:未采用Inception的多分支结构或ResNet的残差连接,特征融合能力弱。

    • 通道设计保守:通道数仅按2倍增长(64→128→256→512),未探索更高效的通道分配策略。

总结:VGG是深度学习史上的“功勋模型”,其简洁设计和深度探索为后续发展铺平道路。尽管因参数冗余、计算低效逐渐退出主流,但其特征提取能力设计思想仍具重要参考价值。在资源充足或需要强特征的任务中,VGG仍是可靠选择;但对效率敏感的场景,需选择更现代的轻量架构。


4 基于Pytorch实现

以下项目实现了 VGG 网络,并在 CIFAR10 上验证了该网络的有效性,项目目录如下:

VGG_CIFAR10/│├── model/                  # 存放模型定义│   ├── __init__.py│   └── VGG.py              # VGG模型定义│├── data/                   # 存放数据集(CIFAR-10会自动下载)│├── utils/                  # 工具函数│   ├── __init__.py│   ├── transforms.py       # 数据预处理│   └── visualization.py     # 可视化工具│├── train.py                # 训练脚本├── test.py                 # 测试脚本└── config.py               # 配置文件

配置文件

 # config.pyimport torch​# 训练参数class Config:# 数据参数data_dir = './data'batch_size = 128num_workers = 4​# 模型参数num_classes = 10model_name = 'vgg16'​# 训练参数num_epochs = 50learning_rate = 0.01momentum = 0.9weight_decay = 5e-4lr_step_size = 30lr_gamma = 0.1​# 其他device = 'cuda' if torch.cuda.is_available() else 'cpu'save_path = './checkpoints/'log_interval = 100

模型定义

 
# VGG.pyimport torchimport torch.nn as nn​​class VGG(nn.Module):def __init__(self, num_classes=10, init_weights=True):super(VGG, self).__init__()​# 特征提取部分(卷积层)self.features = nn.Sequential(# Block 1nn.Conv2d(3, 64, kernel_size=3, padding=1),nn.BatchNorm2d(64),  # 添加批归一化nn.ReLU(inplace=True),nn.Conv2d(64, 64, kernel_size=3, padding=1),nn.BatchNorm2d(64),nn.ReLU(inplace=True),nn.MaxPool2d(kernel_size=2, stride=2),​# Block 2nn.Conv2d(64, 128, kernel_size=3, padding=1),nn.BatchNorm2d(128),nn.ReLU(inplace=True),nn.Conv2d(128, 128, kernel_size=3, padding=1),nn.BatchNorm2d(128),nn.ReLU(inplace=True),nn.MaxPool2d(kernel_size=2, stride=2),​# Block 3nn.Conv2d(128, 256, kernel_size=3, padding=1),nn.BatchNorm2d(256),nn.ReLU(inplace=True),nn.Conv2d(256, 256, kernel_size=3, padding=1),nn.BatchNorm2d(256),nn.ReLU(inplace=True),nn.Conv2d(256, 256, kernel_size=3, padding=1),nn.BatchNorm2d(256),nn.ReLU(inplace=True),nn.MaxPool2d(kernel_size=2, stride=2),​# Block 4nn.Conv2d(256, 512, kernel_size=3, padding=1),nn.BatchNorm2d(512),nn.ReLU(inplace=True),nn.Conv2d(512, 512, kernel_size=3, padding=1),nn.BatchNorm2d(512),nn.ReLU(inplace=True),nn.Conv2d(512, 512, kernel_size=3, padding=1),nn.BatchNorm2d(512),nn.ReLU(inplace=True),nn.MaxPool2d(kernel_size=2, stride=2),​# Block 5nn.Conv2d(512, 512, kernel_size=3, padding=1),nn.BatchNorm2d(512),nn.ReLU(inplace=True),nn.Conv2d(512, 512, kernel_size=3, padding=1),nn.BatchNorm2d(512),nn.ReLU(inplace=True),nn.Conv2d(512, 512, kernel_size=3, padding=1),nn.BatchNorm2d(512),nn.ReLU(inplace=True),nn.MaxPool2d(kernel_size=2, stride=2))​# 分类器部分(全连接层)self.avgpool = nn.AdaptiveAvgPool2d((7, 7))self.classifier = nn.Sequential(nn.Linear(512 * 7 * 7, 4096),nn.ReLU(inplace=True),nn.Dropout(0.5),  # 增加Dropout率nn.Linear(4096, 4096),nn.ReLU(inplace=True),nn.Dropout(0.5),nn.Linear(4096, num_classes))​if init_weights:self._initialize_weights()​def forward(self, x):x = self.features(x)x = self.avgpool(x)x = torch.flatten(x, 1)x = self.classifier(x)return x​def _initialize_weights(self):for m in self.modules():if isinstance(m, nn.Conv2d):nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')if m.bias is not None:nn.init.constant_(m.bias, 0)elif isinstance(m, nn.BatchNorm2d):nn.init.constant_(m.weight, 1)nn.init.constant_(m.bias, 0)elif isinstance(m, nn.Linear):nn.init.normal_(m.weight, 0, 0.01)nn.init.constant_(m.bias, 0)​​def vgg16(num_classes=10):return VGG(num_classes=num_classes)

工具函数

 # transforms.pyimport torchvision.transforms as transforms​def get_train_transform():return transforms.Compose([transforms.RandomCrop(32, padding=4),transforms.RandomHorizontalFlip(),transforms.RandomRotation(15),  # 添加随机旋转transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2),  # 颜色抖动transforms.ToTensor(),transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)),])​def get_test_transform():return transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)),])
 # visualization.pyimport matplotlib.pyplot as pltimport numpy as npimport torch​​'''用于训练可视化'''def plot_results(train_losses, train_accs, test_losses, test_accs):plt.figure(figsize=(12, 4))​# 绘制损失曲线plt.subplot(1, 2, 1)plt.plot(train_losses, label='Train Loss')plt.plot(test_losses, label='Test Loss')plt.xlabel('Epoch')plt.ylabel('Loss')plt.legend()plt.title('Training and Test Loss')​# 绘制准确率曲线plt.subplot(1, 2, 2)plt.plot(train_accs, label='Train Accuracy')plt.plot(test_accs, label='Test Accuracy')plt.xlabel('Epoch')plt.ylabel('Accuracy')plt.legend()plt.title('Training and Test Accuracy')​plt.tight_layout()plt.savefig('results.png')plt.show()​​'''用于测试可视化'''def visualize_predictions(model, device, test_loader, classes, num_samples=8):# 获取一批测试数据dataiter = iter(test_loader)images, labels = next(dataiter)​# 选择前num_samples个样本images = images[:num_samples]labels = labels[:num_samples]​# 将图像移至设备images = images.to(device)​# 获取模型预测outputs = model(images)_, predicted = torch.max(outputs, 1)​# 将预测和标签移回CPUpredicted = predicted.cpu().numpy()labels = labels.numpy()​# 显示图像plt.figure(figsize=(12, 6))for i in range(num_samples):plt.subplot(2, 4, i + 1)# 反归一化img = images[i].cpu().numpy().transpose(1, 2, 0)mean = np.array([0.4914, 0.4822, 0.4465])std = np.array([0.2023, 0.1994, 0.2010])img = std * img + meanimg = np.clip(img, 0, 1)plt.imshow(img)plt.title(f"True: {classes[labels[i]]}\nPred: {classes[predicted[i]]}")plt.axis('off')plt.tight_layout()plt.savefig('predictions.png')plt.show()

模型训练

 # train.pyimport torchimport torch.optim as optimimport torch.nn.functional as Ffrom torch.utils.data import DataLoaderfrom torchvision import datasetsimport osimport timefrom tqdm import tqdm​from model.VGG import vgg16from utils.transforms import get_train_transform, get_test_transformfrom utils.visualization import plot_resultsfrom config import Config​​def train(model, device, train_loader, optimizer, epoch, config):model.train()running_loss = 0.0correct = 0total = 0​progress_bar = tqdm(train_loader, desc=f'Epoch {epoch}/{config.num_epochs}')for batch_idx, (inputs, targets) in enumerate(progress_bar):inputs, targets = inputs.to(device), targets.to(device)​# 前向传播outputs = model(inputs)loss = F.cross_entropy(outputs, targets)​# 反向传播与优化optimizer.zero_grad()loss.backward()optimizer.step()​# 统计信息running_loss += loss.item()_, predicted = outputs.max(1)total += targets.size(0)correct += predicted.eq(targets).sum().item()​# 更新进度条progress_bar.set_postfix({'Loss': running_loss / (batch_idx + 1),'Acc': 100. * correct / total})​train_loss = running_loss / len(train_loader)train_acc = 100. * correct / totalreturn train_loss, train_acc​​def test(model, device, test_loader):model.eval()test_loss = 0correct = 0total = 0​with torch.no_grad():for inputs, targets in test_loader:inputs, targets = inputs.to(device), targets.to(device)outputs = model(inputs)test_loss += F.cross_entropy(outputs, targets, reduction='sum').item()_, predicted = outputs.max(1)total += targets.size(0)correct += predicted.eq(targets).sum().item()​test_loss /= len(test_loader.dataset)test_acc = 100. * correct / totalreturn test_loss, test_acc​​def main():config = Config()​# 创建保存目录os.makedirs(config.save_path, exist_ok=True)​# 加载数据集train_set = datasets.CIFAR10(root=config.data_dir, train=True, download=True, transform=get_train_transform())test_set = datasets.CIFAR10(root=config.data_dir, train=False, download=True, transform=get_test_transform())​train_loader = DataLoader(train_set, batch_size=config.batch_size, shuffle=True, num_workers=config.num_workers)test_loader = DataLoader(test_set, batch_size=config.batch_size, shuffle=False, num_workers=config.num_workers)​# 创建模型model = vgg16(num_classes=config.num_classes).to(config.device)​# 打印模型结构print(model)​# 定义优化器和损失函数optimizer = optim.SGD(model.parameters(),lr=config.learning_rate,momentum=config.momentum,weight_decay=config.weight_decay)scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=config.lr_step_size, gamma=config.lr_gamma)​# 训练记录train_losses = []train_accs = []test_losses = []test_accs = []best_acc = 0.0​# 训练循环for epoch in range(1, config.num_epochs + 1):start_time = time.time()​# 训练train_loss, train_acc = train(model, config.device, train_loader, optimizer, epoch, config)​# 测试test_loss, test_acc = test(model, config.device, test_loader)​# 学习率调整scheduler.step()​# 记录train_losses.append(train_loss)train_accs.append(train_acc)test_losses.append(test_loss)test_accs.append(test_acc)​# 保存最佳模型if test_acc > best_acc:best_acc = test_acctorch.save(model.state_dict(), os.path.join(config.save_path, 'best_model.pth'))​# 打印信息elapsed_time = time.time() - start_timeprint(f'Epoch {epoch}/{config.num_epochs} - Time: {elapsed_time:.2f}s')print(f'Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.2f}%')print(f'Test Loss: {test_loss:.4f}, Test Acc: {test_acc:.2f}%')print(f'Best Test Acc: {best_acc:.2f}%\n')​# 绘制结果plot_results(train_losses, train_accs, test_losses, test_accs)​print(f'Training completed. Best test accuracy: {best_acc:.2f}%')​​if __name__ == '__main__':main()

模型测试

# test.py 
import torchfrom torch.utils.data import DataLoaderfrom torchvision import datasetsimport os​from model.VGG import vgg16from utils.transforms import get_test_transformfrom utils.visualization import visualize_predictionsfrom config import Config​​def main():config = Config()​# 加载测试数据集test_set = datasets.CIFAR10(root=config.data_dir, train=False, download=True, transform=get_test_transform())test_loader = DataLoader(test_set, batch_size=config.batch_size, shuffle=False, num_workers=config.num_workers)​# 加载模型model = vgg16(num_classes=config.num_classes).to(config.device)model.load_state_dict(torch.load(os.path.join(config.save_path, 'best_model.pth')))model.eval()​# 类别标签classes = ('plane', 'car', 'bird', 'cat', 'deer','dog', 'frog', 'horse', 'ship', 'truck')​# 可视化预测结果visualize_predictions(model, config.device, test_loader, classes)​​if __name__ == '__main__':main()

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

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

相关文章

【分享】中小学教材课本 PDF 资源获取指南

很多人都不知道,其实官方提供的中小学教材课本 PDF 文档是完全免费且正版的,无需使用扫描版,清晰度和质量都非常高。 这些资源就藏在国家中小学智慧教育平台(basic.smartedu.cn)上。这个平台涵盖了从小学到高中的各个…

js趣味游戏 贪吃蛇

以下是关于JavaScript趣味游戏的系统性整理,涵盖经典案例、开发工具、教程资源及创意方向,助您快速掌握JS游戏开发的核心逻辑:一、经典JS趣味游戏案例贪吃蛇(Snake Game)核心机制:键盘控制蛇的移动方向&…

【Redis#11】Redis 在 C++ 客户端下的安装使用流程(一条龙服务)

一、安装使用 --Ubuntu 下启用 1. 前置依赖 - hiredis hiredis 是一个用 C 语言实现的 Redis 客户端库,redis-plus-plus 库基于 hiredis 实现。在开始之前,请确保已安装 libhiredis-dev,可以通过以下命令安装: sudo apt install l…

kibana+elasticsearch console查询示例

kibana console查询入口如下 http://localhost:5601/app/dev_tools#/console/shell 1 整体查询 获取index为newbook的所有数据 GET newbook/_search 2 通用查询 获取index为newbook的数据中,bookname包含“西游”的所有数据。 GET newbook/_search { "query&q…

软考系统架构设计师之软件风格篇

一、软件架构风格-数据流风格 数据-》第1步处理-》数据-》第2步处理-》数据-》第N步处理 【分步处理】 优点: 1、松耦合【高内聚-低耦合】 2、良好的重用性/可维护性; 3、可扩展性【标准接口适配】 4、良好的隐蔽性; 5、支持并行。 缺点 1、交互性较差; 2、复杂性较…

初始QML

由于项目原因,最近要进行qml相关开发,我之前也没有搞过qml,因此开一个qml系列的专栏,记录自己关于qml的相关学习新建第一个qml工程按如下图所示方法新建一个最简单的qml工程:编译运行可以看到是一个标题为“hello word…

Coze源码分析-资源库-创建知识库-基础设施/存储/安全

6. 基础设施层 基础设施层为知识库创建功能提供底层技术支撑,包括数据存储、缓存、消息队列、文档处理、向量化等核心服务。 6.1 数据存储服务 6.1.1 MySQL数据库 文件位置: backend/infra/rdb/mysql.go // MySQLConfig MySQL配置 type MySQLConfig struct {Host …

【iOS】设计模式复习

目录 观察者模式 通知机制 基本使用 注册观察者 创建一个通知 发送通知 通知与多线程 使用异步发送通知 NSNotificationQueue通知队列 在子线程中运行观察者函数 实现原理 named表 nameless表 wildcard表 添加观察者 发送通知 移除通知​ KVO机制 基本使用 …

RK3568 NPU :RKNN-ToolKit2环境搭建

1. 安装Miniconda3 下载 Linux 64 位 Miniconda 最新版安装脚本 wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh安装 Miniconda bash Miniconda3-latest-Linux-x86_64.sh -u安装完成后会自动设置环境变量。打开新的终端,发现用户名前…

Ubuntu 24.04 Server 版系统安装及配置

Ubuntu 24.04 Server 版安装及配置 文章目录Ubuntu 24.04 Server 版安装及配置一、获取安装文件二、虚拟机配置三、安装界面四、配置网络五、扩容根分区 一、获取安装文件二、虚拟机配置三、安装界面 选择English(US)问是否升级内核配置键盘手动配置ipv4…

Java 事务失效场景全解析

在 Java 开发中,事务管理是保证数据一致性的核心机制,尤其是在 Spring 框架中,Transactional注解的使用极大简化了事务配置。然而,在实际开发中,事务常常会因为一些细节问题而失效,导致数据异常。本文将详细…

【Coze搞钱实战】14. 抖音直播间自动回复机器人实战教程:三小时搭建智能客服,互动率提升150%(保姆级无代码指南)

摘要:抖音直播间高频问题重复回复、观众互动不及时是运营痛点。本文针对新手和进阶用户,提供无代码的自动回复机器人搭建方案:新手1小时完成基础配置(Coze+抖音对接),进阶用户通过促销倒计时、粉丝分层、热点借势三大策略提升互动率150%。方案基于某女装直播间实测数据(…

云计算核心知识梳理

云计算作为新一代信息技术的核心,其体系涵盖特点、定义、技术演进及分类等多个维度,以下是对相关知识的系统整合与解读。 一、云计算的核心特点 / 优势 云计算的优势围绕资源利用效率、服务灵活性和管理便捷性展开,具体可拆解为五大核心特性: 按需自助服务:用户无需人工干…

安卓13_ROM修改定制化-----安卓 13 系统 ROM 定制化与低版本系统的核心区别

安卓系统凭借其全球领先的市场占有率,开放特性为厂商和开发者提供了深度定制的空间,形成了丰富的ROM生态圈。从最初的安卓1.0到最新的安卓15,系统在功能、性能和安全方面不断迭代升级,同时也为ROM定制带来了新的机遇与挑战。特别是从安卓11开始,谷歌对系统架构和安全机制进…

【Java后端】Spring Boot 2.7.x 和 Swagger 3.0.x (springfox 3.x) 的兼容性问题

springfox 在 Spring Boot 2.6 开始就有很多兼容性 bug(主要是 Spring MVC PathPatternParser 的引入),导致在 Spring Boot 2.6/2.7 里经常出现 无法启动 / 无法访问 swagger-ui.html 的情况。🔎 问题原因Spring Boot 2.6 开始默认…

Vue3+ts使用oidc-client-ts

配置 OIDC 客户端 在项目中创建 authOptions 对象,定义 OIDC 认证所需的配置项: export const authOptions {authority: https://xxxxxxxxx/UserCenter, // 认证服务器 URLclient_id: xxxx, // 客户端 IDredirect_uri: http://localhost:3000/callbac…

从 “数据中转站“ 到 “边缘智能中枢“:区域网关的技术突围与开发范式重构

在物联网架构中,区域网关长期被视为 "边缘与云端的桥梁"—— 负责协议转换、数据转发、设备接入等基础功能。但随着边缘计算兴起与 AI 模型轻量化,区域网关正经历从 "被动转发" 到 "主动决策" 的范式跃迁。 本文将从开发视角拆解区域网关的三大…

Django全栈班v1.04 Python基础语法 20250913 早上

print 函数基本用法 print函数会自加换行符,一个print,会打印一行输出。 print("第一行") print("第二行") print("第三行")输出结果:print 输出多个值 一个print可以同时输出多个值,这多个值会在一…

面试鸭Java八股之Kafka

Kafka是什么?它的主要应用场景有哪些? Kafka是一种分布式流事件处理平台,最初由 LinkedIn 开发,现在是 Apache 基金会的一部分。它的核心功能主要包括消息队列、流处理和数据集成。Kafka以高吞吐量、低延迟、可扩展和高容错性著称。 Kafka…

ARM32平台Bus Error深度排查:从调用栈到硬件原理的完整拆解

ARM32平台Bus Error深度排查:从调用栈到硬件原理的完整拆解 在嵌入式开发中,Bus Error(信号7)是个容易让人头疼的问题——它不像SIGSEGV(段错误)那样直观,常与硬件内存布局、指针破坏等底层问题…