SAMSUNG_022024C Advertisement SAMSUNG_022024C Advertisement SAMSUNG_022024C Advertisement

ML v Pythone 12 – vytvorenie a natrénovanie neurónovej siete ktorá rozozná či je huba jedlá

0

V predchádzajúcej časti  sme ukázali vytvorenie neurónovej siete, ktorá predpovedala výskyt cukrovky u indiánskeho kmeňa. Príklad v tejto stati bude zameraný na analýzu vzoriek údajov z oblasti prírodných vied. Úlohou neurónovej siete bude rozlíšiť jedlé huby od húb nejedlých a jedovatých. Príklad je názorný. Vo videu je kompletný postup.

V príklade predikcie cukrovky u indiánov kmeňa Pima boli všetky atribúty spojité. Napríklad vyššia hodnota atribútu vek znamenala že osoba je staršia, vyššia hodnota BMI, že je obéznejšia, rovnako je to aj s hladinou cukru a podobne.

Diskrétne kategorizačné údaje takúto súvislosť nemajú. Napríklad huba môže mať klobúk zvonovitý, kužeľovitý, vypuklý, plochý, s hrbolčekom uprostred, avšak to sú len kategórie. Nemôžeme povedať, že plochý klobúk huby je väčší, menší, jedlejší jedovatejší, lepším horší  ako zvonovitý, či vypuklý. Úlohou neurónovej siete bude odhaliť prípadné vzťahy medzi kategóriami. Napríklad poštové smerovacie čísla v adrese nemajú priamu súvislosť, ale podľa nich sa dá určiť, že niektoré adresy sú geograficky blízko seba, prípadne že ľudia, ktorí majú v adrese rovnaké smerovacie číslo majú podobný sociálno-ekonomický status a podobne.

Alebo iný príklad: pondelok a utorok sú dosť podobné dni, avšak sú v mnohých ohľadoch iné ako sobota, či nedeľa. Preto sa odporúča mesiac, rok, deň v týždni a niektoré ďalšie premenné za kategorizačné aj keď by sa mohli na prvý pohľad považovať za spojité. Pre premenné s relatívne malým počtom unikátnych hodnôt to často vedie k lepšiemu výsledku. Toto je jedno z dôležitých rozhodnutí, ktoré robí dátový vedec. Premenné reprezentované číslami s pohyblivou rádovou čiarkou ako takmer vždy považujeme za spojité.

Hoci sa môžeme rozhodnúť považovať spojité premenné za kategorizačné, opačne to neplatí: všetky premenné, ktoré sú kategorizačné, sa musia považovať za kategorizačné.

Popis vstupných údajov

