当前位置: 首页 > article >正文

tensorflow案例5--基于改进VGG16模型的马铃薯识别,准确率提升0.6%,计算量降低78.07%

  • 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
  • 🍖 原作者:K同学啊

前言

  • 本次采用VGG16模型进行预测,准确率达到了98.875,但是修改VGG16网络结构, 准确率达到了0.9969,并且计算量下降78.07%

1、API积累

VGG16简介

VGG优缺点分析:

  • VGG优点

VGG的结构非常简洁,整个网络都使用了同样大小的卷积核尺寸(3x3)和最大池化尺寸(2x2)

  • VGG缺点

1)训练时间过长,调参难度大。2)需要的存储容量大,不利于部署。例如存储VGG-16权重值文件的大小为500多MB,不利于安装到嵌入式系统中。

后面优化也是基于VGG的缺点来进行

VGG结构图如下(PPT绘制):

在这里插入图片描述

API积累

🚄 优化

  • shuffle() :打乱数据,关于此函数的详细介绍可以参考:https://zhuanlan.zhihu.com/p/42417456
  • prefetch() :预取数据,加速运行,TensorFlow的prefetch方法用于在GPU执行计算时,由CPU预处理下一个批次的数据,实现生产者/消费者重叠,提高训练效率,参考本专栏案例一:https://yxzbk.blog.csdn.net/article/details/142862154
  • cache() :将数据集缓存到内存当中,加速运行

💂 像素归一化

讲像素映射到—> [0, 1]中,代码如下:

# 归一化数据
normalization_layer = layers.experimental.preprocessing.Rescaling(1.0 / 255)

# 训练集和验证集归一化
train_ds = train_ds.map(lambda x, y : (normalization_layer(x), y))
val_ds = val_ds.map(lambda x, y : (normalization_layer(x), y))

💛 优化器

本文全连接层最后一层采用softmax,故优化器为SparseCategoricalCrossentropy

SparseCategoricalCrossentropy函数注意事项:

from_logits参数:

  • 布尔值,默认值为 False
  • 当为 True 时,函数假设传入的预测值是未经过激活函数处理的原始 logits 值。如果模型的最后一层没有使用 softmax 激活函数(即返回 logits),需要将 from_logits 设置为 True
  • 当为 False 时,函数假设传入的预测值已经是经过 softmax 处理的概率分布。

2、案例

1、数据处理

1、导入库

import tensorflow as tf 
from tensorflow.keras import models, layers, datasets
import matplotlib.pyplot as plt 
import numpy as np 

# 判断支持gpu
gpus = tf.config.list_physical_devices("GPU")

if gpus:
    gpu0 = gpus[0]
    tf.config.experimental.set_memory_growth(gpu0, True)
    tf.config.set_visible_devices([gpu0], "GPU")
    
gpus
[PhysicalDevice(name='/physical_device:GPU:0', device_type='GPU')]

2、查看数据目录,获取类别

数据存储格式:data/ 下每个类别分别存储在不同模块中

import os, pathlib

data_dir = './data/'
data_dir = pathlib.Path(data_dir)

# 查看data_dir下的所有文件名
classnames = os.listdir(data_dir)
classnames
['Dark', 'Green', 'Light', 'Medium']

3、导入数据与划分数据集

# 训练集 : 测试集 = 8 :2

batch_size = 32 
img_width, img_height = 224, 224

train_ds = tf.keras.preprocessing.image_dataset_from_directory(
    './data/',
    validation_split = 0.2,
    batch_size=batch_size,
    image_size = (img_width, img_height),
    shuffle = True,
    subset='training',
    seed=42
)

val_ds = tf.keras.preprocessing.image_dataset_from_directory(
    './data/',
    validation_split = 0.2,
    batch_size=batch_size,
    image_size = (img_width, img_height),
    shuffle = True,
    subset='validation',
    seed=42
)
Found 1200 files belonging to 4 classes.
Using 960 files for training.
Found 1200 files belonging to 4 classes.
Using 240 files for validation.
# 查看数据格式
for X, y in train_ds.take(1):
    print("[N, W, H, C]", X.shape)
    print("lables: ", y)
    break
