为了账号安全,请及时绑定邮箱和手机立即绑定

CNN入门:轻松掌握卷积神经网络基础

概述

卷积神经网络(CNN)是一种专门处理图像数据的深度学习模型,在计算机视觉领域取得了显著成就。本文将介绍CNN的基本组成部分、应用场景以及如何使用Python和TensorFlow构建CNN模型。文章还将详细讲解CNN的工作原理、超参数调整和数据增强技术,帮助读者快速掌握CNN入门知识。

什么是CNN

卷积神经网络简介

卷积神经网络(Convolutional Neural Network,简称CNN)是一种专门用于处理图像数据的深度学习模型。它在计算机视觉领域中取得了显著的成就,广泛应用于图像分类、目标检测、图像分割等多种任务。

CNN的核心思想是通过局部感受野、权重共享和池化等机制,有效地提取图像的局部特征,并通过多层网络学习到更复杂的特征表示。这种设计使得CNN能够高效地处理图像数据,并在识别任务中达到很高的准确率。

CNN的基本组成部分

CNN的主要组成部分包括卷积层、池化层、全连接层和激活函数。这些组件共同构成了CNN的基本架构,使得网络能够有效地学习和提取图像特征。

  1. 卷积层(Convolutional Layer):卷积层通过滑动窗口(卷积核)在输入上进行扫描,提取局部特征。卷积核的参数在一层内部是共享的,这有助于减少参数数量并提高模型的表达能力。

  2. 池化层(Pooling Layer):池化层用于对特征图进行下采样,减少特征图的空间大小,降低模型复杂度,同时保留重要的特征。常用的池化操作包括最大池化(Max Pooling)和平均池化(Average Pooling)。

  3. 全连接层(Fully Connected Layer):全连接层负责将卷积层和池化层提取的特征映射到最终的分类任务上,通常用于对特征进行线性组合并输出最终的分类结果。

  4. 激活函数(Activation Function):激活函数用于引入非线性,使得模型能够学习到更加复杂的特征。常用的激活函数包括ReLU、Sigmoid和Tanh等。

CNN的应用场景

卷积神经网络的应用场景非常广泛,主要集中在处理图像数据相关的任务上。以下是一些常见的应用场景:

  1. 图像分类:将图像分类为预定义的类别,例如在ImageNet数据集上的图像分类任务。
  2. 目标检测:识别图像中的特定目标并定位其位置,例如物体检测任务。
  3. 图像分割:将图像中的每个像素分类为不同的类别,例如语义分割和实例分割任务。
  4. 图像生成:生成具有特定特性的图像,例如通过生成对抗网络(GAN)生成逼真的图像。
  5. 图像修复:修复图像中的缺失或损坏部分,例如去除图像中的噪声或填补图像的空洞。
  6. 图像超分辨率:将低分辨率图像转换为高分辨率图像,例如超分辨率重建。

通过这些应用,CNN展示了其在图像处理任务中的强大能力和广泛适用性。

CNN的基本架构

卷积层详解

卷积层是CNN的核心组成部分,它通过卷积操作提取图像的局部特征。卷积层通常由卷积核(或称为滤波器)组成,这些卷积核通过滑动窗口的方式在输入上扫描,以提取局部特征。每个卷积核共享相同的参数,这有助于减少参数数量并提高模型的表达能力。

卷积层的主要参数包括:

  1. 卷积核大小(Kernel Size):卷积核的大小决定了卷积层的感受野。常用的卷积核大小包括3x3、5x5等。
  2. 卷积核数量(Number of Kernels):卷积核的数量决定了卷积层输出的特征图的数量。更多的卷积核可以提取更丰富的特征。
  3. 步长(Stride):卷积核每次移动的步长。步长为1时,卷积核每次移动一格;步长为2时,卷积核每次移动两格。
  4. 填充(Padding):填充指的是在输入周围添加零填充,以保持输出特征图的大小。常用的填充方式包括无填充(Valid)、全填充(Same)等。

下面是一个简单的卷积层示例,展示了如何使用TensorFlow构建卷积层:

import tensorflow as tf

