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 }