Diff of /deepheart/model.py [000000] .. [d3af21]

Switch to unified view

a b/deepheart/model.py
1
import tensorflow as tf
2
import os
3
from datetime import datetime
4
5
6
class CNN:
7
    def __init__(self, pcg, nclasses=2, learning_rate=0.001,
8
                 epochs=5, batch_size=100, dropout=0.75, base_dir="/tmp",
9
                 model_name="cnn"):
10
        self.pcg = pcg
11
        self.nclasses = nclasses
12
        self.d_input = self.pcg.train.X.shape[1]
13
        self.learning_rate = learning_rate
14
        self.epochs = epochs
15
        self.batch_size = batch_size
16
        self.dropout = dropout
17
        self.nbatches = int(self.pcg.train.X.shape[0] / float(self.batch_size))
18
        self.model_name = model_name
19
        self.base_dir = base_dir
20
21
    def train(self):
22
        """
23
        Train a convolutional neural network over the input PCG dataset.
24
        This method is beefy: it is responsible for defining tensorflow
25
        variables, defining the training objective function, defining summary
26
        statistics creating the tensorflow session, running gradient
27
        descent and, ultimately, writing statistics
28
29
        In the future this will be refactored into more easily tested
30
        training segments.
31
32
        Parameters
33
        ----------
34
        None
35
36
        Returns
37
        -------
38
        None
39
40
        """
41
        print('begin train')
42
        print(self.__get_output_name())
43
44
        with tf.name_scope('input'):
45
            X = tf.placeholder(tf.float32, [None, self.d_input], name='X')
46
            y = tf.placeholder(tf.float32, [None, self.nclasses], name='y')
47
            do_drop = tf.placeholder(tf.float32, name='drop')
48
49
        with tf.name_scope('weights'):
50
            weights = {
51
                'wc1': tf.Variable(tf.random_normal([5, 1, 1, 32]), name='wc1'),
52
                'wc2': tf.Variable(tf.random_normal([5, 1, 32, 64]), name='wc2'),
53
                # 2 Max pools have taken original 10612 signal down to
54
                # 5306 --> 2653. Each max pool has a ksize=2.
55
                # 'wd1': tf.Variable(tf.random_normal([2653 * 1 * 64, 1024])),
56
                'wd1': tf.Variable(tf.random_normal([int(self.d_input / 4) * 1 * 64, 1024]), name='wd1'),
57
                'out': tf.Variable(tf.random_normal([1024, self.nclasses]), name='outW')
58
            }
59
        with tf.name_scope('biases'):
60
            biases = {
61
                'bc1': tf.Variable(tf.random_normal([32]), name='bc1'),
62
                'bc2': tf.Variable(tf.random_normal([64]), name='bc2'),
63
                'bd1': tf.Variable(tf.random_normal([1024]), name='bd1'),
64
                'out': tf.Variable(tf.random_normal([self.nclasses]), name='outB')
65
            }
66
67
        with tf.name_scope('pred'):
68
            pred = self.model1D(X, weights, biases, do_drop)
69
70
        with tf.name_scope('cost'):
71
            cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(pred, y, name='cost'))
72
            optimizer = tf.train.AdamOptimizer(learning_rate=self.learning_rate).minimize(cost)
73
74
        dim = tf.shape(y)[0]
75
76
        with tf.name_scope('sensitivity'):
77
            # sensitivity = correctly predicted abnormal / total number of actual abnormal
78
            abnormal_idxs = tf.cast(tf.equal(tf.argmax(pred, 1), 1), tf.float32)
79
            pred1d = tf.reshape(tf.slice(y, [0, 1], [dim, 1]), [-1])
80
            abn = tf.mul(pred1d, abnormal_idxs)
81
            sensitivity = tf.reduce_sum(abn) / tf.reduce_sum(pred1d)
82
            tf.scalar_summary('sensitivity', sensitivity)
83
84
        with tf.name_scope('specificity'):
85
            # specificity = correctly predicted normal / total number of actual normal
86
            normal_idxs = tf.cast(tf.equal(tf.argmax(pred, 1), 0), tf.float32)
87
            pred1d_n = tf.reshape(tf.slice(y, [0, 0], [dim, 1]), [-1])
88
            normal = tf.mul(pred1d_n, normal_idxs)
89
            specificity = tf.reduce_sum(normal) / tf.reduce_sum(pred1d_n)
90
            tf.scalar_summary('specificity', sensitivity)
91
92
        # Physionet score is the mean of sensitivity and specificity
93
        score = (sensitivity + specificity) / 2.0
94
        tf.scalar_summary('score', score)
95
96
        init = tf.initialize_all_variables()
97
98
        saver = tf.train.Saver()
