第7部分:CUDA 与 Python 的集成

发布: (2025年12月31日 GMT+8 23:30)
10 min read
原文: Dev.to

I’m happy to translate the article for you, but I’ll need the full text of the post (the paragraphs, headings, code snippets, etc.) in order to do so. Could you please paste the content you’d like translated? I’ll keep the source link at the top and preserve all formatting, markdown, and technical terms as requested.

XOR 测试

在成功搭建神经网络后,我用 XOR 运算对其进行了测试。XOR 是一种非线性运算,因此它算是验证网络能否检测非线性的 “Hello World”。测试顺利完成,表明网络已经可以承担更大的任务。

我必须在 Rust 中复现同样的操作,但我的思绪总是阻止我动手。每当我尝试写一行 Rust 代码时,脑子里就会冒出以下疑问:

  • 如果我输入一个复杂的方程会怎样?
  • 如果把线性回归数据喂给神经网络会怎样?
  • 对于逻辑回归数据集它会怎么处理?
  • 它能在 CUDA 上运行吗?
  • 线性回归程序能在 CUDA 上运行吗?

太多未解之谜,打断了我的思路。

基于 CUDA 的线性回归程序

当时我已经在使用 CUDA,对我来说最容易的事就是把 CUDA 集成到我的库的 Python 和 Rust 版本中。我把线性回归程序切换到 GPU 上运行。

(不)出所料,这并没有成功。我的 CPU 版程序的根均方误差(Root MSE)只有 7.75,但 GPU 版返回了 40。

调试

在逐行搜索代码后,我发现在线性预测函数中我从 GPU 返回了一个全零矩阵。我将其修正为返回实际输出,程序便恢复正常。

Results:
Total samples: 93
Mean Squared Error: 59.6779
Root MSE: 7.7251

同样,我编写了另一个用于逻辑回归的 CUDA 程序,也运行得很顺利。遗憾的是,我不知何故没有捕获 CUDA 逻辑回归程序的结果。

一个问题已解决: Rust CUDA 程序可以使用回归数据集。我们继续。


Source:

CUDA 与 Python 的集成

当我对结果感到满意时,我转向用于 XOR 测试数据集的 Python 神经网络脚本。我已经处理过更大的数据集,所以运行这个简单的 XOR 测试感觉不太对劲。我计划也在神经网络上运行线性回归和逻辑回归数据集;理想情况下,它们应该能够顺利运行。

我把 JSON 文件接入脚本并开始训练神经网络。

哎呀,我又打开了另一个兔子洞。

在大数据加载时,CPU 的顺序处理能力不足,尽管 NumPy 对数组进行了一些微小的并行化。电子邮件垃圾/正常数据集成为了临界点——我的 CPU 再也承受不住负载。我在 scikit‑learn 中寻找解决方案,发现该库通过 cupy 提供了有限的 GPU 支持,但设置上有一些挑战。

由于我已经理解了数学原理,并且手头有一个基于 NumPy 的神经网络程序,我把 NumPy 的导入改成了 CuPy。显然,我会失去很多优化手段,但这可以促使我以后学习优化技术。

计划的解决方案: 我启动了 WSL,安装了 cupy,并在脚本中将 numpy 替换为 cupy。随后又遇到了一段“流沙”。

在 NumPy 版本中,数千次迭代只用了 10 分钟的程序,在 CuPy 版本中甚至连 1 000 次迭代都完成不了 10 分钟。幸好,我之前的 Rust 经验帮了忙:瓶颈出在主机到设备(H2D)以及设备到主机(D2H)的数据拷贝开销上。

我重新查阅了库文档,找到了解决办法并加以实施。需要做两项调整:

  1. 在每个 epoch 中去掉误差计算,以避免 D2H 拷贝。
  2. 在一组操作完成后使用 synchronize,而不是在每个 epoch 后都同步。

The Takeaway

