Õppematerjal · TensorFlow ja Keras

TensorFlow nullist MLP-ni

Selle materjali lõpuks oskad Kerasega ehitada ja treenida mitmekihilise pertseptroni (MLP), mis eristab andmetes klasse. Närvivõrgu teooriat siin ei korrata, selleks on nn-explainer. Siin läheme otse TensorFlow poole: kõigepealt tensor, siis üks neuron, siis kihid ja lõpuks täielik MLP.

Teooria kordamiseks: nn-explainer ↗
Eeldus: Pythoni põhitõed ja närvivõrgu põhimõisted (neuron, kiht, kaofunktsioon) Aeg: umbes 2 tundi Vahendid: Jupyter, Colab (TensorFlow on seal olemas) või VS Code

Mis on süvaõpe, TensorFlow ja Keras?

Enne koodi lühidalt kolm sõna, mida selles materjalis kogu aeg kuuled.

Süvaõpe

Masinõppes ei kirjuta inimene reegleid ette, vaid arvuti leiab need andmetest. Närvivõrk on üks masinõppe mudel: see koosneb neuronitest, mis on seatud kihtidesse. Süvaõpe (deep learning) on närvivõrgud, kus on mitu peidetud kihti. Sõna „sügav“ tulebki kihtide arvust: mida rohkem kihte, seda keerukamaid mustreid võrk õppida suudab. Pildi puhul õpib esimene kiht näiteks servi, järgmine kujundeid ja viimane terveid objekte.

Tehisintellektarvuti teeb asju, mis tavaliselt nõuavad inimese mõtlemistMasinõpereeglid leitakse andmetest, mitte ei kirjutata ette (nt random forest)Närvivõrgudneuronid kihtidesSüvaõpenärvivõrk, kus on mitu peidetud kihtiMLPtabelid (siin)CNNpildidLSTMaegread, tekstTransformerkeelemudelid
Süvaõpe on närvivõrkude osa: närvivõrgud, kus on mitu peidetud kihti. Meie MLP kahe peidetud kihiga on selle lihtsaim näide. Teised tüübid (CNN, LSTM, Transformer) kasutavad samu ehitusklotse, aga teistsuguseid kihte.

Meie MLP kahe peidetud kihiga on süvaõppe kõige lihtsam vorm. Süvaõpet kasutatakse eriti siis, kui andmed on keerulised (pildid, tekst, heli, aegread) ja neid on palju. Väikese tabeli puhul annavad sageli sama head tulemust ka sklearni tavalised mudelid.

TensorFlow

TensorFlow on Google'i loodud teek, mis arvutab tensoritega (arvude tabelitega) väga kiiresti, vajadusel graafikakaardil. Ta oskab ka ise leida tuletisi, mida närvivõrgu õppimiseks vaja läheb (nn-explainer'is nägid seda backward-sammuna). Seda vajab iga süvaõppe võrk, seepärast on TensorFlow kõige aluseks.

Keras

Keras on TensorFlow kasutajasõbralik pool. Kerases on valmis ehitusklotsid (kihid, kaofunktsioonid, parandajad) ja lihtne töövoog: mudel, compile, fit, predict. Sa kirjutad koodi Kerasega ja TensorFlow töötab selle all: ta teeb raske arvutuse ära. Võrdle autoga: Keras on rool ja pedaalid, TensorFlow on mootor. Sõitmiseks ei pea mootori ehitust teadma.

Kerasmudel, kihid, compile, fit()koodis:from tensorflow import kerasTensorFlowtensorid, tuletised, kiire arvutuskoodis:import tensorflow as tfRiistvaraprotsessor (CPU) või graafikakaart (GPU)tavaline arvuti sobib, GPU-d pole vajakasutabkasutab
Sina kirjutad Kerasega, TensorFlow teeb arvutuse ära. Võrdle autoga: Keras on rool ja pedaalid, TensorFlow on mootor.

Koodis näed seda nii:

RidaMis see on
import tensorflow as tfTensorFlow: tensorid ja tehted (osad 1 ja 2)
from tensorflow import kerasKeras: mudel ja treenimine (osad 3 kuni 7)
from tensorflow.keras import layersKerase kihid, näiteks layers.Dense(...)

Selles materjalis on peategelane Keras. Tensoreid õpime tundma selleks, et näha, mida mudel sisse võtab ja välja annab.

Miks TensorFlow, kui sklearnis on MLP olemas?

Kui oled kasutanud MLPClassifierit või MLPRegressorit, oled MLP-d juba treeninud. Sklearn teeb kõik ühe fit()-iga. Keras näeb välja sarnane, ka seal on fit(). Vahe on selles, et võrgu ehitad sa ise kihtidest ja kihte on palju rohkem kui sklearnis. Vaata sama mudelit kõrvuti.

sklearn
from sklearn.neural_network import MLPClassifier

clf = MLPClassifier(
    hidden_layer_sizes=(16, 16),
    activation="relu",
    solver="adam",
    learning_rate_init=0.01,
    batch_size=32,
    max_iter=50,
)
clf.fit(X_train, y_train)
clf.predict(X_test)
keras
model = keras.Sequential([
    keras.Input(shape=(2,)),
    layers.Dense(16, activation="relu"),
    layers.Dense(16, activation="relu"),
    layers.Dense(1, activation="sigmoid"),
])
model.compile(
    optimizer=keras.optimizers.Adam(
        learning_rate=0.01),
    loss="binary_crossentropy",
)
model.fit(X_train, y_train,
          epochs=50, batch_size=32)
model.predict(X_test)

See on sama võrk: 2 sisendit, kaks peidetud kihti 16 neuroniga, ReLU, Adam, partii 32 ja 50 epohhi. Täisnäite andmetel (osa 7) said mõlemad testi täpsuseks umbes 97–98%. Tulemus on peaaegu sama, aga Kerases näed ja valid ise iga kihi. Samamoodi saab Kerasega ehitada ka pildi-, teksti- ja aegridade võrke.

Mida tahad tehasklearn MLPTensorFlow / Keras
Väike tabel, kiire esimene mudelVäga hea, vähe koodiSaab, aga koodi on veidi rohkem
Pildid, tekst, heli, aegreadAinult täisühendatud kihidKonvolutsioon (CNN), RNN ja LSTM, Transformer jt kihid on valmis
Graafikakaart (GPU)Ainult protsessorKasutab GPU-d, kui see on olemas (Linux, WSL2, Colab)
Treeningu jälgimineloss_curve_, early_stoppinghistory ja callback'id (EarlyStopping, ModelCheckpoint, TensorBoard)
Oma kiht, oma kaofunktsioonPoleSaab teha
Mudeli viimine mujalejoblib, ainult Pythonis.keras fail; TensorFlow Lite (telefon), TensorFlow.js (brauser)
Eeltreenitud mudelidPolekeras.applications (pildimudelid)
Andmed, mis ei mahu mälluKõik peab korraga mälus olematf.data loeb osade kaupa
Millal kumba

Sklearn, kui andmed on väike tabel ja tahad MLP-d kiiresti võrrelda teiste mudelitega (otsustuspuu, random forest, logistiline regressioon) ühes Pipelineis. TensorFlow/Keras, kui võrk on suurem või keerulisem (pildid, tekst, aegread), kui tahad kasutada GPU-d või kui mudel peab jõudma telefoni, veebi või serverisse.

Teooria kordamiseks

Selles materjalis ei seleta me närvivõrgu teooriat uuesti. Kui neuron, kiht, aktivatsioon, kaofunktsioon või gradient descent vajavad kordamist, vaata enne nn-explainer'it. Siin näitame, kuidas need mõisted Kerases välja näevad.

nn-explainer'isKerases
Neuronlayers.Dense(1)
Kiht mitme neuronigalayers.Dense(16)
Sisendkeras.Input(shape=(2,))
Aktivatsioon (ReLU, sigmoid, softmax)activation="relu"
Kaofunktsioon (viga)loss="binary_crossentropy"
Gradient descent, õppesammoptimizer=keras.optimizers.Adam(learning_rate=0.01)
Forward, backward ja kaalude uuendaminemodel.fit(...) teeb need ise
Epohh ja partiiepochs=50, batch_size=32
Ennustaminemodel.predict(...)