# 定义输入形状
input_shape = (None, 28, 28, 1)  # 输入形状为 (batch_size, height, width, channels)

# 构建卷积层
conv_layer = tf.keras.layers.Conv2D(
    filters=32,                 # 卷积核数量
    kernel_size=(3, 3),         # 卷积核大小 (3x3)
    strides=(1, 1),             # 步长 (1x1)
    padding='same',             # 填充方式 (全填充)
    activation='relu'           # 激活函数 (ReLU)
)

# 输入示例数据
input_tensor = tf.random.normal(input_shape)

# 通过卷积层处理输入数据
output_tensor = conv_layer(input_tensor)

print(output_tensor.shape)  # 输出形状为 (batch_size, height, width, filters)

该代码定义了一个卷积层,使用3x3的卷积核,卷积核数量为32,步长为1,全填充,激活函数为ReLU。输入数据的形状为(批量大小, 高度, 宽度, 通道数),输出的形状为(批量大小, 高度, 宽度, 卷积核数量)。

池化层功能

池化层用于对特征图进行下采样,减少特征图的空间大小,从而降低模型的复杂度。同时,池化层还能保留重要的特征,提高模型的鲁棒性。常见的池化操作包括最大池化(Max Pooling)和平均池化(Average Pooling)。

最大池化(Max Pooling):最大池化层通过滑动窗口在特征图上进行扫描,每次取窗口内的最大值作为输出。最大池化可以有效地降低特征图的空间维度,同时保留重要的特征。

平均池化(Average Pooling):平均池化层通过滑动窗口在特征图上进行扫描,每次取窗口内的平均值作为输出。平均池化也可以减少特征图的空间维度,但相比于最大池化,平均池化保留了更多的上下文信息。

下面是一个简单的池化层示例,展示了如何使用TensorFlow构建最大池化层:

import tensorflow as tf

# 定义输入形状
input_shape = (None, 28, 28, 32)  # 输入形状为 (batch_size, height, width, channels)

# 构建最大池化层
pool_layer = tf.keras.layers.MaxPooling2D(
    pool_size=(2, 2),             # 池化窗口大小 (2x2)
    strides=(2, 2),               # 步长 (2x2)
    padding='same'                # 填充方式 (全填充)
)

# 输入示例数据
input_tensor = tf.random.normal(input_shape)

# 通过池化层处理输入数据
output_tensor = pool_layer(input_tensor)

print(output_tensor.shape)  # 输出形状为 (batch_size, height/2, width/2, channels)

该代码定义了一个最大池化层,池化窗口大小为2x2,步长为2,全填充。输入数据的形状为(批量大小, 高度, 宽度, 通道数),输出的形状为(批量大小, 高度/2, 宽度/2, 通道数)。

全连接层作用

全连接层用于将卷积层和池化层提取的特征映射到最终的分类任务上。通常,全连接层位于卷积层和池化层之后,用于将特征图展平并映射到最终的分类输出。

全连接层的主要作用包括:

  1. 特征映射:将卷积层和池化层提取的特征映射到高维空间,以便进一步分类。
  2. 分类输出:通过全连接层将特征图展平并映射到最终的分类输出,通常使用Softmax激活函数进行多分类任务。

下面是一个简单的全连接层示例,展示了如何使用TensorFlow构建全连接层:

import tensorflow as tf

# 定义输入形状
input_shape = (None, 7, 7, 64)  # 输入形状为 (batch_size, height, width, channels)

# 构建全连接层
fc_layer = tf.keras.layers.Dense(
    units=128,                   # 输出单元数
    activation='relu'            # 激活函数 (ReLU)
)

# 输入示例数据
input_tensor = tf.random.normal(input_shape)

# 将特征图展平
input_tensor = tf.keras.layers.Flatten()(input_tensor)

# 通过全连接层处理输入数据
output_tensor = fc_layer(input_tensor)

print(output_tensor.shape)  # 输出形状为 (batch_size, units)

该代码定义了一个全连接层,输出单元数为128,激活函数为ReLU。输入数据的形状为(批量大小, 高度, 宽度, 通道数),首先将输入数据展平,然后通过全连接层处理,输出的形状为(批量大小, 单元数)。

