Mojo: убийца Python и будущее Ai?

от автора

Всем привет! Меня зовут Вадим Шубин, я Data Scientist в компании Raft, и сегодня мы погрузимся в Mojo. Я уже делал обзор данного языка программирования и рассмотрел его преимущества, примеры использования, а также провел сравнение с Python.

Теперь давайте посмотрим, как обучить простую сверточную нейронную сеть, и разберём один из методов машинного обучения — линейную регрессию. В качестве примеров задач возьмем стандартные соревнования машинного обучения: предсказание стоимости жилья и классификацию рукописных цифр MNIST. Для проведения экспериментов на Python используем фреймворк машинного обучения PyTorch. А на Mojo — фреймворк машинного обучения Basalt.

Немного о датасетах

MNIST (Modified National Institute of Standards and Technology) — это набор данных (датасет) для задачи распознавания рукописных цифр от 0 до 9. Набор состоит из 70 тысяч картинок с разрешением 28х28 черного цвета с белой цифрой. Задача состоит в том, чтобы распознать цифру, которая изображена на картинке.

Пример данных MNIST

Пример данных MNIST

Housing Prices Dataset — это набор данных для предсказания стоимости жилья на основе некоторых признаков. Например, площадь участка, тип жилья, наличия гаража, количества комнат и так далее.

Пример данных House Price Dataset

Пример данных House Price Dataset

Погружаемся в код

Эксперимент на MNIST

Для решения задачи классификации рукописных цифр напишем простую CNN (convolutional neural network), которая будет состоять из 2-х частей:

  • построение карты признаков (feature map), реализованной через 2 слоя сверток;

  • классификатор, состоящий из 3-х полносвязных слоев.

Более подробно архитектура представлена в таблице 1.

Layer

Future map

Size

Kernel size

Stride

Padding

Activation

Input

Image

1

28×28

1

Convolution

16

28×28

5×5

1

2

ReLU

2

Maxpool

16

14×14

2×2

0

0

3

Convolution

32

14×14

5×5

1

2

ReLU

4

Maxpool

32

7×7

2×2

0

0

5

FC

120

ReLU

6

FC

184

ReLU

Output

FC

10

Гиперпараметры обучения:

  • num_epochs = 20

  • batch_size = 8

  • learning_rate = 2e-3

  • оптимизатор Adam

  • функция потерь CrossEntropyLoss

Реализация архитектуры сети на Python и Mojo немного отличается. В первом случае, используя PyTorch, мы могли бы определить архитектуру как последовательность блоков.

class CNN(nn.Module):      def __init__(self):          super(CNN, self).__init__()          self.block1 = nn.Sequential(              nn.Conv2d(in_channels=1, out_channels=16, kernel_size=5, padding=2),              nn.ReLU(),              nn.MaxPool2d(kernel_size=2),          )          self.block2 = nn.Sequential(              nn.Conv2d(in_channels=16,out_channels= 32, kernel_size=5, padding=2),              nn.ReLU(),              nn.MaxPool2d(kernel_size=2),          )          self.fc1 = nn.Linear(in_features=32 * 7 * 7, out_features=120)          self.fc2 = nn.Linear(in_features=120, out_features=84)          self.out = nn.Linear(in_features=84, out_features=10)      def forward(self, x):          x = self.block1(x)          x = self.block2(x)          x = x.view(x.size(0), -1)          x = nn.ReLU()(self.fc1(x))          x = nn.ReLU()(self.fc2(x))          return self.out(x)

В случае с Mojo необходимо определить структуру Graph, которая реализует так называемый граф вычислений. Он применяется для вычислений в предсказании (feed forward) и обратного распространения ошибки (backpropagation).

fn create_CNN(batch_size: Int) -> Graph:     # инициализируем граф и наш вход      var g = Graph()      var x = g.input(TensorShape(batch_size, 1, 28, 28))     # инициализируем и применяем сверточные слои      var conv1 = nn.Conv2d(g, x, out_channels=16, kernel_size=5, padding=2)      var act_conv1 = nn.ReLU(g, conv1)      var max_pool1 = nn.MaxPool2d(g, act_conv1, kernel_size=2)       var conv2 = nn.Conv2d(g, max_pool1, out_channels=32, kernel_size=5, padding=2)      var act_conv2 = nn.ReLU(g, conv2)      var max_pool2 = nn.MaxPool2d(g, act_conv2, kernel_size=2)      # переводим выходной тензор в вектор      var x_reshape = g.op(          OP.RESHAPE,          max_pool2,          attributes=AttributeVector(              Attribute(                  "shape",                  TensorShape(max_pool2.shape[0], max_pool2.shape[1] * max_pool2.shape[2] * max_pool2.shape[3]),              )          ),      )      # классифицируем, извлеченные признаки, полносвязной сетью      var fc1 = nn.Linear(g, x_reshape, n_outputs=120)      var act_fc1 = nn.ReLU(g, fc1)      var fc2 = nn.Linear(g, act_fc1, n_outputs=84)      var act_fc2 = nn.ReLU(g, fc2)      var out = nn.Linear(g, act_fc2, n_outputs=10)      g.out(out)      # считаем потери, используя CrossEntropyLoss      var y_true = g.input(TensorShape(batch_size, 10))      var loss = nn.CrossEntropyLoss(g, out, y_true)      g.loss(loss)      return g

