├── .gitignore
├── README.md
├── gen_feat_weight.py
├── sample-usps.ipynb
└── stream_fast_weight.py
/.gitignore:
--------------------------------------------------------------------------------
1 | .*.swp
2 | .DS_Store
3 | *.pyc
4 | .ipynb_checkpoints/
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Unsupervised Feature Selection on Data Streams
2 | ===
3 |
4 | My implementation of the algorithms described in:
5 |
6 | - **[Huang, et al. 2015]**
H. Huang, et al., "Unsupervised Feature Selection on Data Streams," Proc. of CIKM 2015, pp. 1031-1040 (Oct. 2015).
7 |
8 | *sample-usps.ipynb* includes the result of experiments for the USPS handwritten dataset.
9 |
--------------------------------------------------------------------------------
/gen_feat_weight.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | """
4 | Implementation of algorithms proposed by:
5 |
6 | H. Huang, et al., "Unsupervised Feature Selection on Data Streams," Proc. of CIKM 2015, pp. 1031-1040 (Oct. 2015).
7 | """
8 |
9 | import numpy as np
10 | import numpy.linalg as ln
11 |
12 | class GenFeatWeight:
13 | """
14 | Alg. 1: Prototype algorithm for feature weighting (p=2)
15 | """
16 |
17 | def __init__(self, m, k):
18 | """
19 | :param m: number of original features
20 | :param k: number of singular vectors (this can be the same as the number of clusters in the dataset)
21 | """
22 |
23 | self.m = m
24 | self.k = k
25 |
26 | def update(self, Yt):
27 | """
28 | Update the weights based on new inputs at time t,
29 | :param Yt: m-by-n_t input matrix from data stream
30 | """
31 |
32 | if hasattr(self, 'Y'):
33 | self.Y = np.hstack((self.Y, Yt))
34 | else:
35 | # for Y0, we need to first initialize Y
36 | self.Y = Yt
37 |
38 | U, s, V = ln.svd(self.Y, full_matrices=False)
39 |
40 | # According to Section 5.1, for all experiments,
41 | # the authors set alpha = 2^3 * sigma_k based on the pre-experiment
42 | alpha = (2 ** 3) * s[self.k-1]
43 |
44 | # solve the ridge regression by using the top-k singular values
45 | # X: m-by-k matrix (k <= ell)
46 | D = np.diag(s[:self.k] / (s[:self.k] ** 2 + alpha))
47 | X = np.dot(U[:, :self.k], D)
48 |
49 | return np.amax(abs(X), axis=1)
50 |
--------------------------------------------------------------------------------
/sample-usps.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Unsupervised Feature Selection for the USPS handwritten Dataset\n",
8 | "\n",
9 | "H. Huang, et al., \"**Unsupervised Feature Selection on Data Streams**,\" Proc. of CIKM 2015, pp. 1031-1040, Oct. 2015."
10 | ]
11 | },
12 | {
13 | "cell_type": "code",
14 | "execution_count": 1,
15 | "metadata": {
16 | "collapsed": true
17 | },
18 | "outputs": [],
19 | "source": [
20 | "import numpy as np\n",
21 | "import numpy.linalg as ln\n",
22 | "import pandas as pd\n",
23 | "import sklearn.metrics as met\n",
24 | "import time\n",
25 | "\n",
26 | "%matplotlib inline\n",
27 | "import matplotlib.pyplot as plt"
28 | ]
29 | },
30 | {
31 | "cell_type": "code",
32 | "execution_count": 2,
33 | "metadata": {
34 | "collapsed": false
35 | },
36 | "outputs": [],
37 | "source": [
38 | "%load_ext autoreload\n",
39 | "%autoreload 2\n",
40 | "\n",
41 | "from stream_fast_weight import StreamFastWeight\n",
42 | "from gen_feat_weight import GenFeatWeight"
43 | ]
44 | },
45 | {
46 | "cell_type": "markdown",
47 | "metadata": {},
48 | "source": [
49 | "## Load data"
50 | ]
51 | },
52 | {
53 | "cell_type": "code",
54 | "execution_count": 3,
55 | "metadata": {
56 | "collapsed": false
57 | },
58 | "outputs": [
59 | {
60 | "data": {
61 | "text/plain": [
62 | "((256, 7291), (7291,))"
63 | ]
64 | },
65 | "execution_count": 3,
66 | "metadata": {},
67 | "output_type": "execute_result"
68 | }
69 | ],
70 | "source": [
71 | "# train\n",
72 | "with open('../../data/usps/zip.train') as f:\n",
73 | " X_train = []\n",
74 | " y_train = []\n",
75 | " for line in f.readlines():\n",
76 | " l = map(float, line.rstrip().split(' '))\n",
77 | " y_train.append(l[0])\n",
78 | " X_train.append(l[1:])\n",
79 | " X_train = np.array(X_train).T\n",
80 | " y_train = np.array(y_train).T\n",
81 | "X_train.shape, y_train.shape"
82 | ]
83 | },
84 | {
85 | "cell_type": "code",
86 | "execution_count": 4,
87 | "metadata": {
88 | "collapsed": false
89 | },
90 | "outputs": [
91 | {
92 | "data": {
93 | "text/plain": [
94 | "((256, 2007), (2007,))"
95 | ]
96 | },
97 | "execution_count": 4,
98 | "metadata": {},
99 | "output_type": "execute_result"
100 | }
101 | ],
102 | "source": [
103 | "# test\n",
104 | "with open('../../data/usps/zip.test') as f:\n",
105 | " X_test = []\n",
106 | " y_test = []\n",
107 | " for line in f.readlines():\n",
108 | " l = map(float, line.rstrip().split(' '))\n",
109 | " y_test.append(l[0])\n",
110 | " X_test.append(l[1:])\n",
111 | " X_test = np.array(X_test).T\n",
112 | " y_test = np.array(y_test).T\n",
113 | "X_test.shape, y_test.shape"
114 | ]
115 | },
116 | {
117 | "cell_type": "code",
118 | "execution_count": 5,
119 | "metadata": {
120 | "collapsed": false
121 | },
122 | "outputs": [
123 | {
124 | "data": {
125 | "text/plain": [
126 | "((256, 9298), (9298,))"
127 | ]
128 | },
129 | "execution_count": 5,
130 | "metadata": {},
131 | "output_type": "execute_result"
132 | }
133 | ],
134 | "source": [
135 | "# merge train and test\n",
136 | "X = np.hstack((X_train, X_test))\n",
137 | "y = np.hstack((y_train, y_test))\n",
138 | "X.shape, y.shape"
139 | ]
140 | },
141 | {
142 | "cell_type": "markdown",
143 | "metadata": {},
144 | "source": [
145 | "## Import libraries for evaluation and check the baseline"
146 | ]
147 | },
148 | {
149 | "cell_type": "code",
150 | "execution_count": 6,
151 | "metadata": {
152 | "collapsed": true
153 | },
154 | "outputs": [],
155 | "source": [
156 | "from sklearn.cluster import KMeans\n",
157 | "from sklearn.metrics.cluster import normalized_mutual_info_score"
158 | ]
159 | },
160 | {
161 | "cell_type": "code",
162 | "execution_count": 7,
163 | "metadata": {
164 | "collapsed": false
165 | },
166 | "outputs": [
167 | {
168 | "data": {
169 | "text/plain": [
170 | "0.62508382431293497"
171 | ]
172 | },
173 | "execution_count": 7,
174 | "metadata": {},
175 | "output_type": "execute_result"
176 | }
177 | ],
178 | "source": [
179 | "# baseline\n",
180 | "y_pred = KMeans(n_clusters=10).fit_predict(X.T) # shape=(n_sample, n_feature)\n",
181 | "normalized_mutual_info_score(y, y_pred)"
182 | ]
183 | },
184 | {
185 | "cell_type": "markdown",
186 | "metadata": {},
187 | "source": [
188 | "## Run prototype algorithm for feature weighting (Alg. 1)\n",
189 | "\n",
190 | "*this is not sketching method; inputed matrices are stacked internaly.*"
191 | ]
192 | },
193 | {
194 | "cell_type": "code",
195 | "execution_count": 8,
196 | "metadata": {
197 | "collapsed": true
198 | },
199 | "outputs": [],
200 | "source": [
201 | "nt = 1000\n",
202 | "alg1 = GenFeatWeight(X.shape[0], k=10) # initialize"
203 | ]
204 | },
205 | {
206 | "cell_type": "code",
207 | "execution_count": 9,
208 | "metadata": {
209 | "collapsed": false
210 | },
211 | "outputs": [
212 | {
213 | "data": {
214 | "text/html": [
215 | "
\n",
216 | "
\n",
217 | " \n",
218 | " \n",
219 | " | \n",
220 | " n_features | \n",
221 | " NMI | \n",
222 | " time_sec | \n",
223 | "
\n",
224 | " \n",
225 | " \n",
226 | " \n",
227 | " 0 | \n",
228 | " 25 | \n",
229 | " 0.405653 | \n",
230 | " 9.173374 | \n",
231 | "
\n",
232 | " \n",
233 | " 1 | \n",
234 | " 50 | \n",
235 | " 0.520081 | \n",
236 | " 24.586265 | \n",
237 | "
\n",
238 | " \n",
239 | " 2 | \n",
240 | " 75 | \n",
241 | " 0.551276 | \n",
242 | " 39.595395 | \n",
243 | "
\n",
244 | " \n",
245 | " 3 | \n",
246 | " 100 | \n",
247 | " 0.569203 | \n",
248 | " 54.583825 | \n",
249 | "
\n",
250 | " \n",
251 | " 4 | \n",
252 | " 125 | \n",
253 | " 0.603090 | \n",
254 | " 70.261307 | \n",
255 | "
\n",
256 | " \n",
257 | " 5 | \n",
258 | " 150 | \n",
259 | " 0.615642 | \n",
260 | " 85.809544 | \n",
261 | "
\n",
262 | " \n",
263 | " 6 | \n",
264 | " 175 | \n",
265 | " 0.632129 | \n",
266 | " 101.707525 | \n",
267 | "
\n",
268 | " \n",
269 | " 7 | \n",
270 | " 200 | \n",
271 | " 0.626509 | \n",
272 | " 116.845065 | \n",
273 | "
\n",
274 | " \n",
275 | "
\n",
276 | "
"
277 | ],
278 | "text/plain": [
279 | " n_features NMI time_sec\n",
280 | "0 25 0.405653 9.173374\n",
281 | "1 50 0.520081 24.586265\n",
282 | "2 75 0.551276 39.595395\n",
283 | "3 100 0.569203 54.583825\n",
284 | "4 125 0.603090 70.261307\n",
285 | "5 150 0.615642 85.809544\n",
286 | "6 175 0.632129 101.707525\n",
287 | "7 200 0.626509 116.845065"
288 | ]
289 | },
290 | "execution_count": 9,
291 | "metadata": {},
292 | "output_type": "execute_result"
293 | }
294 | ],
295 | "source": [
296 | "result = []\n",
297 | "\n",
298 | "# compute NMIs for different selected feature size\n",
299 | "for n_features in range(25, 201, 25):\n",
300 | " \n",
301 | " start = time.clock()\n",
302 | " for head in xrange(0, X.shape[1], nt): # each time step\n",
303 | " scores = alg1.update(X[:, head:head+nt])\n",
304 | " end = time.clock()\n",
305 | " \n",
306 | " # selected top-n features at the final step\n",
307 | " selected_idx = np.argsort(scores)[::-1][:n_features]\n",
308 | " \n",
309 | " y_pred = KMeans(n_clusters=10).fit_predict(X[selected_idx,:].T)\n",
310 | " NMI = normalized_mutual_info_score(y, y_pred)\n",
311 | " result.append([n_features, NMI, end-start])\n",
312 | "\n",
313 | "alg1_result = pd.DataFrame(result, columns=['n_features', 'NMI', 'time_sec'])\n",
314 | "alg1_result"
315 | ]
316 | },
317 | {
318 | "cell_type": "markdown",
319 | "metadata": {},
320 | "source": [
321 | "## Run fast streaming algorithm for feature weighting (Alg. 2)"
322 | ]
323 | },
324 | {
325 | "cell_type": "code",
326 | "execution_count": 10,
327 | "metadata": {
328 | "collapsed": false
329 | },
330 | "outputs": [],
331 | "source": [
332 | "nt = 1000\n",
333 | "alg2 = StreamFastWeight(X.shape[0], k=10)"
334 | ]
335 | },
336 | {
337 | "cell_type": "code",
338 | "execution_count": 11,
339 | "metadata": {
340 | "collapsed": false
341 | },
342 | "outputs": [
343 | {
344 | "data": {
345 | "text/html": [
346 | "\n",
347 | "
\n",
348 | " \n",
349 | " \n",
350 | " | \n",
351 | " n_features | \n",
352 | " NMI | \n",
353 | " time_sec | \n",
354 | "
\n",
355 | " \n",
356 | " \n",
357 | " \n",
358 | " 0 | \n",
359 | " 25 | \n",
360 | " 0.409419 | \n",
361 | " 1.037534 | \n",
362 | "
\n",
363 | " \n",
364 | " 1 | \n",
365 | " 50 | \n",
366 | " 0.528266 | \n",
367 | " 1.036338 | \n",
368 | "
\n",
369 | " \n",
370 | " 2 | \n",
371 | " 75 | \n",
372 | " 0.542419 | \n",
373 | " 1.035008 | \n",
374 | "
\n",
375 | " \n",
376 | " 3 | \n",
377 | " 100 | \n",
378 | " 0.585669 | \n",
379 | " 1.036351 | \n",
380 | "
\n",
381 | " \n",
382 | " 4 | \n",
383 | " 125 | \n",
384 | " 0.596422 | \n",
385 | " 1.033633 | \n",
386 | "
\n",
387 | " \n",
388 | " 5 | \n",
389 | " 150 | \n",
390 | " 0.615835 | \n",
391 | " 1.023445 | \n",
392 | "
\n",
393 | " \n",
394 | " 6 | \n",
395 | " 175 | \n",
396 | " 0.629191 | \n",
397 | " 1.029916 | \n",
398 | "
\n",
399 | " \n",
400 | " 7 | \n",
401 | " 200 | \n",
402 | " 0.622998 | \n",
403 | " 1.021629 | \n",
404 | "
\n",
405 | " \n",
406 | "
\n",
407 | "
"
408 | ],
409 | "text/plain": [
410 | " n_features NMI time_sec\n",
411 | "0 25 0.409419 1.037534\n",
412 | "1 50 0.528266 1.036338\n",
413 | "2 75 0.542419 1.035008\n",
414 | "3 100 0.585669 1.036351\n",
415 | "4 125 0.596422 1.033633\n",
416 | "5 150 0.615835 1.023445\n",
417 | "6 175 0.629191 1.029916\n",
418 | "7 200 0.622998 1.021629"
419 | ]
420 | },
421 | "execution_count": 11,
422 | "metadata": {},
423 | "output_type": "execute_result"
424 | }
425 | ],
426 | "source": [
427 | "result = []\n",
428 | "\n",
429 | "# compute NMIs for different selected feature size\n",
430 | "for n_features in range(25, 201, 25):\n",
431 | " \n",
432 | " start = time.clock()\n",
433 | " for head in xrange(0, X.shape[1], nt): # each time step\n",
434 | " scores = alg2.update(X[:, head:head+nt])\n",
435 | " end = time.clock()\n",
436 | " \n",
437 | " # selected top-n features at the final step\n",
438 | " selected_idx = np.argsort(scores)[::-1][:n_features]\n",
439 | " \n",
440 | " y_pred = KMeans(n_clusters=10).fit_predict(X[selected_idx,:].T)\n",
441 | " NMI = normalized_mutual_info_score(y, y_pred)\n",
442 | " result.append([n_features, NMI, end-start])\n",
443 | "\n",
444 | "alg2_result = pd.DataFrame(result, columns=['n_features', 'NMI', 'time_sec'])\n",
445 | "alg2_result"
446 | ]
447 | },
448 | {
449 | "cell_type": "markdown",
450 | "metadata": {},
451 | "source": [
452 | "## Plot the result"
453 | ]
454 | },
455 | {
456 | "cell_type": "code",
457 | "execution_count": 12,
458 | "metadata": {
459 | "collapsed": false
460 | },
461 | "outputs": [
462 | {
463 | "data": {
464 | "image/png": "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\nH4134XIk4BBXAAAAAElFTkSuQmCC\n",
465 | "text/plain": [
466 | ""
467 | ]
468 | },
469 | "metadata": {},
470 | "output_type": "display_data"
471 | }
472 | ],
473 | "source": [
474 | "df = pd.DataFrame(np.vstack((np.array(alg1_result['NMI']), np.array(alg2_result['NMI']))).T, columns=['Alg. 1 (exact)', 'Alg. 2 (stream)'])\n",
475 | "ax = df.plot()\n",
476 | "ax.grid()\n",
477 | "ax.set_xlabel('Number of selected features')\n",
478 | "ax.set_ylabel('NMI')\n",
479 | "ax.set_xticklabels(range(25, 201, 25));"
480 | ]
481 | },
482 | {
483 | "cell_type": "code",
484 | "execution_count": 13,
485 | "metadata": {
486 | "collapsed": false
487 | },
488 | "outputs": [
489 | {
490 | "data": {
491 | "image/png": "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\n",
492 | "text/plain": [
493 | ""
494 | ]
495 | },
496 | "metadata": {},
497 | "output_type": "display_data"
498 | }
499 | ],
500 | "source": [
501 | "df = pd.DataFrame(np.vstack((np.array(alg1_result['time_sec']), np.array(alg2_result['time_sec']))).T, columns=['Alg. 1 (exact)', 'Alg. 2 (stream)'])\n",
502 | "ax = df.plot()\n",
503 | "ax.grid()\n",
504 | "ax.set_xlabel('Number of selected features')\n",
505 | "ax.set_ylabel('NMI')\n",
506 | "ax.set_xticklabels(range(25, 201, 25));"
507 | ]
508 | },
509 | {
510 | "cell_type": "code",
511 | "execution_count": null,
512 | "metadata": {
513 | "collapsed": true
514 | },
515 | "outputs": [],
516 | "source": []
517 | }
518 | ],
519 | "metadata": {
520 | "kernelspec": {
521 | "display_name": "Python 2",
522 | "language": "python",
523 | "name": "python2"
524 | },
525 | "language_info": {
526 | "codemirror_mode": {
527 | "name": "ipython",
528 | "version": 2
529 | },
530 | "file_extension": ".py",
531 | "mimetype": "text/x-python",
532 | "name": "python",
533 | "nbconvert_exporter": "python",
534 | "pygments_lexer": "ipython2",
535 | "version": "2.7.11"
536 | }
537 | },
538 | "nbformat": 4,
539 | "nbformat_minor": 0
540 | }
541 |
--------------------------------------------------------------------------------
/stream_fast_weight.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | """
4 | Implementation of algorithms proposed by:
5 |
6 | H. Huang, et al., "Unsupervised Feature Selection on Data Streams," Proc. of CIKM 2015, pp. 1031-1040 (Oct. 2015).
7 | """
8 |
9 | import numpy as np
10 | import numpy.linalg as ln
11 |
12 | class StreamFastWeight:
13 | """
14 | Alg. 2: Streaming update of feature weights at time t
15 | """
16 |
17 | def __init__(self, m, k, ell=0):
18 | """
19 | :param m: number of original features
20 | :param k: number of singular vectors (this can be the same as the number of clusters in the dataset)
21 | :param ell: sketche size for a sketched m-by-ell matrix B
22 | """
23 |
24 | self.m = m
25 | self.k = k
26 | if ell < 1: self.ell = int(np.sqrt(self.m))
27 | else: self.ell = ell
28 |
29 | def update(self, Yt):
30 | """
31 | Update the sketched matrix B based on new inputs at time t,
32 | and return weight of each feature
33 | :param Yt: m-by-n_t input matrix from data stream
34 | """
35 |
36 | # combine current sketched matrix with input at time t
37 | # C: m-by-(n+ell) matrix
38 | if hasattr(self, 'B'):
39 | C = np.hstack((self.B, Yt))
40 | n = Yt.shape[1]
41 | else:
42 | # for Y0, we need to first create an initial sketched matrix
43 | self.B = Yt[:, :self.ell]
44 | C = np.hstack((self.B, Yt[:, self.ell:]))
45 | n = Yt.shape[1] - self.ell
46 |
47 | U, s, V = ln.svd(C, full_matrices=False)
48 | U = U[:, :self.ell]
49 | s = s[:self.ell]
50 | V = V[:, :self.ell]
51 |
52 | # shrink step in Frequent Directions algorithm
53 | # (shrink singular values based on the squared smallest singular value)
54 | delta = s[-1] ** 2
55 | s = np.sqrt(s ** 2 - delta)
56 |
57 | # update sketched matrix B
58 | # (focus on column singular vectors)
59 | self.B = np.dot(U, np.diag(s))
60 |
61 | # According to Section 5.1, for all experiments,
62 | # the authors set alpha = 2^3 * sigma_k based on the pre-experiment
63 | alpha = (2 ** 3) * s[self.k-1]
64 |
65 | # solve the ridge regression by using the top-k singular values
66 | # X: m-by-k matrix (k <= ell)
67 | D = np.diag(s[:self.k] / (s[:self.k] ** 2 + alpha))
68 | X = np.dot(U[:, :self.k], D)
69 |
70 | return np.amax(abs(X), axis=1)
71 |
--------------------------------------------------------------------------------