码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • 深度学习100例 | 第31天-卷积神经网络(DenseNet)识别生活物品


    🚀 我的环境:

    • 语言环境:Python3.6.5
    • 编译器:jupyter notebook
    • 深度学习环境:TensorFlow2.4.1
    • 显卡(GPU):NVIDIA GeForce RTX 3080
    • 数据:📌【传送门】

    🚀 本文选自专栏:《深度学习100例》

    🚀 深度学习新人必看:《小白入门深度学习》

    1. 小白入门深度学习 | 第一篇:配置深度学习环境
    2. 小白入门深度学习 | 第二篇:编译器的使用-Jupyter Notebook
    3. 小白入门深度学习 | 第三篇:深度学习初体验
    4. 小白入门深度学习 | 第四篇:配置PyTorch环境
    5. 小白入门深度学习 | 第五篇:数据不均衡的处理方法

    🚀 往期精彩-卷积神经网络篇:

    1. 深度学习100例-卷积神经网络(CNN)实现mnist手写数字识别 | 第1天
    2. 深度学习100例-卷积神经网络(CNN)彩色图片分类 | 第2天
    3. 深度学习100例-卷积神经网络(CNN)服装图像分类 | 第3天
    4. 深度学习100例-卷积神经网络(CNN)花朵识别 | 第4天
    5. 深度学习100例-卷积神经网络(CNN)天气识别 | 第5天
    6. 深度学习100例-卷积神经网络(VGG-16)识别海贼王草帽一伙 | 第6天
    7. 深度学习100例-卷积神经网络(VGG-19)识别灵笼中的人物 | 第7天
    8. 深度学习100例-卷积神经网络(ResNet-50)鸟类识别 | 第8天
    9. 深度学习100例-卷积神经网络(AlexNet)手把手教学 | 第11天
    10. 深度学习100例-卷积神经网络(CNN)识别验证码 | 第12天
    11. 深度学习100例-卷积神经网络(Inception V3)识别手语 | 第13天
    12. 深度学习100例-卷积神经网络(Inception-ResNet-v2)识别交通标志 | 第14天
    13. 深度学习100例-卷积神经网络(CNN)实现车牌识别 | 第15天
    14. 深度学习100例-卷积神经网络(CNN)识别神奇宝贝小智一伙 | 第16天
    15. 深度学习100例-卷积神经网络(CNN)注意力检测 | 第17天
    16. 深度学习100例-卷积神经网络(LeNet-5)深度学习里的“Hello Word” | 第22天
    17. 深度学习100例-卷积神经网络(CNN)3D医疗影像识别 | 第23天
    18. 深度学习100例 | 第24天-卷积神经网络(Xception):动物识别
    19. 深度学习100例 | 第25天-卷积神经网络(CNN):中文手写数字识别
    20. 深度学习100例 | 第26天-卷积神经网络(CNN):乳腺癌识别
    21. 深度学习100例 | 第27天-卷积神经网络(CNN):艺术作品识别
    22. 深度学习100例 | 第28天:水果的识别与分类(准确率99.9%)
    23. 深度学习100例 | 第29天-ResNet50模型:船型识别
    24. 深度学习100例 | 第30天-MobileNetV2算法:动物识别(90类)
    25. 深度学习100例 | 第33天:迁移学习-实战案例教程(必须掌握的一个点)
    26. 深度学习100例 | 第34天:如何进行数据增强?
    27. 深度学习100例 | 第35天:脑肿瘤识别

    🚀 往期精彩-循环神经网络篇:

    1. 深度学习100例-循环神经网络(RNN)实现股票预测 | 第9天
    2. 深度学习100例-循环神经网络(LSTM)实现股票预测 | 第10天
    3. 深度学习100例 | 第32天-GRU模型:算法生成小说

    🚀 往期精彩-生成对抗网络篇:

    1. 深度学习100例-生成对抗网络(GAN)手写数字生成 | 第18天
    2. 深度学习100例-生成对抗网络(DCGAN)手写数字生成 | 第19天
    3. 深度学习100例-生成对抗网络(DCGAN)生成动漫小姐姐 | 第20天

    我们的代码流程图如下所示:

    一、设置GPU

    import tensorflow as tf
    gpus = tf.config.list_physical_devices("GPU")
    
    if gpus:
        gpu0 = gpus[0] #如果有多个GPU,仅使用第0个GPU
        tf.config.experimental.set_memory_growth(gpu0, True) #设置GPU显存用量按需使用
        tf.config.set_visible_devices([gpu0],"GPU")
        
    import matplotlib.pyplot as plt
    import os,PIL,pathlib
    import numpy as np
    import pandas as pd
    import warnings
    from tensorflow import keras
    
    warnings.filterwarnings("ignore")#忽略警告信息
    plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
    plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    二、导入数据

    1. 查看数据情况

    import pathlib
    
    data_dir = "./31-data/"
    data_dir = pathlib.Path(data_dir)
    image_count = len(list(data_dir.glob('*/*')))
    print("图片总数为:",image_count)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    图片总数为: 30608
    
    • 1
    # 统计每一个类别的数目
    class_name = []
    class_sum  = []
    
    for i in data_dir.glob('*'):
    
        one_dir = ".\\" + str(i)
        one_dir = pathlib.Path(one_dir)
        
        class_name.append(str(i).split(".")[1])
        class_sum.append(len(list(one_dir.glob('*'))))
    
    class_dict = {'class_name':class_name,'class_sum':class_sum}
    class_df   = pd.DataFrame(class_dict,columns=['class_name', 'class_sum'])
    # 按照图片数量进行降序排序
    class_df = class_df.sort_values(by="class_sum" , ascending=False)
    class_df.head()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    class_nameclass_sum
    256clutter827
    250airplanes-101800
    144motorbikes-101798
    252faces-easy-101435
    231t-shirt358
    def draw_bar(X,Y):
        
        plt.figure(figsize=(6, 4))
    
        plt.barh(X, Y, align='center',color=['r','g','b','c','y','m'],alpha=0.7)
        plt.xticks(fontsize=12)    # 设置刻度字体大小
        plt.yticks(fontsize=12)   
        plt.xlabel('图片数量',size=12)  #设置x轴标签
    #     plt.ylabel('y轴',size=12)  #设置y轴标签
        plt.title('数据集数量top-5与bottom-5对比分析',size=14)
        
        plt.show()
    
    top_5_X    = list(class_df["class_name"][  :5])
    top_5_Y    = list(class_df["class_sum"][  :5])
    
    bottom_5_X = list(class_df["class_name"][-5: ])
    bottom_5_Y = list(class_df["class_sum"][-5: ])
    
    X= top_5_X+bottom_5_X
    Y =top_5_Y+bottom_5_Y
    
    draw_bar(X,Y)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在实验开始时查看数据集分布情况,部分类别图片数量过少时,需要及时补充数据。

    2. 加载数据

    batch_size = 16
    img_height = 224
    img_width  = 224
    
    • 1
    • 2
    • 3
    """
    关于image_dataset_from_directory()的详细介绍可以参考文章:https://mtyjkh.blog.csdn.net/article/details/117018789
    
    通过该方法导入数据时,会同时打乱数据
    """
    train_ds = tf.keras.preprocessing.image_dataset_from_directory(
        data_dir,
        validation_split=0.2,
        subset="training",
        seed=12,
        image_size=(img_height, img_width),
        batch_size=batch_size)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    Found 30607 files belonging to 257 classes.
    Using 24486 files for training.
    
    • 1
    • 2
    """
    关于image_dataset_from_directory()的详细介绍可以参考文章:https://mtyjkh.blog.csdn.net/article/details/117018789
    
    通过该方法导入数据时,会同时打乱数据
    """
    val_ds = tf.keras.preprocessing.image_dataset_from_directory(
        data_dir,
        validation_split=0.2,
        subset="validation",
        seed=12,
        image_size=(img_height, img_width),
        batch_size=batch_size)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    Found 30607 files belonging to 257 classes.
    Using 6121 files for validation.
    
    • 1
    • 2
    class_names = list(class_df["class_name"])
    # print("数据类别有:",class_names)
    print("需要识别的物体一共有%d类"%len(class_names))
    
    • 1
    • 2
    • 3
    需要识别的物体一共有257类
    
    • 1
    for image_batch, labels_batch in train_ds:
        print(image_batch.shape)
        print(labels_batch.shape)
        break
    
    • 1
    • 2
    • 3
    • 4
    (16, 224, 224, 3)
    (16,)
    
    • 1
    • 2

    3. 配置数据集

    • shuffle() : 打乱数据。
    • prefetch() : 预取数据,加速运行,其详细介绍可以参考我前两篇文章,里面都有讲解。
    • cache() : 将数据集缓存到内存当中,加速运行
    AUTOTUNE = tf.data.AUTOTUNE
    
    def train_preprocessing(image,label):
        return (image/255.0,label)
    
    train_ds = (
        train_ds#.cache()              # 如果数据量过大,尽量不要缓存,不然电脑会卡死
        .map(train_preprocessing)      # 这里可以设置预处理函数
        .prefetch(buffer_size=AUTOTUNE)
    )
    
    val_ds = (
        val_ds#.cache()                # 如果数据量过大,尽量不要缓存,不然电脑会卡死
        .map(train_preprocessing)      # 这里可以设置预处理函数
        .prefetch(buffer_size=AUTOTUNE)
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    4. 数据可视化

    plt.figure(figsize=(10, 8))  # 图形的宽为10高为5
    plt.suptitle("数据展示")
    
    for images, labels in train_ds.take(1):
        for i in range(15):
            plt.subplot(4, 5, i + 1)
            plt.xticks([])
            plt.yticks([])
            plt.grid(False)
    
            # 显示图片
            plt.imshow(images[i])
            # 显示标签
            # plt.xlabel(class_names[labels[i]-1])
    
    plt.show()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    三、构建DenseNet模型

    from tensorflow.keras.models import Model
    from tensorflow.keras import layers
    from tensorflow.keras import backend 
    
    def dense_block(x, blocks, name):
        for i in range(blocks):
            x = conv_block(x, 32, name=name + '_block' + str(i + 1))
        return x
    
    def conv_block(x, growth_rate, name):
        bn_axis = 3 
        x1 = layers.BatchNormalization(axis=bn_axis,
                                       epsilon=1.001e-5,
                                       name=name + '_0_bn')(x)
        x1 = layers.Activation('relu', name=name + '_0_relu')(x1)
        x1 = layers.Conv2D(4 * growth_rate, 1,
                           use_bias=False,
                           name=name + '_1_conv')(x1)
        x1 = layers.BatchNormalization(axis=bn_axis, epsilon=1.001e-5,
                                       name=name + '_1_bn')(x1)
        x1 = layers.Activation('relu', name=name + '_1_relu')(x1)
        x1 = layers.Conv2D(growth_rate, 3,
                           padding='same',
                           use_bias=False,
                           name=name + '_2_conv')(x1)
        x = layers.Concatenate(axis=bn_axis, name=name + '_concat')([x, x1])
        return x
    
    def transition_block(x, reduction, name):
        bn_axis = 3
        x = layers.BatchNormalization(axis=bn_axis, epsilon=1.001e-5,
                                      name=name + '_bn')(x)
        x = layers.Activation('relu', name=name + '_relu')(x)
        x = layers.Conv2D(int(backend.int_shape(x)[bn_axis] * reduction), 1,
                          use_bias=False,
                          name=name + '_conv')(x)
        x = layers.AveragePooling2D(2, strides=2, name=name + '_pool')(x)
        return x
    
    def DenseNet(blocks, input_shape=None, classes=1000, **kwargs):
    
        img_input = layers.Input(shape=input_shape)
    
        bn_axis = 3
    
        # 224,224,3 -> 112,112,64
        x = layers.ZeroPadding2D(padding=((3, 3), (3, 3)))(img_input)
        x = layers.Conv2D(64, 7, strides=2, use_bias=False, name='conv1/conv')(x)
        x = layers.BatchNormalization(
            axis=bn_axis, epsilon=1.001e-5, name='conv1/bn')(x)
        x = layers.Activation('relu', name='conv1/relu')(x)
        
        # 112,112,64 -> 56,56,64
        x = layers.ZeroPadding2D(padding=((1, 1), (1, 1)))(x)
        x = layers.MaxPooling2D(3, strides=2, name='pool1')(x)
    
        # 56,56,64 -> 56,56,64+32*block[0]
        # Densenet121 56,56,64 -> 56,56,64+32*6 == 56,56,256
        x = dense_block(x, blocks[0], name='conv2')
    
        # 56,56,64+32*block[0] -> 28,28,32+16*block[0]
        # Densenet121 56,56,256 -> 28,28,32+16*6 == 28,28,128
        x = transition_block(x, 0.5, name='pool2')
    
        # 28,28,32+16*block[0] -> 28,28,32+16*block[0]+32*block[1]
        # Densenet121 28,28,128 -> 28,28,128+32*12 == 28,28,512
        x = dense_block(x, blocks[1], name='conv3')
        
        # Densenet121 28,28,512 -> 14,14,256
        x = transition_block(x, 0.5, name='pool3')
    
        # Densenet121 14,14,256 -> 14,14,256+32*block[2] == 14,14,1024
        x = dense_block(x, blocks[2], name='conv4')
    
        # Densenet121 14,14,1024 -> 7,7,512
        x = transition_block(x, 0.5, name='pool4')
    
        # Densenet121 7,7,512 -> 7,7,256+32*block[3] == 7,7,1024
        x = dense_block(x, blocks[3], name='conv5')
        
        x = layers.BatchNormalization(axis=bn_axis, epsilon=1.001e-5, name='bn')(x)
        x = layers.Activation('relu', name='relu')(x)
    
        x = layers.GlobalAveragePooling2D(name='avg_pool')(x)
        x = layers.Dense(classes, activation='softmax', name='fc1000')(x)
    
        inputs = img_input
    
        if blocks == [6, 12, 24, 16]:
            model = Model(inputs, x, name='densenet121')
        elif blocks == [6, 12, 32, 32]:
            model = Model(inputs, x, name='densenet169')
        elif blocks == [6, 12, 48, 32]:
            model = Model(inputs, x, name='densenet201')
        else:
            model = Model(inputs, x, name='densenet')
        return model
    
    def DenseNet121(input_shape=[224,224,3], classes=len(class_names), **kwargs):
        return DenseNet([6, 12, 24, 16], input_shape, classes, **kwargs)
    
    def DenseNet169(input_shape=[224,224,3], classes=len(class_names), **kwargs):
        return DenseNet([6, 12, 32, 32], input_shape, classes, **kwargs)
    
    def DenseNet201(input_shape=[224,224,3], classes=len(class_names), **kwargs):
        return DenseNet([6, 12, 48, 32], input_shape, classes, **kwargs)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106

    四、编译

    model = DenseNet121()
    
    optimizer = tf.keras.optimizers.Adam(lr=1e-3)
    
    model.compile(optimizer=optimizer,
                  loss='sparse_categorical_crossentropy',
                  metrics=['accuracy'])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    五、训练模型

    from tensorflow.keras.callbacks import ModelCheckpoint, Callback, EarlyStopping, ReduceLROnPlateau, LearningRateScheduler
    
    NO_EPOCHS = 40
    PATIENCE  = 5
    VERBOSE   = 1
    
    # 设置动态学习率
    # annealer = LearningRateScheduler(lambda x: 1e-4 * 0.98 ** x)
    
    # 设置早停
    # earlystopper = EarlyStopping(monitor='loss', patience=PATIENCE, verbose=VERBOSE)
    
    # 设置回调函数
    checkpointer = ModelCheckpoint('best_model.h5',
                                    monitor='val_accuracy',
                                    verbose=VERBOSE,
                                    save_best_only=True,
                                    save_weights_only=True)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    train_model  = model.fit(train_ds,
                      epochs=NO_EPOCHS,
                      verbose=1,
                      validation_data=val_ds,
                      callbacks=[checkpointer])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    Epoch 1/40
    1531/1531 [==============================] - 155s 94ms/step - loss: 5.1739 - accuracy: 0.0677 - val_loss: 19.6963 - val_accuracy: 0.0601
    
    Epoch 00001: val_accuracy improved from -inf to 0.06012, saving model to best_model.h5
    Epoch 2/40
    1531/1531 [==============================] - 140s 92ms/step - loss: 4.7255 - accuracy: 0.1058 - val_loss: 4.7597 - val_accuracy: 0.1132
    
    Epoch 00002: val_accuracy improved from 0.06012 to 0.11322, saving model to best_model.h5
    Epoch 3/40
    1531/1531 [==============================] - 139s 91ms/step - loss: 4.3067 - accuracy: 0.1527 - val_loss: 4.2869 - val_accuracy: 0.1626
    
     ......
    
    Epoch 00036: val_accuracy did not improve from 0.44257
    Epoch 37/40
    1531/1531 [==============================] - 140s 91ms/step - loss: 0.1387 - accuracy: 0.9556 - val_loss: 4.4015 - val_accuracy: 0.4468
    
    Epoch 00037: val_accuracy improved from 0.44257 to 0.44682, saving model to best_model.h5
    Epoch 38/40
    1531/1531 [==============================] - 140s 91ms/step - loss: 0.1244 - accuracy: 0.9622 - val_loss: 4.2792 - val_accuracy: 0.4485
    
    Epoch 00038: val_accuracy improved from 0.44682 to 0.44846, saving model to best_model.h5
    Epoch 39/40
    1531/1531 [==============================] - 136s 89ms/step - loss: 0.1210 - accuracy: 0.9627 - val_loss: 4.3829 - val_accuracy: 0.4511
    
    Epoch 00039: val_accuracy improved from 0.44846 to 0.45107, saving model to best_model.h5
    Epoch 40/40
    1531/1531 [==============================] - 138s 90ms/step - loss: 0.1113 - accuracy: 0.9668 - val_loss: 4.4362 - val_accuracy: 0.4527
    
    Epoch 00040: val_accuracy improved from 0.45107 to 0.45270, saving model to best_model.h5
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    六、评估模型

    1. Accuracy与Loss图

    acc = train_model.history['accuracy']
    val_acc = train_model.history['val_accuracy']
    
    loss = train_model.history['loss']
    val_loss = train_model.history['val_loss']
    
    epochs_range = range(len(acc))
    
    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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    加入Dropout层后过拟合现象得到了缓解,没有那么明显了。

    2. 各项指标评估

    from sklearn import metrics
    
    def test_accuracy_report(model):
    #     print(metrics.classification_report(val_label, val_pre, target_names=class_names)) 
        score = model.evaluate(val_ds, verbose=0)
        print('Loss : %s, accuracy:' % score[0], score[1])
        
    test_accuracy_report(model)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    Loss : 4.436242580413818, accuracy: 0.4527038037776947
    
    • 1
    
    
    • 1
    
    
    • 1
    
    
    • 1
  • 相关阅读:
    【计算机视觉】3D视觉
    什么是React的虚拟DOM?它如何工作以提高性能?
    Arduino从零开始(1)——按钮控制LED
    Win11如何取消任务栏隐藏?Win11取消任务栏隐藏的方法
    等保设备是什么,等保设备有哪些
    Redis 只会用缓存?20种妙用让同事直呼牛X(荣耀典藏版)
    基于LS-SVM对偶问题的分类、回归、时间序列预测和无监督学习研究(Matlab代码实现)
    猿创征文|学习记录之 PHP 中的面向对象编程
    如何快速高效的掌握一门新技术
    2.X版本又一个极端情况下的偶现严重问题
  • 原文地址:https://blog.csdn.net/qq_38251616/article/details/131973363
  • 最新文章
  • 攻防演习之三天拿下官网站群
    数据安全治理学习——前期安全规划和安全管理体系建设
    企业安全 | 企业内一次钓鱼演练准备过程
    内网渗透测试 | Kerberos协议及其部分攻击手法
    0day的产生 | 不懂代码的"代码审计"
    安装scrcpy-client模块av模块异常,环境问题解决方案
    leetcode hot100【LeetCode 279. 完全平方数】java实现
    OpenWrt下安装Mosquitto
    AnatoMask论文汇总
    【AI日记】24.11.01 LangChain、openai api和github copilot
  • 热门文章
  • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
    奉劝各位学弟学妹们,该打造你的技术影响力了!
    五年了,我在 CSDN 的两个一百万。
    Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
    面试官都震惊,你这网络基础可以啊!
    你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
    心情不好的时候,用 Python 画棵樱花树送给自己吧
    通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
    13 万字 C 语言从入门到精通保姆级教程2021 年版
    10行代码集2000张美女图,Python爬虫120例,再上征途
Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1
正则表达式工具 cron表达式工具 密码生成工具

京公网安备 11010502049817号