AI Geek Programmer https://aigeekprogrammer.com/pl/ Machine learning / computer vision engineer and manager Sun, 04 Dec 2022 19:12:46 +0000 pl-PL hourly 1 https://wordpress.org/?v=6.1.1 https://aigeekprogrammer.com/wp-content/uploads/2020/01/cropped-AI-Geek-Programmer-Site-Icon-32x32.jpg AI Geek Programmer https://aigeekprogrammer.com/pl/ 32 32 Przywitajmy jutro – czyli jak AI zmieni świat przez najbliższe 10 lat https://aigeekprogrammer.com/pl/przywitajmy-jutro-czyli-jak-ai-zmieni-swiat-przez-najblizsze-10-lat/ https://aigeekprogrammer.com/pl/przywitajmy-jutro-czyli-jak-ai-zmieni-swiat-przez-najblizsze-10-lat/#respond Sun, 04 Dec 2022 19:04:37 +0000 https://aigeekprogrammer.com/?p=9031 Kiedy w 2019 roku zacząłem tworzyć wpisy na tym blogu, pozwoliłem sobie na małą prowokację pisząc „AI zmieni świat bardziej niż rewolucja przemysłowa„. Oczywiście przywidywanie jest bardzo trudne, szczególnie jeżeli dotyczy przyszłości (Niels Bohr), ale ostatnie osiągnięcia w obszarze uczenia maszynowego zachęciły mnie do przemyśleń...

The post Przywitajmy jutro – czyli jak AI zmieni świat przez najbliższe 10 lat appeared first on AI Geek Programmer.

]]>
Kiedy w 2019 roku zacząłem tworzyć wpisy na tym blogu, pozwoliłem sobie na małą prowokację pisząc „AI zmieni świat bardziej niż rewolucja przemysłowa„. Oczywiście przywidywanie jest bardzo trudne, szczególnie jeżeli dotyczy przyszłości (Niels Bohr), ale ostatnie osiągnięcia w obszarze uczenia maszynowego zachęciły mnie do przemyśleń na temat tego, jak AI zmieni świat w najbliższych 10 latach. A zmieni dość mocno i to jest w zasadzie pewne. W końcu 10 lat to szmat czasu, szczególnie jeżeli technologia wkracza na wykładniczą ścieżkę wzrostu.

Warto w tym miejscu zauważyć, że na takie przemyślenia czeka potencjalna pułapka – łatwo być w przewidywaniach zbyt optymistycznym. Wystarczy sięgnąć do filmów lub tekstów przewidujących 30 lat temu jak będzie wyglądał świat w 2020. Pełno było w nich przewidywań o robotach, latających pojazdach i kolonizacji Księżyca. Nic z tego się nie spełniło. Warto więc podejść do poniższych przewidywań z przymrużeniem oka i z pewnym dystansem.

Zanim przejdziemy do wróżenia, spójrzmy najpierw na kilka ważnych wydarzeń AI w tym kończącym się 2022 roku. Uczenie maszynowe jest już tak szeroką i tak dynamicznie rozwijającą się dziedziną, że w zasadzie brak jest możliwości śledzenia wszystkich wartościowych projektów. Pisząc o ważnych wydarzeniach mam na myśli następujące trzy projekty, które dla mnie w pewnym sensie wyróżniły się z tłumu: Stable Diffusion, GitHub Copilot i udostępniony w ostatnich dniach listopada 2022 ChatGPT. Ten post nie jest o żadnym z tych rozwiązań. Jednak w kilku zdaniach zasygnalizuję, co każdy z tych projektów oferuje. Jeżeli nie widzieliście ich jeszcze w akcji, to warto się tym bliżej zainteresować – przyszłość puka do drzwi.

Stable Diffusion umożliwia generowanie wysokiej jakości obrazów na podstawie tekstu wprowadzonego przez człowieka. Został zbudowany przez kilka organizacji z wiodącą rolą CompVis – Computer Vision & Learning research group na Uniwersytecie Ludwika i Maksymiliana w Monachium oraz firmy Stability AI. Poniżej próbka możliwości rozwiązania ze strony https://stability.ai/blog/stable-diffusion-v2-release. Już wcześniej pojawiały się modele typu text-to-image, takie jak chociażby DALL-E, ale dopiero Stable Diffusion zaoferował kod i parametry modelu w formule open source. Ten i podobne modele mają szansę na zrewolucjonizowanie pracy kreatywnej. BTW: już powstały modele, które generują video.

Example from https://stability.ai/blog/stable-diffusion-v2-release

GitHub Copilot jest narzędziem AI, z którego mogą skorzystać programiści. W dużym skrócie: zaczynacie pisać kod lub wskazujecie co chcecie stworzyć i Copilot podpowiada wam propozycję dalszej części kodu. Na chwilę obecną jest to taki bardziej interaktywny, bardziej inteligentny i łatwiejszy do wykorzystania Stack Overflow. Do narzędzia trzeba się przyzwyczaić, ale wydaje się, że tkwi w nim duży potencjał do podniesienia efektywności. Z narzędziem wiążą się oczywiście kontrowersje związane z prawami autorskimi, pytaniem na ile generowany kod powinno się sprawdzać i nadzorować, niektórzy nie czują się komfortowo prowadząc ciągłą inspekcję nieswojego kodu. Są też oczywiście obawy o to czy takie narzędzia nie zniszczą rynku pracownika w IT. Jestem „z branży”, więc w tym miejscu temat zaczyna się robić nieco delikatny ;-). Idźmy zatem dalej.

Przykład działania GitHub Copilot ze strony https://github.com/features/copilot

ChatGPT jest tak świeżutki, że w dniu pisania tego posta nawet nie ma strony na Wikipedii. Warto tu nadmienić, że jest to model oparty o GPT-3.5, ale z interfejsem zoptymalizowanym w kierunku konwersacji. Jak twierdzą autorzy „The dialogue format makes it possible for ChatGPT to answer followup questions, admit its mistakes, challenge incorrect premises, and reject inappropriate requests”. ChatGPT stał się dość szybko viralem, szczególnie na Twitterze. Przy okazji mała uwaga dla produktowców: silna obecność tematu w mediach daje sporo do myślenia w zakresie tego, jak odpowiednie opakowanie może zmienić percepcję, użycie i ocenę istniejącego produktu (bo GPT-3.5 został już udostępniony w Q1 2022). Funkcjonalnie, oczywiście w dużym skrócie, jest to model konwersacyjny, czyli można z nim porozmawiać na najróżniejsze tematy, można prosić o napisanie krótkiego eseju, wejść w rozmowę na trudne egzystencjalne kwestie. Na Twitterze jest mnóstwo ciekawych przykładów rezultatów zwracanych przez model. Niektóre z nich są mocno zabawne, niektóre dają mocno do myślenia. Duża część z nich, w tym moje poniżej, to nie rozmowa, a bardziej prośba o wygenerowania tekstu spełniającego konkretne, czasami dziwne wymagania.

Chat można sobie samemu wypróbować rejestrując się na stronie https://openai.com/blog/chatgpt/. Z modelem można bawić się na miliony sposobów. Ja dla przykładu poprosiłem go o napisanie krótkiej historii o gekonie, który wykorzystując prywatną znajomość z Elonem Muskiem wybrał się na Marsa.

Wygenerowane na https://chat.openai.com/chat BTW: 96 słów, lol

Chcesz napisać przykładowa pracę pisemną na maturę z angielskiego? Proszę bardzo. Wziąłem z netu przykładowy temat (uwaga: link prowadzi do PDFa: https://www.ceziu.pl/images/dokumenty_pdf/matura_angielski/Matura-j.angielski-przykadowe-prace-pisemne.pdf):

Wróciłeś właśnie z ferii Wielkiej Brytanii. Po powrocie zorientowałeś się, że zabrałeś przez pomyłkę, jakaś rzecz należącą do koleżanki z Danii, z którą wynajmowałeś pokój.
Napisz list, w którym:
• Wyjaśnisz na czym polega pomyłka i poinformujesz, jak do niej doszło.
• Opiszesz pomyłkowo zabraną rzecz, podając przynajmniej jej dwie cechy.
• Poprosisz o sprawdzenie, czy kolega / koleżanka nie zabrał/a podobnej rzeczy należącej do ciebie i podaj element, który je różni
• Obiecasz odesłane pomyłkowo zabranej rzeczy i poprosisz o to samo.
Pamiętaj o zachowaniu odpowiedniej formy i stylu listu. Nie umieszczaj żadnych adresów. Podpisz się jako XYZ. Długość listu powinna wynosić od 120 do 150 słów.

Przetłumaczyłem w Google Translate do poniższej postaci …

Just got back from UK holidays. After returning, you realized that you took, by mistake, some thing belonging to a friend from Denmark, with whom you rented a room.
Write a letter in which:
• Explain what the mistake is and how it happened.
• You will describe the mistakenly taken item, giving at least two of its characteristics.
• You will ask to check whether a colleague has taken a similar item belonging to you and specify the element that makes them different
• Promise to return the wrongly taken item and ask for the same.
Remember to keep the proper form and style of the letter. Do not include any addresses. Sign as XYZ. The length of the letter should be between 120 and 150 words.

i poprosiłem ChatGPT o napisanie listu zgodnie z tymi wytycznymi. Oto wynik:

Wygenerowane na https://chat.openai.com/chat

Zacząłem się przez chwilę zastanawiać z synem, ile można w jego liceum wziąć od uczniów za napisanie jednego zadania domowego z angielskiego, ale doszliśmy szybko do wniosku, że wieści rozejdą się błyskawicznie i biznes upadnie. Plus nie do pominięcia jest fakt, że mogłoby to być źle odebrane przez szkołę. 😉

Wiemy co leży na stole dziś, wybierzmy się zatem w podróż w czasie…

Przez głupie eksperymenty z czasoprzestrzenią, jakie w weekendowe wieczory prowadzisz w garażu, obudziłeś się niespodziewanie w roku 2032. Nerwowo wpisujesz na telefonie google.com, aby sprawdzić co się dzieje na świecie. Jak to „Ta witryna jest nieosiągalna„? Czyżby Google wypadł z gry?

Nieeee, no żartuję. Z Google wszystko będzie ok, bo od wielu lat mają świetne zespoły AI i na pewno pracują nad nowym modelem wyszukiwania informacji. Takim, który będzie można przede wszystkim bardzo skutecznie monetyzować – jak obecny model. Teoretycznie jednak, jeżeli Google z tym niewiele zrobi, to jeżeli ktoś w 2032 roku będzie chciał się dowiedzieć kto i dlaczego został prezydentem Stanów Zjednoczonych w 2028, to w zasadzie będzie mógłby spytać o to ChatGPT w jego wersji 7, czy jaka tam wersja będzie wtedy obowiązująca. Będzie mógł o to spytać, podobnie jak my możemy już spytać obecną wersję ChatGPT o wybory w 2016 roku.

Wygenerowane na https://chat.openai.com/chat

A całkiem serio, sposób w jaki będziemy prowadzić wyszukiwanie informacji będzie w 2032 roku całkowicie inny i oparty w większości o sztuczną inteligencję z interfejsem konwersacyjnym.

Istotnej zmianie ulegnie również sposób, w jaki rozwiązujemy skomplikowane problemy w praktycznie wszystkich dziedzinach nauki. Obecnie powszechnie przyjętym schematem jest stworzenie zespołu wysokiej klasy specjalistów w danej dziedzinie, którzy budują lub wykorzystują teoretyczne podstawy danej nauki, aby w praktyce stworzyć nowy artefakt świata fizycznego: nowy lek, nową substancję chemiczną, czy nowe urządzenie. Na początku następnej dekady powinniśmy obserwować przesunięcie tego schematu w kierunku najpierw zbudowania modelu AI, który będzie w stanie zaproponować teoretyczne lub praktyczne rozwiązanie danego problemu, a dopiero potem przejęcie, sprawdzenie i wdrożenie w praktyce pomysłu przez zespół specjalistów.

Powszechne stanie się korzystanie z doradców AI. Już obecnie nasz ChatGPT jest w stanie podjąć próbę rozwiązania niektórych problemów różnej natury. Stable Diffusion może nas natchnąć ciekawym pomysłem na oprawę graficzną. A GitHub Copilot podpowiedzieć kod. Rozwiązując poniższy problem ChatGPT popełnił dość oczywisty i prosty błąd. Ale jest to model językowo-konwersacyjny, a nie model matematyczny. To co robi największe wrażenie już obecnie, to właściwy – iteracyjny sposób podejścia modelu do rozwiązania problemu.

Wygenerowane na https://chat.openai.com/chat

W najbliższych latach powstaną wyspecjalizowane modele będące w stanie wspomagać inżynierów w rozwiązywaniu trudnych zagadnień. Te modele, które będą nieco prostsze i tańsze lub darmowe (np. matematyczne, językowe, historyczne) ograniczą sens zadawania nastolatkom szkolnych zadań domowych. Skoro nastolatek będzie w stanie na komórce rozwiązać zestaw równań lub wygenerować pracę językową, nie będzie sensu kontynuowania wątpliwej według mnie praktyki zlecania zadań domowych. Wymusi to w końcu zmianę sposobu nauczania dzieci i młodzieży. Nastąpi odejście od modelu opartego na zapamiętywaniu setek, często bezużytecznych w danej chwili dla dziecka informacji, w kierunku modelu, który promuje umiejętność znalezienia informacji i rozwiązania z ich pomocą danego problemu. Oczywiście już i teraz są kraje, które doskonale zreformowały szkolnictwo. Piszę zatem o tym głównie z perspektywy ojca mającego syna w polskiej szkole.

Rewolucja modeli specjalistycznych (tzw. Narrow AI) będzie dotyczyła również innych branż, często mniej technicznych niż IT, fizyka, chemia czy biologia. Mogę wyobrazić sobie robota AI analizującego w 2032 stan prawny w danym obszarze i doradzającego profesjonalnemu prawnikowi w kwestii podejścia do danej sprawy karnej. W krajach gdzie dostęp do służby zdrowia jest mocno ograniczony, powszechne stanie się korzystanie z serwisów medycznych. Dla mieszkańców zachodniego świata być może wydaje się to obecnie nie do przyjęcia, ale mieszkańcy wielu regionów naszej planety po prostu mogą nie mieć fizycznej możliwości bezpośredniego skonsultowania się z dermatologiem lub psychologiem lub po prostu nie stać ich na to. Obecnie podobne odejście od usług tradycyjnych do usług następnej generacji obserwujemy w krajach, w których z różnych przyczyn mamy do czynienia z uciskiem finansowym. Hiperinflacja, opresyjne rządy, brak prostego dostępu do usług bankowych powodują, że ludzie naturalnie sięgają do płatności Bitcoinem. Życie nie znosi próżni, więc jeżeli nie będzie dostępu do lekarza, a będzie dostęp do internetu, to sami wiecie co się stanie.

Wygenerowane na https://chat.openai.com/chat

Rewolucja AI nie ominie oczywiście samej branży IT. Czy piłujemy gałąź, na której obecnie wygodnie siedzimy? Ogólny schemat tego, w jaki sposób tworzymy oprogramowanie niewiele zmienił się w ostatnich 20 latach. Nadal należy ustalić ze sponsorem / klientem wymagania biznesowe, przełożyć je na wymagania techniczne, zaprojektować architekturę i zacząć pisać kod. Kod jest w IT źródłem prawdy. Prawdy o tym jak działa system i prawdy o tym jak dobry jest programista. Umiesz dobrze kodować? Będziesz dobrze żył! Oczywiście zmieniły się języki programowania, powstały tysiące zaawansowanych narzędzi, agile na całe szczęście zdominował styl pracy, ale ogólna metodologia pozostała niezmieniona. Dodatkowo, AD 2022 mamy rynek pracownika, zapotrzebowanie na usługi IT pozostaje niezmiennie wysokie i prawie wszyscy są zadowoleni. Co może pójść nie tak? 😉

Obecnie rozwiązania typu Copilot będą pełniły rolę lokalnego Stack Overflow. Jednak czy nam się to podoba czy nie, w przeciągu kilku lat różnej maści copiloty staną się coraz bardziej zaawansowane. W mojej opinii kodowanie będzie stopniowo ewoluowało od pisania kodu poprzez wystukiwanie każdego znaku na klawiaturze, przerywanego okresowym copy-paste ze Stack Overflow, do opisywania oczekiwanego rezultatu i analizy zaproponowanego przez copilota rozwiązania. Nadal konieczna będzie bardzo dobra znajomość składni, nadal niezbędne będzie rozumienie i ręczne programowanie przepływu informacji na wyższym poziomie, ale efektywność tworzenia prostych funkcjonalności czy to biznesowo-backendowych czy frontendowych będzie dużo wyższa. Dużo bardziej zaawansowana będzie analiza poprawności zapisanego kodu i usuwania błędów. Pamiętajmy, że w 2032 roku tego typu konwersacja jak poniżej, będzie dużo bardziej skuteczna, szybsza i precyzyjna.

Żródło: https://openai.com/blog/chatgpt/

Jest 2022, jestem programistą, co ja mam teraz zrobić? Jak mam utrzymać rodzinę?” Myślę, że obawy co do tego, że AI zabierze nam – informatykom – część pracy są nieco przesadzone lub przedwczesne. Niemniej, jako programista zamierzam z ciekawością obserwować te narzędzia i próbować włączać je do swojego arsenału umiejętności. A jako menadżer zamierzam z ciekawością obserwować te narzędzia i zachęcać programistów do stopniowego włączania ich do arsenału ich umiejętności. 😉

Wracamy do rzeczywistości AD 2032. A tam bardzo duże państwo (oznaczmy je literą C), które ma chrapkę na pewną niewielką wyspę, ale boi się dominacji militarnej innego bardzo dużego państwa (oznaczmy je literą A), jest w trakcie wprowadzania znacznej reformy w swojej armii i uzbrojeniu. Reforma ta została zainicjowana po bardzo szczegółowej analizie, jaką C robiło w trakcie napaści Rosji na Ukrainie w 2022. Wojna ta zakończyła się ostatecznie totalną porażką Rosji i upadkiem reżimu, ale zwróciła uwagę wszystkich na wyjątkową skuteczność działania małych pół-autonomicznych jednostek bojowych, które wówczas nazwane były dronami oraz oczywiście na ogólne znaczenie przewagi technologicznej (to akurat znane było od stuleci). Państwo C, czy nam się to na Zachodzie podoba czy nie, spędziło całą dekadę lat 20 na budowaniu coraz większych kompetencji w militarnym wykorzystaniu AI, dzięki czemu uważa się obecnie (w 2032), że C jest w stanie militarnie przyłączyć małą wyspę do swojego terytorium i to mimo wsparcia tej wyspy przez A i przez inne kraje zachodnie.

Wyżej opisana sytuacja zaskoczyła nieco zachodni establishment. Wprawdzie Państwo A ma również bardzo zaawansowane rozwiązania militarne, nawet bardziej zaawansowane niż C, ale ze względu na odległość dzielącą małą wyspę od A, pomoc może być ograniczona. Europa w zasadzie przestała się liczyć w tej kwestii. Protesty społeczne, jakie pojawiły się w Europie pod koniec lat 20 w związku z obawami o zabieranie miejsc pracy przez AI, oraz w związku z ogromnym zapotrzebowaniem AI na energię niezbędną do trenowania coraz większych modeli, zwróciły uwagę europejskich populistycznych polityków, którzy chcąc zyskać na popularności i powołując się na dobro społeczne oraz ekologię uzyskali poparcie dużej części europejskich wyborców i zaczęli hamować rozwój AI w Europie. Czytaliście może Diunę? Dla mnie jedna z trzech najlepszych powieści science-fiction. Genialny Herbert w roku 1963 (sic!) opisał w niej profesję Mentata jako ludzkiego odpowiednika komputerów. Ludzi trenowano na mentatów, gdyż po wojnie ludzi z maszynami prawo zabraniało tworzenia maszyn na podobieństwo ludzkiego umysłu. Mam nadzieję, że Frank był po prostu genialny a nie, że był prorokiem losów Europy.

Ogromnym zagadnieniem przez całą dekadę lat 20 będą kradzież tożsamości, wszelkiego rodzaju fake newsy i związane z tym inklinacje do cenzurowania. Temat cenzury i wolności słowa nieco odbiega od tematu posta. Nadmienię więc jedynie, że z perspektywy post-covidowej w mojej opinii duże domy medialne nie zdały egzaminu i nie są już dla społeczeństw żadną gwarancją, że prezentowane nam papki informacyjne są zbliżone do prawdy i dają pole do dyskusji. W dużej części przypadków okazało się, że prezentowane treści są albo niesprawdzone, albo umotywowane politycznie lub ekonomicznie (ukierunkowane na sensację i klikalność). Największe nadzieje w zakresie wolności słowa wiążę z Twitterem, który pod rządami Muska ma szanse stać się najlepszym źródłem informacji, bo informacji płynącej bezpośrednio od zróżnicowanej poglądowo grupy ludzi zainteresowanych daną dziedziną. Jeżeli treści na Twitterze nie będą tendencyjnie filtrowane, to będzie to dużo lepsza platforma informacyjna niż serwowana nam papka przefiltrowana przez pana redaktora naczelnego nawet najlepszego outletu medialnego.

W mojej opinii wraz ze wzrostem możliwości AI walka z kradzieżą tożsamości i fake news będzie coraz trudniejsza. Mogę wyobrazić sobie, że realizowana obecnie telefonicznie „metoda na wnuczka” może być niewielkim kosztem efektywniej zrealizowana na warstwie wizualno-głosowej, gdzie AI w czasie rzeczywistym zmoduluje wygląd i brzmienie głosu, próbując wyłudzić od atakowanego cenne informacje lub zmusić go do przekazania środków pieniężnych. Ja swojemu synowi już przekazałem, że nawet jakby wyświetlił mu się mój numer, nawet jakby usłyszał w telefonie mój głos i nawet jak na facetime zobaczy moją twarz, a ten niby-ja będzie się zarzekał, że potrzebuje kasy, bo znalazł się w trudnej sytuacji o 3 ranem, to żeby pod żadnym pozorem syn nie oddawał ostatnich zaskórniaków ze swojego kieszonkowego żadnemu „mojemu koledze”, który za chwilę podjedzie po dom, aby odebrać kasę. Pewnym rozwiązaniem problemów z kradzieżą tożsamości i fake-newsami jawi się zastosowanie w większym stopniu kryptografii i technologii publicznego blockchain, ale to temat dla innych specjalistów.

Duże zmiany czekają twórców na polu kreatywności. Codziennością stanie się korzystanie z modeli generacyjnych, które będą generowały obrazy, video i muzykę. Osobiście nie uważam, aby modele generacyjne były dużym zagrożeniem dla artystów i twórców. Będą raczej wspierały ich naturalne predyspozycje i staną się dodatkowym istotnym narzędziem. Mogą jednak doprowadzić do mniejszego zapotrzebowania na pracę ludzką w obszarach, które nie wymagają utworów wysokiej jakości. Szata graficzna dla prostych stron internetowych, obrazki na blog zamiast zdjęć wykonywanych przez fotografów, elementy graficzne w grach z niskiej i średniej półki. Nie widzę przeszkód, aby wszystkie one były generowane przez modele AI, pod nadzorem grafika. Stawiałbym również na to, że AI zwiększy dostępność dla osób kreatywnych, które nie posiadają obecnie wystarczających zdolności manualnych lub technicznych. Coś jak rap, który otworzył muzykę dla osób, które niekoniecznie umieją dobrze śpiewać, potrafią jednak tworzyć ciekawe sample i pisać dobre teksty. W sztuce pojawi się nowa kategoria – sztuki wygenerowanej. A dzieła wykonywane tradycyjnie / ręcznie będą postrzegane jako zdecydowanie bardziej wartościowe.

Example from https://stability.ai/blog/stable-diffusion-v2-release

Rozwiązania AI zbliżą się w następnej dekadzie do Świętego Graala sztucznej inteligencji, czyli do General Artificial Intelligence (AGI). Obecnie większość rozwiązań operuje w dość wąskiej dziedzinie: modele językowe, rozpoznawanie obrazów, modele generacyjne, modele eksperckie – tak zwane Narrow AI. Kolejnym naturalnym krokiem jest obranie strategii bottom-up, czyli próba stworzenia rozwiązania, które będzie łączyło funkcjonalność wielu wąskich modeli i tym samym będzie w stanie realizować o wiele bardzie złożone i wielodziedzinowe zadania. Czy to jednak pozwoli je nazwać mianem AGI?

Branża nadal mierzy się ze ścisłym zdefiniowaniem pojęcia AGI. Istnieje co najmniej kilka propozycji testów (Turing Test, Coffee Test, Robot College Student Test, Employment Test), ale konsensusu w tej materii brak. Wraz z powstawaniem coraz bardziej zaawansowanych rozwiązań, prawdopodobnie pojawią się bardziej formalne definicje i korporacje rozpoczną ostatnią fazę wyścigu o miano pierwszej organizacji, która wytworzyła AGI. Zastanawiam się jedynie, czy do wytworzenia AGI wystarczy nam królestwo cyfrowe? Czy trening nie będzie musiał wyjść poza kompresję internetu i przejść do świata fizycznego? Być może modele, które będą stanie czuć zapachy i rozpoznawać smaki oraz dotyk, w naturalny sposób zyskają nowe umiejętności, niejako „wyjdą do ludzi”, i tym samym rozszerzą swoją sztuczną świadomość. Być może w latach trzydziestych doczekamy się pierwszej restauracji z posiłkami robionymi wedle przepisów AI? Czy otrzyma ona Gwiazdkę Michelin?

Po co to wszystko, czyli czas na podsumowanie

Tempo zmian na świecie oraz złożoność procesów i to praktycznie w każdej dziedzinie jest tak wysokie, że przewidywanie tego co się zdarzy za 3 kwartały jest bardzo trudne. Wystarczy spojrzeć na ekonomię – naukę z blisko 250-letnią historią, licząc od Adama Smitha – z jej aktualnymi (grudzień 2022) wątpliwościami co do tego, co nas czeka w globalnej gospodarce w 2023. A co dopiero przewidywanie zmian na przestrzeni dekady i to w tak innowacyjnej dziedzinie jak uczenie maszynowe. Być może powyższe przewidywania okażą się niepotrzebne i nietrafione, a ja zostanę za chwilę bezlitośnie wypunktowany przez kolegów i czytelników, kto wie? Myślę jednak, że od czasu do czasu warto oderwać się od ziemi, wznieść się myślami w chmury i puścić wodze fantazji.

Ostatnie osiągnięcia w dziedzinie sztucznej inteligencji, których kilka wyróżniających się przykładów podałem powyżej (Stable Diffusion, GitHub Copilot, ChatGPT), utwierdzają mnie w przekonaniu, że rewolucja AI rozpoczęła się na dobre. Wątpliwości co do tego, czy czeka nas kolejna zima AI, można w zasadzie uznać za na jakiś czas rozwiane. Oczywiście branża nadal stoi przed wieloma wyzwaniami. Jednym z najbardziej fundamentalnych jest jak monetyzować AI? Jak wykorzystać modele budowane przez inżynierów w realnej gospodarce? To nie jest ani oczywiste, ani proste, może też spotykać się ze znacznym oporem społecznym. A przecież nadzieje związane z AI w kontekście gospodarczym są olbrzymie. Trzy dekady, licząc od lat 90-tych, były dezinflacyjne głównie dzięki trzem siłom: postępowi technologicznemu, dostępności taniej energii i globalizacji wynikającej z otwierających się gospodarek Chin i Europy Środkowo-Wschodniej. Zalały one świat tanią siłą roboczą i równolegle stworzyły silne rozwijające się rynki zbytu dla produktów i usług starych ekonomii. Na początku lat 20-tych, wraz nasyceniem się tych rynków, wraz covidem i wojną w Ukrainie, wydaje się, że co najmniej 2 z tych sił zostały na jakiś czas lub bezpowrotnie utracone. Czy AI przejmie pałeczkę?

Jeszcze innym zagadnieniem przed jakim stoi świat AI, jest sposób w jaki cały czas trenujemy modele, wykorzystując do tego różne wariacje metody gradientu prostego. Być może to stanie się wkrótce główną przeszkodą na drodze do AGI i dopóki nie zostanie znaleziona metoda lepsza, AI będzie tkwiła w pewnym zawieszeniu.

Podane powyżej przewidywania proszę oczywiście potraktować z dużym przymrużeniem oka. Były one dla mnie okazją do ćwiczenia intelektualnego. Stały się też łatwym pretekstem do chwilowego „wyjścia z pudełka” i oderwania się od codziennej pracy i nauki. Proszę z góry o wybaczenie szczególnie tych, u których powyższe przewidywania wytworzyły jakiś poziom dyskomfortu lub niepokoju. ;-). Na koniec przyznam, że po cichu liczę, że te mniej pozytywne przewidywania się nie spełnią, a te bardziej optymistyczne zaskoczą nas skalą manifestacji, czego wszystkim czytelnikom życzę.


