云计算百科
云计算领域专业知识百科平台

【机器学习入门】PyTorch 神经网络、卷积神经网络 CNN 实现矿物分类

文章目录

  • 一、前言
  • 二、数据集说明
  • 三、环境依赖
  • 四、数据加载
  • 五、全连接神经网络(BP 神经网络)
    • 5.1 网络结构
    • 5.2 核心原理
    • 5.3 代码实现
    • 5.4 数据转张量
    • 5.5 模型评估函数
    • 5.6 训练过程
  • 六、卷积神经网络(1D CNN)
    • 6.1 为什么表格数据可以用 CNN?
    • 6.2 网络结构
    • 6.3 核心原理
    • 6.4 代码实现
    • 6.5 训练过程
  • 七、结果输出
  • 八、两种模型对比分析
  • 九、改进建议
    • 9.1 数据标准化
    • 9.2 改用 ReLU 激活函数
    • 9.3 设置随机种子
    • 9.4 早停(Early Stopping)
    • 9.5 学习率调度
    • 9.6 更丰富的评估指标
  • 十、完整代码汇总
  • 十一、总结

一、前言

在地质勘探与矿物识别领域,传统的人工鉴定方法依赖专家经验,效率低且主观性强。随着机器学习技术的发展,利用矿物的物理化学特征(如密度、硬度、折射率、化学成分含量等)进行自动分类已成为一种高效可行的方案。

本文将基于一份矿物特征数据集,使用 PyTorch 框架分别搭建:

  • 全连接神经网络(BP 神经网络)
  • 一维卷积神经网络(1D CNN)

两种模型对矿物类型进行四分类任务,并对比它们的分类效果。文章包含完整的原理讲解、代码实现与结果分析,适合机器学习入门读者参考。


二、数据集说明

本实验使用经过平均值填充处理后的矿物数据集,分为训练集和测试集两个文件:

文件说明
2_训练数据集_平均值填充.xlsx 训练集
2_测试数据集_平均值填充.xlsx 测试集

数据结构如下:

  • 第 1 列:标签列,矿物类型(共 4 类,记为 0、1、2、3)
  • 第 2 列及以后:特征列,共 13 维矿物特征

平均值填充是处理缺失值的常用方法:用该特征列的均值替换缺失值,简单且不会改变数据整体分布。


三、环境依赖

pip install pandas scikit-learn torch openpyxl

库用途
pandas 读取 Excel 数据、数据处理
scikit-learn 模型评估指标(metrics)
torch PyTorch 深度学习框架
openpyxl pandas 读取 .xlsx 文件的引擎

四、数据加载

import pandas as pd
from sklearn import metrics

train_data = pd.read_excel(r'.//temp_data//2_训练数据集_平均值填充.xlsx') # 读取平均值填充后的训练数据集Excel文件
train_data_x = train_data.iloc[:,1:] # 提取训练数据的特征列(从第2列到最后一列)
train_data_y = train_data.iloc[:,0] # 提取训练数据的标签列(第1列,即矿物类型)

test_data = pd.read_excel(r'.//temp_data//2_测试数据集_平均值填充.xlsx') # 读取平均值填充后的测试数据集Excel文件
test_data_x = test_data.iloc[:,1:] # 提取测试数据的特征列(从第2列到最后一列)
test_data_y = test_data.iloc[:,0] # 提取测试数据的标签列(第1列,即矿物类型)

result_data = {} # 初始化一个空字典,用于存储平均值填充方式下各模型的评估结果

关键点说明:

  • iloc[:, 1:]:按位置索引,取所有行、第 2 列到最后一列作为特征。
  • iloc[:, 0]:取第 1 列作为标签。
  • 数据读取后特征为 DataFrame,后续需通过 .values 转为 numpy 数组再转成 PyTorch 张量。

五、全连接神经网络(BP 神经网络)

5.1 网络结构

全连接神经网络(Fully Connected Neural Network),也叫多层感知机(MLP)或 BP 神经网络,每一层的每个神经元都与上一层的所有神经元相连。

本实验搭建的网络结构如下:

