当前位置:   article > 正文

(3-4)特征提取:特征抽取

特征抽取

3.4  特征抽取

特征抽取是一种将原始数据转化为更高级、更有信息量的表示形式的过程,以便于机器学习模型能够更好地理解和处理数据。与特征选择不同,特征抽取通常是通过转换数据的方式来创建新的特征,而不是从原始特征集中选择子集。

3.4.1  特征抽取的概念

特征抽取是指从原始数据中提取出对于任务有用的、更高级别的信息或特征的过程。在机器学习和数据分析中,原始数据可能包含大量的维度和信息,其中很多信息可能是冗余的、无用的或嘈杂的。特征抽取的目标是通过一系列变换和处理,将原始数据转化为更有信息量、更有区分性的特征,从而改善模型的性能、泛化能力和效率。

特征抽取可以用于不同类型的数据,如文本、图像、音频、时间序列等,它可以通过各种数学和统计方法来实现。下面是特征抽取的几个关键概念:

  1. 数据表示转换:特征抽取涉及将数据从一个表示形式转换为另一个表示形式。这个新的表示形式通常更加适合于机器学习算法的处理和学习。
  2. 降维:在高维数据中,往往存在大量的冗余信息。特征抽取可以通过降维技术将数据映射到低维空间,以减少维度的同时保留重要的信息。
  3. 信息提取:特征抽取的目标是从原始数据中提取出与任务相关的信息。这可能涉及识别模式、关联性、统计属性等。
  4. 非线性变换:特征抽取可以涉及对数据进行非线性变换,以捕捉数据中复杂的关系和模式。
  5. 领域知识:在进行特征抽取时,领域知识可以发挥重要作用,帮助选择合适的变换和特征。
  6. 模型训练前处理:特征抽取通常在模型训练之前进行,以便将经过处理的数据用于训练。它可以帮助提高模型的性能和泛化能力。

特征抽取的目标是将数据转化为更有信息量的表示形式,以便于机器学习模型更好地学习和预测。在选择特征抽取方法时,需要根据数据的类型和任务的需求进行合理的选择,并通过实验进行调整和验证。在在实际应用中,常用的特征抽取方法有:主成分分析(Principal Component Analysis,PCA)、独立成分分析(ICA)、自动编码器(Autoencoders)等。

3.4.2  主成分分析(PCA)

主成分分析(Principal Component Analysis,PCA)是一种线性降维方法,通过将数据投影到新的低维子空间,保留最大方差的特征,以实现维度降低和噪声削减。例如下面是一个使用PyTorch实现主成分分析(PCA)方法进行特征抽取的例子,本实例将使用PCA降低图像数据的维度,并使用降维后的数据训练一个简单的神经网络模型。