0Alustus

Paigalda TensorFlow. Kõik siinsed näited töötavad tavalise protsessoriga, graafikakaarti pole vaja. Google Colabis on TensorFlow juba olemas.

terminal
pip install tensorflow

Kontrollime, et kõik töötab:

python
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

print(tf.__version__)
print(keras.__version__)
print(tf.config.list_physical_devices("GPU"))
väljund
2.21.0
3.15.1
[]
  • Esimene rida toob sisse TensorFlow, teine Kerase (osa, millega võrke ehitame) ja kolmas kihtide klassid.
  • Tühi loend [] tähendab, et graafikakaarti ei leitud. See on täiesti korras.
  • Kõik näited on katsetatud TensorFlow 2.21.0 ja Keras 3.15.1 peal. Sinu versioon võib olla teine, aga kood töötab Keras 3-ga (TensorFlow 2.16 ja uuem).
Pikad teated

TensorFlow prindib esimesel käivitusel sageli pikki teateid, näiteks oneDNN custom operations are on või hoiatuse, et GPU-d ei leitud. Need on teated, mitte vead. Võid need rahulikult ignoreerida.

Soovitus: ära kopeeri plokke korraga. Käivita iga plokk eraldi, vaata väljundit ja muuda mõnda arvu. Nii jääb rohkem meelde.

1Tensor

Tensor on TensorFlow põhiline andmetüüp. See on nagu NumPy massiiv: arvude tabel, millel on kuju (shape) ja tüüp (dtype). Kerase mudelid võtavad sisse ja annavad välja tensoreid. Lisaks oskab TensorFlow arvutada graafikakaardil ja leida tuletisi ise. Viimase teeb fit() sinu eest, seda me ise ei kirjuta.

python
a = tf.constant([1.0, 2.0, 3.0])
print(a)
print(a.shape)
print(a.dtype)

M = tf.constant([[1, 2, 3],
                 [4, 5, 6]])
print(M.shape)
print(M.dtype)
väljund
tf.Tensor([1. 2. 3.], shape=(3,), dtype=float32)
(3,)
<dtype: 'float32'>
(2, 3)
<dtype: 'int32'>
  • tf.constant(...) teeb tensori.
  • print(a) näitab kolme asja korraga: arve, kuju ja tüüpi.
  • Täisarvud on int32, komaga arvud float32. Kui tahad komaga arvu, kirjuta 1.0, mitte 1.
MõõtmeidNäideKujuMasinõppes
0tf.constant(3.5)()üks arv, nt kaofunktsiooni väärtus
1tf.constant([1., 2., 3.])(3,)ühe näite tunnused või kõik sildid
2tf.constant([[1, 2, 3], [4, 5, 6]])(2, 3)tabel: read on näited, veerud on tunnused
2andmetabel X(150, 4)iirise andmed: 150 lille, 4 tunnust
3.51.2.3.1234563 veergu2 rida5.13.51.40.24.93.01.40.24.73.21.30.24.63.11.50.2⋮4 tunnust (veerud)150 näidetskalaarkuju ()vektorkuju (3,)maatrikskuju (2, 3)andmetabelkuju (150, 4)
Kuju ütleb, mitu arvu on igas mõõtmes. Andmetabelis on üks rida üks näide (sinine rida: esimene iiris) ja üks veerg üks tunnus.
Kõige tähtsam reegel

Keras tahab sisendit alati kujuga (näidete arv, tunnuste arv). Esimene arv on, mitu rida (näidet) on, teine, mitu veergu (tunnust). Enamik vigu tuleb sellest, et kuju on vale.

Tensori loomine

python
print(tf.zeros((2, 3)))
print(tf.ones((2, 2)))
print(tf.range(5))

tf.random.set_seed(0)                    # et juhuslikud arvud korduksid
print(tf.random.normal((2, 2)))
väljund
tf.Tensor(
[[0. 0. 0.]
 [0. 0. 0.]], shape=(2, 3), dtype=float32)
tf.Tensor(
[[1. 1.]
 [1. 1.]], shape=(2, 2), dtype=float32)
tf.Tensor([0 1 2 3 4], shape=(5,), dtype=int32)
tf.Tensor(
[[ 1.5110626   0.42292204]
 [-0.41969493 -1.0360372 ]], shape=(2, 2), dtype=float32)

tf.zeros ja tf.ones teevad nullidest ja ühtedest tabeli. tf.range loeb nullist peale. tf.random.normal annab juhuslikke arve, seega on sinu arvud teised.

NumPy ja tensor

python
import numpy as np

arr = np.array([[1.0, 2.0], [3.0, 4.0]])
t = tf.constant(arr)
print(t.dtype)                           # float64, sest NumPy kasutab vaikimisi seda

t32 = tf.cast(t, tf.float32)             # muudame tüübi
print(t32.dtype)

print(t32.numpy())                       # tagasi NumPy massiiviks
väljund
<dtype: 'float64'>
<dtype: 'float32'>
[[1. 2.]
 [3. 4.]]

NumPy kasutab vaikimisi float64, Keras eelistab float32. Kui andmed on NumPy massiivis, pole häda: model.fit(X, y) muudab tüübi ise. Aga TensorFlow oma tehetes ei luba float32 ja float64 segada. Siis aitab tf.cast.

2Kujud ja tehted

Siin on tensorite kõige tavalisemad võtted. Enamik neist on sul NumPy-st juba tuttavad.

Kuju muutmine

Tabeli saab teisiti kokku panna: samad arvud, uus kuju. Arv -1 tähendab „arvuta see ise“.

python
x = tf.range(6)
print(x)
print(tf.reshape(x, (2, 3)))
print(tf.reshape(x, (3, -1)).shape)      # -1: TensorFlow arvutab selle arvu ise
väljund
tf.Tensor([0 1 2 3 4 5], shape=(6,), dtype=int32)
tf.Tensor(
[[0 1 2]
 [3 4 5]], shape=(2, 3), dtype=int32)
(3, 2)

Valimine

Indekseerimine töötab nagu NumPy-s. M[0] on esimene rida, M[:, 1] on teine veerg.

python
M = tf.constant([[1, 2, 3],
                 [4, 5, 6]])

print(M[0])           # esimene rida
print(M[:, 1])        # teine veerg
print(M[1, 2])        # üks arv
väljund
tf.Tensor([1 2 3], shape=(3,), dtype=int32)
tf.Tensor([2 5], shape=(2,), dtype=int32)
tf.Tensor(6, shape=(), dtype=int32)

Tehted

Tehted käivad elemendiviisi: a + b liidab esimese arvu esimesega, teise teisega ja nii edasi. Kokkuvõtted algavad sõnaga reduce_ (suruvad palju arve üheks).

python
a = tf.constant([1.0, 2.0, 3.0])
b = tf.constant([10.0, 20.0, 30.0])

print(a + b)
print(a * 2)
print(tf.square(a))
print(tf.reduce_sum(a))
print(tf.reduce_mean(a))
väljund
tf.Tensor([11. 22. 33.], shape=(3,), dtype=float32)
tf.Tensor([2. 4. 6.], shape=(3,), dtype=float32)
tf.Tensor([1. 4. 9.], shape=(3,), dtype=float32)
tf.Tensor(6.0, shape=(), dtype=float32)
tf.Tensor(2.0, shape=(), dtype=float32)

Telg (axis): mida mööda kokku võtta

Tabelis saab arve kokku liita kahte moodi: ridade kaupa või veergude kaupa. Seda ütleb axis.

python
x = tf.constant([[1.0, 2.0],
                 [3.0, 4.0]])