激活函数的作用

激活函数在神经网络中起到引入非线性的作用,使得模型能够学习到更加复杂的特征。常见的激活函数包括ReLU、Sigmoid、Tanh等。

ReLU(Rectified Linear Unit):ReLU激活函数定义为$f(x) = max(0, x)$,在$x \leq 0$时输出0,在$x > 0$时输出$x$。ReLU激活函数简单且计算效率高,同时可以引入非线性,使得模型能够学习到更复杂的特征。

Sigmoid:Sigmoid激活函数定义为$f(x) = \frac{1}{1+e^{-x}}$,将输入映射到(0, 1)区间。Sigmoid函数在某些情况下能够将输出归一化到0-1之间,但其计算效率较低且容易导致梯度消失问题。

Tanh:Tanh激活函数定义为$f(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}}$,将输入映射到(-1, 1)区间。Tanh激活函数与Sigmoid类似,但其输出范围更广,且中心对称。

下面是一个简单的激活函数示例,展示了如何使用TensorFlow构建ReLU激活函数:

import tensorflow as tf

# 定义输入
x = tf.random.normal((10, 10))  # 输入形状为 (batch_size, units)

# 构建ReLU激活函数
relu_activation = tf.keras.layers.ReLU()

# 通过ReLU激活函数处理输入
output = relu_activation(x)

print(output)

该代码定义了一个ReLU激活函数,输入数据的形状为(批量大小, 单元数),输出的形状与输入相同。ReLU激活函数将所有负数映射为0,保持正数不变。

CNN的工作原理

数据流过程

CNN的工作原理涉及数据流过程、权重与偏置的理解以及反向传播算法的使用。以下是CNN的数据流过程:

  1. 输入数据:输入数据通常为图像数据,形状为(批量大小, 高度, 宽度, 通道数)。
  2. 卷积层:输入数据通过卷积层处理,提取局部特征。卷积层通过卷积核扫描输入数据,生成多个特征图。
  3. 池化层:特征图通过池化层处理,降低特征图的空间维度。池化层通过滑动窗口在特征图上进行扫描,取每个窗口的最大值或平均值作为输出。
  4. 全连接层:特征图通过全连接层处理,映射到最终的分类输出。全连接层将特征图展平并映射到高维空间,以便进一步分类。
  5. 输出结果:全连接层输出分类结果,通常使用Softmax激活函数进行多分类任务。

权重与偏置理解

在CNN中,权重和偏置是模型的重要参数,用于定义模型的结构和计算过程。

权重(Weights):权重是卷积核的参数,决定了卷积层的感受野。每个卷积核共享相同的参数,这有助于减少参数数量并提高模型的表达能力。

偏置(Biases):偏置是卷积层和全连接层中的可学习参数,用于调整每个特征图的输出。偏置使得模型在学习过程中更加灵活,可以更好地适应不同的数据分布。

权重和偏置通过反向传播算法进行更新,以最小化损失函数。反向传播算法通过计算损失函数对权重和偏置的梯度,更新权重和偏置,使得模型能够更好地拟合训练数据。

下面是一个简单的权重与偏置的示例,展示了如何使用TensorFlow初始化权重和偏置:

import tensorflow as tf

# 初始化权重
weights = tf.Variable(tf.random.normal((3, 3, 1, 32)))  # 权重形状为 (kernel_height, kernel_width, input_channels, output_channels)

# 初始化偏置
biases = tf.Variable(tf.zeros((32,)))  # 偏置形状为 (output_channels,)

print(weights)
print(biases)

该代码初始化了权重和偏置。权重的形状为(卷积核高度, 卷积核宽度, 输入通道数, 输出通道数),偏置的形状为(输出通道数,)。

反向传播算法简介

反向传播算法是深度学习中用于训练神经网络的重要算法,其主要目标是通过计算损失函数对权重和偏置的梯度,更新模型参数,以最小化损失函数。反向传播算法的核心步骤包括:

  1. 前向传播:将输入数据通过整个网络,计算输出结果。
  2. 计算损失:根据输出结果和目标标签计算损失函数值。
  3. 计算梯度:通过链式法则计算损失函数对权重和偏置的梯度。
  4. 更新参数:根据梯度更新权重和偏置,使得损失函数值最小化。

