MPSCNN 权重排序

MPSCNN Weight Ordering

Metal Performance Shader 框架支持构建您自己的卷积神经网络。例如,当创建一个 MSPCNNConvolution 时,它需要一个 4D 权重张量作为 init 参数,表示为一维浮点指针。

init(device: MTLDevice,
  convolutionDescriptor: MPSCNNConvolutionDescriptor,
  kernelWeights: UnsafePointer<Float>,
  biasTerms: UnsafePointer<Float>?,
  flags: MPSCNNConvolutionFlags)

文档中有关于 4D 张量的内容

The layout of the filter weight is arranged so that it can be reinterpreted as a 4D tensor (array) weight[outputChannels][kernelHeight][kernelWidth][inputChannels/groups]

不幸的是,这些信息并没有真正告诉我如何将 4D 数组排列成一维 Float 指针。

我尝试按照 BNNS 对方要求的方式订购权重,但没有成功。

如何将 4D 张量(数组)正确表示为 1D Float 指针(数组)?

PS: 我试过像 C 数组一样排列它并获取指向平面数组的指针,但它没有用。

更新

@RhythmicFistman:这就是我将它存储在普通数组中的方式,我可以将其转换为 UsafePointer<Float>(但不起作用):

var output = Array<Float>(repeating: 0, count: weights.count)

for o in 0..<outputChannels {
    for ky in 0..<kernelHeight {
        for kx in 0..<kernelWidth {
            for i in 0..<inputChannels {
                let offset = ((o * kernelHeight + ky) * kernelWidth + kx) * inputChannels + i
                output[offset] = ...
            }
        }
    }
}

好的,我明白了。这是我用来改造卷积和全连接矩阵的 2 python 函数

# shape required for MPSCNN [oC kH kW iC]
# tensorflow order is [kH kW iC oC]
def convshape(a):
    a = np.swapaxes(a, 2, 3)
    a = np.swapaxes(a, 1, 2)
    a = np.swapaxes(a, 0, 1)
    return a

# fully connected only requires a x/y swap
def fullshape(a):
    a = np.swapaxes(a, 0, 1)
    return a

这是我最近必须为 Caffe 权重做的事情,因此我可以提供 Swift 实现来说明我如何重新排序这些权重。下面的函数接收一个用于卷积的 Caffe 权重的 Float 数组(按 [c_o][c_i][h][w] 顺序)并将它们重新排序为 Metal 所期望的([c_o][h][w][c_i]顺序):

public func convertCaffeWeightsToMPS(_ weights:[Float], kernelSize:(width:Int, height:Int), inputChannels:Int, outputChannels:Int, groups:Int) -> [Float] {

    var weightArray:[Float] = Array(repeating:0.0, count:weights.count)
    var outputIndex = 0

    let groupedInputChannels = inputChannels / groups
    let outputChannelWidth = groupedInputChannels * kernelSize.width * kernelSize.height

    // MPS ordering: [c_o][h][w][c_i]
    for outputChannel in 0..<outputChannels {
        for heightInKernel in 0..<kernelSize.height {
            for widthInKernel in 0..<kernelSize.width {
                for inputChannel in 0..<groupedInputChannels {
                    // Caffe ordering: [c_o][c_i][h][w]
                    let calculatedIndex = outputChannel * outputChannelWidth + inputChannel * kernelSize.width * kernelSize.height + heightInKernel * kernelSize.width + widthInKernel
                    weightArray[outputIndex] = weights[calculatedIndex]
                    outputIndex += 1
                }
            }
        }
    }

    return weightArray
}

根据我的图层可视化,这似乎生成了正确的卷积结果(与 Ca​​ffe 生成的结果相匹配)。我相信它也适当地考虑了分组,但我需要验证这一点。

Tensorflow 的顺序与 Caffe 不同,但您应该能够更改循环内部的数学来解决这个问题。

此处的文档假定您具有一些 C 语言的专业知识。在这种情况下,当 x、y 和 z 是编译时已知的常量时,a[x][y][z] 通常会折叠成一维数组。发生这种情况时,z 分量变化最快,其次是 y,然后是 x -- 从外到内。

如果我们有 a[2][2][2],它会折叠成 1D 为:

{ a[0][0][0], a[0][0][1], a[0][1][0], a[0][1][1], 
  a[1][0][0], a[1][0][1], a[1][1][0], a[1][1][1] }

我认为tensorflow已经有一个方便的方法来完成这样的任务:

tf.transpose(aWeightTensor, perm=[3, 0, 1, 2])

完整文档:https://www.tensorflow.org/api_docs/python/tf/transpose