From ac087d922feef2f56a27dc20d10a18fae6ed62f0 Mon Sep 17 00:00:00 2001 From: jaredgorski Date: Sun, 30 Aug 2020 13:37:53 -0700 Subject: [PATCH] Update API --- README.md | 48 +++++++++++++++++++++--- cnn/cnn.py | 6 +-- cnn/layers/__init__.py | 5 ++- cnn/layers/conv.py | 2 +- cnn/layers/maxpool.py | 4 +- cnn/layers/{activation.py => softmax.py} | 6 +-- run_mnist.py | 5 ++- test/test_conv_layer.py | 6 +-- test/test_maxpool_layer.py | 8 ++-- 9 files changed, 62 insertions(+), 28 deletions(-) rename cnn/layers/{activation.py => softmax.py} (95%) diff --git a/README.md b/README.md index c516894..fee120b 100644 --- a/README.md +++ b/README.md @@ -7,19 +7,57 @@ -The `cnn` package contains a primary `cnn.CNN` class as well as convolution and max-pooling layers at `cnn.ConvLayer` and `cnn.MaxPoolLayer`, respectively. These layers can be configured along with the learning rate in order to fine-tune the training of the network. This network currently works with the [MNIST handwritten digits dataset](http://yann.lecun.com/exdb/mnist/), which can be tested by running `python run_mnist.py`. +The `cnn` package contains a primary `cnn.CNN` class as well as convolution, max-pooling, and softmax activation layers at `cnn.layers.Conv`, `cnn.layers.MaxPool` and `cnn.layers.SoftMax`, respectively. These layers can be configured along with the learning rate in order to fine-tune the training of the network. This network currently works with the [MNIST handwritten digits dataset](http://yann.lecun.com/exdb/mnist/), which can be tested by running `python run_mnist.py`. The network supports both grayscale and RGB images. -#### To run with the MNIST dataset: +## To run with the MNIST dataset: 1. clone this repo locally 2. have Python 3 and pip installed on your machine 3. install dependencies with `pip install -r requirements.txt` 4. run `python run_mnist.py` -To run unit tests, run `python -m pytest`. +## Package usage +```python +# package must exist locally, whether cloned or copied into a project +import cnn -#### To do: +# get training images (RGB or grayscale) and labels, ordered +training_images = get_ordered_images_list() +training_labels = get_ordered_labels_list() + +# define list of classes +classes = ['cat', 'dog'] + +# initialize layer stack +layers = [ + cnn.layers.Conv(num_kernels=16, kernel_dimension=5, stride=1), + cnn.layers.MaxPool(kernel_dimension=2, stride=2), + cnn.layers.Conv(num_kernels=16, kernel_dimension=3, stride=1), + cnn.layers.MaxPool(kernel_dimension=2, stride=2), + cnn.layers.SoftMax(num_classes=10), +] + +# initialize network object +net = cnn.CNN(layers) + +# train +net.train(training_images, training_labels, classes, num_epochs=20, rate=0.001) + +# get test image and label +test_image = get_dog_png() +test_label = 'dog' + +# test model prediction +prediction_index = net.predict(test_image) + +prediction = classes[prediction_index] +correct = prediction == test_label +``` + +#### Tests: +- To run unit tests, run `python -m pytest`. + +## To do: - improve unit test coverage and depth -- improve activation layer logic - improve code clarity/internal documentation diff --git a/cnn/cnn.py b/cnn/cnn.py index 44f0a48..86e91e5 100644 --- a/cnn/cnn.py +++ b/cnn/cnn.py @@ -1,7 +1,6 @@ import numpy as np from .util import log -from .layers.activation import ActivationLayer """ If program `gnuplot` is available, set global @@ -42,7 +41,7 @@ def __feedforward_output(self, input): for layer in self.layers: output = layer.feedforward(output) - return self.activation_layer.feedforward(output) + return output def __feedforward(self, input, class_index): output = self.__feedforward_output(input) @@ -54,8 +53,6 @@ def __feedforward(self, input, class_index): return output, loss, correct def __backpropagate(self, loss_gradient, rate): - loss_gradient = self.activation_layer.backpropagate(loss_gradient, rate) - for layer in self.reverse_layers: loss_gradient = layer.backpropagate(loss_gradient, rate) @@ -75,7 +72,6 @@ def train(self, training_images, training_labels, classes, num_epochs=10, rate=0 self.training_images = training_images self.training_labels = training_labels self.classes = classes - self.activation_layer = ActivationLayer(len(classes)) print('\n\n>>> Train is leaving the station.\n'); diff --git a/cnn/layers/__init__.py b/cnn/layers/__init__.py index c8fb604..3210339 100644 --- a/cnn/layers/__init__.py +++ b/cnn/layers/__init__.py @@ -1,2 +1,3 @@ -from .conv import ConvLayer -from .maxpool import MaxPoolLayer +from .conv import Conv +from .maxpool import MaxPool +from .softmax import SoftMax diff --git a/cnn/layers/conv.py b/cnn/layers/conv.py index 4083e62..c544dc9 100644 --- a/cnn/layers/conv.py +++ b/cnn/layers/conv.py @@ -1,6 +1,6 @@ import numpy as np -class ConvLayer: +class Conv: def __init__(self, num_kernels, kernel_dimension=3, stride=1): self.l_name = f'Conv num_kernels={num_kernels} kernel_dim={kernel_dimension} stride={stride}' diff --git a/cnn/layers/maxpool.py b/cnn/layers/maxpool.py index a091133..a92bff1 100644 --- a/cnn/layers/maxpool.py +++ b/cnn/layers/maxpool.py @@ -1,8 +1,8 @@ import numpy as np -class MaxPoolLayer: +class MaxPool: - def __init__(self, stride=2, kernel_dimension=2): + def __init__(self, kernel_dimension=2, stride=2): self.l_name = f'MaxPool kernel_dim={kernel_dimension} stride={stride}' self.depth = None self.kernel_dimension = kernel_dimension diff --git a/cnn/layers/activation.py b/cnn/layers/softmax.py similarity index 95% rename from cnn/layers/activation.py rename to cnn/layers/softmax.py index 0fe45ce..275f482 100644 --- a/cnn/layers/activation.py +++ b/cnn/layers/softmax.py @@ -1,15 +1,13 @@ import numpy as np -class ActivationLayer: +class SoftMax: def __init__(self, num_classes): + self.l_name = f'SoftMax num_classes={num_classes}' self.num_classes = num_classes self.weights = [] self.biases = [] - def __relu_activation(self, input): - return max(0.0, input) - def __init_weights_and_biases(self): if self.flattened_input_len and len(self.weights) == 0 and len(self.biases) == 0: input_len = self.flattened_input_len diff --git a/run_mnist.py b/run_mnist.py index 4e07b5b..e4f6b66 100644 --- a/run_mnist.py +++ b/run_mnist.py @@ -26,8 +26,9 @@ net = pickle.load(pickle_in) else: layers = [ - cnn.ConvLayer(num_kernels=16), - cnn.MaxPoolLayer(), + cnn.layers.Conv(num_kernels=16), + cnn.layers.MaxPool(), + cnn.layers.SoftMax(num_classes=10), ] net = cnn.CNN(layers) diff --git a/test/test_conv_layer.py b/test/test_conv_layer.py index b349071..203b911 100644 --- a/test/test_conv_layer.py +++ b/test/test_conv_layer.py @@ -1,14 +1,14 @@ import numpy as np -from cnn import ConvLayer +from cnn.layers import Conv # setup test maps test_maps_8x8x1 = np.arange(64).reshape(8, 8, 1) test_maps_8x8x3 = np.arange(192).reshape(8, 8, 3) # setup test layers -conv_layer_3x3 = ConvLayer(num_kernels=5, kernel_dimension=3) -conv_layer_5x5 = ConvLayer(num_kernels=5, kernel_dimension=5) +conv_layer_3x3 = Conv(num_kernels=5, kernel_dimension=3) +conv_layer_5x5 = Conv(num_kernels=5, kernel_dimension=5) """ A layer with 5 kernels should result in output maps with depth=5. diff --git a/test/test_maxpool_layer.py b/test/test_maxpool_layer.py index 41570e8..1d903c0 100644 --- a/test/test_maxpool_layer.py +++ b/test/test_maxpool_layer.py @@ -1,6 +1,6 @@ import numpy as np -from cnn import MaxPoolLayer +from cnn.layers import MaxPool # setup test maps test_maps_8x8x1 = np.arange(64).reshape(8, 8, 1) @@ -9,9 +9,9 @@ test_maps_9x9x3 = np.arange(243).reshape(9, 9, 3) # setup test layers -maxpool_layer_122 = MaxPoolLayer(stride=2, kernel_dimension=2) -maxpool_layer_322 = MaxPoolLayer(stride=2, kernel_dimension=2) -maxpool_layer_133 = MaxPoolLayer(stride=3, kernel_dimension=3) +maxpool_layer_122 = MaxPool(kernel_dimension=2, stride=2) +maxpool_layer_322 = MaxPool(kernel_dimension=2, stride=2) +maxpool_layer_133 = MaxPool(kernel_dimension=3, stride=3) """ The layer should not change the number of maps in the input.