99
        with tf.Session() as sess:
100
            sess.run(init)
101
102
            merged = tf.merge_all_summaries()
103
            train_writer = tf.train.SummaryWriter(os.path.join(self.base_dir, 'train'), sess.graph)
104
105
            for epoch in range(self.epochs):
106
                avg_cost = 0
107
                for batch in range(self.nbatches):
108
                    batch_x, batch_y = self.pcg.get_mini_batch(self.batch_size)
109
                    summary, _, c = sess.run([merged, optimizer, cost],
110
                                             feed_dict={X: batch_x,
111
                                                        y: batch_y,
112
                                                        do_drop: self.dropout})
113
                    train_writer.add_summary(summary, epoch*batch)
114
                    avg_cost += c
115
                avg_cost /= float(self.nbatches)
116
                print('Epoch %s\tcost %s' % (epoch, avg_cost))
117
118
                if epoch % 10 == 0:
119
                    acc, sens, spec = sess.run([score, sensitivity, specificity],
120
                                                feed_dict={X: self.pcg.test.X,
121
                                                           y: self.pcg.test.y,
122
                                                           do_drop: 1.})
123
                    print('Score %s\tSensitivity %s\tSpecificity %s' % (acc, sens, spec))
124
125
                    saver.save(sess, self.__get_output_name())
126
                    print('Epoch written')
127
128
    def __get_output_name(self):
129
        now = datetime.now()
130
        time_str = "-%s" % (now.date())  # now.hour, now.minute, now.second)
131
        model_path = os.path.join(self.base_dir, self.model_name + time_str + '.tnfl')
132
        return model_path
133
134
    def conv2d(self, x, w, b, strides=1):
135
        """
136
        A small helper function for calcualting a 1D convolution
137
        from tensorflow's conv2d method
138
139
        Parameters
140
        ----------
141
        x: tensorflow.placeholder
142
            The feature vector
143
        w: tensorflow.Variable
144
            The unknown weights to learn
145
        b: tensorflow.Variable
146
            The unknown biases to learn
147
        strides: int
148
            The length of the stride to use for convolution
149
150
        Returns
151
        -------
152
        tensorflow.Variable
153
            A convolution over the input feature vector
154
155
        """
156
157
158
        x = tf.nn.conv2d(x, w, strides=[1, strides, strides, 1], padding="SAME")
159
        x = tf.nn.bias_add(x, b)
160
        return tf.nn.relu(x)
161
162
    def model1D(self, x, weights, biases, dropout):
163
        """
164
        A Wrapper to chain several TensorFlow convolutional units together. This 1D model
165
        ultimately calls TensorFlow's conv2d, mapping a 1D feature vector to a collapsed
166
        2D convolution
167
168
        Parameters
169
        ----------
170
        x: tensorflow.placeholder
171
            A feature vector of size [None, no_features]
172
173
        weights: dict<str, tensorflow.Variable>
174
            Dictionary of Unknown weights to learn
175
176
        biases: dict<str, tensorflow.Variable>
177
            Dictionary of unknown biases to learn
178
179
        dropout: float
180
            the dropout fraction for convolutional units
181
182
        Returns
183
        -------
184
        out: tensorflow.Variable
185
            The result of applying multiple convolutional layers and
186
            a fully connected unit to the input feature vector
187
188
        """
189
190
        with tf.name_scope('reshape'):
191
            x = tf.reshape(x, shape=[-1, self.d_input, 1, 1])  # [n_images, width, height, n_channels]
192
193
        with tf.name_scope('conv1'):
194
            conv1 = self.conv2d(x, weights['wc1'], biases['bc1'])
195
            conv1 = tf.nn.max_pool(conv1, ksize=[1, 2, 1, 1], strides=[1, 2, 1, 1], padding='SAME')
196
            conv1 = tf.nn.relu(conv1)
197
198
        with tf.name_scope('conv2'):
199
            conv2 = self.conv2d(conv1, weights['wc2'], biases['bc2'])
200
            conv2 = tf.nn.max_pool(conv2, ksize=[1, 2, 1, 1], strides=[1, 2, 1, 1], padding="SAME")
201
            conv2 = tf.nn.relu(conv2)
202
203
        with tf.name_scope('fullyConnected'):
204
            d_layer1 = weights['wd1'].get_shape().as_list()[0]
205
            fc1 = tf.reshape(conv2, [-1, d_layer1])
206
            fc1 = tf.add(tf.matmul(fc1, weights['wd1']), biases['bd1'])
207
            fc1 = tf.nn.relu(fc1)
208
            fc1 = tf.nn.dropout(fc1, dropout)
209
210
            out = tf.add(tf.matmul(fc1, weights['out']), biases['out'])
211
        return out