Инициализация модели вместе с оптимизатором и цикл её обучения на Python достаточно стандартный для PyTorch: прогоняем весь датасет некоторое число раз (эпох) по батчам (пакетам), определяем признаки (images) и метки к ним (labels). Затем предсказываем метку класса, рассчитываем ошибку и обновляем градиенты.

cnn = CNN()  loss_func = nn.CrossEntropyLoss()  optimizer = optim.Adam(cnn.parameters(), lr=learning_rate)  cnn.train()  for epoch in range(num_epochs):    for i, (images, labels) in enumerate(loaders["train"]):           b_x = Variable(images)         b_y = Variable(labels)         output = cnn(b_x)         loss = loss_func(output, b_y)         optimizer.zero_grad()         loss.backward()         optimizer.step()

На Mojo есть небольшие отличия:

  1. необходимо определять функцию для выполнения кода;

  2. необходимо определить модель и оптимизатор через структуру graph;

  3. перед подачей изображений в сеть необходимо произвести one hot encoding  меток.

В остальном процесс обучения сети схож со стилем PyTorch, за исключением особенностей синтаксиса языка.

fn main():      alias graph = create_CNN(batch_size)      var model = nn.Model[graph]()      var optim = nn.optim.Adam[graph](Reference(model.parameters), lr=learning_rate)      for epoch in range(num_epochs):                  var num_batches: Int = 0                  var epoch_loss: Float32 = 0.0                  for batch in training_loader:                      var labels_one_hot = Tensor[dtype](batch.labels.dim(0), 10)                      for bb in range(batch.labels.dim(0)):                          labels_one_hot[int((bb * 10 + batch.labels[bb]))] = 1.0                      var loss = model.forward(batch.data, labels_one_hot)                      optim.zero_grad()                      model.backward()                      optim.step()                      epoch_loss += loss[0]                      num_batches += 1

House price prediction

Для решения этой задачи мы применим стандартную линейную регрессию, реализованную через один полносвязный слой.

Гиперпараметры обучения следующие:

  • num_epochs = 500

  • batch_size = 32

  • learning_rate = 0.01

  • оптимизатор Adam 

  • функция потерь MSELoss

На Python код с использованием PyTorch будет выглядеть следующим образом.

class LinearRegression(nn.Module):      def __init__(self, input_dim):          super(LinearRegression, self).__init__()          self.linear = nn.Linear(in_features=input_dim, out_features=1)      def forward(self, x):          return self.linear(x)

На Mojo снова необходимо определить структуру Graph и слой с функцией потерь, через которые будут происходить вычисления.  

fn linear_regression(batch_size: Int, n_inputs: Int, n_outputs: Int) -> Graph:      var g = Graph()      var x = g.input(TensorShape(batch_size, n_inputs))      var y_true = g.input(TensorShape(batch_size, n_outputs))      var y_pred = nn.Linear(g, x, n_outputs)      g.out(y_pred)      var loss = nn.MSELoss(g, y_pred, y_true)      g.loss(loss)      return g

Цикл обучения совпадает с тем, что был показан на MNIST, за исключением того, что отпадает необходимость в ohe hot encoding, так как метки уже закодированы.

Результаты

Обучая сверточную сеть на MNIST и линейную регрессию на предсказании стоимости жилья, было проведено множество экспериментов с настройкой различных гиперпараметров. Результаты с оптимальными значениями по времени обучения представлены в таблице 2.

MNIST

House Price 

Python

1.58 сек

23.18 сек

Mojo

4.89 сек

0.15 сек

По задаче классификации MNIST язык программирования Python продемонстрировал лучшую производительность в классификации рукописных цифр. В то время как Mojo показал менее удовлетворительные результаты, что можно объяснить недостаточной оптимизацией сверток в текущем фреймворке Mojo Basalt.

Для задачи предсказания стоимости домов Python уступил Mojo в линейной регрессии. Mojo продемонстрировал хорошие результаты, превзойдя Python, что подтверждает высокую производительность языка, особенно в задачах, связанных с линейными вычислениями.

Заключение

Потенциал Mojo раскрывается в задачах, где важна скорость. Хотя на данный момент он пока не так хорош в работе с нейронными сетями, как Python, поскольку имеет ограниченной функционал. Но с развитием фреймворков и различных оптимизаций, вероятно, будут улучшения.

А что думаете вы? Пишите в комментариях!

 Ссылки:

  1. Репозиторий с кодом экспериментов 

  2. Соревнование на Kaggle о MNIST

  3. Соревнование на Kaggle о House Price Prediction

  4. Документация PyTorch

  5. Документация Mojo

  6. Статья о свертках


ссылка на оригинал статьи https://habr.com/ru/articles/843044/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *