Perceptron: jedan neuron u Pythonu

Modul 3 · Lekcija 4 od 7 · Veštačka inteligencija

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.)

aba I ba ILI ba XOR b
00000
01011
10011
11110

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:

  1. izračunamo izlaz neurona;
  2. greška = tačan izlaz − izlaz neurona (0 ako je tačno, +1 ako je trebalo 1, −1 ako je trebalo 0);
  3. 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.

Za čas (nastavnicima): trajanje 45 minuta; pribor: računari, tabla.
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.

  1. Nauči perceptron sa jednim ulazom funkciji NE (0 → 1, 1 → 0).
  2. Nauči perceptron sa tri ulaza funkciji „bar dva od tri“ (izlaz 1 ako su bar dva ulaza 1).
  3. 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?