![](https://habrastorage.org/getpro/habr/upload_files/ec9/34c/00b/ec934c00b5c0e36ac07e6b2c08d20425.png)
Всем привет! Меня зовут Вадим, я Data Scientist в компании Raft, и сегодня мы продолжим погружаться в Mojo. Эта статья является продолжением первой части обзора данного языка программирования. В ней я подробно рассмотрел его преимущества, примеры использования, а также провел сравнение с Python. Если вы ещё не читали прошлую часть, то рекомендую сделать это!
В этой статье мы погрузимся в практическое применение: обучим линейную регрессию и простую сверточную нейронную сеть, а затем проведём сравнение производительности на этих языках, чтобы понять: действительно ли Mojo превосходит Python в 30 раз?
В качестве задач я выбрал два стандартных соревнования по машинному обучению: предсказание стоимости жилья и классификацию рукописных цифр MNIST. Для проведения экспериментов на Python будет использоваться фреймворк машинного обучения - PyTorch, а на Mojo - Basalt, который описывался в первой части.
Примечание: если вас интересует только производительность Mojo в сравнении с Python, вы сможете найти эту информацию в последней секции данной статьи.
Итак, давайте начнём!
![](https://habrastorage.org/getpro/habr/upload_files/4ef/6d4/344/4ef6d43448c0dae7805ec491c3a4b45e.png)
Немного о датасетах
MNIST (Modified National Institute of Standards and Technology) представляет из себя датасет для задачи распознавания рукописных цифр от 0 до 9. Данные состоят из 70 тысяч картинок с разрешением 28х28, каждая из которых имеет черный фон, на котором изображена цифра белого цвета. Задача состоит в том, чтобы распознать цифру, которая изображена на картинке.
![Пример данных MNIST Пример данных MNIST](https://habrastorage.org/getpro/habr/upload_files/9fb/b16/f6b/9fbb16f6ba064203a600637c15a0714e.png)
Housing Prices Dataset представляет из себя набор данных для предсказания стоимости жилья на основе некоторых признаков, например, площадь участка, тип жилья, наличие гаража, количество комнат и так далее.
![Соревнование House Prices Соревнование House Prices](https://habrastorage.org/getpro/habr/upload_files/42b/6a3/9db/42b6a39db25b6c73959b1854aaec8b91.png)
Погружаемся в код
Эксперимент на MNIST
Для решения задачи классификации рукописных цифр напишем простую CNN (convolutional neural network), которая будет состоять из 2-х частей:
построение карты признаков (feature map), реализованной через 2 слоя сверток
классификатор, состоящий из 3-х полносвязных слоев.
Более подробно архитектура представлена в таблице ниже.
Layer |
Future map |
Size |
Kernel size |
Stride |
Padding |
Activation |
|
Input |
Image |
1 |
28x28 |
- |
- |
- |
- |
1 |
Convolution |
16 |
28x28 |
5x5 |
1 |
2 |
ReLU |
2 |
Maxpool |
16 |
14x14 |
2x2 |
0 |
0 |
|
3 |
Convolution |
32 |
14x14 |
5x5 |
1 |
2 |
ReLU |
4 |
Maxpool |
32 |
7x7 |
2x2 |
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 есть небольшие отличия:
необходимо определять функцию для выполнения кода
необходимо определить модель и оптимизатор через структуру graph
перед подачей в изображений в сеть необходимо произвести 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 и предсказания стоимости домов с использованием простой CNN и линейной регрессии соответственно, мы смогли наглядно сравнить производительность Python и Mojo, оценив, за какое время при равных условиях модели обучаются на разных языках. Лучшие результаты представлены в таблице ниже.
MNIST |
House Price |
|
Python |
1.58 сек |
23.18 сек |
Mojo |
4.89 сек |
0.15 сек |
Ниже приводятся основные выводы, которые я выявил по результатам экспериментов.
Задача |
Язык программирования |
Результат |
Классификация MNIST |
Python |
Показал лучшую производительность в задаче классификации рукописных цифр. |
Классификация MNIST |
Mojo |
Показал более низкую производительность, что может быть связано с низкой оптимизацией сверток в текущем фреймворке Mojo — Basalt. |
Предсказание стоимости домов |
Python |
Уступил Mojo в задаче линейной регрессии для предсказания стоимости домов. |
Предсказание стоимости домов |
Mojo |
Продемонстрировал хорошие результаты, превзойдя Python. Это подтверждает обещания разработчиков о высокой производительности языка, особенно в задачах, связанных с линейными вычислениями. |
Заключение
Mojo имеет большой потенциал, особенно в тех задачах, где важна скорость. Хотя на данный момент он пока не так хорош в работе с нейронными сетями, как Python, поскольку имеет более ограниченный функционал, будет здорово, если в будущем он расширит его и разовьёт комьюнити с большим количество различных библиотек и фреймворков.
А что думаете вы? Пишите в комментариях!
P.S: вы можете подписаться на мой телеграм-канал, в котором я освещаю различные темы из AI, а также на блог компании Raft, в нем собрано множество интересных статей.
Ссылки
Комментарии (3)
orefkov
09.08.2024 11:48+4Я в ML не особо, но всегда думал, что в работе с нейросетками python используется просто как обёртка вокруг вызова функций низкоуровневых библиотек, которые собственно и делают все вычисления, и что скорость собственно самого питона на общую скорость не особо влияет.
Dominux
09.08.2024 11:48До сих пор не понимаю этих недалёких людей, которые считают, что AI можно ускорить просто переписав AI-решения на что-то более производительное. Вероятно, они находятся на уровне студентов, которые знают, что C - быстрый, а Python - медленный.
Но докопаться хотя бы до того, что весь AI работает как минимум на уровне C/C++, но чаще всего (у адекватных людей) - на CUDA, а Python - чисто обёртка для простоты написания сервисов, сочетающий человеческий синтаксис и многие штучки из функциональщины, которую любят учёные, которые и развивают AI - у них не получается.
Но главное - переписать!
digtatordigtatorov
Максимально отвратительно не показательное сравнение. Выбрать только две задачи, сравнить только по скорости. Ну и как эта информация в конце статьи может нам дать хоть какое-то представление о том, лучше этот убийца питона или нет?
Где статистика? Почему не были запущены тесты несколько раз? Почему это не отражено в таблице? Может быть при решении задач с цифрами Ваш компьютер чихнул или пернул, откуда нам знать, что не было проблем с видеокартой? Почему нет информации о работе видеокарты или процессора во время обучения? Почему не приведена информация о системе, на которой решались эти задачи? Может вы вообще разные компы для каждого языка использовали?
За ссылку на тележку в конце статьи отдельная карма… Надоели безвкусные статьи, которые делаются на отье****, лишь бы подписоту вогнать в свой канал.