Я работал над реализацией сверточных нейронных сетей.Первоначально я реализовал их со стандартными свертками, которые работали, как ожидалось, и прошел проверку градиента, поэтому я начал работу по оптимизации сверток, используя im2col для прямых проходов и col2im для обратных проходов.Я правильно реализовал im2col, и прямой проход работает, как и ожидалось, однако я заметил, что обратный проход не работает правильно, в основном распространяя градиент в обратном направлении.
Проблема заключается в том, что, как описано в https://stackoverflow.com/a/51717536 и в http://www.programmersought.com/article/16963235/, функция col2im не совсем противоположна im2col, в моем случае это приводит к результатуобратного прохода, чтобы быть полностью неправильным, несмотря на то, что и col2im, и умножение матриц, кажется, работают правильно самостоятельно.
Таким образом, так как я изо всех сил пытаюсь найти много информации в Интернете об этом, как решить эту проблемуПроблема?
Мой код умножения матриц, код col2im и код обратного прохода приведены ниже:
/// <summary>
/// O = (A dot B) + C + D
/// </summary>
/// <param name="a">NxM dimensional input matrix</param>
/// <param name="b">MxP dimensional input matrix</param>
/// <param name="c">Px1 dimensional optional input matrix</param>
/// <param name="d">Nx1 dimensional optional input matrix</param>
/// <param name="o">NxP dimensional output matrix</param>
public static void Mad(Matrix a, Matrix b, Matrix c, Matrix d, Matrix o, bool reset)
{
if (a.Columns != b.Rows)
throw new ArgumentException();
if (a.Rows != o.Rows)
throw new ArgumentException();
if (b.Columns != o.Columns)
throw new ArgumentException();
if (c != null && c.Rows != b.Columns)
throw new ArgumentException();
if (d != null && d.Rows != a.Rows)
throw new ArgumentException();
Parallel.For(0, a.Rows, (i) =>
//for(int i = 0; i < a.Rows; i++)
{
for (int j = 0; j < b.Columns; j++)
{
float acc = 0;
for (int k = 0; k < a.Columns; k++)
{
acc += a.memory[a.Index(i, k)] * b.memory[b.Index(k, j)];
}
if (reset)
o.memory[o.Index(i, j)] = acc + (c == null ? 0 : c.memory[c.Index(j, 0)]) + (d == null ? 0 : d.memory[d.Index(i, 0)]);
else
o.memory[o.Index(i, j)] += acc + (c == null ? 0 : c.memory[c.Index(j, 0)]) + (d == null ? 0 : d.memory[d.Index(i, 0)]);
}
});
}
public static void Column2Image(int input_sz, int input_cnt, int stride_len, int padding, int filter_sz, int output_sz, Matrix input, Matrix output)
{
//Foreach column in input, rearrange it into a block, stripping padding and applying appropriate strides
int block_sz = filter_sz * filter_sz * input_cnt;
int len = output_sz * output_sz;
input.Clear();
//Using a version based on Caffe for comparison
//for (int c = 0; c < block_sz; c++)
Parallel.For(0, block_sz, (c) =>
{
int col_off = (c % filter_sz);
int row_off = (c / filter_sz) % filter_sz;
int c_im = c / filter_sz / filter_sz;
for (int row = 0; row < output_sz; row++)
for (int col = 0; col < output_sz; col++)
{
int row_pad = row * stride_len - padding + row_off;
int col_pad = col * stride_len - padding + col_off;
if (row_pad >= 0 && row_pad < input_sz && col_pad >= 0 && col_pad < input_sz)
{
input.memory[input.Index(c_im, row_pad * input_sz + col_pad)] += output.memory[c * output_sz * output_sz + row * output_sz + col];
}
}
});
}
public Matrix[] Propagate(Matrix[] prev_delta)
{
//cur_delta = BackwardDelta = Full convolution of prev_delta with 180 rotated filter <- sum from all filters in terms of filterCnt, but spread across inputDepth?
//Flatten and transpose the Weights as is, dot the flattened prev_delta
Matrix.Mad(Weights.Transpose(), prev_delta[0].Reshape(filterCnt, outputSz * outputSz), null, null, BackwardDeltaIC, true);
Matrix.Column2Image(inputSz, inputDepth, strideLen, paddingSz, filterSz, outputSz, BackwardDelta.Reshape(inputDepth, inputSz * inputSz), BackwardDeltaIC);
//col2im the result
return new Matrix[] { BackwardDelta };
}
Я пытался сравнить реализацию caffe, но я не мог понять, какони справились с этим.Любая информация будет принята с благодарностью!
РЕДАКТИРОВАТЬ: Я должен также упомянуть, что я думаю, что проблема частично связана с наличием ненулевого заполнения, которое, как представляется, также кратко обсуждается https://github.com/pytorch/pytorch/blob/master/aten/src/THNN/generic/Col2Im.c, ноЯ не очень понимаю, как они решают проблему.