Использование предобученной нейросети ResNet для классификации изображений
Нейросети – удобный гибкий алгоритм, который можно обучить вычислениям. При успешно выстроенном процессе обучения, одну и ту же нейросеть можно будет использовать во множестве разных задач. Этим, например, знаменито семейство нейросетей ResNet, созданные для классификации изображений. Частично или целиком, подобные нейросети используются в самых разных задачах, где нужно работать с численными репрезентациями изображений, от графических баз данных до переноса стиля.
В этом примере мы запустим нейросеть ResNet небольшой глубины (18 слоёв) для классификации изображений. Покажем всю цепочку подготовки данных и обработки выходной информации, которая для любой картинки выдаст нам более или менее подходящую текстовую метку, характеризующую объект, изображенный на картинке.
Подготовительная работа
Объект Tape библиотеки Umlaut, на данный момент, является основным контейнером для вычислений, куда можно распаковать нейросеть из формата ONNX. Этот механизм запуска, скорее всего, будет изменен в ближайшем будущем, поскольку библиотека ONNX сейчас находится в процессе обновления.
Pkg.add(["Umlaut", "ONNX"])
import Pkg; Pkg.add("Umlaut", io=devnull)
import Umlaut: Tape, play!
Конечно, нам также понадобятся встроенные в Engee библиотеки для работы с форматом ONNX, в котором часто хранятся предобученные нейросети, и библиотека для работы с изображениями.
using ONNX
using Images
Установим рабочую папку
cd( @__DIR__ )
Классы объектов в ImageNet
Названия всех классов объектов, которые умеет распознавать наша предобученная нейросеть, представлены в качестве упорядоченного вектора и загружаются из следующего файла.
include( "imagenet_classes.jl" );
Функции ввода-вывода
Напишем три простые вспомогательные функции для подачи данных в нейросеть и обработки результатов:
- Загрузка изображения: мы масштабируем его до размера 244*244 (нормализация и обрезка краев с сохранением пропорций были бы желательным дополнением)
- Сортировка предсказаний: нейросеть возвращает вектор чисел, которые означают вероятность того, что на изображении наблюдается тот или иной класс из датасета ImageNet. Отберем
kнаиболее вероятно представленных классов - Оболочка для этих функций позволяет за одно действие загрузить изображение и выдать
kнаиболее вероятных предсказаний
# Загрузка изображения из файла
function imread(path::AbstractString; sz=(224,224))
img = Images.load(path);
img = imresize(img, sz);
x = convert(Array{Float32}, channelview(img))
# Заменим порядок слоев: CHW -> WHC
x = permutedims(x, (3, 2, 1))
return x
end
# Выдача индексов первых k предсказаний
function maxk(a, k)
b = partialsortperm(a, 1:k, rev=true)
return collect(zip(b, a[b]))
end
# Загрузка изображения и выдача десяти наиболее вероятных классов в убывающем порядке
function test_image(tape::Tape, path::AbstractString)
x = imread(path)
x = reshape(x, size(x)..., 1)
y = play!(tape, x)
y = reshape(y, size(y, 1))
top = maxk(y, 10)
classes = []
for (i, (idx, val)) in enumerate(top)
name = IMAGENET_CLASSES[idx - 1]
classes = [classes; "$i: $name ($val)"]
end
return join(classes, "\n")
end
Скачаем нейросеть ResNet18
Мы будем пользоваться нейросетью, которая лежит в файле с расширением *.onnx. Есть библиотеки, которые позволяют создать и загрузить эту нейросеть при помощи еще более высокоуровневых команд (например, библиотека Metalhead.jl из коллекции FluxML), но пока мы это сделаем без дополнительных библиотек.
Предобученная нейросеть уже находится в указанном каталоге, поэтому команда выполнится без повторного скачивания.
path = "resnet18.onnx"
if !isfile(path)
download("https://github.com/onnx/models/raw/main/vision/classification/resnet/model/resnet18-v1-7.onnx", path)
end
# Создадим пустую матрицу на месте которой будет входное изображение
img = rand( Float32, 224, 224, 3, 1 )
# Загружаем модель в виде объекта Umlaut.Tape
resnet = ONNX.load( path, img );
Загрузим несколько изображений
Если они уже загружены, аналогично, повторно они скачаны не будут
path = "data/"
goose_path = download( "https://upload.wikimedia.org/wikipedia/commons/3/3f/Snow_goose_2.jpg", path*"goose.jpg");
dog_path = download( "https://farm4.staticflickr.com/1301/4694470234_6f27a4f602_o.jpg", path*"dog.jpg");
plane_path = download( "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c9/Rossiya%2C_RA-89043%2C_Sukhoi_Superjet_100-95B_%2851271265892%29.jpg/1024px-Rossiya%2C_RA-89043%2C_Sukhoi_Superjet_100-95B_%2851271265892%29.jpg", path*"plane.jpg");
Классифицируем изображения
display( load(plane_path)[1:5:end, 1:5:end] )
print( test_image( resnet, plane_path ))
display( load(goose_path)[1:5:end, 1:5:end] )
print( test_image( resnet, goose_path ))
display( load(dog_path)[1:5:end, 1:5:end] )
print( test_image( resnet, dog_path ))
Заключение
Мы показали, что в Engee несложно скачать нейросеть и выполнить с ее помощью вычисления.
Этот механизм позволяет организовать сложный конвейер обработки информации, состоящий из высокоуровневых компонентов. В частности, доверить некотореы этапы обработки информации предобученным нейросетям.
Библиография: https://github.com/FluxML/ONNX.jl