反向传播算法通过逐层计算梯度,有效地更新模型参数,使得模型能够更好地拟合训练数据。以下是使用TensorFlow实现反向传播算法的一个简单示例:

import tensorflow as tf

# 定义输入数据
input_data = tf.random.normal((10, 10))  # 输入形状为 (batch_size, units)

# 定义目标数据
target_data = tf.random.normal((10, 10))  # 目标形状为 (batch_size, units)

# 定义模型
model = tf.keras.Sequential([
    tf.keras.layers.Dense(units=10, activation='relu'),
    tf.keras.layers.Dense(units=10, activation='linear')
])

# 定义损失函数
loss_fn = tf.keras.losses.MeanSquaredError()

# 定义优化器
optimizer = tf.keras.optimizers.Adam()

# 训练模型
with tf.GradientTape() as tape:
    predictions = model(input_data)
    loss_value = loss_fn(target_data, predictions)

# 计算梯度
grads = tape.gradient(loss_value, model.trainable_variables)

# 更新参数
optimizer.apply_gradients(zip(grads, model.trainable_variables))

print(loss_value)

该代码定义了一个简单的模型,包括两个全连接层。通过输入数据和目标数据计算损失函数值,并使用Adam优化器计算梯度和更新模型参数,以最小化损失函数值。

使用Python和TensorFlow构建CNN

安装必要的库

在开始构建CNN模型之前,首先需要安装TensorFlow库。TensorFlow是一个强大的深度学习框架,支持构建和训练各种深度学习模型。以下是安装TensorFlow的步骤:

pip install tensorflow

安装完成后,可以通过以下代码导入TensorFlow库:

import tensorflow as tf

导入数据集

在构建CNN模型之前,需要导入数据集。在本例中,我们将使用MNIST手写数字数据集,这是一个广泛使用的图像分类数据集,包含60000张训练图像和10000张测试图像,每张图像都是28x28像素的灰度图像。

from tensorflow.keras.datasets import mnist
from tensorflow.keras.utils import to_categorical

# 加载MNIST数据集
(x_train, y_train), (x_test, y_test) = mnist.load_data()

# 数据预处理
x_train = x_train.astype('float32') / 255.0  # 归一化到[0, 1]区间
x_test = x_test.astype('float32') / 255.0
x_train = x_train[..., tf.newaxis]          # 增加通道维度
x_test = x_test[..., tf.newaxis]
y_train = to_categorical(y_train, num_classes=10)  # 转换为one-hot编码
y_test = to_categorical(y_test, num_classes=10)

构建CNN模型

接下来,我们将使用TensorFlow构建一个简单的CNN模型。该模型包括卷积层、池化层、全连接层和激活函数。以下是CNN模型的构建过程:

import tensorflow as tf

# 构建CNN模型
model = tf.keras.Sequential([
    tf.keras.layers.Conv2D(32, kernel_size=(3, 3), activation='relu', input_shape=(28, 28, 1)),
    tf.keras.layers.MaxPooling2D(pool_size=(2, 2)),
    tf.keras.layers.Conv2D(64, kernel_size=(3, 3), activation='relu'),
    tf.keras.layers.MaxPooling2D(pool_size=(2, 2)),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(128, activation='relu'),
    tf.keras.layers.Dense(10, activation='softmax')
])

# 编译模型
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])

# 输出模型结构
model.summary()

该代码构建了一个简单的CNN模型,包括两个卷积层、两个池化层、一个展平层、两个全连接层和一个Softmax输出层。模型使用Adam优化器和交叉熵损失函数进行训练,并计算准确率作为评估指标。

模型训练和评估

在模型训练之前,需要定义训练参数,包括批量大小、训练轮数等。然后,使用训练数据集对模型进行训练,并使用测试数据集进行评估。以下是模型的训练和评估过程:

# 训练模型
batch_size = 128
epochs = 10