The post Przywitajmy jutro – czyli jak AI zmieni świat przez najbliższe 10 lat appeared first on AI Geek Programmer.

]]>
https://aigeekprogrammer.com/pl/przywitajmy-jutro-czyli-jak-ai-zmieni-swiat-przez-najblizsze-10-lat/feed/ 0
PyTorch: podział zbioru, transformacje, uczenie na GPU oraz wizualizacja metryki https://aigeekprogrammer.com/pl/pytorch-podzial-zbioru-transformacje-uczenie-na-gpu-oraz-wizualizacja-metryki/ https://aigeekprogrammer.com/pl/pytorch-podzial-zbioru-transformacje-uczenie-na-gpu-oraz-wizualizacja-metryki/#respond Tue, 05 Apr 2022 17:33:07 +0000 https://aigeekprogrammer.com/?p=8917 Dziś taki lekki misz-masz. W uczeniu maszynowym określenie struktury modelu i trening sieci neuronowej to stosunkowo niewielkie elementy dłuższego łańcucha czynności, który rozpoczyna się od załadowania zbioru danych, jego podziału na podzbiory uczący, walidacyjny oraz testowy i odpowiedniego serwowania danych do modelu. Po drodze pojawiają...

The post PyTorch: podział zbioru, transformacje, uczenie na GPU oraz wizualizacja metryki appeared first on AI Geek Programmer.

]]>
Dziś taki lekki misz-masz. W uczeniu maszynowym określenie struktury modelu i trening sieci neuronowej to stosunkowo niewielkie elementy dłuższego łańcucha czynności, który rozpoczyna się od załadowania zbioru danych, jego podziału na podzbiory uczący, walidacyjny oraz testowy i odpowiedniego serwowania danych do modelu. Po drodze pojawiają się również takie kwestie jak transformacja danych, uczenie na GPU oraz zbieranie metryk i ich wizualizacja, w celu określenia skuteczności naszego modelu. W niniejszym poście chciałbym skupić się nie tyle na architekturze modelu i na samym uczeniu, co właśnie na tych kilku czynnościach, które często wymagają od nas całkiem sporo czasu i wysiłku. Do kodowania wykorzystana zostanie moja ulubiona biblioteka PyTorch.

Z niniejszego posta dowiesz się między innymi:

  • Jak można podzielić zbiór danych na uczący, walidacyjny i testowy?
  • Jak dokonać transformacji zbioru, np. znormalizować dane i co jeżeli potrzebujesz odwrócić proces normalizacji?
  • W jaki sposób w PyTorch wykorzystać GPU?
  • Jak wyliczać najpopularniejszą metrykę, czyli accuracy, dla pętli uczącej, walidacyjnej i testowej?

Załadowanie i transformacje zbioru

Na początku trochę „formalności”, czyli niezbędne importy z krótkimi wyjaśnieniami w komentarzach dlaczego i po co.

# main libraries
import torch
import torchvision

# All datasets in torchvision.dataset are subclasses
# of torch.utils.data.Dataset, thus we may use MNIST directly in DataLoader
from torchvision.datasets import CIFAR10
from torch.utils.data import DataLoader

# an optimizer and a loss function
from torch.optim import Adam
from torch.nn import CrossEntropyLoss

# required for creating a model
from torch.nn import Conv2d, BatchNorm2d, MaxPool2d, Linear, Dropout
import torch.nn.functional as F

# tools and helpers
import numpy as np
from timeit import default_timer as timer
import matplotlib.pyplot as plt
from torch.utils.data import random_split
from torchvision import transforms
import matplotlib.pyplot as plt

Korzystamy z pakietu torchvision, który oferuje klasy do ładowania najpopularniejszych zbiorów danych, na których najprościej eksperymentować. Klasa ładująca zbiór CIFAR10, którą zaraz zastosujemy, jako jeden z parametrów przyjmuje obiekt klasy torchvision.transforms. Umożliwia on wykonanie na ładowanym zbiorze szeregu transformacji takich jak zamiana danych na tensory, normalizacja, dodanie paddingów, wycinanie fragmentów obrazu, obroty, transformacje perspektywy, itp. Przydają się one zarówno w prostych przypadkach, jak i w bardziej skomplikowanych, gdy np. chcemy wykonać data augmentation. Dodatkowo transformacje można serializować, używając torchvision.transforms.Compose.

My potrzebujemy jedynie przekształcić dane do tensora i znormalizować je, stąd:

# Transformations, including normalization based on mean and std
mean = torch.tensor([0.4915, 0.4823, 0.4468])
std = torch.tensor([0.2470, 0.2435, 0.2616])
transform_train = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize(mean, std)
])
transform_test = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize(mean, std)    
])

Trzy uwagi do powyższego:

  • mean i std to wyliczone wartości średnie oraz ich odchylenie standardowe dla każdego z kanałów obrazka,
  • jak widać definiujemy transformatę osobno dla zbioru uczącego i testowego. Są one takie same, bo nie stosujemy dla zbioru uczącego data augmentation. Teoretycznie więc można było zastosować jedną transformatę w obu przypadkach, ale dla jasności, jak również na wypadek gdybyśmy potem chcieli to jednak zmienić, zostawiamy obie transformaty oddzielnie,
  • wartości mean i std przydadzą się nam do późniejszej de-normalizacji na potrzeby wyświetlania przykładowych obrazków, bo po normalizacji przestaną one być czytelne dla ludzkiego oka.

Mając transformaty, możemy załadować zbiory. Klasa CIFAR10 jest podklasą torch.utils.data.Dataset, o której szerzej pisałem w tym wpisie.

# Download CIFAR10 dataset 
dataset = CIFAR10('./', train=True, download=True, transform=transform_train)
test_dataset = CIFAR10('./', train=False, download=True, transform=transform_test)

dataset_length = len(dataset)
print(f'Train and validation dataset size: {dataset_length}')
print(f'Test dataset size: {len(test_dataset)}')
>>>Train and validation dataset size: 50000
>>>Test dataset size: 10000

# The output of torchvision datasets are PILImage images of range [0, 1].
# but they have been normalized and converted to tensors
dataset[0][0][0]
>>> tensor([
>>> [-1.0531, -1.3072, -1.1960, ..., 0.5187, 0.4234, 0.3599],
>>> [-1.7358, -1.9899, -1.7041, ..., -0.0370, -0.1005, -0.0529],
>>> [-1.5930, -1.7358, -1.2119, ..., -0.1164, -0.0847, -0.2593],
>>> ...,
>>> [ 1.3125, 1.2014, 1.1537, ..., 0.5504, -1.1008, -1.1484],
>>> [ 0.8679, 0.7568, 0.9632, ..., 0.9315, -0.4498, -0.6721],
>>> [ 0.8203, 0.6774, 0.8521, ..., 1.4395, 0.4075, -0.0370]])

Denormalizacja i wyświetlenie

Dobrą praktyką jest podejrzenie przykładowych elementów ze zbioru uczącego. Problemem jest jednak to, że zostały one znormalizowane, czyli wartości poszczególnych pikseli zostały zmienione oraz przekształcone do tensora, co z kolei zmieniło kolejność kanałów obrazka. Poniżej klasa de-normalizująca i przywracająca pierwotny kształt.

# Helper callable class that will un-normalize image and
# change the order of tensor elements to display image using pyplot.
class Detransform():
  def __init__(self, mean, std):
    self.mean = mean
    self.std = std
  
  # PIL images loaded into dataset are normalized.
  # In order to display them correctly we need to un-normalize them first
  def un_normalize_image(self, image):
    un_normalize = transforms.Normalize(
        (-self.mean / self.std).tolist(), (1.0 / self.std).tolist()
    )
    return un_normalize(image)
  
  # If 'ToTensor' transormation was applied then the PIL images have CHW format.
  # To show them using pyplot.imshow(), we need to change it to HWC with 
  # permute() function.
  def reshape(self, image):
    return image.permute(1,2,0)
  def __call__(self, image):
    return self.reshape(self.un_normalize_image(image))

# Create detransformer to be used whil printing images
detransformer = Detransform(mean, std)

Potrzebujemy jeszcze słownika, który tłumaczyłby numery klas na ich nazwy, zgodnie z definicją zbioru CIFAR10, a także funkcji wyświetlającej kilka losowo wybranych obrazków.

# Translation between class id and name
class_translator = {
    0 : 'airplane',
    1 : 'automobile',
    2 : 'bird',
    3 : 'cat',
    4 : 'deer', 
    5 : 'dog',
    6 : 'frog', 
    7 : 'horse', 
    8 : 'ship', 
    9 : 'truck',
}

# Helper function printing 9 randomly selected pictures from the dataset
def print_images():
  fig = plt.figure()
  fig.set_size_inches(fig.get_size_inches() * 2)
  for i in range(9):
    idx = torch.randint(0, 50000, (1,)).item()
    picture = detransformer(dataset[idx][0])
    ax = plt.subplot(3, 3, i + 1)
    ax.set_title(class_translator[dataset[idx][1]] + ' - #' + str(idx))
    ax.axis('off')
    plt.imshow(picture)
  plt.show()

 

No to przyjrzymy się kilku elementom tego zbioru …

print(f'The first element of the dataset is a {class_translator[dataset[0][1]]}.')
>>>The first element of the dataset is a frog.
image = detransformer(dataset[0][0])
plt.imshow(image)
cifar10 obrazek żaba

To jest żaba. Może nie?!? 😉


print_images()
cifar10 przykładowe obrazki ze zbioru

Kilka losowo wybranych obrazków ze zbioru CIFAR10


Podział zbioru na uczący, testowy i walidacyjny

Należy zauważyć, że konstruktor zbioru CIFAR10 umożliwia pobranie albo zbioru uczącego, albo testowego. Co jednak, jeśli chcemy wydzielić jeszcze zbiór walidacyjny, który pozwoli nam określić skuteczność w trakcie uczenia? Musimy samodzielnie wydzielić go z ze zbioru uczącego. Pomocna w tym zakresie będzie funkcja random_split z pakietu torch.utils.data.

validation_length = 5000
# Split training dataset between actual train and validation datasets
train_dataset, validation_dataset = random_split(dataset, [(dataset_length - validation_length), validation_length])

Mając trzy obiekty klasy Dataset: train_dataset, validation_dataset i test_dataset możemy zdefiniować DataLoadery, które umożliwią serwowanie danych w batchach.

# Create DataLoaders
train_dataloader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
validation_dataloader = DataLoader(validation_dataset, batch_size=batch_size, shuffle=False)
test_dataloader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)

# Print some statistics
print(f'Batch size: {batch_size} data points')
print(f'Train dataset (# of batches): {len(train_dataloader)}')
print(f'Validation dataset (# of batches): {len(validation_dataloader)}')
print(f'Test dataset (# of batches): {len(test_dataloader)}')
>>> Batch size: 256 data points
>>> Train dataset (# of batches): 176
>>> Validation dataset (# of batches): 20
>>> Test dataset (# of batches): 40

Architektura modelu

Aby nie skupiać się zbytnio na architekturze sieci, którą chcemy zastosować – bo nie taki jest cel niniejszego posta – wykorzystamy sieć zaprojektowaną w tym wpisie na temat konwolucyjnych sieci neuronowych. Warto jednak w tym miejscu zauważyć, że jednym z zagadnień przy projektowaniu sieci jest wymiarowanie danych. Aby sprawdzić wielkość wektora wejściowego, jaki będzie serwowany przez DataLoader, można wykonać następujący kod:

# Before CNN definition, let's check the sizing of input tensor
data, label = next(iter(train_dataloader))
print(data.size())
print(label.size())
>>> torch.Size([256, 3, 32, 32])
>>> torch.Size([256])

Mamy tu zatem batch wielkości 256, następnie trzy kanały RBG obrazka, każdy o wielkości 32 na 32.

W wymiarowaniu sieci konwolucyjnej pomóc może ten skrypt. Oczywiście wymaga on rozbudowania / dostosowania do potrzeb danego modelu.

Ostatecznie nasza architektura będzie wyglądała następująco:

class CifarNN(torch.nn.Module):
  def __init__(self):
    super().__init__()
    self.conv1 = Conv2d(3, 128, kernel_size=(5,5), stride=1, padding='same')  # [B, 128, 32, 32]
    self.bnorm1 = BatchNorm2d(128)
    self.conv2 = Conv2d(128, 128, kernel_size=(5,5), stride=1, padding='same')  # [B, 128, 32, 32]
    self.bnorm2 = BatchNorm2d(128)
    self.pool1 = MaxPool2d((2,2))  # [B, 128, 16, 16]
    self.conv3 = Conv2d(128, 64, kernel_size=(5,5), stride=1, padding='same')  # [B, 64, 16, 16]
    self.bnorm3 = BatchNorm2d(64)
    self.conv4 = Conv2d(64, 64, kernel_size=(5,5), stride=1, padding='same')  # [B, 64, 16, 16]
    self.bnorm4 = BatchNorm2d(64)
    self.pool2 = MaxPool2d((2,2))  # [B, 64, 8, 8]
    self.conv5 = Conv2d(64, 32, kernel_size=(5,5), stride=1, padding='same')  # [B, 32, 8, 8]
    self.bnorm5 = BatchNorm2d(32)
    self.conv6 = Conv2d(32, 32, kernel_size=(5,5), stride=1, padding='same')  # [B, 32, 8, 8]
    self.bnorm6 = BatchNorm2d(32)
    self.pool3 = MaxPool2d((2,2))  # [B, 32, 4, 4]
    self.conv7 = Conv2d(32, 16, kernel_size=(3,3), stride=1, padding='same')  # [B, 16, 4, 4]
    self.bnorm7 = BatchNorm2d(16)
    self.conv8 = Conv2d(16, 16, kernel_size=(3,3), stride=1, padding='same')  # [B, 16, 4, 4]
    self.bnorm8 = BatchNorm2d(16)    
    self.linear1 = Linear(16*4*4, 32)
    self.drop1 = Dropout(0.15)
    self.linear2 = Linear(32, 16)
    self.drop2 = Dropout(0.05)
    self.linear3 = Linear(16, 10)
  def forward(self, x):
    # the first conv group
    x = self.bnorm1(self.conv1(x))
    x = self.bnorm2(self.conv2(x))
    x = self.pool1(x)
    # the second conv group
    x = self.bnorm3(self.conv3(x))
    x = self.bnorm4(self.conv4(x))
    x = self.pool2(x)
    # the third conv group
    x = self.bnorm5(self.conv5(x))
    x = self.bnorm6(self.conv6(x))
    x = self.pool3(x)
    # the fourth conv group (no maxpooling at the end)
    x = self.bnorm7(self.conv7(x))
    x = self.bnorm8(self.conv8(x))
    # flatten
    x = x.reshape( -1, 16*4*4) 
    # the first linear layer with ReLU
    x = self.linear1(x)
    x = F.relu(x)
    # the first dropout
    x = self.drop1(x)
    # the second linear layer with ReLU
    x = self.linear2(x)
    x = F.relu(x)
    # the second dropout
    x = self.drop2(x)
    # the output layer logits (10 neurons)
    x = self.linear3(x)
  
    return x

 

Przenosimy się na GPU i wyliczamy accuracy

Uczenie nieco bardziej rozbudowanej sieci neuronowej na CPU nie ma większego sensu. Prosty test jaki przeprowadziłem na Google Colab pokazał, że przejście jednej epoki na CPU zajmuje około 2600 sekund, gdy tymczasem na GPU to samo zajęło 66 sekund. Te czasy w oczywisty sposób zależą od wielu czynników, na które nie mamy wpływu, jak choćby na jakie maszyny skieruje nas silnik Google Colab, ale wnioski zawsze będą podobne – uczenie na GPU może być kilkadziesiąt razy szybsze.

Jak zatem najprościej przesiąść się w PyTorch z CPU na GPU? Oczywiście w Google Colab musimy wykonać Runtime->Change runtime type i zmienić środowisko wykonawcze na GPU. Ale główne zmiany należy wykonać w kodzie. Na szczęście nie ma ich dużo i są one dość proste.

Sprawa zasadnicza to ustalenie z jakim środowiskiem mamy do czynienia. Poniższy fragment kodu jest powszechnie stosowaną dobrą praktyką.

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(device)
>>> cuda

W następnym kroku tworzymy model i przenosimy go na aktualnie dostępne urządzenie.

model = CifarNN()
model = model.to(device)

Każde uczenie musi mieć zdefiniowane kilka parametrów oraz wskazanie użytych narzędzi. Poniżej określamy ilość epok, learning_rate oraz optimizer, jak również sposób liczenia błędu. Pojawiają się również po raz pierwszy dwie listy, w których w każdej epoce będziemy zapisywali accuracy. Umożliwi to nam potem narysowanie ładnego wykresu.

epochs = 40
learning_rate = 0.001
train_accuracies = []  # cummulated accuracies from training dataset for each epoch
val_accuracies = []  # cummulated accuracies from validation dataset for each epoch
optimizer = Adam( model.parameters(), lr=learning_rate)
criterion = CrossEntropyLoss()

Poniższy kod to główna pętla ucząca. Dzieje się tu kilka rzeczy, które nas mogą interesować w kontekście niniejszego posta, więc do niektórych linii przypisałem indeksy, które krótko skomentuję:

(1) – będziemy mierzyli czas uczenia.
(2) – dobrą praktyką jest sygnalizowanie silnikowi PyTorch kiedy następuje uczenie, a kiedy jedynie ewaluacja na zbiorze walidacyjnym lub testowym. Istotnie poprawia to wydajność części ewaluacyjnych.
(3) – przenosimy dane (batch) na GPU. 
(4) – wartość, którą otrzymujemy po przepuszczeniu danej wejściowej przez sieć (tu: yhat) to tzw. logits, czyli wartości teoretycznie z przedziału od plus do minus nieskończoności. Target zawiera liczby od 0 do 9 wskazujące na właściwą klasę. Funkcja obliczająca błąd sieci (tu: CrossEntropyLoss) wewnętrznie radzi sobie z odpowiednim porównaniem tych wartości. My jednak obliczając accuracy – w punkcie (5) – musimy najpierw samodzielnie obliczyć odpowiedź sieci. Używamy do tego funkcji argmax, która zwraca indeks, w którym występuje najsilniejsza (największa co do wartości) odpowiedź sieci. Indeks ten będzie jednocześnie numerem klasy, do której sieć przypisała wartość wejściową. W ten sposób otrzymujemy prediction – wektor zawierający przypisanie klasy dla każdego elementu aktualnie przetwarzanego batcha.
(5) – do wyliczenia accuracy bazującej na danych w dwóch wektorach y i prediction najwygodniej użyć biblioteki numpy, która świetnie radzi sobie z wektoryzacją operacji. Aby dane, które znajdują się na GPU trafiły ostatecznie do listy przetwarzanej w środowisku CPU musimy użyć .detach().cpu().numpy().
(6) – dla każdej epoki uczenia przetwarzamy zbiór walidacyjny i wyliczamy dla niego accuracy, aby porównać z accuracy wyliczoną dla zbioru uczącego. Na tej podstawie będziemy widzieli czy proces uczenia wpadł w overfitting czy nie.

 

start = timer()    # (1)
for epoch in range(epochs):
   model.train() # (2)
   train_accuracy = []
   for x, y in train_dataloader:
      x = x.to(device) # (3)
      y = y.to(device)  # (3)
      optimizer.zero_grad()
      yhat = model.forward(x)
      loss = criterion(yhat, y)
      loss.backward()
      optimizer.step()
      prediction = torch.argmax(yhat, dim=1) # (4)
      train_accuracy.extend((y == prediction).detach().cpu().numpy()) # (5)
   train_accuracies.append(np.mean(train_accuracy)*100)

   # for every epoch we do a validation step to asses accuracy and overfitting
   model.eval() # (2)
   with torch.no_grad(): # (2)
      val_accuracy = []  # accuracies for each batch of validation dataset
      for vx, vy in validation_dataloader: (6)
         vx = vx.to(device) # (3)
         vy = vy.to(device) # (3)
         yhat = model.forward(vx)
         prediction = torch.argmax(yhat, dim=1) (4)
         # to numpy in order to use next the vectorized np.mean
         val_accuracy.extend((vy == prediction).detach().cpu().numpy()) (5)
      val_accuracies.append(np.mean(val_accuracy)*100)
   # simple logging during training
   print(f'Epoch #{epoch+1}. Train accuracy: {np.mean(train_accuracy)*100:.2f}. \
                      Validation accuracy: {np.mean(val_accuracy)*100:.2f}')
 end = timer() # (1)

W efekcie uczenia na 40 epokach otrzymujemy następujące wyniki:

>>> Epoch #1. Train accuracy: 34.20. Validation accuracy: 47.32
>>> Epoch #2. Train accuracy: 51.58. Validation accuracy: 57.00
>>> Epoch #3. Train accuracy: 58.11. Validation accuracy: 61.56
>>> Epoch #4. Train accuracy: 62.18. Validation accuracy: 64.16
................................................................
>>> Epoch #38. Train accuracy: 90.86. Validation accuracy: 73.86
>>> Epoch #39. Train accuracy: 91.42. Validation accuracy: 73.30
>>> Epoch #40. Train accuracy: 91.68. Validation accuracy: 73.40

Jak widać różnica między accuracy na zbiorze uczącym (91%) a zbiorze walidacyjnym (73%) jest spora. Model wpadł w overfitting, co bardzo dobrze pokazuje zresztą poniższy wykres.

print(f'Processing time on a GPU: {end-start:.2f}s.') 
>>> Processing time on a GPU: 3113.36s.
plt.plot(train_accuracies, label="Train accuracy")
plt.plot(val_accuracies, label="Validation accuracy")
leg = plt.legend(loc='lower right')
plt.show()

Training & validation accuracy

Problem overfittingu jest istotny i jest kilka metod, które można zastosować, aby go zmniejszyć. Część została zresztą zaaplikowana do powyższego modelu (np. warstwa Dropout). Więcej o zapobieganiu overfittingowi w tym poście.

Na koniec procesu uczenia sprawdzamy jak model radzi sobie na zbiorze testowym, czyli danych, których model nie widział podczas uczenia.

# calculate accuracy on the test dataset that the model has never seen before
model.eval()
with torch.no_grad():
  test_accuracies = []
  for x, y in test_dataloader:
    x = x.to(device)
    y = y.to(device)    
    yhat = model.forward(x)
    prediction = torch.argmax(yhat, dim=1)
    test_accuracies.extend((prediction == y).detach().cpu().numpy())  # we store accuracy using numpy
  test_accuracy = np.mean(test_accuracies)*100              # to easily compute mean on boolean values
print(f'Accuracy on the test set: {test_accuracy:.2f}%')  
>>>Accuracy on the test set: 72.78%

 

Podsumowanie

Niniejszy post miał charakter narzędziowy. Skupiliśmy się na kilku obszarach, które są czasami trudniejsze technicznie niż sam proces budowania architektury modelu. Zobaczyliśmy w jaki sposób można załadować zbiór i podzielić go na trzy podzbiory: uczący, walidacyjny i testowy. Pobieżnie przyjrzeliśmy się transformacjom jakich można dokonywać korzystając z klasy transforms oraz w jaki sposób można wyświetlić obrazki ze zbioru uczącego, odwracając uprzednio transformację. Po krótkim przystanku przy wymiarowaniu sieci nasza uwaga przeniosła się na uczenie w środowisku GPU oraz wyliczenie accuracy. Uzyskany efekt: 72% trafności na zbiorze testowym to nie jest zbyt dobry wynik dla zbioru CIFAR10, ale nie to było celem posta. Osobom zainteresowanym podniesieniem efektywności uczenia polecam post dotyczący data augmentation. Wprawdzie wykorzystuje on framework Keras, ale PyTorch również oferuje narzędzia do wykonania analogicznych operacji. Bardziej zresztą istotna jest sama idea stojąca za serwowaniem do uczenia sztucznie modyfikowanych danych niż użyta biblioteka. 

Całość skryptu dostępna w moim repo na githubie.

The post PyTorch: podział zbioru, transformacje, uczenie na GPU oraz wizualizacja metryki appeared first on AI Geek Programmer.

]]>
https://aigeekprogrammer.com/pl/pytorch-podzial-zbioru-transformacje-uczenie-na-gpu-oraz-wizualizacja-metryki/feed/ 0
Przygotowanie danych do uczenia maszynowego w PyTorch https://aigeekprogrammer.com/pl/przygotowanie-danych-do-uczenia-maszynowego-w-pytorch/ https://aigeekprogrammer.com/pl/przygotowanie-danych-do-uczenia-maszynowego-w-pytorch/#respond Sun, 22 Aug 2021 11:47:47 +0000 https://aigeekprogrammer.com/?p=8861 Przygotowanie danych do uczenia maszynowego nie jest zadaniem, za którym tęskni większość specjalistów AI. Dane bywają różnej jakości, najczęściej wymagają bardzo dokładnej analizy, czasami ręcznego przeglądu, a na pewno selekcji i wstępnego przetworzenia. W przypadku zadań klasyfikacyjnych podział zbioru na klasy bywa niewłaściwy lub niewystarczająco...

The post Przygotowanie danych do uczenia maszynowego w PyTorch appeared first on AI Geek Programmer.

]]>
Przygotowanie danych do uczenia maszynowego nie jest zadaniem, za którym tęskni większość specjalistów AI. Dane bywają różnej jakości, najczęściej wymagają bardzo dokładnej analizy, czasami ręcznego przeglądu, a na pewno selekcji i wstępnego przetworzenia. W przypadku zadań klasyfikacyjnych podział zbioru na klasy bywa niewłaściwy lub niewystarczająco zbalansowany. Często danych jest również po prostu za mało i trzeba je sztucznie wygenerować. Jednym zdaniem: łatwo nie jest.  Niemniej, jest to niezbędny krok i kto wie czy nie ważniejszy niż późniejszy tuning algorytmu uczącego. Częścią etapu przygotowania danych jest ich pobranie z uprzednio przygotowanego zbioru i możliwość serwowania (najczęściej w paczkach) do algorytmu uczącego. W niniejszym wpisie chciałbym przyjrzeć się metodom serwowania danych, jakie oferuje biblioteka PyTorch.

