Всем привет. Каждый из нас наверняка интересовался, как же устроены различные библиотеки машинного обучения? Так вот, сейчас вы читаете продолжение рубрики экспериментов в машинном обучении. Мы пошагово будем создавать свой интерфейс, свои функции и свои модели. Поехали!

Подпишись на группу Вконтакте и Телеграм-канал. Там еще больше полезного контента для программистов.
А на YouTube-канале ты найдешь обучающие видео по программированию. Подписывайся!

Класс модели

Для начала создадим класс модели. Реализуем некоторые методы, связанны со стандартным программным интерфейсом моделей машинного обучения, реализованных классов.

class Model:
  def __init__(self, layers=None):
    if not layers:
      layers = list()
       
    for layer in layers:
      if not isinstance(layer, Layer):
        raise TypeError('Argument is not a layer.')
     
    self.__layers = layers
    self.__fitted = False

В конструктор класса можно передать список слоёв, каждый из которых мы проверяем на принадлежность класса Layer, который мы реализуем позже.

Также реализуем метод, который позволяет добавить доп.слой до обучения модели:

Model...

  def add(self, layer):
    if self.__fitted:
      raise AttributeError('The model has already been fitted.')
     
    if not isinstance(layer, Layer):
      raise TypeError('Argument is not a layer.')
       
    self.__layers.append(layer)
     
    return self

Мы возвращяем этот же класс, дабы дать возможность пользователям использовать цепной интерфейс.

Теперь реализуем метод обучения, основанный на Дельта-правиле (правиле обучения персептрона).

Model...

  def fit(self, X, Y, epochs=10, eta=.01):
    X = np.array(X); Y = np.array(Y)
    self.__input_shape = X[0].shape
     
     
     
    if X.shape[0] != Y.shape[0]:
      raise ValueError('Inputs have different shape.')
     
    for layer in self.__layers:
      layer._set_input_shape(self.__input_shape)
       
         
    for epoch in range(epochs):  
      for i in range(len(X)):
        x = X[i]; y = Y[i];
        for layer in self.__layers:
          x = layer._net_input(x)
             
        update = eta * (y - x)  
           
        for layer in self.__layers:
          layer._update(update)  
           
    self.__fitted = True       
    return self  

Всё это вы могли видеть в моей статье про Персептрон.

Реализуем метод предсказывания. В первой части мы ограничимся разделением на два класса:

Model...
  def predict(self, X):
    if not self.__fitted: raise TypeError('The model is not fitted.') 
    return [1 if self._net_input(x) > 0 else 0 for x in X]

Также реализуем метод расчёта для определённого набора признаков:

Model...

  def _net_input(self, x):
    for layer in self.__layers:
      x = layer._net_input(x)
       
    return x.sum() 

Ну и метод оценивания, возвращающий точность предсказания модели.

Model...

  def score(self, X, Y):
    return sum(np.array(self.predict(X)) == Y) / len(Y) 

Слои — строительные блоки модели

В первой части мы реализуем класс, который даже не будет иметь функции активации :)

class Layer:
     
  def _net_input(self, x):
    if not self.__input_shape == x.shape:
      raise ValueError(f'X has bad shape {x.shape}, must be {self.__input_shape}')
       
    self.__input = x   
       
    return x * self.__weights + self.__intercept  
   
  def _set_input_shape(self, shape):
    self.__input_shape = shape
    self.__weights = np.random.random(self.__input_shape)
    self.__intercept = 0
     
  def _update(self, update):
    delta_w = update * self.__input
     
    self.__weights += delta_w
    self.__intercept += delta_w

Использование всех реализованных методов мы могли лицезреть в классе Model.

Оценивание

Я не надеялся на высокую точность, таким результат и оказался.

In[1]:
import numpy as np

from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
In[4]:
data = load_breast_cancer()

X = data.data
y = data.target

Теперь обучим модель на полученных данных:

In[5]:
model = Model()
model.add(Layer())
model.add(Layer())
model.add(Layer())

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1)

model.fit(X_train, y_train, epochs=500, eta=0.1)

Точность модели:

In[6]:
model.score(X_test, y_test)

Out[6]:
0.49122807017543857

Заключение

В первой части я всего лишь хотел ввести вас в курс дела и получить какой-то фидбек. В будущем нас ждут функции активации, оптимизаторы, метрики и так далее. Всем хорошего обучения и достаточного количества памяти!

Ссылочка на документ с кодом тута.

Также рекомендую прочитать статью Функциональный API библиотеки Keras. А также подписывайтесь на группу ВКонтакте, Telegram и YouTube-канал. Там еще больше полезного и интересного для программистов.