前言

最近在做昇腾平台MindSpore深度学习实验,Auto MPG汽车油耗预测是经典的回归任务,和MNIST分类任务形成强烈对比。很多初学者容易混淆分类和回归网络结构、损失函数选择。本文完整记录从数据集加载、数据预处理、归一化、One-Hot编码、搭建全连接网络、训练循环、绘制误差曲线全过程,同时记录实验踩坑点(pandas append报错、维度不匹配等)。

任务目标:根据汽车的气缸数、排量、马力、重量、产地等特征,预测汽车每加仑里程数MPG(油耗,连续数值)

一、实验环境

  • 框架:MindSpore 2.3
  • 硬件:昇腾
  • Python:3.x
  • 依赖:pandas、numpy、matplotlib
# !pip install pandas==1.3.0
import os
import csv
import time
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt

import mindspore as ms
import mindspore.dataset as ds
import mindspore.context as context
from mindspore import nn, Tensor
from mindspore.train import Model
from mindspore.nn.metrics import MAE, MSE
from mindspore.train.serialization import load_checkpoint, load_param_into_net
from mindspore.train.callback import ModelCheckpoint, CheckpointConfig, LossMonitor, TimeMonitor

# 设置昇腾图模式
context.set_context(mode=context.GRAPH_MODE, device_target='Ascend')

二、数据集介绍:Auto MPG

Auto MPG数据集收集70~80年代汽车燃油效率数据。 字段:MPG(目标)、Cylinders气缸数、Displacement排量、Horsepower马力、Weight车重、Acceleration加速、Model Year车型年份、Origin产地

  • Origin:1美国,2欧洲,3日本
  • 原始样本398条,Horsepower字段存在缺失值?,删除空值剩余392条

2.1 读取数据集

# 简单查看原始数据
with open('/data/DL_ascend/B03/auto-mpg.data') as csv_file:
    data = list(csv.reader(csv_file, delimiter=','))
print(data[20:40])

# pandas读取
column_names = ['MPG','Cylinders','Displacement','Horsepower','Weight',
                'Acceleration', 'Model Year', 'Origin']
raw_data = pd.read_csv('/data/DL_ascend/B03/auto-mpg.data', names=column_names,
                      na_values = "?", comment='\t',
                      sep=" ", skipinitialspace=True)
data = raw_data.copy()
print("原始数据集shape:", data.shape)

2.2 数据预处理

  1. 缺失值处理:样本较少,直接dropna()删除空行
  2. Origin是类别特征,做One-Hot编码,转为USA、Europe、Japan三个0/1特征
  3. 特征归一化:消除量纲影响,使用均值方差归一 $x_{norm}=\frac{x-mean}{std}$
# 删除空值
data = data.dropna()
print("删除空值后shape:", data.shape)

# 分离标签和特征
origin = data.pop('Origin')
data_labels = data.pop('MPG')

# 查看统计信息
train_stats = data.describe()
train_stats = train_stats.transpose()

# 归一化函数
def norm(x):
    return (x - train_stats['mean']) / train_stats['std']
normed_data = norm(data)

# 把目标MPG放回数据集
normed_data['MPG'] = data_labels

# Origin独热编码
normed_data['USA'] = (origin == 1)*1.0
normed_data['Europe'] = (origin == 2)*1.0
normed_data['Japan'] = (origin == 3)*1.0

2.3 划分训练集、测试集(8:2)

# 80%训练集,20%测试集
train_dataset = normed_data.sample(frac=0.8,random_state=0)
test_dataset = normed_data.drop(train_dataset.index)

# 分离特征X,标签Y
train_labels = train_dataset.pop('MPG')
test_labels = test_dataset.pop('MPG')

X_train, Y_train = np.array(train_dataset), np.array(train_labels)
X_test, Y_test = np.array(test_dataset), np.array(test_labels)

print('训练数据x尺寸:',X_train.shape)
print('训练数据y尺寸:',Y_train.shape)
print('测试数据x尺寸:',X_test.shape)
print('测试数据y尺寸:',Y_test.shape)

# 转为MindSpore Tensor
ds_xtrain= Tensor(X_train, ms.float32)
ds_ytrain= Tensor(Y_train, ms.float32)

ds_xtest=Tensor(X_test, ms.float32)
ds_ytest=Tensor(Y_test, ms.float32)

注意:标签这里是连续浮点数,类型要用ms.float32,不要写成int32!

三、搭建全连接回归网络

网络结构:

  • 3层全连接层
  • 隐藏层使用ReLU激活
  • 输出层无激活函数(回归任务),输出1个连续值
class Regression_car(nn.Cell):
    def __init__(self):
        super(Regression_car, self).__init__()
        self.flatten = nn.Flatten()
        self.relu = nn.ReLU()
        self.fc1 = nn.Dense(9,64, activation='relu')
        self.fc2 = nn.Dense(64,64, activation='relu')
        self.fc3 = nn.Dense(64,1)                      
    def construct(self, x):
        x = self.flatten(x)
        x = self.fc1(x)
        x = self.fc2(x)
        x = self.fc3(x)
        return x

