-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_im2col.cpp
More file actions
313 lines (244 loc) · 10.4 KB
/
test_im2col.cpp
File metadata and controls
313 lines (244 loc) · 10.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
#include "im2col.h"
#include <iostream>
#include <cassert>
#include <cmath>
#define ASSERT_EQ(a, b) assert((a) == (b))
#define ASSERT_NEAR(a, b, eps) assert(std::abs((a) - (b)) < (eps))
void test_im2col_basic() {
std::cout << "Testing im2col basic functionality..." << std::endl;
// Create a simple 4x4 image with 1 channel, batch=1
Tensor<float> input({1, 1, 4, 4});
for (int i = 0; i < 16; ++i) {
input(i) = (float)(i + 1); // 1, 2, 3, ..., 16
}
// Apply im2col with 3x3 kernel, stride=1, no padding
// Expected output: 2x2 spatial positions, each with 9 values (3x3 patch)
auto col = nn::im2col<float>(input, 3, 3, 1, 1, 0, 0);
auto col_size = col.size();
ASSERT_EQ(col_size.cy, 4); // 1 * 2 * 2 = 4 patches
ASSERT_EQ(col_size.cx, 9); // 1 * 3 * 3 = 9 values per patch
// Check first patch (top-left 3x3)
// Should be: 1, 2, 3, 5, 6, 7, 9, 10, 11
ASSERT_NEAR(col.getAt(0, 0), 1.0f, 1e-6f);
ASSERT_NEAR(col.getAt(0, 1), 2.0f, 1e-6f);
ASSERT_NEAR(col.getAt(0, 2), 3.0f, 1e-6f);
ASSERT_NEAR(col.getAt(0, 3), 5.0f, 1e-6f);
ASSERT_NEAR(col.getAt(0, 4), 6.0f, 1e-6f);
ASSERT_NEAR(col.getAt(0, 5), 7.0f, 1e-6f);
ASSERT_NEAR(col.getAt(0, 6), 9.0f, 1e-6f);
ASSERT_NEAR(col.getAt(0, 7), 10.0f, 1e-6f);
ASSERT_NEAR(col.getAt(0, 8), 11.0f, 1e-6f);
// Check second patch (top-right 3x3, shifted by 1)
// Should be: 2, 3, 4, 6, 7, 8, 10, 11, 12
ASSERT_NEAR(col.getAt(1, 0), 2.0f, 1e-6f);
ASSERT_NEAR(col.getAt(1, 1), 3.0f, 1e-6f);
ASSERT_NEAR(col.getAt(1, 2), 4.0f, 1e-6f);
std::cout << " ✓ im2col basic test passed" << std::endl;
}
void test_im2col_with_padding() {
std::cout << "Testing im2col with padding..." << std::endl;
// 2x2 image
Tensor<float> input({1, 1, 2, 2});
input(0, 0, 0, 0) = 1.0f;
input(0, 0, 0, 1) = 2.0f;
input(0, 0, 1, 0) = 3.0f;
input(0, 0, 1, 1) = 4.0f;
// 3x3 kernel with padding=1
// With padding, input becomes:
// [0 0 0 0]
// [0 1 2 0]
// [0 3 4 0]
// [0 0 0 0]
// Output: 2x2 patches
auto col = nn::im2col<float>(input, 3, 3, 1, 1, 1, 1);
auto col_size = col.size();
ASSERT_EQ(col_size.cy, 4); // 1 * 2 * 2 = 4 patches
ASSERT_EQ(col_size.cx, 9); // 3 * 3 = 9
// First patch (top-left, mostly padding)
// Should be: 0, 0, 0, 0, 1, 2, 0, 3, 4
ASSERT_NEAR(col.getAt(0, 0), 0.0f, 1e-6f); // padding
ASSERT_NEAR(col.getAt(0, 4), 1.0f, 1e-6f); // center of patch
ASSERT_NEAR(col.getAt(0, 5), 2.0f, 1e-6f);
ASSERT_NEAR(col.getAt(0, 7), 3.0f, 1e-6f);
ASSERT_NEAR(col.getAt(0, 8), 4.0f, 1e-6f);
std::cout << " ✓ im2col with padding test passed" << std::endl;
}
void test_im2col_with_stride() {
std::cout << "Testing im2col with stride > 1..." << std::endl;
// 4x4 image
Tensor<float> input({1, 1, 4, 4});
for (int i = 0; i < 16; ++i) {
input(i) = (float)(i + 1);
}
// 2x2 kernel with stride=2
// Should extract 4 non-overlapping 2x2 patches
auto col = nn::im2col<float>(input, 2, 2, 2, 2, 0, 0);
auto col_size = col.size();
ASSERT_EQ(col_size.cy, 4); // 1 * 2 * 2 = 4 patches (2x2 grid with stride 2)
ASSERT_EQ(col_size.cx, 4); // 2 * 2 = 4 values per patch
// First patch: top-left 2x2 = [1, 2, 5, 6]
ASSERT_NEAR(col.getAt(0, 0), 1.0f, 1e-6f);
ASSERT_NEAR(col.getAt(0, 1), 2.0f, 1e-6f);
ASSERT_NEAR(col.getAt(0, 2), 5.0f, 1e-6f);
ASSERT_NEAR(col.getAt(0, 3), 6.0f, 1e-6f);
// Second patch: top-right 2x2 = [3, 4, 7, 8]
ASSERT_NEAR(col.getAt(1, 0), 3.0f, 1e-6f);
ASSERT_NEAR(col.getAt(1, 1), 4.0f, 1e-6f);
// Fourth patch: bottom-right 2x2 = [11, 12, 15, 16]
ASSERT_NEAR(col.getAt(3, 0), 11.0f, 1e-6f);
ASSERT_NEAR(col.getAt(3, 1), 12.0f, 1e-6f);
ASSERT_NEAR(col.getAt(3, 2), 15.0f, 1e-6f);
ASSERT_NEAR(col.getAt(3, 3), 16.0f, 1e-6f);
std::cout << " ✓ im2col with stride test passed" << std::endl;
}
void test_im2col_multi_channel() {
std::cout << "Testing im2col with multiple channels..." << std::endl;
// 3x3 image with 2 channels
Tensor<float> input({1, 2, 3, 3});
// Channel 0
for (int i = 0; i < 9; ++i) {
input(0, 0, i / 3, i % 3) = (float)(i + 1);
}
// Channel 1
for (int i = 0; i < 9; ++i) {
input(0, 1, i / 3, i % 3) = (float)((i + 1) * 10);
}
// 2x2 kernel, no stride, no padding
auto col = nn::im2col<float>(input, 2, 2, 1, 1, 0, 0);
auto col_size = col.size();
ASSERT_EQ(col_size.cy, 4); // 1 * 2 * 2 = 4 patches
ASSERT_EQ(col_size.cx, 8); // 2 channels * 2 * 2 = 8
// First patch, channel 0: [1, 2, 4, 5]
ASSERT_NEAR(col.getAt(0, 0), 1.0f, 1e-6f);
ASSERT_NEAR(col.getAt(0, 1), 2.0f, 1e-6f);
ASSERT_NEAR(col.getAt(0, 2), 4.0f, 1e-6f);
ASSERT_NEAR(col.getAt(0, 3), 5.0f, 1e-6f);
// First patch, channel 1: [10, 20, 40, 50]
ASSERT_NEAR(col.getAt(0, 4), 10.0f, 1e-6f);
ASSERT_NEAR(col.getAt(0, 5), 20.0f, 1e-6f);
ASSERT_NEAR(col.getAt(0, 6), 40.0f, 1e-6f);
ASSERT_NEAR(col.getAt(0, 7), 50.0f, 1e-6f);
std::cout << " ✓ im2col multi-channel test passed" << std::endl;
}
void test_col2im_basic() {
std::cout << "Testing col2im basic functionality..." << std::endl;
// Create column matrix (2x2 patches with 2x2 values each)
ml::Mat<float> col(4, 4);
// Patch 0: all 1s
for (int j = 0; j < 4; ++j) col.setAt(0, j, 1.0f);
// Patch 1: all 2s
for (int j = 0; j < 4; ++j) col.setAt(1, j, 2.0f);
// Patch 2: all 3s
for (int j = 0; j < 4; ++j) col.setAt(2, j, 3.0f);
// Patch 3: all 4s
for (int j = 0; j < 4; ++j) col.setAt(3, j, 4.0f);
// Convert back to image (4x4 with stride=2, no overlap)
auto output = nn::col2im<float>(col, 1, 1, 4, 4, 2, 2, 2, 2, 0, 0);
ASSERT_EQ(output.ndim(), 4);
ASSERT_EQ(output.shape(0), 1); // batch
ASSERT_EQ(output.shape(1), 1); // channels
ASSERT_EQ(output.shape(2), 4); // height
ASSERT_EQ(output.shape(3), 4); // width
// With non-overlapping patches, each position gets value from one patch
ASSERT_NEAR(output(0, 0, 0, 0), 1.0f, 1e-6f);
ASSERT_NEAR(output(0, 0, 0, 1), 1.0f, 1e-6f);
ASSERT_NEAR(output(0, 0, 0, 2), 2.0f, 1e-6f);
ASSERT_NEAR(output(0, 0, 0, 3), 2.0f, 1e-6f);
ASSERT_NEAR(output(0, 0, 2, 0), 3.0f, 1e-6f);
ASSERT_NEAR(output(0, 0, 3, 3), 4.0f, 1e-6f);
std::cout << " ✓ col2im basic test passed" << std::endl;
}
void test_im2col_col2im_roundtrip() {
std::cout << "Testing im2col → col2im round-trip..." << std::endl;
// Create original image
Tensor<float> input({1, 1, 4, 4});
for (int i = 0; i < 16; ++i) {
input(i) = (float)(i + 1);
}
// im2col with non-overlapping patches (stride = kernel size)
auto col = nn::im2col<float>(input, 2, 2, 2, 2, 0, 0);
// col2im back to image
auto output = nn::col2im<float>(col, 1, 1, 4, 4, 2, 2, 2, 2, 0, 0);
// Should match original
for (size_t i = 0; i < input.size(); ++i) {
ASSERT_NEAR(input(i), output(i), 1e-6f);
}
std::cout << " ✓ im2col/col2im round-trip test passed" << std::endl;
}
void test_im2col_col2im_with_overlap() {
std::cout << "Testing im2col → col2im with overlapping patches..." << std::endl;
// Create image
Tensor<float> input({1, 1, 3, 3}, 1.0f); // All ones
// im2col with overlapping patches (2x2 kernel, stride=1)
auto col = nn::im2col<float>(input, 2, 2, 1, 1, 0, 0);
// col2im back - values will accumulate where patches overlap
auto output = nn::col2im<float>(col, 1, 1, 3, 3, 2, 2, 1, 1, 0, 0);
// Corner pixels appear in 1 patch each
ASSERT_NEAR(output(0, 0, 0, 0), 1.0f, 1e-6f);
ASSERT_NEAR(output(0, 0, 0, 2), 1.0f, 1e-6f);
ASSERT_NEAR(output(0, 0, 2, 0), 1.0f, 1e-6f);
ASSERT_NEAR(output(0, 0, 2, 2), 1.0f, 1e-6f);
// Edge pixels appear in 2 patches each
ASSERT_NEAR(output(0, 0, 0, 1), 2.0f, 1e-6f);
ASSERT_NEAR(output(0, 0, 1, 0), 2.0f, 1e-6f);
// Center pixel appears in 4 patches
ASSERT_NEAR(output(0, 0, 1, 1), 4.0f, 1e-6f);
std::cout << " ✓ im2col/col2im with overlap test passed" << std::endl;
}
void test_output_dims_calculation() {
std::cout << "Testing output dimensions calculation..." << std::endl;
int out_h, out_w;
// Basic case: 28x28 input, 5x5 kernel, stride 1, no padding
nn::im2col_get_output_dims<float>(28, 28, 5, 5, 1, 1, 0, 0, out_h, out_w);
ASSERT_EQ(out_h, 24);
ASSERT_EQ(out_w, 24);
// With padding: 28x28 input, 5x5 kernel, stride 1, padding 2
nn::im2col_get_output_dims<float>(28, 28, 5, 5, 1, 1, 2, 2, out_h, out_w);
ASSERT_EQ(out_h, 28); // Same as input (padding preserves size)
ASSERT_EQ(out_w, 28);
// With stride: 28x28 input, 3x3 kernel, stride 2, padding 1
nn::im2col_get_output_dims<float>(28, 28, 3, 3, 2, 2, 1, 1, out_h, out_w);
ASSERT_EQ(out_h, 14);
ASSERT_EQ(out_w, 14);
std::cout << " ✓ Output dimensions calculation test passed" << std::endl;
}
void test_batch_processing() {
std::cout << "Testing im2col with batch > 1..." << std::endl;
// Create batch of 2 images
Tensor<float> input({2, 1, 3, 3});
// Batch 0: values 1-9
for (int i = 0; i < 9; ++i) {
input(0, 0, i / 3, i % 3) = (float)(i + 1);
}
// Batch 1: values 10-18
for (int i = 0; i < 9; ++i) {
input(1, 0, i / 3, i % 3) = (float)(i + 10);
}
// 2x2 kernel, stride 1
auto col = nn::im2col<float>(input, 2, 2, 1, 1, 0, 0);
auto col_size = col.size();
ASSERT_EQ(col_size.cy, 8); // 2 batches * 2 * 2 = 8 patches
ASSERT_EQ(col_size.cx, 4); // 2 * 2 = 4 values per patch
// First patch of batch 0: [1, 2, 4, 5]
ASSERT_NEAR(col.getAt(0, 0), 1.0f, 1e-6f);
ASSERT_NEAR(col.getAt(0, 3), 5.0f, 1e-6f);
// First patch of batch 1 (5th overall patch): [10, 11, 13, 14]
ASSERT_NEAR(col.getAt(4, 0), 10.0f, 1e-6f);
ASSERT_NEAR(col.getAt(4, 3), 14.0f, 1e-6f);
std::cout << " ✓ Batch processing test passed" << std::endl;
}
int main() {
std::cout << "\n=== im2col/col2im Test Suite ===" << std::endl;
test_im2col_basic();
test_im2col_with_padding();
test_im2col_with_stride();
test_im2col_multi_channel();
test_col2im_basic();
test_im2col_col2im_roundtrip();
test_im2col_col2im_with_overlap();
test_output_dims_calculation();
test_batch_processing();
std::cout << "\n✓ All im2col/col2im tests passed!" << std::endl;
std::cout << "\nim2col/col2im is working correctly and ready for CNN implementation." << std::endl;
return 0;
}