Обновить

Поиск черной кошки в 2000-мерной темной комнате. Турнир алгоритмов машинного обучения

Уровень сложностиСредний
Время на прочтение22 мин
Охват и читатели7.3K
Всего голосов 7: ↑7 и ↓0+10
Комментарии19

Комментарии 19

Действительно исследование и выглядит интересно, хорошо что математика осталась в pdf) У меня исследовательский вопрос: r7 и r19, если их сделать r1000 и r1 или рядом подряд, меняется время, точность? Второй вопрос, а что если похожее изображение добавить, но с инверсией например, или урезать/добавить в "яркости".

На иллюстрации r7 и r19 были выбраны просто для примера. В реальности они перемешиваются случайным образом но с одинаковым seed, чтобы была повторяемость и разные алгоритмы были в полностью одинаковых условиях. На счет изображения верно, - его можно поменять и результаты изменятся. Я пробовал с другими но похожими изображениями, результаты численно были другие, но общая картина была схожей. Но... поскольку в качестве изображения можно подать всё что угодно... не берусь судить, что может произойти в каждом конкретном случае)

Понятно, а вот про другие изображения имел ввиду, что если при обучении параллельно будет присутствовать "клон" изображения, только помятый )

Код открыт, про PCA сами ответили - честно. Но "сломал физику" звучит громко.

Ваш поворот обратимый, геометрию данных он не меняет. Деревья и бустинги мрут на нём потому, что умеют резать только по отдельным осям, их давняя слабость, не сенсация. А каскад ловит форму целиком, и ему всё равно, повёрнуты оси или нет: вы это сами показываете - RMSE с поворотом и без одинаковый. Получается, задача идеально легла под ваш метод, а не "порвала всех вообще".

Проверьте на смешивании посложнее (не ровный поворот, а перекос) и на рваной, негладкой функции - вот там и станет ясно, метод сильный или комната удачно выбрана.

Простите, если где-то написал неправильно, я только разбираюсь в этом) Написал ради того, чтобы выглядеть умным, мне статья очень понравилась и вечером буду перечитывать.

Не буду отрицать. Некоторые элементы в статье, намеренная провокация)

В этом тестировании он выступал в роли новичка‑аутсайдера. Но то, что он сделал с фаворитами, выглядит как читерство.

Есть ли независимые исследования метода? Все ссылки в конце даны на ваши собственные статьи, судя по всему. Честно было бы упомянуть это обстоятельство.

Это просто веселая статья, а не полноценное исследование. Но код открыт и всё, что в ней изложено, можно повторить. В одной из публикаций, на которую дана ссылка есть более полноценные исследования на известных бенчмарках. Код по ним тоже на github выложен. Математика подробно описана. Независимых исследований метода я пока не встречал (от публикаций на arXIv прошло лишь несколько месяцев) и это как раз то, что мне бы очень хотелось увидеть

Статья интересная, да и работа немаленькая
Однако, раз под капотом у полигармонического каскада работает расстояние (которое сохраняется при умножении на матрицу поворота), то было бы честно сделать другое преобразование с данными (например, аффинное сжатие/нелинейное преобразование). А то получается, что вы читерите: задача — рубить дерево, инструменты — чайник vs полка vs бензопила...
К слову, было забавно, что для svr взяли rbf ядро. Страшный вы человек, однако)))
В любом случае, спасибо за статью, было интересно

Улучшив "17_test_nnet3.py" думаю я разгромил PHC:
Device: cuda, Загрузка данных: rm=2000, rotate=True
Эпоха: 199 Ошибка обучения, RMSE: 0.0130, 0:00:02.410549 сек
Ошибка валидации, RMSE: 0.0143
0.0138, 0:10:01.892468, 0:00:03.905147

Очень интересно. Можно увидеть код?

class Block(nn.Module):
    def __init__(self, in_dim, expand, out_dim):
        super().__init__()
        self.i_l = nn.Linear(in_dim, in_dim * expand * 2)
        self.o_l = nn.Linear(in_dim * expand, out_dim)
        self.act = nn.Tanh()

    def forward(self, x):
        x, y = torch.chunk(self.i_l(x), 2, -1)
        return self.o_l(self.act(x * y))

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(rm, 3)
        self.fc2 = nn.Linear(3, 50)
        self.fc3 = Block(50, 2, 50)
        self.fc4 = Block(50, 2, 50)
        self.fc5 = Block(50, 2, 50)
        self.fc6 = Block(50, 2, 50)
        self.fc10 = nn.Linear(50, 1)

    def forward(self, x):
        x = self.fc1(x)
        x = self.fc2(x)
        x = self.fc3(x)
        x = self.fc4(x)
        x = self.fc5(x)
        x = self.fc6(x)
        x = self.fc10(x)
        return x

