File size: 3,490 Bytes
027680f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import numpy as np
from abc import ABC, abstractmethod

class Layer(ABC):
    def __init__(self, input_size, output_size):
        self.input = None
        self.output = None
        self.input_size = input_size
        self.output_size = output_size
        self.weights = np.random.randn(input_size, output_size) * np.sqrt(2. / input_size)
        self.bias = np.zeros((1, output_size))

    @staticmethod
    def activate(z, activation: str):
        if activation == 'sigmoid':
            return 1 / (1 + np.exp(-z))
        elif activation == 'relu':
            return np.maximum(0, z)
        else:
            print("Undefined activation type")
            return None

    @staticmethod
    def derivative(activation: str, z):
        if activation == 'sigmoid':
            return z * (1 - z)
        elif activation == 'relu':
            return np.where(z > 0, 1, 0)
        else:
            print("Undefined activation type")
            return None

    @abstractmethod
    def feedforward(self, x_train):
        pass

    @abstractmethod
    def backpropagation(self, x_train, y_train, learning_rate):
        pass

class Dense(Layer):
    def __init__(self, input_size, output_size, activation: str):
        super().__init__(input_size, output_size)
        self.activation = activation

    def feedforward(self, input):
        self.input = input
        z = np.dot(self.input, self.weights) + self.bias
        self.output = self.activate(z, self.activation)
        return self.output

    def backpropagation(self, error, learning_rate):
        d = self.derivative(self.activation, self.output)
        db = np.sum(error * d, axis=0, keepdims=True)
        dW = np.dot(self.input.T, error * d)

        input_error = np.dot(error * d, self.weights.T)

        self.weights -= learning_rate * dW
        self.bias -= learning_rate * db

        return input_error

class MLP:
    def __init__(self):
        self.layers = []

    @staticmethod
    def loss_MSE(y_train, yhat):
        loss = np.mean(np.square(yhat - y_train))
        return loss

    @staticmethod
    def loss_cross(y_train, yhat):
        epsilon = 1e-9  # avoid log(0)
        loss = -np.sum(y_train * np.log(yhat + epsilon)) / y_train.shape[0]
        return loss

    def addlayer(self, layer: Dense):
        self.layers.append(layer)

    def predict(self, input):
        for layer in self.layers:
            input = layer.feedforward(input)
        return input

    def fit(self, x_train, y_train, learning_rate=0.01, batch_size=8, epochs=10, loss_type: str = 'MSE'):
        num_samples = x_train.shape[0]
        for epoch in range(epochs):
            indices = np.arange(num_samples)
            np.random.shuffle(indices)
            x_train = x_train[indices]
            y_train = y_train[indices]
            loss = 0

            for i in range(0, num_samples, batch_size):
                x_batch = x_train[i: i + batch_size]
                y_batch = y_train[i: i + batch_size]

                output = self.predict(input=x_batch)
                error = output - y_batch

                if loss_type == 'MSE':
                    loss += self.loss_MSE(y_batch, output)
                else:
                    loss += self.loss_cross(y_batch, output)

                for layer in reversed(self.layers):
                    error = layer.backpropagation(error, learning_rate)

            print(f'Epoch {epoch + 1}/{epochs}, Loss: {loss / (num_samples // batch_size)}')