-
Notifications
You must be signed in to change notification settings - Fork 12
Expand file tree
/
Copy pathtfnmf.py
More file actions
142 lines (112 loc) · 4.43 KB
/
tfnmf.py
File metadata and controls
142 lines (112 loc) · 4.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
"""Non-negative Matrix Factorization on TensorFlow
Authors : Shun Nukui
License : GNU General Public License v2.0
"""
from __future__ import division
import numpy as np
import tensorflow as tf
INFINITY = 10e+12
class TFNMF(object):
"""class for Non-negative Matrix Factorization on TensorFlow
Requirements:
TensorFlow version >= 0.6
"""
def __init__(self, V, rank, algo="mu", learning_rate=0.01):
#convert numpy matrix(2D-array) into TF Tensor
self.V = tf.constant(V, dtype=tf.float32)
shape = V.shape
self.rank = rank
self.algo = algo
self.lr = learning_rate
#scale uniform random with sqrt(V.mean() / rank)
scale = 2 * np.sqrt(V.mean() / rank)
initializer = tf.random_uniform_initializer(maxval=scale)
self.H = tf.get_variable("H", [rank, shape[1]],
initializer=initializer)
self.W = tf.get_variable(name="W", shape=[shape[0], rank],
initializer=initializer)
if algo == "mu":
self._build_mu_algorithm()
elif algo == "grad":
self._build_grad_algorithm()
else:
raise ValueError("The attribute algo must be in {'mu', 'grad'}")
def _build_grad_algorithm(self):
"""build dataflow graph for optimization with Adagrad algorithm"""
V, H, W = self.V, self.H, self.W
WH = tf.matmul(W, H)
#cost of Frobenius norm
f_norm = tf.reduce_sum(tf.pow(V - WH, 2))
#Non-negative constraint
#If all elements positive, constraint will be 0
nn_w = tf.reduce_sum(tf.abs(W) - W)
nn_h = tf.reduce_sum(tf.abs(H) - H)
constraint = INFINITY * (nn_w + nn_h)
self.loss = loss= f_norm + constraint
self.optimize = tf.train.AdagradOptimizer(self.lr).minimize(loss)
def _build_mu_algorithm(self):
"""build dataflow graph for Multiplicative algorithm"""
V, H, W = self.V, self.H, self.W
rank = self.rank
shape = V.get_shape()
graph = tf.get_default_graph()
#save W for calculating delta with the updated W
W_old = tf.get_variable(name="W_old", shape=[shape[0], rank])
save_W = W_old.assign(W)
#Multiplicative updates
with graph.control_dependencies([save_W]):
#update operation for H
Wt = tf.transpose(W)
WV = tf.matmul(Wt, V)
WWH = tf.matmul(tf.matmul(Wt, W), H)
WV_WWH = WV / WWH
#select op should be executed in CPU not in GPU
with tf.device('/cpu:0'):
#convert nan to zero
WV_WWH = tf.select(tf.is_nan(WV_WWH),
tf.zeros_like(WV_WWH),
WV_WWH)
H_new = H * WV_WWH
update_H = H.assign(H_new)
with graph.control_dependencies([save_W, update_H]):
#update operation for W (after updating H)
Ht = tf.transpose(H)
VH = tf.matmul(V, Ht)
WHH = tf.matmul(W, tf.matmul(H, Ht))
VH_WHH = VH / WHH
with tf.device('/cpu:0'):
VH_WHH = tf.select(tf.is_nan(VH_WHH),
tf.zeros_like(VH_WHH),
VH_WHH)
W_new = W * VH_WHH
update_W = W.assign(W_new)
self.delta = tf.reduce_sum(tf.abs(W_old - W))
self.step = tf.group(save_W, update_H, update_W)
def run(self, sess, max_iter=200, min_delta=0.001):
algo = self.algo
tf.initialize_all_variables().run()
if algo == "mu":
return self._run_mu(sess, max_iter, min_delta)
elif algo == "grad":
return self._run_grad(sess, max_iter, min_delta)
else:
raise ValueError
def _run_mu(self, sess, max_iter, min_delta):
for i in xrange(max_iter):
self.step.run()
delta = self.delta.eval()
if delta < min_delta:
break
W = self.W.eval()
H = self.H.eval()
return W, H
def _run_grad(self, sess, max_iter, min_delta):
pre_loss = INFINITY
for i in xrange(max_iter):
loss, _ = sess.run([self.loss, self.optimize])
if pre_loss - loss < min_delta:
break
pre_loss = loss
W = self.W.eval()
H = self.H.eval()
return W, H