summaryrefslogtreecommitdiff
path: root/neural_network.py
blob: 6c44ddb5340b1f1f8a667ef9f183a180a988ac50 (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
116
117
118
119
import numpy as np
import math
from scipy.special import expit
from secrets import token_hex

CYRILLIC_ALPHABET = ['I', 'А', 'Б', 'В', 'Г', 'Д', 'Е', 'Ë', 'Ж', 'З',
                     'И', 'Й', 'К', 'Л', 'М', 'Н', 'О', 'П', 'Р', 'С',
                     'Т', 'У', 'Ф', 'Х', 'Ц', 'Ч', 'Ш', 'Щ', 'Ъ', 'Ы',
                     'Ь', 'Э', 'Ю', 'Я']

"""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))