Diff of /modules/DL_utils.py [000000] .. [1a9b40]

Switch to unified view

a b/modules/DL_utils.py
1
# Utility functions for deep learning with Keras
2
# Dr. Tirthajyoti Sarkar, Fremont, CA 94536
3
# ==============================================
4
5
# NOTES
6
# Used tf.keras in general except in special functions where older/independent Keras has been used.
7
8
import tensorflow as tf
9
import numpy as np
10
import matplotlib.pyplot as plt
11
12
13
class myCallback(tf.keras.callbacks.Callback):
14
    """
15
  User can pass on the desired accuracy threshold while creating an instance of the class
16
  """
17
18
    def __init__(self, acc_threshold=0.9, print_msg=True):
19
        self.acc_threshold = acc_threshold
20
        self.print_msg = print_msg
21
22
    def on_epoch_end(self, epoch, logs={}):
23
        if logs.get("acc") > self.acc_threshold:
24
            if self.print_msg:
25
                print(
26
                    "\nReached {}% accuracy so cancelling the training!".format(
27
                        self.acc_threshold
28
                    )
29
                )
30
            self.model.stop_training = True
31
        else:
32
            if self.print_msg:
33
                print("\nAccuracy not high enough. Starting another epoch...\n")
34
35
    def build_classification_model(
36
        num_layers=1,
37
        architecture=[32],
38
        act_func="relu",
39
        input_shape=(28, 28),
40
        output_class=10,
41
    ):
42
        """
43
  Builds a densely connected neural network model from user input
44
  
45
  Arguments
46
          num_layers: Number of hidden layers
47
          architecture: Architecture of the hidden layers (densely connected)
48
          act_func: Activation function. Could be 'relu', 'sigmoid', or 'tanh'.
49
          input_shape: Dimension of the input vector
50
          output_class: Number of classes in the output vector
51
  Returns
52
          A neural net (Keras) model for classification
53
  """
54
        layers = [tf.keras.layers.Flatten(input_shape=input_shape)]
55
        if act_func == "relu":
56
            activation = tf.nn.relu
57
        elif act_func == "sigmoid":
58
            activation = tf.nn.sigmoid
59
        elif act_func == "tanh":
60
            activation = tf.nn.tanh
61
62
        for i in range(num_layers):
63
            layers.append(tf.keras.layers.Dense(architecture[i], activation=tf.nn.relu))
64
        layers.append(tf.keras.layers.Dense(output_class, activation=tf.nn.softmax))
65
66
        model = tf.keras.models.Sequential(layers)
67
        return model
68
69
70
def build_regression_model(
71
    input_neurons=10, input_dim=1, num_layers=1, architecture=[32], act_func="relu"
72
):
73
    """
74
  Builds a densely connected neural network model from user input
75
  
76
  Arguments
77
          num_layers: Number of hidden layers
78
          architecture: Architecture of the hidden layers (densely connected)
79
          act_func: Activation function. Could be 'relu', 'sigmoid', or 'tanh'.
80
          input_shape: Dimension of the input vector
81
  Returns
82
          A neural net (Keras) model for regression
83
  """
84
    if act_func == "relu":
85
        activation = tf.nn.relu
86
    elif act_func == "sigmoid":
87
        activation = tf.nn.sigmoid
88
    elif act_func == "tanh":
89
        activation = tf.nn.tanh
90
91
    layers = [
92
        tf.keras.layers.Dense(input_neurons, input_dim=input_dim, activation=activation)
93
    ]
94
95
    for i in range(num_layers):
96
        layers.append(tf.keras.layers.Dense(architecture[i], activation=activation))
97
    layers.append(tf.keras.layers.Dense(1))
98
99
    model = tf.keras.models.Sequential(layers)
100
    return model
101
102
103
def compile_train_classification_model(
104
    model,
105
    x_train,
106
    y_train,
107
    callbacks=None,
108
    learning_rate=0.001,
109
    batch_size=1,
110
    epochs=10,
111
    verbose=0,
112
):
113
    """
114
  Compiles and trains a given Keras model with the given data. 
115
  Assumes Adam optimizer for this implementation.
116
  Assumes categorical cross-entropy loss.
117
  
118
  Arguments
119
          learning_rate: Learning rate for the optimizer Adam
120
          batch_size: Batch size for the mini-batch optimization
121
          epochs: Number of epochs to train
122
          verbose: Verbosity of the training process
123
  
124
  Returns
125
  A copy of the model
126
  """