# 网络、损失函数、优化器定义
network = Regression_car()
net_loss = nn.MSELoss() # 回归任务使用均方误差MSE
net_opt = nn.RMSProp(network.trainable_params(), 0.001)

# 封装训练单元和评估单元
with_loss=nn.WithLossCell(network, net_loss)
train_step = nn.TrainOneStepCell(with_loss, net_opt).set_train()
evalcell=nn.WithEvalCell(network,net_loss)

# 评估指标MAE、MSE
mae = nn.MAE()
mse = nn.MSE()
val_mae = nn.MAE()
val_mse = nn.MSE()

 重点区分: 分类任务:输出维度=类别数,损失用交叉熵; 回归任务:输出维度=1,损失MSE/MAE。

四、训练循环

旧版本写法 result.append() 在pandas2.0+会直接报AttributeError替换为pd.concat

# 创建空DataFrame保存训练记录
result =pd.DataFrame(columns=['_epoch','_loss','_mae','_mse','val_mae','val_mse'])

print("============== Starting Training ==============")
for epoch in range(300):
    loss = train_step(ds_xtrain,ds_ytrain)
    _, outputs, label = evalcell(ds_xtrain,ds_ytrain)
    _, val_outputs, val_label = evalcell(ds_xtest,ds_ytest)
    
    # 清空指标并更新
    mae.clear()
    mae.update(outputs, label)
    mse.clear()
    mse.update(outputs, label)
    val_mae.clear()
    val_mae.update(val_outputs, val_label)
    val_mse.clear()
    val_mse.update(val_outputs, val_label)

    Mae = mae.eval()
    Mse = mse.eval()
    Val_Mae = val_mae.eval()
    Val_Mse = val_mse.eval()

    nd_loss = loss.asnumpy()
    fl_loss = float(nd_loss)/24.0

    # pandas新版本不能用append!用pd.concat
    new_row = pd.DataFrame({
        '_epoch':[epoch],
        '_loss':[fl_loss],
        '_mae':[Mae],
        '_mse':[Mse],
        'val_mae':[Val_Mae],
        'val_mse':[Val_Mse]
    })
    result = pd.concat([result, new_row], ignore_index=True)

    if epoch%10==0:
        print(f'epoch:{epoch},loss:{fl_loss:.4f},mae:{Mae:.4f},mse:{Mse:.4f},val_mae:{Val_Mae:.4f},val_mse:{Val_Mse:.4f}')
        print("*" * 110)
print(result)

五、绘制训练误差曲线

观察训练集和验证集MAE、MSE,判断是否过拟合

def plot_history(result):
    plt.figure()
    plt.xlabel('Epoch')
    plt.ylabel('Mean Abs Error [MPG]')
    plt.plot(result['_epoch'], result['_mae'], label='Train Error')
    plt.plot(result['_epoch'], result['val_mae'], label = 'Val Error')
    plt.ylim([0,20])
    plt.legend()

    plt.figure()
    plt.xlabel('Epoch')
    plt.ylabel('Mean Square Error [$MPG^2$]')
    plt.plot(result['_epoch'], result['_mse'], label='Train Error')
    plt.plot(result['_epoch'], result['val_mse'], label = 'Val Error')
    plt.ylim([0,200])
    plt.legend()
    plt.show()

plot_history(result)

结果分析

  1. 训练误差持续下降,验证误差后期明显高于训练误差 → 过拟合
  2. MAE代表预测值和真实油耗平均相差多少MPG;MSE对大误差惩罚更大

六、实验核心知识点总结

  1. 回归任务:预测连续实数;分类任务:预测离散类别。
  2. 归一化:消除特征量纲差异,加速梯度收敛。
  3. One-Hot编码:把产地Origin类别转为0/1向量,防止模型误把类别编号当成数值。
  4. nn.WithLossCell:网络+损失封装,用于训练;nn.WithEvalCell:评估阶段,返回loss、预测输出、标签。
  5. 过拟合现象:训练误差很低,验证误差显著偏高。解决方法:正则化、Dropout、提前停止。

七、踩坑记录

  1.  pandas新版本DataFrame.append()被移除,报AttributeError,改用pd.concat
  2.  标签类型写错,回归标签是float,不能用int32
  3.  混淆分类与回归网络,输出层错误加Softmax
  4.  MindSpore旧版c_transforms,新版本导入报错,图像算子表格任务不需要
  5.  忘记归一化,loss很难下降,模型收敛极慢

结尾

MindSpore+昇腾做回归任务和分类任务思路差异很大,Auto MPG是非常适合入门回归的小数据集。本文完整代码可直接在昇腾Notebook运行,有问题欢迎评论区交流。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