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