127
128
    model_copy = model
129
    model_copy.compile(
130
        optimizer=tf.keras.optimizers.Adam(lr=learning_rate),
131
        loss="sparse_categorical_crossentropy",
132
        metrics=["accuracy"],
133
    )
134
135
    if callbacks != None:
136
        model_copy.fit(
137
            x_train,
138
            y_train,
139
            epochs=epochs,
140
            batch_size=batch_size,
141
            callbacks=[callbacks],
142
            verbose=verbose,
143
        )
144
    else:
145
        model_copy.fit(
146
            x_train, y_train, epochs=epochs, batch_size=batch_size, verbose=verbose
147
        )
148
    return model_copy
149
150
151
def compile_train_regression_model(
152
    model,
153
    x_train,
154
    y_train,
155
    callbacks=None,
156
    learning_rate=0.001,
157
    batch_size=1,
158
    epochs=10,
159
    verbose=0,
160
):
161
    """
162
  Compiles and trains a given Keras model with the given data for regression. 
163
  Assumes Adam optimizer for this implementation.
164
  Assumes mean-squared-error loss
165
  
166
  Arguments
167
          learning_rate: Learning rate for the optimizer Adam
168
          batch_size: Batch size for the mini-batch operation
169
          epochs: Number of epochs to train
170
          verbose: Verbosity of the training process
171
  
172
  Returns
173
  A copy of the model
174
  """
175
176
    model_copy = model
177
    model_copy.compile(
178
        optimizer=tf.keras.optimizers.Adam(lr=learning_rate),
179
        loss="mean_squared_error",
180
        metrics=["accuracy"],
181
    )
182
    if callbacks != None:
183
        model_copy.fit(
184
            x_train,
185
            y_train,
186
            epochs=epochs,
187
            batch_size=batch_size,
188
            callbacks=[callbacks],
189
            verbose=verbose,
190
        )
191
    else:
192
        model_copy.fit(
193
            x_train, y_train, epochs=epochs, batch_size=batch_size, verbose=verbose
194
        )
195
    return model_copy
196
197
198
def plot_loss_acc(model, target_acc=0.9, title=None):
199
    """
200
  Takes a Keras model and plots the loss and accuracy over epochs.
201
  The same plot shows loss and accuracy on two axes - left and right (with separate scales)
202
  Users can supply a title if desired
203
  Arguments:
204
            target_acc (optional): The desired/ target acc for the function to show a horizontal bar.
205
            title (optional): A Python string object to show as the plot's title
206
  """
207
    e = (
208
        np.array(model.history.epoch) + 1
209
    )  # Add one to the list of epochs which is zero-indexed
210
    # Check to see if loss metric is in the model history
211
    assert (
212
        "loss" in model.history.history.keys()
213
    ), "No loss metric found in the model history"
214
    l = np.array(model.history.history["loss"])
215
    # Check to see if loss metric is in the model history
216
    assert (
217
        "acc" in model.history.history.keys()
218
    ), "No accuracy metric found in the model history"
219
    a = np.array(model.history.history["acc"])
220
221
    fig, ax1 = plt.subplots()
222
223
    color = "tab:red"
224
    ax1.set_xlabel("Epochs", fontsize=15)
225
    ax1.set_ylabel("Loss", color=color, fontsize=15)
226
    ax1.plot(e, l, color=color, lw=2)
227
    ax1.tick_params(axis="y", labelcolor=color)
228
    ax1.grid(True)
229
230
    ax2 = ax1.twinx()  # instantiate a second axes that shares the same x-axis
231
232
    color = "tab:blue"
233
    ax2.set_ylabel(
234
        "Accuracy", color=color, fontsize=15
235
    )  # we already handled the x-label with ax1
236
    ax2.plot(e, a, color=color, lw=2)
237
    ax2.tick_params(axis="y", labelcolor=color)
238
239
    fig.tight_layout()  # otherwise the right y-label is slightly clipped
240
    if title != None:
241
        plt.title(title)
242
    plt.hlines(
243
        y=target_acc, xmin=1, xmax=e.max(), colors="k", linestyles="dashed", lw=3
244
    )
245
    plt.show()
246
247
248
def plot_train_val_acc(model, target_acc=0.9, title=None):
249
    """
250
  Takes a Keras model and plots the training and validation set accuracy over epochs.
251
  The same plot shows both the accuracies on two axes - left and right (with separate scales)
252
  Users can supply a title if desired
253
  Arguments:
254
            target_acc (optional): The desired/ target acc for the function to show a horizontal bar.
255
            title (optional): A Python string object to show as the plot's title
256
  """
