{"id":219,"date":"2021-09-15T12:07:20","date_gmt":"2021-09-15T12:07:20","guid":{"rendered":"http:\/\/liipetti.net\/retkia\/?p=219"},"modified":"2021-09-16T10:51:47","modified_gmt":"2021-09-16T10:51:47","slug":"neurokuvatreenit-harjoitus-1","status":"publish","type":"post","link":"https:\/\/liipetti.net\/retkia\/neurokuvatreenit-harjoitus-1\/","title":{"rendered":"Neurokuvatreenit, harjoitus 1"},"content":{"rendered":"\n<p>N\u00e4iss\u00e4 treeneiss\u00e4 tutustutaan neuroverkkopohjaiseen kuvank\u00e4sittelyyn, tehd\u00e4\u00e4n tai muutetaan pikselikuvia uudella tavalla, ik\u00e4\u00e4nkuin ohjaamalla kuvaa itse\u00e4\u00e4n, sen jokaista pikseli\u00e4, kohti haluttua lopputulosta, sen sijaan ett\u00e4 kirjoitettaisiin koodia joka lukee ja muuttaa itse pikseleit\u00e4.<\/p>\n\n\n\n<p>T\u00e4ss\u00e4 ensimm\u00e4isess\u00e4 osassa ei viel\u00e4 k\u00e4ytet\u00e4 edes neuroverkkoakaan. Kokeillaan vain, mit\u00e4 kuvan ohjaaminen kohti tavoitetta k\u00e4yt\u00e4nn\u00f6ss\u00e4 tarkoittaa. <\/p>\n\n\n\n<p>Periaate on oikeastaan aika yksinkertainen, kun sen ensin vain tajuaa. T\u00e4ss\u00e4 yksinkertaisessa esimerkiss\u00e4 luomme satunnaisesti alustetun pikselikuvan, ns. lumisadetta, ja ohjaamme sen kohti tavoitetta, puhtaan vihre\u00e4\u00e4 kuvaa.<\/p>\n\n\n\n<p>Ohjaus tapahtuu mittaamalla, kuinka paljon olemme pieless\u00e4 tavoitteesta ja muuttamalla kuvaa aavistuksen verran kohti tavoitetta. Poikkeaman voimme arvioida vaikkapa laskemalla kunkin pikselin virheen ja ottamalla n\u00e4ist\u00e4 keskiarvon. Jos pystymme ohjaamaan pikseleit\u00e4 siihen suuntaan, ett\u00e4 virhe pienenee, voimme kenties p\u00e4\u00e4st\u00e4 tavoitteeseen, jolloin virhe on nolla.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/liipetti.net\/retkia\/wp-content\/uploads\/2021\/09\/harj1.png\" alt=\"\" class=\"wp-image-220\" width=\"598\" height=\"401\" srcset=\"https:\/\/liipetti.net\/retkia\/wp-content\/uploads\/2021\/09\/harj1.png 852w, https:\/\/liipetti.net\/retkia\/wp-content\/uploads\/2021\/09\/harj1-300x201.png 300w, https:\/\/liipetti.net\/retkia\/wp-content\/uploads\/2021\/09\/harj1-768x516.png 768w, https:\/\/liipetti.net\/retkia\/wp-content\/uploads\/2021\/09\/harj1-223x150.png 223w, https:\/\/liipetti.net\/retkia\/wp-content\/uploads\/2021\/09\/harj1-150x101.png 150w\" sizes=\"auto, (max-width: 598px) 100vw, 598px\" \/><\/figure>\n\n\n\n<p>Mutta vaikka tied\u00e4mme kokonaisvirheen, kuinka pystymme siit\u00e4 p\u00e4\u00e4ttelem\u00e4\u00e4n kuinka paljon kutakin yksitt\u00e4ist\u00e4 pikseli\u00e4 pit\u00e4\u00e4 muuttaa, ja mihin suuntaan? Siihen l\u00f6ytyy keino, ns. backpropagation, er\u00e4\u00e4nlainen virheen valuttaminen laskentaketjun l\u00e4pi, takaperin, niin ett\u00e4 lopuksi tied\u00e4mme kunkin pikselin kohdalla kuinka paljon sit\u00e4 pit\u00e4\u00e4 muuttaa. <\/p>\n\n\n\n<p>T\u00e4ll\u00e4 kertaa tavoitteemme on niin yksinkertainen, ett\u00e4 kun tied\u00e4mme kaukanako kukin pikseli on puhtaasta vihre\u00e4st\u00e4, voisimme suoraan korjata pikselin tavoitteen mukaiseksi. Mutta jatkossa, kun tavoitteet mutkistuvat, niit\u00e4 voi olla useitakin, ei suoraa tiet\u00e4 optimiin en\u00e4\u00e4 ole. Siksi optimia haetaan pienin askelin, t\u00e4t\u00e4 hoitaa ns. optimoija (optimizer), jolle kerrotaan miten hienojakoisin askelin pit\u00e4\u00e4 edet\u00e4 (learning rate, koulutus- tai oppimistahti). Yleens\u00e4 ongelmat ovat sellaisia, ett\u00e4 liian suurin askelin eksyt\u00e4\u00e4n reitilt\u00e4, liian pienin askelin optimin l\u00f6yt\u00e4minen taas vie kauemmin.<\/p>\n\n\n\n<p>Koodin tasolla homma menee seuraavasti. K\u00e4yt\u00e4mme python-kielt\u00e4 ja <a href=\"http:\/\/pytorch.org\">pytorch-kirjastoa<\/a>, joiden lis\u00e4ksi otamme k\u00e4ytt\u00f6\u00f6n muita kirjastoja tarpeen mukaan. Ohjelman alussa ilmoitamme mit\u00e4 kirjastoja tulemme t\u00e4ss\u00e4 k\u00e4ytt\u00e4m\u00e4\u00e4n.<br><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">import torch\nfrom torchvision.transforms import Normalize\nfrom torchvision.utils import save_image\n<\/pre>\n\n\n\n<p>Luodaan sitten nuo kaksi kuvaa, satunnainen ja tavoite. Kuvan esitt\u00e4miseen k\u00e4yt\u00e4mme 3-ulotteista taulukkoa, jossa kutakin p\u00e4\u00e4v\u00e4ri\u00e4 (R, G, B eli punainen, vihre\u00e4 ja sininen) on kaksiulotteinen pikselikuva (korkeus x leveys). Pikselien esitt\u00e4miseen on parikin erilaista k\u00e4yt\u00e4nt\u00f6\u00e4: liukulukuina nollasta ykk\u00f6seen tai kokonaislukuina nollasta 255:een, pienempi aina tummempi ja suurempi kirkkaampi. T\u00e4ss\u00e4 menn\u00e4\u00e4n aluksi liukuluvuilla nollasta ykk\u00f6seen.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"># make a 256x256 rgb image with random pixels\n# pixel values here float in range is 0..1 \n\nimgG = torch.Tensor(3,256,256).normal_(mean=0.5, std=0.5).clamp_(0,1) \n\n# check shape, min and max\n\nprint(imgG.shape, imgG.min(), imgG.max())\n\n# make target image with green pixels\n\nimgT =  torch.zeros(3,256,256)\nimgT[1,:,:] = 1<\/pre>\n\n\n\n<p>Tuossa viimeisell\u00e4 rivill\u00e4 siis luodaan se puhtaan vihre\u00e4 kuva. Kuinka? Jos j\u00e4t\u00e4mme pois koko rivin, meill\u00e4 on taulukko jonka kaikkien pikselien kaikki v\u00e4rit ovat 0, mik\u00e4 on t\u00e4ysin musta kuva. Nyt asetamme keskimm\u00e4isen v\u00e4rikanavan ykk\u00f6seksi, saamme vihre\u00e4n kuvan. V\u00e4rikanavien indeksit menev\u00e4t tuossa 0, 1, 2 jolloin 1 on se keskimm\u00e4inen. Kaksoispisteet taas kertovat, ett\u00e4 t\u00e4yt\u00e4mme pikselit laidasta laitaan; jatkossa opimme my\u00f6s kajoamaan vain osaan kuvasta.<br><br>Meill\u00e4 siis pikselien arvot nyt menev\u00e4t nollan ja ykk\u00f6sen v\u00e4lill\u00e4. Neuroverkkojen kanssa yleens\u00e4 toimii paremmin, jos arvot jakautuvat nollan molemmin puolin. Luomme sit\u00e4 varten funktion norm() ja normalisoimme arvot v\u00e4lille -1 ja 1. T\u00e4m\u00e4 ei aina ole v\u00e4ltt\u00e4m\u00e4t\u00f6nt\u00e4, mutta hyv\u00e4 oppia.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"># normalize both images into range -1 .. 1\n\nnorm = Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))\nimgG = norm(imgG)\nimgT = norm(imgT)\n\nprint(imgG.shape, imgG.min(), imgG.max())\n<\/pre>\n\n\n\n<p>Sitten tarvitsemme optimoijan hoitamaan optimin hakua. Se menee n\u00e4in. M\u00e4\u00e4rittelemme oppimistahdin lr. Mietimme hetken mit\u00e4 haluamme muuttaa&#8230; seh\u00e4n on kuva imgG, kerromme ett\u00e4 se tarvitsee gradientin. Sill\u00e4 tarkoitamme haluamme tiet\u00e4\u00e4 miten paljon nimenomaan se, imgG, on pieless\u00e4 ja mihin suuntaan. Luomme viel\u00e4 itse optimoijan ja kerromme sille ett\u00e4 nyt optimoidaan juuri tuota imgG:t\u00e4 tahdilla lr.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"># We are going to optimize imgG, so we need gradients for it\n\nlr = 0.05 # you might want to change this and see what happens\n\nimgG.requires_grad = True\n\noptimizer = torch.optim.Adam([imgG], lr)<\/pre>\n\n\n\n<p>Nyt voidaankin k\u00e4yd\u00e4 optimoimaan. Se tehd\u00e4\u00e4n silmukassa jota k\u00e4yd\u00e4\u00e4n l\u00e4pi enint\u00e4\u00e4n 150 kertaa. Silmukassa ensin nollataan optimoija, ettei sill\u00e4 ole mit\u00e4\u00e4n vanhaa tietoa muistissa. Sitten lasketaan virhe. Voimme suoraan laskea erotuksen noista kahdesta kuvasta, tuloksena on 3-ulotteinen taulukko jossa on ero laskettuna jokaisen v\u00e4rikanavan jokaisen pikselin kohdalla. Otetaan n\u00e4ist\u00e4 itseisarvo (abs) eli negatiiviset arvot k\u00e4\u00e4nnet\u00e4\u00e4n positiivisiksi. Ja sitten keskiarvo n\u00e4ist\u00e4, saadan yksi luku joka mittaa kuvien v\u00e4list\u00e4 eroa.<\/p>\n\n\n\n<p>Nyt pit\u00e4\u00e4 jyvitt\u00e4\u00e4 virhe kullekin pikselille. Se k\u00e4y helpommin kuin uskoisikaan. Loss.backward() laskee virheest\u00e4 l\u00e4htien takaperin koko laskentaketjun, ik\u00e4\u00e4nkuin valuttaa virheen takaisin kunkin pikselin kohdalle, jossa se tallentuu imgG:n gradienttiin. T\u00e4ss\u00e4 kohtaa voisimme vaikka tutkia mist\u00e4 se koostuu, lis\u00e4\u00e4m\u00e4ll\u00e4 rivin print(imgG.grad). Se on samanmuotoinen kuin kuvakin, 3 x 256 x 256 taulukko, jossa on poikkeama kullekin pikselille.  <\/p>\n\n\n\n<p>Voisimme itsekin alkaa muuttaa imgG:t\u00e4 sen gradientin mukaan, mutta yleens\u00e4 parempi j\u00e4tt\u00e4\u00e4 se optimoijan teht\u00e4v\u00e4ksi, kuten t\u00e4ss\u00e4.<\/p>\n\n\n\n<p>Lopuksi tulostamme kierroksen numeron ja senhetkisen virheen arvon, jotta voimme seurata pieneneek\u00f6 virhe eli toimiiko optimointi. Talletamme my\u00f6s t\u00e4m\u00e4nhetkisen imgG:n levylle jotta voimme seurata kuvan muuttumista.<\/p>\n\n\n\n<p>Ja sitten sama uudestaan, 150 kertaa.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">niter = 150\n\nfor i in range(0, niter):\n    optimizer.zero_grad()\n\n    # calculate loss\n    loss = torch.abs(imgT - imgG).mean()\n\n    # run backwards to find gradient (how to change imgG to make loss smaller) \n    loss.backward()\n\n    # run optimizer to actually change imgG\n    optimizer.step()\n\n    # print loss to show how we are doing\n    print(i, loss.item())\n\n    # save image\n    save_image(imgG, \"imgG\"+str(i)+\".jpg\")\n    <\/pre>\n\n\n\n<p>Tuosta videolta alta voi katsoa kun ajan kokeeksi tuon harjoituksen koodin.<\/p>\n\n\n<p><iframe loading=\"lazy\" src=\"https:\/\/player.vimeo.com\/video\/605599579?h=2f517ed1b1&amp;dnt=1&amp;app_id=122963\" width=\"640\" height=\"370\" frameborder=\"0\" allow=\"autoplay; fullscreen; picture-in-picture\" allowfullscreen><\/iframe><\/p>\n\n\n\n<p>Seuraavassa harjoituksessa jatketaan t\u00e4st\u00e4, mutta k\u00e4ytet\u00e4\u00e4n kohteena omaa kuvaa. Kokeillaan my\u00f6s mit\u00e4 tapahtuu jos meill\u00e4 on kaksi eri tavoitetta.<br><br>Lopuksi mietitt\u00e4v\u00e4\u00e4 tai jopa itse kokeiltavaa.<br><br>1. Mit\u00e4 tapahtuu jos oppimistahtia muutetaan. Paljon pienemm\u00e4ksi? Paljon isommaksi? Mit\u00e4 jos se on 1, tai viel\u00e4kin suurempi?<\/p>\n\n\n\n<p>2. Ent\u00e4 jos vihre\u00e4n sijaan haluaakin punaisen tai sinisen neli\u00f6n? Tai kokonaan jonkin muun v\u00e4rin?<br><br>3. Aika yleisesti on tiedossa ett\u00e4 neuroverkkoja koulutetaan. Koulutettiinko t\u00e4ss\u00e4 neuroverkko? Oliko meill\u00e4 t\u00e4ss\u00e4 neuroverkko? Mit\u00e4 itse asiassa koulutimme?<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>N\u00e4iss\u00e4 treeneiss\u00e4 tutustutaan neuroverkkopohjaiseen kuvank\u00e4sittelyyn, tehd\u00e4\u00e4n tai muutetaan pikselikuvia uudella tavalla, ik\u00e4\u00e4nkuin ohjaamalla kuvaa itse\u00e4\u00e4n, sen jokaista pikseli\u00e4, kohti haluttua lopputulosta, sen sijaan ett\u00e4 kirjoitettaisiin koodia joka lukee ja muuttaa itse pikseleit\u00e4. T\u00e4ss\u00e4 ensimm\u00e4isess\u00e4 osassa ei viel\u00e4 k\u00e4ytet\u00e4 edes neuroverkkoakaan. Kokeillaan vain, mit\u00e4 kuvan ohjaaminen kohti tavoitetta k\u00e4yt\u00e4nn\u00f6ss\u00e4 tarkoittaa. Periaate\u2026<\/p>\n<p class=\"continue-reading-button\"> <a class=\"continue-reading-link\" href=\"https:\/\/liipetti.net\/retkia\/neurokuvatreenit-harjoitus-1\/\">Continue reading<i class=\"crycon-right-dir\"><\/i><\/a><\/p>\n","protected":false},"author":1,"featured_media":220,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8],"tags":[],"class_list":["post-219","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-neurokuvatreenit"],"_links":{"self":[{"href":"https:\/\/liipetti.net\/retkia\/wp-json\/wp\/v2\/posts\/219","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/liipetti.net\/retkia\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/liipetti.net\/retkia\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/liipetti.net\/retkia\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/liipetti.net\/retkia\/wp-json\/wp\/v2\/comments?post=219"}],"version-history":[{"count":15,"href":"https:\/\/liipetti.net\/retkia\/wp-json\/wp\/v2\/posts\/219\/revisions"}],"predecessor-version":[{"id":241,"href":"https:\/\/liipetti.net\/retkia\/wp-json\/wp\/v2\/posts\/219\/revisions\/241"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/liipetti.net\/retkia\/wp-json\/wp\/v2\/media\/220"}],"wp:attachment":[{"href":"https:\/\/liipetti.net\/retkia\/wp-json\/wp\/v2\/media?parent=219"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/liipetti.net\/retkia\/wp-json\/wp\/v2\/categories?post=219"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/liipetti.net\/retkia\/wp-json\/wp\/v2\/tags?post=219"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}