summaryrefslogtreecommitdiff
path: root/neural_network.py
blob: 21631733e3e208a8951fa8ca9057f27adafa9087 (plain)
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
import numpy as np
import math
from scipy.special import expit
from secrets import token_hex
from alphabet import CYRILLIC_ALPHABET

"""The neural network class."""


class NeuralNetwork:
    def __init__(self, learning_rate: float, input_resolution: int) -> None:
        self.learning_rate = learning_rate
        self.input_layer_size = input_resolution
        self.hidden_layer_size = len(CYRILLIC_ALPHABET)
        self._hidden_weights = self._random_array(self.hidden_layer_size,
                                                  input_resolution)
        self._output_weights = self._random_array(input_resolution, 1)

    """
    Train the neural network. It loads the dataset contained in ./data,
    converts each image into a numpy array and uses that data for training.
    Algorithm:
    1-. Feedforward the  input matrix.
    2-. Calculate the output layer error.
    3-. Adjust the weights of the output layer via gradient descent.
    3-. Calculate the hidden layer error by backpropagating the output layer
        error.
    4-. Adjust the weights of the hidden layer via gradient descent.
    """
    def train(self):
        pass

    """
    Guess the letter contained in the image file pointed by
    input_image (a path).
    """
    def guess(self, input_image: np.array) -> str:
        output_layer = self.feedforward(input_image)

        return self._guessed_char(output_layer)

    """
    Feedforwarding.
    """
    def feedforward(self, input_layer: np.array):
        hidden_layer_inputs = np.dot(self._hidden_weights, input_layer)
        hidden_layer_outputs = self._get_layer_output(hidden_layer_inputs)
        output_layer_inputs = np.dot(hidden_layer_outputs,
                                     self._output_weights)

        # The output layer outputs. (Final output of the neural network).
        return self._get_layer_output(output_layer_inputs)

    """
    Save the weights to a csv file.
    """
    def save(self):
        np.savetxt(f"./hidden_weights_{token_hex(8)}.csv",
                   self._hidden_weights, delimiter=',')
        np.savetxt(f"./output_weights_{token_hex(8)}.csv",
                   self._output_weights, delimiter=',')

    """
    Load the weights from a csv file.
    """
    def load(self, hidden_weights_file: str, output_weights_file: str):
        with open(hidden_weights_file) as hidden_weights:
            self._hidden_weights = np.loadtxt(hidden_weights, delimiter=',')

        with open(output_weights_file) as output_weights:
            self._output_weights = np.loadtxt(output_weights, delimiter=',')

    """
    Get the result from a sigmoid matrix (the index with the highest chance
    of being the correct answer).
    """
    def _guessed_char(self, output_layer: np.array) -> str:
        return CYRILLIC_ALPHABET[np.argmax(np.transpose(output_layer))]

    """
    Apply the sigmoid function to a given layer
    """
    def _get_layer_output(self, layer: np.array) -> np.array:
        return expit(layer)

    """
    Get the hidden layer and output layer error matrices.
    """
    def _get_errors(self, target: str) -> tuple:
        output_layer_errors = np.substract(self._get_expected_outputs(target),
                                           self._output_layer)
        # Backpropagate the errors.
        hidden_layer_errors = np.dot(np.transpose(self._hidden_weights),
                                     output_layer_errors)

        return (hidden_layer_errors, output_layer_errors)

    """
    Given a cyrillic letter, get the target outputs.
    """
    def _get_expected_outputs(self, target: str) -> np.array:
        index = CYRILLIC_ALPHABET.index(target)
        expected_outputs = np.zeros(len(CYRILLIC_ALPHABET), dtype=np.int8)
        expected_outputs[index] = 1

        return expected_outputs

    """
    Generate a random array via an uniform distribution.
    """
    def _random_array(self, rows: int, columns: int) -> np.array:
        low = -1 / math.sqrt(rows)
        high = 1 / math.sqrt(columns)

        return np.random.uniform(low, high, (rows, columns))