从零手写感知机到MindSpore实战:我的鸢尾花分类作业踩坑全记录

张开发
2026/4/18 11:09:33 15 分钟阅读

分享文章

从零手写感知机到MindSpore实战:我的鸢尾花分类作业踩坑全记录
从零手写感知机到MindSpore实战我的鸢尾花分类作业踩坑全记录第一次接触机器学习作业时看着鸢尾花分类这个看似简单的题目我完全没料到后面会经历这么多波折。从手写感知机时对梯度下降的困惑到使用MindSpore框架时踩的各种坑这段学习历程让我深刻体会到理论与实践之间的鸿沟。本文将完整记录我的学习路径、遇到的问题及解决方案希望能给同样入门机器学习的同学一些参考。1. 手写感知机的痛苦与顿悟1.1 随机梯度下降的初体验当我第一次尝试手写感知机实现鸢尾花二分类时最让我困惑的是梯度下降算法的各种变体。随机梯度下降(SGD)看似简单——每次随机选取一个样本计算梯度并更新权重但实际编码时却遇到了几个关键问题# 随机梯度下降的核心代码片段 def train(x, y, w, lr0.001, epoch10): for i in range(epoch): for j in range(len(x)): if loss(x[j],y[j],w) 0: w w - lr*gradient(x[j],y[j],w)第一个坑学习率的选择。最初我随意设置了lr0.1结果模型根本无法收敛。通过反复试验才发现对于这个数据集lr0.001才是比较合适的选择。第二个坑特征处理。原始数据有4个特征但线性模型需要包含偏置项。我花了两个小时才想明白需要在特征向量末尾添加一个常数1# 特征处理的关键步骤 for i in data[0:num]: d i[0:D] d.append(1) # 添加偏置项对应的特征 data_x.append(d)1.2 全批量梯度下降的对比实验当我改用全批量梯度下降时发现了几个有趣的现象算法类型收敛速度内存消耗最终准确率随机梯度下降慢低100%全批量梯度下降快高100%全批量梯度下降虽然每个epoch收敛更快但计算每个梯度需要遍历全部训练样本内存消耗更大。而且我注意到当学习率设置不当时全批量梯度下降更容易陷入局部最优。1.3 动态调整学习率的技巧在拓展实验中我尝试了动态调整学习率的方法效果出奇地好# 动态调整学习率的实现 def train_adaptive_global(x, y, w, epoch): for i in range(epoch): lr 0.01 - i / 20 * 0.001 # 线性衰减的学习率 w w - lr*gradient_global(x,y,w)这种方法让模型在初期以大步伐快速接近最优解后期用小步伐精细调整大大减少了达到100%准确率所需的epoch数量。2. 从二分类到三分类的挑战2.1 Softmax回归的实现难点当作业要求扩展到三分类时我不得不实现Softmax回归。最大的挑战来自以下几个方面One-hot编码的处理需要将类别标签转换为向量形式交叉熵损失的计算与感知机的损失函数完全不同多维权重的更新现在权重矩阵是3×5而不是向量# Softmax模型函数 def model(x,w): numerator np.exp(np.dot(w,x)) denominator np.dot(numerator,[1,1,1]) return numerator/denominator2.2 训练中的玄学现象在训练Softmax模型时我遇到了一个奇怪的现象测试集准确率有时能达到100%但训练集准确率最高只有98.33%。经过多次实验和分析我发现某些样本在特征空间中非常接近决策边界随机划分训练测试集时这些困难样本可能被分到测试集模型在训练集上无法完美拟合所有样本提示当遇到这种情况时可以尝试增加模型复杂度或调整特征工程而不是简单地增加训练epoch。3. MindSpore框架的实战踩坑3.1 环境配置的坑当我转向使用MindSpore框架时本以为会轻松很多结果第一步环境配置就卡住了本地安装MindSpore时版本不兼容华为云Notebook上的预装环境有时也会出现奇怪的问题不同的计算设备(CPU/GPU/NPU)需要不同的安装包最终我选择直接在华为云上使用预配置好的Notebook环境省去了本地安装的麻烦。3.2 数据处理的差异MindSpore的数据处理流程与纯Python有很大不同# MindSpore数据加载流程 XY_train list(zip(X_train, Y_train)) ds_train dataset.GeneratorDataset(XY_train, [x, y]) ds_train ds_train.shuffle(buffer_size125).batch(32, drop_remainderTrue)这里需要注意必须使用MindSpore提供的Dataset类shuffle操作需要指定buffer_sizebatch操作可以设置drop_remainder处理不完整的批次3.3 训练监控的解决方案最让我头疼的是如何监控训练过程中的loss和accuracy变化。官方文档主要推荐使用mindinsight但配置起来相当复杂。最终我采用了自定义Callback的方法class StepLossAccInfo(Callback): def __init__(self, model, eval_dataset, steps_loss, steps_eval): self.model model self.eval_dataset eval_dataset self.steps_loss steps_loss self.steps_eval steps_eval def step_end(self, run_context): cb_params run_context.original_args() # 记录loss和accuracy self.steps_loss.append(cb_params.net_outputs) acc self.model.eval(self.eval_dataset) self.steps_eval.append(acc[acc])这个方法虽然不如mindinsight功能强大但足够简单实用完美满足了我的需求。4. 不同实现方式的对比分析4.1 代码复杂度对比实现方式代码行数需要实现的函数调试难度手写感知机~200所有高手写Softmax~300所有非常高MindSpore实现~150模型定义中4.2 性能对比在相同数据集上的实验结果指标手写感知机手写SoftmaxMindSpore训练时间(秒)3.258.71.8测试准确率(%)100100100最大内存占用(MB)50801204.3 学习曲线对比通过可视化三种实现的训练过程我发现手写实现的曲线波动更大收敛不稳定MindSpore实现的曲线更平滑收敛更快使用动态学习率可以显著改善手写实现的收敛性# 绘制准确率曲线的代码示例 plt.plot(epoch_eval[epoch], epoch_eval[acc]) plt.xlabel(Epochs) plt.ylabel(Accuracy) plt.title(Training Accuracy Curve) plt.show()这段从零开始实现机器学习算法的经历让我深刻理解了魔鬼在细节中这句话的含义。每一个看似简单的概念背后都有无数需要关注的细节。而框架的使用虽然能提高效率但也需要花费时间学习其特有的设计理念和最佳实践。

更多文章