Perceptron: jedan neuron u Pythonu
U modulu 2 smo veštačkom neuronu sami podešavali težine. Sada pišemo program u kome neuron težine nauči sam, iz primera. Takav neuron zove se perceptron – izmislio ga je Frenk Rozenblat 1958. godine. Naučićemo ga logičkim funkcijama I i ILI, a videćemo i šta jedan neuron ne može.
Podsetnik: kako radi neuron
Neuron množi svaki ulaz njegovom težinom, sabira i poredi sa pragom: ako je zbir ≥ prag, izlaz je 1, inače 0. (Pogledaj interaktivni primer u lekciji Neuronske mreže.)
| a | b | a I b | a ILI b | a XOR b |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 |
| 0 | 1 | 0 | 1 | 1 |
| 1 | 0 | 0 | 1 | 1 |
| 1 | 1 | 1 | 1 | 0 |
Ove tabele su naši primeri za učenje: ulazi (a, b) i tačan izlaz.
Pravilo učenja
Na početku su sve težine 0. Za svaki primer:
- izračunamo izlaz neurona;
- greška = tačan izlaz − izlaz neurona (0 ako je tačno, +1 ako je trebalo 1, −1 ako je trebalo 0);
- ako postoji greška: svaku težinu pomerimo za greška · ulaz, a prag za −greška.
Ponavljamo kroz sve primere („krugove“) dok u celom krugu ne bude nijedne greške.
Program: perceptron uči I i ILI
# Perceptron: jedan veštački neuron koji uči logičke funkcije I (AND) i ILI (OR)
def izlaz(x, tezine, prag):
"""Neuron: zbir (ulaz · težina); ako je zbir ≥ prag, izlaz je 1, inače 0."""
zbir = sum(xi * wi for xi, wi in zip(x, tezine))
return 1 if zbir >= prag else 0
def treniraj(primeri, brzina=1, krugova=20):
tezine = [0, 0] # na početku neuron ništa ne zna
prag = 0
for krug in range(1, krugova + 1):
greske = 0
for x, tacno in primeri:
greska = tacno - izlaz(x, tezine, prag) # 0 ako je tačno, +1 ili −1 ako nije
if greska != 0:
greske += 1
# pravilo učenja: pomeri težine malo u smeru tačnog odgovora
for i in range(len(tezine)):
tezine[i] += brzina * greska * x[i]
prag -= brzina * greska
print(f"krug {krug}: grešaka {greske}, težine {tezine}, prag {prag}")
if greske == 0:
break # neuron je naučio – prekidamo
return tezine, prag
I_FUNKCIJA = [((0, 0), 0), ((0, 1), 0), ((1, 0), 0), ((1, 1), 1)]
ILI_FUNKCIJA = [((0, 0), 0), ((0, 1), 1), ((1, 0), 1), ((1, 1), 1)]
for ime, primeri in [("I", I_FUNKCIJA), ("ILI", ILI_FUNKCIJA)]:
print(f"--- učenje funkcije {ime} ---")
tezine, prag = treniraj(primeri)
for x, tacno in primeri:
print(f" {x} -> {izlaz(x, tezine, prag)} (tačno: {tacno})")
Ispis programa:
--- učenje funkcije I ---
krug 1: grešaka 2, težine [1, 1], prag 0
krug 2: grešaka 3, težine [2, 1], prag 1
krug 3: grešaka 3, težine [2, 1], prag 2
krug 4: grešaka 2, težine [2, 2], prag 2
krug 5: grešaka 1, težine [2, 1], prag 3
krug 6: grešaka 0, težine [2, 1], prag 3
(0, 0) -> 0 (tačno: 0)
(0, 1) -> 0 (tačno: 0)
(1, 0) -> 0 (tačno: 0)
(1, 1) -> 1 (tačno: 1)
--- učenje funkcije ILI ---
krug 1: grešaka 2, težine [0, 1], prag 0
krug 2: grešaka 2, težine [1, 1], prag 0
krug 3: grešaka 1, težine [1, 1], prag 1
krug 4: grešaka 0, težine [1, 1], prag 1
(0, 0) -> 0 (tačno: 0)
(0, 1) -> 1 (tačno: 1)
(1, 0) -> 1 (tačno: 1)
(1, 1) -> 1 (tačno: 1)
- Za funkciju I neuron je naučio težine [2, 1] i prag 3: samo kad su oba ulaza 1, zbir je 3. Za ILI: težine [1, 1] i prag 1.
- Primeti da težine na kraju nisu „lepe“ – neuronu je svejedno, važno je da odgovori budu tačni.
- Koristimo cele brojeve (korak 1) da bi se računi lako pratili; u pravim mrežama korak je mali decimalni broj.
Šta jedan neuron ne može: XOR
# Isti perceptron pokušava da nauči XOR („ili jedno ili drugo, ali ne oba“)
def izlaz(x, tezine, prag):
zbir = sum(xi * wi for xi, wi in zip(x, tezine))
return 1 if zbir >= prag else 0
XOR = [((0, 0), 0), ((0, 1), 1), ((1, 0), 1), ((1, 1), 0)]
tezine, prag = [0, 0], 0
for krug in range(1, 101):
greske = 0
for x, tacno in XOR:
greska = tacno - izlaz(x, tezine, prag)
if greska:
greske += 1
tezine = [w + greska * xi for w, xi in zip(tezine, x)]
prag -= greska
if krug in (1, 10, 50, 100):
print(f"krug {krug}: grešaka {greske}")
print("Posle 100 krugova neuron i dalje greši – XOR ne može da se nauči jednim neuronom.")
Ispis programa:
krug 1: grešaka 3
krug 10: grešaka 4
krug 50: grešaka 4
krug 100: grešaka 4
Posle 100 krugova neuron i dalje greši – XOR ne može da se nauči jednim neuronom.
Neuron u suštini povlači jednu pravu liniju koja deli ulaze na one sa izlazom 1 i one sa izlazom 0. Za I i ILI takva linija postoji. Za XOR – ne: tačke (0, 1) i (1, 0) moraju na jednu stranu, a (0, 0) i (1, 1) na drugu, a to nijedna prava ne može.
Ovo su 1969. godine pokazali Minski i Papert, i to je bio jedan od uzroka prve „AI zime“. Rešenje je pronađeno kasnije: više slojeva neurona – videćeš ga u trećem zadatku i u sledećoj lekciji.
Prvi krug učenja za funkciju I uradite zajedno na tabli (tabela sa težinama posle svakog primera), pa uporedite sa ispisom programa.
Česte zablude
- „Perceptron uvek nauči ako dovoljno dugo uči“ – nauči samo ako zadatak može da se reši jednom pravom linijom (kao I i ILI); XOR ne može.
- „Težine posle učenja moraju biti iste kod svih“ – postoji mnogo težina koje rade; koje će se dobiti zavisi od redosleda primera i početnih vrednosti.
Zadaci za vežbu
Pokušaj prvo sam, pa tek onda otvori rešenje.
- Nauči perceptron sa jednim ulazom funkciji NE (0 → 1, 1 → 0).
- Nauči perceptron sa tri ulaza funkciji „bar dva od tri“ (izlaz 1 ako su bar dva ulaza 1).
- Napravi XOR od tri neurona u dva sloja: prvi sloj računa „a ILI b“ i „ne (a I b)“, drugi sloj spaja ih funkcijom I. Težine izaberi sam.
Rešenje 1: funkcija NE
# Zadatak: perceptron sa JEDNIM ulazom uči funkciju NE (0 -> 1, 1 -> 0)
def izlaz(x, tezine, prag):
return 1 if sum(xi * wi for xi, wi in zip(x, tezine)) >= prag else 0
NE = [((0,), 1), ((1,), 0)]
tezine, prag = [0], 0
for krug in range(1, 21):
greske = 0
for x, tacno in NE:
greska = tacno - izlaz(x, tezine, prag)
if greska:
greske += 1
tezine = [w + greska * xi for w, xi in zip(tezine, x)]
prag -= greska
if greske == 0:
break
print(f"težina: {tezine}, prag: {prag} (naučeno za {krug} kruga)")
for x, tacno in NE:
print(x, "->", izlaz(x, tezine, prag))
Ispis programa:
težina: [-1], prag: 0 (naučeno za 3 kruga)
(0,) -> 1
(1,) -> 0
Negativna težina „okreće“ ulaz: kada je ulaz 1, zbir je −1 < 0.
Rešenje 2: bar dva od tri
# Zadatak: „bar dva od tri“ – izlaz je 1 ako su bar dva od tri ulaza jednaka 1
from itertools import product
def izlaz(x, tezine, prag):
return 1 if sum(xi * wi for xi, wi in zip(x, tezine)) >= prag else 0
primeri = [(x, 1 if sum(x) >= 2 else 0) for x in product([0, 1], repeat=3)] # svih 8 kombinacija
tezine, prag = [0, 0, 0], 0
for krug in range(1, 51):
greske = 0
for x, tacno in primeri:
greska = tacno - izlaz(x, tezine, prag)
if greska:
greske += 1
tezine = [w + greska * xi for w, xi in zip(tezine, x)]
prag -= greska
if greske == 0:
break
print("težine:", tezine, "prag:", prag, "krugova:", krug)
for x, tacno in primeri:
print(f"{x} -> {izlaz(x, tezine, prag)} (tačno: {tacno})")
Ispis programa:
težine: [1, 1, 1] prag: 2 krugova: 4
(0, 0, 0) -> 0 (tačno: 0)
(0, 0, 1) -> 0 (tačno: 0)
(0, 1, 0) -> 0 (tačno: 0)
(0, 1, 1) -> 1 (tačno: 1)
(1, 0, 0) -> 0 (tačno: 0)
(1, 0, 1) -> 1 (tačno: 1)
(1, 1, 0) -> 1 (tačno: 1)
(1, 1, 1) -> 1 (tačno: 1)
Neuron je pronašao prirodno rešenje: sve težine 1 i prag 2.
Rešenje 3: XOR iz dva sloja
# Zadatak: XOR pomoću DVA SLOJA neurona (težine smo izabrali sami)
# XOR(a, b) = (a ILI b) I NE(a I b)
def neuron(x, tezine, prag):
return 1 if sum(xi * wi for xi, wi in zip(x, tezine)) >= prag else 0
def xor(a, b):
ili = neuron((a, b), (1, 1), 1) # prvi sloj: ILI
ni = neuron((a, b), (-1, -1), -1) # prvi sloj: NE-I (1 osim kad su oba 1)
return neuron((ili, ni), (1, 1), 2) # drugi sloj: I
for a in (0, 1):
for b in (0, 1):
print(a, b, "->", xor(a, b))
Ispis programa:
0 0 -> 0
0 1 -> 1
1 0 -> 1
1 1 -> 0
Dva sloja mogu ono što jedan ne može – to je osnovna ideja neuronskih mreža.
Proveri znanje
Otvori kviz
Kviz: perceptron
1. Kako se menjaju težine kada perceptron pogreši?
2. Koju funkciju jedan perceptron NE može da nauči?
3. Kada se učenje prekida?
4. Ko je izmislio perceptron?
