воскресенье, 8 марта 2020 г.

Нейронные сети и компьютерное зрение

Это краткий пересказ курса Нейронные сети и компьютерное зрение.
В этой заметке больше внимания будет уделено практической части. Теорию можно почитать в предыдщей статье "Введение в нейронные сети".
Код практической части на гитхабе курса.


Библиотека PyTorch

Для ML будет использоваться библиотека PyTorch

Преобразование из numpy и обратно
Преобразование Numpy массива в torch tensor и обратно:
x = torch.from_numpy(x)
x = x.numpy()
Перенос данных в GPU
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
x_cuda = x.to(device)
Расчет производной и градиентный шаг:
import torch
#тензор с данными
w = torch.tensor([8., 8.], requires_grad=True)
#функция потерь, для которой считаем производную
def my_func(variable):
    return 10 * (variable ** 2).sum()
    
optimizer = torch.optim.SGD([w], lr=0.001)

#шаг градиента в сторону уменьшения потерь (в обратную сторону от производной)
def make_gradient_step(function, variable):
    function_result = function(variable)
    function_result.backward()
    optimizer.step()
    optimizer.zero_grad()

Задачи, решаемые при помощи нейронных сетей

Регрессия
Аппроксимация обучающей цифровой выборки в виде функции (нахождение вещественного числа от входных параметров)
Пример регрессии на PyTorch для предсказания функции 2**x * torch.sin(2**-x)

Бинарная классификация
Разделение выборки на 2 класса (да / нет)
Функция активации - сигмойда.
Для валидации лучше подходит функцию потерь: бинарная кросс-энтропия
BCE(p,t) = (t-1)*log(1-p) - t*log(p) Производная функции потерь BCE = сигма - реальное значение
Квадрат ошибки (MSE) - очень часто дает 0 при производной, т.е. по ней плохо обучаться (замедления обучения при уменьшении ошибки)

Классификация нескольких классов
Если выходов много, то лучше использовать softmax функцию активации = e^ответ/SUM(e^ответы)
Каждый выход дает результат от 0 до 1, но в сумме все выходы дают 1
Функция потерь = кросс энтропия
Производная функции потурь = pi-ti
Пример классификации вина на 3 типа по 2 признакам

Локализация
Выходы = вероятность, координаты, ширина/высота
Выходы "вероятность, координаты" можно обработать сигмойдой (ограничение: центр объекта должен быть на картинке, т.к. сигма 0,5 указывает на середину)
Функция потерь = BCE

Ширина/высота - как exp(выход), т.к. она (0-бесконечности)
функция потерь = выход - log(ширины или высоты)

Сегментация
Отделение искомого объекта от остальных (для каждого пикселя выдается скор, что в этом пикселе есть искомый объект)
Активация = сигмойда, т.к. ищем вероятность
Функция потерь = SUM(BCE) по всем пикселям картинки

Сжатие размерности
Кодирование картинки в меньший массив чисел
потеря = BCE по всем пикселям между исходной и на выходе после расшифрования

SuperreSolution
улучшение разрешения картинки (1пкс входной = 4 выходной)
обучение на сжатых больших картинках и сравнение с большими

Методы оптимизации скорости обучения

SGD - градиентный спуск
* стохастический - спуск на 1 примере
* батчевый - спуск на N примеров (в питоне также называется стохастическим)
стохастический спуск плох для вытянутых функций, т.к. нужно много шагов зигзагом, которые почти не смещаются
* стохастический спуск с моментумом (импульсом) - физическое моделирование катящегося шара (спуск идет не зигзагами, а затухающей синусойдой)
+ лосс функция больше влияет на скорость, а не только координаты (шар как бы ускоряется в сторону минимума)
* экспеденциальное скользящее среднее - к координате прибавляется среднее лосс функции

RPROP - learning rate = учитывает только знак градиента * learning rate *1.2 (+) или 0,6 (-)
RMSprop - подстройка learning rate под величину градиента (если медленно меняется - больше LR, быстро спускаемся - уменьшаем LR)
Adam - подстройка скорости (стохастически с импульсом + RMSprop) под величину градиента

Визуализация со сравнением разных типов оптимизаторов

Пример классификации рукописных цифр используя Adam (MNIST - тестовый сет)

Сверточные нейронные сети

Сверточные нейронные сети - для распознавания объекта на изображении в любой его части
(для обычной сети пришлось бы иметь картинки с объектами во всех возможных координатах)
Используется для решения проблемы ограниченных ресурсов и вычисления дополнительных данных изображения (какие пиксели находятся рядом)

Свертка:
* исходное изображение дополняется padding по 1 пикселю со всех сторону
* выбирается ярдро сверки: к примеру, матрица 3*3 из -1,0,1
* ядро последовательно прикладывается своим центром к каждому пикселю изображения (захватывая краями паддинг)
* каждый пиксель ядра перемножается с пикселем наложенного изображения и складывается, результат перезаписывает пиксель изображения
=> в каждом пикселе изображения у нас есть информация о соседях на удалении ядра
=> чем больше ядро, тем больше паддинг (максимальный размер паддинга = ядру)
=> сверку можно делать с большим другим шагом (stride) - это сожмет нам размер изображения / шаг (шаг увеличивается по обоим осям)
для каждого канала цвета (RGB) свое ядро - что является выходом

Пулинг:
* исходной изображение бьется на области размером strideX, strideY (обычно не пересекающиеся)
* выбирается максимум (или другая агрегация) из области
* двигаемся по областям
* записываем максимумы в результирующую матрицу
Что дает сжатие за счет выбора наиболее значимого значения