[N, W, H, C] (32, 224, 224, 3)
lables:  tf.Tensor([0 0 2 3 1 1 1 3 0 1 2 2 2 1 0 2 0 2 1 0 0 1 2 1 3 2 2 2 1 0 2 3], shape=(32,), dtype=int32)
# 查看原始数据像素
imgs, labelss = next(iter(train_ds))  # 获取一批数据
first = imgs[0]
print(first.shape)
print(np.min(first), np.max(first))
(224, 224, 3)
0.0 255.0

4、展示一批数据

plt.figure(figsize=(20, 10))

for images, labels in train_ds.take(1):
    for i in range(20):
        plt.subplot(5, 10, i + 1)  # H, W
        
        plt.imshow(images[i].numpy().astype("uint8"))
        plt.title(classnames[labels[i]])
        
        plt.axis('off')
        
plt.show()


在这里插入图片描述

5、配置数据集与归一化数据

  • shuffle() :打乱数据,关于此函数的详细介绍可以参考:https://zhuanlan.zhihu.com/p/42417456
  • prefetch() :预取数据,加速运行,TensorFlow的prefetch方法用于在GPU执行计算时,由CPU预处理下一个批次的数据,实现生产者/消费者重叠,提高训练效率,参考本专栏案例一:https://yxzbk.blog.csdn.net/article/details/142862154
  • cache() :将数据集缓存到内存当中,加速运行
# 加速
# 变量名比较复杂,但是代码比较固定
from tensorflow.data.experimental import AUTOTUNE

AUTOTUNE = tf.data.experimental.AUTOTUNE

# 打乱加速
train_ds = train_ds.cache().shuffle(1000).prefetch(buffer_size=AUTOTUNE)
val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE)
# 归一化数据
normalization_layer = layers.experimental.preprocessing.Rescaling(1.0 / 255)

# 训练集和验证集归一化
train_ds = train_ds.map(lambda x, y : (normalization_layer(x), y))
val_ds = val_ds.map(lambda x, y : (normalization_layer(x), y))
# 查看归一化数据
image_batch, label_batch = next(iter(val_ds))
# 取一个元素
first_image = image_batch[0]

# 查看
print(np.min(first_image), np.max(first_image))   # 查看像素最大值,最小值
print(image_batch.shape)
print(first_image.shape)
0.0 1.0
(32, 224, 224, 3)
(224, 224, 3)

2024-11-08 18:37:15.334784: W tensorflow/core/kernels/data/cache_dataset_ops.cc:856] The calling iterator did not fully read the dataset being cached. In order to avoid unexpected truncation of the dataset, the partially cached contents of the dataset  will be discarded. This can happen if you have an input pipeline similar to `dataset.cache().take(k).repeat()`. You should use `dataset.take(k).cache().repeat()` instead.

2、手动搭建VGG16网络

def VGG16(class_num, input_shape):
    inputs = layers.Input(input_shape)
    
     # 1st block
    x = layers.Conv2D(64, kernel_size=(3, 3), activation='relu', strides=(1, 1), padding='same')(inputs)
    x = layers.Conv2D(64, kernel_size=(3, 3), activation='relu', strides=(1, 1), padding='same')(x)
    x = layers.MaxPooling2D((2, 2), strides=(2, 2))(x)

    # 2nd block
    x = layers.Conv2D(128, kernel_size=(3, 3), activation='relu', strides=(1, 1), padding='same')(x)
    x = layers.Conv2D(128, kernel_size=(3, 3), activation='relu', strides=(1, 1), padding='same')(x)
    x = layers.MaxPooling2D((2, 2), strides=(2, 2))(x)

    # 3rd block
    x = layers.Conv2D(256, kernel_size=(3, 3), activation='relu', strides=(1, 1), padding='same')(x)
    x = layers.Conv2D(256, kernel_size=(3, 3), activation='relu', strides=(1, 1), padding='same')(x)
    x = layers.Conv2D(256, kernel_size=(3, 3), activation='relu', strides=(1, 1), padding='same')(x)
    x = layers.MaxPooling2D((2, 2), strides=(2, 2))(x)

    # 4th block
    x = layers.Conv2D(512, kernel_size=(3, 3), activation='relu', strides=(1, 1), padding='same')(x)
    x = layers.Conv2D(512, kernel_size=(3, 3), activation='relu', strides=(1, 1), padding='same')(x)
    x = layers.Conv2D(512, kernel_size=(3, 3), activation='relu', strides=(1, 1), padding='same')(x)
    x = layers.MaxPooling2D((2, 2), strides=(2, 2))(x)

    # 5th block
    x = layers.Conv2D(512, kernel_size=(3, 3), activation='relu', strides=(1, 1), padding='same')(x)
    x = layers.Conv2D(512, kernel_size=(3, 3), activation='relu', strides=(1, 1), padding='same')(x)
    x = layers.Conv2D(512, kernel_size=(3, 3), activation='relu', strides=(1, 1), padding='same')(x)
    x = layers.MaxPooling2D((2, 2), strides=(2, 2))(x)
    
    # 全连接层, 这里修改以下
    x = layers.Flatten()(x)
    x = layers.Dense(4096, activation='relu')(x)
    x = layers.Dense(4096, activation='relu')(x)
    # 最后一层用激活函数:softmax
    out_shape = layers.Dense(class_num, activation='softmax')(x)
    
    # 创建模型
    model = models.Model(inputs=inputs, outputs=out_shape)
    
    return model
    