print(tf.reduce_sum(x, axis=0))    # read liidetakse kokku
print(tf.reduce_sum(x, axis=1))    # veerud liidetakse kokku
väljund
tf.Tensor([4. 6.], shape=(2,), dtype=float32)
tf.Tensor([3. 7.], shape=(2,), dtype=float32)
1.2.3.4.4.6.axis=0tf.reduce_sum(x, axis=0)read liidetakse kokku(2, 2) → (2,)1.2.3.4.3.7.axis=1tf.reduce_sum(x, axis=1)veerud liidetakse kokku(2, 2) → (2,)
Nool näitab, mida mööda liidetakse. Telg, mille nimetad, kaob ära: kahest mõõtmest jääb üks.

axis=0 liidab read kokku (alles jääb üks rida), axis=1 liidab veerud kokku (alles jääb üks veerg).

Maatrikskorrutis

Pisinäide. Kolm õpilast, igaühel kaks tunnust. Neuron korrutab tunnused kaaludega, liidab kokku ja lisab nihke. Kõigi õpilaste jaoks korraga teeb seda üks tehe, @:

python
X = tf.constant([[1.0, 4.0],
                 [2.0, 3.0],
                 [0.0, 5.0]])          # 3 õpilast, 2 tunnust
W = tf.constant([[2.0],
                 [3.0]])               # kaalud, kuju (2, 1)
b = tf.constant([1.0])                 # nihe

print(X @ W + b)                       # kuju (3, 1)
väljund
tf.Tensor(
[[15.]
 [14.]
 [16.]], shape=(3, 1), dtype=float32)
1.4.2.3.0.5.Xkuju (3, 2)3 õpilast, 2 tunnust@2.3.Wkuju (2, 1)kaalud+1.bnihe, kuju (1,)=15.14.16.ŷkuju (3, 1)1 ennustus õpilase kohtaesimene õpilane: 1·2 + 4·3 + 1 = 15Iga rida korrutataksekaaludega ja liidetaksekokku, siis lisatakse b.Sisemised arvud (2 ja 2)peavad võrdsed olema.
X @ W + b arvutab kõigi õpilaste ennustused korraga. Oranž raam on esimene õpilane.

Esimese õpilase vastus on 1·2 + 4·3 + 1 = 15. Tähtis on, et sisemised kujud klapiksid: (3, 2) @ (2, 1) annab (3, 1). Just seda teeb Kerase Dense kiht (osa 4).

Broadcasting: standardiseerimine ühe reaga

Mudelile sobivad paremini ühesuurused arvud. Siin on üks tunnus väike (1, 2, 3) ja teine suur (100, 200, 300). Standardiseerimine võtab iga veeru keskmise maha ja jagab standardhälbega.

python
X = tf.constant([[1.0, 100.0],
                 [2.0, 200.0],
                 [3.0, 300.0]])        # 3 rida, 2 tunnust

mu = tf.reduce_mean(X, axis=0)         # iga veeru keskmine, kuju (2,)
sigma = tf.math.reduce_std(X, axis=0)

print(mu)
print((X - mu) / sigma)                # mu ja sigma "venivad" igale reale
väljund
tf.Tensor([  2. 200.], shape=(2,), dtype=float32)
tf.Tensor(
[[-1.2247448 -1.2247449]
 [ 0.         0.       ]
 [ 1.2247448  1.2247449]], shape=(3, 2), dtype=float32)
1.100.2.200.3.300.−2.200.2.200.2.200.=-1.-100.0.0.1.100.Xkuju (3, 2)mukuju (2,) → venitatakseX − mukuju (3, 2)iga rida saabsama mu rea(kriipsjoon onmõtteline koopia)
Kriipsjoonega read on mu mõtteline koopia, mille TensorFlow iga rea jaoks teeb.

X on kujuga (3, 2), mu on kujuga (2,). Kujud ei klapi, aga TensorFlow „venitab“ mu rea kõigile ridadele ja lahutab elemendiviisi. Jagamine sigma-ga käib samamoodi. Sellepärast ei pea sa ise tsüklit kirjutama.

Muudetavad tensorid ja suurima arvu koht

Tavalist tensorit ei saa muuta. Mudeli kaalud on aga muutuvad, seepärast on need tf.Variable'id. Neid sa ise tavaliselt ei loo, Keras teeb seda sinu eest.

python
try:
    M[0, 0] = 99                       # tavalist tensorit muuta ei saa
except TypeError as e:
    print(e)

v = tf.Variable([1.0, 2.0, 3.0])       # Variable on muudetav
v.assign_add([10.0, 10.0, 10.0])
print(v.numpy())
väljund
'tensorflow.python.framework.ops.EagerTensor' object does not support item assignment
[11. 12. 13.]

Klassifitseerimisel tuleb tihti välja valida suurima tõenäosusega klass. Seda teeb tf.argmax:

python
probs = tf.constant([[0.1, 0.7, 0.2],
                    [0.8, 0.1, 0.1]])

print(tf.argmax(probs, axis=1))        # kus on iga rea suurim arv?
väljund
tf.Tensor([1 0], shape=(2,), dtype=int64)

Esimese rea suurim arv (0.7) on kohal 1, teise rea suurim arv (0.8) kohal 0.

Meelespea
  • Kuju kirjutatakse (read, veerud). Keras tahab andmeid kujuga (näidete arv, tunnuste arv).
  • tf.constant on muutumatu, tf.Variable on muudetav.
  • tf.reduce_sum(x, axis=0): nimetatud telg kaob ära.
  • X @ W + b on neuronite arvutus kõigile näidetele korraga.
  • Tehetes ei tohi segada int32, float32 ja float64. Muuda tf.castiga.

3Esimene mudel: üks neuron

Alustame kõige väiksema võrguga, mis üldse õpib: ühe neuroniga. See on sama sirge ŷ = w · x + b, mille nn-explainer'is neuronina nägid. Seekord ei arvuta me midagi ise: Keras teeb kogu treenimise ära. Meie ülesanne on öelda kolm asja: milline mudel, kuidas viga mõõta ja kuidas seda parandada.

3.1 Ülesanne: andmed X ja y

Neli õpilast kirjutasid testi. Teame, mitu tundi igaüks õppis ja mitu punkti ta sai:

ÕpilaneÕppetunnid (X)Punktid (y)
A13
B25
C37
D49
  • X on see, mida me teame: sisend ehk tunnus.
  • y on see, mida tahame ennustada: sihtmuutuja.

Küsimus: mitu punkti saab õpilane, kes õppis 5 tundi? Vaata tabelit: iga lisatund annab 2 punkti juurde ja 0 tunniga oleks 1 punkt. Reegel on y = 2·X + 1.

Meie nägime seda silmaga. Mudel peab selle ise leidma, ja seda me nimetamegi õppimiseks. Õige reegli teame ette ainult selleks, et lõpus kontrollida, kas mudel leidis selle.

python
X = tf.constant([[1.0], [2.0], [3.0], [4.0]])   # õppetunnid, kuju (4, 1)
y = tf.constant([[3.0], [5.0], [7.0], [9.0]])   # punktid, kuju (4, 1)

print(X.shape, y.shape)
väljund
(4, 1) (4, 1)

Kuju (4, 1) tähendab 4 rida (õpilast) ja 1 veergu (tunnust). Keras tahab sisendi alati kujuga (näidete arv, tunnuste arv), ka siis, kui tunnus on ainult üks.

036912012345+1 tund+2 punkti = wABCDb = 1 (0 tundi → 1 punkt)y (punktid)X (õppetunnid)ŷ = w · X + bw = 2tõus: iga tund annab2 punkti juurdeb = 1nihe: punktid, kuiüldse ei õppinud
Sinised punktid on neli õpilast. Oranž sirge on see, mille mudel peab ise leidma. w on tõus: üks tund juurde annab 2 punkti juurde (lilla kolmnurk). b on koht, kus sirge lõikab y-telge: 0 tunniga on 1 punkt.

3.2 Mudel: üks neuron

Mudel on lihtne valem, mis teeb X-ist ennustuse:

ŷ = w · X + b