Z niniejszego posta dowiesz się między innymi:

  • Jak przygotować środowisko conda do pracy z Pytorch?
  • Do czego służą klasy Dataset i DataLoader?
  • Jak użyć ich do pracy z jednym z predefiniowanych zbiorów danych udostępnianych przez bibliotekę PyTorch?
  • Jak użyć Dataset i DataLoader do zaimportowania własnego zbioru danych?
  • Jak można pobierać danych do uczenia maszynowego ze zbioru danych składającego z wielu oddzielnych plików, np. plików graficznych leżących na dysku?

Ale najpierw przygotujmy środowisko…

Poniżej załączam krótką instrukcję przygotowania środowiska dla PyTorch. Dzięki temu będzie prościej samodzielnie odtworzyć metody zaprezentowane poniżej. Oczywiście, jeżeli masz już gotowe środowisko możesz pominąć ten fragment posta.

Korzystam z condy i na lokalnym środowisku nie mam karty zgodnej z CUDA, więc potrzebuję kompilacji dla CPU. Na początek zawsze warto sprawdzić aktualność wersji condy:

> conda -V
> conda update -n base -c defaults conda

Następnie tworzymy środowisko pod nasze prace, aktywujemy i instalujemy niezbędne pakiety. Zwróć uwagę na cpuonly – jeżeli masz CUDA, to instalacja nie powinna obejmować tego parametru. Istotny może być również parametr -c pytorch, wskazujący dedykowany kanał, w którym znajdą się odpowiednie wersje pytorch i torchvision.

> conda create --name pytorch_env
> conda activate my_env
> conda install jupyter pytorch torchvision numpy matplotlib cpuonly -c pytorch

Na koniec przechodzimy do katalogu roboczego, w którym zamierzamy zapisywać skrypty, uruchamiamy Jupyter Notebook i wykonujemy prosty test.

> cd <<my working directory>>
> jupyter notebook

import torch
x = torch.rand(5, 3)
print(x)
>>> tensor([[0.3425, 0.0880, 0.5301],
            [0.5414, 0.2990, 0.5740],
            [0.3530, 0.0147, 0.5289],
            [0.2170, 0.3744, 0.7805],
            [0.6985, 0.5344, 0.7144]])

Jeżeli przy wykonywaniu powyższego napotkasz na jakiekolwiek problemy, nie masz zainstalowanej Anacondy lub możesz / chcesz używać CUDA, to odsyłam do tej instrukcji.

Przygotowanie danych – scenariusz najprostszy.

PyTorch daje możliwość skorzystania z dwóch klas do obróbki danych: torch.utils.data.Dataset oraz torch.utils.data.DataLoader. Nieco upraszczając, zadaniem Dataset jest pobranie ze zbioru pojedynczej danej wraz z jej opisem (label), natomiast DataLoader otacza dane pobrane przez Dataset iteratorem, zapewnia serwowanie ich w batchach, działanie w wielu wątkach, aby przyspieszyć pobieranie danych do uczenia, jak również takie operacje jak choćby mieszanie danych.

PyTorch udostępnia również wiele przykładowych zbiorów, na których dość prosto można przeprowadzić testy pobierania danych i uczenia. I od takiego scenariusza rozpoczniemy, przygotowując dane do uczenia na znanym nam już zbiorze MNIST. Poniżej importujemy bibliotekę torch, Dataset oraz pakiet torchvision.datasets zawierający wiele przykładowych zbiorów z obszaru rozpoznawania obrazów. Każdy zbiór danych jest podklasą Dataset co oznacza, że zaimplementowano za nas metody _getitem__ oraz __len__, o czym więcej nieco później.

import torch
from torch.utils.data import Dataset
from torchvision import datasets

Kiedy importujemy dane z dowolnego zbioru, najczęściej potrzebujemy je w pewien sposób przekształcić (np. znormalizować). Pakiet torchvision, jak również inne pakiety z przykładowymi zbiorami danych dostępne w PyTorch, posiadają zdefiniowane transformaty dostępne w pakiecie transforms. W naszym przykładzie skorzystamy z jednej z nich, która konwertuje daną pobraną ze zbioru do tensora PyTorch.

from torchvision.transforms import ToTensor
import matplotlib.pyplot as plt

Samo pobranie zbioru jest bardzo proste: tworzymy obiekt klasy danego zbioru (w naszym przykładzie MNIST) przekazując kilka parametrów, tu katalog lokalny, do którego ściągnięte zostaną dane, wskazanie czy pobieramy zbiór uczący czy testowy, aplikowane transformaty – a możemy podać ich kilka – oraz flagę czy zbiór pobrać na dysk, tak aby nie było konieczności pobierania go za każdym razem.

training_dataset = datasets.MNIST(root='mnistdata', train=True, transform=ToTensor(), download=True)

Dalsze korzystanie ze zbioru sprowadza się do wywołania obiektu, który zwróci parę (tuple): dana i label:

image, label = training_dataset[100]
print(type(image))
print(image.size())
print(type(label))

>>> <class 'torch.Tensor'>
>>> torch.Size([1, 28, 28])
>>> <class 'int'>

Pobrany obrazek możemy wyświetlić:

plt.imshow(image.squeeze())
plt.title(label)
plt.show()

Przygotowanie danych do uczenia maszynowego w PyTorch

Teraz na scenę może wkroczyć klasa DataLoader i wykorzystując utworzony obiekt klasy Dataset możemy opakować go w dodatkowe funkcje przydatne do uczenia maszynowego:

from torch.utils.data import DataLoader
dataloader = DataLoader(
   dataset=training_dataset,
   batch_size=5
)

Po tak utworzonym DataLoaderze możemy swobodnie iterować, a każda iteracja dostarczy nam odpowiednią ilość danych – w naszym przypadku batch o wielkości 5:

images, labels = next(iter(dataloader))
print(type(images), type(labels))
print(images.size(), labels.size())

>>> <class 'torch.Tensor'> <class 'torch.Tensor'>
>>> torch.Size([5, 1, 28, 28]) torch.Size([5])

Załóżmy, że chcemy wyświetlić zawartość drugiego obrazka w tak pobranym batchu:

idx = 2
label = labels[idx].item()
image = images[idx]
plt.imshow(image.squeeze())
plt.title(label)
plt.show()

Wykorzystanie klas Dataset i DataLoader w Pytorch

Teraz wystarczy wykorzystać wytworzone struktury danych w procesie uczenia. O tym jak to zrobić zajmiemy się w innym poście, a teraz zobaczmy jak możemy wykorzystać Dataset i DataLoader w bardziej życiowych sytuacjach.

Jak wykorzystać własny Dataset?

Bardziej życiową sytuacją będzie na pewno wykorzystanie własnego zbioru danych, a nie przykładowego zbioru osadzonego w pakiecie PyTorch. Dla uproszczenia przyjmijmy, że naszym zbiorem danych będzie 500 odczytów 10 liczb całkowitych, wraz z ich klasyfikacją na 10 klas, oznaczonych cyframi od 0 do 9.

Pierwszym krokiem w procesie przygotowania własnego zbioru danych jest zdefiniowanie naszej własnej klasy, ale dziedziczącej po omawianej powyżej „abstrakcyjnej” klasie Dataset. Implementacja jest o tyle prosta, że klasa taka wymaga nadpisania jedynie dwóch metod, o których pisałem powyżej: __getitem__ i __len__. Plus oczywiście należy zapewnić kod metody inicjującej obiekt __init__. Ponieważ nasz zbiór będzie randomowo generowany, to konstruktor będzie przyjmował 4 parametry: początek i koniec przedziału liczb całkowitych dla generatora liczb oraz wielkość zbioru, tu 500 wierszy po 10 wartości każdy. W konstruktorze inicjujemy również labelki – także losowo. Całościowo kod przedstawia się następująco:

import torch
from torch.utils.data import Dataset, DataLoader

class RandomIntDataset(Dataset):
    def __init__(self, start, stop, x, y):
        # losowo generujemy tablicę intów, które będą pełniły funkcję danych
        self.data = torch.randint(start, stop, (x,y))
        # losowo generujemy wektor intów, pełniących rolę labelek
        self.labels = torch.randint(0, 10, (x,))

    def __len__(self):
        # wielkość zbioru równa jest długości wektora
        return len(self.labels)

    def __str__(self):
        # łączymy obie struktury danych, aby zaprezentować je w formie jednej tabeli
        return str(torch.cat((self.data, self.labels.unsqueeze(1)), 1))

    def __getitem__(self, i):
        # metoda zwraca zwraca parę: dana - labelka dla indeksu o numerze i
        return self.data[i], self.labels[i]

W kolejnym kroku tworzymy obiekt klasy RandomIntDataset podając odpowiednie parametry i sprawdzamy wielkość wygenerowanego zbioru:

dataset = RandomIntDataset(100, 1000, 500, 10)
len(dataset)
>>> 500

Zobaczmy jak wygląda nasz nowo utworzony zbiór – w ostatniej kolumnie widoczna jest klasa pojedynczej próbki danych:

print(dataset)
>>> tensor([[627, 160, 881, ..., 485, 457, 9],
            [705, 511, 947, ..., 744, 465, 5],
            [692, 427, 701, ..., 639, 378, 9],
             ...,
            [601, 228, 749, ..., 155, 823, 4],
            [599, 627, 802, ..., 179, 693, 4],
            [740, 861, 697, ..., 286, 160, 4]])

Tak przygotowany obiekt możemy wykorzystać otaczając go, jak w poprzednim przykładzie, DataLoaderem, a następnie w dość prosty sposób iterować po batchach zbioru – w naszym przypadku 4-elementowych.

dataset_loader = DataLoader(dataset, batch_size=4, shuffle=True)
data, labels = next(iter(dataset_loader))
data
>>> tensor([[724, 232, 501, 555, 369, 142, 504, 226, 849, 924],
            [170, 510, 711, 502, 641, 458, 378, 927, 324, 701],
            [838, 482, 299, 379, 181, 394, 473, 739, 888, 265],
            [945, 421, 983, 531, 237, 106, 261, 399, 161, 459]])
labels
>>> tensor([3, 6, 9, 7])

Pobieranie danych z plików

W zadaniach z obszaru computer vision często spotykamy się sytuacją, w której dane (obrazy, filmy, anotacje) są zapisane w postaci danych / plików na dysku. Dziedziczenie po klasie abstrakcyjnej Dataset i nadpisanie jej metod da nam możliwość zrealizowania pobierania takich plików wg schematu bliźniaczego do opisanego powyżej:

  • tworzymy klasę dziedziczącą po Dataset,
  • definiujemy metody __init__, __getitem__ oraz __len__, plus ewentualne inne pomocnicze metody,
  • tworzymy obiekt tej klasy i przekazujemy go do DataLoadera.

Przyjrzyjmy się teraz, w jaki sposób można zaimplementować pobieranie danych dla zbioru Facial Key Point Detection Dataset. Po pobraniu i rozpakowaniu pliku otrzymamy katalog images zawierający 5000 plików graficznych, dociętych do tego samego rozmiaru oraz plik w formacie json zawierający koordynaty 68 kluczowych punktów twarzy dla każdego z plików. Te kluczowe punkty z reguły identyfikują oczy, linię ust, brwi oraz owal twarzy.

Zbiór został przygotowany przez Prashant Arora jako podzbiór oryginalnego, dużo większego zbioru danych Flickr-Faces-HQ, stworzonego przez zespół NVIDIA i udostępnionego z wykorzystaniem licencji Creative Commons BY-NC-SA 4.0.

Importujemy niezbędne biblioteki i tworzymy klasę dziedziczącą po Dataset, w której implementujemy trzy wymagane metody. Metoda __init__ ustawia zmienną wskazującą na nazwę katalogu z danymi. Powinien on znajdować się w katalogu, z którego uruchamiamy niniejszy skrypt. Do katalogu z danymi powinniśmy uprzednio wypakować pobrany zbiór. Metoda __len__ zwraca wielkość zmiennej z koordynatami kluczowych punktów, która jest jest jednocześnie wielkością całego zbioru. Metoda __getitem__ w pierwszej kolejności pobiera nazwę pliku o indeksie i ze zmiennej z koordynatami, a następnie wczytuje obraz z odpowiedniego pliku z katalogu images.

import torch
from torch.utils.data import Dataset, DataLoader
import json # we need to import json file with key points coordinates
import numpy as np
import matplotlib.image as img
import matplotlib.pyplot as plt

class FacialDetection(Dataset):
   def __init__(self, dataset_directory="FacialKeyPoint"):
      # set root directory for your dataset
      self.dataset_directory = dataset_directory

      # read json file with annotations
      annotations_file = open(self.dataset_directory + "\\all_data.json")
      self.annotations = json.load(annotations_file)

   def __len__(self):
      return len(self.annotations)

   def __getitem__(self, i):
      image_filename = self.annotations[str(i)]['file_name']
      image_path = self.dataset_directory + "\\images\\" + image_filename
      image = img.imread(image_path)

      points = self.annotations[str(i)]['face_landmarks']

      return image, np.array(points)

Możemy teraz utworzyć obiekt naszej nowej klasy i sprawdzić czy na pewno dysponujemy zbiorem o 5000 elementów:

dataset = FacialDetection()
len(dataset)
>>> 5000

Pobierzmy jeden z obrazków, np. o indeksie 888 i wyświetlmy zawartość:

image, key_points = dataset.__getitem__(888)
plt.imshow(image)
plt.show()

Wykorzystanie Dataset do pobrania danych z plików graficznych

I ten sam obrazek z naniesionymi punktami kluczowymi:

plt.imshow(image)
plt.scatter(key_points[:, 0], key_points[:, 1], marker='o', c='y', s=5)
plt.show()

Dataset i DataLoader - standaryzowany sposób serwowania danych do uczenia maszynowego

Gdybyśmy chcieli serwować ten zbiór do uczenia maszynowego, wystarczy go jak uprzednio otoczyć klasą DataLoader i iterować po zwróconym obiekcie.

dataset_loader = DataLoader(dataset, batch_size=4, shuffle=True)
data, labels = next(iter(dataset_loader))
data.size()
>>> torch.Size([4, 512, 512, 3])

labels.size()
>>> torch.Size([4, 68, 2])

Krótkie podsumowanie

Klasy Dataset i DataLoader oferują prosty i co ważne standaryzowany sposób dostępu do danych i ich dalszego przetwarzania w uczeniu maszynowym. Poza samym faktem standaryzacji, który mocno upraszcza programowanie w wielu zastosowaniach, klasy te są również wykorzystywane do łatwego dostępu do zbiorów danych udostępnianych w bibliotece Pytorch, a dotyczących rozpoznawania obrazu, pracy z dźwiękiem i tekstem. Co ważne torchvision, torchtext i torchaudio dają możliwość skorzystania z predefiniowanych transformat (tu przykład dla torchvision) i użycia ich w DataLoaderze. Można również skorzystać z tych transformat we własnej klasie lub napisać swoją transformatę. Tematu tego nie poruszyłem w powyższym wpisie, ale warto go zasygnalizować, jako dodatkową zaletę. Jeszcze inną korzyścią z wykorzystania ww. klas jest możliwość sparametryzowania przetwarzania równoległego na wielu CPU, na GPU oraz optymalizacji transferu danych między CPU a GPU, co ma znaczenie przy przetwarzaniu bardzo dużych ilości danych.

The post Przygotowanie danych do uczenia maszynowego w PyTorch appeared first on AI Geek Programmer.

]]>
https://aigeekprogrammer.com/pl/przygotowanie-danych-do-uczenia-maszynowego-w-pytorch/feed/ 0
YOLO szybka detekcja i klasyfikacja obiektów https://aigeekprogrammer.com/pl/yolo-szybka-detekcja-klasyfikacja-obiektow/ https://aigeekprogrammer.com/pl/yolo-szybka-detekcja-klasyfikacja-obiektow/#respond Wed, 02 Jun 2021 18:28:54 +0000 https://aigeekprogrammer.com/?p=8807 Computer Vision jest jednym z najciekawszych i moim ulubionym obszarem zastosowań dla sztucznej inteligencji. Sporym wyzwaniem dla algorytmów analizy obrazów jest szybka detekcja i klasyfikacja obiektów w czasie rzeczywistym. Problem detekcji obiektów jest dużo trudniejszy niż klasyfikacja, którą wielokrotnie omawiałem na moim blogu, ponieważ nie...

The post YOLO szybka detekcja i klasyfikacja obiektów appeared first on AI Geek Programmer.

]]>
Computer Vision jest jednym z najciekawszych i moim ulubionym obszarem zastosowań dla sztucznej inteligencji. Sporym wyzwaniem dla algorytmów analizy obrazów jest szybka detekcja i klasyfikacja obiektów w czasie rzeczywistym. Problem detekcji obiektów jest dużo trudniejszy niż klasyfikacja, którą wielokrotnie omawiałem na moim blogu, ponieważ nie tylko musimy wskazać co to za obiekt, ale również miejsce, w którym się on znajduje. Można prosto wymienić dziesiątki zastosowań dla algorytmów detekcji, ale uogólniając można założyć, że maszyna (np. autonomiczny samochód, robot przemysłowy, system detekcji / oceny) powinna być w stanie w czasie rzeczywistym identyfikować widoczne obiekty (ludzi, oznakowanie, obiekty przemysłowe, inne maszyny, itp.), aby dostosowywać swoje kolejne zachowania lub wysyłane sygnały do sytuacji zastanej w otoczeniu. I tu na scenę wchodzi You Only Look Once (YOLO).

YOLO został zaproponowany przez Josepha Redmona et al., a jego najnowsza, na dzień pisania tego posta, wersja trzecia jest opisana w pracy YOLOv3: An Incremental Improvement. Polecam również poniższe nagranie wystąpienia Redmona na TEDx.

Trzema najważniejszymi cechami algorytmu YOLO, które wyróżniają go na tle konkurencji są:

  • Użycie grida zamiast pojedynczego okna przesuwającego się po obrazku (jak to ma miejsce np. w Fast R-CNN). Dzięki takiemu podejściu sieć neuronowa widzi od razu cały obrazek a nie tylko jego niewielką część. Dzięki temu może nie tylko przeanalizować cały obraz szybciej, ale również wyciągać wnioski z całej zawartości informacyjnej obrazu, a nie tylko z jego wycinka, który nie zawsze niesie ze sobą informacje kontekstowe. Dzięki tej ostatniej cesze, YOLO generuje dużo mniej pomyłek polegających na wzięciu fragmentu tła za obiekt – jeden z głównych problemów konkurencyjnego algorytmu Fast R-CNN.
  • Sprowadzenie problemu klasyfikacji i lokalizacji obiektu do jednego problemu klasy regresji, kiedy to wektor wyjściowy zawiera zarówno prawdopodobieństwa klas, jak i koordynaty obszaru zawierającego obiekt (tzw. bounding box).
  • Bardzo skuteczna generalizacja wiedzy. Jako ciekawostkę potwierdzającą tę cechę autorzy wykazują, że YOLO wyuczone na obrazach przedstawiających naturę, doskonale radzi sobie w detekcji obiektów na dziełach sztuki.

W efekcie otrzymujemy algorytm, który nie tylko jest w stanie przetworzyć ponad 45 klatek na sekundę, ale również daje zbliżoną (choć nieco niższą) skuteczność detekcji do zdecydowanie wolniejszych rozwiązań.

YOLO benchmark

Źródło: YOLOv3: An Incremental Improvement Joseph RedmonAli Farhadi, University of Washington

YOLO szybka detekcja i klasyfikacja obiektów – jak to działa?

Tradycyjne metody detekcji obiektów najczęściej dzielą cały proces na kilka etapów. Dla przykładu Faster R-CNN najpierw wykorzystuje konwolucyjną sieć neuronową aby wydobyć poszukiwane cechy obrazka (tzw. feature extraction). Następnie wynik w postaci wyjściowego feature map jest wejściem do kolejnej sieci neuronowej, której zadaniem jest zaproponowanie regionów obrazka, w których mogą znajdować się obiekty. Taka sieć nosi nazwę Region Proposal Network (RPN) i jest ona zarówno klasyfikatorem (wskazuje prawdopodobieństwo, że dany region zawiera obiekt), jak i modelem regresyjnym (opisuje region obrazka zawierający potencjalny obiekt). Wynik działania RPN jest przekazywany do trzeciej sieci neuronowej, której zadaniem jest przewidzenie klasy obiektu i prostokąta otaczającego (bounding box). Jak widać jest to dość skomplikowany, wieloetapowy proces, który niejako z założenia musi trwać dość długo, przynajmniej w porównaniu do YOLO.

YOLO przyjmuje zupełnie inne podejście. Przede wszystkim traktuje zagadnienie detekcji jak pojedynczy problem regresji. Nie dzieli analizy na etapy. Zamiast tego pojedyncza konwolucyjna sieć neuronowa jednocześnie przewiduje wiele obszarów, w których powinien znajdować się obiekt (bounding boxes) oraz wyznacza prawdopodobieństwa klas dla każdego z obszarów, w których obiekt został wykryty.

W pierwszym kroku YOLO nakłada na obraz siatkę o rozmiarach S x S. Na przykład dla S=4 otrzymamy 16 komórek, jak na obrazku poniżej.