model = VGG16(len(classnames), (img_width, img_height, 3))
model.summary()
    

Model: "model"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 input_1 (InputLayer)        [(None, 224, 224, 3)]     0         
                                                                 
 conv2d (Conv2D)             (None, 224, 224, 64)      1792      
                                                                 
 conv2d_1 (Conv2D)           (None, 224, 224, 64)      36928     
                                                                 
 max_pooling2d (MaxPooling2D  (None, 112, 112, 64)     0         
 )                                                               
                                                                 
 conv2d_2 (Conv2D)           (None, 112, 112, 128)     73856     
                                                                 
 conv2d_3 (Conv2D)           (None, 112, 112, 128)     147584    
                                                                 
 max_pooling2d_1 (MaxPooling  (None, 56, 56, 128)      0         
 2D)                                                             
                                                                 
 conv2d_4 (Conv2D)           (None, 56, 56, 256)       295168    
                                                                 
 conv2d_5 (Conv2D)           (None, 56, 56, 256)       590080    
                                                                 
 conv2d_6 (Conv2D)           (None, 56, 56, 256)       590080    
                                                                 
 max_pooling2d_2 (MaxPooling  (None, 28, 28, 256)      0         
 2D)                                                             
                                                                 
 conv2d_7 (Conv2D)           (None, 28, 28, 512)       1180160   
                                                                 
 conv2d_8 (Conv2D)           (None, 28, 28, 512)       2359808   
                                                                 
 conv2d_9 (Conv2D)           (None, 28, 28, 512)       2359808   
                                                                 
 max_pooling2d_3 (MaxPooling  (None, 14, 14, 512)      0         
 2D)                                                             
                                                                 
 conv2d_10 (Conv2D)          (None, 14, 14, 512)       2359808   
                                                                 
 conv2d_11 (Conv2D)          (None, 14, 14, 512)       2359808   
                                                                 
 conv2d_12 (Conv2D)          (None, 14, 14, 512)       2359808   
                                                                 
 max_pooling2d_4 (MaxPooling  (None, 7, 7, 512)        0         
 2D)                                                             
                                                                 
 flatten (Flatten)           (None, 25088)             0         
                                                                 
 dense (Dense)               (None, 4096)              102764544 
                                                                 
 dense_1 (Dense)             (None, 4096)              16781312  
                                                                 
 dense_2 (Dense)             (None, 4)                 16388     
                                                                 
=================================================================
Total params: 134,276,932
Trainable params: 134,276,932
Non-trainable params: 0
_________________________________________________________________

3、模型的训练

1、设置超参数

learn_rate = 1e-4

# 动态学习率
lr_schedule = tf.keras.optimizers.schedules.ExponentialDecay(
    learn_rate,
    decay_steps=20,
    decay_rate=0.95,
    staircase=True
)

# 设置优化器
opt = tf.keras.optimizers.Adam(learning_rate=learn_rate)