Märk ŷ (loe „y-katus“, koodis ennustus) tähendab ennustust. Nii ei aeta seda segi tegeliku y-ga.

  • w (weight, kaal) ütleb, mitu punkti annab üks lisatund. See on sirge tõus.
  • b (bias, nihe) ütleb, mitu punkti saab õpilane, kes üldse ei õppinud (X = 0).

Neid kahte arvu me ei tea. Mudel peab need andmetest ise leidma. Neid arve nimetatakse mudeli parameetriteks.

Kerases ehitatakse mudel kihtidest. Meil on üks kiht ja selles üks neuron:

python
keras.utils.set_random_seed(1)          # et algväärtused oleksid samad mis siin

model = keras.Sequential([
    keras.Input(shape=(1,)),            # sisend: 1 tunnus (õppetunnid)
    layers.Dense(1),                    # 1 neuron: ŷ = w · x + b
])

model.summary()
väljund
Model: "sequential"
┌─────────────────────────────────┬────────────────────────┬───────────────┐
│ Layer (type)                    │ Output Shape           │       Param # │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense (Dense)                   │ (None, 1)              │             2 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
 Total params: 2 (8.00 B)
 Trainable params: 2 (8.00 B)
 Non-trainable params: 0 (0.00 B)
  • keras.Sequential([...]): mudel, kus kihid on üksteise järel, sisendist väljundini.
  • keras.Input(shape=(1,)): ütleb, et üks näide on 1 arv (õppetunnid). See ei ole kiht, ainult kuju kirjeldus.
  • layers.Dense(1): üks neuron, mis arvutab w · x + b. Dense tähendab, et iga sisend on ühendatud iga neuroniga.
  • model.summary(): mudeli kokkuvõte.

Kokkuvõttes tähendab (None, 1), et väljundis on 1 arv iga näite kohta. None asemel võib olla ükskõik mitu näidet: mudel sobib nii ühe õpilase kui tuhande õpilase jaoks. Param # on 2, sest parameetreid on kaks: w ja b.

x3sisendõppetunnid(None, 1)w = 2kaalΣDense(1)üks neuron+ b = 1niheŷ7väljundennustus(None, 1)ŷ = w·x + b = 2·3 + 1 = 7
Üks neuron korrutab sisendi kaaluga, liidab nihke juurde ja annab ennustuse. Kui w = 2 ja b = 1, siis õpilane, kes õppis 3 tundi, saab 2·3 + 1 = 7 punkti. Kerases on see Dense(1).

Mudel ei ole veel midagi õppinud. Keras valib w alguses juhuslikult ja paneb b = 0. Vaatame neid ja mudeli esimesi ennustusi:

python
w, b = model.layers[0].get_weights()
print("w =", w)
print("b =", b)

print(model.predict(X, verbose=0))      # mudel ei ole veel midagi õppinud
väljund
w = [[-0.83484906]]
b = [0.]
[[-0.83484906]
 [-1.6696981 ]
 [-2.504547  ]
 [-3.3393962 ]]

Ennustused on kaugel õigetest vastustest (3, 5, 7, 9). Sinu w on teine, sest see on juhuslik.

3.3 Viga ja parandaja: compile

Enne treenimist tuleb Kerasele öelda kaks asja:

ArgumentMida see tähendab
loss="mse"Viga: iga õpilase ennustuse ja õige vastuse vahe võetakse ruutu ja neist võetakse keskmine (mean squared error). Mida väiksem, seda parem mudel.
optimizer=SGD(learning_rate=0.02)Parandaja: muudab w-d ja b-d nii, et viga väheneks. learning_rate on õppesamm: kui suure sammu see iga kord teeb.
python
model.compile(
    optimizer=keras.optimizers.SGD(learning_rate=0.02),   # kuidas w ja b-d parandatakse
    loss="mse",                                           # kuidas viga mõõdetakse
)

print(round(model.evaluate(X, y, verbose=0), 2))   # viga enne treenimist
väljund
75.45

Praegu on viga umbes 75. Õppimine tähendab: muuda w-d ja b-d nii, et see arv väheneks. Kui kaofunktsioon või gradient descent on veel udused, vaata nn-explainer'it.

3.4 Õppimine: fit

Nüüd treenime. Epohh tähendab, et mudel on kõik andmed korra läbi vaadanud ja kaale natuke parandanud. Teeme neid 500.

python
history = model.fit(X, y, epochs=500, verbose=0)

loss = history.history["loss"]
for e in (1, 2, 3, 5, 10, 20, 500):
    print(f"epohh {e:3d}   viga = {loss[e - 1]:.4f}")
väljund
epohh   1   viga = 75.4470
epohh   2   viga = 33.4638
epohh   3   viga = 14.8426
epohh   5   viga = 2.9201
epohh  10   viga = 0.0503
epohh  20   viga = 0.0002
epohh 500   viga = 0.0000
  • model.fit(X, y, epochs=500): treeni 500 epohhi. verbose=0 tähendab, et Keras ei prindi iga epohhi.
  • history.history["loss"] on loend: viga igal epohhil. Prindime sellest mõne.

Viga langeb 75-lt peaaegu nullini juba paarikümne epohhiga. Vaatame, mis w ja b-st sai, ja kasutame mudelit:

python
w, b = model.layers[0].get_weights()
print(f"w = {w[0, 0]:.3f}   b = {b[0]:.3f}")

print(model.predict(tf.constant([[5.0]]), verbose=0))   # õpilane, kes õppis 5 tundi
väljund
w = 2.001   b = 0.998
[[11.001116]]

w ja b on peaaegu täpselt 2 ja 1. Mudel leidis ise üles reegli y = 2·X + 1, mida me talle ei öelnud. Õpilane, kes õppis 5 tundi, saab umbes 11 punkti.

-3036912012345epohh 0epohh 1epohh 3epohh 10epohh 500X10010.010.0001020406075viga (log-skaala)epohh
Vasakul: sirge asend mõne epohhi järel. Algul on sirge vales kohas (negatiivne tõus), kümne epohhiga on ta juba andmete peal. Paremal: viga esimese 60 epohhi jooksul. Skaala on logaritmiline, et näha nii suuri kui ka väga väikeseid arve. Sinu arvud on veidi teised, sest algväärtused on juhuslikud.

3.5 Mida fit() sees teeb

Ühe epohhi jooksul teeb Keras neli sammu ja kordab neid:

  1. Ennustus (forward): mudel arvutab ŷ = w · x + b kõigile õpilastele.
  2. Viga: võrdleb ennustusi õigete vastustega.
  3. Tuletised (backward): arvutab, kummale poole w-d ja b-d keerata, et viga väheneks.
  4. Samm: parandaja liigutab w-d ja b-d natuke selles suunas. Sammu suurus on learning_rate.
1ennustusŷ = model(X)forward2vigaloss(ŷ, y)kui palju eksib3tuletisedkuhu poole keeratabackward4sammoptimizer muudab w ja bkaalud uuenevadüks epohh↺ korda epochs kordaKõik see on model.fit() sees
Neli sammu, mida fit() sinu eest kordab.

Need on samad sammud, mida nn-explainer'is samm-sammult nägid. Kerases ei kirjuta sa neid ise: fit() teeb need sinu eest.

3.6 Mida Keras treenimise ajal prindib

Seni panime verbose=0. Vaatame, mida Keras tavaliselt näitab. Paneme mudeli ehituse ühte funktsiooni, et saaksime sama mudelit mitu korda uuesti teha, ja treenime 3 epohhi (verbose=2 prindib ühe rea epohhi kohta):

python
def make_model(lr):
    m = keras.Sequential([keras.Input(shape=(1,)), layers.Dense(1)])
    m.compile(optimizer=keras.optimizers.SGD(learning_rate=lr), loss="mse")
    return m