YOLO - szybka detekcja obrazów

Siatka YOLO

Dla każdej komórki YOLO przewiduję B obiektów. B z reguły jest niewielką liczbą, np. 2. Oznacza to, że zakładamy, iż w każdej komórce YOLO zidentyfikuje co najwyżej 2 obiekty. Oczywiście na obrazie może być więcej nakładających się na siebie obiektów. Ale w sytuacji, gdy w danej komórce są 3 lub więcej nachodzących na siebie obiektów, to stają się one bardzo trudne do identyfikacji – szczególnie, jeżeli weźmiemy pod uwagę fakt, że S z reguły jest większe niż użyte w naszym przykładzie 4 i co za tym idzie nakładana siatka ma większą granulację.

Tak więc dla każdej komórki grida YOLO przewiduje czy znajdują się w nim obiekty i dla każdego z nich określane są koordynaty prostokąta otaczającego obiekt. Oznacza to, że w wektorze wyjściowym musimy przewidzieć miejsce na B * 5 wartości. Dlaczego 5? Wynika to z tego, że prostokąt otaczający określany jest 4 wartościami: dwie współrzędne środka obiektu (relatywnie do współrzędnych analizowanej komórki) oraz szerokość i wysokość prostokąta (zapisane jako ułamek wielkości obrazka). Dodatkowo dochodzi 5 wartość, która określa logicznie czy w danej komórce jest obiekt czy go nie ma. 

Na samym końcu do wektora wyjściowego należy dołączyć prawdopodobieństwa dla klas – dla każdego z obiektów z osobna. Dla przykładu jeżeli zamierzamy przewidzieć C = 10 klas dla maksymalnie B = 2 obiektów w jednej komórce grida, to ostateczny tensor wyjściowy będzie miał rozmiar: S x S x (B * (5 + C)), równy dla naszego przykładu  4 x 4 x (2 * (5 + 10)) = 4 x 4 x 30.

Zatem uczymy sieć i prowadzimy predykcję zakładając na wyjściu tensor – stąd łatwo zrozumieć dlaczego problem identyfikacji i klasyfikacji obiektu został sprowadzony przez YOLO do problemu klasy regresji.

Dwie istotne kwestie, które być może zwróciły Waszą uwagę:

  1. W naturalny sposób pojawia się pytanie, czy jeżeli w jakiejś komórce grida algorytm zidentyfikuje obiekt, to czy otaczający go prostokąt jest w jakiś sposób związany z komórką grida? I tak, i nie. Tak, bo w tejże komórce przypada środek prostokąta otaczającego. Nie, bo oczywiście faktyczny prostokąt otaczający prawie nigdy nie pokryje się granicami komórki grida.
  2. W związku z nakładaniem grida, który ma tyle samo wierszy co kolumn, analizowane obrazki muszą być kwadratowe i mieć zakładany przez daną implementację rozmiar. Jeżeli obrazki są prostokątne i nie odpowiadają oczekiwanym przez sieć rozmiarom, to wiele implementacji algorytmu YOLO dokonuje zmiany rozmiarów obrazków wejściowych i przekształcenia na aspect ratio = 1 (np. uzupełniając obraz czarnym paddingiem).

YOLO – struktura sieci

W wersji trzeciej algorytmu autorzy użyli rozbudowanej sieci konwolucyjnej o architekturze zaprezentowanej poniżej. BTW: jeżeli chcecie poznać sieci konwolucyjne od podstaw, to na moim blogu dostępny jest czteroczęściowy tutorial.

Sieć ma aż 53 warstwy konwolucyjne, stąd została nazwana Darknet-53.

YOLO - architektura CNN

Darnknet-53 – architektura sieci konwolucyjnej

YOLO w praktyce

Zobaczmy jak YOLO zachowuje się w praktyce. Instrukcja instalacji jest dostępna na stronie autora, przy czym nie udało mi się jej pomyślnie wykonać na Windows 10. Zalecam zatem od razu przesiadkę na Linuxa lub Maca. Poniższe wskazówki są dla Ubuntu w wersji 20.04.

Do zbudowania klasyfikatora niezbędna będzie możliwość kompilacji z wykorzystaniem gcc. Jeżeli kompilator nie jest zainstalowany w systemie operacyjnym (gcc – -version), to sugeruję wykonanie tych trzech komend:

$ sudo apt update
$ sudo apt install build-essential
$ gcc --version

Jeżeli wersja kompilatora wyświetla się poprawnie, to możemy przejść do pobrania i skompilowania Darknetu:

$ git clone https://github.com/pjreddie/darknet
$ cd darknet
$ make

W repozytorium znajduje się kod, nie ma natomiast danych wag wyuczonej sieci. Te musimy ściągnąć oddzielnie. Poniższą komendę należy wykonać z katalogu darknet i tam też powinien być zapisany plik z wagami sieci.

$ wget https://pjreddie.com/media/files/yolov3.weights

W zależności od tego jak dużą ilością pamięci RAM dysponujemy (moje Ubuntu miało tylko 512MB RAM), to należałoby zwiększyć swapfile. Ja zwiększyłem go do 2GB, ale być może mniejsze wartości również wystarczą:

$ sudo fallocate -l 2G /swapfile
$ sudo chmod 600 /swapfile
$ sudo mkswap /swapfile
$ sudo swapon /swapfile

W tej chwili powinniśmy być w stanie uruchomić predykcję dla jednego z przykładowych zdjęć w katalogu data. Poniżej przykład dla pliku kite.jpg. Uwaga: może okazać się konieczne uruchamianie predykcji jako super user – stąd dodane sudo:

$ sudo ./darknet detect cfg/yolov3.cfg yolov3.weights data/kite.jpg

Wynik zapisywany jest na serwerze w pliku predictions.jpg. Należy go pobrać i wyświetlić. Oto wynik klasyfikacji dla latawców – połączone obrazki wejściowy i wyjściowy:

YOLO szybka detekcja i klasyfikacja obiektów - przykład nr 1

Działanie algorytmu YOLO – przykład nr 1

A poniżej użyte przeze mnie prywatne zdjęcie z końmi na wybiegu:

$ sudo ./darknet detect cfg/yolov3.cfg yolov3.weights data/horses-square.jpg

YOLO szybka detekcja i klasyfikacja obiektów - przykład nr 2

Działanie algorytmu YOLO – przykład nr 2

Jak widać patatajce zostały w większości zlokalizowane i poprawnie zakwalifikowane. Dwa z trzech koni na dalekim tle, obrócone tyłem, nie zostały znalezione, ale trzeba przyznać, że również dla ludzkiego oka nie byłoby to łatwe. Akcentem humorystycznym jest kwalifikacja zbiornika z gazem jako konia, ale też trudno nie zgodzić się, że zbiornik w tym ujęciu z daleka przypomina nieco zad konia. 😉

Ta pomyłka ze zbiornikiem jest paradoksalnie potwierdzeniem jednej z silnych cech YOLO – algorytm patrzy na obrazek jako na całość i wnioskuje kontekstowo z całej zawartości obrazu, a nie z wąskiego fragmentu. YOLO ma pewne problemy z detekcją małych obiektów i poradzi sobie gorzej ze scenami przedstawiającymi wiele nakładających się na siebie obiektów, ale generalnie jest to genialna architektura, do tego napisana w C z wykorzystaniem CUDA, co czyni go bardzo szybkim i skutecznym narzędziem detekcji i klasyfikacji obiektów w czasie rzeczywistym.

The post YOLO szybka detekcja i klasyfikacja obiektów appeared first on AI Geek Programmer.

]]>
https://aigeekprogrammer.com/pl/yolo-szybka-detekcja-klasyfikacja-obiektow/feed/ 0
Sztuczna inteligencja i blockchain https://aigeekprogrammer.com/pl/sztuczna-inteligencja-blockchain/ https://aigeekprogrammer.com/pl/sztuczna-inteligencja-blockchain/#respond Sun, 08 Nov 2020 14:39:36 +0000 https://aigeekprogrammer.com/?p=8710 Patrząc na postęp technologiczny, jaki dokonał się w ostatnich kilku latach, trudno wymienić dwie bardziej przełomowe technologie niż sztuczna inteligencja i blockchain. Pierwsza z nich otworzyła całkiem nowe możliwości na polach analizy danych, przewidywania wyników i robotyce. Druga na całkiem nowy poziom wyniosła decentralizację, transparentność...

The post Sztuczna inteligencja i blockchain appeared first on AI Geek Programmer.

]]>
Patrząc na postęp technologiczny, jaki dokonał się w ostatnich kilku latach, trudno wymienić dwie bardziej przełomowe technologie niż sztuczna inteligencja i blockchain. Pierwsza z nich otworzyła całkiem nowe możliwości na polach analizy danych, przewidywania wyników i robotyce. Druga na całkiem nowy poziom wyniosła decentralizację, transparentność oraz bezpieczeństwo wynikające z wbudowanej w blockchain gwarancji niezmienności. Obie technologie są cały czas w dość wczesnej fazie adaptacji, ale rozpoczynająca się dekada może być dla nich okresem pełnego rozkwitu. Utworzona z wykorzystaniem AI i blockchain nowa ekonomia oparta na danych, sztucznej inteligencji i systemach zdecentralizowanych jest obietnicą jednocześnie kuszącą, jak i rodzącą pewne obawy. Czy sztuczna inteligencja i blockchain mogą na trwałe skrzyżować swoje drogi? Czy ich współpraca może przynieść efekt synergii, czy też ich dalszy rozwój będzie bardziej silosowy, z nielicznymi punktami styku? Jakie są pola do potencjalnej współpracy i jakie korzyści może przynieść jednoczesne zastosowanie obu technologii?

Sztuczna inteligencja – technologia (najbliższej) przyszłości

Paradoksalnie AI jest dość wiekową nauką, bo jej początki sięgają lat pięćdziesiątych ubiegłego stulecia. Dziedzina przechodziła przez ten czas przez wiele trudnych okresów, głównie z dwóch powodów: zbyt małej ilości danych i braku wystarczających mocy obliczeniowych. W międzyczasie zdążyła wykształcić wiele metod, które silnie opierały się o statystykę i modele matematyczne – hasło machine learning słyszał już chyba każdy. W ostatnich latach nastąpił jednak rozkwit bardziej zaawansowanego AI. Po pierwsze, pojawiły się ogromne ilości danych zbieranych głównie przez korporacje oraz organizacje państwowe w USA i Chinach. Po drugie, dostępność wysokich mocy obliczeniowych jest najwyższa w historii. Możemy ją dostać nawet za darmo, dla zastosowań niekomercyjnych i naukowych albo kupić w dowolnej ilości w chmurze. W końcu, pojawiła się idea uczenia głębokiego (Deep Learning), która wykorzystując sieci neuronowe

Sztuczna inteligencja i blockchain

Rysunek 1 – AI vs. uczenie maszynowe vs. sieci neuronowe vs. Deep Learning

zasilane ogromnymi ilościami danych, przetwarzane w wysoko wydajnych środowiskach, pozwoliła na uzyskanie niesamowitych rezultatów na wielu polach, na których dominacja człowieka była dotychczas niepodważalna: szachy, GO, gry komputerowe, rozpoznawanie obrazów, przetwarzanie mowy, analityka danych, diagnozowanie medyczne, produkcja nowych leków – ta lista mogłaby być dużo dłuższa. Co ciekawe, głębokie sieci neuronowe rozwiązały częściowo jedną z podstawowych bolączek uczenia maszynowego, jakim była konieczność mozolnego opisu danych przez człowieka, przed zaprezentowaniem danych algorytmowi. Sieci neuronowe robią to w dużej części same w trakcie procesu uczenia (tzw. feature extraction).

Sztuczna inteligencja jest obecnie w fazie wąskich praktycznych zastosowań – tzw. narrow AI. Oznacza to, że praktyczne zastosowania sztucznej inteligencji doskonale sprawdzają się w wąskich dziedzinach, których część wymieniłem powyżej. Nie oszukujmy się jednak, w zaciszu dobrze strzeżonych korporacyjnych i rządowych laboratoriów trwają prace nad stworzeniem general AI, która będzie posiadała autonomię i umiejętność generalizowania problemów oraz ich abstrakcji na wyższe poziomy, podobną do tej, jaką dysponują ludzie. Zmieni to całkowicie układ sił i charakter światowego ładu ekonomicznego w dużo większym stopniu niż zrobił to covid-19, choć tempo wprowadzenia tej zmiany będzie dużo mniejsze i bardziej kontrolowalne. Transformacja ta prawdopodobnie jest nieunikniona i budzi spore obawy u większości z nas. Jesteśmy w końcu jako gatunek od tysiącleci przyzwyczajeni do zdecydowanej intelektualnej dominacji nad resztą gatunków żyjących na ziemi. Perspektywa pojawienia się tworu, który intelektualnie może nas przewyższyć, jest mocno niepokojąca.

Jeżeli chcesz się dowiedzieć więcej o AI i kilku podstawowych pojęciach z nią związanych, to zapraszam do lektury jednego z moich poprzednich wpisów.

Blockchain? Chwila, ale o co chodzi?

Kiedy ktoś rzuci hasło blockchain, jednym z pierwszych skojarzeń będą zapewne kryptowaluty, w szczególności bitcoin. Jest to zrozumiałe, gdyż blockchain po raz pierwszy na masową skalę w praktyce wykorzystany został przez Satoshi Nakamoto (według wielu tego pseudonimu użył nieżyjący już Hal Finney – ale kwestia tożsamości Nakamoto, jak również pytanie czy ktoś ma dostęp do klucza prywatnego jego portfela, 😉 to temat na zupełnie osobny wpis). Satoshi stworzył pierwszą niezależną, zdecentralizowaną i w pełni elektroniczną walutę. Technologię blockchain łatwo wyjaśnić na przykładzie bitcoina, jeżeli jednak ktoś myśli, że blockchain równa się kryptowaluty, to trzeba mu powiedzieć: nie tak szybko!

Fundamentami technologii blockchain są kryptografia, sieć peer-to-peer oraz tzw. Ditributed Ledger Technology (DLT), czyli rozproszona baza danych, zarządzana przez wielu uczestników, bez jednego wyróżnionego koordynatora. Ale zacznijmy od początku: skąd w ogóle wzięła się ta nazwa? Blockchain jest jednym ze sposobów na przechowywanie informacji. Podobnie jak w tradycyjnych bazach danych informacje te grupowane są w bloki – stąd fraza „block”. Bloki te są z kolei wiązane ze sobą w szeregowy łańcuch wynikiem działania kryptograficznej funkcji skrótu – stąd fraza „chain”. W uproszczeniu: wykopanie bloku polega na wyliczeniu hasha dla jego zawartości (bitcoin wykorzystuje SHA-256) i zapisanie jego wartości do następnego bloku, gdzie wartość ta będzie stanowiła jeden ze składników do wyliczenia hasha dla tego nowego bloku. I tak łańcuszek idzie do przodu. Obecnie (październik 2020) blockchain bitcoina przechowuje około 580 milionów transakcji, a całkowity rozmiar blockchaina dla bitcoina wynosi około 300 GB.

Sztuczna inteligencja i blockchain

Rysunek 2 – blockchain uproszczony schemat

Bitcoin działa od 2009 roku i przez ten czas nikomu nie udało się skutecznie przeprowadzić żadnego ataku, który zmieniłby dane nawet pojedynczej transakcji. Dlaczego? Ponieważ hash dla każdego bloku powinien spełniać pewne specyficzne warunki, np. zawierać na początku N zer. Aby uzyskać właściwy hash, górnicy zmieniają zawartość bloku, losowo modyfikując dedykowane temu pole nonce lub jeżeli żadna wartość nonce nie daje pożądanego hasha, zmieniając nieznacznie zawartość bloku w inny dopuszczalny sposób – więcej o praktyce kopania bloku bitcoina można znaleźć tu. W rezultacie wykopanie pojedynczego bloku bitcoina jest bardzo kosztowne obliczeniowo. Gdyby ktoś chciał zmienić transakcję w blockchain bitcoina, która została zarejestrowana ileś tam bloków temu, musiałby zmienić zawartość tego konkretnego bloku (aby zmienić transakcję), wyliczyć na nowo hash dla niego, a ze względu na referencyjne związanie kolejnych bloków hashami poprzednich, wyliczyć również nowe hashe dla kolejnych bloków! A to wszystko w trakcie, gdy cały czas sieć produkuje nowe bloki. Teoretycznie taki atak jest możliwy, ale w praktyce jest technicznie niewykonalny i ekonomicznie nieuzasadniony.

Odporność bitcoina na ataki jest potwierdzeniem jednej z najważniejszych cech technologii blockchain – gwarancji niezmienności zapisów umieszczonych w DLT. Ma to ogromne znaczenie w oczywisty sposób dla transakcji finansowych, ale również dla wszystkich innych potencjalnych obszarów zastosowań, gdzie pewność, iż widzimy prawdziwe i niezmienione dane jest kluczowa, np.: wyniki badań medycznych, rezultaty głosowania, zawartość księgi wieczystej nieruchomości, którą planujemy zakupić, dowody w kryminalistyce, analityka danych, na podstawie których zarząd firmy podejmuje kluczowe decyzje, itp.

Dyskutując najistotniejsze cechy technologii blockchain, warto ponadto zauważyć, że:

  • publiczny blockchain jest strukturą w pełni rozproszoną i niezależną, w tym sensie, że nie zależy od jakiejkolwiek centralnej organizacji (banku centralnego, rządu, korporacji), zatem organizacje te nie mogą w żaden sposób wpływać na zapisy w DLT. To nie oznacza oczywiście, że rządy, banki centralne czy korporacje nie mogą tej technologii używać. Używają jej już teraz i perspektywy użycia są coraz bardziej dalekosiężne. Rozproszenie sieci blockchain zapewnia również odporność na awarię i na potencjalnie zbyt duży wpływ pojedynczego uczestnika sieci na jej działanie,
  • wszystkie transakcje w publicznym blockchain są jawne i dostępne dla każdego. To nie znaczy, że każdy może zajrzeć do naszego portfela, chyba że zna jego adres. Są zresztą obecnie dostępne blockchainy, które zapewniają ukrywanie części danych w bloku, co powoduje istotne zwiększenie prywatności,
  • wszystkie transakcje są nie tylko niezmienne, ale również w sposób niepodważalny oznaczone czasem – wynika to z natury blockchaina, który jest strukturą jedynie dodającą kolejne zapisy. Archiwalne zapisy nie mogą być modyfikowane.

Powyższe to jedynie skromny wycinek całego ekosystemu technologii blockchain, a dyskutowanie w szczegółach właściwości tej technologii wykracza zdecydowanie poza zakres tego artykułu. Zainteresowanym pogłębieniem wiedzy na temat technikaliów bitcoina polecam ten artykuł, gdzie autorowi udało się w mojej ocenie wytłumaczyć podstawy tej technologii w wyjątkowo przystępny i spójny sposób.

Jako ciekawostkę dodam jeszcze, że treści postów na moim blogu chronione są przez plugin WordProof, który wykorzystuje technologię blockchain do ochrony własności intelektualnej.

Sztuczna inteligencja i blockchain – jak to połączyć?

Zastanówmy się przez chwilę, jak obie technologie pasują do siebie pod kątem najważniejszych cech.

Jednym z głównych zastosowań AI jest przewidywanie przyszłości. Ocena sytuacji na drodze autonomicznego samochodu, przewidzenie następnego ruchu szachisty, czy prognozowanie zachowań ekonomicznych. Blockchain tymczasem skupia swoją uwagę na trwałym zapisie przeszłości – zapisy w DLT raz uzgodnione przez sieć, pozostaną niezmienne do końca jej istnienia.

AI jest ekstremalnym konsumentem danych. Aby być skuteczna potrzebuje danych tak, jak ludzie potrzebują tlenu do życia. Danych w ogromnej ilości. Blockchain jest co do zasady bazą danych, której głównym zadaniem jest ich utrwalanie i serwowanie w sposób zdecentralizowany i transparentny. Ilość danych konsumowanych przez blockchain jest relatywnie niewielka.

Sztuczna inteligencja jest niezwykle dynamiczna, w tym sensie, że dostosowuje swoje działanie i generowane wyniki do zmieniającego się otoczenia (danych). Opiera się na w dużej części na statystyce i rachunku prawdopodobieństwa. Blockchain jest statyczny, można nawet powiedzieć, że jest deterministyczny, przewidywalny.

Cechą z całą pewnością wiążąca obie technologie jest ich ogromny apetyt na coraz większą moc obliczeniową.

Nie wiem ja wam, ale mnie się wydaje, że sztuczna inteligencja i blockchain mają cechy, które mogą świetnie nawzajem się uzupełniać. Ich umiejętne użycie w jednym rozwiązaniu może dać efekt silnie synergiczny. Można powiedzieć, że sztuczna inteligencja i blockchain są jak ogień i woda. Choć według mnie bardziej trafnym porównaniem będzie wodór i tlen. Pierwszy jest dynamiczny i silnie wybuchowy. Drugi sam w sobie podtrzymuje reakcje chemiczne. Połączone razem dają wodę – substancję niezbędną do życia.

Porzucając jednak filozoficzno-chemiczne rozważania, zastanówmy się w jakich obszarach można liczyć na największą synergię.

Czy centralizacja jest dobra dla rozwoju sztucznej inteligencji?

Zacznę od obszaru wg mnie zdecydowanie najważniejszego – konieczności większej decentralizacji i demokratyzacji AI. Stan obecny, kiedy większość rewolucyjnych rozwiązań AI tworzonych jest przez największe korporacje i nieliczne rządy nie może doprowadzić do niczego dobrego. W najlepszym wypadku doprowadzi do dalszego wzrostu nierówności społecznych i zgromadzenia większości światowego majątku przez mniej niż tysięczną promila całej populacji. Nierówności społeczne są nieuniknione w gospodarce kapitalistycznej, ale ich nadmierne wyolbrzymienie prowadzi z reguły do niepokojów społecznych, rządów populistów, a czasami rewolucji.

Ryzyko centralizacji AI jest efektem skupienia danych, mocy obliczeniowych i najbardziej płodnych umysłów w rękach garstki największych korporacji i nielicznych rządów. W efekcie powstaną rozwiązania, które będą służyły tymże rządom (do kontroli) oraz tymże korporacjom (do dalszego gromadzenia kapitału). Czy chcemy aby rozwój AI szedł w tym kierunku? Czy może wolelibyśmy, aby rozwój AI był bardziej zdemokratyzowany?

Jak humorystycznie zauważył jeden z orędowników zdecentralizowanej AI – Ben Goertzel – kilka lat temu ludzie byli przekonani, że sztuczna inteligencja nas zabije, kiedy tylko przewyższy poziomem intelektualnym swoich twórców. Było to z całą pewnością pokłosie filmów typu Matrix, czy Terminator. Obecnie ludzie bardziej martwią się, że AI odbierze im pracę. Ta ciekawa zmiana w postrzeganiu AI może oznaczać, że ludzie pogodzili się z wizją zniszczenia naszego gatunku przez AI, ale przez te ostatnie lata życia nie chcieliby być bezrobotni. 😉 Polecam obejrzeć poniższe wystąpienie Bena Goertzela na TEDx

Goertzel i jemu podobni promują idee rozwiązań opartych na AI i blockchain w poszukiwaniu zdecentralizowanej sztucznej inteligencji a ostatecznie Artificial General Intelligence (AGI) – tworu, który poziomem i sposobem myślenia dorówna człowiekowi. Tak rozwijana sztuczna inteligencja nie będzie kontrolowana przez jedną organizację lub jakąkolwiek „grupę trzymającą władzę” 😉 , a zarządzana i rozwijana w sposób demokratyczny i zdecentralizowany.

Na kanwie tych przekonań powstało wiele projektów, z których wybrałem dwa, o których należałoby krótko wspomnieć. Pierwszym z nich jest fundacja DAIA, która zrzesza organizacje pracujące nad zdecentralizowaną sztuczną inteligencją wykorzystującą blockchain, co ma w zamierzeniu przeciwstawić się skupieniu potęgi AI w jednym miejscu. Jeżeli ktoś szuka informacji o projektach Decentralized Artificial Intelligence, to DAIA może być dobrym miejscem do startu poszukiwań. Jednym z założeń twórców fundacji jest przekonanie, że AGI może być osiągnięta nie tyle przez jedną sieć, ale przez wiele rozproszonych sieci, komunikujących się ze sobą i dzielących się nawzajem danymi. A te możliwości w oczywisty sposób może zapewnić technologia blockchain. To założenie przyjmuje również drugi projekt – SingularityNET, którego jednym z celów jest udostępnienie zdecentralizowanej giełdy rozwiązań sztucznej inteligencji, do której można wrzucić model AI i ogłosić go w zdecentralizowanej sieci tak, aby firma, która potrzebuje takiej usługi mogła z niej łatwo, szybko i bezpiecznie skorzystać.

Jakkolwiek projekty te wydają się nieco niedoinwestowane i przyciągające obecnie niewielkie zainteresowanie, to są one niezwykle istotne z punktu widzenia zdemokratyzowania AI i wpływu szerszej grupy społecznej na kształt i formę dalszego rozwoju sztucznej inteligencji. Jeżeli można byłoby je do czegoś porównać, to być może w pewnym stopniu do ruchu open-source w jego wczesnej fazie rozwoju i późniejszego wpływu, jaki miał na demokratyzację i demonopolizację wielu rozwiązań IT.