# 设置超参数
model.compile(
    optimizer=opt,
    loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
    metrics=['accuracy']
)

2、模型训练

from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping

# 设置训练次数
epochs = 20

# 设置早停
earlystopper = EarlyStopping(monitor='val_accuracy',
                             min_delta=0.001,
                             patience=20,
                             verbose=1)

# 保存最佳模型
checkpointer = ModelCheckpoint('best_model.h5',
                               monitor='val_accuracy',
                               verbose=1,
                               save_best_only=True,
                               save_weight_only=True)

history = model.fit(
    x=train_ds,
    validation_data=val_ds,
    epochs=epochs,
    verbose=1,
    callbacks=[earlystopper, checkpointer]
)

Epoch 1/20

2024-11-08 18:37:27.650111: I tensorflow/stream_executor/cuda/cuda_dnn.cc:384] Loaded cuDNN version 8101
2024-11-08 18:37:31.754452: I tensorflow/stream_executor/cuda/cuda_blas.cc:1786] TensorFloat-32 will be used for the matrix multiplication. This will only be logged once.

30/30 [==============================] - ETA: 0s - loss: 1.3401 - accuracy: 0.3094
Epoch 1: val_accuracy improved from -inf to 0.55833, saving model to best_model.h5
30/30 [==============================] - 17s 255ms/step - loss: 1.3401 - accuracy: 0.3094 - val_loss: 0.9073 - val_accuracy: 0.5583
Epoch 2/20
30/30 [==============================] - ETA: 0s - loss: 0.9208 - accuracy: 0.5406
Epoch 2: val_accuracy improved from 0.55833 to 0.63333, saving model to best_model.h5
30/30 [==============================] - 7s 223ms/step - loss: 0.9208 - accuracy: 0.5406 - val_loss: 0.6053 - val_accuracy: 0.6333
Epoch 3/20
30/30 [==============================] - ETA: 0s - loss: 0.6325 - accuracy: 0.6594
Epoch 3: val_accuracy did not improve from 0.63333
30/30 [==============================] - 4s 128ms/step - loss: 0.6325 - accuracy: 0.6594 - val_loss: 0.7538 - val_accuracy: 0.5542
Epoch 4/20
30/30 [==============================] - ETA: 0s - loss: 0.5219 - accuracy: 0.7115
Epoch 4: val_accuracy improved from 0.63333 to 0.82083, saving model to best_model.h5
30/30 [==============================] - 7s 246ms/step - loss: 0.5219 - accuracy: 0.7115 - val_loss: 0.4044 - val_accuracy: 0.8208
Epoch 5/20
30/30 [==============================] - ETA: 0s - loss: 0.3322 - accuracy: 0.8771
Epoch 5: val_accuracy improved from 0.82083 to 0.86667, saving model to best_model.h5
30/30 [==============================] - 7s 238ms/step - loss: 0.3322 - accuracy: 0.8771 - val_loss: 0.3286 - val_accuracy: 0.8667
Epoch 6/20
30/30 [==============================] - ETA: 0s - loss: 0.1433 - accuracy: 0.9573
Epoch 6: val_accuracy improved from 0.86667 to 0.95417, saving model to best_model.h5
30/30 [==============================] - 7s 230ms/step - loss: 0.1433 - accuracy: 0.9573 - val_loss: 0.1310 - val_accuracy: 0.9542
Epoch 7/20
30/30 [==============================] - ETA: 0s - loss: 0.0982 - accuracy: 0.9594
Epoch 7: val_accuracy improved from 0.95417 to 0.97917, saving model to best_model.h5
30/30 [==============================] - 7s 233ms/step - loss: 0.0982 - accuracy: 0.9594 - val_loss: 0.0739 - val_accuracy: 0.9792
Epoch 8/20
30/30 [==============================] - ETA: 0s - loss: 0.0630 - accuracy: 0.9802
Epoch 8: val_accuracy did not improve from 0.97917
30/30 [==============================] - 4s 127ms/step - loss: 0.0630 - accuracy: 0.9802 - val_loss: 0.2461 - val_accuracy: 0.9250
Epoch 9/20
30/30 [==============================] - ETA: 0s - loss: 0.1089 - accuracy: 0.9625
Epoch 9: val_accuracy improved from 0.97917 to 0.98333, saving model to best_model.h5
30/30 [==============================] - 6s 217ms/step - loss: 0.1089 - accuracy: 0.9625 - val_loss: 0.0717 - val_accuracy: 0.9833
Epoch 10/20
30/30 [==============================] - ETA: 0s - loss: 0.0392 - accuracy: 0.9885
Epoch 10: val_accuracy did not improve from 0.98333
30/30 [==============================] - 4s 126ms/step - loss: 0.0392 - accuracy: 0.9885 - val_loss: 0.0901 - val_accuracy: 0.9708
Epoch 11/20
30/30 [==============================] - ETA: 0s - loss: 0.0297 - accuracy: 0.9854
Epoch 11: val_accuracy improved from 0.98333 to 0.98750, saving model to best_model.h5
30/30 [==============================] - 7s 232ms/step - loss: 0.0297 - accuracy: 0.9854 - val_loss: 0.0629 - val_accuracy: 0.9875
Epoch 12/20
30/30 [==============================] - ETA: 0s - loss: 0.0331 - accuracy: 0.9885
Epoch 12: val_accuracy did not improve from 0.98750
30/30 [==============================] - 4s 127ms/step - loss: 0.0331 - accuracy: 0.9885 - val_loss: 0.0384 - val_accuracy: 0.9875
Epoch 13/20
30/30 [==============================] - ETA: 0s - loss: 0.1043 - accuracy: 0.9708
Epoch 13: val_accuracy did not improve from 0.98750
30/30 [==============================] - 4s 128ms/step - loss: 0.1043 - accuracy: 0.9708 - val_loss: 0.0445 - val_accuracy: 0.9833
Epoch 14/20
30/30 [==============================] - ETA: 0s - loss: 0.0352 - accuracy: 0.9833
Epoch 14: val_accuracy did not improve from 0.98750
30/30 [==============================] - 4s 134ms/step - loss: 0.0352 - accuracy: 0.9833 - val_loss: 0.1387 - val_accuracy: 0.9500
Epoch 15/20
30/30 [==============================] - ETA: 0s - loss: 0.1128 - accuracy: 0.9594
Epoch 15: val_accuracy did not improve from 0.98750
30/30 [==============================] - 4s 128ms/step - loss: 0.1128 - accuracy: 0.9594 - val_loss: 0.4397 - val_accuracy: 0.8125
Epoch 16/20
30/30 [==============================] - ETA: 0s - loss: 0.0949 - accuracy: 0.9646
Epoch 16: val_accuracy did not improve from 0.98750
30/30 [==============================] - 4s 130ms/step - loss: 0.0949 - accuracy: 0.9646 - val_loss: 0.1068 - val_accuracy: 0.9500
Epoch 17/20
30/30 [==============================] - ETA: 0s - loss: 0.0618 - accuracy: 0.9781
Epoch 17: val_accuracy did not improve from 0.98750
30/30 [==============================] - 4s 128ms/step - loss: 0.0618 - accuracy: 0.9781 - val_loss: 0.1663 - val_accuracy: 0.9292
Epoch 18/20
30/30 [==============================] - ETA: 0s - loss: 0.0351 - accuracy: 0.9854
Epoch 18: val_accuracy did not improve from 0.98750
30/30 [==============================] - 4s 128ms/step - loss: 0.0351 - accuracy: 0.9854 - val_loss: 0.0687 - val_accuracy: 0.9792
Epoch 19/20
30/30 [==============================] - ETA: 0s - loss: 0.0609 - accuracy: 0.9781
Epoch 19: val_accuracy did not improve from 0.98750
30/30 [==============================] - 4s 128ms/step - loss: 0.0609 - accuracy: 0.9781 - val_loss: 0.0963 - val_accuracy: 0.9708
Epoch 20/20
30/30 [==============================] - ETA: 0s - loss: 0.0263 - accuracy: 0.9896
Epoch 20: val_accuracy did not improve from 0.98750
30/30 [==============================] - 4s 127ms/step - loss: 0.0263 - accuracy: 0.9896 - val_loss: 0.2104 - val_accuracy: 0.9458

  • 最好效果:val_accuracy did not improve from 0.98750

