Narzędzia sztucznej inteligencji - laboratorium |
Karol Pyka |
Rok III INF, gr. L4 poniedzialek godz. 10.05 |
Ćwiczenie 3: Zastosowanie sieci neuronowej z współzawodnictwem (Kohonena) do kompresji obrazów |
||
Ćwiczenie wykonywane w dniach: 4.06.2012 |
Sprawozdanie oddano dnia: 11.06.2012 |
Ocena: |
1. Cel i zakres ćwiczenia
Celem ćwiczenia jest wykorzystanie warstwy neuronów z tzw. współzawodnictwem, uczonych metodą Kohonena (która jest metodą uczenia nienadzorowanego) w zadaniu kompresji obrazu. Sieć taka jest w stanie realizować kompresją stratną, polegającą na zmniejszeniu ilości informacji reprezentującej dany obraz przy zachowaniu błędu odwzorowania na określonym poziomie.
2. Wstęp teoretyczny
Sieć neuronowa (sztuczna sieć neuronowa) to ogólna nazwa struktur matematycznych i ich programowych lub sprzętowych modeli, realizujących obliczenia lub przetwarzanie sygnałów poprzez rzędy elementów, zwanych sztucznymi neuronami, wykonujących pewną podstawową operację na swoim wejściu. Oryginalną inspiracją takiej struktury była budowa naturalnych neuronów oraz układów nerwowych, w szczególności mózgu.
Samoorganizujące się mapy (Self Organizing Maps, SOM), zwane też sieciami Kohonena to sieci neuronów, z którymi są stowarzyszone współrzędne na prostej, płaszczyźnie lub w dowolnej n-wymiarowej przestrzeni. Uczenie tego rodzaju sieci polega na zmianach współrzędnych neuronów, tak aby dążyły one do wzorca zgodnego ze strukturą analizowanych danych. Sieci zatem "rozpinają się" wokół zbiorów danych, dopasowując do nich swoją strukturę.
Sieci te stosowane są do klasyfikacji wzorców, np. głosek mowy ciągłej, tekstu, muzyki. Do najciekawszych zastosowań należy rozpinanie siatki wokół komputerowego modelu skanowanego obiektu.
3. Przebieg wykonywanego ćwiczenia
Umieszczamy w skrypcie wywołanie funkcji imfinfo(), wyświetlającej informacje o danym pliku graficznym.
Do wyświetlenia informacji o pliku posłużył następujący kod:
info = imfinfo('lena.tif')
Korzystając z funkcji imread() wczytujemy zawartość pliku graficznego do zmiennej (macierzy) obraz.
Wykorzystujemy poniższy kod:
obraz = imread('lena.tif')
Kolejnym krokiem będzie wyświetlenie wczytanego obrazu w oknie graficznym (rys. 1).
Posłużył do tego kod:
figure(1)
colormap gray
imagesc(obraz,[0 255])
Rys. 1. Obraz lena.tif wyświetlony z macierzy obraz.
Następnie tworzymy tablicę blokową (cell) odzwierciedlającę podział obrazu na ramki o określonym rozmiarze. W naszym przypadku obraz (512x512px) dzielimy na ramki o rozmiarze 4x4px.
Kod:
nx = 4;
ny = 4;
[X,Y] = size(obraz);
Nx = X/nx;
Ny = Y/ny;
A = mat2cell(obraz,ny*ones(1,Ny),nx*ones(1,Nx));
Sprawdzamy typ i rozmiar zmiennej A za pomocą funkcji whos() oraz wyświetlamy teraz zawartość zmiennej A oraz jej wybranego bloku (ramki)
Kod:
disp(A);
whos A;
disp(A{1,1});
Wynik działania ostatnich dwóch linijek skryptu:
Name Size Bytes Class Attributes
A 256x256 7602176 cell
145 55
116 101
Na podstawie danych z tablicy A tworzymy macierz wzorców wejściowych P
Kod:
P=zeros(nx*ny,Nx*Ny);
for i = 1:Ny
for j = 1:Nx
P(:,j+(i-1)*Ny) =reshape(A{i,j},nx*ny,1);
end
end
Następnym krokiem jest utworzenie sieci Kohonena (czyli warstwę neuronów „z współzawodnictwem”, uczonych bez nauczyciela) o odpowiedniej strukturze.
Kod:
N = 8;
eta = 0.1;
net=newc(P,N,eta);
Dopisujemy do skryptu instrukcje, które po inicjalizacji warstwy wyświetlą rozmiary i początkowe wartości elementów macierzy współczynników wagowych.
Kod:
disp('Rozmiary macierzy wag: ')
disp(net.IW)
disp('Zawartość macierzy wag: ')
disp(net.IW{1})
Ponieważ macierz wzorców wejściowych P ma stosunkowo duże rozmiary (zawiera Nx*Ny kolumn reprezentujących wszystkie ramki obrazu), wybierzemy z niej losowo pewną ilość kolumn (ramek), które pełnić będą rolę wzorców uczących. Wzorce te utworzą macierz wzorców uczących Pu.
Kod:
nf = Nx*Ny/4;
r=randperm(Nx*Ny);
Fi=r(1:nf);
Pu=P(:,Fi);
Korzystając z funkcji train przeprowadzamy uczenie sieci w oparciu o przygotowaną w poprzednim punkcie macierz wzorców uczących Pu.
Kod:
net.trainParam.epochs = 1;
net = train(net,Pu)
disp('Rozmiary macierzy wag: ')
disp(net.IW)
disp('Zawartość macierzy wag: ')
disp(net.IW{1})
Teraz musimy przypisać macierz współczynników wagowych (net.IW{1}) nauczonej warstwy zmiennej w.
Kod:
w = net.IW{1};
Przeprowadzamy symulację działania sieci (sim), podając na jej wejścia w sposób wsadowy wszystkie wzorce wejściowe, czyli ramki składające się na obraz zapisane w macierzy P. Wynik działania sieci przypisujemy zmiennej a.
Kod:
a = sim(net,P);
Odpowiedzi sieci zapisane zostały w zmiennej a, reprezentowanej przez macierz o rozmiarach N wierszy na Nx*Ny kolumn. Zawiera ona tylko po jednej jedynce w każdej kolumnie, określającej odpowiedź zwycięskiego neuronu - pozostałe wartości macierzy to zera. Z tego względu zmienna a została zapisana w pamięci Matlaba w postaci tzw. macierzy rzadkiej (ang. sparse array). Elementy zerowe takiej macierzy nie są zapamiętywane w pamięci, co daje znaczną oszczędność jej wykorzystania. Korzystając z funkcji full konwertujemy macierz a do postaci macierzy pełnej. Wynik konwersji zapisujemy w zmiennej af.
Kod:
af = full(a);
Po zapoznaniu się z działaniem funkcji vec2ind konwertujemy macierz a (lub af) do wektora ac o długości Nx*Ny, zawierającego numery neuronów zwyciężających przy prezentacji poszczególnych ramek. Wektor ten reprezentować będzie tzw. „książkę kodową”, w oparciu o którą możliwe będzie odtworzenie uśrednionych wartości pikseli wchodzących w skład poszczególnych ramek, zakodowanych w ich współczynnikach wagowych.
Kod:
ac = vec2ind(af);
Sprawdzamy (przy pomocy polecenia whos) jakiego typu są elementy macierzy współczynników wagowych w i ile bajtów zajmują. Korzystając z funkcji uint8() konwertujemy je do jednobajtowego typu całkowitego bez znaku, a wynik zapisujemy ponownie w zmiennej w. Ten sam zabieg stosujemy dla zmiennej ac, reprezentującej „książkę kodową”.
Kod:
whos w
w = uint8(w)
ac = uint8(ac)
Korzystając z polecenia save zapisujemy w pliku binarnym obraz.mat zmienne przechowujące informacje o skompresowanym obrazie, tzn.: W i ac, oraz dodatkowo zmienne: Nx, Ny, nx, ny. Po zapisaniu sprawdzamy rozmiar pliku obraz.mat i porównujemy go z rozmiarem oryginalnego pliku obrazu.
Kod:
save obraz.mat w ac Nx Ny nx ny;
Rozmiar obrazu początkowego: 257KB
Rozmiar pliku wynikowego: 19KB
W oparciu o zależność (1) obliczamy wartość współczynnika kompresji Kr, wyrażonego jako stosunek liczby bitów oryginalnego obrazu do sumy bitów wymaganych do zapamiętania współczynników wagowych sieci oraz „książki kodowej”.
(1)
Kod:
disp('Współczynnik kompresji: ');
Kr = (Nx*Ny*nx*ny*8)/(Nx*Ny*8+N*nx*ny*8);
disp(Kr);
otrzymujemy w ten sposób:
Współczynnik kompresji: 3.9985
Otwieramy nowe okno edytora m-plików, zapisujemy w nim polecenia clear oraz close all. Wykorzystujemy polecenie load wczytujące z pliku obraz.mat dane potrzebne do odtworzenia obrazu oraz konwertujemy zmienne w i ac do typu rzeczywistego (double), zachowując te same nazwy zmiennych.
Kod:
clear
close all
load obraz.mat;
w = double(w);
ac = double(ac);
Korzystając z funkcji cell tworzymy pustą tablicę blokową Ad o rozmiarach Nx*Ny - będziemy do niej wpisywać poszczególne ramki obrazu podlegającego dekompresji.
Kod:
Ad = cell(Nx,Ny);
Piszemy podwójnie zagnieżdżoną instrukcję pętli for, a w niej operację „wpisywania” odpowiednich wartości pikseli do odpowiedniej składowej (ramki) tablicy Ad.
Kod:
for i=1:Nx
for j=1:Ny
Ad{j+(i-1)*Nx} = reshape(w(ac(i+(j-1)*Nx),:),nx,ny);
end
end
Po wpisaniu do tablicy Ad wszystkich odtworzonych ramek, przekształcamy tę tablicę w „zwykłą” macierz obraz_d korzystając w tym celu z funkcji cell2mat. Macierz ta reprezentować będzie cały odtworzony obraz, już bez podziału na ramki. Po wszystkim zapisujemy i uruchomiamy skrypt oraz sprawdzamy rozmiary i zawartość macierzy Ad.
Kod:
obraz_d=cell2mat(Ad);
Wczytujemy zawartość pliku z oryginalnym obrazem do zmiennej obraz za pomocą imread.
Kod:
obraz = imread('lena.tif');
Korzystając z funkcji subplot wyświetlamy obraz oryginalny (obraz) oraz odtworzony (obraz_d) we wspólnym oknie graficznym, po jego lewej i prawej stronie (rys. 2).
Kod:
figure(2)
colormap gray
subplot(1,2,1)
imagesc(obraz,[0 255])
subplot(1,2,2)
imagesc(obraz_d,[0 255])
Rys. 2. Porównanie obrazu oryginalnego (z lewej) z obrazem po kompresji (z prawej).
Konwertujemy zmienną obraz do typu rzeczywistego podwójnej precyzji (double). Korzystając z funkcji sumsqr obliczamy wartość średniokwadratowego błędu kompresji (ang. Mean-Square-Error, MSE).
Kod:
obraz=double(obraz);
[X Y]=size(obraz);
MSE=sumsqr(obraz-obraz_d)/(X*Y);
otrzymujemy:
MSE=373.6534
W oparciu o podaną zależność obliczamy wartość współczynnika PSNR (ang. Peak Signal-to-Noise Ratio - szczytowy stosunek sygnału do szumu), służącego do określenia poziomu podobieństwa obrazu oryginalnego z obrazem skompresowanym.
Kod:
PSNR=10*log10((((2^8)-1)^2)/MSE);
otrzymujemy:
PSNR= 22.4061
4. Wnioski i spostrzeżenia
Sieć neuronowa Kohonena jest ciekawa ze względu na jej samo organizującą się strukturę. Nie wymaga ona nadzoru w procesie uczenia się (wzorce wyjściowe). Nasza sieć przy zadanych parametrach zmniejszyła (skompresowała) rozmiar obrazu prawie 14 - krotnie. Kompresja taka jednak okazała się stratna w zauważalnym stopniu (Rys. 2.). Utratę informacji (danych) widać na pierwszy rzut oka - obraz po kompresji jest miejscami niewyraźny. W obrazie obszary o podobnych wartościach (np. nos) pikseli zostały zamazane, natomiast tam gdzie sąsiadujące piksele ze sobą kontrastują (np. sierść) kompresja jest prawie niezauważalna.
Dla porównania zmieniliśmy rozmiar ramki z 2*2px na 4*4px, rozmiar obrazu zmniejszył się w porównaniu do oryginału aż 43 - krotnie. Natomiast jakość uległa znaczącemu pogorszeniu, co widać na rys. 3. Jak widać wielkość ramki znacząco wpływa na pogorszenie jakości obrazu po kompresji. Natomiast zwiększenie liczby neuronów sieci z 6 do 12 nie dało zauważalnej poprawy jakości.
Strona 12