Jak chronić swoje dane i ich prywatność jednocześnie zarabiając na nich?

Sztuczna inteligencja żywi się danymi. Dane we współczesnej ekonomii potrafią być cenniejsze niż złoto. Szczególnie te dane, które są rzadkie, zawierają dane wrażliwe oraz są odpowiednio przygotowane do użycia w uczeniu maszynowym. Dlaczego? Można zaryzykować stwierdzenie, że w projektach uczenia maszynowego między 30% a 60% czasu i kosztów pochłania inicjalne zgromadzenie danych, ich wstępna analiza, omówienie biznesowe, sprawdzenie czy spełniają różnorakie kryteria (np. związane z ochroną danych wrażliwych), następnie czyszczenie danych, sprawdzanie jakości zbioru i przekształcanie do pożądanej postaci.

Co jeżeli jesteśmy w posiadaniu odpowiednio opracowanego zbioru rzadkich danych medycznych, które chcielibyśmy udostępnić, aby inni naukowcy mogli z nich skorzystać w swoich badaniach. Co jeżeli chcielibyśmy taki zbiór sprzedawać? Czy zbiór zostanie skopiowany, zmieniony w nieuprawniony sposób? Jak dane zmienione w nieuprawniony sposób wpłyną na wyniki badań innych zespołów? Kilka projektów z obszaru sztucznej inteligencji i blockchain zaadresowało powyższe problemy. Jednym z takich projektów jest wykorzystujący blockchain Ocean Protocol. W dużym skrócie, korzystając z giełdy udostępnionej przez Ocean Protocol można utworzyć na niej coś w rodzaju aktywa AI. Takim aktywem może być odpowiednio przygotowany zbiór danych, ale też może być nim już wyuczony model, gotowy do użycia. Po opublikowaniu go w Ocean Protocol aktywa zostają zarejestrowane w blockchain i otrzymują unikalny identyfikator. Wszystkie aktywa posiadają metadane je opisujące a właściciel otrzymuje oczywiście klucz prywatny potwierdzający właścicielstwo. Ocean Protocol zapewnia również zasoby do przechowywania aktywów AI. Co ciekawe, w celu ochrony prywatności można przetwarzać model w infrastrukturze Ocean Protocol bez ujawniania danych żadnej stronie trzeciej, co jest szczególnie ważne dla danych wrażliwych i prywatnych.

Moce obliczeniowe do wykorzystania

Jak już wspominałem powyżej, sztuczna inteligencja i blockchain z całą pewnością współdzielą jedną cechę – zachłanną konsumpcję mocy obliczeniowych. Jednym z potencjalnych pól do współpracy wydaje się zatem współdzielenie mocy niewykorzystanej. Obecnie większość obliczeń AI odbywa się w scentralizowanych ośrodkach obliczeniowych. Mają one określoną wydajność, a korporacje udostępniające moc obliczeniową regulują dostęp do niej mechanizmami cenowymi. To może mocno ograniczać innowacje poza światem bogatych organizacji, szczególnie wśród naukowców i biznesmenów z krajów z walutami o niższej sile nabywczej. A zapotrzebowanie na moc obliczeniową związaną ze sztuczną inteligencją z roku na rok rośnie bardziej niż liniowo. Blockchain w naturalny sposób mógłby zdemokratyzować dostęp do mocy obliczeniowej bazując na fakcie, że zdecydowana większość blockchainów działa jako potężne obliczeniowo sieci peer-to-peer.

Testowanie inteligentnych kontraktów

Rok 2020 będzie w obszarze kryptowalut zapamiętany jako okres narodzin zdecentralizowanych rozwiązań finansowych. Ruch ten określany jest jako Decentralized Finance lub w skrócie DeFI. Jest to o tyle ciekawa gałąź technologiczna, że demokratyzuje jedną z podstawowych potrzeb współczesnego człowieka – zarządzanie finansami. Poprzez demokratyzację rozumiem uniezależnienie się od jednej centralnej instytucji, która nadzoruje, ale też kontroluje wszelkie operacje finansowe. Ta kontrola jest wielopoziomowa, bo nad bankami są instytucje nadzoru i banki centralne. Ma to oczywiście ogromne zalety, ale też spore wady. Wchodzenie w celowość DeFI wykracza poza zakres tego artykułu. Istotne z naszego punktu widzenia jest to, że DeFI, jak i wiele innych aplikacji bazujących na blockchain, opiera swoje działanie o tzw. smart contracts, czyli aplikacje publikowane na blockchain, których zadaniem jest automatyczne wykonywanie transakcji określonego typu. Są one zapisywane w blockchain, a co za tym idzie nie wymagają nadzoru zaufanej strony trzeciej. DeFi w 2020 są w bardzo wczesnej fazie rozwoju. Rozwiązania powstają jak grzyby po deszczu, a dość częste błędy w implementacji są kosztowne dla twórców i jak na razie głównie spekulacyjnych inwestorów. W sektorze obserwuje się obecnie wiele ataków hackerskich opartych albo o backdoory albo celowo lub niecelowo ukryte podatności, kończące się często tzw. rug pull lub exit scam. Niemniej rynek DeFI rozkwita i płynie do niego coraz więcej pieniędzy. Niektóre projekty potrafią w krótkim czasie przyciągnąć miliony dolarów spekulacyjnych inwestycji. I tu na scenę może wejść AI, która otwiera możliwości w dwóch obszarach. Po pierwsze, szybkie i zautomatyzowane testowanie nowych rozwiązań, które szczególnie w obecnej fazie dojrzałości tego rynku wydają się mocno niedoinwestowane pod względem QA. Testowanie z wykorzystaniem sztucznej inteligencji może polegać na wspartej maszynowo formalnej weryfikacji lub wsparciu testów automatycznych. Drugim obszarem, w którym AI może sprawdzić się doskonale w przypadku aplikacji publikowanych na blockchainie, jest aktywne monitorowanie zachowań smart contracts, w celu wykrywania w czasie rzeczywistym zagrożeń i wycieków danych. Oczywiście na razie żadne AI nie zastąpi testowania przez człowieka, ale wdrożenie takich narzędzi może sprawić, że przynajmniej nie będziemy mieli do czynienia z całkowitym testowaniem na produkcji.

Sztuczna inteligencja i blockchain – przyszłość na wyciągnięcie ręki

W niniejszym artykule starałem się przybliżyć nieco naturę AI i technologii blockchain, wskazać podobieństwa i różnice oraz przykładowe pola do potencjalnej współpracy. Zarówno sztuczna inteligencja, jak i blockchain są cały czas w dość początkowych stadiach biznesowego wykorzystania. Stopień ich wspólnego wykorzystania w rozwiązaniach praktycznych wręcz raczkuje. Etap ten można porównać do internetu wczesnych lat 90-tych, kiedy ciągle był on jeszcze ciekawostką technologiczną i jedynie niewielka część firm i organizacji starała się wykorzystać sieć biznesowo. Obecnie nikt nie kwestionuje roli internetu w demokratyzacji dostępu do informacji, a największe firmy opierają swoją potęgę właśnie o sieć. Czy tak się również stanie ze sztuczną inteligencją i blockchain? Uważam, że tak. Czy będzie to efektem ścisłej współpracy obu technologii w obszarze Decentralized Artificial Intelligence – czas pokaże.


Spodobał ci się post? Będzie mi miło, gdy go polecisz.

Jeżeli chcesz być na bieżąco informowany o nowych artykułach na moim blogu obserwuj moje konto na Twitterze.

 

 

The post Sztuczna inteligencja i blockchain appeared first on AI Geek Programmer.

]]>
https://aigeekprogrammer.com/pl/sztuczna-inteligencja-blockchain/feed/ 0
Sztuczna inteligencja – kilka kluczowych pojęć https://aigeekprogrammer.com/pl/sztuczna-inteligencja-kilka-kluczowych-pojec/ https://aigeekprogrammer.com/pl/sztuczna-inteligencja-kilka-kluczowych-pojec/#respond Sun, 16 Aug 2020 18:08:26 +0000 https://aigeekprogrammer.com/?p=8675 Jeszcze do niedawna duża część kluczowych pojęć z zakresu szeroko rozumianej sztucznej inteligencji nie była jednoznacznie zdefiniowana. Niektóre z nich, jak Deep Learning, były nawet określane mianem “buzzwords”, czyli pojęć używanych głównie przez marketing i nie mających ścisłego przełożenia na obszary naukowe. Obecnie wydaje się,...

The post Sztuczna inteligencja – kilka kluczowych pojęć appeared first on AI Geek Programmer.

]]>
Jeszcze do niedawna duża część kluczowych pojęć z zakresu szeroko rozumianej sztucznej inteligencji nie była jednoznacznie zdefiniowana. Niektóre z nich, jak Deep Learning, były nawet określane mianem “buzzwords”, czyli pojęć używanych głównie przez marketing i nie mających ścisłego przełożenia na obszary naukowe. Obecnie wydaje się, że podstawowe pojęcia ugruntowały się i większość osób zajmujących profesjonalnie AI jest zgodna co do ich znaczenia. Ponieważ pewne definicje przewijają się przez większość postów na moim blogu, a także są obecne w artykułach, tutorialach i kursach dostępnych w sieci, to postanowiłem przybliżyć je wam w możliwie najbardziej przejrzysty sposób.

Po przeczytaniu niniejszego posta:

  • Uporządkujesz sobie wiedzę na temat czterech kluczowych pojęć AI: sztuczna inteligencja (artificial intelligence), uczenie maszynowe (machine learning), sieci neuronowe (neural networks) oraz głębokie uczenie (deep learning).
  • Dowiesz się jakie są różnice między uczeniem nadzorowanym i nienadzorowanym.
  • Będziesz w stanie opowiedzieć czym są zbiory: uczący, testowy i walidacyjny oraz na czym polega zjawisko overfittingu.
  • Określisz czym są hiperparametry, a czym parametry modelu.

Sztuczna inteligencja a uczenie maszynowe a głębokie uczenie

Z reguły najwięcej wątpliwości budzi rozróżnienie pojęć sztucznej inteligencji, uczenia maszynowego, uczenia głębokiego, ich wzajemnych relacji oraz jak w to wszystko wpasowują się niezwykle popularne sieci neuronowe.

Sztuczna inteligencja (AI) jest najszerszym pojęciem określającym de facto nową dziedzinę nauki. Podobną do matematyki, fizyki, czy chemii. Pojęcie to ma bardziej teoretyczno-filozoficzny wydźwięk niż praktyczny i egzystuje od połowy lat pięćdziesiątych, kiedy to podejmowano pierwsze nieudane próby matematycznego modelowania funkcjonowania ludzkiego mózgu. Podam Wam moją ulubioną, bo prostą definicję:

AI to nauka o tym jak zbudować maszynę, która będzie w stanie wykonywać zadania w sposób, który można nazwać inteligentnym.

Obecnie działające systemy sztucznej inteligencji dotyczą wąskich dziedzin i są często określane mianem “narrow” lub “applied”. Dla przykładu, sztuczna inteligencja jest w stanie wygrać w GO lub w szachy z arcymistrzem. Bardzo dobrze poradzi sobie z przetwarzaniem mowy i pisma. Umożliwi szybkie i skuteczne rozpoznawanie otoczenia, np. obiektów na i wokół drogi, itp. Świętym graalem AI jest jednak tak zwana “general AI”, która ma umożliwić maszynie skuteczne rozwiązywanie dużej grupy różnorodnych problemów, czyli zachowywać się w dużej mierze jak człowiek. Jak na razie nikomu nie udało się nawet zbliżyć do tego celu. Niektórzy przewidują, że pierwsze modele general AI będą powstawały jako połączenie wielu modeli “narrow”, co wydaje się na chwilę obecną jedyną rozsądną i wykonalną drogą. Choć kto tam wie, co się dzieje w zaciszu ściśle chronionych pokojów chińskich i amerykańskich korporacji. Droga do prawdziwego Skynetu jest na szczęście bardzo odległa, jeśli w ogóle osiągalna.

Uczenie maszynowe jest jedynie częścią szerszej domeny naukowej, jaką jest sztuczna inteligencja. Najbardziej charakterystyczną cechą uczenia maszynowego jest umiejętność automatycznego uczenia się i doskonalenia poprzez nabywanie nowej wiedzy, w celu rozwiązywania problemu, ale bez implementacji dedykowanego algorytmu.

Ten ostatni fragment jest wg mnie szczególnie istotny dla rozróżnienia uczenia maszynowego od innego “inteligentnego” oprogramowania. Można napisać bardzo skuteczny, dedykowany algorytm, który będzie przewidywał wystąpienie opadów na podstawie aktualnego wyglądu nieba, ale nie będzie to uczenie maszynowe. Uczeniem maszynowym będzie za to zgromadzenie dużej ilości zdjęć nieba, wraz z informacją, czy wystąpiły opady i przetworzenie tych danych przez jeden z algorytmów uczenia maszynowego (np. regresję logistyczną, KNN, sieć neuronową, itp.), w celu uzyskania modelu skutecznie przewidującego wystąpienie opadów.

Sieci neuronowe są jednym z algorytmów / sposobów uczenia maszynowego. Algorytm ten wykorzystuje struktury matematyczne w zachowaniu przypominające działania ludzkich neuronów. Takie sztuczne neurony, połączone w sieć, przyjmują sygnały na wejściu i wykonując na nich relatywnie prostą operację, emitują sygnał na wyjściu, który przesyłany jest do kolejnej warstwy neuronów lub do wyjścia sieci, jako wynik jej działania. Wejściem do sieci mogą być np. wartości poszczególnych pikseli ze zdjęcia nieba lub odpowiednio przetworzone do postaci cyfrowej dane aplikacji kredytowej.

Każdy sygnał wejściowy do sztucznego neuronu posiada wagę, która wzmacnia lub osłabia sygnał wyjściowy tegoż neuronu. W trakcie uczenia sieci, wykorzystując sprzężenie zwrotne, algorytm uczenia sieci neuronowej modyfikuje wagi przypisane do poszczególnych neuronów tak, aby odpowiedź sieci była obarczona jak najmniejszym błędem. Błąd oblicza się porównując odpowiedź sieci na zestaw danych (np. wygląd nieba) z odpowiedzią prawidłową – jest to tak zwane uczenie nadzorowane, o czym niżej. Do realizacji sprzężenia zwrotnego, czyli odpowiedniej korekcji wag neuronów wykorzystuje się metody obliczeniowe, w tym algorytmy spadku gradientowego, dzięki czemu sieć “uczy się” coraz lepiej rozpoznawać dane. To wszystko w nadziei na to, że pokazując sieci nowe dane, np. aktualny, a wcześniej nie widziany przez sieć wygląd nieba, otrzymamy poprawną prognozę.

I dochodzimy do głębokiego uczenia (deep learning) – fascynującej gałęzi uczenia maszynowego, wykorzystującej bardzo rozbudowane sieci neuronowe, ogromne – podkreślę to raz jeszcze – OGROMNE ilości danych i do niedawna nieosiągalne moce obliczeniowe, do nauczenia komputera rzeczy, które wydawały się jedynie w zasięgu ludzkiego umysłu. W ostatnich latach głębokie uczenie okazało się bardzo skuteczne w rozwiązywaniu problemów dotyczących rozpoznawania obrazów, mowy, szeroko pojętej interakcji z otoczeniem (roboty, samochody), ale także w medycynie, testowaniu gier, czy detekcji i zapobieganiu oszustwom.

Zależności między wyżej wymienionymi pojęciami ilustruje poniższy rysunek.

Sztuczna inteligencja - kilka kluczowych pojęć

Uczenie nadzorowane a nienadzorowane

Skupiając się już tylko na uczeniu maszynowym, należy zwrócić uwagę, że rozróżniamy dwa typy uczenia: nadzorowane i nienadzorowane.

Uczenie nadzorowane jest jak praca dziecka z nauczycielem. Dziecko otrzymuje zestaw obrazków z informacją co jest na danym obrazku: tu jest koń, tu jest drzewo, tutaj auto – nazwijmy ten etap fazą uczenia. Po wystarczającej ilości prób można zapytać dziecko „co jest na obrazku?”, pokazując mu nieco inny samochód, inny gatunek drzewa lub konia o innym umaszczeniu. Moglibyśmy nazwać to fazą testowania, czy dziecko przyswoiło poprawnie pojęcia i co ważniejsze czy jest w stanie generalizować przyswojoną wiedzę. Innymi słowy, czy widząc drzewo, ale nie to samo co w fazie uczenia, jest w stanie nadal rozpoznać obiekt jako drzewo. Jeżeli nie jest, to proces uczenia powtarzamy do skutku.

Dla ludzi nadzorowany sposób uczenia jest naturalny i jesteśmy w tym bardzo dobrzy. W świecie cyfrowym uczenie nadzorowane opiera się o zbiór uczący, który z jednej strony pokazuje dane wejściowe, a z drugiej ich opis, nazywany często labelką lub celem (ang. label lub target). Dane wejściowe są z reguły mozolnie przygotowane uprzednio przez człowieka. Na przykład to człowiek opisuje każdy z dziesiątek tysięcy obrazków, których zamierza użyć w procesie nauki. Algorytm uczy się klasyfikować lub przewidywać wartości na podstawie danych wejściowych zbioru uczącego oraz labelek. Nauka przebiega na zasadzie kolejnych prób, oceny popełnionego błędu i sprzężenia zwrotnego, aby algorytm w razie potrzeby mógł skorygować swoje działanie. Następnie, tak wyuczonej maszynie możemy przedstawić zbiór danych bez targetu i zapytać o predykcję klasy lub wartości.

Uczenie nienadzorowane to jak praca bez nauczyciela. Otrzymujemy zestaw danych i naszym zadaniem jest ich pogrupowanie lub znalezienie pewnych struktur i zależności (czasami ukrytych), bez uprzedniego wskazania czego szukamy lub na jakie klasy mamy dane podzielić. Wyobraźmy sobie, że otrzymaliśmy grupę zdjęć różnych drobnoustrojów i nie znając się na biologii musimy je pogrupować, biorąc pod uwagę na przykład podobieństwa i różnice w wyglądzie lub obserwowalnym zachowaniu.

Dane wykorzystywane do uczenia nienadzorowanego nie muszą być opisane labelkami. Są zatem dużo prostsze do przygotowania niż dane do uczenia nadzorowanego. Niestety, uczenie nienadzorowane może być efektywnie wykorzystane w dość wąskich zastosowaniach. Przykładami jest klasteryzacja, która umożliwia podział zbioru danych na grupy podobnych danych lub tak zwane autoencodery, dzięki którym bez wiedzy o specyfice analizowanego zbioru możemy dokonać jego kompresji do postaci, która będzie zawierała bardziej wartościową informację (pozbawioną szumu).

Rodzaje zbiorów danych i overfitting

Omawiając powyżej sposoby uczenia nawiązywałem do dwóch rodzajów zbiorów: uczącego i testowego. Tak naprawdę mamy jeszcze jeden zbiór – walidacyjny. Warto chwilę poświęcić temu zagadnieniu.

Podział na takie trzy zbiory można dobrze wytłumaczyć poprzez analogię do nauki przedmiotu na studiach. W pierwszej fazie studentom przedstawiany jest materiał na wykładach. Studenci słuchają wykładowcy, robią notatki, starają się zrozumieć podane przykłady. Innymi słowy pracują na zbiorze uczącym. Pominę tu fakt, że wykłady są nieobowiązkowe, 😉 tu akurat nie jest to najlepsza analogia do uczenia maszynowego, gdzie fazy uczenia po prostu nie można pominąć.

W dalszej kolejności studenci idą na ćwiczenia, gdzie rozwiązują zagadnienia samodzielnie, ale nadal pod okiem wykładowcy. To praca na zbiorze walidacyjnym. Ma on pokazać jak dobrze student przyswoił wiedzę z wykładu, czy jego aktualne umiejętności są wystarczające i czy materiał należy dodatkowo wyjaśnić. Proces uczenia na zbiorze testowym (wykład) i walidacyjnym (ćwiczenia) jest powtarzany wielokrotnie. W przypadku uczenia maszynowego musi być powtarzany dużo częściej, nawet dziesiątki lub setki tysięcy razy. W końcu nie od dziś wiadomo, że żadna maszyna nie jest tak efektywna jak student tuż przed kolokwium. 😉 Na koniec semestru następuje jednak ten jakże lubiany przez wszystkich studentów moment – egzamin. Ostateczne potwierdzenie umiejętności zdobytych na wykładach i ćwiczeniach. Student otrzymuje materiał, którego uprzednio nie widział i musi samodzielnie wykazać się odpowiednimi umiejętnościami. To praca na zbiorze testowym.

W zagadnieniu, które chcemy rozwiązać z wykorzystaniem jakiegoś wybranego algorytmu uczenia maszynowego, musimy mieć do dyspozycji zbiór danych – często bardzo duży. W praktyce zbiór taki dzielimy na zbiór uczący i testowy, z reguły w proporcji 80/20. Taki zabieg jest konieczny, aby po nauczeniu modelu móc ocenić, czy nabył on umiejętność generalizowania. Innymi słowy, czy prawidłowo oceni również dane, których nie widział w procesie nauki. Jeżeli nie wydzielimy zbioru testowego, nie będziemy w stanie określić, czy nauczony model dobrze rozwiązuje postawione przed nim zadanie, czy też po prostu nauczył się rozpoznawać dane ze zbioru uczącego i przy prezentacji całkowicie nowej danej już sobie tak dobrze nie poradzi.

Przy okazji: zjawisko, które występuje w sytuacji w której model zapamiętał zbiór uczący, zamiast go zgeneralizować nazywamy overfittingiem. Algorytm nadmiernie dopasował się do danych i przez to nie jest w stanie poradzić sobie równie dobrze z danymi spoza zbioru uczącego.

Często ze zbioru danych (najczęściej ze zbioru uczącego) wyodrębnia się dodatkowo zbiór walidacyjny. Algorytm nie uczy się na zbiorze walidacyjnym, a a jedynie na uczącym, a zbiór walidacyjny używany jest do sprawdzenia efektów w trakcie nauki, w celu odpowiedniego dostosowania hiperparametrów modelu (o nich nieco dalej). Generalnie jest spore zamieszanie z definicją zbioru walidacyjnego. Różni specjaliści definiują go nieco inaczej. Różne biblioteki implementują podział w odmienny sposób. Ale ogólny sens pozostaje taki sam: zbiór walidacyjny, to ocena procesu nauki w jego trakcie.

Czy możemy wykorzystać zbiór walidacyjny jako zbiór testowy, aby ocenić skuteczność gotowego modelu? Niestety nie. Mimo, że zbiór walidacyjny nie jest bezpośrednio używany w procesie nauki, to poprzez oddziaływanie na hiperparametry modelu, ma on istotny wpływ na sposób jego działania i tym samym nie nadaje się obiektywnej oceny skuteczności.

Czym są właściwie hiperparametry?

Skoro już kilkukrotnie nawiązaliśmy do hiperparametrów, to warto przyjrzeć się im nieco bliżej. Zanim jednak przejdziemy do hiperparametrów, warto ustalić czym są parametry modelu, bo rozróżnienie między nimi często nastręcza problemów.

Podstawowym celem uczenia maszynowego jest takie przetworzenie zbioru danych, aby powstał pewien model matematyczny, którego możemy potem użyć do predykcji. W trakcie konstrukcji modelu używamy algorytmów optymalizujących, tak aby predykcja modelu była jak najtrafniejsza. Algorytmy te działają w dużej mierze poprzez tuning szeregu zmiennych opisujących model. Zmienne te nazywamy parametrami modelu. Nie są one ustawiane ręcznie, lecz wynikają z analizy danych zbioru uczącego i ich automatycznej optymalizacji przez algorytm. Przykładem parametrów modelu są wagi przypisane poszczególnym neuronom w sieci neuronowej, a w przypadku regresji logistycznej będą to współczynniki ω wielomianu postaci

y =ω1*x12*x2+…+ωn*xn

definiującego n-wymiarową płaszczyznę, dzielącą dane na klasy.

W przeciwieństwie do parametrów modelu, hiperparametry są zewnętrzne wobec modelu i nie są wyznaczane na podstawie danych w zbiorze uczącym. Są z reguły ustalane przez człowieka nadzorującego budowę modelu, a ich optymalna wartość nie jest znana. Najczęściej są inicjalnie wyznaczane na podstawie najlepszych praktyk, a potem ich wartość może być odpowiednio dostosowana w zależności od wyników uczenia.

Przykładami hiperparametrów może być k, czyli ilość sąsiadów w algorytmie k-najbliższych sąsiadów, ilość warstw sieci neuronowej i ilość neuronów w każdej z warstw, czy tempo uczenia (ang. learning rate), które określa jak szybko poruszamy się po funkcji straty, aby znaleźć jej minimum.

Reasumując, parametry modelu są ustalane automatycznie na podstawie danych zbioru uczącego, a hiperparametry są ustalane ręcznie na podstawie dobry praktyk oraz wyników osiąganych przez model i ich podstawową rolą jest de facto zoptymalizowanie algorytmu, aby ten wyznaczył jak najlepsze parametry modelu.

Mam nadzieję, że niniejszy post przybliżył wam podstawowe pojęcia w zakresu sztucznej inteligencji i uczenia maszynowego. Ich dobre zrozumienie z całą pewnością pomoże w efektywnej dalszej nauce.


Masz pytanie? Zadaj je w komentarzu.

