##### Raport przygotowała: Kinga Molik ##### Raportowany okres: 10 maja - 17 maja 2020 ##### Niniejszy raport poświęcony jest przekazaniu informacji na temat stanu mini-projektu indywidualnego w ramach projektu grupowego realizowanego na przedmiot Sztuczna Inteligencja w roku akademickim 2019/2020. Tematem realizowanego projektu indywidualnego jest rozpoznawanie nominałów banknotów oraz kart płatniczych. Do rozwiązania problemu zostały wykorzystane **sieci neuronowe**, biblioteki: - Keras (backend: Tensorflow), - Pillow Uczenie modelu -------------- #### Dane wejściowe: Do utworzenia modelu przygotowane zostały zdjęcia przedstawiające polskie banknoty oraz różne karty płatnicze. W folderach *train* i *examine* znajdują się foldery: - 10, - 20, - 50, - 100, - 200, - 500 Odpowiadające nominałom polskich banknotów (zdjęć jest odpowiednio: po 35 i po 10) oraz: - cards, w których umieszczone zostały zdjęcia kart bankowych (80 oraz 20). Wszystkie zdjęcia są jednego typu (rozszerzenie .png), wszystkie są nasycone kolorami (nie ma zdjęć czarno-białych) oraz moją różne wielkości, co będzie normalizowane w kodzie. #### Proces uczenia: *Inicjalizacja sieci neuronowej*: > ``` {.python} > model = Sequential() > model.add(Conv2D(32, (2, 2), input_shape=input_shape)) > model.add(Activation('relu')) > model.add(MaxPooling2D(pool_size=(2, 2))) > > model.add(Conv2D(32, (2, 2))) > model.add(Activation('relu')) > model.add(MaxPooling2D(pool_size=(2, 2))) > > model.add(Conv2D(64, (2, 2))) > model.add(Activation('relu')) > model.add(MaxPooling2D(pool_size=(2, 2))) > > model.add(Flatten()) > model.add(Dense(64)) > model.add(Activation('relu')) > model.add(Dropout(0.5)) > model.add(Dense(7)) > model.add(Activation('sigmoid')) > ``` - **`Conv2D`** - warstwa do konwulsji obrazu w wiele obrazów - poprzez mnożenie ich przez macierz (2,2) . - **`Activation('relu')`** - funkcja aktywacji (negatywne wyniki są zerowane). - **`MaxPooling2D`** - funkcja zmiany rozdzielczości zdjęcia (raz dodana używana jest również przez kolejne warstwy). - **`Flatten`** - spłaszcza macierze do wektorów.\ - **`Dense(64)`** - używana do łącznia całego modelu (jest ukrytą warstwą).\ - **`Dropout`** - używana, by uniknąć nadmiernego dopasowania bazy danych.\ - **`Dense(7)`** - warstwa wyjściowa zawierająca 7 neuronów (bo tyle mamy różnych kategorii) - decyduje o przynależności danego zdjęcia do kategorii. - **`Activation('sigmoid')`**- normalizuje wartości do przedziału [0,1] (ponieważ mamy tu do czynienia z prawdopodobieństwem). *Kompilacja modelu* : > ``` {.python} > model.compile(loss='categorical_crossentropy', > optimizer='rmsprop', > metrics=['accuracy']) > ``` Mamy 7 kategorii, które chcielibyśmy rozróżniać, dlatego zastosujemy funkcję straty **categorical\_crossentropy**, do optymalizacji wykorzystamy **rmsprop**, a parametr **metrics = ['accuracy']** pomaga nam sprawdzać dokładność nauki. *Generatory danych*: > ``` {.python} > train_datagen = ImageDataGenerator( > rotation_range=45, > width_shift_range=0.3, > height_shift_range=0.3, > rescale=1./256, > shear_range=0.25, > zoom_range=0.1, > horizontal_flip=True) > > examine_datagen = ImageDataGenerator(rescale=1. / 256) > > train_generator = train_datagen.flow_from_directory( > train_data_dir, > target_size=(img_width, img_height), > batch_size=batch_size, > class_mode='categorical') > > examine_generator = examine_datagen.flow_from_directory( > examine_data_dir, > target_size=(img_width, img_height), > batch_size=batch_size, > class_mode='categorical') > ``` **train\_datagen** - generator obrazów z bazy danych uczących. **examine\_datagen** - generator obrazów z bazy danych sprawdzających. Generujemy obrazy, które są niewielką modyfikacją oryginalnych obrazów z folderów *train* i *examine* w celu zwiększenia ich ilości. Czym większa baza wiedzy, tymlepsze wyniki podczas sprawdzania. **train\_generator** oraz **examine\_genertor** zaczytują nam pliki z danych folderów (*train*, *examine*), normalizuje wielkość zdjęć (img\_width, img\_height = 256, 256), przypisujemy batch\_size = 16 oraz class\_mode='categorical', ponieważ mamy 7 różnych klas. > ``` {.python} > model.fit_generator( > train_generator, > steps_per_epoch=nb_train_samples // batch_size, > epochs=epochs, > validation_data=examine_generator, > validation_steps=nb_examine_samples // batch_size) > > model.save_weights('model_payment.h5') > ``` **model.fit\_generator** - generujemy model danych z danymi: - train\_generator - genetarator danych do nauki - steps\_per\_epoch - liczba kroków w danej epoce (obliczana przez podzielenie ilości danych uczących (290) przez batch\_size (16)) - validation\_data - generator danych do sprawdzania - validation\_steps - liczna kroków podczas sprawdzania (ilość danych sprawdzających (80) przez batch\_size(16)) ' **model\_payment.h5** ' - plik, do którego zapisywane są wagi modelu. Integracja z projektem ---------------------- Na początku zostaje stworzony model sekwencyjny, na którym zostaje wywołana funkcja *recognizeCash()* oraz załadowany zostaje wcześniej stworzony model z wagami. > ``` {.python} > pln_classify = Sequential() > recognizeCash(pln_classify) > pln_classify.load_weights('Kinga/model_payment.h5') > ``` Funkcja do rozpoznawania banknotów uruchomiona zostaje po wybraniu na ekranie startowym przycisku *Rozpoznawanie banknotów*. W funkcji *payment()* mamy podany przykładowy stan początkowy restauracji, czyli dane dotyczące klientów, tj. numer stolika, zamówione dania. Zakładamy, że każdy z klientów zakończył już konsumpcje oraz chciałby zapłacić. Każdy klient ma podany budżet (zakładamy tu, że budget \>0 oznacza ilość posiadanej gotówki, a budget=0 oznacza chęć zapłaty kartą.) Agent powinien rozpoznać jakim banknotem zapłacił klient oraz wydać mu resztę. W przypadku zapłaty kartą, agent oznajmia, że restauracja nie przyjmuje płatności kartą. By to uschematyzować, została dodana funkcja w klasie *Client*: > ``` {.python} > def pay(self, image): > load = Image.open(image) > load.show() > ``` Po podejściu do stolika, agent odczytuje cenę zamówionej przez klienta potrawy, zostaje wywołana funkcja *decide()* (z atrybutami *client* - dany klient, *image* - zdjęcie z bazy ćwiczeniowej, *dish* - potrawa klienta), w której zostaje wyświetlone zdjęcie z płatnością klienta oraz sprawdzamy w niej, do której klasy należy ów zdjęcie. Nasze sprawdzenie wykonujemy poprzez przesłanie znormalizowanego zdjęcia do modelu, który wcześniej zainicjowaliśmy. > ``` {.python} > def decide(client, image, dish): > img_width, img_height = 256, 256 > > client.pay(image) > > test_image = load_img(image, target_size=(img_width, img_height)) > test_image = img_to_array(test_image) > test_image = test_image.reshape((1,) + test_image.shape) > > result = pln_model.predict(test_image) > print(result) > > if result.tolist() == [[1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]]: > messagebox.showinfo("Rachunek", "Dziękuję, należy się reszta: " + str(10 - dish.price) + " zł.") > return 0 > elif result.tolist() == [[0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0]]: > messagebox.showinfo("Rachunek", "Dziękuję, należy się reszta: " + str(100 - dish.price) + " zł.") > return 0 > elif result.tolist() == [[0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0]]: > messagebox.showinfo("Rachunek", "Dziękuję, należy się reszta: " + str(20 - dish.price) + " zł.") > return 0 > elif result.tolist() == [[0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]]: > messagebox.showinfo("Rachunek", "Dziękuję, należy się reszta: " + str(200 - dish.price) + " zł.") > return 0 > elif result.tolist() == [[0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0]]: > messagebox.showinfo("Rachunek", "Dziękuję, należy się reszta: " + str(50 - dish.price) + " zł.") > return 0 > elif result.tolist() == [[0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0]]: > messagebox.showinfo("Rachunek", "Dziękuję, należy się reszta: " + str(500 - dish.price) + " zł.") > return 0 > else: > messagebox.showinfo("Uwaga", "Niestety, nie przyjmujemy zapłaty kartą.") > return 1 > ``` Zmienna *result* ma typ numpy.ndarray, dlatego przy użyciu jej w funkcji warunkowej *if* konwertujemy ją do listy. Nasze zdjęcie może przynależeć do jednej z 7 klas, gdzie **1.0** oznacza właśnie tą przynależność. Klasy zaczytywane są w kolejności alfabetycznej, czyli: 10, 100, 20, 200, 50, 500, cards. A więc lista: [[0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0]] oznacza, że na danym zdjęciu mamy banknot 20 złotowy. W zależności od zdjęcia, mamy komunikat o reszcie lub o braku możliwości płacenia kartą. Po przejściu przykładowo wyznaczonej trasy, aplikacja kończy swoje działanie.