make_model(0.02).fit(X, y, epochs=3, verbose=2)
väljund
Epoch 1/3
1/1 - 0s - 262ms/step - loss: 30.6848
Epoch 2/3
1/1 - 0s - 67ms/step - loss: 13.6233
Epoch 3/3
1/1 - 0s - 74ms/step - loss: 6.0556
  • Epoch 1/3: epohh 1 kolmest.
  • 1/1: üks partii. Kõik neli õpilast mahtusid korraga, seega on epohhis üks samm. Suuremate andmetega on partiisid palju (osa 7).
  • loss: viga selle epohhi jooksul. See langeb iga epohhiga.
  • Ajad (ms/step) on sinu arvutis teised.

3.7 Õppesamm (learning_rate)

Õppesamm ütleb, kui suure sammu parandaja iga kord teeb. Proovime kolme väärtust, igaühe jaoks uus mudel ja 100 epohhi:

python
for lr in (0.001, 0.02, 0.3):
    m = make_model(lr)
    h = m.fit(X, y, epochs=100, verbose=0)
    print(f"lr = {lr:<6}   viga pärast 100 epohhi = {h.history['loss'][-1]:.4f}")
väljund
lr = 0.001    viga pärast 100 epohhi = 1.4628
lr = 0.02     viga pärast 100 epohhi = 0.0038
lr = 0.3      viga pärast 100 epohhi = nan
  • 0.001 on liiga väike: mudel õpib, aga nii aeglaselt, et 100 epohhi pärast on viga alles umbes 1.5.
  • 0.02 on sobiv: viga on peaaegu null.
  • 0.3 on liiga suur: iga samm hüppab miinimumist üle ja viga kasvab üha suuremaks, kuni tuleb nan (not a number). Kui näed nani, vähenda learning_rate.

3.8 Keras'e töövoog

Iga Kerase projekt käib samade nelja sammuga:

1MudelSequential([...])kihid üksteise järel2compilecompile(loss, optimizer)viga ja parandaja3fitfit(X, y, epochs=…)treenimine4predictpredict(X_uus)ennustamineandmed X ja yuus XTeha tuleb neli asja. Treenimise ise (kordused, tuletised, kaalude uuendus) teeb Keras.
Kerase töövoog: mudel, compile, fit, predict. Hindamiseks on lisaks evaluate.
Meelespea
  • Mudel: keras.Sequential([keras.Input(...), layers.Dense(...)]). Parameetrid w ja b loob Keras ise.
  • compile: loss ütleb, kuidas viga mõõta, optimizer ütleb, kuidas seda vähendada.
  • fit: model.fit(X, y, epochs=...) kordab neid nelja sammu (ennustus, viga, tuletised, samm).
  • predict: model.predict(X_uus) annab ennustused. X peab olema kujuga (näidete arv, tunnuste arv).
  • Kui loss on nan, on õppesamm liiga suur.

Ühe neuroniga saab joonistada ainult sirge. Järgmises osas paneme neuroneid kõrvuti ja kihte üksteise taha.

4Dense kiht ja aktivatsioon

Üks neuron joonistab sirge. Päris ülesannetes on vaja mitut neuronit kõrvuti ja mitut kihti üksteise taga. Esimese jaoks on Kerases Dense kiht.

4.1 Dense: mitu neuronit kõrvuti

layers.Dense(3) on 3 neuronit kõrvuti. Iga neuron vaatab kõiki sisendeid. Kui sisendeid on 4, on igal neuronil 4 kaalu ja 1 nihe. Parameetreid on seega 3·4 + 3 = 15.

python
layer = layers.Dense(3)                 # 3 neuronit kõrvuti
X10 = tf.random.normal((10, 4))         # 10 näidet, 4 tunnust

out = layer(X10)                        # esimesel kutsel loob Keras kaalud
print(out.shape)
print(layer.kernel.shape)               # W: (sisse, välja)
print(layer.bias.shape)
väljund
(10, 3)
(4, 3)
(3,)
  • (10, 3): 10 näidet, igaühe kohta 3 väljundit (üks iga neuroni kohta).
  • layer.kernel on kaalude tabel W, kuju (4, 3): 4 sisendit, 3 neuronit. Keras hoiab kaalusid kujuga (sisse, välja).
  • layer.bias on nihked b, üks iga neuroni kohta.
w₁₁w₂₁w₃₁w₄₁x₁x₂x₃x₄h₁h₂h₃Σ+b₁sisend(None, 4)(None, 3)väljundkernel (W): kuju (4, 3)→ 12 kaalu (jooned)bias (b): kuju (3,)→ 3 nihetkokku 15 parameetrith₁ = w₁₁·x₁ + w₂₁·x₂ + w₃₁·x₃ + w₄₁·x₄ + b₁
Dense(3) neljal sisendil: iga väljund on kõigi nelja sisendi kaalutud summa pluss nihe. Iga joon on üks kaal. Lillad jooned on väljundi h₁ kaalud, kollases kastis on sama valemina.

Dense kiht ei ole midagi salapärast. See on lihtsalt see X @ W + b, mille osas 2 tensoritega ise kirjutasime. Kontrollime:

python
import numpy as np

W, b = layer.get_weights()
print(np.allclose(X10.numpy() @ W + b, out.numpy()))
väljund
True

True tähendab, et käsitsi arvutatud X @ W + b ja kihi väljund on samad.

4.2 Aktivatsioon: murdja kihtide vahel

Pärast neuronite arvutust võib panna väikese „murdja“, mida nimetatakse aktivatsioonifunktsiooniks. Kerases antakse see kihile argumendiga activation=. Kolm tavalisemat:

AktivatsioonMida teebKus kasutatakse
"relu"Negatiivne arv muutub nulliks, positiivne jääb samaksPeidetud kihtides
"sigmoid"Surub arvu vahemikku 0 kuni 1, nagu tõenäosusKahe klassi väljundis
"softmax"Teeb arvude rea tõenäosusteks, mis annavad kokku 1Mitme klassi väljundis
None (vaikimisi)Ei tee midagi, arv jääb samaksRegressiooni väljundis

Vaatame neid väikeste arvudega:

python
z = tf.constant([-2.0, -0.5, 0.0, 1.5])

print(tf.nn.relu(z).numpy())                 # negatiivsed saavad nulliks
print(tf.nn.sigmoid(z).numpy())              # kõik arvud 0 ja 1 vahele

logits = tf.constant([[2.0, 0.5]])
print(tf.nn.softmax(logits).numpy())         # tõenäosused, summa on 1
väljund
[0.  0.  0.  1.5]
[0.11920294 0.37754068 0.5        0.81757444]
[[0.81757444 0.18242551]]
  • ReLU: -2, -0.5 ja 0 said nulliks, 1.5 jäi samaks.
  • Sigmoid: kõik arvud on nüüd 0 ja 1 vahel. Suur arv saab 1-le lähedase, väga negatiivne 0-le lähedase.
  • Softmax: kaks arvu (2.0 ja 0.5) said tõenäosusteks 0.82 ja 0.18. Nende summa on 1. Suurem arv sai suurema tõenäosuse.
-1123-3-2-1123−2 ja −0.5 → 01.5 → 1.5relu(z) = max(0, z)zDense → Denseikka sirgeDense(relu) → Densemurdekohad → kõver
Vasakul: ReLU paneb negatiivsed arvud nulliks ja jätab positiivsed samaks. Paremal: ilma aktivatsioonita on mitu Dense kihti kokku ikka sirge. ReLU-ga tekivad murdekohad ja paljudest murdekohtadest saab kõvera piiri.

Miks aktivatsiooni üldse vaja on? nn-explainer näitab seda pildil: ilma selleta on mitu kihti kokku ikka üks sirge. Järgmises osas näed seda ka Kerases oma silmaga.

5Mitu kihti: MLP

MLP (multilayer perceptron) on võrk, kus Dense kihid on üksteise taga ja nende vahel on aktivatsioon. Vaatame päris ülesandel, miks seda vaja on.

5.1 Ülesanne: kaks poolkuud

Andmed on kaks poolkuud (make_moons). Igal punktil on kaks tunnust ja klass 0 või 1. Jagame andmed treeninguks ja testiks ning skaleerime need.

