201 lines
9.1 KiB
Markdown
201 lines
9.1 KiB
Markdown
|
##### 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.
|