层输入维度输出维度激活函数
输入层(fc1) 13 32 Sigmoid
隐藏层(fc2) 32 64 Sigmoid
输出层(fc3) 64 4
  • 输入维度 13 对应 13 个矿物特征。
  • 输出维度 4 对应 4 种矿物类别。
  • 输出层不使用激活函数,因为 CrossEntropyLoss 内部已包含 Softmax。

5.2 核心原理

Sigmoid 激活函数:

σ

(

x

)

=

1

1

+

e

x

\\sigma(x) = \\frac{1}{1 + e^{-x}}

σ(x)=1+ex1

将输入压缩到 (0, 1) 区间,历史上常用于二分类和隐藏层。但 Sigmoid 存在梯度消失问题:当输入绝对值较大时,导数趋近于 0,深层网络训练困难。因此现代网络更常用 ReLU。

交叉熵损失(CrossEntropyLoss):

L

o

s

s

=

i

=

1

C

y

i

log

(

y

^

i

)

Loss = -\\sum_{i=1}^{C} y_i \\log(\\hat{y}_i)

Loss=i=1Cyilog(y^i)

其中

y

i

y_i

yi 是真实标签的 one-hot 编码,

y

^

i

\\hat{y}_i

y^i 是模型预测的概率。交叉熵损失是多分类任务的标准选择。

Adam 优化器:结合了动量(Momentum)和自适应学习率(RMSprop)的优点,是目前最常用的优化器之一。

5.3 代码实现

import torch
import torch.nn as nn

class Net(nn.Module): # 定义神经网络类,继承自nn.Module
def __init__(self): # 构造函数,用于初始化网络结构
super(Net, self).__init__() # 调用父类nn.Module的构造函数
self.fc1 = nn.Linear(13, 32) # 定义第一个全连接层,输入维度13,输出维度32
self.fc2 = nn.Linear(32, 64) # 定义第二个全连接层,输入维度32,输出维度64
self.fc3 = nn.Linear(64, 4) # 定义第三个全连接层(输出层),输入维度64,输出维度4(对应4个类别)

def forward(self, x): # 定义前向传播函数
x = torch.sigmoid(self.fc1.forward(x)) # 第一层全连接后使用sigmoid激活函数
x = torch.sigmoid(self.fc2(x)) # 第二层全连接后使用sigmoid激活函数
x = self.fc3(x) # 输出层全连接,不使用激活函数(CrossEntropyLoss内部会做softmax)
return x # 返回最终输出

5.4 数据转张量

X_train = torch.tensor(train_data_x.values, dtype=torch.float32)
Y_train = torch.tensor(train_data_y.values)
X_test = torch.tensor(test_data_x.values, dtype=torch.float32)
Y_test = torch.tensor(test_data_y.values)

  • 特征用 float32,与网络参数默认类型一致。
  • 标签直接转张量,CrossEntropyLoss 要求标签为 LongTensor(整型),pandas 读取的数值默认会转为对应整型。

5.5 模型评估函数

def evaluate_model(model, X_data, Y_data, train_or_test):
"""评估模型在给定数据集上的准确率"""
size = len(X_data)
with torch.no_grad(): # 关闭梯度,节省内存
predictions = model(X_data) # 前向传播得到 logits
correct = (predictions.argmax(1) == Y_data).type(torch.float).sum().item()
correct /= size
print(f"{train_or_test}: \\t Accuracy: {(100 * correct):.2f}%")
return correct

  • torch.no_grad():评估阶段不需要计算梯度,可大幅减少显存占用。
  • argmax(1):在维度 1(类别维度)上取最大值索引,即预测类别。
  • .item():将单元素 PyTorch 张量转为 Python 标量。

5.6 训练过程

model = Net()
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.0001)

epochs = 15000
accs = []

for epoch in range(epochs):
outputs = model(X_train) # 前向传播
loss = criterion(outputs, Y_train) # 计算损失
optimizer.zero_grad() # 清空梯度
loss.backward() # 反向传播
optimizer.step() # 更新参数

if (epoch + 1) % 100 == 0:
print(f'Epoch [{epoch + 1}/{epochs}], Loss: {loss.item():.4f}')
train_acc = evaluate_model(model, X_train, Y_train, 'train')
test_acc = evaluate_model(model, X_test, Y_test, 'test')
accs.append(test_acc * 100)