python
from sklearn.datasets import make_moons
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

X, y = make_moons(n_samples=1000, noise=0.2, random_state=42)
print(X.shape, y.shape)
print(X[:3].round(2))
print(y[:3])

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)      # skaleerimise õpime ainult treeningandmetelt
X_test = scaler.transform(X_test)

print(X_train.shape, X_test.shape)
väljund
(1000, 2) (1000,)
[[-0.11  0.52]
 [ 1.14 -0.34]
 [ 0.8  -0.01]]
[1 1 1]
(800, 2) (200, 2)
  • (1000, 2) tähendab 1000 punkti, igaühel 2 tunnust. Sildid y on 0 või 1.
  • train_test_split paneb 200 punkti testiks kõrvale. Nendega mudelit ei treenita. Nendega kontrollime lõpus, kui hästi mudel uute andmetega töötab.
  • StandardScaler toob tunnused samasse suurusjärku (keskmine 0, standardhälve 1). Närvivõrk õpib skaleeritud andmetega palju kiiremini. Skaleerimise õpime ainult treeningandmetelt (fit_transform) ja testile rakendame sama (transform).
-2-2-1-1001122x₁x₂klass 0 (y = 0)klass 1 (y = 1)Kaks poolkuud.Igal punktil on kaks tunnust(x₁ ja x₂, skaleeritud).Ühe sirgega neidkaheks ei lõika.Pildil on 240 treeningpunkti 800-st.
Meie ülesanne: eralda sinised punktid oranžidest. Punktid on päris treeningandmetest (skaleeritud).

5.2 Katse: üks neuron, kaks kihti, kaks kihti ReLU-ga

Treenime kolm mudelit samadel andmetel ja vaatame testi täpsust. Et ei peaks sama koodi kolm korda kirjutama, paneme mudeli ehituse funktsiooni build sisse:

python
def build(hidden, activation):
    model = keras.Sequential([keras.Input(shape=(2,))])
    for units in hidden:
        model.add(layers.Dense(units, activation=activation))
    model.add(layers.Dense(1, activation="sigmoid"))
    model.compile(optimizer=keras.optimizers.Adam(learning_rate=0.01),
                  loss="binary_crossentropy", metrics=["accuracy"])
    return model

variants = [
    ("üks neuron",                       [],       None),
    ("kaks kihti, aktivatsioonita",      [16, 16], None),
    ("kaks kihti ja ReLU",               [16, 16], "relu"),
]

models = {}
for name, hidden, act in variants:
    keras.utils.set_random_seed(0)
    m = build(hidden, act)
    m.fit(X_train, y_train, epochs=50, batch_size=32, verbose=0)
    acc = m.evaluate(X_test, y_test, verbose=0)[1]
    models[name] = m
    print(f"{name:30s} testi täpsus {acc:.1%}")
väljund
üks neuron                     testi täpsus 86.0%
kaks kihti, aktivatsioonita    testi täpsus 85.5%
kaks kihti ja ReLU             testi täpsus 96.5%
  • model.add(...) lisab mudelile kihi juurde.
  • Viimane kiht on alati Dense(1, activation="sigmoid"): üks neuron, mis annab tõenäosuse, et punkt on klassi 1.
  • activation=None tähendab, et peidetud kihtidel pole aktivatsiooni.

Tulemused:

  • Üks neuron (86.0%): tõmbab ühe sirge. Poolkuude vahele sirget ei saa.
  • Kaks kihti ilma aktivatsioonita (85.5%): peaaegu sama. Mitu Dense kihti järjest ilma aktivatsioonita on kokku ikka üks sirge, nagu nn-explainer näitas. Sellest pole kasu.
  • Kaks kihti ja ReLU (96.5%): ReLU murrab sirge ja paljudest murdekohtadest saab kõver piir.
üks neuronsirge piir · testi täpsus 86.0%kaks kihti ja ReLUkõver piir · testi täpsus 96.5%
Taustavärv näitab, millise klassi mudel selles kohas ennustaks. Punktid on 200 testipunkti, mida mudel treenimisel ei näinud.

5.3 MLP kokku

Sama võrk, kirjutatud nii, nagu sa seda tavaliselt teed:

python
keras.utils.set_random_seed(0)
model = keras.Sequential([
    keras.Input(shape=(2,)),                     # 2 tunnust
    layers.Dense(16, activation="relu"),         # 1. peidetud kiht
    layers.Dense(16, activation="relu"),         # 2. peidetud kiht
    layers.Dense(1, activation="sigmoid"),       # väljund: tõenäosus 0...1
])

model.summary()
väljund
Model: "sequential"
┌─────────────────────────────────┬────────────────────────┬───────────────┐
│ Layer (type)                    │ Output Shape           │       Param # │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense (Dense)                   │ (None, 16)             │            48 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_1 (Dense)                 │ (None, 16)             │           272 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_2 (Dense)                 │ (None, 1)              │            17 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
 Total params: 337 (1.32 KB)
 Trainable params: 337 (1.32 KB)
 Non-trainable params: 0 (0.00 B)
  • Kaks peidetud kihti (Dense(16, activation="relu")), igaühes 16 neuronit.
  • Viimane kiht on väljund: üks neuron, mis annab tõenäosuse (sigmoid).
  • Output Shape näitab kuju pärast iga kihti. Param # näitab parameetrite arvu: 2·16 + 16 = 48, 16·16 + 16 = 272 ja 16·1 + 1 = 17. Kokku 337.
relurelusigmoidx₁x₂⋮⋮pDense(16)48 param.Dense(16)272 param.Dense(1)17 param.(None, 2)Input(None, 16)peidetud 1(None, 16)peidetud 2(None, 1)tõenäosus
Võrk 2 → 16 → 16 → 1. Joonisel on kummastki peidetud kihist näha viis neuronit 16-st. Kriipsjoonega kast näitab aktivatsiooni, all on kuju pärast iga kihti (None on näidete arv).
Reegel

Esimese kihi sisendi kuju ütled ise: keras.Input(shape=(tunnuste arv,)). Iga järgmise kihi sisendi arvutab Keras ise eelmise kihi neuronite arvust. Viimase kihi neuronite arv sõltub ülesandest.

5.4 Viimane kiht ja kaofunktsioon sõltuvad ülesandest

ÜlesanneViimane kihtKaofunktsioon (loss)Sildid y
Regressioon (arv)Dense(1)"mse"arvud, kuju (N,)
Kaks klassiDense(1, activation="sigmoid")"binary_crossentropy"0 või 1, kuju (N,)
K klassiDense(K, activation="softmax")"sparse_categorical_crossentropy"täisarvud 0…K−1, kuju (N,)

Pisinäide. Kolme klassi puhul annab viimane kiht kolm tõenäosust, näiteks [0.1, 0.7, 0.2]. Ennustatud klass on suurima tõenäosusega koht, siin klass 1 (seda leiab argmax, osa 2). Õige silt oleks sel juhul täisarv 1.

Kui sildid on one-hot kujul või tahad logiteid

Kui sildid on kirjutatud kujul [0, 1, 0] (one-hot), kasuta loss="categorical_crossentropy". Sellisel juhul on sildid kujuga (N, K).

Viimase kihi aktivatsiooni võib ka ära jätta (Dense(K)) ja anda kaofunktsioonile teada: keras.losses.SparseCategoricalCrossentropy(from_logits=True). Siis ei anna mudel tõenäosusi, vaid „toorarve“ (logiteid). Algajale on lihtsam esimene variant.

6Mänguväljak

Siin treenib brauseris päris väike MLP samade sammudega, mida fit() teeb. Taustavärv näitab, millise klassi võrk igas punktis ennustaks. Muuda seadeid ja vaata, kuidas piir muutub. Paremal on sama mudel Kerase koodina: seadistus ja kood käivad kokku.