4、结果显示

# 获取训练集和验证集损失率和准确率
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']

loss = history.history['loss']
val_loss = history.history['val_loss']

epochs_range = range(epochs)

plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()


在这里插入图片描述

3、优化

讲全连接层进行优化,对其减少全连接层神经元的数量:

# 原来
x = layers.Flatten()(x)
x = layers.Dense(4096, activation='relu')(x)
x = layers.Dense(4096, activation='relu')(x)
# 最后一层用激活函数:softmax
out_shape = layers.Dense(class_num, activation='softmax')(x)

# 修改
x = layers.Flatten()(x)
x = layers.Dense(1024, activation='relu')(x)
x = layers.Dense(512, activation='relu')(x)
# 最后一层用激活函数:softmax
out_shape = layers.Dense(class_num, activation='softmax')(x)

修改效果:loss: 0.0166 - accuracy: 0.9969,准确率提升:0.6%个百分点,但是计算量确实大量减少

在这里插入图片描述

修改前的全连接层参数数量

  1. 第一个 Dense 层:输入 25088,输出 4096
    • 参数数量:( (25088 + 1) \times 4096 = 102764544 )
  2. 第二个 Dense 层:输入 4096,输出 4096
    • 参数数量:( (4096 + 1) \times 4096 = 16781312 )
  3. 输出层:输入 4096,输出 4
    • 参数数量:( (4096 + 1) \times 4 = 16388 )