net_result = {}
net_result['acc'] = max(accs) # 记录最佳测试准确率
result_data['net'] = net_result

训练五步曲(每次迭代必做):

  • 前向传播:outputs = model(X_train)
  • 计算损失:loss = criterion(outputs, Y_train)
  • 清空梯度:optimizer.zero_grad()(必须在反向传播前)
  • 反向传播:loss.backward()
  • 更新参数:optimizer.step()
  • 共训练 15000 轮,每 100 轮评估一次,最终取所有评估点中的最大测试准确率作为模型结果。


    六、卷积神经网络(1D CNN)

    6.1 为什么表格数据可以用 CNN?

    卷积神经网络通常用于图像(2D CNN)和文本/序列(1D CNN)。对于表格数据,我们可以将 13 维特征看作一个长度为 13 的一维序列,使用 1D 卷积提取特征之间的局部关联模式。

    例如,矿物的"密度"和"硬度"可能存在局部相关性,卷积核可以捕捉这种相邻特征间的组合规律。

    6.2 网络结构

    层输入通道输出通道卷积核填充说明
    Conv1 1 16 3 1 第一层卷积
    Conv2 16 32 3 1 第二层卷积
    Conv3 32 64 3 1 第三层卷积
    GAP 全局平均池化
    FC 64 4 全连接输出层
    • kernel_size=3, padding=1:卷积后序列长度不变(

      L

      o

      u

      t

      =

      L

      i

      n

      +

      2

      p

      k

      +

      1

      =

      L

      i

      n

      L_{out} = L_{in} + 2p – k + 1 = L_{in}

      Lout=Lin+2pk+1=Lin)。

    • 全局平均池化(Global Average Pooling):在特征维度上求均值,将 (batch, 64, 13) 压缩为 (batch, 64),参数量远小于展平后接全连接。

    6.3 核心原理

    一维卷积运算:

    (

    f

    g

    )

    [

    n

    ]

    =

    k

    f

    [

    k

    ]

    g

    [

    n

    k

    ]

    (f * g)[n] = \\sum_{k} f[k] \\cdot g[n – k]

    (fg)[n]=kf[k]g[nk]

    卷积核在特征序列上滑动,提取局部模式。多层卷积堆叠可以提取从低级到高级的特征组合。

    全局平均池化(GAP):对每个通道的整个特征图取平均值,相比全连接展平:

    • 大幅减少参数量,降低过拟合风险
    • 对输入长度变化更鲁棒
    • 增强模型对空间位置的不变性

    6.4 代码实现

    import torch
    import torch.nn as nn
    import torch.optim as optim

    class ConvNet(nn.Module):
    def __init__(self, num_features, hidden_size, num_classes):
    super(ConvNet, self).__init__()
    # 三层1D卷积,通道数逐步增加:1 -> 16 -> 32 -> 64
    self.conv1 = nn.Conv1d(in_channels=1, out_channels=16, kernel_size=3, padding=1)
    self.conv2 = nn.Conv1d(in_channels=16, out_channels=32, kernel_size=3, padding=1)
    self.conv3 = nn.Conv1d(in_channels=32, out_channels=64, kernel_size=3, padding=1)
    self.activation = nn.Sigmoid() # 注意:此处实际为Sigmoid激活
    self.fc = nn.Linear(64, num_classes)

    def forward(self, x):
    # x 形状: (batch_size, 13) -> 增加通道维度 -> (batch_size, 1, 13)
    x = x.unsqueeze(1)
    x = self.conv1(x) # (batch, 16, 13)
    x = self.activation (x)
    x = self.conv2(x) # (batch, 32, 13)
    x = self.activation (x)
    x = self.conv3(x) # (batch, 64, 13)
    x = self.activation (x)
    x = x.mean(dim=2) # 全局平均池化 -> (batch, 64)
    x = self.fc(x) # (batch, 4)
    return x

    unsqueeze(1) 是关键操作:Conv1d 要求输入形状为 (batch, in_channels, length),而原始数据为 (batch, 13),需要在第 1 维插入通道维度。

    6.5 训练过程

    X_train = torch.tensor(train_data_x.values, dtype=torch.float32) # 将训练集特征转换为PyTorch张量,数据类型为float32,形状为(样本数, 13)
    Y_train = torch.tensor(train_data_y.values) # 将训练集标签转换为PyTorch张量
    X_test = torch.tensor(test_data_x.values, dtype=torch.float32) # 将测试集特征转换为PyTorch张量,数据类型为float32,形状为(样本数, 13)
    Y_test = torch.tensor(test_data_y.values) # 将测试集标签转换为PyTorch张量

    hidden_size = 10 # 定义隐藏层大小(此参数在ConvNet中未直接使用,仅作为接口参数)
    num_classes = 4 # 定义类别数量(矿物类型A、B、C、D共4类)
    model = ConvNet(13, hidden_size, num_classes) # 实例化卷积神经网络模型,输入特征维度为13,隐藏层大小为10,输出类别为4
    criterion = nn.CrossEntropyLoss() # 定义损失函数为交叉熵损失(适用于多分类问题)
    optimizer = optim.Adam(model.parameters(), lr=0.001) # 定义优化器为Adam,学习率为0.001

    num_epochs = 15000 # 设置训练迭代次数(轮数)
    accs = [] # 初始化空列表,用于存储每个评估点的测试准确率
    for epoch in range(num_epochs): # 循环训练num_epochs次
    outputs = model(X_train) # 前向传播:将训练数据输入模型,得到预测输出(每个类别的logits)
    loss = criterion(outputs, Y_train) # 计算损失值:使用交叉熵损失函数比较预测值和真实标签
    optimizer.zero_grad() # 清空梯度缓存(防止梯度累加)
    loss.backward() # 反向传播:计算损失关于模型参数的梯度
    optimizer.step() # 更新模型参数:使用Adam优化器根据梯度更新权重

    if (epoch + 1) % 100 == 0: # 每100轮打印一次训练进度和评估结果
    print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {loss.item():.4f}') # 打印当前轮次和损失值

    with torch.no_grad(): # 禁用梯度计算(评估时不需要反向传播,节省内存和计算资源)
    # 评估训练集准确率
    predictions = model(X_train) # 将训练数据输入模型,得到预测输出
    predicted_classes = predictions.argmax(dim=1) # 获取每行最大值的索引(预测类别),形状为(batch_size,)
    accuracy = (predicted_classes == Y_train).float().mean() # 计算训练集准确率:比较预测类别与真实标签,求均值
    print(f'Train Accuracy: {accuracy.item() * 100:.2f}%') # 打印训练集准确率(百分比形式,保留2位小数)

    # 评估测试集准确率
    predictions = model(X_test) # 将测试数据输入模型,得到预测输出
    predicted_classes = predictions.argmax(dim=1) # 获取每行最大值的索引(预测类别)
    accuracy = (predicted_classes == Y_test).float().mean() # 计算测试集准确率:比较预测类别与真实标签,求均值
    print(f'Test Accuracy: {accuracy.item() * 100:.2f}%') # 打印测试集准确率(百分比形式,保留2位小数)
    accs.append(accuracy * 100) # 将测试准确率(转换为百分比)存入列表

    cnn_result = {} # 初始化一个空字典,用于存储CNN模型的评估结果
    cnn_result['acc'] = max(accs).item() # 从所有记录的测试准确率中取最大值(转换为Python标量),作为模型的最佳测试准确率
    result_data['cnn'] = cnn_result # 将CNN的结果存入总结果字典

    print(result_data) # 打印所有模型的评估结果

    CNN 的训练流程与全连接网络完全一致,区别仅在于网络结构定义和学习率设置(CNN 用 lr=0.001,全连接用 lr=0.0001)。


    七、结果输出

    训练完成后,result_data 字典中存储了两个模型的最佳测试准确率:

    print(result_data)
    # 输出示例(实际值取决于数据和随机初始化):
    # {'net': {'acc': 92.5}, 'cnn': {'acc': 95.0}}

    模型最佳测试准确率
    全连接神经网络(Net) result_data['net']['acc']
    卷积神经网络(CNN) result_data['cnn']['acc']

    由于代码未设置随机种子(torch.manual_seed),每次运行结果会有波动。如需可复现,建议在训练前设置:

    torch.manual_seed(42)


    八、两种模型对比分析

    对比维度全连接神经网络1D 卷积神经网络
    参数量 较多(每层全连接) 较少(卷积权重共享 + GAP)
    特征提取方式 全局组合,每个特征独立权重 局部模式提取,捕捉相邻特征关联
    过拟合风险 较高 较低(权重共享、GAP)
    对特征顺序敏感度 不敏感(换列不影响) 敏感(卷积依赖相邻关系)
    训练稳定性 Sigmoid 易梯度消失 同样受 Sigmoid 影响
    适用场景 通用表格分类 有序特征/序列型数据

    对于矿物这种特征间有物理化学关联的数据,1D CNN 往往能学到更有意义的局部组合模式,在测试集上表现可能更优。


    九、改进建议

    原代码可从以下几个方面优化,以获得更好的分类效果:

    9.1 数据标准化

    from sklearn.preprocessing import StandardScaler
    scaler = StandardScaler()
    train_data_x = scaler.fit_transform(train_data_x)
    test_data_x = scaler.transform(test_data_x) # 注意:测试集只用transform

    不同矿物特征量纲差异大,标准化后可加速收敛、提升精度。

    9.2 改用 ReLU 激活函数

    Sigmoid 在深层网络中容易梯度消失,建议替换为 ReLU:

    x = torch.relu(self.fc1(x))
    # 或在CNN中:self.activation = nn.ReLU()

    ReLU 计算简单、缓解梯度消失,是目前隐藏层的主流选择。

    9.3 设置随机种子

    torch.manual_seed(42)
    torch.cuda.manual_seed_all(42)

    保证实验可复现。

    9.4 早停(Early Stopping)

    记录验证集损失,当连续多轮不再下降时停止训练,防止过拟合:

    best_loss = float('inf')
    patience = 500
    counter = 0
    # 训练中每轮检查验证集损失,若不下降则 counter++,达到 patience 则 break

    9.5 学习率调度

    scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=3000, gamma=0.5)
    # 每3000轮学习率减半

    训练后期降低学习率有助于精细收敛。

    9.6 更丰富的评估指标

    除准确率外,建议补充:

    from sklearn.metrics import classification_report, confusion_matrix
    print(classification_report(Y_test.numpy(), predicted_classes.numpy()))
    print(confusion_matrix(Y_test.numpy(), predicted_classes.numpy()))

    可查看每一类的精确率、召回率、F1 值和混淆矩阵,更全面评估模型。


    十、完整代码汇总

    # -*- coding: utf-8 -*-
    """
    PyTorch 实现矿物分类:全连接神经网络 + 1D卷积神经网络
    数据集:平均值填充后的矿物特征数据集(13维特征,4分类)
    """

    import pandas as pd
    from sklearn import metrics
    import torch
    import torch.nn as nn
    import torch.optim as optim

    """==================== 1. 数据加载 ===================="""
    train_data = pd.read_excel(r'.//temp_data//2_训练数据集_平均值填充.xlsx')
    train_data_x = train_data.iloc[:, 1:]
    train_data_y = train_data.iloc[:, 0]

    test_data = pd.read_excel(r'.//temp_data//2_测试数据集_平均值填充.xlsx')
    test_data_x = test_data.iloc[:, 1:]
    test_data_y = test_data.iloc[:, 0]

    result_data = {}

    # 转张量
    X_train = torch.tensor(train_data_x.values, dtype=torch.float32)
    Y_train = torch.tensor(train_data_y.values)
    X_test = torch.tensor(test_data_x.values, dtype=torch.float32)
    Y_test = torch.tensor(test_data_y.values)

    """==================== 2. 全连接神经网络 ===================="""
    class Net(nn.Module):
    def __init__(self):
    super(Net, self).__init__()
    self.fc1 = nn.Linear(13, 32)
    self.fc2 = nn.Linear(32, 64)
    self.fc3 = nn.Linear(64, 4)

    def forward(self, x):
    x = torch.sigmoid(self.fc1(x))
    x = torch.sigmoid(self.fc2(x))
    x = self.fc3(x)
    return x

    def evaluate_model(model, X_data, Y_data, train_or_test):
    size = len(X_data)
    with torch.no_grad():
    predictions = model(X_data)
    correct = (predictions.argmax(1) == Y_data).type(torch.float).sum().item()
    correct /= size
    print(f"{train_or_test}: \\t Accuracy: {(100 * correct):.2f}%")
    return correct

    model = Net()
    criterion = nn.CrossEntropyLoss()
    optimizer = torch.optim.Adam(model.parameters(), lr=0.0001)

    epochs = 15000
    accs = []
    for epoch in range(epochs):
    outputs = model(X_train)
    loss = criterion(outputs, Y_train)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    if (epoch + 1) % 100 == 0:
    print(f'Epoch [{epoch + 1}/{epochs}], Loss: {loss.item():.4f}')
    evaluate_model(model, X_train, Y_train, 'train')
    test_acc = evaluate_model(model, X_test, Y_test, 'test')
    accs.append(test_acc * 100)

    result_data['net'] = {'acc': max(accs)}

    """==================== 3. 1D卷积神经网络 ===================="""
    class ConvNet(nn.Module):
    def __init__(self, num_features, hidden_size, num_classes):
    super(ConvNet, self).__init__()
    self.conv1 = nn.Conv1d(1, 16, kernel_size=3, padding=1)
    self.conv2 = nn.Conv1d(16, 32, kernel_size=3, padding=1)
    self.conv3 = nn.Conv1d(32, 64, kernel_size=3, padding=1)
    self.activation = nn.Sigmoid()
    self.fc = nn.Linear(64, num_classes)

    def forward(self, x):
    x = x.unsqueeze(1)
    x = self.activation(self.conv1(x))
    x = self.activation(self.conv2(x))
    x = self.activation(self.conv3(x))
    x = x.mean(dim=2)
    x = self.fc(x)
    return x

    model = ConvNet(13, 10, 4)
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.Adam(model.parameters(), lr=0.001)

    num_epochs = 15000
    accs = []
    for epoch in range(num_epochs):
    outputs = model(X_train)
    loss = criterion(outputs, Y_train)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    if (epoch + 1) % 100 == 0:
    print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {loss.item():.4f}')
    with torch.no_grad():
    train_pred = model(X_train).argmax(dim=1)
    train_acc = (train_pred == Y_train).float().mean().item() * 100
    print(f'Train Accuracy: {train_acc:.2f}%')

    test_pred = model(X_test).argmax(dim=1)
    test_acc = (test_pred == Y_test).float().mean().item() * 100
    print(f'Test Accuracy: {test_acc:.2f}%')
    accs.append(test_acc)

    result_data['cnn'] = {'acc': max(accs)}

    """==================== 4. 结果输出 ===================="""
    print("=" * 50)
    print("各模型最佳测试准确率:")
    print(f"全连接神经网络: {result_data['net']['acc']:.2f}%")
    print(f"卷积神经网络: {result_data['cnn']['acc']:.2f}%")
    print("=" * 50)


    十一、总结

    本文基于矿物特征数据集,使用 PyTorch 实现了两种深度学习分类模型:

  • 全连接神经网络:结构简单,通过三层全连接层学习特征的全局组合。
  • 1D 卷积神经网络:将 13 维特征视为序列,通过三层卷积提取局部模式,配合全局平均池化减少参数量。
  • 两者均使用交叉熵损失和 Adam 优化器,训练 15000 轮后取最佳测试准确率进行对比。

    通过本文的学习,你可以掌握:

    • PyTorch 自定义网络的标准写法(继承 nn.Module、实现 forward)
    • 训练五步曲:前向传播 → 计算损失 → 清空梯度 → 反向传播 → 更新参数
    • torch.no_grad() 在评估阶段的使用
    • 1D CNN 处理表格数据的方法与 unsqueeze 维度变换
    • 全局平均池化的原理与优势

    建议在实际项目中结合数据标准化、ReLU 激活、早停等技巧进一步提升模型性能。


    如果本文对你有帮助,欢迎点赞、收藏、关注!有任何问题欢迎在评论区交流。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【机器学习入门】PyTorch 神经网络、卷积神经网络 CNN 实现矿物分类
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!