257
    e = (
258
        np.array(model.history.epoch) + 1
259
    )  # Add one to the list of epochs which is zero-indexed
260
    # Check to see if loss metric is in the model history
261
    assert (
262
        "acc" in model.history.history.keys()
263
    ), "No accuracy metric found in the model history"
264
    a = np.array(model.history.history["acc"])
265
    # Check to see if loss metric is in the model history
266
    assert (
267
        "val_acc" in model.history.history.keys()
268
    ), "No validation accuracy metric found in the model history"
269
    va = np.array(model.history.history["val_acc"])
270
271
    fig, ax1 = plt.subplots()
272
273
    color = "tab:red"
274
    ax1.set_xlabel("Epochs", fontsize=15)
275
    ax1.set_ylabel("Training accuracy", color=color, fontsize=15)
276
    ax1.plot(e, a, color=color, lw=2)
277
    ax1.tick_params(axis="y", labelcolor=color)
278
    ax1.grid(True)
279
280
    ax2 = ax1.twinx()  # instantiate a second axes that shares the same x-axis
281
282
    color = "tab:blue"
283
    ax2.set_ylabel(
284
        "Validation accuracy", color=color, fontsize=15
285
    )  # we already handled the x-label with ax1
286
    ax2.plot(e, va, color=color, lw=2)
287
    ax2.tick_params(axis="y", labelcolor=color)
288
289
    fig.tight_layout()  # otherwise the right y-label is slightly clipped
290
    if title != None:
291
        plt.title(title)
292
293
    plt.hlines(
294
        y=target_acc, xmin=1, xmax=e.max(), colors="k", linestyles="dashed", lw=3
295
    )
296
297
    plt.show()
298
299
300
def train_CNN(
301
    train_directory,
302
    target_size=(256, 256),
303
    callbacks=None,
304
    classes=None,
305
    batch_size=128,
306
    num_classes=2,
307
    num_epochs=20,
308
    verbose=0,
309
):
310
    """
311
    Trains a conv net for a given dataset contained within a training directory.
312
    Users can just supply the path of the training directory and get back a fully trained, 5-layer, convolutional network.
313
    
314
    Arguments:
315
            train_directory: The directory where the training images are stored in separate folders.
316
                            These folders should be named as per the classes.
317
            target_size: Target size for the training images. A tuple e.g. (200,200)
318
            classes: A Python list with the classes 
319
            batch_size: Batch size for training
320
            num_epochs: Number of epochs for training
321
            num_classes: Number of output classes to consider
322
            verbose: Verbosity level of the training, passed on to the `fit_generator` method
323
    Returns:
324
            A trained conv net model
325
    
326
    """
327
    from tensorflow.keras.preprocessing.image import ImageDataGenerator
328
    import tensorflow as tf
329
    from tensorflow.keras.optimizers import RMSprop
330
331
    # ImageDataGenerator object instance with scaling
332
    train_datagen = ImageDataGenerator(rescale=1 / 255)
333
334
    # Flow training images in batches using the generator
335
    train_generator = train_datagen.flow_from_directory(
336
        train_directory,  # This is the source directory for training images
337
        target_size=target_size,  # All images will be resized to 200 x 200
338
        batch_size=batch_size,
339
        # Specify the classes explicitly
340
        classes=classes,
341
        # Since we use categorical_crossentropy loss, we need categorical labels
342
        class_mode="categorical",
343
    )
344
345
    input_shape = tuple(list(target_size) + [3])
346
347
    # Model architecture