Spodobał ci się post? Będzie mi miło, gdy go polecisz.

Do zobaczenia wkrótce, przy okazji omawiania innego ciekawego tematu!


Czy oglądasz mecze NBA? Sprawdź mój darmowy serwis NBA Games Ranked i ciesz się oglądaniem wyłącznie dobrych meczów.


The post Sztuczna inteligencja – kilka kluczowych pojęć appeared first on AI Geek Programmer.

]]>
https://aigeekprogrammer.com/pl/sztuczna-inteligencja-kilka-kluczowych-pojec/feed/ 0
k najbliższych sąsiadów w klasyfikacji pisma https://aigeekprogrammer.com/pl/k-najblizszych-sasiadow-w-klasyfikacji-pisma/ https://aigeekprogrammer.com/pl/k-najblizszych-sasiadow-w-klasyfikacji-pisma/#respond Sat, 16 May 2020 16:16:16 +0000 https://aigeekprogrammer.com/?p=8468 W uczeniu maszynowym jest takie stare, ale sprawdzone powiedzenie: „Nie da ci ojciec, nie da ci matka, tego co może dać ci … k najbliższych sąsiadów”. 😉 Nie wierzycie? Ja też nie mogłem w to uwierzyć, póki nie spróbowałem sklasyfikować pisma odręcznego z użyciem tego...

The post k najbliższych sąsiadów w klasyfikacji pisma appeared first on AI Geek Programmer.

]]>
W uczeniu maszynowym jest takie stare, ale sprawdzone powiedzenie: „Nie da ci ojciec, nie da ci matka, tego co może dać ci … k najbliższych sąsiadów”. 😉 Nie wierzycie? Ja też nie mogłem w to uwierzyć, póki nie spróbowałem sklasyfikować pisma odręcznego z użyciem tego algorytmu. k najbliższych sąsiadów – z ang. k-nearest neighbours lub po prostu KNN – nie jest może pierwszą myślą, jaka przychodzi do głowy przy klasyfikacji pisma, ale jak się okazuje, użycie tej metody wcale nie jest pozbawione sensu.

Z niniejszego posta dowiesz się między innymi:

  • Jakie są najważniejsze cechy KNN i w jakich klasach zagadnień można jej użyć?
  • Na jakiej zasadzie działa metoda k najbliższych sąsiadów?
  • Jak można skutecznie sklasyfikować pismo odręczne z wykorzystaniem KNN?

Co na pewno trzeba wiedzieć o k najbliższych sąsiadów?

KNN jest bardzo prostym do zrozumienia i zaimplementowania algorytmem. Jednocześnie oferuje on zaskakująco wysoką skuteczność w zastosowaniach praktycznych. Jego prostota, która z całą pewnością bywa jego zaletą, jest jednocześnie cechą, która sprawia, że czasami niepotrzebnie pomija się go przy rozwiązywaniu bardziej skomplikowanych problemów. Co ciekawe, zastosowanie algorytmu jest bardzo szerokie. Możemy przeprowadzić z jego użyciem zarówno uczenie nienadzorowane, jak i nadzorowane: w zakresie regresji oraz klasyfikacji. I tym ostatnim zajmiemy się w niniejszym poście, w dalszej jego części, próbując sklasyfikować pismo odręczne.

Czytając formalne definicje, można zauważyć, że KNN jest charakteryzowany jako algorytm nieparametryczny i „leniwy” (non-parametristic and lazy). Nieparametryczny w tym przypadku nie oznacza wcale, że nie mamy tu żadnych hiperparametrów – bo przecież choćby mamy najważniejszy parametr, jakim jest liczba k, czyli liczba sąsiadów. Oznacza to tylko tyle, albo aż tyle, że algorytm ten nie zakłada z góry, iż mamy do czynienia z pewnym rozkładem danych. Jest to bardzo przydatne założenie, bo w świecie rzeczywistym dane, którymi dysponujemy, często nie są łatwo separowalne liniowo lub niezbyt dobrze odwzorowują dystrybucję normalną czy jakąkolwiek inną. Stąd KNN może się przydawać, szczególnie w przypadkach, gdy zależność między danymi wejściowymi a wynikiem jest nietypowa lub bardzo złożona, co sprawia, że klasyczne metody w rodzaju regresji liniowej lub logistycznej mogą nie podołać zadaniu.

„Leniwy” z kolei oznacza, że algorytm nie buduje modelu generalizującego dany problem w fazie uczenia. Można powiedzieć, że uczenie jest odroczone do momentu, kiedy do modelu trafia zapytanie. Z powyższego faktu płyną dwa istotne dla KNN wnioski. Po pierwsze, faza testowania lub predykcji będzie trwała dłużej niż faza „uczenia się”. Po drugie, w większości przypadków KNN będzie potrzebował w trakcie predykcji mieć dostępne wszystkie dane ze zbioru uczącego – to z kolei, przy dużych zbiorach, nakłada dość spore wymagania na ilość pamięci.

Jak to działa – prosty przykład

Działanie algorytmu najlepiej wyjaśnić graficznie i w przestrzeni dwuwymiarowej. Załóżmy, że mamy zbiór danych opisany dwoma cechami (tu reprezentowanymi przez współrzędne na płaszczyźnie) i sklasyfikowany na dwie klasy: koła i trójkąty.

K najbliszych sąsiadów - proste wytłumaczenie

Jak widać, ten zbiór jest dość łatwo separowalny liniowo, więc pewnie wystarczyłoby np. wykorzystać regresję logistyczną dla klasyfikacji binarnej, aby uzyskać świetny efekt, ale dla celów wytłumaczenia zasady działania algorytmu, taki przykład nada się doskonale.

Załóżmy teraz, że mając taki zbiór, chcemy sklasyfikować nowy obiekt (czerwona plama) jako trójkąt lub koło:

K najbliższych sąsiadów - proste wyjaśnienie

KNN działa w ten sposób, że patrzy na „najbliższą okolicę” nowej danej i sprawdza, jakiej klasy obiektów jest tam więcej i na podstawie tego decyduje o opisaniu jej albo jako trójkąt, albo jako koło. Okay, ale co rozumiemy poprzez „najbliższą okolicę”? I tu do gry wchodzi parametr k, który określa na ilu najbliższych sąsiadów chcemy spojrzeć. Jeżeli spojrzymy na jednego (co jest raczej rzadko spotykaną wartością parametru k), to okaże się, że nasza nowa dana jest trójkątem.

Klasyfikacja KNN dla K równego 1

Jeżeli pod uwagę weźmiemy trzech sąsiadów, to nasza czerwona próbka zmieni się w koło – zupełnie jak w fizyce kwantowej: 😉

Klasyfikacja KNN dla K równego 3

Z kolei dla k=5 znowu mamy do czynienia z klasyfikacją jako trójkąt, bo w najbliższym otoczeniu są trzy trójkąty i dwa koła:

Klasyfikacja KNN dla K równego 5

Warto tu zauważyć dwie kwestie. Jak widać, wartości k przyjąłem nieparzyste. Jest to o tyle uzasadnione, że nie trafimy na przypadek, w którym nowa dana jest w sąsiedztwie dwóch kół i dwóch trójkątów. Nie oznacza to, że takich wartości k nie stosuje się w praktyce. Po prostu dla powyższego przykładu niepotrzebnie skomplikowałoby to tok naszego rozumowania. Po drugie, nowa dana pojawiła się niejako na granicy dwóch klas. Stąd raz jest ona kołem, a raz trójkątem, w zależności od wartości parametru k. Jeżeli jednak nowy obiekt pojawi się w innym, bardziej „trójkątnym” miejscu, to bez względu na wartość parametru k, pozostanie on trójkątem:

K najbliższych sąsiadów - przykład klasyfikacji

k najbliższych sąsiadów – nieco głębsze spojrzenie

Mimo, że KNN jest niezwykle prosty, to jednak jest kilka zagadnień, które warto nieco zgłębić, bo jak zwykle diabeł tkwi w szczegółach.

W jaki sposób liczymy odległość?

Wydaje się to dość oczywiste, ale warto w tym miejscu podkreślić, że warunkiem koniecznym do zastosowania algorytmu k najbliższych sąsiadów na danym zbiorze jest możliwość wyliczenia odległości między elementami tego zbioru. Nie musi być to odległość euklidesowa, ale brak możliwości kalkulacji odległości w zasadzie eliminuje KNN z analizy takiego zbioru danych.

W dwuwymiarowej przestrzeni euklidesowej, którą rozważaliśmy powyżej, odległość może być wyliczona z twierdzenia Pitagorasa. Ponieważ jednak rzadko kiedy analizujemy zbiór danych tylko z dwoma cechami (dwuwymiarowy), to uogólniając problem odległości do przestrzeni n wymiarowej, funkcję odległości można zdefiniować następującą metryką:

i=1nxi  yi2

Jest to najczęstszy sposób wyliczenia odległości, ale nie jedyny. Biblioteka scikit-learn, której użyjemy w tym poście nieco później, oferuje możliwość użycia kilkunastu różnych metryk, w zależności od rodzaju danych i typu zadania.

Jak dobrać wartość k?

Liczba k jest hiperparametrem i tak jak w przypadku innych hiperparametrów w uczeniu maszynowym, nie ma reguły ani wzoru na ustalenie jej wartości. Najlepiej zatem ustalić ją eksperymentalnie, oceniając skuteczność predykcji dla różnych wartości k. Dla dużych zbiorów może to być jednak czasochłonne. Przyda się kilka wskazówek:

  • W dość oczywisty sposób małe wartości k będą niosły ze sobą większe ryzyko niepoprawnej klasyfikacji.
  • Z kolei duże wartości k dadzą pewniejsze wyniki, ale będą dużo bardziej wymagające obliczeniowo. Poza tym wartość k ustawiona dajmy na to na 80, intuicyjnie kłóci się ideą najbliższych sąsiadów. W końcu chodzi o to, aby spojrzeć na najbliższe otoczenie nowego punktu danych.
  • W klasyfikacji binarnej dobrze jest używać wartości nieparzystych, aby uniknąć konieczności dokonywania losowego wyboru w przypadku równego podziału głosów.
  • Jedną z polecanych dość powszechnie wartości k do wypróbowania jest pierwiastek kwadratowy z ilości elementów w zbiorze uczącym. Dla przykładu, jeżeli nasz zbiór uczący ma 1000 elementów, to jako jedną z wartości k powinniśmy rozważyć 32. Dla 50 000 będzie to już 224. Osobiście nie jestem przekonany do tej metody. Nie znalazłem również jej pochodzenia ani naukowego wytłumaczenia (co nie oznacza, że ono nie istnieje). Uważam, że uzyskiwane w ten sposób wartości są za wysokie.
  • Metodą, którą dla mnie wydaje się rozsądniejsza jest pierwiastek 4 stopnia z ilości danych w zbiorze uczącym. Dla 500 próbek k będzie wynosić 5, dla 1000 k=6, dla 50 000 jest to 15, a wartości powyżej 30 stosujemy dla zbiorów o ilości danych przekraczających milion.
  • Są również metody, które głosują nie na podstawie wybrania ze zbioru k najbliższych sąsiadów, ale na podstawie głosów zebranych z danych znajdujących się w określonym promieniu od badanego punktu. Taki klasyfikator – Radius Neighbors Classifier – oferuje między innymi biblioteka scikit-learn.
  • Trzeba jednak wyraźnie zaznaczyć, że ustalenie optymalnej wartości k jest mocno zależne od danych i rozważanego problemu, stąd do powyższych wskazówek i wzorów trzeba podchodzić krytycznie i przede wszystkim samemu szukać najlepszego rozwiązania.

Co jeżeli liczba sąsiadów jest taka sama w dwóch lub więcej klasach?

Jest to ciekawy przypadek, gdyż w takiej sytuacji jest tak samo prawdopodobne, że badana próbka należy do jednej z tych dwóch lub więcej klas. Rozwiązaniem może być wybór losowy lub przypisanie wagi dla każdego z k najbliższych sąsiadów. Czym sąsiad bliższy, tym jego waga bardziej istotna. Biblioteka scikit-learn oferuje specjalny parametr „weights”, który ustawiony na wartość 'uniform’ zakłada, że każdy sąsiad ma taką samą wagę, a ustawiony na wartość 'distance’ przypisuje sąsiadom wagę odwrotnie proporcjonalną do jego odległości od badanego punktu danych.

A co z efektywnością obliczeń?

Wraz ze wzrostem ilości danych (N) w zbiorze uczącym, istoty nabiera kwestia efektywności obliczeń. Najmniej wyrafinowanym podejściem jest zastosowanie 'brute force’, czyli wyliczenie odległości między badanym punktem, a wszystkimi punktami zbioru uczącego. Koszt takiej metody rośnie wykładniczo, wraz ze wzrostem N. Dla zbiorów rzędu kilkunastu tysięcy próbek i większych stosowanie 'brute force’ jest w zasadzie niepraktyczne lub nieuzasadnione. Stąd stosuje się często bardziej wyrafinowane metody, oparte o struktury drzewiaste, które sprowadzają się do selekcji punktów leżących blisko badanego punktu danych. W uproszczeniu: jeżeli punkt X leży bardzo daleko od punktu Y, a punkt Z leży bardzo blisko punktu Y, to można założyć, że punkty X i Z leżą daleko od siebie i nie ma potrzeby wyliczania dla nich dystansu. Scikit-learn implementuje dwie takie struktury: K-D Tree oraz Ball Tree. O ich zastosowaniu biblioteka decyduje sama, na podstawie charakterystyki zbioru danych, chyba że celowo narzucimy jej użycie jednego z trzech ww. sposobów. Szczegółowo jest to opisane w tej sekcji.

k najbliższych sąsiadów w klasyfikacji pisma odręcznego

Okay, po solidnej dawce teorii sprawdźmy, jak KNN poradzi sobie z zagadnieniem praktycznym. Biblioteka scikit-learn udostępnia rozbudowane narzędzia dla algorytmu k najbliższych sąsiadów –  szkoda byłoby nie skorzystać. Na rozgrzewkę na warsztat weźmiemy jeden z testowych zbiorów udostępnianych przez scikit-learn – The Digits Dataset.

Dla celów poniższego zadania najlepiej utworzyć nowe środowisko wirtualne condy. Jeżeli chcesz się dowiedzieć więcej o środowiskach wirtualnych, zapraszam do zapoznania się z tym postem. Na Windows wystarczy uruchomić Anaconda prompt, przejść do katalogu, w którym chcemy zapisywać skrypty i wykonać trzy komendy:

conda create --name <nazwa>
conda activate <nazwa>
conda install numpy matplotlib scikit-learn jupyter keras

Z tak skonfigurowanego środowiska można skorzystać, uruchamiając jupyter notebook lub konfigurując projekt bazujący na tym środowisku w Waszym ulubionym IDE.

Wykonując klasyfikację, będziemy musieli skorzystać z szeregu komponentów, które na początku należy zaimportować. Jest to też dobry test na to, czy środowisko zostało poprawnie utworzone i aktywowane:

from sklearn import datasets, neighbors
from sklearn.model_selection import train_test_split
from keras.datasets import mnist
import matplotlib.pyplot as plt
import numpy as np
from random import randint
import time

W pierwszym kroku zaimportowaliśmy testowe zbiory danych, w tym Digits, a także klasyfikator knn. W dalszej części posta potrzebna nam będzie również funkcja dzieląca zbiór danych na uczący i testowy oraz bardziej zaawansowany zbiór MNIST, który pobierzemy z biblioteki Keras. Wyświetlanie wyników w formie graficznej zapewni nam matplotlib, a do części operacji na zbiorze danych wykorzystamy oczywiście numpy. Na sam koniec dwa importy funkcji narzędziowych, związanych z generowaniem liczb losowych i pomiarem czasu.

Jeżeli wszystkie importy wykonały się prawidłowo, możemy przystąpić do dalszego kodowania. Do pobrania zbioru danych Digits użyjemy funkcji narzędziowych oferowanych przez scikit-learn:

# load Digits data set divided into data X and labels y
X, y = datasets.load_digits(return_X_y=True)

# check data shapes - data is already flattened
print("X shape:", X.shape[0:])
print("y shape:", y.shape[0:])
>>> X shape: (1797, 64)
>>> y shape: (1797,)

Metoda load_digits, odpowiednio sparametryzowana, zwróciła nam dane do zmiennej X, a etykiety do zmiennej y. Zobaczmy kilkanaście losowo wybranych elementów zbioru Digits:

# let's see some random data samples.
pics_count = 16
digits = np.zeros((pics_count,8,8), dtype=int)
labels = np.zeros((pics_count,1), dtype=int)
for i in range(pics_count):
    idx = randint(0, X.shape[0]-1)
    # as data is flattened we need them to be reshaped to the original 2D shape
    digits[i] = X[idx].reshape(8,8)
    labels[i] = y[idx]

# then we print them all
fig = plt.figure()
fig.suptitle("A sample from the original dataset", fontsize=18)
for n, (digit, label) in enumerate(zip(digits, labels)):
    a = fig.add_subplot(4, 4, n + 1)
    plt.imshow(digit)
    a.set_title(label[0])
    a.axis('off')
fig.set_size_inches(fig.get_size_inches() * pics_count / 7)
plt.show()

scikit-learn digits

Nie ma się co oszukiwać, nie wygląda to jak grafika w World of Tanks, ale większość kształtów jest dość prosta do sklasyfikowania ludzkim okiem. Swoją drogą ciekawe, jak dużo cyfr bylibyśmy w stanie samemu szybko sklasyfikować i czy ta klasyfikacja byłaby lepsza od wyniku algorytmu. 😉

Dzielimy zbiór na uczący (70% danych) i testowy (30% danych), aby zasymulować sytuację, w której otrzymujemy nowe dane – tymi nowymi danymi będzie zbiór testowy – a następnie klasyfikujemy je na podstawie zbioru uczącego. Pamiętajmy, że w przypadku KNN nie mamy klasycznego uczenia, bo klasyfikator jest „leniwy”:

# splitting into train and test data sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)

Sprawdźmy kształty uzyskanych w ten sposób zmiennych:

# checking shapes
print("X train shape:", X_train.shape[0:])
print("y train shape:", y_train.shape[0:])
print("X test shape:", X_test.shape[0:])
print("y test shape:", y_test.shape[0:])
>>> X train shape: (1257, 64)
>>> y train shape: (1257,)
>>> X test shape: (540, 64)
>>> y test shape: (540,)

Mając dane załadowane do zmiennych, zdefiniujemy sobie funkcję klasyfikującą (lets_knn), którą potem wykorzystamy również przy innym zbiorze. Funkcja przyjmuje na wejściu oba zbiory oraz ich etykiety, tworzy klasyfikator, przetwarza zbiór uczący (knn.fit), a następnie przeprowadza predykcję i określa jakość klasyfikacji (accuracy). Na sam koniec wyświetlamy wyniki klasyfikacji, a także identyfikujemy, które dane zostały sklasyfikowane nieprawidłowo (wrong_pred), jaką otrzymały etykietę (wrong_labels), a jaką powinny (correct_labels).  Funkcja akceptuje również, jako parametry, liczbę sąsiadów, sposób określania wag dla odległości oraz parametr określający, czy prezentować w formie graficznej błędne predykcje:

def lets_knn(X_train, y_train, X_test, y_test, n_neighbors=3, weights='uniform', print_wrong_pred=False):
    t0 = time.time()
    # creating and training knn classifier
    knn = neighbors.KNeighborsClassifier(n_neighbors=n_neighbors, weights=weights)
    knn.fit(X_train, y_train)
    t1 = time.time()

    # predicting classes and comparing them with actual labels
    pred = knn.predict(X_test)
    t2 = time.time()
    # calculating accuracy
    accuracy = round(np.mean(pred == y_test)*100, 1)

    print("Accuracy of", weights ,"KNN with", n_neighbors, "neighbors:", accuracy,"%. Fit in",
          round(t1 - t0, 1), "s. Prediction in", round(t2 - t1, 1), "s")

    # selecting wrong predictions with correct and wrong labels
    wrong_pred = X_test[(pred != y_test)]
    correct_labels = y_test[(pred != y_test)]
    wrong_labels = pred[(pred != y_test)]

    if print_wrong_pred:
        # the we print first 16 of them
        fig = plt.figure()
        fig.suptitle("Incorrect predictions", fontsize=18)
        # in order to print different sized photos, we need to determine to what shape we want to reshape
        size = int(np.sqrt(X_train.shape[1]))
        for n, (digit, wrong_label, correct_label) in enumerate(zip(wrong_pred, wrong_labels, correct_labels)):
            a = fig.add_subplot(4, 4, n + 1)
            plt.imshow(digit.reshape(size,size))
            a.set_title("Correct: " + str(correct_label) + ". Predicted: " + str(wrong_label))
            a.axis('off')
            if n == 15:
                break
        fig.set_size_inches(fig.get_size_inches() * pics_count / 7)
        plt.show()

Wykorzystajmy powyższą funkcję do wykonania predykcji dla zbioru Digits:

lets_knn(X_train, y_train, X_test, y_test, 5, 'uniform', print_wrong_pred=True)
>>> Accuracy of uniform KNN with 5 neighbors: 98.0 %. Fit in 0.0 s. Prediction in 0.1 s

Funkcja została wywołana dla k=5 sąsiadów, z jednolitymi wagami (’uniform’) dla każdej odległości i uzyskała przyzwoitą poprawność na poziomie 98%.

Spójrzmy, które z danych zostały sklasyfikowane niepoprawnie:

KNN - nieprawidłowa klasyfikacja dla zbioru Digits

No, nie wiem, 🙂 dyskutowałbym z „poprawnymi” opisami niektórych labelek lub przynajmniej musiałbym nieco dłużej zastanowić się nad prawidłową odpowiedzią. Jak widać, KNN dał radę, bo powyższe to wszystkie błędne predykcje na 540 próbek testowych.

Sprawdźmy teraz, jak nasz klasyfikator poradzi sobie z dużo większym i bardziej wymagającym zbiorem danych, jakim jest MNIST. Gdyby ktoś chciał się dowiedzieć nieco więcej o zbiorze MNIST, to pisałem o nim na samym początku postu nt. klasyfikacji pisma odręcznego. Zbiór MNIST jest prosty do załadowanie przy wykorzystaniu loadera z biblioteki Keras:

# now let's play with MNIST data set
(X_train, y_train), (X_test, y_test) = mnist.load_data()
# checking initial shapes
print("X train initial shape:", X_train.shape[0:])
print("y train initial shape:", y_train.shape[0:])
print("X test initial shape:", X_test.shape[0:])
print("y test initial shape:", y_test.shape[0:])
>>> X train initial shape: (60000, 28, 28)
>>> y train initial shape: (60000,)
>>> X test initial shape: (10000, 28, 28)
>>> y test initial shape: (10000,)

Jak widać, mamy do dyspozycji 60 000 próbek w zbiorze uczącym i 10 000 w zbiorze testowym. Dodatkowo każdy element ze zbioru, to obrazek o wymiarach 28 x 28, co w porównaniu do zbioru Digits, z jego 8 x 8, daje ponad 12-krotnie większy wymiar danych. Klasyfikacja na takiej ilości danych zabierze sporo czasu. Zgodnie z tym, co rozważaliśmy powyżej, KNN jest klasyfikatorem leniwym, co oznacza, że większość obliczeń następuje w fazie predykcji. Warto więc ograniczyć ilość danych w zbiorze testowym, co znacząco powinno przyspieszyć uzyskanie wyników. Oczywiście, jeżeli dysponujecie odpowiednio mocnym środowiskiem obliczeniowym lub macie dużo czasu, możecie eksperymentować z wartościami większymi. Niestety, biblioteka scikit-learn nie oferuje wsparcia dla GPU:

# Reducing the size of testing data set, as it's the most time-consuming
X_train = X_train[:60000]
y_train = y_train[:60000]
X_test = X_test[:1000]
y_test = y_test[:1000]

Dane pobrane loaderem są dwuwymiarowe: 28 x 28. Uczenie maszynowe oczekuje płaskiego wektora, stąd musimy je spłaszczyć:

# reshaping
X_train = X_train.reshape((-1, 28*28))
X_test = X_test.reshape((-1, 28*28))

# checking shapes
print("X train shape:", X_train.shape[0:])
print("y train shape:", y_train.shape[0:])
print("X test shape:", X_test.shape[0:])
print("y test shape:", y_test.shape[0:])
>>> X train shape: (60000, 784)
>>> y train shape: (60000,)
>>> X test shape: (1000, 784)
>>> y test shape: (1000,)

Jesteśmy obecnie gotowi do uruchomienia naszego klasyfikatora. Aby było ciekawiej, uruchomimy go dla różnych wartości k oraz dla dwóch sposobów określania wag najbliższych sąsiadów:

# lets run it with different parameters to check which one is the best
for weights in ['uniform', 'distance']:
    for n in range(1,11):
        lets_knn(X_train, y_train, X_test, y_test, n_neighbors=n, weights=weights, print_wrong_pred=True)