scheduler = torch.optim.lr_scheduler.MultiplicativeLR(
    optimizer, lambda epoch: 0.98
)

    with torch.no_grad():
        mean_loss /= batch_n
        mean_loss = math.sqrt(mean_loss)
        scheduler.step()

Это все изменения.

Возник вопрос... но давай.. чтобы исключить любое непонимание или несоответствие... можно целиком весь 17_test_nnet3.py в новой версии? пожалуйста ;)

Скрытый текст
from torchvision import transforms
import datetime as dt
import torch
from torch.utils.data import Dataset, DataLoader
import torch.nn as nn
import math
import os
import im2data

fileIm = "1.png"
n = 240000
#rm = 2             # общее число признаков Раунд 1
#rm = 10            # общее число признаков Раунд 2,3
#rm = 500           # общее число признаков Раунд 4,5     
#rm = 1000          # общее число признаков Раунд 6,7
rm = 2000          # общее число признаков Раунд 8,9       
#rotate = False     # поворот пространства признаков Раунд 1,2,4,6,8
rotate = True     # поворот пространства признаков Раунд 3,5,7,9

EPOCHS = 200       # раунд 1,2,3
# EPOCHS = 400       # раунд 4,5
# EPOCHS = 1000       # раунд 6,7
# EPOCHS = 2000       # раунд 8,9

use_gpu = True    # почему-то на моём железе, на cpu эта нейросеть работала быстрее
device = torch.device('cuda' if use_gpu and torch.cuda.is_available() else 'cpu')


print(f"Device: {device}, Загрузка данных: rm={rm}, rotate={rotate}")

im, X_train_full, X_test_new, X_train, y_train, X_test, y_test, rot_mat = \
    im2data.obraz2d2(fileIm, n, rm, rotate=rotate)


class MyDataset(Dataset):
    def __init__(self, X, y):
        self.X = X
        self.y = y

    def __len__(self):
        return self.X.shape[0]
  
    def __getitem__(self, index):
        return (self.X[index], self.y[index])
    
train = MyDataset(X_train,y_train)
test = MyDataset(X_test, y_test)
trainset = DataLoader(train, batch_size=500, shuffle=True)
testset = DataLoader(test, batch_size=30000, shuffle=False)

act = nn.Tanh()

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(rm, 3)
        self.fc2 = nn.Linear(3, 30)
        self.fc3 = nn.Linear(30, 30)
        self.fc4 = nn.Linear(30, 30)
        self.fc5 = nn.Linear(30, 30)
        self.fc6 = nn.Linear(30, 30)
        self.fc7 = nn.Linear(30, 30)
        self.fc8 = nn.Linear(30, 30)
        self.fc9 = nn.Linear(30, 30)
        self.fc10 = nn.Linear(30, 30)
        self.fc11 = nn.Linear(30, 1)

    def forward(self, x):
        x = self.fc1(x)
        x = self.fc2(x)
        x = act(x * self.fc3(x))
        x = act(x + self.fc4(x))
        x = act(x * self.fc5(x))
        x = act(x + self.fc6(x))
        x = act(x * self.fc7(x))
        x = act(x + self.fc8(x))
        x = act(x * self.fc9(x))
        x = act(x + self.fc10(x))
        return self.fc11(x)

# torch.manual_seed(0)
startL = dt.datetime.now()
model = Net().to(device)
torch.set_float32_matmul_precision('high')
model = torch.compile(model, mode="reduce-overhead")

# criterion = nn.CrossEntropyLoss()
criterion = nn.MSELoss()
# criterion = nn.NLLLoss()

optimizer = torch.optim.Adam(model.parameters(), lr=0.003)
# optimizer = torch.optim.SGD(model.parameters(), lr=0.02, momentum=0.9)
scheduler = torch.optim.lr_scheduler.MultiplicativeLR(
    optimizer, lambda epoch: 0.98
)