总参数数量:( 102764544 + 16781312 + 16388 = 119562244 )

修改后的全连接层参数数量

  1. 第一个 Dense 层:输入 25088,输出 1024
    • 参数数量:( (25088 + 1) \times 1024 = 25690112 )
  2. 第二个 Dense 层:输入 1024,输出 512
    • 参数数量:( (1024 + 1) \times 512 = 524800 )
  3. 输出层:输入 512,输出 4
    • 参数数量:( (512 + 1) \times 4 = 2052 )

总参数数量:( 25690112 + 524800 + 2052 = 26216964 )

计算减少的百分比

减少的参数数量:
119562244−26216964=93345280

减少的百分比:
93345280 119562244 × 100 % ≈ 78.07 % \frac{93345280}{119562244}\times100\%\approx78.07\% 11956224493345280×100%78.07%

因此,修改后计算量(以参数数量衡量)减少了约 78.07%


http://www.kler.cn/a/390594.html

相关文章:

  • 【ChatGPT】 如何让ChatGPT分析数据并得出结论
  • python装饰器的使用以及私有化
  • 机器学习day3-KNN算法、模型调优与选择
  • 10款翻译工具实践体验感受与解析!!!!!
  • 记录学习react的一些内容
  • Java项目实战II基于微信小程序的个人行政复议在线预约系统微信小程序(开发文档+数据库+源码)
  • C++ 面试问题集合
  • 2024年11月杂记
  • OceanBase 4.3.3 功能解析:列存副本
  • Docker实践与应用举例:从入门到进阶
  • 黑马Java API应用文档下载,中文版!!!!!
  • C02S11-Linux系统的安全与控制
  • Cesium基础-(Entity)-(model )
  • 如何详细查询全球药品研发的进度信息?
  • 【大数据测试 Elasticsearch — 详细教程及实例】
  • 系统启动时将自动加载环境变量,并后台启动 MinIO、Nacos 和 Redis 服务
  • 第七部分:2. STM32之ADC实验--AD多通道(AD采集三路传感器模块实验:光敏传感器、热敏传感器、反射式传感器附赠温湿度传感器教程)
  • 【Pikachu】越权访问实战
  • Oracle数据库 查看SQL执行计划的几种方法
  • vue2 通过路由拦截实现 token 刷新功能
  • 手持测温热像仪市场规模:预计2030年全球市场规模将达到24.9亿美元
  • 【Leecode】Leecode刷题之路第45天之跳跃游戏II
  • HARCT 2025 新增分论坛2:机器人系统智能控制
  • docker基础:搭建centos7(详见B站泷羽sec)
  • kafka夺命三十问——16-22问
  • 网络安全:挑战、策略与未来趋势