在修复了 H2D/D2H 拷贝开销后,程序的运行速度已经相当不错。我现在可以在 10–15 秒 内运行相同的脚本——≈60 倍提速

老实说,我已经对这种速度上瘾了…

经过一天的搭建和调试后,终于可以稍作玩耍。我开始进行实验:

  • 首先是单层(几乎没有负载)。
  • 然后为了好玩,又加了两层。

于是,神经网络把线性回归数据集的 MSE 再次压得更低:

Test MSE: 52.3265 (From earlier 59.6779)
Test MAE: 5.3395

这次的付出再次值得。看到网络在每一次输出中一步步学习的过程非常令人满足。得益于速度提升,我可以选择更低的学习率和更高的 epoch 数(训练迭代次数),并且可以调节网络配置(层数、每层节点数等)。

Epoch 1/200000   | Error: 25.926468
Epoch 1000/200000| Error: 0.482236
Epoch 2000/200000| Error: 0.414885
Epoch 3000/200000| Error: 0.377820
Epoch 4000/200000| Error: 0.354329
Epoch 5000/200000| Error: 0.340112
Epoch 6000/200000| Error: 0.331060
Epoch 7000/200000| Error: 0.324392
Epoch 8000/200000| Error: 0.319276
Epoch 9000/200000| Error: 0.315130
Epoch 10000/200000| Error: 0.311793
Epoch 11000/200000| Error: 0.308888
Epoch 12000/200000| Error: 0.306242
Epoch 13000/200000| Error: 0.303405
Epoch 14000/200000| Error: 0.300487
Epoch 15000/200000| Error: 0.298240
Epoch 16000/200000| Error: 0.296392

在查看误差时,我注意到梯度下降的工作方式:一开始误差很高,网络会快速纠正以趋向收敛;随着时间推移,网络学习后误差下降,纠正幅度也随之变小。

我尝试了不同的学习率。当我选择较大的学习率时,神经网络无法收敛——它在两个点之间来回振荡,最终误差反而更高。

学习率与网络深度的实验

相反,使用较小的学习率可以得到更平滑的收敛过程,但收敛所需时间会非常长。

我还注意到,即使学习率降到 0.005 这样的小值,网络在 20 000 epochs 左右后几乎已经稳定,但仍会出现轻微的振荡。

Epoch‑error plot

另一个观察是,增加训练循环的次数对 平均绝对误差(MAE)的影响并不大。到了一定程度后,误差会出现饱和。

2 隐藏层 的最佳结果

Training completed in 285.5212 seconds.

Final Results after 100 000 epochs and learning rate 0.001:

Test MSE: 46.2653
Test MAE: 5.2730

可比结果(更少 epoch、更高学习率)

Training completed in 117.2746 seconds.

Final Results after 40 000 epochs and learning rate 0.005:

Test MSE: 52.6716
Test MAE: 5.3199
Starting training...

1 隐藏层 的最佳结果

Training completed in 77.6143 seconds.

Final Results after 40 000 epochs and learning rate 0.005:

Test MSE: 45.5669
Test MAE: 5.1707

等我把所有实验都做完时,已经过去了 6 小时。这些洞见让我对神经网络的理解比以前更深入了一些。

我一直对之前用 Rust 编写的逻辑回归程序的性能持怀疑态度。随着 CUDA 集成成功,是时候做一次公平的对比了。我在神经网络中实现了逻辑回归,使用单个线性层后接 sigmoid 层。

结果让我惊讶:我写得并不高效的 CUDA 程序实际上比我的 cupy 程序 更快

程序。我没有进一步调查;我就收工了。

快速清单检查

  • CPU 线性回归
  • CPU 逻辑回归
  • GPU 线性回归
  • GPU 逻辑回归
  • 用于运行 GPU 加速神经网络的 Python 脚本

你知道吗?这为更多实验奠定了完美的基础。敬请期待下一步!

Back to Blog

相关文章

阅读更多 »