Skip to content

Commit

Permalink
Update API
Browse files Browse the repository at this point in the history
  • Loading branch information
jaredgorski committed Aug 30, 2020
1 parent 3ae7e0c commit ac087d9
Show file tree
Hide file tree
Showing 9 changed files with 62 additions and 28 deletions.
48 changes: 43 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,57 @@
<img src="https://github.com/jaredgorski/cnn4fun/raw/master/.media/screenshot.png" width="600" />
</div>

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
6 changes: 1 addition & 5 deletions cnn/cnn.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import numpy as np

from .util import log
from .layers.activation import ActivationLayer

"""
If program `gnuplot` is available, set global
Expand Down Expand Up @@ -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)
Expand All @@ -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)

Expand All @@ -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');

Expand Down
5 changes: 3 additions & 2 deletions cnn/layers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from .conv import ConvLayer
from .maxpool import MaxPoolLayer
from .conv import Conv
from .maxpool import MaxPool
from .softmax import SoftMax
2 changes: 1 addition & 1 deletion cnn/layers/conv.py
Original file line number Diff line number Diff line change
@@ -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}'
Expand Down
4 changes: 2 additions & 2 deletions cnn/layers/maxpool.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
6 changes: 2 additions & 4 deletions cnn/layers/activation.py → cnn/layers/softmax.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
5 changes: 3 additions & 2 deletions run_mnist.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
6 changes: 3 additions & 3 deletions test/test_conv_layer.py
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
8 changes: 4 additions & 4 deletions test/test_maxpool_layer.py
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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.
Expand Down

0 comments on commit ac087d9

Please sign in to comment.