klass 0klass 1
epohh0
loss–
täpsus–
param.–
sama Kerases
  1. Vali aktivatsiooniks Puudub. Piir jääb sirgeks, ükskõik kui palju kihte või neuroneid lisad. See ongi põhjus, miks ReLU on vajalik.
  2. Vali Ring ringi sees, üks peidetud kiht ja 2 neuronit. Võrk ei suuda ringi ümbritseda (täpsus jääb umbes 87% peale). Lisa neuroneid ükshaaval ja leia, millal hakkab välja tulema.
  3. Vali Neli nurka (XOR) ja pane õppesammuks 0.3. Loss kõigub ega lange ühtlaselt. Pane tagasi 0.01: loss langeb rahulikult. Pane 0.001: õpib, aga aeglaselt.
  4. Võrdle partii suurusi 8 ja 200. Väikese partiiga tehakse ühes epohhis palju samme (kaalusid uuendatakse tihti), suure partiiga ainult üks. Sama epohhide arvu juures õpib väike partii tavaliselt kiiremini.

Mänguväljaku võrk on kirjutatud puhtas JavaScriptis ja ei kasuta TensorFlow'd. Ta arvutab sama, mida Keras: Glorot-algväärtused kaaludele, Adam ja binaarne risti-entroopia. Sellepärast näed siin samu mõjusid, mida päris Kerases.

7Täisnäide: poolkuud algusest lõpuni

Nüüd paneme kõik kokku: andmed, mudel, treenimine, hindamine ja ennustamine. Selle ühe ploki võid kopeerida oma märkmikusse ja käivitada.

python
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
from sklearn.datasets import make_moons
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

# 1. Andmed
X, y = make_moons(n_samples=1000, noise=0.2, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)

# 2. Mudel
keras.utils.set_random_seed(42)
model = keras.Sequential([
    keras.Input(shape=(2,)),
    layers.Dense(16, activation="relu"),
    layers.Dense(16, activation="relu"),
    layers.Dense(1, activation="sigmoid"),
])

# 3. Compile: viga ja parandaja
model.compile(
    optimizer=keras.optimizers.Adam(learning_rate=0.01),
    loss="binary_crossentropy",
    metrics=["accuracy"],
)

# 4. Treenimine (20% treeningandmetest jääb kõrvale kontrolliks)
history = model.fit(
    X_train, y_train,
    epochs=50,
    batch_size=32,
    validation_split=0.2,
    verbose=0,
)

for e in (1, 10, 20, 30, 40, 50):
    print(f"epohh {e:2d}   viga {history.history['loss'][e - 1]:.3f}"
          f"   kontrolli viga {history.history['val_loss'][e - 1]:.3f}"
          f"   kontrolli täpsus {history.history['val_accuracy'][e - 1]:.1%}")

# 5. Hindamine testandmetel
loss, acc = model.evaluate(X_test, y_test, verbose=0)
print(f"testi täpsus: {acc:.1%}")
väljund
epohh  1   viga 0.555   kontrolli viga 0.354   kontrolli täpsus 83.7%
epohh 10   viga 0.143   kontrolli viga 0.118   kontrolli täpsus 95.6%
epohh 20   viga 0.090   kontrolli viga 0.067   kontrolli täpsus 97.5%
epohh 30   viga 0.081   kontrolli viga 0.064   kontrolli täpsus 97.5%
epohh 40   viga 0.072   kontrolli viga 0.063   kontrolli täpsus 97.5%
epohh 50   viga 0.069   kontrolli viga 0.061   kontrolli täpsus 97.5%
testi täpsus: 97.0%

Mida uued sõnad tähendavad

  • validation_split=0.2: Keras paneb 20% treeningandmetest kõrvale ja kontrollib nende peal iga epohhi järel, kuidas mudelil läheb (val_loss, val_accuracy). Nende andmete peal mudel ei õpi.
  • batch_size=32: partii on väike tükk andmeid. Keras uuendab kaalusid pärast iga partiid, mitte alles epohhi lõpus. See on kiirem ja õpib sageli paremini.
  • Adam: SGD targem sugulane, mis kohandab sammu iga parameetri jaoks ise. Hea vaikevalik.
  • metrics=["accuracy"]: lisaks veale näita ka täpsust (õigete ennustuste osa). Täpsus on inimesele arusaadav, viga on mudelile õppimiseks.
  • history: fit() tagastab sõnastiku, kus on iga epohhi loss, val_loss, accuracy ja val_accuracy.
õpib (fit): 640 ridakontroll: 160test: 200treeningandmed: 800 rida (X_train)X_testmudel muudab kaalusid selle põhjalvalidation_split=0.2ei õpi, ainult kontrollviimane kontroll(evaluate)
Test (20%) jätame lõpuni puutumata. Treeningandmetest viimased 20% (160 rida) jäävad kontrolliks.
32↓ partii 1: 32 rida = 1 sammpartii 201 epohh = 20 partiid = 20 kaalude uuendustshuffle=True: iga epohhi alguses segatakse read, nii et partiid tulevad iga kord erinevad50 epohhi × 20 partiid = 1000 sammu
Täisnäites on treeningus 640 rida (800 miinus kontrolliks kõrvale pandud 160). batch_size=32 lõikab need 32-realisteks partiideks ja iga partii järel uuendatakse kaalusid. Keras näitab seda treenimisel kui 20/20.

Treeningu viga ja kontrolli viga langevad koos ning täpsus on umbes 97%. Testi täpsus (97.0%) on peaaegu sama mis kontrolli täpsus: mudel töötab ka andmetega, mida ta pole näinud.

Ennusta uut punkti

Uue punkti peab skaleerima sama skaleerijaga, mida treeningandmetega kasutasime. Ja punkt peab olema kujuga (1, 2), seega kaks nurksulgu:

python
new = scaler.transform([[0.5, -0.3]])      # sama skaleerimine!
p = model.predict(new, verbose=0)

print(p)                                   # tõenäosus, et klass on 1
print(int(p[0, 0] > 0.5))                  # ennustatud klass
väljund
[[0.9918686]]
1

predict annab tõenäosuse, et klass on 1. Siin on see 0.99, seega ennustame klassi 1. Kahe klassi puhul on lävi 0.5: üle selle on klass 1, alla selle klass 0.

Kauem ei ole parem: EarlyStopping

Kui treenida liiga kaua, hakkab mudel treeningandmeid pähe õppima. Treeningu viga langeb edasi, aga kontrolli viga hakkab tõusma. Seda nimetatakse ülesobitamiseks.

00.20.40.61102030405050 epohhimõlemad langevad: kõik korrasepohh00.050.10.150.210100200300300 epohhiliiga kaua treenides: kontrolli viga tõusebepohhparim: epohh 70EarlyStopping peatasepohhil 33treeningu viga (loss)kontrolli viga (val_loss)
Vasakul on täisnäite 50 epohhi (päris history), paremal sama mudel 300 epohhi. Oranž punkt on kontrolli vea miinimum (epohh 70), kriipsjoon on koht, kus EarlyStopping treeningu peatas.

Keras oskab treeningu õigel ajal ise peatada. Selleks on callback EarlyStopping: ta jälgib kontrolli viga ja peatab, kui see ei parane mitu epohhi järjest.

python
keras.utils.set_random_seed(42)
model = build([16, 16], "relu")                # sama mudel nagu varem

stop = keras.callbacks.EarlyStopping(
    monitor="val_loss",                        # jälgi kontrolli viga
    patience=5,                                # oota 5 epohhi paranemist
    restore_best_weights=True,                 # võta tagasi parimad kaalud
)

history = model.fit(X_train, y_train, epochs=200, batch_size=32,
                    validation_split=0.2, callbacks=[stop], verbose=0)

print("tehtud epohhe:", len(history.history["loss"]))
print(f"testi täpsus: {model.evaluate(X_test, y_test, verbose=0)[1]:.1%}")
väljund
tehtud epohhe: 33
testi täpsus: 96.5%
  • monitor="val_loss": jälgi kontrolli viga.
  • patience=5: oota 5 epohhi, kas viga paraneb. Kui ei parane, peata.
  • restore_best_weights=True: pärast peatamist võta tagasi parima epohhi kaalud.
  • Nüüd sai mudel hakkama 33 epohhiga ja testi täpsus on peaaegu sama (96.5%). Me ei pidanud epohhide arvu ise ära arvama.