>>> Accuracy of uniform KNN with 1 neighbors: 96.2 %. Fit in 49.2 s. Prediction in 63.5 s
>>> Accuracy of uniform KNN with 2 neighbors: 94.8 %. Fit in 50.4 s. Prediction in 61.4 s
>>> Accuracy of uniform KNN with 3 neighbors: 96.2 %. Fit in 47.6 s. Prediction in 61.4 s
>>> Accuracy of uniform KNN with 4 neighbors: 96.4 %. Fit in 46.1 s. Prediction in 61.0 s
>>> Accuracy of uniform KNN with 5 neighbors: 96.1 %. Fit in 47.2 s. Prediction in 60.7 s
>>> Accuracy of uniform KNN with 6 neighbors: 95.9 %. Fit in 46.1 s. Prediction in 60.7 s
>>> Accuracy of uniform KNN with 7 neighbors: 96.2 %. Fit in 46.4 s. Prediction in 60.8 s
>>> Accuracy of uniform KNN with 8 neighbors: 95.8 %. Fit in 47.5 s. Prediction in 60.8 s
>>> Accuracy of uniform KNN with 9 neighbors: 95.2 %. Fit in 46.3 s. Prediction in 61.8 s
>>> Accuracy of uniform KNN with 10 neighbors: 95.4 %. Fit in 50.8 s. Prediction in 62.8 s
>>> Accuracy of distance KNN with 1 neighbors: 96.2 %. Fit in 48.7 s. Prediction in 63.1 s
>>> Accuracy of distance KNN with 2 neighbors: 96.2 %. Fit in 53.2 s. Prediction in 63.5 s
>>> Accuracy of distance KNN with 3 neighbors: 96.5 %. Fit in 51.5 s. Prediction in 63.0 s
>>> Accuracy of distance KNN with 4 neighbors: 96.4 %. Fit in 50.3 s. Prediction in 60.5 s
>>> Accuracy of distance KNN with 5 neighbors: 96.4 %. Fit in 46.0 s. Prediction in 60.4 s
>>> Accuracy of distance KNN with 6 neighbors: 96.5 %. Fit in 48.5 s. Prediction in 61.5 s
>>> Accuracy of distance KNN with 7 neighbors: 96.4 %. Fit in 48.6 s. Prediction in 61.5 s
>>> Accuracy of distance KNN with 8 neighbors: 96.4 %. Fit in 48.3 s. Prediction in 61.5 s
>>> Accuracy of distance KNN with 9 neighbors: 95.7 %. Fit in 48.1 s. Prediction in 61.6 s
>>> Accuracy of distance KNN with 10 neighbors: 95.7 %. Fit in 48.4 s. Prediction in 61.5 s

Wyniki są bardzo zbliżone dla wszystkich konfiguracji. Nie wklejałem wyników dla większych k, ale podnoszenie wartości parametru, nawet do 20, nie zmienia sytuacji. Wydaje się, że dla zbioru MNIST nieco lepsze efekty daje ważone mierzenie odległości – 'distance’ – dla którego zarejestrowaliśmy dwa najlepsze wyniki klasyfikacji 96,5% dla k=3 i tyle samo dla k=6. Warto również zwrócić uwagę na dużo większe czasy wykonania klasyfikacji niż w przypadku zbioru Digits – około 49 s trwa faza „uczenia”, a około 61 s faza predykcji.

Na sam koniec rzućmy jeszcze okiem na przykłady błędnej klasyfikacji:

k najbliższych sąsiadów - klasyfikacja zbioru MNIST

Za wyjątkiem kilku pozycji: (wiersz 1, kolumna 3), (2,1), (3,3), (4,3) i (4,4), błędy są nieco bardziej oczywiste niż w przypadku zbioru Digits. Niemniej, klasyfikacja na poziomie 96,5% dla tak prostego w założeniach algorytmu może robić dobre wrażenie.

W niniejszym poście przybliżyłem Wam teoretyczne podstawy stojące za metodą k najbliższych sąsiadów. Zastosowaliśmy również algorytm w praktyce, rozwiązując dwa problemy klasyfikacyjne. KNN, mimo swojej prostoty i niewątpliwej łatwości użycia, daje bardzo dobre wyniki w szeregu zagadnień i mam nadzieję, że zachęciłem Was do częstszego korzystania z tej metody.


Masz pytanie? Zadaj je w komentarzu.

Spodobał ci się post? Będzie mi miło, gdy go polecisz.

Do zobaczenia wkrótce, przy okazji omawiania innego ciekawego tematu!

The post k najbliższych sąsiadów w klasyfikacji pisma appeared first on AI Geek Programmer.

]]>
https://aigeekprogrammer.com/pl/k-najblizszych-sasiadow-w-klasyfikacji-pisma/feed/ 0
Anaconda cron na Amazon Linux https://aigeekprogrammer.com/pl/anaconda-cron-na-amazon-linux/ https://aigeekprogrammer.com/pl/anaconda-cron-na-amazon-linux/#respond Fri, 13 Mar 2020 21:59:16 +0000 https://aigeekprogrammer.com/?p=8408 Jeżeli jesteś programistą Pythona oraz korzystasz ze środowiska AWS i Anacondy, to prędzej czy później napotkasz potrzebę uruchomienia skryptu Pythona jako procesu cronowego na Amazon Linux w środowisku Anaconda. To chyba nie powinno być trudne, prawda? Hmmm, niestety jest. Ponieważ spędziłem trochę czasu na konfiguracji...

The post Anaconda cron na Amazon Linux appeared first on AI Geek Programmer.

]]>
Jeżeli jesteś programistą Pythona oraz korzystasz ze środowiska AWS i Anacondy, to prędzej czy później napotkasz potrzebę uruchomienia skryptu Pythona jako procesu cronowego na Amazon Linux w środowisku Anaconda. To chyba nie powinno być trudne, prawda? Hmmm, niestety jest. Ponieważ spędziłem trochę czasu na konfiguracji crona na Amazon Linux w EC2, tak aby korzystał ze środowiska wirtualnego Anacondy, i nie raz padały przy tym z moich ust słowa potocznie uznawane za obelżywe, 😉 to chciałbym podzielić się z Wami pomysłem na to, jak to można zrobić prosto, szybko i bez nerwów.

W sieci jest oczywiście całkiem sporo treści dotyczącej konfiguracji pythonowych procesów cronowych. Cześć nawet dotyczy EC2 i Amazon Linux, ale jakoś żaden z tych postów nie rozwiązywał do końca wszystkich problemów, jakie ja napotkałem.

OK, zdefiniujmy zatem poszczególne kroki, które są niezbędne do konfiguracji pythonowego crona na AWS:

  • Musimy mieć dostęp przez SSH do Amazon Linux działającego na AWS EC2 – zadanie w zasadzie trywialne, nie będę tego opisywał.
  • Zainstalowana i zainicjowana Anaconda – instalacja również jest prosta. Na końcu procesu instalator pyta, czy zainicjalizować condę – należy wybrać „tak”. W efekcie, po zalogowaniu się przez SSH, mamy zawsze zaktywowane środowisko (base). Może być to dla niektórych denerwujące, ale upraszcza wiele kwestii, jeżeli użytkownik systemu operacyjnego jest wykorzystywany głównie do wykonywania skryptów Pythona. Tak powinno to wyglądać tuż po zalogowaniu:

anaconda cron na amazon linux AWS

  • W pierwszej kolejności tworzymy wirtualne środowisko condy (załóżmy nazwę test-env). Tu jest opisane, jak to zrobić, wykonując kilka prostych komend. Można teoretycznie wykonać skrypt w środowisku base, ale jest to mocno niezalecane. Warto po prostu utworzyć dedykowane środowisko i zaimportować moduły, których potrzebujemy.
  • Tworzymy skrypt pythonowy (załóżmy nazwę test.py), który chcemy uruchomić w cronie. Dla celów testowych będzie on wyświetlał informację, które środowisko condy jest aktywne. Wyświetlał? Proces cronowy? Oczywiście proces cronowy nie ma terminala, ale jego output przekierujemy potem do pliku i tam będzie można dla celów testowych podejrzeć wynik wykonania.

import os
print(os.environ['CONDA_DEFAULT_ENV'])

  • Teraz najważniejszy krok, nie tak oczywisty, choć ostatecznie trywialny. Tworzymy skrypt systemu operacyjnego (załóżmy nazwę test.sh), w którym aktywujemy środowisko test-env i uruchomimy skrypt test.py. Kluczowa dla poprawnego działania jest linia pierwsza, pozostałe są dość oczywiste. Zakłada ona istnienie domyślnego usera Amazon Linux (ec2-user) oraz instalację Anacondy w katalogu anaconda3. Należy pamiętać, aby sprawdzić i w miarę potrzeby nadać uprawnienia do uruchomienia skryptu.

source /home/ec2-user/anaconda3/bin/activate
conda activate test-env
# Jeżeli chcesz w skrypcie sprawdzić, które środowisko jest aktywne: echo $CONDA_DEFAULT_ENV
python test.py
conda deactivate

  • Prawie na sam koniec tworzymy skrypt z definicją joba cronowego (zakładam nazwę test.cron). Poniższe polecenie uruchamia nasz test.sh (a więc ostatecznie test.py) codziennie o godzinie 10:00 czasu lokalnego serwera – uwaga zależy on od centrum danych, w którym ulokujemy wirtualkę. Wywołanie skryptu ma przekierowany output do pliku test.log, gdzie można obejrzeć wynik wykonania.

00 10 * * * bash test.sh >> /home/ec2-user/test.log 2>&1

crontab test.cron

  • Aktualne ustawienia crona można sprawdzić poleceniem:

crontab -l

I to by było na tyle. 🙂


Masz pytanie? Zadaj je w komentarzu.

Spodobał ci się post? Będzie mi miło, gdy go polecisz.

Do zobaczenia wkrótce, przy okazji omawiania innego ciekawego tematu!

The post Anaconda cron na Amazon Linux appeared first on AI Geek Programmer.

]]>
https://aigeekprogrammer.com/pl/anaconda-cron-na-amazon-linux/feed/ 0
Sieci konwolucyjne 4: data augmentation https://aigeekprogrammer.com/pl/sieci-konwolucyjne-data-augmentation/ https://aigeekprogrammer.com/pl/sieci-konwolucyjne-data-augmentation/#comments Sun, 08 Mar 2020 14:17:46 +0000 https://aigeekprogrammer.com/?p=8322 W poprzednich trzech częściach tutoriala w szczegółach poznaliśmy sieci konwolucyjne. Przyjrzeliśmy się operacji konwolucji, architekturze sieci konwolucyjnych oraz problemowi overfittingu. W klasyfikacji zbioru CIFAR-10 osiągnęliśmy wynik 81% na zbiorze testowym. Aby pójść dalej, musielibyśmy zmienić architekturę naszej sieci, poeksperymentować z hiperparametrami lub uzyskać więcej danych....

The post Sieci konwolucyjne 4: data augmentation appeared first on AI Geek Programmer.

]]>
W poprzednich trzech częściach tutoriala w szczegółach poznaliśmy sieci konwolucyjne. Przyjrzeliśmy się operacji konwolucji, architekturze sieci konwolucyjnych oraz problemowi overfittingu. W klasyfikacji zbioru CIFAR-10 osiągnęliśmy wynik 81% na zbiorze testowym. Aby pójść dalej, musielibyśmy zmienić architekturę naszej sieci, poeksperymentować z hiperparametrami lub uzyskać więcej danych. Dwa pierwsze rozwiązania zostawiam dla was, 😉 a sam będę chciał w tej części tutoriala przedstawić sieci więcej danych. Skorzystam przy tym z tzw. data augmentation, czyli sztucznego wygenerowania dużej ilości nowych danych.

W czwartej części tutoriala dowiesz się między innymi:

  • Czym jest data augmentation?
  • Jak skorzystać z generatora danych z biblioteki Keras?
  • Jak sztucznie wygenerować nowe dane dla zbioru CIFAR-10?
  • Jak poradzi sobie nasz model na zbiorze sztucznie wygenerowanych danych?

Czym jest data augmentation?

Jak już wspominałem w poprzedniej części tutoriala, jeżeli mamy do czynienia z zamkniętym zbiorem danych, czyli takim, którego nie można w istotny sposób powiększyć lub jego powiększenie jest bardzo kosztowne, możemy sięgnąć po mechanizm tzw. data augmentation. Jest to szczególnie wartościowa technika w przypadku analizy obrazów. Dlaczego? Dlatego, że obrazy są podatne na drobne modyfikacje, które dla algorytmu będą nową daną, choć dla ludzkiego oka będą nadal w zasadzie tym samym. Co więcej, takie „drobne modyfikacje” występują w świecie rzeczywistym. Stojąc naprzeciwko samochodu, możemy patrzeć na niego centralnie lub lekko z boku. Będzie to nadal ten sam pojazd i na pewno będzie to dla naszego mózgu samochód. Dla algorytmu spojrzenie na obiekt z innej perspektywy jest cenną informacją pozwalającą lepiej generalizować proces uczenia.

Co w zasadzie możemy zrobić z obrazkiem, który chcemy sztucznie przetworzyć? Teoretycznie mamy nieskończenie wiele rozwiązań: możemy obrazek lekko obrócić, w dowolnym kierunku, o dowolny kąt. Przesunąć w lewo, w prawo, w górę i dół. Zmienić jego kolory lub dokonać innych mniej lub bardziej subtelnych zmian, które dadzą modelowi tony nowych danych. W praktyce zbiór kilkudziesięciu tysięcy obrazków może się stać zbiorem z milionami elementów. Jest to pole, na którym możliwości są naprawdę duże. Jako ciekawostka: technologie związane z autonomicznymi pojazdami są trenowane również na zbiorach danych sztucznie generowanych, np. z wykorzystaniem środowisk realistycznych gier, takich jak GTA.

Generowanie danych z biblioteką Keras

Biblioteka Keras oferuje zestaw pomocnych narzędzi do generowania danych. Spróbujmy przetworzyć generatorem widziany już uprzednio obrazek budynku na Krecie. W pierwszej kolejności dokonujemy niezbędnych importów i definiujemy funkcję, która załaduje obraz z pliku i skonwertuje go do tablicy numpy:

import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
%matplotlib inline

def convert_image(file):
   return np.array(Image.open(file))

Ładujemy obrazek, który można pobrać sobie tutaj, wyświetlamy kształt tablicy numpy oraz sam obrazek:

image = convert_image(r'<<wpis_ścieżkę_do_pliku_na_dysku>>\house-small.jpg')
image.shape
>>> (302, 403, 3)

plt.imshow(image)

Image for data augmentaion

Do generowania danych będziemy używali metody flow(x,y) z klasy ImageDataGenerator. Abyśmy byli w stanie użyć jej poprawnie, musimy oczywiście zaimportować klasę, ale także przystosować odpowiednio dane. Metoda oczekuje tensora x, w którym pierwszą pozycją będzie indeks. W naszym przypadku będzie tylko jeden element, ale metoda i tak wymaga indeksu. Dana wejściowa y to labelki, które nie są nam potrzebne dla tego prostego eksperymentu, niemniej musimy je dostarczyć. Stąd:

from tensorflow.keras.preprocessing.image import ImageDataGenerator

x = np.expand_dims(image, 0)
x.shape
>>> (1, 302, 403, 3)

y = np.asarray(['jakakolwiek-labelka'])

Następnie tworzymy obiekt generatora, przekazując odpowiednie parametry. W specyfikacji dostępnych jest ich naprawdę sporo, poniżej zaprezentowałem kilka przykładowych:

datagen = ImageDataGenerator(
   width_shift_range=0.2,  # przesunięcie wzdłuż osi x
   height_shift_range=0.2, # przesunięcie wzdłuż osi y
   rotation_range=20,      # rotacja
   horizontal_flip=True,   # odwrócenie poziome
   vertical_flip = True,   # odwrócenie pionowe
   rescale=1./255,         # parametr niezbędny, aby dobrze zwizualizować dane
   shear_range=0.25,       # przycinanie obrazu
   zoom_range=0.25,        # zoom
)

Pozostaje teraz wywołać metodę flow(x, y), przekazując do niej przygotowane dane i odbierając oraz wyświetlając wygenerowane obrazy.

figure = plt.figure()
i = 0
for x_batch, y_batch in datagen.flow(x, y):
   a = figure.add_subplot(5, 5, i + 1)
   plt.imshow(np.squeeze(x_batch))
   a.axis('off')
   if i == 24: break
   i += 1
figure.set_size_inches(np.array(figure.get_size_inches()) * 3)
plt.show()

Wynik? Dosłownie i w przenośni nieco postawiony na głowie 😉 i trochę „przerysowany”, bo niektóre parametry mają ustawione duże wartości, ale dobrze oddaje możliwości generatora. Możecie sami poeksperymentować z ustawieniami.

Data augmentation with ImageDataGenerator in Keras

Data augmentation na zbiorze CIFAR-10

Uzbrojeni w generator, możemy jeszcze raz podejść do klasyfikacji zbioru CIFAR-10. Większość kodu była już omawiana w poprzednich częściach tutoriala, więc podam ją tu tylko dla zapewnienia spójności i jasności. Na wstępie wykonujemy niezbędne importy, załadowanie zbioru oraz budujemy model:

import numpy as np

%tensorflow_version 2.x
import tensorflow

import matplotlib.pyplot as plt
%matplotlib inline

from tensorflow import keras
print(tensorflow.__version__)
print(keras.__version__)
>>> 1.15.0
>>> 2.2.4-tf
from tensorflow.keras.datasets import cifar10
(x_train,y_train), (x_test,y_test) = cifar10.load_data()
from tensorflow.keras.models import Sequential
from tensorflow.keras.optimizers import RMSprop
from tensorflow.keras.layers import Convolution2D, MaxPool2D, Flatten, Dense, Dropout, BatchNormalization
from tensorflow.keras import regularizers
from tensorflow.keras.utils import to_categorical
model = Sequential([
   Convolution2D(filters=128, kernel_size=(5,5), input_shape=(32,32,3), activation='relu', padding='same'),
   BatchNormalization(),
   Convolution2D(filters=128, kernel_size=(5,5), activation='relu', padding='same'),
   BatchNormalization(),
   MaxPool2D((2,2)),
   Convolution2D(filters=64, kernel_size=(5,5), activation='relu', padding='same'),
   BatchNormalization(),
   Convolution2D(filters=64, kernel_size=(5,5), activation='relu', padding='same'),
   BatchNormalization(),
   MaxPool2D((2,2)),
   Convolution2D(filters=32, kernel_size=(5,5), activation='relu', padding='same'),
   BatchNormalization(),
   Convolution2D(filters=32, kernel_size=(5,5), activation='relu', padding='same'),
   BatchNormalization(),
   MaxPool2D((2,2)),
   Convolution2D(filters=16, kernel_size=(3,3), activation='relu', padding='same'),
   BatchNormalization(),
   Convolution2D(filters=16, kernel_size=(3,3), activation='relu', padding='same'),
   BatchNormalization(),
   Flatten(),
   Dense(units=32, activation="relu"),
   Dropout(0.15),
   Dense(units=16, activation="relu"),
   Dropout(0.05),
   Dense(units=10, activation="softmax")
])
optim = RMSprop(lr=0.001)
model.compile(optimizer=optim, loss='categorical_crossentropy', metrics=['accuracy'])

Po przygotowaniu i pomyślnym skompilowaniu modelu definiujemy generator. Zakładamy, że dane będą rotowane o 10 stopni, dopuszczamy odwrócenie poziome, ale już nie pionowe, aby nie „stawiać rzeczy na głowie”. Generator będzie też przesuwał obrazki w pionie i poziomie o 10%. Dopuszczalny jest też niewielki zoom i shear. Pamiętajmy, że obrazki są niewielkie i mocniejsze modyfikacje mogą sprawić, że obraz będzie trudny do rozpoznania nawet dla człowieka:

from tensorflow.keras.preprocessing.image import ImageDataGenerator

datagen = ImageDataGenerator(  
    rotation_range=10,    
    horizontal_flip=True,
    vertical_flip = False,
    width_shift_range=0.1,
    height_shift_range=0.1,
    rescale = 1. / 255,
    shear_range=0.05,
    zoom_range=0.05,
)

Potrzebujemy również one-hot encodingu dla labelek zbiorów uczących i testowych. Ustalamy wielkość batcha i generator w zasadzie gotowy:

y_train = to_categorical(y_train)
y_test = to_categorical(y_test)

batch_size = 64
train_generator = datagen.flow(x_train, y_train, batch_size=batch_size)

Powyższy generator będzie źródłem danych dla procesu uczenia. Co jednak ze zbiorem walidacyjnym, który umożliwi nam śledzenie postępów? Otóż musimy zdefiniować odrębny generator, który jednak nie będzie w żaden sposób modyfikował źródłowych obrazków:

datagen_valid = ImageDataGenerator(
   rescale = 1. / 255,
)

x_valid = x_train[:100*batch_size]
y_valid = y_train[:100*batch_size]

x_valid.shape[0]
>>>6400

valid_steps = x_valid.shape[0] // batch_size
validation_generator = datagen_valid.flow(x_valid, y_valid, batch_size=batch_size)

Jak widać powyżej, zbiór, którego proces uczenia będzie używał do walidacji, będzie miał wielkość 100 paczek danych. Na podstawie wielkości tego zbioru i wielkości paczki wyliczamy również ilość kroków walidacji – dane te będą potrzebne do wywołania funkcji uczenia.

history = model.fit_generator(
   train_generator,
   steps_per_epoch=len(x_train) // batch_size,
   epochs=120,
   validation_data=validation_generator,
   validation_freq=1,
   validation_steps=valid_steps,
   verbose=2
)

Zwróćcie uwagę, że nie używamy metody fit(), jak to miało miejsce uprzednio, lecz metody fit_generator(), która przyjmuje na wejściu generator z danymi uczącymi oraz (opcjonalnie) generator danych walidacyjnych. Mając tak dużo danych, będziemy uczyli 120 zamiast 80 epok, licząc na to, że unikniemy overfittingu.

>>> Epoch 1/120
>>> Epoch 1/120
>>> 781/781 - 49s - loss: 1.8050 - acc: 0.3331 - val_loss: 1.5368 - val_acc: 0.4581
>>> Epoch 2/120
>>> Epoch 1/120
>>> 781/781 - 41s - loss: 1.3230 - acc: 0.5249 - val_loss: 1.1828 - val_acc: 0.5916
>>> Epoch 3/120

(...)

>>> 781/781 - 39s - loss: 0.1679 - acc: 0.9473 - val_loss: 0.1484 - val_acc: 0.9463
>>> Epoch 119/120
>>> Epoch 1/120
>>> 781/781 - 38s - loss: 0.1708 - acc: 0.9466 - val_loss: 0.1538 - val_acc: 0.9538
>>> Epoch 120/120
>>> Epoch 1/120
>>> 781/781 - 39s - loss: 0.1681 - acc: 0.9486 - val_loss: 0.1379 - val_acc: 0.9534

W procesie uczenia otrzymaliśmy accuracy na poziomie 95% i to dla obu zbiorów. Widać to zresztą również na poniższym wykresie:

print(history.history.keys())
>>> dict_keys(['loss', 'acc', 'val_loss', 'val_acc'])

plt.plot(history.history['acc'])
plt.plot(history.history['val_acc'])
plt.title('Model accuracy')
plt.ylabel('Accuracy')
plt.xlabel('Epoch')
plt.legend(['Train', 'Valid'], loc='upper left')
plt.show()

Accuracy for a model with a data generator

Ze względu na brak overfittingu i widoczny gołym okiem progres parametrów uczenia do samego końca, być może moglibyśmy pokusić się o dalsze zwiększenie ilości epok.

Sprawdźmy jak wyuczony model poradzi sobie na danych testowych, których jeszcze nie widział.

x_final_test = x_test / 255.0
eval = model.evaluate(x_final_test, y_test)
>>> 10000/10000 [==============================] - 3s 314us/sample - loss: 0.5128 - acc: 0.8687

A zatem osiągnęliśmy accuracy na poziomie 87%, o 6% więcej niż w wersji modelu bez generowania danych.

Najważniejsze jednak, że model wykazuje ochotę do dalszego uczenia, bez szkody dla accuracy na zbiorze walidacyjnym i testowym.


To już ostatni post w tym tutorialu. Mam nadzieję, że udało mi się przybliżyć kilka ciekawych zagadnień związanych z sieciami konwolucyjnymi.

Masz pytanie? Zadaj je w komentarzu.

Spodobał ci się post i cały tutorial? Będzie mi miło, gdy go polecisz.

Do zobaczenia wkrótce, przy okazji omawiania innego ciekawego tematu!

The post Sieci konwolucyjne 4: data augmentation appeared first on AI Geek Programmer.

]]>
https://aigeekprogrammer.com/pl/sieci-konwolucyjne-data-augmentation/feed/ 4
Konwolucyjne sieci neuronowe 3: overfitting https://aigeekprogrammer.com/pl/konwolucyjne-sieci-neuronowe-tutorial-czesc-3/ https://aigeekprogrammer.com/pl/konwolucyjne-sieci-neuronowe-tutorial-czesc-3/#respond Sat, 11 Jan 2020 22:39:38 +0000 https://aigeekprogrammer.com/?p=8114 Konwolucyjne sieci neuronowe to jedna z najbardziej skutecznych architektur sieci neuronowych w obszarze klasyfikacji obrazów. W pierwszej części tutoriala omówiliśmy zagadnienie konwolucji oraz zbudowaliśmy prostą, gęsto połączoną sieć neuronową, której użyliśmy do klasyfikacji zbioru CIFAR-10, uzyskując skuteczność na poziomie 47%. W części drugiej tutoriala zapoznaliśmy...