实例3-3:PyTorch使用特征选择方法制作神经网络模型(源码路径:daima\3\zhu.py

实例文件zhu.py的具体实现代码如下所示。

  1. # 加载MNIST数据集
  2. transform = transforms.Compose([transforms.ToTensor()])
  3. train_loader = torch.utils.data.DataLoader(datasets.MNIST('./data', train=True, download=True, transform=transform), batch_size=64, shuffle=True)
  4. # 提取数据并进行PCA降维
  5. X = []
  6. y = []
  7. for images, labels in train_loader:
  8. images = images.view(images.size(0), -1) # 将图像展平为向量
  9. X.append(images)
  10. y.append(labels)
  11. X = torch.cat(X, dim=0).numpy()
  12. y = torch.cat(y, dim=0).numpy()
  13. num_components = 20 # 选择降维后的维度
  14. pca = PCA(n_components=num_components)
  15. X_pca = pca.fit_transform(X)
  16. # 划分数据集
  17. X_train, X_test, y_train, y_test = train_test_split(X_pca, y, test_size=0.2, random_state=42)
  18. # 定义简单的神经网络模型
  19. class SimpleModel(nn.Module):
  20. def __init__(self, input_dim, output_dim):
  21. super(SimpleModel, self).__init__()
  22. self.fc = nn.Linear(input_dim, output_dim)
  23. def forward(self, x):
  24. return self.fc(x)
  25. # 设置模型参数
  26. input_dim = num_components
  27. output_dim = 10 # 类别数
  28. learning_rate = 0.01
  29. num_epochs = 10
  30. # 初始化模型、损失函数和优化器
  31. model = SimpleModel(input_dim, output_dim)
  32. criterion = nn.CrossEntropyLoss()
  33. optimizer = optim.SGD(model.parameters(), lr=learning_rate)
  34. # 训练模型
  35. for epoch in range(num_epochs):
  36. inputs = torch.tensor(X_train, dtype=torch.float32)
  37. labels = torch.tensor(y_train, dtype=torch.long)
  38. optimizer.zero_grad()
  39. outputs = model(inputs)
  40. loss = criterion(outputs, labels)
  41. loss.backward()
  42. optimizer.step()
  43. if (epoch+1) % 1 == 0:
  44. print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')
  45. # 在测试集上评估模型性能
  46. with torch.no_grad():
  47. inputs = torch.tensor(X_test, dtype=torch.float32)
  48. labels = torch.tensor(y_test, dtype=torch.long)
  49. outputs = model(inputs)
  50. _, predicted = torch.max(outputs.data, 1)
  51. accuracy = (predicted == labels).sum().item() / labels.size(0)
  52. print(f'Accuracy on test set: {accuracy:.2f}')

在这个例子中,首先加载了MNIST数据集并进行了数据预处理。然后,将图像数据展平为向量,并使用PCA对数据进行降维。接下来,定义了一个简单的神经网络模型,使用降维后的数据进行训练。最后,在测试集上评估了模型的性能。执行后会输出:

  1. Epoch [1/10], Loss: 2.3977
  2. Epoch [2/10], Loss: 2.3872
  3. Epoch [3/10], Loss: 2.3768
  4. Epoch [4/10], Loss: 2.3665
  5. Epoch [5/10], Loss: 2.3563
  6. Epoch [6/10], Loss: 2.3461
  7. Epoch [7/10], Loss: 2.3360
  8. Epoch [8/10], Loss: 2.3260
  9. Epoch [9/10], Loss: 2.3160
  10. Epoch [10/10], Loss: 2.3061
  11. Accuracy on test set: 0.18

下面是一个使用TensorFlow使用主成分分析(PCA)方法进行特征抽取的例子,并保存处理后的模型。

实例3-4:Tensorflow使用PCA方法制作神经网络模型并保存(源码路径:daima\3\tzhu.py

实例文件tzhu.py的具体实现代码如下所示。

  1. import tensorflow as tf
  2. from tensorflow.keras.datasets import mnist
  3. from tensorflow.keras.layers import Input, Dense
  4. from tensorflow.keras.models import Model
  5. from sklearn.decomposition import PCA
  6. from sklearn.model_selection import train_test_split
  7. # 加载MNIST数据集
  8. (X_train, y_train), (X_test, y_test) = mnist.load_data()
  9. X_train = X_train.reshape(-1, 28 * 28) / 255.0 # 归一化
  10. X_test = X_test.reshape(-1, 28 * 28) / 255.0
  11. # 使用PCA进行降维
  12. num_components = 20 # 选择降维后的维度
  13. pca = PCA(n_components=num_components)
  14. X_train_pca = pca.fit_transform(X_train)
  15. X_test_pca = pca.transform(X_test)
  16. # 划分数据集
  17. X_train_split, X_val_split, y_train_split, y_val_split = train_test_split(X_train_pca, y_train, test_size=0.1, random_state=42)
  18. # 定义神经网络模型
  19. input_layer = Input(shape=(num_components,))
  20. x = Dense(128, activation='relu')(input_layer)
  21. output_layer = Dense(10, activation='softmax')(x)
  22. model = Model(inputs=input_layer, outputs=output_layer)
  23. # 编译模型
  24. model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
  25. # 训练模型
  26. batch_size = 64
  27. epochs = 10
  28. history = model.fit(X_train_split, y_train_split, batch_size=batch_size, epochs=epochs, validation_data=(X_val_split, y_val_split))
  29. # 保存模型
  30. model.save('pca_model.h5')
  31. print("Model saved")
  32. # 在测试集上评估模型性能
  33. test_loss, test_accuracy = model.evaluate(X_test_pca, y_test, verbose=0)
  34. print(f'Test accuracy: {test_accuracy:.4f}')
  35. # 加载保存的模型
  36. loaded_model = tf.keras.models.load_model('pca_model.h5')
  37. # 在测试集上评估加载的模型性能
  38. loaded_test_loss, loaded_test_accuracy = loaded_model.evaluate(X_test_pca, y_test, verbose=0)
  39. print(f'Loaded model test accuracy: {loaded_test_accuracy:.4f}')

上述代码的实现流程如下:

  1. 数据加载和预处理:代码开始加载MNIST手写数字数据集,并对图像数据进行预处理,将图像展平为向量,并进行归一化(将像素值从0到255缩放到0到1之间)。
  2. PCA降维:使用PCA算法对训练集的图像数据进行降维,将原始高维数据转换为包含更少特征的低维数据。这将有助于减少数据的维度,并保留数据中的主要信息。
  3. 数据划分:划分降维后的训练集为训练集和验证集,以便在训练模型时进行验证。
  4. 神经网络模型定义:定义了一个简单的神经网络模型,该模型接受PCA降维后的数据作为输入,并包含一个隐层和一个输出层。
  5. 模型编译:编译神经网络模型,指定优化器和损失函数。
  6. 模型训练:使用划分后的训练集对神经网络模型进行训练。训练过程将执行一定数量的epoch(迭代次数),在每个epoch中,模型将根据训练数据进行参数更新,并在验证集上计算性能指标。
  7. 保存模型:保存经过训练的神经网络模型为一个HDF5文件(扩展名为.h5),以便以后加载和使用。
  8. 模型性能评估:使用测试集评估经过训练的神经网络模型的性能,计算并输出测试准确率。
  9. 加载模型和再次评估:加载之前保存的模型,然后使用相同的测试集对加载的模型进行评估,计算并输出加载模型的测试准确率。

执行后会输出:

  1. Epoch 1/10
  2. 844/844 [==============================] - 4s 4ms/step - loss: 0.4939 - accuracy: 0.8608 - val_loss: 0.2515 - val_accuracy: 0.9273
  3. Epoch 2/10
  4. 844/844 [==============================] - 3s 3ms/step - loss: 0.2107 - accuracy: 0.9376 - val_loss: 0.1775 - val_accuracy: 0.9498
  5. Epoch 3/10
  6. 844/844 [==============================] - 4s 5ms/step - loss: 0.1604 - accuracy: 0.9521 - val_loss: 0.1490 - val_accuracy: 0.9577
  7. Epoch 4/10
  8. 844/844 [==============================] - 5s 6ms/step - loss: 0.1363 - accuracy: 0.9592 - val_loss: 0.1332 - val_accuracy: 0.9612
  9. Epoch 5/10
  10. 844/844 [==============================] - 3s 4ms/step - loss: 0.1218 - accuracy: 0.9630 - val_loss: 0.1236 - val_accuracy: 0.9640
  11. Epoch 6/10
  12. 844/844 [==============================] - 3s 3ms/step - loss: 0.1115 - accuracy: 0.9654 - val_loss: 0.1166 - val_accuracy: 0.9638
  13. Epoch 7/10
  14. 844/844 [==============================] - 3s 4ms/step - loss: 0.1034 - accuracy: 0.9681 - val_loss: 0.1091 - val_accuracy: 0.9658
  15. Epoch 8/10
  16. 844/844 [==============================] - 3s 4ms/step - loss: 0.0978 - accuracy: 0.9697 - val_loss: 0.1104 - val_accuracy: 0.9653
  17. Epoch 9/10
  18. 844/844 [==============================] - 2s 3ms/step - loss: 0.0934 - accuracy: 0.9712 - val_loss: 0.1063 - val_accuracy: 0.9657
  19. Epoch 10/10
  20. 844/844 [==============================] - 2s 3ms/step - loss: 0.0890 - accuracy: 0.9727 - val_loss: 0.1034 - val_accuracy: 0.9670
  21. Model saved
  22. Test accuracy: 0.9671
  23. Loaded model test accuracy: 0.9671

3.4.3  独立成分分析(ICA)

独立成分分析(Independent Component Analysis,ICA)是一种用于从混合信号中提取独立成分的统计方法。它的目标是将多个随机信号分离为原始信号的线性组合,使得这些独立成分在某种意义上是统计独立的。

ICA 在信号处理、图像处理、神经科学、脑成像等领域有广泛的应用。与主成分分析(PCA)不同,PCA 旨在找到数据的主要方向,而 ICA 则专注于找到数据中的独立成分。这使得 ICA 在许多实际问题中更有用,特别是当信号是从不同源混合而来时,如麦克风阵列捕获的声音信号、脑电图(EEG)信号等。

ICA 的基本思想是,假设观测信号是源信号的线性混合,而每个观测信号都是源信号的线性组合,其中混合系数和源信号相互独立。通过对观测信号进行变换,可以尝试找到一组独立的成分信号,这些信号通过某种方式是统计上不相关的。

独立成分分析(ICA)通常不用于直接构建模型,而是用于信号处理中的特征提取。因此,在PyTorch中,我们可以使用ICA对数据进行降维和特征提取,然后将提取的特征用于后续模型构建。下面是一个使用PyTorch进行数据降维和模型构建的完整示例,其中包括数据加载、ICA降维、模型构建和保存模型等功能。

实例3-5:使用PyTorch进行ICA数据降维和模型构建(源码路径:daima\3\du.py

实例文件du.py的具体实现代码如下所示。

  1. # 加载MNIST数据集
  2. transform = transforms.Compose([transforms.ToTensor()])
  3. train_loader = torch.utils.data.DataLoader(datasets.MNIST('./data', train=True, download=True, transform=transform), batch_size=64, shuffle=True)
  4. # 提取数据并进行标准化
  5. X = []
  6. y = []
  7. for images, labels in train_loader:
  8. images = images.view(images.size(0), -1) # 将图像展平为向量
  9. X.append(images)
  10. y.append(labels)
  11. X = torch.cat(X, dim=0).numpy()
  12. y = torch.cat(y, dim=0).numpy()
  13. scaler = StandardScaler()
  14. X_scaled = scaler.fit_transform(X)
  15. # 使用FastICA进行降维
  16. num_components = 20 # 选择降维后的成分数
  17. ica = FastICA(n_components=num_components)
  18. X_ica = ica.fit_transform(X_scaled)
  19. # 划分数据集
  20. X_train, X_val, y_train, y_val = train_test_split(X_ica, y, test_size=0.1, random_state=42)
  21. # 定义简单的神经网络模型
  22. class SimpleModel(nn.Module):
  23. def __init__(self, input_dim, output_dim):
  24. super(SimpleModel, self).__init__()
  25. self.fc = nn.Linear(input_dim, output_dim)
  26. def forward(self, x):
  27. return self.fc(x)
  28. # 设置模型参数
  29. input_dim = num_components
  30. output_dim = 10 # 类别数
  31. learning_rate = 0.01
  32. num_epochs = 10
  33. # 初始化模型、损失函数和优化器
  34. model = SimpleModel(input_dim, output_dim)
  35. criterion = nn.CrossEntropyLoss()
  36. optimizer = optim.SGD(model.parameters(), lr=learning_rate)
  37. # 训练模型
  38. for epoch in range(num_epochs):
  39. inputs = torch.tensor(X_train, dtype=torch.float32)
  40. labels = torch.tensor(y_train, dtype=torch.long)
  41. optimizer.zero_grad()
  42. outputs = model(inputs)
  43. loss = criterion(outputs, labels)
  44. loss.backward()
  45. optimizer.step()
  46. if (epoch+1) % 1 == 0:
  47. print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')
  48. # 保存模型
  49. torch.save(model.state_dict(), 'ica_model.pth')
  50. print("Model saved")
  51. # 在验证集上评估模型性能
  52. with torch.no_grad():
  53. inputs = torch.tensor(X_val, dtype=torch.float32)
  54. labels = torch.tensor(y_val, dtype=torch.long)
  55. outputs = model(inputs)
  56. _, predicted = torch.max(outputs.data, 1)
  57. accuracy = (predicted == labels).sum().item() / labels.size(0)
  58. print(f'Validation accuracy: {accuracy:.2f}')

在这个例子中,首先加载了MNIST数据集并进行了数据预处理。然后,使用StandardScaler对数据进行标准化处理,以便进行ICA降维。接下来,使用FastICA进行降维处理,将原始数据降维为20个独立成分。然后,定义了一个简单的神经网络模型,使用降维后的数据进行训练。最后,将训练好的模型保存为模型文件'ica_model.pth'。

在 TensorFlow 中,我们可以使用 ICA 对数据进行降维和特征提取,然后将提取的特征用于后续模型构建。例如下面是一个使用 TensorFlow 进行ICA数据降维和模型构建的例子,其中包括数据加载、ICA 降维、模型构建和保存模型等功能。

实例3-6:使用PyTorch进行ICA数据降维和模型构建(源码路径:daima\3\tdu.py

实例文件tdu.py的具体实现代码如下所示。

  1. # 加载MNIST数据集
  2. (X_train, y_train), (X_test, y_test) = tf.keras.datasets.mnist.load_data()
  3. X_train = X_train.reshape(-1, 28 * 28) / 255.0 # 归一化
  4. X_test = X_test.reshape(-1, 28 * 28) / 255.0
  5. # 使用StandardScaler进行标准化
  6. scaler = StandardScaler()
  7. X_scaled = scaler.fit_transform(X_train)
  8. # 使用FastICA进行降维
  9. num_components = 20 # 选择降维后的成分数
  10. ica = FastICA(n_components=num_components)
  11. X_ica = ica.fit_transform(X_scaled)
  12. # 划分数据集
  13. X_train_split, X_val_split, y_train_split, y_val_split = train_test_split(X_ica, y_train, test_size=0.1, random_state=42)
  14. # 定义神经网络模型
  15. input_layer = Input(shape=(num_components,))
  16. x = Dense(128, activation='relu')(input_layer)
  17. output_layer = Dense(10, activation='softmax')(x)
  18. model = Model(inputs=input_layer, outputs=output_layer)
  19. # 编译模型
  20. model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
  21. # 训练模型
  22. batch_size = 64
  23. epochs = 10
  24. history = model.fit(X_train_split, y_train_split, batch_size=batch_size, epochs=epochs, validation_data=(X_val_split, y_val_split))
  25. # 保存模型
  26. model.save('ica_model1.h5')
  27. print("Model saved")
  28. # 在测试集上评估模型性能
  29. test_loss, test_accuracy = model.evaluate(X_ica, y_test, verbose=0)
  30. print(f'Test accuracy: {test_accuracy:.4f}')

在这个例子中,首先加载了MNIST数据集并进行了数据预处理。然后,使用StandardScaler对数据进行标准化,以便进行ICA降维。接下来,使用FastICA进行降维,将原始数据降维为20个独立成分。然后,定义了一个简单的神经网络模型,使用降维后的数据进行训练。最后,将训练好的模型保存为模型文件'ica_model1.h5'。

3.4.4  自动编码器(Autoencoders)

自动编码器(Autoencoder)是一种无监督学习算法,用于学习有效的数据表示,通常用于特征提取、降维和数据去噪。它由两部分组成:编码器(Encoder)和解码器(Decoder)。编码器将输入数据映射到一个较低维度的表示,而解码器则将该低维度表示映射回原始数据空间,尽可能地复原输入数据。这种结构迫使模型学习到数据的关键特征,从而实现了降维和特征提取的目标。

自动编码器的训练过程是通过最小化输入数据与解码器输出之间的重构误差来实现的。在训练期间,模型尝试找到一个紧凑的表示,以便能够在解码器中恢复输入数据。一旦训练完成,编码器可以用于生成有用的特征表示,这些特征可用于其他任务,如分类、聚类等。例如下面是一个使用 TensorFlow 构建简单自动编码器的例子。

实例3-7:使用 TensorFlow 构建简单自动编码器(源码路径:daima\3\tzi.py

实例文件tzi.py的具体实现代码如下所示。

  1. # 加载MNIST数据集并进行归一化
  2. (X_train, _), (X_test, _) = mnist.load_data()
  3. X_train = X_train.reshape(-1, 28 * 28) / 255.0
  4. X_test = X_test.reshape(-1, 28 * 28) / 255.0
  5. # 定义自动编码器模型
  6. input_dim = 784 # 输入维度,MNIST图像为28x28
  7. encoding_dim = 32 # 编码维度
  8. input_layer = Input(shape=(input_dim,))
  9. encoded = Dense(encoding_dim, activation='relu')(input_layer)
  10. decoded = Dense(input_dim, activation='sigmoid')(encoded)
  11. autoencoder = Model(inputs=input_layer, outputs=decoded)
  12. # 编译自动编码器
  13. autoencoder.compile(optimizer='adam', loss='binary_crossentropy')
  14. # 训练自动编码器
  15. batch_size = 128
  16. epochs = 50
  17. autoencoder.fit(X_train, X_train, batch_size=batch_size, epochs=epochs, shuffle=True, validation_data=(X_test, X_test))
  18. # 保存自动编码器模型
  19. autoencoder.save('autoencoder_model.h5')
  20. print("Model saved")

在这个例子中,定义了一个简单的自动编码器模型,它包括一个输入层、一个编码层和一个解码层。编码层将输入数据映射到32维的编码表示,解码层将编码表示映射回784维的原始数据空间。模型的目标是最小化输入与解码器输出之间的重构误差。训练过程使用MNIST数据集,并将输入数据设置为目标,以最小化重构误差。训练完成后,可以使用训练好的自动编码器模型来生成有用的特征表示,也可以用于数据重建和去噪等任务。

下面是一个使用 PyTorch 构建自动编码器并保存模型的例子,展示了如何使用 PyTorch 构建自动编码器并保存模型,以及如何进行训练和数据加载的过程。

实例3-8:使用 PyTorch 构建自动编码器并保存模型(源码路径:daima\3\zi.py

实例文件zi.py的具体实现代码如下所示。

  1. # 自定义自动编码器类
  2. class Autoencoder(nn.Module):
  3. def __init__(self, encoding_dim):
  4. super(Autoencoder, self).__init__()
  5. self.encoder = nn.Sequential(
  6. nn.Linear(784, encoding_dim),
  7. nn.ReLU()
  8. )
  9. self.decoder = nn.Sequential(
  10. nn.Linear(encoding_dim, 784),
  11. nn.Sigmoid()
  12. )
  13. def forward(self, x):
  14. encoded = self.encoder(x)
  15. decoded = self.decoder(encoded)
  16. return decoded
  17. # 加载MNIST数据集
  18. transform = transforms.Compose([transforms.ToTensor()])
  19. train_dataset = datasets.MNIST('./data', train=True, download=True, transform=transform)
  20. train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
  21. # 划分训练集和验证集
  22. train_data, val_data = train_test_split(train_dataset, test_size=0.1, random_state=42)
  23. # 实例化自动编码器模型
  24. encoding_dim = 32
  25. autoencoder = Autoencoder(encoding_dim)
  26. # 定义损失函数和优化器
  27. criterion = nn.MSELoss()
  28. optimizer = optim.Adam(autoencoder.parameters(), lr=0.001)
  29. # 训练自动编码器
  30. num_epochs = 10
  31. for epoch in range(num_epochs):
  32. for data in train_loader:
  33. img, _ = data
  34. img = img.view(img.size(0), -1)
  35. optimizer.zero_grad()
  36. outputs = autoencoder(img)
  37. loss = criterion(outputs, img)
  38. loss.backward()
  39. optimizer.step()
  40. print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')
  41. # 保存自动编码器模型
  42. torch.save(autoencoder.state_dict(), 'autoencoder_model.pth')
  43. print("Model saved")

一个自定义的自动编码器类 Autoencoder,其中包含一个编码器和一个解码器。编码器将输入数据映射到较低维度的表示,解码器将这个低维度表示映射回原始数据空间。然后,加载 MNIST 数据集,实例化自动编码器模型,定义损失函数和优化器,并使用训练集进行模型训练。训练完成后,将训练好的自动编码器模型保存为 'autoencoder_model.pth' 文件。

未完待续

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/514373
推荐阅读
相关标签
  

闽ICP备14008679号