348
    model = tf.keras.models.Sequential(
349
        [
350
            # Note the input shape is the desired size of the image 200x 200 with 3 bytes color
351
            # The first convolution
352
            tf.keras.layers.Conv2D(
353
                16, (3, 3), activation="relu", input_shape=input_shape
354
            ),
355
            tf.keras.layers.MaxPooling2D(2, 2),
356
            # The second convolution
357
            tf.keras.layers.Conv2D(32, (3, 3), activation="relu"),
358
            tf.keras.layers.MaxPooling2D(2, 2),
359
            # The third convolution
360
            tf.keras.layers.Conv2D(64, (3, 3), activation="relu"),
361
            tf.keras.layers.MaxPooling2D(2, 2),
362
            # The fourth convolution
363
            tf.keras.layers.Conv2D(64, (3, 3), activation="relu"),
364
            tf.keras.layers.MaxPooling2D(2, 2),
365
            # The fifth convolution
366
            tf.keras.layers.Conv2D(64, (3, 3), activation="relu"),
367
            tf.keras.layers.MaxPooling2D(2, 2),
368
            # Flatten the results to feed into a dense layer
369
            tf.keras.layers.Flatten(),
370
            # 512 neuron in the fully-connected layer
371
            tf.keras.layers.Dense(512, activation="relu"),
372
            # Output neurons for `num_classes` classes with the softmax activation
373
            tf.keras.layers.Dense(num_classes, activation="softmax"),
374
        ]
375
    )
376
377
    # Optimizer and compilation
378
    model.compile(
379
        loss="categorical_crossentropy", optimizer=RMSprop(lr=0.001), metrics=["acc"]
380
    )
381
382
    # Total sample count
383
    total_sample = train_generator.n
384
385
    # Training
386
    model.fit_generator(
387
        train_generator,
388
        callbacks=callbacks,
389
        steps_per_epoch=int(total_sample / batch_size),
390
        epochs=num_epochs,
391
        verbose=verbose,
392
    )
393
394
    return model
395
396
397
def train_CNN_keras(
398
    train_directory,
399
    target_size=(256, 256),
400
    classes=None,
401
    batch_size=128,
402
    num_classes=2,
403
    num_epochs=20,
404
    verbose=0,
405
):
406
    """
407
    Trains a conv net for a given dataset contained within a training directory.
408
    Users can just supply the path of the training directory and get back a fully trained, 5-layer, convolutional network.
409
    
410
    Arguments:
411
            train_directory: The directory where the training images are stored in separate folders.
412
                            These folders should be named as per the classes.
413
            target_size: Target size for the training images. A tuple e.g. (200,200)
414
            classes: A Python list with the classes 
415
            batch_size: Batch size for training
416
            num_epochs: Number of epochs for training
417
            num_classes: Number of output classes to consider
418
            verbose: Verbosity level of the training, passed on to the `fit_generator` method
419
    Returns:
420
            A trained conv net model
421
    
422
    """
423
    from keras.layers import Conv2D, MaxPooling2D
424
    from keras.layers import Dense, Dropout, Flatten
425
    from keras.models import Sequential
426
    from keras.optimizers import RMSprop
427
    from keras.preprocessing.image import ImageDataGenerator
428
429
    # ImageDataGenerator object instance with scaling
430
    train_datagen = ImageDataGenerator(rescale=1 / 255)
431
432
    # Flow training images in batches using the generator
433
    train_generator = train_datagen.flow_from_directory(
434
        train_directory,  # This is the source directory for training images
435
        target_size=target_size,  # All images will be resized to 200 x 200
436
        batch_size=batch_size,
437
        # Specify the classes explicitly
438
        classes=classes,
439
        # Since we use categorical_crossentropy loss, we need categorical labels
440
        class_mode="categorical",
441
    )
442
443
    input_shape = tuple(list(target_size) + [3])
444
445
    # Model architecture
446
    model = Sequential(
447
        [
448
            # Note the input shape is the desired size of the image 200x 200 with 3 bytes color
449
            # The first convolution
450
            Conv2D(16, (3, 3), activation="relu", input_shape=input_shape),
451
            MaxPooling2D(2, 2),
452
            # The second convolution
453
            Conv2D(32, (3, 3), activation="relu"),
454
            MaxPooling2D(2, 2),
455
            # The third convolution
456
            Conv2D(64, (3, 3), activation="relu"),
457
            MaxPooling2D(2, 2),
458
            # The fourth convolution
459
            Conv2D(64, (3, 3), activation="relu"),
460
            MaxPooling2D(2, 2),
461
            # The fifth convolution
462
            Conv2D(64, (3, 3), activation="relu"),
463
            MaxPooling2D(2, 2),
464
            # Flatten the results to feed into a dense layer
465
            Flatten(),
466
            # 512 neuron in the fully-connected layer
467
            Dense(512, activation="relu"),
468
            # Output neurons for `num_classes` classes with the softmax activation
469
            Dense(num_classes, activation="softmax"),
470
        ]
471
    )
472
473
    # Optimizer and compilation
474
    model.compile(
475
        loss="categorical_crossentropy", optimizer=RMSprop(lr=0.001), metrics=["acc"]
476
    )
