Наскоро попаднах на глава в книгата на Франсоа Шоле Deep Learning With Python», описваща внедряването на Class Activation Mapping за мрежата VGG16. Той имплементира алгоритъма с помощта на Keras, тъй като той е създателят на библиотеката. Следователно инстинктът ми беше да внедря отново CAM алгоритъма с помощта на PyTorch.

Grad-CAM

Самият алгоритъм идва от тази хартия. Това беше страхотно допълнение към инструментите за анализ на компютърното зрение поради една основна причина. Предоставя ни начин да разгледаме кои конкретни части от изображението са повлияли на решението на целия модел за конкретно присвоен етикет. Той е особено полезен при анализиране на грешно класифицирани проби. Алгоритъмът Grad-CAM е много интуитивен и сравнително лесен за изпълнение.

Интуицията зад алгоритъма се основава на факта, че моделът трябва да е видял някои пиксели (или области от изображението) и да е решил какъв обект присъства в изображението. Влиянието в математически термини може да се опише с градиент. На високо ниво това прави алгоритъмът. Започва с намиране на градиента на най-доминиращия логит по отношение на най-новата активационна карта в модела. Можем да тълкуваме това като някои кодирани характеристики, които в крайна сметка се активираха в крайната карта за активиране, убедиха модела като цяло да избере този конкретен logit (впоследствие съответния клас). След това градиентите се обединяват по канали и каналите за активиране се претеглят със съответните градиенти, като се получава колекцията от претеглени канали за активиране. Като инспектираме тези канали, можем да кажем кои са изиграли най-важна роля в решението на класа.

В тази публикация ще внедря повторно алгоритъма Grad-CAM, използвайки PyTorch и, за да го направя малко по-забавно, ще го използвам с различни архитектури.

VGG19

В тази част ще се опитам да възпроизведа резултатите на Chollet, използвайки много подобен модел — VGG19 (имайте предвид, че в книгата той използва VGG16). Основната идея в моето изпълнение е да разчленя мрежата, за да можем да получим активирането на последния конволюционен слой. Keras има много ясен начин да направи това чрез функциите на Keras. В PyTorch обаче трябваше да прескоча някои незначителни обръчи.

Стратегията се определя, както следва:

  • Заредете модела VGG19
  • Намерете последния му конволюционен слой
  • Изчислете най-вероятния клас
  • Вземете градиента на logit на класа по отношение на картите за активиране, които току-що получихме
  • Обединете градиентите
  • Претеглете каналите на картата чрез съответните обединени градиенти
  • Интерполирайте топлинната карта

Отделих няколко изображения (включително изображенията на слоновете, използвани от Шоле в книгата си) от набора от данни на ImageNet, за да проуча алгоритъма. Също така приложих Grad-CAM към някои снимки от моя Facebook, за да видя как работи алгоритъмът в „полеви“ условия. Ето оригиналните изображения, с които ще работим:

Добре, нека заредим модела VGG19 от модула torchvision и подготвим трансформациите и програмата за зареждане на данни:

Тук импортирам всички стандартни неща, които използваме за работа с невронни мрежи в PyTorch. Използвам основната трансформация, необходима за използване на всеки модел, който е обучен на набора от данни на ImageNet, включително нормализирането на изображението. Ще подавам едно изображение наведнъж, затова определям моя набор от данни да бъде изображението на слоновете, в опит да получа подобни резултати като в книгата.

Тук идва сложната част (най-сложната в цялото начинание, но не твърде сложна). Можем да изчислим градиентите в PyTorch, като използваме метода .backward(), извикан на torch.Tensor. Точно това ще направя: ще извикам backward() на най-вероятния logit, който получавам, като извърша прехвърляне на изображението през мрежата. Въпреки това PyTorch кешира само градиентите на листовите възли в изчислителната графика, като тегла, отклонения и други параметри. Градиентите на изхода по отношение на активациите са просто междинни стойности и се отхвърлят като веднага щом градиентът се разпространи през тях на връщане. И така, какви са нашите възможности?

Закачете ги

Ако сте завършили Тексаския университет в Остин като мен, тази част ще ви хареса. В PyTorch има инструмент за обратно извикване: куки. Куките могат да се използват в различни сценарии, нашият е един от тях. Тази част от документацията на PyTorch ни казва точно как да прикрепим кука към нашите междинни стойности, за да изтеглим градиентите от модела, преди да бъдат изхвърлени. Документацията ни казва:

Куката ще се извиква всеки път, когато се изчислява градиент по отношение на тензора.

Сега знаем, че трябва да регистрираме обратната кука към картата на активиране на последния конволюционен слой в нашия VGG19 модел. Да намерим къде да се закачим.

Можем лесно да наблюдаваме архитектурата на VGG19, като извикаме vgg19(pretrained=True):

Предварително обучените модели в PyTorch използват силно модулите Sequential(), което в повечето случаи ги прави трудни за дисекция, ще видим примера за това по-късно.

На изображението виждаме цялата VGG19 архитектура. Маркирах последния конволюционен слой в блока с функции (включително функцията за активиране). Е, сега знаем, че искаме да регистрираме обратната кука на 35-ия слой на функционалния блок на нашата мрежа. Точно това смятам да направя. Също така си струва да се спомене, че е необходимо да се регистрира куката вътре в метода forward(), за да се избегне проблемът с регистриране на кука към дублиран тензор и последваща загуба на градиента.

Както можете да видите, в блока с функции остава оставащ максимален слой за обединяване, не се притеснявайте, ще добавя този слой в метода forward().

Това изглежда страхотно досега, най-накрая можем да извадим градиентите и активациите от модела.

Чертене на CAM

Първо, нека накараме напред да премине през мрежата с изображението на слоновете и да видим какво прогнозира VGG19. Не забравяйте да зададете модела си в режим на оценка, в противен случай можете да получите много произволни резултати:

Както се очакваше, получаваме същите резултати като Шоле в своята книга:

Predicted: [('n02504458', 'African_elephant', 20.891441), ('n01871265', 'tusker', 18.035757), ('n02504013', 'Indian_elephant', 15.153353)]

Сега ще направим обратното разпространение с logit на 386-ия клас, който представлява „African_elephant“ в набора от данни на ImageNet.

Накрая получаваме топлинната карта за изображението на слона. Това е 14x14 едноканално изображение. Размерът се определя от пространствените размери на картите за активиране в последния конволюционен слой на мрежата.

Сега можем да използваме OpenCV, за да интерполираме топлинната карта и да я проектираме върху оригиналното изображение, тук използвах кода от книгата на Chollet:

В изображението по-долу можем да видим областите на изображението, които нашата мрежа VGG19 взе най-сериозно при решаването кой клас („African_elephant“) да присвои на изображението. Можем да предположим, че мрежата е приела формата на главата и ушите на слоновете като силен знак за присъствието на слон в изображението. По-интересното е, че мрежата също направи разграничение между африканския слон и слонът Tusker и индийския слон. Не съм експерт по слоновете, но предполагам, че формата на ушите и бивните е доста добър критерий за разграничение. Общо взето, точно така би подходил човек към подобна задача. Експерт би изследвал ушите и формата на бивника, може би някои други фини характеристики, които биха могли да хвърлят светлина върху това какъв вид слон е.

Добре, нека повторим същата процедура с някои други изображения.

Акулите се идентифицират най-вече по областта на устата/зъбите в горното изображение и формата на тялото и заобикалящата вода в долното изображение. Много готино!

Преминаване отвъд VGG

VGG е страхотна архитектура, но оттогава изследователите излязоха с по-нови и по-ефективни архитектури за класифициране на изображения. В тази част ще проучим една от тези архитектури: DenseNet.

Има някои проблеми, на които се натъкнах, докато се опитвах да внедря Grad-CAM за гъсто свързаната мрежа. Първо, както вече споменах, предварително обучените модели от зоологическата градина на PyTorch са предимно изградени с вложени блокове. Това е чудесен избор за четливост и ефективност; това обаче повдига проблем с дисекцията на такива вложени мрежи. Забележете, че VGG се формира с 2 блока: функционален блок и напълно свързан класификатор. DenseNet е направен от множество вложени блокове и опитът да стигнете до картите за активиране на последния конволюционен слой е непрактичен. Има 2 начина, по които можем да заобиколим този проблем: можем да вземем последната карта за активиране със съответния слой за нормализиране на партида. Това дава доста добри резултати, както ще видим скоро. Второто нещо, което можем да направим, е да изградим DenseNet от нулата и да запълним отново теглата на блоковете/слоевете, така че да имаме директен достъп до слоевете. Вторият подход изглежда твърде сложен и отнема много време, затова го избегнах.

Кодът за DenseNet CAM е почти идентичен с този, който използвах за VGG мрежата, единствената разлика е в индекса на слоя (блок в случая на DenseNet), от който ще получим нашите активации:

Важно е да следвате архитектурния дизайн на DenseNet, затова добавих глобалното средно обединяване към мрежата преди класификатора (винаги можете да намерите тези ръководства в оригиналните документи).

Ще прекарам и двете изображения на игуана през нашата плътно свързана мрежа, за да намеря класа, който е присвоен на изображенията:

Predicted: [('n01698640', 'American_alligator', 14.080595), ('n03000684', 'chain_saw', 13.87465), ('n01440764', 'tench', 13.023708)]

Тук мрежата прогнозира, че това е изображението на „американски алигатор“. Хм, нека стартираме нашия Grad-CAM алгоритъм срещу класа „Американски алигатор“. В изображенията по-долу показвам топлинната карта и проекцията на топлинната карта върху изображението. Виждаме, че мрежата гледаше най-вече на „създанието“. Очевидно е, че алигаторите могат да изглеждат като игуани, тъй като и двете споделят формата на тялото и общата структура.

Забележете обаче, че има друга част от изображението, която е повлияла на резултатите на класа. Фотографът на снимка може да хвърли мрежата със своята позиция и поза. Моделът е взел предвид както игуаната, така и човека, докато е правил избора. Да видим какво ще се случи, ако изрежем фотографа от изображението. Ето топ 3 прогнози за класа за изрязано изображение:

Predicted: [('n01677366', 'common_iguana', 13.84251), ('n01644900', 'tailed_frog', 11.90448), ('n01675722', 'banded_gecko', 10.639269)]

Сега виждаме, че изрязването на човека от изображението всъщност е помогнало за получаването на правилния класов етикет за изображението. Това е едно от най-добрите приложения на Grad-CAM: възможност за получаване на информация за това какво може да се обърка в неправилно класифицирани изображения. След като разберем какво може да се е случило, можем ефективно да отстраняваме грешки в модела (в този случай изрязването на човека помогна).

Втората игуана е класифицирана правилно и ето съответната топлинна карта и проекция.

Преминаване отвъд ImageNet

Нека опитаме някои от изображенията, които изтеглих от страницата си във Facebook. Ще използвам нашия DenseNet201 за тази цел.

Изображението, на което държа котката си, е класифицирано по следния начин:

Predicted: [('n02104365', 'schipperke', 12.584991), ('n02445715', 'skunk', 9.826308), ('n02093256', 'Staffordshire_bullterrier', 8.28862)]

Нека да разгледаме картата за активиране на класа за това изображение.

На изображенията по-долу виждаме, че моделът изглежда на правилното място.

Да видим дали изрязването ще помогне с класификацията.

Изрязването ми помогна драматично:

Predicted: [('n02123597', 'Siamese_cat', 6.8055286), ('n02124075', 'Egyptian_cat', 6.7294292), ('n07836838', 'chocolate_sauce', 6.4594917)]

Сега Луна е предвидена поне като котка, което е много по-близо до истинския етикет (което не знам, защото не знам какъв вид котка е).

Последното изображение, което ще разгледаме, е изображението на мен, съпругата ми и моя приятел, които пътуваме със скоростен влак от Москва до Санкт Петербург.

Изображението е класифицирано правилно:

Predicted: [('n02917067', 'bullet_train', 10.605988), ('n04037443', 'racer', 9.134802), ('n04228054', 'ski', 9.074459)]

Наистина сме пред влак-стрела. Тогава нека да разгледаме картата за активиране на класове само за забавление.

Важно е да се отбележи, че последният конволюционен слой на DenseNet дава 7x7 пространствени карти за активиране (за разлика от 14x14 във VGG мрежата), следователно разделителната способност на топлинната карта може да бъде малко преувеличена, когато се проектира обратно в пространството на изображението (съответства на червеният цвят на вниманието върху лицата ни).

Друг потенциален въпрос, който може да възникне, е защо просто не изчислим градиента на logit на класа по отношение на входното изображение. Не забравяйте, че конволюционната невронна мрежа работи като екстрактор на функции и по-дълбоките слоеве на мрежата работят във все по-абстрактни пространства. Искаме да видим кои от характеристиките действително са повлияли на избора на класа на модела, а не само отделни пиксели на изображението. Ето защо е изключително важно да се вземат картите за активиране на по-дълбоките конволюционни слоеве.

Надявам се, че тази статия ви е харесала, благодаря ви, че я прочетохте.