history = model.fit(x_train, y_train, batch_size=batch_size, epochs=epochs, validation_data=(x_test, y_test))

# 评估模型
test_loss, test_acc = model.evaluate(x_test, y_test)
print("Test accuracy: {:.2f}%".format(test_acc * 100))

该代码使用训练数据集对模型进行训练,每轮使用128个样本进行批量训练,共训练10轮。训练完成后,使用测试数据集评估模型性能,并输出测试准确率。

CNN优化技巧

超参数调整

超参数在CNN模型中扮演着重要角色,通过调整超参数可以显著提升模型性能。常见的超参数包括学习率、批归一化(Batch Normalization)、Dropout率等。

  1. 学习率:学习率决定了模型参数更新的速度。较小的学习率可以使模型稳定但收敛速度较慢,较大的学习率可以使模型快速收敛但可能导致模型振荡或发散。常见的方法包括自适应学习率调整和学习率衰减。
  2. 批归一化(Batch Normalization):批归一化是一种正则化技术,通过标准化每个批次的数据,使得模型更加稳定。批归一化可以加速训练过程,并提高模型的泛化能力。
  3. Dropout率:Dropout是一种正则化技术,通过随机丢弃部分神经元来防止过拟合。合适的Dropout率可以减少过拟合,提高模型的泛化能力。常用的Dropout率范围为0.2到0.5。

下面是一个超参数优化的示例代码,展示了如何调整超参数以提高模型性能:

import tensorflow as tf

# 构建优化后的CNN模型
model = tf.keras.Sequential([
    tf.keras.layers.Conv2D(32, kernel_size=(3, 3), activation='relu', input_shape=(28, 28, 1)),
    tf.keras.layers.BatchNormalization(),
    tf.keras.layers.MaxPooling2D(pool_size=(2, 2)),
    tf.keras.layers.Dropout(0.2),
    tf.keras.layers.Conv2D(64, kernel_size=(3, 3), activation='relu'),
    tf.keras.layers.BatchNormalization(),
    tf.keras.layers.MaxPooling2D(pool_size=(2, 2)),
    tf.keras.layers.Dropout(0.2),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(128, activation='relu'),
    tf.keras.layers.Dropout(0.2),
    tf.keras.layers.Dense(10, activation='softmax')
])

# 编译模型
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.001), loss='categorical_crossentropy', metrics=['accuracy'])

# 输出模型结构
model.summary()

该代码构建了一个优化后的CNN模型,包括批归一化层和Dropout层。通过调整学习率、批归一化和Dropout率,可以提高模型的泛化能力和收敛速度。

数据增强技术

数据增强是一种常用的技巧,通过生成更多的训练数据来防止过拟合。常见的数据增强技术包括随机裁剪、翻转、旋转、平移和缩放等。

下面是一个数据增强的示例代码,展示了如何使用TensorFlow的ImageDataGenerator进行数据增强:

from tensorflow.keras.preprocessing.image import ImageDataGenerator

# 定义数据增强生成器
datagen = ImageDataGenerator(
    rotation_range=20,       # 随机旋转角度范围
    width_shift_range=0.1,   # 水平平移范围
    height_shift_range=0.1,  # 垂直平移范围
    shear_range=0.2,         # 随机剪切范围
    zoom_range=0.2,          # 随机缩放范围
    horizontal_flip=True,     # 随机水平翻转
    fill_mode='nearest'       # 缺失值填充方式
)

# 应用数据增强
datagen.fit(x_train)

该代码定义了一个数据增强生成器,通过随机旋转、平移、剪切和缩放等操作生成更多的训练数据。通过应用数据增强,可以增加训练数据的多样性,提高模型的泛化能力。

模型剪枝和压缩

模型剪枝和压缩是一种减少模型大小和加速模型推理时间的技术。常见的模型剪枝方法包括权重剪枝、通道剪枝和层剪枝等。模型压缩技术包括量化(Quantization)、知识蒸馏(Knowledge Distillation)等。

下面是一个模型剪枝的示例代码,展示了如何使用TensorFlow的Prune API进行模型剪枝:

import tensorflow as tf
from tensorflow_model_optimization.sparsity import keras as sparsity

# 构建模型
model = tf.keras.Sequential([
    tf.keras.layers.Conv2D(32, kernel_size=(3, 3), activation='relu', input_shape=(28, 28, 1)),
    tf.keras.layers.MaxPooling2D(pool_size=(2, 2)),
    tf.keras.layers.Conv2D(64, kernel_size=(3, 3), activation='relu'),
    tf.keras.layers.MaxPooling2D(pool_size=(2, 2)),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(128, activation='relu'),
    tf.keras.layers.Dense(10, activation='softmax')
])

# 编译模型
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])

# 定义剪枝策略
pruning_params = {
    'pruning_schedule': sparsity.PolynomialDecay(
        initial_sparsity=0.0,
        final_sparsity=0.8,
        begin_step=2000,
        end_step=4000
    )
}

# 应用剪枝策略
pruned_model = sparsity.prune_lowMagnitude(model, **pruning_params)

# 训练剪枝模型
pruned_model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
pruned_model.fit(x_train, y_train, epochs=10, validation_data=(x_test, y_test))

# 评估剪枝模型
test_loss, test_acc = pruned_model.evaluate(x_test, y_test)
print("Test accuracy: {:.2f}%".format(test_acc * 100))

该代码定义了一个简单的CNN模型,并使用TensorFlow的Prune API进行模型剪枝。通过调整剪枝策略,可以减少模型的大小并提高模型的推理速度。

实践案例:图像分类任务

准备数据集

在本例中,我们将使用MNIST手写数字数据集进行图像分类任务。MNIST数据集包含60000张训练图像和10000张测试图像,每张图像是28x28像素的灰度图像。我们将使用Python和TensorFlow库进行数据加载和预处理。

import tensorflow as tf
from tensorflow.keras.datasets import mnist
from tensorflow.keras.utils import to_categorical

# 加载MNIST数据集
(x_train, y_train), (x_test, y_test) = mnist.load_data()

# 数据预处理
x_train = x_train.astype('float32') / 255.0  # 归一化到[0, 1]区间
x_test = x_test.astype('float32') / 255.0
x_train = x_train[..., tf.newaxis]          # 增加通道维度
x_test = x_test[..., tf.newaxis]
y_train = to_categorical(y_train, num_classes=10)  # 转换为one-hot编码
y_test = to_categorical(y_test, num_classes=10)

构建并训练模型

接下来,我们将使用TensorFlow构建一个简单的CNN模型,并使用MNIST数据集进行训练。

import tensorflow as tf

# 构建CNN模型
model = tf.keras.Sequential([
    tf.keras.layers.Conv2D(32, kernel_size=(3, 3), activation='relu', input_shape=(28, 28, 1)),
    tf.keras.layers.MaxPooling2D(pool_size=(2, 2)),
    tf.keras.layers.Conv2D(64, kernel_size=(3, 3), activation='relu'),
    tf.keras.layers.MaxPooling2D(pool_size=(2, 2)),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(128, activation='relu'),
    tf.keras.layers.Dense(10, activation='softmax')
])

# 编译模型
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])

# 训练模型
batch_size = 128
epochs = 10

history = model.fit(x_train, y_train, batch_size=batch_size, epochs=epochs, validation_data=(x_test, y_test))

该代码构建了一个简单的CNN模型,包括两个卷积层、两个池化层、一个展平层、两个全连接层和一个Softmax输出层。模型使用Adam优化器和交叉熵损失函数进行训练,并计算准确率作为评估指标。

测试与评估模型性能

在模型训练完成后,我们将使用测试数据集评估模型性能,并输出测试准确率。

# 评估模型
test_loss, test_acc = model.evaluate(x_test, y_test)
print("Test accuracy: {:.2f}%".format(test_acc * 100))

该代码使用测试数据集评估模型性能,并输出测试准确率。通过测试准确率可以了解模型在未知数据上的泛化能力。

以上是使用Python和TensorFlow构建CNN模型并进行图像分类任务的完整过程。通过实际操作,可以更好地理解和掌握CNN的基础知识和实现方法。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消