The post Konwolucyjne sieci neuronowe 3: overfitting appeared first on AI Geek Programmer.

]]>
Konwolucyjne sieci neuronowe to jedna z najbardziej skutecznych architektur sieci neuronowych w obszarze klasyfikacji obrazów. W pierwszej części tutoriala omówiliśmy zagadnienie konwolucji oraz zbudowaliśmy prostą, gęsto połączoną sieć neuronową, której użyliśmy do klasyfikacji zbioru CIFAR-10, uzyskując skuteczność na poziomie 47%. W części drugiej tutoriala zapoznaliśmy się szczegółowo z architekturą i parametrami konwolucyjnej sieci neuronowej, zbudowaliśmy własną sieć i uzyskaliśmy na niej ~70% skuteczność klasyfikacji na zbiorze testowym. Jak się okazało, zetknęliśmy się jednak z problemem overfittingu, który uniemożliwił nam uzyskanie lepszych rezultatów. W tej części tutoriala przyjrzymy się bliżej kwestii overfittingu oraz poznamy różne techniki regularyzacji, czyli przeciwdziałania nadmiernemu dopasowaniu do zbioru uczącego. Post zakończymy listą praktycznych wskazówek, które mogą być przydatne przy okazji budowy konwolucyjnej sieci neuronowej.

Z trzeciej części tutoriala dowiesz się:

  • Czym jest overfitting?
  • Jak poradzić sobie z problemem overfittingu?
  • Co to jest internal covariate shift?
  • Jak zastosować normalizację batchową?
  • Na czym polega dropout?
  • Poznasz też kilka praktycznych porad dotyczących budowania konwolucyjnych sieci neuronowych.

Czy oglądasz mecze NBA? Sprawdź mój darmowy serwis NBA Games Ranked i ciesz się oglądaniem wyłącznie dobrych meczów.


Czym jest overfitting?

Sięgnijmy raz jeszcze do wyników uczenia naszej sieci konwolucyjnej z części drugiej tutoriala. Na Rysunku 1 widać wynik klasyfikacji na zbiorze treningowym, który ostatecznie sięgnął nawet 95% (linia niebieska). Pod nim wyrysowany jest wynik klasyfikacji na zbiorze walidacyjnym (linia pomarańczowa). Jak widać, wyniki dla obu zbiorów zaczęły się oddalać od siebie już w okolicy 15 epoki, a ostateczna różnica dla 80 epoki wyniosła nawet ~25%.

Konwolucyjne sieci neuronowe - overfitting

Rysunek 1 – wyniki uczenia na zbiorach uczącym i testowym

Taką sytuację nazywamy overfittingiem. Sieć na tyle dobrze nauczyła się klasyfikować zbiór treningowy, że utraciła jednocześnie zdolność do generalizowania, czyli umiejętność poprawnej klasyfikacji danych, których uprzednio nie widziała.

Aby lepiej zrozumieć overfitting, wyobraźmy sobie przykład z życia wzięty. Zawodowy koszykarz musi mieć buty najwyższej jakości. Współpracuje z firmą produkującą obuwie, a firma ta przygotowuje buty, które są idealnie dopasowane do kształtu i budowy jego stopy. Z reguły wymaga to nie tyle dopasowania kształtu buta, ile wykonania specjalnie dobranej wkładki. Koszykarz świetnie czuje się w nowych butach i jego gra jest jeszcze lepsza. Czy to oznacza, że tak wykonane buty będą równie dobre dla innego koszykarza lub dla graczy-amatorów? Prawdopodobnie w zdecydowanej większości przypadków nie. Te buty zostały na tyle dokładnie dopasowane do stopy tego konkretnego koszykarza, że nie będą się dobrze sprawowały na innej stopie. Jest to overfitting, a firmy produkujące obuwie dla szerokiego grona klientów starają się je zaprojektować w taki sposób, aby kształty buta i wkładki pasowały na jak największą liczbę stóp, zapewniając jednocześnie jak największy komfort gry.

Jeszcze inny przykład – tym razem graficzny. Załóżmy, że chcemy zbudować klasyfikator, który będzie poprawnie klasyfikował dane na „koliste” i „trójkątne”.

Konwolucyjne sieci neuronowe - problem overfittingu

Rysunek 2 – overfitting vs. lepiej generalizujący model

Jeżeli zbyt mocno dopasujemy klasyfikator do danych uczących, to nie będzie on w stanie poprawnie klasyfikować nowych danych, bo jest raczej mało prawdopodobne, aby te nowe dane wpasowywały się idealnie w rozkład danych treningowych. Stąd lepiej, aby model był nieco mniej skomplikowany / mniej złożony. Będzie wprawdzie osiągał nieco gorsze wyniki na zbiorze uczącym, ale za to zdecydowanie lepiej będzie generalizował problem, czyli również poprawniej klasyfikował nowe dane.

Jak rozwiązać problem overfittingu?

Ok, w takim razie w jaki sposób przeciwdziałać overfittingowi? Jest na to co najmniej kilka skutecznych metod. Poniżej opiszę najważniejsze i postaramy się zastosować część z nich w naszym klasyfikatorze.

Zbieramy więcej danych – jest to często najbardziej skuteczna metoda, aby zapobiec nadmiernemu dopasowaniu. Jeżeli model zobaczy więcej danych, będzie w stanie lepiej generalizować swoją odpowiedź. Pamiętajmy, że sieci neuronowe, czy też – uogólniając – uczenie maszynowe uwielbia ogromne ilości danych i duże moce obliczeniowe. Niestety, często metoda ta jest najtrudniejsza do zastosowania w praktyce lub wręcz niemożliwa – jak w naszym przypadku, gdy mamy zamknięty zbiór danych.

Jeżeli nie możemy zebrać większej ilości danych, to czasami możemy je samodzielnie wytworzyć. Jakkolwiek brzmi to dość karkołomnie i może zastanawiać założenie, że sztucznie wytworzone dane poprawią odpowiedź modelu, ale w praktyce metoda ta przynosi dobre efekty. Szczególnie w przetwarzaniu obrazu mamy szerokie pole do popisu. Możemy obrazek lekko obrócić, przesunąć, zmienić jego kolory lub dokonać innych mniej lub bardziej subtelnych zmian, które dadzą modelowi tony nowych danych. Z logicznego punktu widzenia: dysponując oryginalnym zdjęciem konia, możemy dokonać jego lustrzanego odbicia lub zmiany barwy i nadal będzie to zdjęcie konia. Technika ta nosi nazwę data augmentation i czołowe biblioteki oferują gotowe narzędzia do wykorzystania. Z jednego z nich skorzystamy w następnej części  tutoriala.

Jak już wspominałem w drugiej części tutoriala, każda sieć neuronowa posiada wiele tzw. hiperparametrów. Mają one istotny wpływ na sposób działania sieci. Stanowią część architektury modelu i sterując nimi, można uzyskiwać lepsze lub gorsze wyniki. Przy budowie każdego modelu warto eksperymentalnie poszukać architektury, która daje nam lepsze rezultaty. Czasami zmniejszenie złożoności architektury daje zaskakująco dobre efekty. Zbyt złożona architektura będzie w stanie dość szybko wygenerować overfitting, bo będzie jej łatwiej dokładnie dopasować się do zbioru uczącego.

Zaczniemy właśnie od tego prostego ruchu. Sieć z części drugiej tutoriala składa się z podsieci konwolucyjnej i gęsto połączonej. Sieć konwolucyjna nie jest gęsto połączona i raczej powinniśmy dążyć do zwiększenia jej złożoności niż do zmniejszenia, bo dzięki temu będzie ona w stanie wychwycić więcej cech obrazka. Zmniejszając złożoność architektury, warto więc w pierwszym ruchu ograniczyć złożoność części gęsto połączonej. 

Z modelu podsieci gęsto połączonej w postaci:

Dense(units=512, activation="relu"),
Dense(units=64, activation="relu"),
Dense(units=10, activation="softmax")

przejdziemy do dużo prostszego:

Dense(units=32, activation="relu"),
Dense(units=16, activation="relu"),
Dense(units=10, activation="softmax")

(...)
>>> Epoch 78/80
>>> loss: 0.5725 - accuracy: 0.7968 - val_loss: 0.7897 - val_accuracy: 0.7367
>>> Epoch 79/80
>>> loss: 0.5667 - accuracy: 0.8014 - val_loss: 0.8373 - val_accuracy: 0.7259
>>> Epoch 80/80
>>> loss: 0.5611 - accuracy: 0.8019 - val_loss: 0.8255 - val_accuracy: 0.7220

eval = model.evaluate(x_test, to_categorical(y_test))
>>> loss: 0.8427 - accuracy: 0.7164

Sieć konwolucyjna - wyniku uczenia

Rysunek 3 – wyniki uczenia po zmniejszeniu złożoności sieci gęsto połączonej

Jak widać, korzyści jest kilka. O około 2% wyższa skuteczność klasyfikacji na zbiorze testowym. Szybsze uczenie, bo sieć jest mniej wymagająca obliczeniowo. A także zmniejszony, choć nie wyeliminowany, overfitting – obecnie na poziomie około 10%.

Architektura naszej pierwszej wersji sieci, zaproponowana w części drugiej tutoriala, zakładała przetworzenie każdego obrazka przez trzy „moduły” konwolucyjne, z odpowiednio: 64, 32 i 16 filtrami. Taka złożoność sieci konwolucyjnej pozwoliła na uzyskanie około 80% skuteczności klasyfikacji na zbiorze treningowym, co przełożyło się na ~72% na zbiorze testowym. Dla przypomnienia wyglądała ona tak:

Convolution2D(filters=64, kernel_size=(3,3), input_shape=(32,32,3), activation='relu', padding='same'),
Convolution2D(filters=64, kernel_size=(3,3), activation='relu', padding='same'),
MaxPool2D((2,2)),
Convolution2D(filters=32, kernel_size=(3,3), activation='relu', padding='same'),
Convolution2D(filters=32, kernel_size=(3,3), activation='relu', padding='same'),
MaxPool2D((2,2)),
Convolution2D(filters=16, kernel_size=(3,3), activation='relu', padding='same'),
Convolution2D(filters=16, kernel_size=(3,3), activation='relu', padding='same'),

Abyśmy mogli uzyskać lepsze rezultaty klasyfikacji, powinniśmy poprawić wyniki w dwóch obszarach. Po pierwsze, zwiększyć poprawność klasyfikacji na zbiorze treningowym, bo jak widać skuteczność dla zbioru testowego jest zawsze niższa niż skuteczność dla zbioru uczącego. Po drugie, powinniśmy zmniejszyć overfitting. Sieć, która nauczy się dobrze generalizować wyniki, osiągnie dużo lepsze wyniki na danych, których nie widziała. Poza tym będziemy w stanie uczyć ją dłużej niż przez 80 epok. Obecnie nie ma to większego sensu, bo mimo iż skuteczność na zbiorze treningowym może jeszcze urosnąć, to ten sam parametr na zbiorze walidacyjnym wskazuje, że to uczenie nie jest generalizowaniem, a dopasowaniem do zbioru uczącego.

Jak zwiększyć poprawność klasyfikacji na zbiorze treningowym? Jedną z dróg jest pogłębienie podsieci konwolucyjnej. Dodając kolejne warstwy i zwiększając ilość filtrów, dajemy możliwość wychwycenia przez sieć większej ilości cech i tym samym większej skuteczności w klasyfikacji. Aby to osiągnąć, do naszej architektury, na sam jej początek, dodamy jeszcze jeden „moduł” konwolucyjny, ze zwiększoną do 128 ilością filtrów:

Convolution2D(filters=128, kernel_size=(5,5), input_shape=(32,32,3), activation='relu', padding='same'),
Convolution2D(filters=128, kernel_size=(5,5), activation='relu', padding='same'),
MaxPool2D((2,2)),
Convolution2D(filters=64, kernel_size=(5,5), activation='relu', padding='same'),
Convolution2D(filters=64, kernel_size=(5,5), activation='relu', padding='same'),
MaxPool2D((2,2)),
Convolution2D(filters=32, kernel_size=(5,5), activation='relu', padding='same'),
Convolution2D(filters=32, kernel_size=(5,5), activation='relu', padding='same'),
MaxPool2D((2,2)),
Convolution2D(filters=16, kernel_size=(3,3), activation='relu', padding='same'),
Convolution2D(filters=16, kernel_size=(3,3), activation='relu', padding='same'),

Próba zmniejszenia overfittingu wymaga wprowadzenia dwóch nowych elementów: normalizacji batchowej (batch normalization) i techniki dropout.

Normalizacja batchowa

Normalizacja batchowa ma na celu redukcję tzw. internal covariate shift. Aby zrozumieć ideę stojącą za normalizacją batchową, należy uprzednio zrozumieć, czym jest internal covariate shift.

Covariate jest dość szeroko używanym terminem, głównie w statystyce, i oznacza zmienną niezależną, czyli innymi słowy zmienną wejściową. Na podstawie zmiennych wejściowych wyznacza się zmienne zależne – wyjściowe. Przez analogię, w uczeniu maszynowym covariate będzie oznaczał zmienną wejściową / input / X / features. W naszym przykładzie covariate to wartości składowych kolorów poszczególnych pikseli przetwarzanych obrazków.

Każdy zbiór danych posiada pewną dystrybucję / rozkład danych wejściowych. Dla przykładu, gdybyśmy w zbiorze CIFAR-10 analizowali rozkład średniej jasności obrazków przedstawiających samoloty, zapewne byłby on różny od jasności obrazków przedstawiających żaby. Gdybyśmy nałożyli na siebie takie dwa rozkłady, to byłyby one względem siebie przesunięte. To przesunięcie nazywamy covariate shift.

Mimo że zbiory danych, których używamy do uczenia maszynowego są z reguły dobrze zbilansowane, to jednak podział zbioru na uczący, walidacyjny i testowy powoduje, że zbiory te mają różny rozkład danych wejściowych. Z tego też (między innymi) powodu mamy do czynienia z niższą skutecznością wyuczonej sieci dla zbioru testowego, w porównaniu do zbioru uczącego.

Covariate shift

Rysunek 4 – covariate shift

Covariate shift występuje nie tylko przy podziale zbioru lub przy jego wzbogacaniu o nowe dane, ale również w wyniku przechodzenia danych wejściowych przez kolejne warstwy sieci neuronowej. Sieć modyfikuje dane w naturalny sposób, poprzez nakładanie wag przypisanych do połączeń między neuronami w sieci. W konsekwencji każda kolejna warstwa musi się nauczyć danych, które mają nieco inny rozkład niż oryginalne dane wejściowe. Powoduje to nie tylko spowolnienie procesu uczenia, ale również większą podatność sieci na overfitting. Zjawisko przesunięcia rozkładu danych wejściowych w sieci neuronowej zostało opisane przez Ioffe oraz Szegedy i nazwane internal covariate shift.

Ioffe i Szegedy zaproponowali metodę normalizacji danych wykonywaną pomiędzy warstwami sieci neuronowej, jako część jej architektury, dzięki czemu można zminimalizować zjawisko internal covariate shift. Należy tu zauważyć, że niektórzy naukowcy zajmujący się zagadnieniem wskazują, że normalizacja batchowa nie tyle redukuje internal covariate shift, co raczej wygładza funkcję celu, dzięki czemu przyspiesza i poprawia proces uczenia.

Podsumowując: normalizacja batchowa przyspiesza uczenie – pozwala przy mniejszej liczbie iteracji uzyskać takie same rezultaty jak sieci nieposiadające normalizacji batchowej. Umożliwia zastosowanie wyższych learning rates bez negatywnego wpływu na gradienty oraz pomaga również eliminować overfitting. Większość bibliotek uczenia maszynowego, w tym keras, posiada wbudowane funkcje realizujące normalizację batchową.

Dla zainteresowanych: wpis na wiki oraz artykuł naukowy Sergeya Ioffe i Christiana Szegedy, którzy zaproponowali i opisali metodę normalizacji batchowej. Artykuł jest mocno techniczny, z dużą dawką matematyki, ale abstrakt, wprowadzenie i podsumowanie są całkiem zjadliwe. 😉

Dropout

Drugą bardzo przydatną techniką, która skutecznie walczy z overfittingiem jest tzw. dropout. Zaproponował ją Geoffrey E. Hinton et al. w pracy Improving neural networks by preventing co-adaptation of feature detectors. Jest to relatywnie prosta, ale zarazem bardzo skuteczna technika przeciwdziałania overfittingowi. Polega na losowym usuwaniu z sieci (z warstw wewnętrznych, czasami również wejściowych) pojedynczych neuronów w trakcie uczenia. Ponieważ skomplikowane sieci (a takie niewątpliwie są głębokie sieci neuronowe), szczególnie dysponujące relatywnie niewielkimi ilościami danych uczących, mają tendencję do dokładnego dopasowywania się do danych, to taki sposób deregulacji zmusza je do uczenia w sposób bardziej zgeneralizowany.

W każdej turze uczenia się każdy z neuronów jest usuwany lub zostawiany w sieci. Szanse na usunięcie definiuje się poprzez określenie prawdopodobieństwa, z jakim neuron zostanie usunięty. W oryginalnej pracy było to 50% dla każdego neuronu. Obecnie możemy samodzielnie określić to prawdopodobieństwo, a do tego dla różnych warstw może być ono różne.

 

Konwolucyjne sieci neuronowe - dropout

Rysunek 5 – dropout jako technika minimalizująca overfitting

Zastosowanie dropout w praktyce prowadzi do sytuacji, w której architektura sieci zmienia się dynamicznie i otrzymujemy model, w którym jeden zbiór danych został wykorzystany do nauczenia wielu sieci o różniących się architekturach, a następnie został przetestowany na zbiorze testowym z uśrednionymi wartościami wag.

Użycie dropout w Keras sprowadza się do dodania kolejnej warstwy o nazwie Dropout(rate), której hiperparametrem jest prawdopodobieństwo, z jakim neuron zostanie usunięty z sieci. Dropout dodajemy do podsieci gęsto połączonej. Użycie jej w podsieci konwolucyjnej jest rzadsze i w zasadzie mija się z ideą stojącą za konwolucjami.

W warstwie konwolucyjnej zastosujemy natomiast normalizację batchową, którą w Keras uzyskujemy poprzez dodanie warstwy BatchNormalization(). W efekcie otrzymamy następującą architekturę sieci:

model = Sequential([
    Convolution2D(filters=128, kernel_size=(5,5), input_shape=(32,32,3), activation='relu', padding='same'),
    BatchNormalization(),
    Convolution2D(filters=128, kernel_size=(5,5), activation='relu', padding='same'),
    BatchNormalization(),
    MaxPool2D((2,2)),
    Convolution2D(filters=64, kernel_size=(5,5), activation='relu', padding='same'),
    BatchNormalization(),
    Convolution2D(filters=64, kernel_size=(5,5), activation='relu', padding='same'),
    BatchNormalization(),
    MaxPool2D((2,2)),
    Convolution2D(filters=32, kernel_size=(5,5), activation='relu', padding='same'),
    BatchNormalization(),
    Convolution2D(filters=32, kernel_size=(5,5), activation='relu', padding='same'),
    BatchNormalization(),
    MaxPool2D((2,2)),
    Convolution2D(filters=16, kernel_size=(3,3), activation='relu', padding='same'),
    BatchNormalization(),
    Convolution2D(filters=16, kernel_size=(3,3), activation='relu', padding='same'),
    BatchNormalization(),
    Flatten(),
    Dense(units=32, activation="relu"),
    Dropout(0.15),
    Dense(units=16, activation="relu"),
    Dropout(0.05),
    Dense(units=10, activation="softmax")
])

optim = RMSprop(lr=0.001)

Jak widać powyżej, zaproponowałem również zmianę optymalizatora z SGD na RMSprop, który jak wynikało z moich testów sprawdził się nieco lepiej dla powyższej architektury.

W tym miejscu mała dygresja: możecie zastanawiać się, skąd są te wszystkie zaproponowane przeze mnie zmiany? No cóż, płyną one z dwóch źródeł: z zebranych doświadczeń oraz eksperymentów z daną siecią. Nad rozwiązaniem, które ostatecznie zaprezentuję w niniejszym tutorialu, spędziłem co najmniej kilkanaście godzin, próbując różnych architektur i wartości hiperparametrów. Tak to wygląda w praktyce, więc jeżeli spędzacie ze swoim modelem drugi dzień i nie macie pojęcia co dalej, to musicie wiedzieć, że jest to całkowicie normalne i za chwilę (lub po małej przerwie) pójdziecie zapewne z pracą dalej.

Sieć była uczona 80 epok i w efekcie uzyskaliśmy skuteczność klasyfikacji na poziomie 81%.

Epoch 77/80
42500/42500 - 19s - loss: 0.0493 - accuracy: 0.9888 - val_loss: 1.7957 - val_accuracy: 0.8119
Epoch 78/80
42500/42500 - 19s - loss: 0.0523 - accuracy: 0.9879 - val_loss: 1.2465 - val_accuracy: 0.8016
Epoch 79/80
42500/42500 - 19s - loss: 0.0499 - accuracy: 0.9880 - val_loss: 1.7057 - val_accuracy: 0.8137
Epoch 80/80
42500/42500 - 18s - loss: 0.0490 - accuracy: 0.9880 - val_loss: 1.5880 - val_accuracy: 0.8175

eval = model.evaluate(x_test, to_categorical(y_test))
>>> 10000/10000 [==============================] - 2s 167us/sample - loss: 1.5605 - accuracy: 0.8112

Rzut oka na wykresy dla zbioru uczącego i walidacyjnego dają mieszane uczucia. Z jednej strony udało nam się podnieść skuteczność klasyfikacji dla wszystkich trzech zbiorów, w tym dla najważniejszego, czyli testowego o blisko 10% (z 71% do 81%). Z drugiej strony znowu pojawił się silny overfitting, co oznacza, że sieć ponownie bardziej „uczy się zbioru treningowego” niż generalizuje klasyfikację.

Konwolucyjne sieci neuronowe - wynik klasyfikacji

Rysunek 6 – wynik klasyfikacji zmienionej architektury

Gdybym chciał w tej chwili uzyskać istotnie lepszy wynik klasyfikacji niż 81%, to wybrałbym jedną z trzech dróg. Po pierwsze, można byłoby poeksperymentować z inną architekturą. W tym można byłoby sięgnąć do jednej z referencyjnych architektur, które uzyskiwały bardzo dobre rezultaty na zbiorze CIFAR-10 lub podobnych. Po drugie, należałoby zbadać reakcję naszej sieci na inne ustawienia hiperparametrów – praca żmudna i wymagająca czasu, ale czasami kilka prostych zmian daje dobre rezultaty. Trzecią drogą jest dalsza walka z overfittingiem, ale nieco inną metodą, o której już wspominałem powyżej – data augmentation. Przyjrzymy się jej w kolejnej części tutoriala.

Konwolucyjne sieci neuronowe – kilka praktycznych porad

Na sam koniec części trzeciej tutoriala zamieszczam kilka praktycznych, luźno powiązanych porad, które można uwzględnić, budując swoją konwolucyjną sieć neuronową.

  • Jeżeli możesz, skorzystaj ze sprawdzonej architektury sieci i w miarę możliwości dostosuj ją do swoich potrzeb.
  • Zacznij od overfittingu i wprowadź regularyzację.
  • Umieszczaj dropout w warstwie gęsto połączonej, a normalizację batchową wprowadź do podsieci konwolucyjnej. Nie trzymaj się jednak sztywno tej zasady. Czasami niestandardowy ruch może dać niespodziewanie dobre efekty.
  • Kernel size powinien być raczej dużo mniejszy niż rozmiar obrazka.
  • Eksperymentuj z różnymi ustawieniami hiperparametrów, a potem eksperymentuj jeszcze więcej.
  • Korzystaj z GPU. Jeżeli nie masz komputera z odpowiednią kartą graficzną, skorzystaj z Google Colaboratory.
  • Zgromadź tyle danych uczących, ile to możliwe. Nie ma czegoś takiego jak „za dużo danych”.
  • Jeżeli nie możesz zgromadzić większej ilości danych, skorzystaj z generatora danych – więcej o tym w czwartej części tutoriala.
  • Bardzo głęboka i rozbudowana sieć będzie miała silne skłonności do overfittingu. Skorzystaj z tak płytkiej sieci, jak to możliwe. W szczególności nie przesadzaj z ilością neuronów i warstw w podsieci gęsto połączonej.
  • Upewnij się, że zbiory uczące i testowe są dobrze zbalansowane i mają zbliżoną dystrybucję. W przeciwnym wypadku na zbiorze testowym będziesz zawsze uzyskiwał dużo gorsze wyniki niż na zbiorze uczącym.
  • Jak już poczujesz się dobrze w świecie Convnetów, zacznij czytać bardziej zaawansowane opracowania naukowe. Pozwolą Ci lepiej zrozumieć jak działają sieci konwolucyjne, wprowadzą do Twojego arsenału nowe techniki i architektury.

Powodzenia i zapraszam do czwartej części tutoriala.


Masz pytanie? Zadaj je w komentarzu.

Spodobał ci się post? Będzie mi miło, gdy go polecisz.

Do zobaczenia wkrótce, przy okazji omawiania innego ciekawego tematu!


Czy oglądasz mecze NBA? Sprawdź mój darmowy serwis NBA Games Ranked i ciesz się oglądaniem wyłącznie dobrych meczów.


The post Konwolucyjne sieci neuronowe 3: overfitting appeared first on AI Geek Programmer.

]]>
https://aigeekprogrammer.com/pl/konwolucyjne-sieci-neuronowe-tutorial-czesc-3/feed/ 0