You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

多进程训练神经网络结果与单进程不符的技术求助

多进程训练神经网络不收敛的问题排查与解决

我看了你用multiprocessing加速神经网络训练时遇到的问题,核心根源其实是多进程的内存隔离机制——每个子进程都会复制父进程的内存空间,子进程里的变量修改完全不会同步回主进程,反过来主进程的变量更新也不会自动传到子进程。咱们逐个拆解你的问题,再给你改代码的思路:

你的核心问题分析

1. 多进程内变量修改不被感知

你在calcGradM里修改deltaMaskWB1deltaMaskWB2这些全局变量,但实际上这些变量在子进程里只是主进程的副本——子进程改的是自己内存里的版本,主进程根本看不到,等于白改。这就导致你的神经网络参数从来没真正更新过,自然没法收敛。

2. 传新数据结果仍不正确

你的Grad函数依赖全局的wb1wb2,但子进程启动时就复制了这些参数的初始值,之后主进程即使更新了参数,子进程用的还是旧副本。每次传新数据时,子进程还是用旧参数计算,结果肯定不对。

3. 梯度计算异常

单进程时某些参数的微小变化不影响结果,梯度为0,但多进程里因为子进程用的是旧参数副本,和主进程的参数状态不一致,计算出来的梯度自然和单进程不一样,甚至全是非0值。

具体修改方案

解决的核心思路是:让子进程只负责计算,把结果返回给主进程,由主进程统一更新参数和梯度掩码,彻底避免全局变量的共享问题。

第一步:重构函数,去掉全局依赖

把所有依赖全局变量的函数改成接收参数的形式,让子进程能拿到最新的参数:

def result(L1, L2, pos1, pos2, neuronsIn):
    ans = []
    for i in range(pos1, pos2):
        ans.append(netResponse(L1, L2, i))
    return np.sum(ans)

def Grad(score0, lvl, layer, i, wb1, wb2, epsilon, pos1, pos2, neuronsIn):
    maskW1 = np.zeros_like(wb1)
    maskW2 = np.zeros_like(wb2)
    maskB1 = np.zeros_like(wb1)
    maskB2 = np.zeros_like(wb2)
    
    if layer == 1:
        maskW1[lvl][0][i] = epsilon
        maskB1[lvl][1][i] = epsilon
    if layer == 2:
        maskW2[lvl][0][i] = epsilon
        maskB2[lvl][1][i] = epsilon
    
    # 计算权重和偏置的变化带来的结果差,返回给主进程
    delta_w = result(wb1 + maskW1, wb2 + maskW2, pos1, pos2, neuronsIn) - score0
    delta_b = result(wb1 + maskB1, wb2 + maskB2, pos1, pos2, neuronsIn) - score0
    return (delta_w, delta_b, layer, lvl, i)

第二步:修改多进程训练逻辑,主进程统一更新

multiTrain里,每次调用多进程时把最新的参数传进去,收集子进程返回的结果后,在主进程里更新梯度掩码和神经网络参数:

def multiTrain():
    T0 = time.time()
    global wb1, wb2
    # 初始化梯度掩码
    deltaMaskWB1 = np.zeros_like(wb1)
    deltaMaskWB2 = np.zeros_like(wb2)
    
    # 用当前最新参数计算基准得分
    score0 = result(wb1, wb2, pos1, pos2, neuronsIn)
    VC = -epsilon / score0  # 提前计算VC,避免重复计算
    
    if __name__ == '__main__':
        pool = multiprocessing.Pool(4)
        
        # 处理第二层的梯度计算
        for l in range(2):
            # 绑定固定参数,传递当前最新的wb1、wb2
            grad_tasks = partial(Grad, score0, l, 2, wb1=wb1, wb2=wb2, epsilon=epsilon, pos1=pos1, pos2=pos2, neuronsIn=neuronsIn)
            grad_results = pool.map(grad_tasks, range(hidLen))
            
            # 主进程统一更新deltaMaskWB2
            for delta_w, delta_b, _, lvl, i in grad_results:
                deltaMaskWB2[lvl][0][i] += delta_w * VC
                deltaMaskWB2[lvl][1][i] += delta_b * VC
        
        # 处理第一层的梯度计算
        for l in range(hidLen):
            grad_tasks = partial(Grad, score0, l, 1, wb1=wb1, wb2=wb2, epsilon=epsilon, pos1=pos1, pos2=pos2, neuronsIn=neuronsIn)
            grad_results = pool.map(grad_tasks, range(len(neuronsIn)))
            
            # 主进程统一更新deltaMaskWB1
            for delta_w, delta_b, _, lvl, i in grad_results:
                deltaMaskWB1[lvl][0][i] += delta_w * VC
                deltaMaskWB1[lvl][1][i] += delta_b * VC
        
        pool.close()
        pool.join()  # 必须等待所有子进程完成再更新参数
        
        # 用计算好的梯度更新神经网络参数
        wb1 += deltaMaskWB1
        wb2 += deltaMaskWB2
        
        print(f"训练耗时:{time.time() - T0}秒")

额外注意事项

  • 尽量避免在多进程中使用全局可变对象(比如numpy数组),子进程的副本和主进程完全独立,修改不会同步。
  • 如果你的模型参数很大,每次传递参数会有开销,可以考虑用共享内存(比如multiprocessing.Array或者numpy的共享数组),但实现起来更复杂,先试试上面的方案,简单直观。
  • 一定要调用pool.join(),确保所有子进程完成计算后,主进程再更新参数,避免出现参数状态不一致的问题。

内容的提问来源于stack exchange,提问作者Kacper Ogórek

火山引擎 最新活动