Mudeli salvestamine

Treenitud mudeli saab ühte faili salvestada ja hiljem tagasi laadida:

python
model.save("moons.keras")                      # kogu mudel ühes failis

loaded = keras.models.load_model("moons.keras")

print(model.predict(new, verbose=0))           # algne mudel
print(loaded.predict(new, verbose=0))          # laetud mudel annab sama vastuse
väljund
[[0.9881414]]
[[0.9881414]]
Ära unusta skaleerijat

.keras fail sisaldab mudelit ja kaale, aga mitte StandardScalerit. Uute andmete skaleerimiseks salvesta skaleerija eraldi (joblib.dump(scaler, "scaler.joblib")) või pane skaleerimine mudeli sisse kihiga layers.Normalization.

Iseseisev ülesanne

Vaheta andmed välja: from sklearn.datasets import load_iris, X, y = load_iris(return_X_y=True). Mida pead mudelis muutma? Vihje: vaata tunnuste ja klasside arvu (need on 4 ja 3) ning osa 5.4 tabelit.

Lahendus
python
from sklearn.datasets import load_iris

X, y = load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0, stratify=y)

scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)

keras.utils.set_random_seed(0)
model = keras.Sequential([
    keras.Input(shape=(4,)),                       # 4 tunnust
    layers.Dense(16, activation="relu"),
    layers.Dense(3, activation="softmax"),         # 3 klassi, tõenäosused
])
model.compile(optimizer=keras.optimizers.Adam(learning_rate=0.01),
              loss="sparse_categorical_crossentropy",   # sildid on täisarvud 0, 1, 2
              metrics=["accuracy"])

model.fit(X_train, y_train, epochs=100, batch_size=16, verbose=0)
print(f"iirise testi täpsus: {model.evaluate(X_test, y_test, verbose=0)[1]:.1%}")
väljund
iirise testi täpsus: 100.0%

Muutus kolm asja: sisend on shape=(4,), viimane kiht on Dense(3, activation="softmax") ja kaofunktsioon on sparse_categorical_crossentropy. Sildid on juba täisarvud 0, 1 ja 2, seega neid muutma ei pea.

8Levinud vead

Siin on veateated, mida algajad kõige sagedamini näevad. Teated on päris, kopeeritud TensorFlow'st ja Kerasest.

Mida näedPõhjusParanda
expected axis -1 of input shape to have value 2, but received input with shape (None, 4)Andmetes on teine arv tunnuseid kui mudeli Input(shape=…).Kontrolli X.shape. Esimene kiht: keras.Input(shape=(X.shape[1],)).
Expected shape (None, 2), but input has incompatible shape (2,) või Unrecognized data type: x=[0.5, -0.3]Ühe näite ennustamisel puudub näidete mõõde.Kasuta kahte nurksulgu: [[0.5, -0.3]], kuju (1, 2).
Received a label value of 3 which is outside the valid range of [0, 3)sparse_categorical_crossentropy tahab silte 0…K−1, sul on 1…K.Lahuta siltidest 1 või kodeeri sildid uuesti (LabelEncoder).
Argument output must have rank (ndim) target.ndim - 1. Received: target.shape=(None, 3), output.shape=(None, 3)Sildid on one-hot kujul, aga loss on sparse_….Kasuta "categorical_crossentropy" või muuda sildid täisarvudeks (y.argmax(axis=1)).
Arguments target and output must have the same rank (ndim). Received: target.shape=(None,), output.shape=(None, 3)Sildid on täisarvud, aga loss="categorical_crossentropy".Kasuta "sparse_categorical_crossentropy".
cannot compute AddV2 as input #1(zero-based) was expected to be a float tensor but is a double tensorfloat32 ja float64 segamini (NumPy annab float64).tf.cast(x, tf.float32)
Cannot convert 0.5 to EagerTensor of dtype int32Täisarvuline tensor (tf.constant([1, 2])) ja komaga arv.Kirjuta tensor komaga ([1., 2.]) või kasuta tf.cast.
Argument(s) not recognized: {'lr': 0.01}Vana õpetus. Keras 3-s on argumendi nimi learning_rate.Adam(learning_rate=0.01)
'Sequential' object has no attribute 'predict_classes'Vana meetod on eemaldatud.Kaks klassi: (model.predict(X) > 0.5).astype(int). Mitu klassi: model.predict(X).argmax(axis=1).
UserWarning: Do not pass an input_shape/input_dim argument to a layerVana stiil: sisendi kuju on antud kihile.Pane esimeseks keras.Input(shape=(…)).
model.summary() näitab ? ja 0 (unbuilt)Mudel ei tea veel sisendi kuju.Lisa keras.Input(shape=…) või treeni mudelit korra.
loss: nanLiiga suur õppesamm, skaleerimata andmed või vale kaofunktsioon.Vähenda learning_rate, skaleeri andmed, kontrolli osa 5.4 tabelit.
Täpsus jääb kinni klasside osakaalu juuresAktivatsioon puudub kihtide vahel või viimases kihis on vale aktivatsioon.Peidetud kihtidesse "relu", väljundisse sigmoid või softmax.
Teine fit() ei alusta nullistfit() jätkab sealt, kus mudel pooleli jäi.Uue katse jaoks ehita mudel uuesti (keras.Sequential(...)).
'EagerTensor' object does not support item assignmentTavalist tensorit ei saa muuta.Kasuta tf.Variable või tee uus tensor.
Silumise nipp

Kui midagi ei tööta, prindi kujud: print(X.shape, y.shape) ja vaata model.summary(). Pooled vead selguvad nendest kahest reast.

Kontrolli ennast

Üheksa küsimust. Vali vastus, kohe näed, kas see oli õige ja miks.

Spikker

tensor
tf.constant([[1., 2.], [3., 4.]])
t.shape, t.dtype
tf.reshape(t, (4, 1))
tf.reduce_sum(t, axis=0)
a @ b          # maatrikskorrutis
tf.cast(t, tf.float32)
t.numpy()      # tagasi NumPy-sse
mudel
model = keras.Sequential([
    keras.Input(shape=(F,)),
    layers.Dense(16, activation="relu"),
    layers.Dense(16, activation="relu"),
    layers.Dense(1, activation="sigmoid"),
])
model.summary()
compile ja fit
model.compile(
    optimizer=keras.optimizers.Adam(
        learning_rate=0.01),
    loss="binary_crossentropy",
    metrics=["accuracy"],
)
history = model.fit(
    X_train, y_train,
    epochs=50, batch_size=32,
    validation_split=0.2,
)
hindamine
loss, acc = model.evaluate(X_test, y_test)
p = model.predict(X_uus)       # tõenäosused
klass = (p > 0.5).astype(int)  # 2 klassi
klass = p.argmax(axis=1)       # mitu klassi
callback ja salvestamine
stop = keras.callbacks.EarlyStopping(
    monitor="val_loss", patience=5,
    restore_best_weights=True)
model.fit(..., callbacks=[stop])

model.save("mudel.keras")
model = keras.models.load_model("mudel.keras")
viimane kiht ja loss
# regressioon
Dense(1)
loss="mse"

# kaks klassi
Dense(1, activation="sigmoid")
loss="binary_crossentropy"

# K klassi (sildid 0..K-1)
Dense(K, activation="softmax")
loss="sparse_categorical_crossentropy"

Mis edasi? Kui MLP on selge, on järgmised sammud ülesobitamise vastu Dropout ja BatchNormalization, suurte andmete jaoks tf.data, piltide jaoks konvolutsioonkihid (layers.Conv2D) ja eeltreenitud mudelid (keras.applications). Kõik need järgivad sama nelja sammu: mudel, compile, fit, predict.