1 /** 2 * A Library for AROW Linear Classification 3 * 4 * Authors: Kazuya Gokita 5 */ 6 7 module arow; 8 9 import std.math; 10 import std.random; 11 12 /*** 13 * Adaptive Regularization of Weight Vectors 14 * 15 * See_Also: 16 * K. Crammer, A. Kulesza, and M. Dredze. "Adaptive regularization of weight vectors" NIPS 2009 17 */ 18 class Arow { 19 private: 20 size_t dimension; // Size of feature vector 21 double[] mean; // Average vector 22 double[] cov; // Variance matrix (diagonal) 23 24 double r; // Hyper parameter ( r > 0 ) 25 26 invariant() { 27 assert(mean != null); 28 assert(cov != null); 29 assert(mean.length == dimension); 30 assert(cov.length == dimension); 31 assert(r > 0); 32 } 33 34 35 /** 36 * Calculate the distance between a vector and the hyperplane 37 * Params: 38 * f = feature 39 * 40 * Returns: Margin(Euclidean distance) 41 */ 42 double getMargin(in double[int] f) @trusted 43 in { 44 assert(f != null); 45 } 46 out(margin) { 47 assert(margin != double.nan); 48 assert(margin != double.nan && margin != -double.infinity); 49 } 50 body { 51 double margin = 0.0; 52 foreach(index; f.keys) { 53 margin += mean[index] * f[index]; 54 } 55 56 return margin; 57 } 58 59 60 /** 61 * Calculate confidence 62 * Params: 63 * f = feature 64 * 65 * Returns: confidence 66 */ 67 double getConfidence(in double[int] f) @trusted 68 in { 69 assert(f != null); 70 } 71 out(confidence) { 72 assert(confidence != double.nan); 73 assert(confidence != double.nan && confidence != -double.infinity); 74 } 75 body { 76 double confidence = 0.0; 77 foreach(index; f.keys) { 78 confidence += cov[index] * f[index] * f[index]; 79 } 80 81 return confidence; 82 } 83 84 85 public: 86 this(size_t num_features, double param = 0.1) { 87 dimension = num_features; 88 mean = new double[dimension]; 89 cov = new double[dimension]; 90 91 mean[] = 0.0; 92 cov[] = 1.0; 93 r = param; 94 } 95 96 97 this(size_t size, double[] mean, double[] cov, double hyperparameter) { 98 this.dimension = size; 99 this.mean = mean; 100 this.cov = cov; 101 this.r = hyperparameter; 102 } 103 104 105 @property { 106 auto dim() { return dimension; } 107 } 108 109 110 @property { 111 auto param() { return r; } 112 auto param(double val) { return r = val; } 113 } 114 115 116 /** 117 * Update weight vector 118 * Params: 119 * fv = feature 120 * label = class label (+1 / -1) 121 * 122 * Returns: loss (0 / 1) 123 */ 124 int update(in double[int] f, int label) @trusted 125 in { 126 assert(label == -1 || label == 1); 127 assert(f != null); 128 } 129 out(loss) { 130 assert(loss == 0 || loss == 1); 131 } 132 body { 133 immutable margin = getMargin(f); 134 if (margin * label >= 1) return 0; 135 136 immutable confidence = getConfidence(f); 137 immutable beta = 1.0 / (confidence + r); 138 immutable alpha = (1.0 - label * margin) * beta; 139 140 // Update mean 141 foreach(index; f.keys) { 142 mean[index] += alpha * cov[index] * label * f[index]; 143 } 144 145 // Update covariance 146 foreach(index; f.keys) { 147 cov[index] = 1.0 / ((1.0 / cov[index]) + f[index] * f[index] / r); 148 } 149 150 return margin * label < 0 ? 1 : 0; 151 } 152 153 154 /** 155 * Predict 156 * Params: 157 * f = feature vector 158 * Returns: class label (-1, 1) 159 */ 160 int predict(in double[int] f) @trusted 161 in { 162 assert(f != null); 163 } 164 out(label) { 165 assert(label == -1 || label == 1); 166 } 167 body { 168 double m = getMargin(f); 169 return m > 0 ? 1 : -1; 170 } 171 172 173 /** 174 * Write AROW data to the specified file 175 * Params: 176 * filename = output path 177 */ 178 void saveArow(in immutable string filename) 179 in { 180 assert(filename != null); 181 } 182 body 183 { 184 import std.stream; 185 auto buffer = new BufferedFile(); 186 buffer.create(filename); 187 buffer.write(dimension); 188 buffer.write(r); 189 foreach (m; mean) buffer.write(m); 190 foreach (c; cov) buffer.write(c); 191 buffer.close(); 192 } 193 194 195 /** 196 * Load AROW data from the specified file 197 * Params: 198 * filename = output path 199 * 200 * Returns: Arow instance 201 */ 202 Arow loadArowFile(in string filename) 203 { 204 import std.stream; 205 Stream file = new BufferedFile(filename); 206 scope(exit) file.close(); 207 208 size_t size; 209 file.read(size); 210 211 double hyperparameter; 212 file.read(hyperparameter); 213 214 double[] mean = new double[size]; 215 for (size_t i = 0; i < size; i++) file.read(mean[i]); 216 217 double[] cov = new double[size]; 218 for (size_t i = 0; i < size; i++) file.read(cov[i]); 219 220 return new Arow(size, mean, cov, hyperparameter); 221 } 222 223 224 Arow opBinary(string op)(Arow b) { 225 static if(op != "+") static assert(0, "Operator " ~ op ~ " not implemented"); 226 227 if(this.dimension != b.dimension) { 228 throw new Exception("Vectors must be the same length"); 229 } 230 231 auto arow = new Arow(dimension, r); 232 233 for (size_t i = 0; i < dimension; i++) { 234 arow.mean[i] = (mean[i] + b.mean[i]) / 2; 235 arow.cov[i] = (cov[i] + b.cov[i]) / 2; 236 } 237 238 return arow; 239 } 240 241 unittest { 242 double[] a_m = [1, 2, 3], a_c = [4, 5, 6]; 243 double[] b_m = [7, 8, 9], b_c = [10, 11, 12]; 244 245 Arow a = new Arow(3, a_m, a_c, 1); 246 Arow b = new Arow(3, b_m, b_c, 1); 247 248 Arow c = a + b; 249 assert(c.mean[0] == 4); 250 assert(c.mean[1] == 5); 251 assert(c.mean[2] == 6); 252 } 253 254 255 Arow opOpAssign(string op)(Arow b) { 256 static if(op != "+") static assert(0, "Operator " ~ op ~ " not implemented"); 257 258 if(this.dimension != b.dimension) { 259 throw new Exception("Vectors must be the same length"); 260 } 261 262 for (size_t i = 0; i < dimension; i++) { 263 mean[i] = (mean[i] + b.mean[i]) / 2; 264 cov[i] = (cov[i] + b.cov[i]) / 2; 265 } 266 267 return this; 268 } 269 270 unittest { 271 double[] a_m = [1, 2, 3], a_c = [4, 5, 6]; 272 double[] b_m = [7, 8, 9], b_c = [10, 11, 12]; 273 274 Arow a = new Arow(3, a_m, a_c, 1); 275 Arow b = new Arow(3, b_m, b_c, 1); 276 277 a += b; 278 assert(a.mean[0] == 4); 279 assert(a.mean[1] == 5); 280 assert(a.mean[2] == 6); 281 } 282 } 283