Пример программы расчета размера выходной матрицы после свертки на основе входящих размеров, шага и отступов:
input_matrix_shape содержит: число изображений в батче (0), число слоев в одном изображении (1), высота изображения (2), ширина изображения (3)
def calc_out_shape(input_matrix_shape, out_channels, kernel_size, stride, padding):
    hout = np.fix((input_matrix_shape[2] + 2*padding - 1 * (kernel_size-1)-1)/stride + 1)
    wout = np.fix((input_matrix_shape[3] + 2*padding - 1 * (kernel_size-1)-1)/stride + 1)
    out_shape = [input_matrix_shape[0], out_channels, hout, wout]
    return out_shape
В torch это делается функцией:
torch.nn.Conv2d(input_matrix_shape[1], out_channels, kernel_size, stride, padding ) 
Пример расчета выходных значений на основе реальных чисел:
Какого размера получится результат свёртки 5 на 5 без паддингов, со страйдом (1,1), если на выходе должно быть 6 каналов? Входное изображение имеет размер 32 на 32:
calc_out_shape([1,1,32,32], 6, 5, 1, 0) #== [1, 6, 28.0, 28.0]

Архитектуры предобученных сетей

LeNet
1. Свертка ядром 5*5 без паддингов (края по 4 пкс теряются) с шагом 1 в 6 разных фильтров
2. Пулинг 2*2 => в итоге получится 6 каналов изображение 28/2 = 14
3. Активация черзе гиперболический тангенс
4. сверка по 16 каналов с фильтром 5 на 5 => получится изображение 16 каналов размером 10*10
5. max пуллинг 2*2 => 16 каналов 5*5
6. Активация через гиперболический тангенс
7. 5*5 матрица сводится к массиву 25 элементов * 16 слоев == 1*1*400 тензор
8. полносвязанный слой (torch.nn.Linear(400, 120) ) => 120 элементов + тангенс
9. полносвязанный слой => 84 элементов + тангенс
10. полносвязанный слой => 10 элементов + softmax
функция потерь = кросс энтропия (т.к. хорошо работает с softmax)
оптимизатор: adam (с параметрами по умолчанию)
Пример реализаций через макспулинг и Relu

AlexNet
классификация на 1000 классов 15 млн изображений
масштабирование до: 224*224 (3 канала) - свертка 11*11 (шаг 4, паддинг = 5) - свертка 5*5 - пулинг - свертка 3*3 - макс пулинг - ... - полносвязанные списки (активация Relu) - вконце softmax
В AlexNet сигмойда заменена на ReLu функцию активации

Минус сигмойды: несколько сигмойд друг за другому дают затухание градиента, т.к. сигма'=сигма(1-сигма) , т.е. это число всегда <= 1/4 .
N подряд сигмойд дадут: 1/4**n -> очень маленькое число (затухание градиента)
Минус гиперболического тангенса: производная = (1-tan)*(1+tan) <= 1 . Т.е. нет затухания в 0, но на краях как сигмойда слишком пологая и затухание будет на больших значениях
ReLu постоянно растет от 0 - по этому нет затухания на всей выборке

GoogleNet и ResNet
Эти сети вводят понятия дополнительных блоков:
* inception block - тестирование нескольких сверток/пулинга (выбирается несколько вариантов и перемешиваются, запускаются параллельно)
* bottleneck - делается сверка 1 на 1 которая уменьшает число каналов (усреднение или максимизация каналов между сжимаемых)
результаты разных запусков конкатенируются в 1 картинку по каждому каналу
* промежуточные вспомогательные функции softmax на частях сети.
Это нужно чтобы распространять ошибку не с самого конца (что дает сильное затухание), а с этих промежуточных точек
* residual block - выходное значение = f(x) + входное x

Пример ResNet для распознования грязных тарелок

Проблема переобучения

Варианты решения:
* упрощение модели
* больше данных или аугментация картинки - обрезка/сжатие/трансформация/зашумленные случайным образом
* early stopping - останов обучения на train, когда ошибка на test начинает расти (или делать несколько прогонов с разным числом эпох и выбирать лучшую)
* регуляризация - к функции потерь дополнительно добавляется штрафующий коэффициент + L*SUM(a**2) - где a обучаемые коэф., L- регуляризационный параметр
также регуляризация изменяет внешний вид функции потерь - уменьшает кол-во минимумов, оставляя только основной глобальный (т.к. a**2 парабола с 1 минимумом, которая оттягивает на себя ошибки)
* dropout - P - вероятность пропадания сигнала между нейронами (или данных самого нейрона)
чем то напоминает зашумление (когда некоторые пиксели зануляются)

Нормализация данных

Как было сказано в разделе "Методы оптимизации скорости обучения" для нейронных сетей плохо, если данные сильно вытянуты или далеко смещены от 0.
Методы нормализации:
* смещение данных наблюдений ближе к 0
* масштабирование данных в круг, если они сильно растянуты (данные делятся на стандартное отклонение или логарифируются)

BatchNorm в PyTorch: * вычисляем среднее фичи
* (значение - среднее значение) / стандартное отклонение значения
* делаем этого на каждом слое в рамках 1 батча (только если вызвана net.train() )
* во время валидации используются средние значения за все батчи (стандартное отклонение не считается, только если вызван net.eval )

Пример LeNet с нормализаций для распознавания чисел
Пример CIFAR с нормализаций для распознавания изображений

Метод максимального правдоподобия

Цель - максимизация независимости наблюдений
* общая вероятность независимости наблюдений = Произведение(вероятность независимости наблюдения)
цель максимизировать Log( общая вероятность независимости наблюдений ) ==> SUM(log(вероятность независимости наблюдения)) ==> среднеквадратическая ошибка

Комментариев нет:

Отправить комментарий