MLBUFFER B_InputOp1;
if (1)
{
int nexttid = 0;
std::vector bindings_in;
std::vector bindings_out;
std::vector outputs;
// Input Tensor
B_InputOp1.Create(ml.d3D12Device, dml::InputTensor(graph1, nexttid++, { DML_TENSOR_DATA_TYPE_FLOAT32, { batch,1,1,(unsigned int)data[0].input.cols() * data[0].input.rows()} }));
bindings_in.push_back(B_InputOp1.BindingDesc());
// Weights and Biases
for (size_t i = 1; i < Layers.size() && Layers.size() >= 3; i++)
{
auto& layer = Layers[i];
layer.B_Weights.Create(ml.d3D12Device, (dml::InputTensor(graph1, nexttid++, { DML_TENSOR_DATA_TYPE_FLOAT32, { batch,1,(unsigned int)layer.weights.rows(),(unsigned int)layer.weights.cols()} })));
bindings_in.push_back(layer.B_Weights.BindingDesc());
// If MNIST Hidden, [batch,1,784,128] 401408
// If MNIST Output, [batch,1,128,10] 5120
layer.B_Biases.Create(ml.d3D12Device, (dml::InputTensor(graph1, nexttid++, { DML_TENSOR_DATA_TYPE_FLOAT32, { batch,1,1,(unsigned int)layer.biases.cols()} })));
bindings_in.push_back(layer.B_Biases.BindingDesc());
// If MNIST Hidden, [batch,1,1,128] 512
// If MNIST Output, [batch,1,1,10] 40
}
// Create Forward Propagation Operators
for (int i = 0; i < Layers.size() ; i++)
{
auto& layer = Layers[i];
if (i == 0) // input layer -> first hidden
{
layer.B_Outputs.Create(ml.d3D12Device, dml::Identity(B_InputOp1.ee));
bindings_out.push_back(layer.B_Outputs.BindingDesc());
// If MNIST, [1,1,1,784] 3136
}
else
{
auto& pl = Layers[i - 1];
auto mul1 = dml::Gemm(pl.B_Outputs.ee, layer.B_Weights.ee); // batch,1,1,128 if mnist hidden, batch,1,1,10 if mnist output
auto add1 = dml::Add(mul1, layer.B_Biases.ee);
if (layer.ActType == 1) // Relu
layer.B_Outputs.Create(ml.d3D12Device, dml::ActivationRelu(add1));
if (layer.ActType == 0) // sigmoid
layer.B_Outputs.Create(ml.d3D12Device, dml::ActivationSigmoid(add1));
bindings_out.push_back(layer.B_Outputs.BindingDesc());
// If MNIST Hidden, [batch,1,1,128] 512
// If MNIST Output, [batch,1,1,10] 40
}
// We want the outputs as output
outputs.push_back(layer.B_Outputs.ee);
}
// Create the operator
auto OutputCompiledOperator2 = graph1.Compile(DML_EXECUTION_FLAG_ALLOW_HALF_PRECISION_COMPUTATION, outputs);
MLOP op2;
op2.dmlCompiledOperator.Attach(OutputCompiledOperator2.Detach());
op2.bindings_out = bindings_out;
op2.bindings_in = bindings_in;
ml.ops.push_back(op2);
}
Это работает. Когда я выполняю оператор в D3D12, он создает выходные данные в тензоре «Выход» выходного слоя, аналогичные результатам вычислений ЦП.
Класс MLBUFFER — это класс, который просто оборачивает ID3D12Resource как буфер и предоставляет методы загрузки и выгрузки.
Код BP в ЦП выглядит следующим образом:
void BackPropagation(Matrix label)
{
auto OurOutput = Layers.back().output; // 1x10
// Calculation of the derivation of MSE, 2 is ignored for simplicity because it doesn't affect gradient descent
auto delta = OurOutput - label; // 1x10
for (int i = (int)(Layers.size() - 1); i > 0; i--)
{
Layer& curr = Layers[i];
Layer& prev = Layers[i - 1];
// biased += σ'(z) , delta = 1x10,
curr.biases += delta * ((float)-curr.lr);
// weights += prev.Y.T * σ'(z)
Matrix gradient = (prev.output.transpose() * delta); // 128x10, 784x128
/* float clip_value = 5.0f;
for (auto& value : gradient.data()) {
value = std::max(-clip_value, std::min(value, clip_value));
}
// to be implemented later
*/
curr.weights += gradient * ((float)-curr.lr);
// σ'(z) = σ(z) * (1 - σ(z))
Matrix one = Matrix(prev.output.rows(), prev.output.cols(), 1); // 1x128 ,1x784
// Sigmoid Derivative
Matrix der = prev.output; // 1x128, 1x784
if (prev.ActType == 0)
der.sigmoid_derivative();
else
der.relu_derivative();
// delta = (delta * prev.W.T) x σ'(z);
// This multiplication implements the chain rule, combining the loss gradient with the derivative of the activation function for each layer.
if (i > 1) // don't calculate for the first layer
delta = (delta * curr.weights.transpose()).multiply_inplace(der); // 1x128 second time
}
}
Когда я пытаюсь реализовать это в DirectML, кажется, что это повреждает мои буферы, и я не знаю, что происходит не так.
// Backward propagation operator
dml::Expression T_InputOp2;
MLBUFFER B_Final;
MLBUFFER B_Label;
if (1)
{
int nexttid = 0;
std::vector bindings_in;
std::vector bindings_out;
std::vector outputs;
// Input Tensor
T_InputOp2 = dml::InputTensor(graph2, nexttid++, { DML_TENSOR_DATA_TYPE_FLOAT32, Layers.back().B_Outputs.GetOutputDesc().sizes });
bindings_in.push_back(Layers.back().B_Outputs.BindingDesc());
// Label Tensor
B_Label.Create(ml.d3D12Device, dml::InputTensor(graph2, nexttid++, { DML_TENSOR_DATA_TYPE_FLOAT32, Layers.back().B_Outputs.GetOutputDesc().sizes }));
bindings_in.push_back(B_Label.BindingDesc());
// Delta Loop
Layers.back().T_Delta = dml::Subtract(T_InputOp2, B_Label.ee);
for (int i = (int)(Layers.size() - 1); i >= 0; i--)
{
Layer& curr = Layers[i];
if (i == 0)
{
B_Final.Create(ml.d3D12Device, dml::Identity(curr.T_Delta));
bindings_out.push_back(B_Final.BindingDesc());
break;
}
Layer& prev = Layers[i - 1];
// biased += σ'(z)
// curr.biases += delta * ((float)-curr.lr);
auto bp_mincurrlr = ml.ConstantValueTensor(graph2, (float)-curr.lr, curr.T_Delta.GetOutputDesc().sizes);
auto bp_mul2 = dml::Multiply(curr.T_Delta, bp_mincurrlr);
if (curr.T_BiasesBPIn.Impl() == 0)
{
curr.T_BiasesBPIn = (dml::InputTensor(graph2, nexttid++, { DML_TENSOR_DATA_TYPE_FLOAT32, curr.B_Biases.ee.GetOutputDesc().sizes }));
bindings_in.push_back(curr.B_Biases.BindingDesc());
}
curr.B_BiasesOut.Create(ml.d3D12Device, dml::Add(curr.T_BiasesBPIn, bp_mul2));
bindings_out.push_back(curr.B_BiasesOut.BindingDesc());
// curr.weights += gradient * ((float)-curr.lr); // Gradient will be first 128x10, second 784x128 for MNIST
// 1,1,1,10, 1,1,1,128
// Matrix gradient = (prev.output.transpose() * delta); // 128x10, 784x128
if (prev.T_OutputsBPIn.Impl() == 0)
{
prev.T_OutputsBPIn = (dml::InputTensor(graph2, nexttid++, { DML_TENSOR_DATA_TYPE_FLOAT32, prev.B_Outputs.GetOutputDesc().sizes }));
bindings_in.push_back(prev.B_Outputs.BindingDesc());
}
curr.bp_PrevOutputTranspose = dml::Reinterpret(prev.T_OutputsBPIn, DML_TENSOR_DATA_TYPE_FLOAT32, { 1,1,(unsigned int)prev.T_OutputsBPIn.GetOutputDesc().sizes[3],(unsigned int)prev.T_OutputsBPIn.GetOutputDesc().sizes[2] }, dml::NullOpt);
curr.bp_gradient = dml::Gemm(curr.bp_PrevOutputTranspose, curr.T_Delta); // 128x10, 784x128 for MNIST
curr.bp_mincurrlr2 = ml.ConstantValueTensor(graph2, (float)-curr.lr, curr.bp_gradient.GetOutputDesc().sizes);
auto bp_mul3 = dml::Multiply(curr.bp_gradient, curr.bp_mincurrlr2);
if (curr.T_WeightsBPIn.Impl() == 0)
{
curr.T_WeightsBPIn = (dml::InputTensor(graph2, nexttid++, { DML_TENSOR_DATA_TYPE_FLOAT32,curr.B_Weights.ee.GetOutputDesc().sizes }));
bindings_in.push_back(curr.B_Weights.BindingDesc());
}
curr.B_WeightsOut.Create(ml.d3D12Device, dml::Add(curr.T_WeightsBPIn, bp_mul3));
bindings_out.push_back(curr.B_WeightsOut.BindingDesc());
// 1,1,128,10, 1,1,784,128
// curr.bp_one = ml.ConstantValueTensor(graph3, 1.0f, { 1,1,prev.T_OutputsBP.GetOutputDesc().sizes[2],prev.T_OutputsBP.GetOutputDesc().sizes[3] });
auto bp_der1 = dml::Identity(prev.T_OutputsBPIn);
if (prev.ActType == 0)
{
// Sigmoid Derivative
auto bp_dx = dml::ActivationSigmoid(bp_der1);
auto bp_one1 = ml.ConstantValueTensor(graph2, 1.0f, bp_dx.GetOutputDesc().sizes);
auto bp_oneMinusSigmoid = dml::Subtract(bp_one1, bp_dx);
curr.bp_der2 = dml::Multiply(bp_dx, bp_oneMinusSigmoid);
}
else
{
// Relu derivative
auto bp_dx = dml::ActivationRelu(bp_der1);
auto bp_zeroTensor = ml.ConstantValueTensor(graph2, 0.0f, bp_dx.GetOutputDesc().sizes);
auto bp_reluMask = dml::GreaterThan(bp_dx, bp_zeroTensor);
curr.bp_der2 = dml::Cast(bp_reluMask, DML_TENSOR_DATA_TYPE_FLOAT32);
}
// Calculate delta again
auto bp_CurrWT = dml::Reinterpret(curr.B_WeightsOut.ee, DML_TENSOR_DATA_TYPE_FLOAT32, { 1,1,(unsigned int)curr.B_WeightsOut.ee.GetOutputDesc().sizes[3],(unsigned int)curr.B_WeightsOut.ee.GetOutputDesc().sizes[2] }, dml::NullOpt);
auto bp_Delta2 = dml::Gemm(curr.T_Delta, bp_CurrWT);
auto bp_Delta3 = dml::Multiply(bp_Delta2, curr.bp_der2);
prev.T_Delta = dml::Identity(bp_Delta3);
}
// Create the operator
for (int i = (int)(Layers.size() - 1); i >= 0; i--)
{
auto& l = Layers[i];
if (l.B_BiasesOut.b)
outputs.push_back(l.B_BiasesOut.ee);
if (l.B_WeightsOut.b)
outputs.push_back(l.B_WeightsOut.ee);
}
outputs.push_back(B_Final.ee);
auto OutputCompiledOperator2 = graph2.Compile(DML_EXECUTION_FLAG_ALLOW_HALF_PRECISION_COMPUTATION, outputs);
MLOP op2;
op2.dmlCompiledOperator.Attach(OutputCompiledOperator2.Detach());
op2.bindings_out = bindings_out;
op2.bindings_in = bindings_in;
ml.ops.push_back(op2);
}
После выполнения этого оператора вещь не работает, повредились буферы. Я не знаю, как это отладить, кроме ручной настройки пользовательских буферов вывода для второго оператора и их проверки, но даже после выполнения этих двух строк:
буфер вывода поврежден.
Буду признателен за ваш совет. С буферами происходит что-то странное.
Редактировать: я уменьшил проблему, просто попробовав обратное распространение ошибки:
Если я это пропущу, буфер в B_Outputs выходного слоя будет в порядке. Почему? Такое ощущение, что он его инициализирует нулями... почему? Насколько мне известно, операторы DirectML могут повторно использовать буферы.
Изменить 3: когда я делаю это с первым оператором, это работает. Почему наличие второго оператора обнуляет входной буфер первого оператора? Но я не могу использовать только один оператор, потому что для NN буферы обратного распространения ошибки будут перекрываться.
Я пытаюсь построить обучение на основе MNIST в DirectML. Итак, оператор кода прямого распространения выглядит в ЦП следующим образом: [code]Matrix ForwardPropagation(Matrix input) { // First layer, put the inputs as flattened Layers[0].output = input.flatten();
// Next layers, forward for (size_t i = 1; i < Layers.size(); i++) { auto& current = Layers[i]; auto& prev = Layers[i - 1];
return Layers.back().output; } [/code] Попытка реализовать это как оператор DirectML выглядит следующим образом: [code] MLBUFFER B_InputOp1; if (1) { int nexttid = 0; std::vector bindings_in; std::vector bindings_out; std::vector outputs;
// If MNIST Hidden, [batch,1,1,128] 512 // If MNIST Output, [batch,1,1,10] 40 }
// Create Forward Propagation Operators for (int i = 0; i < Layers.size() ; i++) { auto& layer = Layers[i]; if (i == 0) // input layer -> first hidden { layer.B_Outputs.Create(ml.d3D12Device, dml::Identity(B_InputOp1.ee)); bindings_out.push_back(layer.B_Outputs.BindingDesc()); // If MNIST, [1,1,1,784] 3136 } else { auto& pl = Layers[i - 1]; auto mul1 = dml::Gemm(pl.B_Outputs.ee, layer.B_Weights.ee); // batch,1,1,128 if mnist hidden, batch,1,1,10 if mnist output auto add1 = dml::Add(mul1, layer.B_Biases.ee); if (layer.ActType == 1) // Relu layer.B_Outputs.Create(ml.d3D12Device, dml::ActivationRelu(add1)); if (layer.ActType == 0) // sigmoid layer.B_Outputs.Create(ml.d3D12Device, dml::ActivationSigmoid(add1)); bindings_out.push_back(layer.B_Outputs.BindingDesc()); // If MNIST Hidden, [batch,1,1,128] 512 // If MNIST Output, [batch,1,1,10] 40 } // We want the outputs as output outputs.push_back(layer.B_Outputs.ee); }
// Create the operator auto OutputCompiledOperator2 = graph1.Compile(DML_EXECUTION_FLAG_ALLOW_HALF_PRECISION_COMPUTATION, outputs); MLOP op2; op2.dmlCompiledOperator.Attach(OutputCompiledOperator2.Detach()); op2.bindings_out = bindings_out; op2.bindings_in = bindings_in; ml.ops.push_back(op2); } [/code] Это работает. Когда я выполняю оператор в D3D12, он создает выходные данные в тензоре «Выход» выходного слоя, аналогичные результатам вычислений ЦП. Класс MLBUFFER — это класс, который просто оборачивает ID3D12Resource как буфер и предоставляет методы загрузки и выгрузки. Код BP в ЦП выглядит следующим образом: [code] void BackPropagation(Matrix label) { auto OurOutput = Layers.back().output; // 1x10
// Calculation of the derivation of MSE, 2 is ignored for simplicity because it doesn't affect gradient descent auto delta = OurOutput - label; // 1x10
for (int i = (int)(Layers.size() - 1); i > 0; i--) { Layer& curr = Layers[i]; Layer& prev = Layers[i - 1];
/* float clip_value = 5.0f; for (auto& value : gradient.data()) { value = std::max(-clip_value, std::min(value, clip_value)); } // to be implemented later */ curr.weights += gradient * ((float)-curr.lr);
// Sigmoid Derivative Matrix der = prev.output; // 1x128, 1x784 if (prev.ActType == 0) der.sigmoid_derivative(); else der.relu_derivative(); // delta = (delta * prev.W.T) x σ'(z); // This multiplication implements the chain rule, combining the loss gradient with the derivative of the activation function for each layer. if (i > 1) // don't calculate for the first layer delta = (delta * curr.weights.transpose()).multiply_inplace(der); // 1x128 second time } } [/code] Когда я пытаюсь реализовать это в DirectML, кажется, что это повреждает мои буферы, и я не знаю, что происходит не так. [code] // Backward propagation operator dml::Expression T_InputOp2; MLBUFFER B_Final; MLBUFFER B_Label; if (1) { int nexttid = 0; std::vector bindings_in; std::vector bindings_out; std::vector outputs;
if (prev.ActType == 0) { // Sigmoid Derivative auto bp_dx = dml::ActivationSigmoid(bp_der1); auto bp_one1 = ml.ConstantValueTensor(graph2, 1.0f, bp_dx.GetOutputDesc().sizes); auto bp_oneMinusSigmoid = dml::Subtract(bp_one1, bp_dx); curr.bp_der2 = dml::Multiply(bp_dx, bp_oneMinusSigmoid); } else { // Relu derivative auto bp_dx = dml::ActivationRelu(bp_der1); auto bp_zeroTensor = ml.ConstantValueTensor(graph2, 0.0f, bp_dx.GetOutputDesc().sizes); auto bp_reluMask = dml::GreaterThan(bp_dx, bp_zeroTensor); curr.bp_der2 = dml::Cast(bp_reluMask, DML_TENSOR_DATA_TYPE_FLOAT32); }
// Calculate delta again auto bp_CurrWT = dml::Reinterpret(curr.B_WeightsOut.ee, DML_TENSOR_DATA_TYPE_FLOAT32, { 1,1,(unsigned int)curr.B_WeightsOut.ee.GetOutputDesc().sizes[3],(unsigned int)curr.B_WeightsOut.ee.GetOutputDesc().sizes[2] }, dml::NullOpt); auto bp_Delta2 = dml::Gemm(curr.T_Delta, bp_CurrWT); auto bp_Delta3 = dml::Multiply(bp_Delta2, curr.bp_der2); prev.T_Delta = dml::Identity(bp_Delta3);
}
// Create the operator for (int i = (int)(Layers.size() - 1); i >= 0; i--) { auto& l = Layers[i]; if (l.B_BiasesOut.b) outputs.push_back(l.B_BiasesOut.ee); if (l.B_WeightsOut.b) outputs.push_back(l.B_WeightsOut.ee); } outputs.push_back(B_Final.ee);
auto OutputCompiledOperator2 = graph2.Compile(DML_EXECUTION_FLAG_ALLOW_HALF_PRECISION_COMPUTATION, outputs); MLOP op2; op2.dmlCompiledOperator.Attach(OutputCompiledOperator2.Detach()); op2.bindings_out = bindings_out; op2.bindings_in = bindings_in; ml.ops.push_back(op2); } [/code] После выполнения этого оператора вещь не работает, повредились буферы. Я не знаю, как это отладить, кроме ручной настройки пользовательских буферов вывода для второго оператора и их проверки, но даже после выполнения этих двух строк: [code] Layer& prev = Layers[i - 1]; // biased += σ'(z) // curr.biases += delta * ((float)-curr.lr); auto bp_mincurrlr = ml.ConstantValueTensor(graph2, (float)-curr.lr, curr.T_Delta.GetOutputDesc().sizes); auto bp_mul2 = dml::Multiply(curr.T_Delta, bp_mincurrlr); [/code] буфер вывода поврежден. Буду признателен за ваш совет. С буферами происходит что-то странное. Редактировать: я уменьшил проблему, просто попробовав обратное распространение ошибки: [code] // Some test if (1) { int nexttid = 0; std::vector bindings_in; std::vector bindings_out; std::vector outputs;
auto OutputCompiledOperator2 = graph2.Compile(DML_EXECUTION_FLAG_ALLOW_HALF_PRECISION_COMPUTATION, outputs); MLOP op2; op2.dmlCompiledOperator.Attach(OutputCompiledOperator2.Detach()); op2.bindings_out = bindings_out; op2.bindings_in = bindings_in; ml.ops.push_back(op2); } [/code] Если я поставлю персональный B_Label, конечный и выходной буферы испортятся... почему? Edit2: [code] // Input Tensor T_InputOp2 = dml::InputTensor(graph2, nexttid++, { DML_TENSOR_DATA_TYPE_FLOAT32, Layers.back().B_Outputs.GetOutputDesc().sizes }); [/code] Если я это пропущу, буфер в B_Outputs выходного слоя будет в порядке. Почему? Такое ощущение, что он его инициализирует нулями... почему? Насколько мне известно, операторы DirectML могут повторно использовать буферы. Изменить 3: когда я делаю это с первым оператором, это работает. Почему наличие второго оператора обнуляет входной буфер первого оператора? Но я не могу использовать только один оператор, потому что для NN буферы обратного распространения ошибки будут перекрываться.