train_loss = []
val_loss = []
for epoch in range(EPOCHS):
    start = dt.datetime.now()
    print(f'Эпоха: {epoch}', end=' ')
    mean_loss = 0
    batch_n = 0
    for X, y in trainset:
        model.zero_grad()
        output = model(X.to(device))

        loss = criterion(output, y.to(device))
        loss.backward()
        optimizer.step()

        mean_loss += loss
        batch_n += 1

    with torch.no_grad():
        mean_loss /= batch_n
        mean_loss = math.sqrt(mean_loss)
        scheduler.step()
        train_loss.append(mean_loss)
        print(f'Ошибка обучения, RMSE: {mean_loss:.4f}, {dt.datetime.now() - start} сек')

    mean_loss = 0
    batch_n = 0
    with torch.no_grad():
        for X, y in testset:
            output = model(X.to(device))
            loss = criterion(output, y.to(device))

            mean_loss += loss
            batch_n += 1
    
    mean_loss /= batch_n
    mean_loss = math.sqrt(mean_loss)
    val_loss.append(mean_loss)
    print(f'Ошибка валидации, RMSE: {mean_loss:.4f}, lr: {optimizer.param_groups[0]["lr"]:.5f}')

train_time = dt.datetime.now() - startL

# Проверка результата
start = dt.datetime.now()
var = model(X_test_new.to(device))
pred_time = dt.datetime.now() - start
var[var<0] = 0
var[var>1] = 1
var = var.reshape(im.shape[0],im.shape[1])
var = var.to('cpu')

# Ошибка
rmse_full = torch.mean((var - im) ** 2) ** 0.5
rmse_full = rmse_full.item()

print(f'{rmse_full:.4f}, {train_time}, {pred_time}')

Ещё улучшил. И сделал слои как в PHC.
Эпоха: 199 Ошибка обучения, RMSE: 0.0197, 0:00:02.415678 сек Ошибка валидации, RMSE: 0.0209, lr: 0.00005 0.0204, 0:09:54.106488, 0:00:03.664881

Попробовал задавать каскаду на валидацию более плоскую картинку, начал изолировать друг от друга объекты, потом заметил такую особенность - если для финального теста использовать перемешивание столбцов от обучения, то ошибка норм, а если другое, то не норм

П.С. потом понял, что модель и не должна в других признаках/столбцах искать ))

вот если использовать неизменный код (искаженная картинка сейчас одинакова с оригиналом)

если изменить код, добавить отдельное перемешивание

то вот так

Может у них одинаковый порядок должен быть? Но вроде как это отдельный тест и не должен быть связан с обучающим сетом

# Предсказания на полной тестовой выборке var = pc.FlammaPars(X_test_new, 50000)

возможно нужно не 10 эпох обучения на 10 признаках делать, попробую подольше

Так задача в том, чтобы аппроксимировать сложную функцию от двух признаков, двух столбцов в таблице (если пока не рассматриваем поворот). Но алгоритм не знает от каких. И в процессе обучения он должен их найти. А при тестировании нельзя в тестовом множестве эти два столбца трогать, переставлять с другими, иначе это как обман модели по отношению к тому чему её учили ).. Но вот другие признаки, шумовые, - их можно (и нужно) сгенерировать совсем по новому. Если правильно модель обучилась, то она должна их проигнорировать

спасибо, это мой первый опыт с обучением, давно хотел попробовать

Очень интересно. Но все, что взято из GitHub под Python 3.13.7 не работает

python 21_test_polyharm.py

Traceback (most recent call last):

File "f:\black-cat-main\21_test_polyharm.py", line 18, in <module>

fileIm = test_utils.get_image_path("1.png")

^^^^^^^^^^^^^^^^^^^^^^^^^

AttributeError: module 'test_utils' has no attribute 'get_image_path'


Верно! Я забыл добавить в репозиторий файл test_utils.py Спасибо! Теперь добавил

Вот итоговая валидация с повернутой картинкой

Странно почему при 0.259 так хорошо смотрится (а оказывается я не на том считал разницу, разница с оригиналом 0.038), хотя видны артефакты, а так выглядит очень похоже, получилось что-то вроде восстановления. Проверил не будет ли из белого шума создавать

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации