1 #!/usr/bin/env dub
2 /+
3 dub.json:
4 {
5     "name": "mnist",
6     "dependencies": {
7         "dopt": {
8             "path": "../"
9         }
10     }
11 }
12 +/
13 module mnist;
14 
15 import std.algorithm;
16 import std.array;
17 import std.file;
18 import std.range;
19 import std.typecons;
20 
21 auto loadMNIST(string path)
22 {
23 	auto trainFeatures = loadFeatures!float(path ~ "/train-images-idx3-ubyte");
24 	auto trainLabels = loadLabels!float(path ~ "/train-labels-idx1-ubyte");
25 	auto testFeatures = loadFeatures!float(path ~ "/t10k-images-idx3-ubyte");
26 	auto testLabels = loadLabels!float(path ~ "/t10k-labels-idx1-ubyte");
27 
28 	return tuple!("trainFeatures", "testFeatures", "trainLabels", "testLabels")
29 					(trainFeatures, testFeatures, trainLabels, testLabels);
30 }
31 
32 T[][] loadFeatures(T)(string filename)
33 {
34 	const size_t numFeatures = 28 * 28;
35 
36 	//Load the data from disk
37 	ubyte[] raw = cast(ubyte[])read(filename);
38 
39 	//Skip over the header
40 	raw = raw[16 .. $];
41 
42 	//Get the number of instances in this file
43 	size_t numInstances = raw.length / numFeatures;
44 
45 	//Allocate space to store the references to each instance
46 	T[][] result = new T[][numInstances];
47 
48 	//Convert the ubytes to floats
49 	T[] features = raw.map!(x => cast(T)x / cast(T)255.0).array();
50 
51 	//Iterate over each instance and set the references to the correct slice
52 	for(size_t i = 0; i < numInstances; i++)
53 	{
54 		result[i] = features[i * numFeatures .. (i + 1) * numFeatures];
55 	}
56 
57 	return result;
58 }
59 
60 T[][] loadLabels(T)(string filename)
61 {
62 	const size_t numLabels = 10;
63 
64 	//Load the data from disk
65 	ubyte[] raw = cast(ubyte[])read(filename);
66 
67 	//Skip over the header
68 	raw = raw[8 .. $];
69 
70 	//Get the number of instances in this file
71 	size_t numInstances = raw.length;
72 
73 	//Allocate space to store the references to each instance
74 	T[][] result = new T[][numInstances];
75 	T[] labels = new T[numInstances * numLabels];
76 	labels[] = 0.0;
77 
78 	//Create the one-hot encoding array and set up references to the appropriate slices for each instance
79 	for(size_t i = 0; i < numInstances; i++)
80 	{
81 		result[i] = labels[i * numLabels .. (i + 1) * numLabels];
82 		result[i][raw[i]] = 1.0;
83 	}
84 
85 	return result;
86 }
87 
88 void main(string[] args)
89 {
90 	import std.stdio : writeln;
91 	
92 	import dopt.core;
93 	import dopt.nnet;
94 	import dopt.online;
95 
96     auto data = loadMNIST(args[1]);
97 
98     auto features = float32([100, 1, 28, 28]);
99 	auto labels = float32([100, 10]);
100 
101 	auto preds = dataSource(features)
102 				.conv2D(32, [5, 5])
103 				.relu()
104 				.maxPool([2, 2])
105 				.conv2D(32, [5, 5])
106 				.relu()
107 				.maxPool([2, 2])
108 				.dense(10)
109 				.softmax();
110 
111 	auto network = new DAGNetwork([features], [preds]);
112 
113 	auto lossSym = crossEntropy(preds.trainOutput, labels) + network.paramLoss;
114 
115 	auto updater = adam([lossSym], network.params, null);
116 
117 	foreach(e; 0 .. 10)
118 	{
119 		float totloss = 0;
120 		float tot = 0;
121 
122 		foreach(fs, ls; zip(data.trainFeatures.chunks(100), data.trainLabels.chunks(100)))
123 		{
124 			auto loss = updater([
125 				features: Buffer(fs.joiner().array()),
126 				labels: Buffer(ls.joiner().array())
127 			]);
128 
129 			totloss += loss[0].as!float[0];
130 			tot++;
131 		}
132 
133 		writeln(e, ": ", totloss / tot);
134 	}
135 
136 	int correct;
137 	int total;
138 
139 	import std.stdio : writeln;
140 
141 	foreach(fs, ls; zip(data.testFeatures.chunks(100), data.testLabels.chunks(100)))
142 	{
143 		auto pred = network.outputs[0].evaluate([
144 			features: Buffer(fs.joiner().array())
145 		]).as!float;
146 
147 		foreach(p, t; zip(pred.chunks(10), ls))
148 		{
149 			if(p.maxIndex == t.maxIndex)
150 			{
151 				correct++;
152 			}
153 
154 			total++;
155 		}
156 	}
157 
158 	writeln(correct / cast(float)total);
159 }