Skip to content

Commit 5a65638

Browse files
committed
Fully connected cpu layer code refactored and tested
1 parent b3d41e3 commit 5a65638

File tree

3 files changed

+73
-22
lines changed

3 files changed

+73
-22
lines changed

NeuralNetwork.NET/Extensions/MatrixExtensions.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -716,7 +716,14 @@ public static unsafe bool ContentEquals(in this Tensor m, in Tensor o,float abso
716716
float* pm = m, po = o;
717717
int items = m.Size;
718718
for (int i = 0; i < items; i++)
719-
if (!pm[i].EqualsWithDelta(po[i], absolute, relative)) return false;
719+
if (!pm[i].EqualsWithDelta(po[i], absolute, relative))
720+
{
721+
#if DEBUG
722+
if (System.Diagnostics.Debugger.IsAttached)
723+
System.Diagnostics.Debug.WriteLine($"[DEBUG] {pm[i]} | {po[i]} | Threshold exceeded");
724+
#endif
725+
return false;
726+
}
720727
return true;
721728
}
722729

NeuralNetwork.NET/Networks/Layers/Cpu/FullyConnectedLayer.cs

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using NeuralNetworkNET.APIs.Enums;
55
using NeuralNetworkNET.APIs.Interfaces;
66
using NeuralNetworkNET.APIs.Structs;
7+
using NeuralNetworkNET.cpuDNN;
78
using NeuralNetworkNET.Extensions;
89
using NeuralNetworkNET.Networks.Activations;
910
using NeuralNetworkNET.Networks.Activations.Delegates;
@@ -35,11 +36,14 @@ public FullyConnectedLayer(in TensorInfo input, int neurons, [NotNull] float[] w
3536
/// <inheritdoc/>
3637
public override unsafe void Forward(in Tensor x, out Tensor z, out Tensor a)
3738
{
38-
fixed (float* pw = Weights)
39+
fixed (float* pw = Weights, pb = Biases)
3940
{
40-
Tensor.Reshape(pw, InputInfo.Size, OutputInfo.Size, out Tensor wTensor);
41-
x.MultiplyWithSum(wTensor, Biases, out z);
42-
z.Activation(ActivationFunctions.Activation, out a);
41+
Tensor.Reshape(pw, InputInfo.Size, OutputInfo.Size, out Tensor w);
42+
Tensor.Reshape(pb, 1, Biases.Length, out Tensor b);
43+
Tensor.New(x.Entities, OutputInfo.Size, out z);
44+
CpuDnn.FullyConnectedForward(x, w, b, z);
45+
Tensor.New(z.Entities, z.Length, out a);
46+
CpuDnn.ActivationForward(z, ActivationFunctions.Activation, a);
4347
}
4448
}
4549

@@ -48,21 +52,19 @@ public override unsafe void Backpropagate(in Tensor dy, in Tensor z, ActivationF
4852
{
4953
fixed (float* pw = Weights)
5054
{
51-
Tensor.Reshape(pw, InputInfo.Size, OutputInfo.Size, out Tensor wTensor);
52-
wTensor.Transpose(out Tensor wt);
53-
z.InPlaceMultiplyAndHadamardProductWithActivationPrime(dy, wt, activationPrime);
54-
wt.Free();
55+
Tensor.Reshape(pw, InputInfo.Size, OutputInfo.Size, out Tensor w);
56+
CpuDnn.FullyConnectedBackwardData(z, w, dy, activationPrime, z);
5557
}
5658
}
5759

5860
/// <inheritdoc/>
5961
public override void ComputeGradient(in Tensor a, in Tensor delta, out Tensor dJdw, out Tensor dJdb)
6062
{
61-
a.Transpose(out Tensor at);
62-
at.Multiply(delta, out Tensor dJdwM);
63-
dJdwM.Reshape(1, Weights.Length, out dJdw);
64-
at.Free();
65-
delta.CompressVertically(out dJdb);
63+
Tensor.New(InputInfo.Size, OutputInfo.Size, out Tensor dw);
64+
CpuDnn.FullyConnectedBackwardFilter(a, delta, dw);
65+
dw.Reshape(1, dw.Size, out dJdw); // Flatten the result
66+
Tensor.New(1, Biases.Length, out dJdb);
67+
CpuDnn.FullyConnectedBackwardBias(delta, dJdb);
6668
}
6769

6870
/// <inheritdoc/>

NeuralNetwork.NET/cpuDNN/CpuDnn{Main}.cs

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,19 @@
77

88
namespace NeuralNetworkNET.cpuDNN
99
{
10+
/// <summary>
11+
/// A static class that contains primitives to implement a CNN running on CPU
12+
/// </summary>
1013
public static partial class CpuDnn
1114
{
1215
#region Activation
1316

17+
/// <summary>
18+
/// Executes the input activation function on the target <see cref="Tensor"/>
19+
/// </summary>
20+
/// <param name="x">The layer input <see cref="Tensor"/></param>
21+
/// <param name="f">The activation function to apply to the input</param>
22+
/// <param name="y">The output <see cref="Tensor"/> - it can be the same as the input</param>
1423
public static unsafe void ActivationForward(in Tensor x, [NotNull] ActivationFunction f, in Tensor y)
1524
{
1625
// Setup
@@ -31,15 +40,22 @@ void Kernel(int i)
3140
Parallel.For(0, n, Kernel).AssertCompleted();
3241
}
3342

34-
public static unsafe void ActivationBackward(in Tensor x, in Tensor y, [NotNull] ActivationFunction f_, in Tensor dx)
43+
/// <summary>
44+
/// Executes the backward activation function on the target <see cref="Tensor"/>, with the given error delta
45+
/// </summary>
46+
/// <param name="x">The activity on the input layer</param>
47+
/// <param name="dy">The current error delta to backpropagate</param>
48+
/// <param name="f_">The derivative of the activation function used in the forward pass</param>
49+
/// <param name="dx">The resulting input error delta - it can be the same as the input <see cref="Tensor"/></param>
50+
public static unsafe void ActivationBackward(in Tensor x, in Tensor dy, [NotNull] ActivationFunction f_, in Tensor dx)
3551
{
3652
// Check
37-
if (!y.MatchShape(x)) throw new ArgumentException("The input tensors must have the same shape", nameof(y));
38-
if (!dx.MatchShape(x)) throw new ArgumentException("The output tensor must have the same shape as the input", nameof(y));
53+
if (!dy.MatchShape(x)) throw new ArgumentException("The input tensors must have the same shape", nameof(dy));
54+
if (!dx.MatchShape(x)) throw new ArgumentException("The output tensor must have the same shape as the input", nameof(dy));
3955
int
4056
n = x.Entities,
4157
l = x.Length;
42-
float* px = x, py = y, pdx = dx;
58+
float* px = x, pdy = dy, pdx = dx;
4359

4460
// Loop in parallel
4561
void Kernel(int i)
@@ -48,7 +64,7 @@ void Kernel(int i)
4864
for (int j = 0; j < l; j++)
4965
{
5066
int target = offset + j;
51-
pdx[target] = f_(px[target]) * py[target];
67+
pdx[target] = f_(px[target]) * pdy[target];
5268
}
5369
}
5470
Parallel.For(0, n, Kernel).AssertCompleted();
@@ -58,6 +74,13 @@ void Kernel(int i)
5874

5975
#region Fully connected
6076

77+
/// <summary>
78+
/// Executes the forward pass on a fully connected layer
79+
/// </summary>
80+
/// <param name="x">The input <see cref="Tensor"/> to process</param>
81+
/// <param name="w">The layer weights</param>
82+
/// <param name="b">The layer biases</param>
83+
/// <param name="y">The output <see cref="Tensor"/> for the current layer</param>
6184
public static unsafe void FullyConnectedForward(in Tensor x, in Tensor w, in Tensor b, in Tensor y)
6285
{
6386
// Initialize the parameters and the result matrix
@@ -89,6 +112,14 @@ void Kernel(int i)
89112
Parallel.For(0, h, Kernel).AssertCompleted();
90113
}
91114

115+
/// <summary>
116+
/// Executes the backward pass on a fully connected layer
117+
/// </summary>
118+
/// <param name="x">The activity on the layer inputs</param>
119+
/// <param name="w">The layer weights</param>
120+
/// <param name="dy">The output error delta</param>
121+
/// <param name="f_">The derivative of the activation function used in the forward pass</param>
122+
/// <param name="dx">The resulting input error delta</param>
92123
public static unsafe void FullyConnectedBackwardData(in Tensor x, in Tensor w, in Tensor dy, [NotNull] ActivationFunction f_, in Tensor dx)
93124
{
94125
if (w.Entities != x.Length) throw new ArgumentException("The weights tensor doesn't have a valid shape", nameof(w));
@@ -127,6 +158,12 @@ void Kernel(int i)
127158
wt.Free();
128159
}
129160

161+
/// <summary>
162+
/// Executes the backward pass on a fully connected layer to calculate the gradient with respect to the weights
163+
/// </summary>
164+
/// <param name="x">The layer inputs</param>
165+
/// <param name="dy">The layer output error delta</param>
166+
/// <param name="dw">The resulting weights gradient <see cref="Tensor"/></param>
130167
public static void FullyConnectedBackwardFilter(in Tensor x, in Tensor dy, in Tensor dw)
131168
{
132169
if (x.Entities != dy.Entities) throw new ArgumentException("The input tensor doesn't match the number of samples from the delta", nameof(x));
@@ -136,10 +173,15 @@ public static void FullyConnectedBackwardFilter(in Tensor x, in Tensor dy, in Te
136173
xt.Free();
137174
}
138175

176+
/// <summary>
177+
/// Executes the backward pass on a fully connected layer to calculate the gradient with respect to the biases
178+
/// </summary>
179+
/// <param name="dy">The layer output error delta</param>
180+
/// <param name="db">The resulting biases gradient <see cref="Tensor"/></param>
139181
public static unsafe void FullyConnectedBackwardBias(in Tensor dy, in Tensor db)
140182
{
141183
// Preliminary checks and declarations
142-
if (!db.MatchShape(1, dy.Length)) throw new ArgumentException("The output tensor doesn't have the right shape", nameof(db));
184+
if (!db.MatchShape(1, dy.Length)) throw new ArgumentException("Invalid result tensor shape", nameof(db));
143185
int
144186
n = dy.Entities,
145187
l = dy.Length;
@@ -150,8 +192,8 @@ void Kernel(int j)
150192
{
151193
float sum = 0;
152194
for (int i = 0; i < n; i++)
153-
sum += pdb[i * l + j];
154-
pdy[j] = sum;
195+
sum += pdy[i * l + j];
196+
pdb[j] = sum;
155197
}
156198
Parallel.For(0, l, Kernel).AssertCompleted();
157199
}

0 commit comments

Comments
 (0)