Line data Source code
1 : #include "doctest/doctest.h"
2 : #include "DataContainer.h"
3 : #include "VolumeDescriptor.h"
4 : #include "Loss.h"
5 :
6 : using namespace elsa;
7 : using namespace doctest;
8 :
9 : TEST_SUITE_BEGIN("ml");
10 :
11 : // TODO(dfrank): remove and replace with proper doctest usage of test cases
12 : #define SECTION(name) DOCTEST_SUBCASE(name)
13 :
14 12 : TEST_CASE_TEMPLATE("BinaryCrossentropy", TestType, float)
15 : {
16 8 : IndexVector_t dims{{2, 4}};
17 8 : VolumeDescriptor dd(dims);
18 :
19 : // predictions
20 8 : Eigen::VectorX<TestType> data_x{{0.6f, 0.4f, 0.4f, 0.6f, 1.f, 0.f, 0.3f, 0.7f}};
21 8 : DataContainer<TestType> x(dd, data_x);
22 :
23 : // labels
24 8 : Eigen::VectorX<TestType> data_y{{0.f, 1.f, 0.f, 0.f, 1.f, 0.f, 1.f, 1.f}};
25 8 : DataContainer<TestType> y(dd, data_y);
26 :
27 5 : SECTION("Unweighted SumOverBatchSize")
28 : {
29 1 : auto bce = ml::BinaryCrossentropy(ml::LossReduction::SumOverBatchSize);
30 1 : REQUIRE(bce(x, y) == Approx(0.602543f));
31 : }
32 :
33 5 : SECTION("Unweighted Sum")
34 : {
35 1 : auto bce = ml::BinaryCrossentropy(ml::LossReduction::Sum);
36 1 : REQUIRE(bce(x, y) == Approx(2.410172f));
37 : }
38 5 : SECTION("Gradient Sum")
39 : {
40 2 : auto bce = ml::BinaryCrossentropy(ml::LossReduction::Sum);
41 :
42 2 : Eigen::VectorXf ref_gradient{{-1.f / 0.8f, -1.f / 0.8f, -1.f / 1.2f, -1.f / 0.8f,
43 : -1.f / 2.f, -1.f / 2.f, -1.f / 0.6f, -1.f / 1.4f}};
44 2 : auto gradient = bce.getLossGradient(x, y);
45 :
46 9 : for (int i = 0; i < gradient.getSize(); ++i)
47 8 : REQUIRE(gradient[i] == Approx(ref_gradient[i]));
48 : }
49 :
50 5 : SECTION("Gradient SumOverBatchSize")
51 : {
52 2 : auto bce = ml::BinaryCrossentropy(ml::LossReduction::SumOverBatchSize);
53 :
54 2 : Eigen::VectorXf ref_gradient{{-1.f / 3.2f, -1.f / 3.2f, -1.f / 4.8f, -1.f / 3.2f,
55 : -1.f / 8.f, -1.f / 8.f, -1.f / 2.4f, -1.f / 5.6f}};
56 2 : auto gradient = bce.getLossGradient(x, y);
57 :
58 9 : for (int i = 0; i < gradient.getSize(); ++i)
59 8 : REQUIRE(gradient[i] == Approx(ref_gradient[i]));
60 : }
61 4 : }
62 :
63 12 : TEST_CASE_TEMPLATE("CategoricalCrossentropy", TestType, float)
64 : {
65 8 : IndexVector_t dims{{3, 4}};
66 8 : VolumeDescriptor dd(dims);
67 :
68 : // predictions
69 : // clang-format off
70 8 : Eigen::VectorX<TestType> data_x{{
71 : // first batch
72 : 0.05f, 0.95f, 0.f,
73 : // second batch
74 : 0.1f, 0.8f, 0.1f,
75 : // third batch
76 : 0.2f, 0.3f, 0.5f,
77 : // fourth batch
78 : 0.0f, 0.2f, 0.8f}};
79 : // clang-format on
80 8 : DataContainer<TestType> x(dd, data_x);
81 :
82 : // labels in one-hot encoding
83 : // clang-format off
84 8 : Eigen::VectorX<TestType> data_y{{
85 : // label 1: 1
86 : 0.f, 1.f, 0.f,
87 : // label 2: 2
88 : 0.f, 0.f, 1.f,
89 : // label 3: 0
90 : 1.f, 0.f, 0.f,
91 : // label 4: 1
92 : 0.f, 1.f, 0.f}};
93 : // clang-format on
94 :
95 8 : DataContainer<TestType> y(dd, data_y);
96 :
97 5 : SECTION("Unweighted SumOverBatchSize")
98 : {
99 1 : auto cce = ml::CategoricalCrossentropy(ml::LossReduction::SumOverBatchSize);
100 1 : REQUIRE(cce(x, y) == Approx(1.3931886f));
101 : }
102 :
103 5 : SECTION("Unweighted Sum")
104 : {
105 1 : auto cce = ml::CategoricalCrossentropy(ml::LossReduction::Sum);
106 1 : REQUIRE(cce(x, y) == Approx(5.5727544f));
107 : }
108 :
109 5 : SECTION("Gradient Sum")
110 : {
111 2 : auto cce = ml::CategoricalCrossentropy(ml::LossReduction::Sum);
112 :
113 2 : Eigen::VectorXf ref_gradient{{0, -1.f / .95f, 0, 0, 0, -10.f, -5.f, 0, 0, 0, -5.f, 0}};
114 2 : auto gradient = cce.getLossGradient(x, y);
115 :
116 13 : for (int i = 0; i < gradient.getSize(); ++i)
117 12 : REQUIRE(gradient[i] == Approx(ref_gradient[i]));
118 : }
119 :
120 5 : SECTION("Gradient SumOverBatchSize")
121 : {
122 2 : auto cce = ml::CategoricalCrossentropy(ml::LossReduction::SumOverBatchSize);
123 :
124 2 : Eigen::VectorXf ref_gradient{
125 : {0, -1.f / 3.8f, 0, 0, 0, -10.f / 4.f, -5.f / 4.f, 0, 0, 0, -5.f / 4.f, 0}};
126 2 : auto gradient = cce.getLossGradient(x, y);
127 :
128 13 : for (int i = 0; i < gradient.getSize(); ++i)
129 12 : REQUIRE(gradient[i] == Approx(ref_gradient[i]));
130 : }
131 4 : }
132 :
133 12 : TEST_CASE_TEMPLATE("SparseCategoricalCrossentropy", TestType, float)
134 : {
135 8 : IndexVector_t predictionDims{{3, 4}};
136 8 : VolumeDescriptor predictionDesc(predictionDims);
137 :
138 : // predictions
139 8 : Eigen::VectorX<TestType> data_x{
140 : {0.05f, 0.95f, 0.f, 0.1f, 0.8f, 0.1f, 0.2f, 0.3f, 0.5f, 0.0f, 0.2f, 0.8f}};
141 8 : DataContainer<TestType> x(predictionDesc, data_x);
142 :
143 8 : IndexVector_t labelDims{{4}};
144 8 : VolumeDescriptor labelDesc(labelDims);
145 :
146 : // labels
147 8 : Eigen::VectorX<TestType> data_y{{1.f, 2.f, 0.f, 1.f}};
148 8 : DataContainer<TestType> y(labelDesc, data_y);
149 :
150 5 : SECTION("Unweighted SumOverBatchSize")
151 : {
152 1 : auto scce = ml::SparseCategoricalCrossentropy(ml::LossReduction::SumOverBatchSize);
153 1 : REQUIRE(scce(x, y) == Approx(1.3931886f));
154 : }
155 :
156 5 : SECTION("Unweighted Sum")
157 : {
158 1 : auto scce = ml::SparseCategoricalCrossentropy(ml::LossReduction::Sum);
159 1 : REQUIRE(scce(x, y) == Approx(5.5727544f));
160 : }
161 5 : SECTION("Gradient Sum")
162 : {
163 2 : auto scce = ml::SparseCategoricalCrossentropy(ml::LossReduction::Sum);
164 :
165 2 : Eigen::VectorXf ref_gradient{{0, -1.f / .95f, 0, 0, 0, -10.f, -5.f, 0, 0, 0, -5.f, 0}};
166 2 : auto gradient = scce.getLossGradient(x, y);
167 :
168 13 : for (int i = 0; i < gradient.getSize(); ++i)
169 12 : REQUIRE(gradient[i] == Approx(ref_gradient[i]));
170 : }
171 :
172 5 : SECTION("Gradient SumOverBatchSize")
173 : {
174 2 : auto scce = ml::SparseCategoricalCrossentropy(ml::LossReduction::SumOverBatchSize);
175 :
176 2 : Eigen::VectorXf ref_gradient{
177 : {0, -1.f / 3.8f, 0, 0, 0, -10.f / 4.f, -5.f / 4.f, 0, 0, 0, -5.f / 4.f, 0}};
178 2 : auto gradient = scce.getLossGradient(x, y);
179 :
180 13 : for (int i = 0; i < gradient.getSize(); ++i)
181 12 : REQUIRE(gradient[i] == Approx(ref_gradient[i]));
182 : }
183 4 : }
184 :
185 12 : TEST_CASE_TEMPLATE("MeanSquaredError", TestType, float)
186 : {
187 8 : IndexVector_t dims{{3, 2}};
188 8 : VolumeDescriptor dd(dims);
189 :
190 : // predictions
191 8 : Eigen::VectorX<TestType> data_x{{1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f}};
192 8 : DataContainer<TestType> x(dd, data_x);
193 :
194 : // labels
195 8 : Eigen::VectorX<TestType> data_y{{0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f}};
196 8 : DataContainer<TestType> y(dd, data_y);
197 :
198 5 : SECTION("Unweighted SumOverBatchSize")
199 : {
200 1 : auto mse = ml::MeanSquaredError(ml::LossReduction::SumOverBatchSize);
201 1 : REQUIRE(mse(x, y) == Approx(0.33333334f));
202 : }
203 :
204 5 : SECTION("Unweighted Sum")
205 : {
206 1 : auto mse = ml::MeanSquaredError(ml::LossReduction::Sum);
207 1 : REQUIRE(mse(x, y) == Approx(0.6666667f));
208 : }
209 :
210 5 : SECTION("Gradient Sum")
211 : {
212 2 : auto mse = ml::MeanSquaredError(ml::LossReduction::Sum);
213 2 : Eigen::VectorXf refDerivative{{-2.f / 3.f, 0, 0, -2.f / 3.f, 0, 0}};
214 2 : auto derivative = mse.getLossGradient(x, y);
215 :
216 7 : for (int i = 0; i < derivative.getSize(); ++i)
217 6 : REQUIRE(derivative[i] == Approx(refDerivative[i]));
218 : }
219 :
220 5 : SECTION("Gradient SumOverBatchSize")
221 : {
222 2 : auto mse = ml::MeanSquaredError(ml::LossReduction::SumOverBatchSize);
223 2 : Eigen::VectorXf refDerivative{{-2.f / 6.f, 0, 0, -2.f / 6.f, 0, 0}};
224 2 : auto derivative = mse.getLossGradient(x, y);
225 :
226 7 : for (int i = 0; i < derivative.getSize(); ++i)
227 6 : REQUIRE(derivative[i] == Approx(refDerivative[i]));
228 : }
229 4 : }
230 : TEST_SUITE_END();
|