Zdrojová databáza získaná z webu Department of Information and Computer Science University of California, Irvine CA.  (http://archive.ics.uci.edu/ml/datasets/Mushroom  ) pozostáva z dvoch textových súborov agaricus-lepiota.data a agaricus-lepiota.names Súbor agaricus-lepiota.data je klasickým súborom flat –file, kde sú jednotlivé atribúty oddelené čiarkami a jednotlivé záznamy sú oddelené prechodom na nový riadok Súbor obsahuje 8124 riadkov. Vo výpise je len ilustračná ukážka

p,x,s,n,t,p,f,c,n,k,e,e,s,s,w,w,p,w,o,p,k,s,u
e,x,s,y,t,a,f,c,b,k,e,c,s,s,w,w,p,w,o,p,n,n,g
e,b,s,w,t,l,f,c,b,n,e,c,s,s,w,w,p,w,o,p,n,n,m
....

Legenda k takto usporiadanému CSV je v súbore agaricus-lepiota.names

 Attribute Information: (classes: edible=e, poisonous=p)
     1. cap-shape:                bell=b,conical=c,convex=x,flat=f,
                                  knobbed=k,sunken=s
     2. cap-surface:              fibrous=f,grooves=g,scaly=y,smooth=s
     3. cap-color:                brown=n,buff=b,cinnamon=c,gray=g,green=r,
                                  pink=p,purple=u,red=e,white=w,yellow=y
     4. bruises?:                 bruises=t,no=f
     5. odor:                     almond=a,anise=l,creosote=c,fishy=y,foul=f,
                                  musty=m,none=n,pungent=p,spicy=s
     …
    21. population:               abundant=a,clustered=c,numerous=n,
                                  scattered=s,several=v,solitary=y
    22. habitat:                  grasses=g,leaves=l,meadows=m,paths=p,
                                  urban=u,waste=w,woods=d

Okrem informácie či je huba jedlá, alebo nie máme k dispozícii ešte 22 ďalších charakteristických vlastností pre každú hubu.

V príklade sú názvy atribútov preložené do češtiny

Pre rámcovú predstavu uvádzame skrátený fragment výpisu cieľovej tabuľky

id   Pozivatelnost      TvarKlobouku  PovrchKlob  BarvaKlob  Ox.ZmenaBarvy  Zapach          
---- ------------------ ------------- ----------- ---------- -------------- -----------------
1    nejedlá, jedovatá  vypouklý      hladký      hnědá      ano            štiplavý, čpavý 
3    jedlá              zvonovitý     hladký      bílá       ano            anýzový         
5    jedlá              vypouklý      hladký      šedá       ne             žádný           
7    jedlá              zvonovitý     hladký      bílá       ano            po mandlích     
9    nejedlá, jedovatá  vypouklý      šupinatý    bílá       ano            štiplavý, čpavý 
11   jedlá              vypouklý      šupinatý    žlutá      ano            anýzový         
13   jedlá              zvonovitý     hladký      žlutá      ano            po mandlích     
15   jedlá              vypouklý      vláknitý    hnědá      ne             žádný           
17   jedlá              plochý        vláknitý    bílá       ne             žádný           
19   nejedlá, jedovatá  vypouklý      šupinatý    bílá       ano            štiplavý, čpavý 
21   jedlá              zvonovitý     hladký      žlutá      ano            po mandlích     

Pre zaujímavosť celková štatistika zastúpenia jedlých a jedovatých húb v prezentovanej vzorke je nasledovná:

  • jedlé:    4208 (51.8%)
  • nejedlé a jedovaté: 3916 (48.2%)
  • spolu:    8124 húb

Na základe tabuľky pre učenie by podľa prezentovaných údajov dokázal vyvodiť len nejaký dobrý hubár, ktorý už má určité svoje poznatku a má ich do istej miery overené. Neurónová sieť žiadne poznatky nemá no môže ich na základe analýzy vzorky údajov získať.

Príklad budeme robiť v prostredí Google Coleboratory, takže súbor agaricus-lepiota.data premenujeme na agaricus-lepiota.data.csv  uložíme na Disk Google, v našom prípade do priečinka ML Neuronova siet. Súbor sme premenovali preto, aby sme mohli štandardnými prehliadačmi zobraziť jeho obsah

Príprava údajov

Pri načítaní CSV súboru priradíme atribútom názvy. CSV súbor, ktorý nahrávame nemá v prvom stĺpci index, ale prvý atribút je požívateľnosť, takže index priradíme explicitne. Inak by bol za index považovaný prvý atribút, v našom prípade požívateľnosť.

Poznámka: pre názornosť načítame všetky atribúty, neskôr pre trénovanie neurónovej siete použijeme len najrelevantnejšie.

import pandas as pd
from google.colab import drive
 
# Import údajov vo formáte CSV
drive.mount('/content/drive')
 
df = pd.read_csv('/content/drive/My Drive/ML Neuronova siet/agaricus-lepiota.csv', index_col=False,
                 names=('Pozivatelnost', 'TvarKlobuka', 'PovrchKlobuka',
                        'FarbaKlobuka', 'OxidacnaZmenaFarby', 'Zapach',
                        'NasadenieRebrovania', 'VzdialenostRebrovania',
                        'VelkostRebrovania', 'FarbaRebrovania', 'TvarNozky',
                        'ZakoncenieNozky', 'PovrchNozkyNadPrstenom',
                        'PovrchNozkyPodPrstenom', 'FarbaNozkyNadPrstenom',
                        'FarbaNozkyPodPrstenom', 'TypZavoja', 'FarbaZavoja',
                        'PocetPrstienkov', 'TypPrstienka',
                        'FarbaVytrusu', 'Populácia', 'Prostredie'))
 
# počet načítaných záznamov a počet atribútov
df.shape
 
---výpis---
(8124, 21)
 
# vypíšeme záhlavie a niekoľko ilustračných záznamov
df.head()
 
# informácie o DataFrame
df.info()
 
---výpis---
class 'pandas.core.frame.DataFrame'>
RangeIndex: 8124 entries, 0 to 8123
Data columns (total 23 columns):
 #   Column                  Non-Null Count  Dtype
---  ------                  --------------  -----
 0   Pozivatelnost           8124 non-null   object
 1   TvarKlobuka             8124 non-null   object
 2   PovrchKlobuka           8124 non-null   object
 3   FarbaKlobuka            8124 non-null   object
 4   OxidacnaZmenaFarby      8124 non-null   object
 5   Zapach                  8124 non-null   object
 6   NasadenieRebrovania     8124 non-null   object
 7   VzdialenostRebrovania   8124 non-null   object
 8   VelkostRebrovania       8124 non-null   object
 9   FarbaRebrovania         8124 non-null   object
 10  TvarNozky               8124 non-null   object
 11  ZakoncenieNozky         8124 non-null   object
 12  PovrchNozkyNadPrstenom  8124 non-null   object
 13  PovrchNozkyPodPrstenom  8124 non-null   object
 14  FarbaNozkyNadPrstenom   8124 non-null   object
 15  FarbaNozkyPodPrstenom   8124 non-null   object
 16  TypZavoja               8124 non-null   object
 17  FarbaZavoja             8124 non-null   object
 18  PocetPrstienkov         8124 non-null   object
 19  TypPrstienka            8124 non-null   object
 20  FarbaVytrusu            8124 non-null   object
 21  Populácia               8124 non-null   object
 22  Prostredie              8124 non-null   object
dtypes: object(23)
memory usage: 1.4+ MB

Zistíme, koľko unikátnych hodnôt obsahuje každý atribút

for i in df.columns:
    print("{} unikátnych hodnôt {} ".format(i,len(df[i].unique())))
 
---výpis---
Pozivatelnost unikátnych hodnôt 2
TvarKlobuka unikátnych hodnôt 6
PovrchKlobuka unikátnych hodnôt 4
FarbaKlobuka unikátnych hodnôt 10
OxidacnaZmenaFarby unikátnych hodnôt 2
Zapach unikátnych hodnôt 9
NasadenieRebrovania unikátnych hodnôt 2
VzdialenostRebrovania unikátnych hodnôt 2
VelkostRebrovania unikátnych hodnôt 2
FarbaRebrovania unikátnych hodnôt 12
TvarNozky unikátnych hodnôt 2
ZakoncenieNozky unikátnych hodnôt 5
PovrchNozkyNadPrstenom unikátnych hodnôt 4
PovrchNozkyPodPrstenom unikátnych hodnôt 4
FarbaNozkyNadPrstenom unikátnych hodnôt 9
FarbaNozkyPodPrstenom unikátnych hodnôt 9
TypZavoja unikátnych hodnôt 1
FarbaZavoja unikátnych hodnôt 4
PocetPrstienkov unikátnych hodnôt 3
TypPrstienka unikátnych hodnôt 5
FarbaVytrusu unikátnych hodnôt 9
Populácia unikátnych hodnôt 6
Prostredie unikátnych hodnôt 7

Pre lepšiu názornosť najskôr zredukujeme počet atribútov na šesť najrelevantnejších. Ale ktoré to sú? Skúsení hubári možno vedia. Ja som tieto údaje pred niekoľkými rokmi použil pre príklad dataminingu v Microsoft SQL Serveri 2012. Tam je k dispozícii nástroj, ktorý dokáže identifikovať, ktoré atribúty majú najvýraznejší vplyv na predikovaný atribút požívateľnosti. Je to: Zápach, FarbaVytrusu, FarbaRebrovania, TypPrstienka, PovrhNozkyPodPrstenom a PovrhNozkyNadPrstenom.

Definujeme kategorizačné atribúty a výstupný atribút

# najrelevantnejšie atribúty
kat_atributy=['Zapach', 'FarbaVytrusu', 'FarbaRebrovania',
              'TypPrstienka','PovrchNozkyNadPrstenom',
              'PovrchNozkyPodPrstenom'
]
out_atribut='Pozivatelnost'

Neurónová sieť pracuje s číslami, takže potrebujeme transformovať textové reťazce na číselné hodnoty. Napríklad atribút Farba výtrusu môže nadobudnúť hodnoty:

k, n, b, h, r, o, u, w, y

tieto písmená znamenajú farby

čierna=k, hnedá=n, hnedožltá=b, čokoládová=h, zelená=r, oranžová=o, purpurová=u, biela=w, žltá=y

df['FarbaVytrusu'].unique()
 
---výpis---
array(['k', 'n', 'u', 'h', 'w', 'r', 'o', 'y', 'b'], dtype=object)

Na transformáciu textových atribútov na číselné hodnoty použijeme príkaz LabelEndocer()

# --------------------------
# --- kód na vysvetlenie ---
# --------------------------
from sklearn.preprocessing import LabelEncoder
lbl_encoders={}
lbl_encoders["FarbaVytrusu"]=LabelEncoder()
lbl_encoders["FarbaVytrusu"].fit_transform(df["FarbaVytrusu"])
 
---výpis---
array([2, 3, 3, ..., 0, 7, 4])

Transformujeme všetky relevantné vstupné atribúty

# Transformácia vstupných atribútov
from sklearn.preprocessing import LabelEncoder
lbl_encoders={}
for atribut in kat_atributy:
    lbl_encoders[atribut]=LabelEncoder()
    df[atribut]=lbl_encoders[atribut].fit_transform(df[atribut])

transformujeme aj predikovaný atribút

# Pozivatelnost
lbl_encoders={}
lbl_encoders['Pozivatelnost']=LabelEncoder()
df['Pozivatelnost']=lbl_encoders['Pozivatelnost'].fit_transform(df['Pozivatelnost'])

Vytvoríme pole relevantných atribútov

# najrelevantnejšie atribúty
import numpy as np
kat_atributy=np.stack([df['Zapach'], df['FarbaVytrusu'],
                       df['FarbaRebrovania'],df['TypPrstienka'],
                       df['PovrchNozkyNadPrstenom'],
                       df['PovrchNozkyPodPrstenom']],1)
kat_atributykat_atributy
 
---výpis---
array([[ 6,  2,  4,  4,  2,  2],
       [ 0,  3,  4,  4,  2,  2],
       [ 3,  3,  5,  4,  2,  2],
       ...,
       [ 5,  0,  5,  4,  2,  2],
       [ 8,  7,  0,  0,  2,  1],
       [ 5,  4, 11,  4,  2,  2]])

Pre ďalšie výpočty je potrebné konvertovať polia na tenzory

#Konverzia array na tenzor
import torch
kat_atributy=torch.tensor(kat_atributy,dtype=torch.int64)
kat_atributy
 
---výpis---
tensor([[ 6,  2,  4,  4,  2,  2],
        [ 0,  3,  4,  4,  2,  2],
        [ 3,  3,  5,  4,  2,  2],
        ...,
        [ 5,  0,  5,  4,  2,  2],
        [ 8,  7,  0,  0,  2,  1],
        [ 5,  4, 11,  4,  2,  2]])

Závislé atribúty sme konvertovali ako celočíselné hodnoty. Predikovaný atribút Poživatelnost napriek tomu že obsahuje len dve hodnoty

1 - jedlá

0 - nejedlá

prekonvertujeme na hodnotu float. Väčšina stratových funkcií pracuje s float hodnotami, tak budeme mať lepšie možnosti výberu

# Pozivatelnosť ako závislá hodnota
y=torch.tensor(df['Pozivatelnost'].values,dtype=torch.float32).reshape(-1,1)
y
 
---výpis---
tensor([[1.],
        [0.],
        [0.],
        ...,
        [0.],
        [1.],
        [0.]])

Každý atribút má určitý počet dimenzií, čiže unikátnych hodnôt. Napríklad

len(df['FarbaVytrusu'].unique())
 
---výpis---
9

Vytvoríme pole počtu dimenzií pre vstupné kategorizačné atribúty

# najrelevantnejšie atribúty
# dimenzie pre vstupné kategorizačné atribúty
kat_dims=[len(df[col].unique()) for col in ['Zapach', 'FarbaVytrusu',
              'FarbaRebrovania', 'TypPrstienka',
              'PovrchNozkyNadPrstenom', 'PovrchNozkyPodPrstenom']]
kat_dims
 
---výpis---
[9, 9, 12, 5, 4, 4]

Vypočítame počet výstupných dimenzií. Pre počet výstupných dimenzií vo všeobecnosti platí pravidlo nazývané Thumb rule. Počet výstupných dimenzií pre kategorizačné atribúty sa určuje podľa počtu vstupných dimenzií podľa vzorca

Výstupné_dimenzie = min(50, vstupné_dimenzie /2)

V prípade ak je výsledkom desatinné číslo počet výstupných dimenzií bude najbližšie vyššie celé číslo

Interpretácia vzorca v Pythone bude:

embedding_dim= [(x, min(50, (x + 1) // 2)) for x in kat_dims]
embedding_dim
 
---výpis---
[(9, 5), (9, 5), (12, 6), (5, 3), (4, 2), (4, 2)]

Prvá dimenzia má 9 vstupov a 5 výstupov, druhá dimenzia má tiež 9 vstupov a 5 výstupov, tretia dimenzia má 12 vstupov a 6 výstupov atď.

Definovanie neurónovej siete

Všetko máme pripravené, môžeme definovať model neurónovej siete. Neurónovú sieť definujeme ako triedu. Náš model bude jednoduchá dopredná neurónová sieť s dvoma skrytými vrstvami, vloženými vrstvami pre kategorické prvky a potrebnými vrstvami pre výpadky a dávkovú normalizáciu. Trieda nn.Module je základná trieda pre všetky neurónové siete v knižnici PyTorch. Náš model, ModelNS, bude podtriedou triedy nn.Module. V metóde __init__ našej triedy budeme inicializovať rôzne vrstvy, ktoré budú použité v modeli a metóda forward() definuje výpočty vykonávané v sieti.

Vysvetlíme parametre v konštruktore, čiže metóde __init__()

emb_dims: Zoznam dvoch prvkov n-tice - bude obsahovať dva prvky pre každý kategorizačný atribút. Prvý prvok n-tice udáva počet jedinečných hodnôt vlastností atribútu. Druhý prvok udáva rozmer vloženia (embedding_dim), ktorý sa má použiť pre tento atribút. V našom prípade to budú dimenzie

[(9, 5), (9, 5), (12, 6), (5, 3), (4, 2), (4, 2)]

output_size: celé číslo, hodnota konečného výstupu.

layers: zoznam celých čísel udávajúcich veľkosť každej lineárnej vrstvy.

p: Float (emb_dropout) výpadok, ktorý sa má použiť po vložených vrstvách. Je to pravdepodobnosť prvku, ktorý bude vynulovaný. Predvolená hodnota je 0,5

Najskôr vysvetlíme ako fungujú funkcie, ktoré použijeme pri definovaní neurónovej siete.

V konštruktore vytvoríme zoznam modulov, ktorých úlohou je prechádzať medzi dimenziami jednotlivých kategorizačných atribútov. Na vytvorenie modulov použijeme funkciu nn.Embedding(inp,out).

# --------------------------
# --- kód na vysvetlenie ---
# --------------------------
import torch
import torch.nn as nn
import torch.nn.functional as F
zoznam_modulov=nn.ModuleList([nn.Embedding(inp,out) for inp,out in embedding_dim])
zoznam_modulov
 
---výpis---
ModuleList(
  (0-1): 2 x Embedding(9, 5)
  (2): Embedding(12, 6)
  (3): Embedding(5, 3)
  (4-5): 2 x Embedding(4, 2)
)

Pre každý modul vytvoríme tenzory kategorizačných atribútov

# --------------------------
# --- kód na vysvetlenie ---
# --------------------------
pd.set_option('display.max_rows', 500)
embedding_val=[]
for i,e in enumerate(zoznam_modulov):
    embedding_val.append(e(kat_atributy[:,i]))
embedding_val
 
---výpis---
[tensor([[ 0.7445,  1.8841,  0.2849,  0.0376,  0.2623],
         [-0.8356,  0.7977,  0.3105,  1.5629,  0.0448],
         [-0.7671, -0.0211, -0.5870, -1.2909,  1.2045],
         ...,
         [ 0.4022,  0.2772, -0.7971,  0.8523,  0.2559],
         [ 0.1669, -0.2771,  0.5955, -1.4151,  0.4162],
         [ 0.4022,  0.2772, -0.7971,  0.8523,  0.2559]],
        grad_fn=),
 tensor([[ 0.6070, -0.4832,  0.2355, -1.4405,  1.2913],
         [ 0.0332,  2.3018,  0.6294,  0.3649, -2.1783],
         [ 0.0332,  2.3018,  0.6294,  0.3649, -2.1783],
         ...,
         [-0.9738, -0.0925,  0.7694,  0.8880,  0.2444],
         [-0.4175, -0.9002, -1.2203, -0.5983,  0.0418],
         [ 0.1905,  1.0526,  0.8444, -0.4629,  0.1212]],
        grad_fn=),
 tensor([[-0.8418, -0.9751, -0.9304, -1.8469,  0.2673,  1.9546],
         [-0.8418, -0.9751, -0.9304, -1.8469,  0.2673,  1.9546],
         [-0.6887, -1.9983, -0.5807, -0.0911,  0.7295,  0.1015],
         ...,
         [-0.6887, -1.9983, -0.5807, -0.0911,  0.7295,  0.1015],
         [-0.9644, -1.6516,  1.0347, -0.2579, -2.0508,  1.2071],
         [ 1.6525, -0.8622, -1.6372, -0.6959, -1.0694, -0.8077]],
        grad_fn=),
 tensor([[-2.4116, -0.4624, -2.0588],
         [-2.4116, -0.4624, -2.0588],
         [-2.4116, -0.4624, -2.0588],
         ...,
         [-2.4116, -0.4624, -2.0588],
         [ 0.4855,  0.7295,  0.1453],
         [-2.4116, -0.4624, -2.0588]], grad_fn=),
 tensor([[-1.9945, -0.9206],
         [-1.9945, -0.9206],
         [-1.9945, -0.9206],
         ...,
         [-1.9945, -0.9206],
         [-1.9945, -0.9206],
         [-1.9945, -0.9206]], grad_fn=),
 tensor([[-0.3063,  0.6173],
         [-0.3063,  0.6173],
         [-0.3063,  0.6173],
         ...,
         [-0.3063,  0.6173],
         [ 1.8537, -0.0721],
         [-0.3063,  0.6173]], grad_fn=)]

 

Každý modul má v tenzore rovnaký počet prvkov ako výstupných atribútov. V našom prípade 5, 5, 6, 3, 2, 2, pretože rozmery pre naše kategorizačné  atribúty sú

[(9, 5), (9, 5), (12, 6), (5, 3), (4, 2), (4, 2)]

V zozname modulov sú rovnaké vrstvy označené násobiteľom výskytu

ModuleList(
  (0-1): 2 x Embedding(9, 5)
  (2): Embedding(12, 6)
  (3): Embedding(5, 3)
  (4-5): 2 x Embedding(4, 2)
)

Na spojenie týchto tenzorov, ktoré majú rôzne dimenzie sa použije funkcia torch.cat().

# --------------------------
# --- kód na vysvetlenie ---
# --------------------------
z = torch.cat(embedding_val, 1)
z
 
---výpis---
tensor([[ 0.7445,  1.8841,  0.2849,  ..., -0.9206, -0.3063,  0.6173],
        [-0.8356,  0.7977,  0.3105,  ..., -0.9206, -0.3063,  0.6173],
        [-0.7671, -0.0211, -0.5870,  ..., -0.9206, -0.3063,  0.6173],
        ...,
        [ 0.4022,  0.2772, -0.7971,  ..., -0.9206, -0.3063,  0.6173],
        [ 0.1669, -0.2771,  0.5955,  ..., -0.9206,  1.8537, -0.0721],
        [ 0.4022,  0.2772, -0.7971,  ..., -0.9206, -0.3063,  0.6173]],
       grad_fn=)

Funkcia nn.Dropout() počas trénovania neurónovej siete náhodne vynuluje niektoré prvky vstupného tenzora s pravdepodobnosťou p s použitím vzoriek z Bernoulliho rozdelenia. Každý kanál sa vynuluje nezávisle pri každom doprednom volaní. Je to účinná technika na regularizáciu a zabránenie ko-adaptácii neurónov. Okrem toho sú počas trénovania výstupy škálované koeficientom

​To znamená, že počas vyhodnocovania modul jednoducho vypočíta funkciu identity.

# --------------------------
# --- kód na vysvetlenie ---
# --------------------------
droput=nn.Dropout(.4)
droput
 
---výpis---
Dropout(p=0.4, inplace=False)
 
# --------------------------
# --- kód na vysvetlenie ---
# --------------------------
final_embed=droput(z)
final_embed
 
---výpis---
tensor([[ 1.2408,  3.1402,  0.0000,  ..., -0.0000, -0.5106,  1.0288],
        [-1.3926,  0.0000,  0.0000,  ..., -1.5343, -0.5106,  0.0000],
        [-0.0000, -0.0351, -0.0000,  ..., -0.0000, -0.5106,  0.0000],
        ...,
        [ 0.6703,  0.4621, -0.0000,  ..., -0.0000, -0.0000,  1.0288],
        [ 0.0000, -0.0000,  0.9924,  ..., -0.0000,  3.0895, -0.1201],
        [ 0.6703,  0.0000, -0.0000,  ..., -0.0000, -0.0000,  1.0288]],
       grad_fn=)

Ako aktivačnú funkciu použijeme ReLu

Funkcie máme vysvetlené, takže uvedieme kompletný kód

#Definovanie neurónovej siete
import torch
import torch.nn as nn
import torch.nn.functional as F
class Model_NS(nn.Module):
    # konštruktor
    def __init__(self, embedding_dim, out_sz, layers, p=0.5):
        super().__init__()
        self.embeds = nn.ModuleList([nn.Embedding(inp,out) for inp,out in embedding_dim])
        self.emb_drop = nn.Dropout(p)
 
        layerlist = []
        n_emb = sum((out for inp,out in embedding_dim))
        n_in = n_emb
 
        for i in layers:
            layerlist.append(nn.Linear(n_in,i))
            layerlist.append(nn.ReLU(inplace=True))
            layerlist.append(nn.BatchNorm1d(i))
            layerlist.append(nn.Dropout(p))
            n_in = i
        layerlist.append(nn.Linear(layers[-1],out_sz))
        self.layers = nn.Sequential(*layerlist)
 
   # pri predpovedaní informácie prúdia „dopredu“
   # zo vstupu cez skryté vrstvy na výstup   
 
    def forward(self, x_cat):
        embeddings = []
        for i,e in enumerate(self.embeds):
            embeddings.append(e(x_cat[:,i]))
        x = torch.cat(embeddings, 1)
        x = self.emb_drop(x)
        x = self.layers(x)
        return x

Vytvoríme model. Prvá lineárna vrstva bude mať 100 a druhá 50 neurónov

torch.manual_seed(100) # na inicializáciu generátora náhodných čísel
model=Model_NS(embedding_dim,1,[100,50],p=0.4)
model
 
---výpis---
Model_NS(
  (embeds): ModuleList(
    (0-1): 2 x Embedding(9, 5)
    (2): Embedding(12, 6)
    (3): Embedding(5, 3)
    (4-5): 2 x Embedding(4, 2)
  )
  (emb_drop): Dropout(p=0.4, inplace=False)
  (layers): Sequential(
    (0): Linear(in_features=23, out_features=100, bias=True)
    (1): ReLU(inplace=True)
    (2): BatchNorm1d(100, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (3): Dropout(p=0.4, inplace=False)
    (4): Linear(in_features=100, out_features=50, bias=True)
    (5): ReLU(inplace=True)
    (6): BatchNorm1d(50, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (7): Dropout(p=0.4, inplace=False)
    (8): Linear(in_features=50, out_features=1, bias=True)
  )
)

definujeme stratovú funkciu a optimizér

loss_function=nn.MSELoss()
optimizer=torch.optim.Adam(model.parameters(),lr=0.01)

Údaje rozdelíme na trénovacie a testovacie, v tomto príklade na polovice, aby sme mali čo najviac testovacích údajov

# rozdelenie náhodne train_test_split()
from sklearn.model_selection import train_test_split
train_vstupy, test_vstupy, y_train, y_test = train_test_split(kat_atributy,y,test_size=0.5,random_state=0)

Overíme počty

len(train_vstupy),len(test_vstupy),len(y_train),len(y_test)
 
---výpis---
(4062, 4062, 4062, 4062)

Overíme tiež či je v trénovacej a testovacej množine rovnomerné zastúpenie jedlých a nejedlých húb

nJedle, nNejedle = 0, 0
for i, x in enumerate(y_train.numpy()):
    if abs(x) <.01:
      nJedle +=1
    else:
      nNejedle +=1
print("Trenovacie dáta: Jedlé", nJedle, "Nejedlé", nNejedle)
 
---výpis---
Trenovacie dáta: Jedlé 2098 Nejedlé 1964
 
nJedle, nNejedle = 0, 0
for i, x in enumerate(y_test.numpy()):
    if abs(x) <.01:
      nJedle +=1
    else:
      nNejedle +=1
print("Testovacie dáta: Jedlé", nJedle, "Nejedlé", nNejedle)
 
---výpis---
Testovacie dáta: Jedlé 2110 Nejedlé 1952

Spustíme trénovanie. 800 iterácií je zrejme veľa, ale vzhľadom k malému objemu údajov bude trénovanie pomerne rýchle aj na CPU.

epochy=800
priebezne_chyby = [] # hodnoty chýb pre graf
perioda_vypisu = 10
 
for i in range(epochy):
    i=i+1
    y_pred=model(train_vstupy)
    loss=loss_function(y_pred,y_train)
    priebezne_chyby.append(loss.item())
    if i % perioda_vypisu == 0:
        print(i+1,"z",epochy, "iterácií", i,"krok",
               "Chyba: {:.3f}".format(loss.item()))
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
 
---výpis---
11 z 800 iterácií 10 krok Chyba: 0.308
21 z 800 iterácií 20 krok Chyba: 0.149
;...
791 z 800 iterácií 790 krok Chyba: 0.008
801 z 800 iterácií 800 krok Chyba: 0.008

Vykreslíme graf stratovej funkcie

# graf stratovej funkcie
import matplotlib.pyplot as plt
%matplotlib inline
import pandas as pd
loss_plot = pd.DataFrame(priebezne_chyby)
loss_plot.plot()

 

Overenie NS na testovacích záznamoch

# Predpoved na testovacích záznamoch
y_pred=""
with torch.no_grad():
    y_pred=model(test_vstupy)

Vytvoríme DataFrame so skutočnými a predpovedanými hodnotami

# 0 - jedlá   1 nejedlá
df_skutocne=pd.DataFrame(y_test.tolist(),columns=["Skutocnost"])
 
for i in range(len(df_skutocne)):
  if abs(df_skutocne.loc[i, "Skutocnost"]) <.01:
     df_skutocne.loc[i, "Skut. Text"] = 'Jedlá'
  else:
      df_skutocne.loc[i, "Skut. Text"] = 'Nejedlá'
 
df_predpoved =pd.DataFrame(y_pred.tolist(),columns=["Predpoved"])
 
prahova_hodnota = 0.5
df_porovnanie=pd.concat([df_skutocne,df_predpoved],axis=1)
# porovnám s prahovou hodnotou výsledok 1 alebo 0
for i in range(len(df_porovnanie)):
    if df_porovnanie.loc[i, "Predpoved"] > prahova_hodnota:
        df_porovnanie.loc[i, "Predpoved1"] = 1
    else:
        df_porovnanie.loc[i, "Predpoved1"] = 0
 
for i in range(len(df_porovnanie)):
  if abs(df_porovnanie.loc[i, "Skutocnost"] - df_porovnanie.loc[i, "Predpoved1"]) <.01:
     df_porovnanie.loc[i, "Vyhodnotenie"] = 'Správne'
  else:
     df_porovnanie.loc[i, "Vyhodnotenie"] = '-'
df_porovnanie.head(10)

Zistíme počet nesprávnych predpovedí

nespravne = 0
for i in range(len(df_porovnanie)):
    if abs(df_porovnanie.loc[i, "Skutocnost"] - df_porovnanie.loc[i, "Predpoved1"]) >.01:
        nespravne += 1
nespravne
 
---výpis---
9

V tomto prípade nie je však omyl ako omyl. Ak necháte jedlú hubu, ktorú algoritmus nesprávne určil ako  jedovatú v lese, nestane sa nič. Opačný prípad, kedy algoritmus určí jedovatú hubu ako jedlú však môže mať tragické následky.

JedlaAkoNejedla = 0
NejedlaAkoJedla = 0
for i in range(len(df_porovnanie)):
    #len pre nesprávne hodnoty
    if abs(df_porovnanie.loc[i, "Skutocnost"] - df_porovnanie.loc[i, "Predpoved1"]) >.01:
        if (df_porovnanie.loc[i, "Skutocnost"] >0.99):
            NejedlaAkoJedla += 1
        else:
            JedlaAkoNejedla += 1
 
print("Nejedlá ako jedlá",NejedlaAkoJedla)
print("Jedlá ako nejedlá",JedlaAkoNejedla)
 
 
---výpis---
Nejedlá ako jedlá 9
Jedlá ako nejedlá 0

Kategorizácia húb je pomerne presná, aj napriek tomu, že sme do úvahy zobrali len 6 atribútov z 22.

Pre zaujímavosť dataminingový algoritmus SQL Servera 2012 z 4062 záznamov nesprávne určil len osem, čo je zanedbateľný zlomok percenta. Všetkých osem nesprávne určených prípadov bola nejedlá, prípadne jedovatá huba identifikovaná ako jedlá.

Rekapitulácia doterajších dielov

ML v Pythone 12 – neurónová sieť predpovedá výskyt cukrovky u indiánskeho kmeňa

ML v Pythone 11 – vytvorenie a učenie neurónovej siete na generovanie poviedok

ML v Pythone 10 – trénovanie neurónovej siete, príprava

ML v Pythone 9 – trénovanie neurónu

ML v Pythone 8 – príklad rozpoznávanie obrazu

ML v Pythone 7 – Využitie grafickej karty NVIDIA na výpočtovo náročné úlohy

ML v Pythone 6 – animované grafy a ich export ako video

ML v Pythone 5 – vizualizácia údajov pomocou grafov

Strojové učenie v Pythone 4 – práca s údajmi

ML v Pythone 3 – export a import údajov vo formáte CSV a Excel

Strojové učenie v Pythone 2 – knižnica Pandas na prácu s údajmi

Strojové učenie v Pythone 1 – prostredie Google Colab

Zobrazit Galériu

Luboslav Lacko

Všetky autorove články
Python strojove ucenie neuronove siete AI

Pridať komentár

Mohlo by vás zaujímať

Mohlo by vás zaujímať