477
478
    # Total sample count
479
    total_sample = train_generator.n
480
481
    # Training
482
    model.fit_generator(
483
        train_generator,
484
        steps_per_epoch=int(total_sample / batch_size),
485
        epochs=num_epochs,
486
        verbose=verbose,
487
    )
488
489
    return model
490
491
492
def preprocess_image(img_path, model=None, rescale=255, resize=(256, 256)):
493
    """
494
    Preprocesses a given image for prediction with a trained model, with rescaling and resizing options
495
    
496
    Arguments:
497
            img_path: The path to the image file
498
            rescale: A float or integer indicating required rescaling. 
499
                    The image array will be divided (scaled) by this number.
500
            resize: A tuple indicating desired target size. 
501
                    This should match the input shape as expected by the model
502
    Returns:
503
            img: A processed image.
504
    """
505
    from keras.preprocessing.image import img_to_array, load_img
506
    import cv2
507
    import numpy as np
508
509
    assert type(img_path) == str, "Image path must be a string"
510
    assert (
511
        type(rescale) == int or type(rescale) == float
512
    ), "Rescale factor must be either a float or int"
513
    assert (
514
        type(resize) == tuple and len(resize) == 2
515
    ), "Resize target must be a tuple with two elements"
516
517
#    img = load_img(img_path)    
518
    img = load_img(img_path,grayscale=True)
519
    img = img_to_array(img)
520
    img = img / float(rescale)
521
    img = cv2.resize(img, resize)
522
    if model != None:
523
        if len(model.input_shape) == 4:
524
            img = np.expand_dims(img, axis=0)
525
526
    return img
527
528
529
def pred_prob_with_model(img_path, model, rescale=255, resize=(256, 256)):
530
    """
531
    Tests a given image with a trained model, with rescaling and resizing options
532
    
533
    Arguments:
534
            img_path: The path to the image file
535
            model: The trained Keras model
536
            rescale: A float or integer indicating required rescaling. 
537
                    The image array will be divided (scaled) by this number.
538
            resize: A tuple indicating desired target size. 
539
                    This should match the input shape as expected by the model
540
    Returns:
541
            pred: A prediction vector (Numpy array).
542
                  Could be either classes or probabilities depending on the model.
543
    """
544
    from keras.preprocessing.image import img_to_array, load_img
545
    import cv2
546
547
    assert type(img_path) == str, "Image path must be a string"
548
    assert (
549
        type(rescale) == int or type(rescale) == float
550
    ), "Rescale factor must be either a float or int"
551
    assert (
552
        type(resize) == tuple and len(resize) == 2
553
    ), "Resize target must be a tuple with two elements"
554
555
    img = load_img(img_path,grayscale=True)
556
    img = img_to_array(img)
557
    img = img / float(rescale)
558
    img = cv2.resize(img, resize)
559
    if len(model.input_shape) == 4:
560
        img = np.expand_dims(img, axis=0)
561
562
    pred = model.predict(img)
563
564
    return pred
565
566
567
def pred_class_with_model(img_path, model, rescale=255, resize=(256, 256)):
568
    """
569
    Tests a given image with a trained model, with rescaling and resizing options
570
    
571
    Arguments:
572
            img_path: The path to the image file
573
            model: The trained Keras model
574
            rescale: A float or integer indicating required rescaling. 
575
                    The image array will be divided (scaled) by this number.
576
            resize: A tuple indicating desired target size. 
577
                    This should match the input shape as expected by the model
578
    Returns:
579
            pred: A prediction vector (Numpy array).
580
                  Could be either classes or probabilities depending on the model.
581
    """
582
    from keras.preprocessing.image import img_to_array, load_img
583
    import cv2
584
585
    assert type(img_path) == str, "Image path must be a string"
586
    assert (
587
        type(rescale) == int or type(rescale) == float
588
    ), "Rescale factor must be either a float or int"
589
    assert (
590
        type(resize) == tuple and len(resize) == 2
591
    ), "Resize target must be a tuple with two elements"
592
593
    img = load_img(img_path)
594
    img = img_to_array(img)
595
    img = img / float(rescale)
596
    img = cv2.resize(img, resize)
597
    if len(model.input_shape) == 4:
598
        img = np.expand_dims(img, axis=0)
599
600
    pred = model.predict(img)
601
    pred_class = pred.argmax(axis=-1)
602
603
    return pred_class