Racket/Neural
Från Täpp-Anders
Hoppa till navigeringHoppa till sök
#lang racket
;;;;;
;; Neuralt nät implementerad i Racket
;; Täpp-Anders Sikvall 2026-04-03 anders@sikvall.se
;;;;;
;;;;; NEURALT NÄT
;;;
;; Detta program implementerar ett litet neuralt nät och tränar det på sannings-
;; tabellen för XOR. Efter några tusen epoker ska prediktionerna ligga extremt nära
;; följande:
;;
;; a,b → a XOR b
;; 0,0 → 0
;; 0,1 → 1
;; 1,0 → 1
;; 1,1 → 0
;;
;; Implementationen har två input-neuroner för a och b
;; det finns 5 interna (dolda neuroner) som tränas av indatat
;; det finns 1 output-neuron som ger svaret
;;
;;;;; Kodens uppbyggnad och de olika funktionernas användning
;;
;; sigmoid, sigmoid-deriv Sigmoid-kurva och derivata för backpropagation
;;
;; random-matrix, met-vec-mul Grundläggande linalg
;; outer-product; vec-add
;;
;; struct nn Nätverksstruktur, lagrar vikter och bias
;;
;; forward Beräknar prediktion
;;
;; train-step En iteration av backpropagation
;;
;; train Kör många epoker av in och förväntat utdata för
;; att träna modellen
;;
;; predict Använder nätverket på ny data!
;;
;;
;;;;; Viktiga delar
;;
;; * sigmoid mappar alla värden steglöst till intervallet (0,1)
;; * derivatan används i backpropagation för att räkna ut gradienten
;;
;; w1: vikter mellan input och hidden layer (5x2 matris)
;; b1: bias för hidden layer (5-vektor)
;; w2: viktar mellan hidden och output (1x5 matris)
;; b2: bias för output (1-vektor)
;;
;; Forward pass
;;
;; 1. Beräkna aktivering i hidden layer
;; z1 = W1 * input + b1
;; h = rho z1
;; 2. Beräkna output
;; z2 = W2 * h +b2
;; output = rho(z2)
;;
;; Backpropagation (uppdatering av viktningen)
;;
;; Hjärtat i träningen av ett neuralt nät. Algorimen är "gradient descent" med batch-
;; storlek 1.
;;
;; 1. Forward pass hidden → output
;; 2. Output-felet
;; delta = (output - target) X rho' (output)
;; 3. Hidden-felet
;; delta hidden = (W2/T * delta out) X rho' (hidden)
;; 4. Uppdatera vikternas gradienter
;; W2 ← W2 - nabla * delta out * h^T
;; W1 ← W1 - nabla * delta hidden * input^T
;; 5. Bias uppdateras på samma sätt (jo det är en genväg)
;;
(require math/base)
;; ========================================
;; Aktiveringsfunktioner
;; ========================================
(define (sigmoid x)
(/ 1.0 (+ 1.0 (exp (- x)))))
(define (sigmoid-deriv y) ; y måste redan vara sigmoid(x)
(* y (- 1.0 y)))
;; ========================================
;; Matris-/vektorhjälp (enkel version)
;; ========================================
(define (random-matrix rows cols)
(build-vector rows
(λ (_) (build-vector cols (λ (_) (- (* 2.0 (random)) 1.0))))))
(define (vec-add v1 v2) ; elementvis addition av två vektorer
(build-vector (vector-length v1)
(λ (i) (+ (vector-ref v1 i) (vector-ref v2 i)))))
(define (scalar-mul s v)
(build-vector (vector-length v) (λ (i) (* s (vector-ref v i)))))
(define (outer-product v1 v2) ; v1 kolumn × v2 rad → matris
(build-vector (vector-length v1)
(λ (i)
(build-vector (vector-length v2)
(λ (j) (* (vector-ref v1 i) (vector-ref v2 j)))))))
(define (mat-vec-mul M v) ; M (rows×cols) × v (cols)
(build-vector (vector-length M)
(λ (i)
(for/sum ([j (in-range (vector-length v))])
(* (vector-ref (vector-ref M i) j)
(vector-ref v j))))))
(define (transpose M)
(build-vector (vector-length (vector-ref M 0))
(λ (j)
(build-vector (vector-length M)
(λ (i) (vector-ref (vector-ref M i) j))))))
;; ========================================
;; Neuralt nätverk
;; ========================================
(struct nn (w1 b1 w2 b2) #:transparent)
(define (make-nn in-size hidden-size out-size)
(nn (random-matrix hidden-size in-size)
(make-vector hidden-size 0.0)
(random-matrix out-size hidden-size)
(make-vector out-size 0.0)))
(define (forward net input) ; input är en vektor t.ex. #(0 0)
(let* ([z1 (vec-add (mat-vec-mul (nn-w1 net) input)
(nn-b1 net))]
[h (build-vector (vector-length z1) (λ (i) (sigmoid (vector-ref z1 i))))]
[z2 (vec-add (mat-vec-mul (nn-w2 net) h)
(nn-b2 net))]
[o (build-vector (vector-length z2) (λ (i) (sigmoid (vector-ref z2 i))))])
(values o h)))
(define (train-step net input target lr)
(let-values ([(output hidden) (forward net input)])
(let* ([out-err (build-vector (vector-length output)
(λ (i) (- (vector-ref output i)
(vector-ref target i))))]
[out-delta (build-vector (vector-length output)
(λ (i) (* (vector-ref out-err i)
(sigmoid-deriv (vector-ref output i)))))]
[hidden-err (mat-vec-mul (transpose (nn-w2 net)) out-delta)]
[hidden-delta (build-vector (vector-length hidden)
(λ (i) (* (vector-ref hidden-err i)
(sigmoid-deriv (vector-ref hidden i)))))]
;; Uppdateringar
[dw2 (outer-product out-delta hidden)]
[new-w2 (build-vector (vector-length (nn-w2 net))
(λ (i)
(build-vector (vector-length (vector-ref (nn-w2 net) i))
(λ (j) (- (vector-ref (vector-ref (nn-w2 net) i) j)
(* lr (vector-ref (vector-ref dw2 i) j)))))))]
[new-b2 (build-vector (vector-length (nn-b2 net))
(λ (i) (- (vector-ref (nn-b2 net) i)
(* lr (vector-ref out-delta i)))))]
[dw1 (outer-product hidden-delta input)]
[new-w1 (build-vector (vector-length (nn-w1 net))
(λ (i)
(build-vector (vector-length (vector-ref (nn-w1 net) i))
(λ (j) (- (vector-ref (vector-ref (nn-w1 net) i) j)
(* lr (vector-ref (vector-ref dw1 i) j)))))))]
[new-b1 (build-vector (vector-length (nn-b1 net))
(λ (i) (- (vector-ref (nn-b1 net) i)
(* lr (vector-ref hidden-delta i)))))])
(nn new-w1 new-b1 new-w2 new-b2))))
;; ========================================
;; Skriv ut vikter och bias – snygg tabell (fixad version)
;; ========================================
(define (print-nn net)
(printf "\n=== NEURALT NÄTVERK - VIKTER OCH BIAS ===\n\n")
;; Hjälpfunktion för att formatera tal snyggt (med minustecken på rätt plats)
(define (fmt x)
(~r x
#:min-width 10
#:precision '(= 4)
#:sign #f)) ; #f = visa minus för negativa, inget tecken för positiva
;; W1: Vikter från input till hidden (5×2)
(printf "W1 (Hidden × Input):\n")
(printf " Input0 Input1\n")
(for ([i (in-range (vector-length (nn-w1 net)))])
(printf "Hidden~a " i)
(for ([j (in-range 2)])
(printf "~a " (fmt (vector-ref (vector-ref (nn-w1 net) i) j))))
(printf "\n"))
(printf "\n")
;; b1: Bias för hidden layer
(printf "b1 (Bias hidden layer):\n")
(for ([i (in-range (vector-length (nn-b1 net)))])
(printf "~a " (fmt (vector-ref (nn-b1 net) i))))
(printf "\n\n")
;; W2: Vikter från hidden till output (1×5)
(printf "W2 (Output × Hidden):\n")
(printf " Hidden0 Hidden1 Hidden2 Hidden3 Hidden4\n")
(printf "Output ")
(for ([j (in-range 5)])
(printf "~a " (fmt (vector-ref (vector-ref (nn-w2 net) 0) j))))
(printf "\n\n")
;; b2: Bias för output
(printf "b2 (Bias output):\n")
(printf "~a\n" (fmt (vector-ref (nn-b2 net) 0)))
(printf "\n========================================\n"))
;; Träna nätverket
(define (train net data epochs lr)
(for ([ep (in-range epochs)])
(for ([sample data])
(set! net (train-step net (car sample) (cadr sample) lr)))
(when (zero? (modulo ep 500)) ; oftare än var 1000:e
(let ([loss (for/sum ([sample data])
(let-values ([(output _) (forward net (car sample))])
(let ([target (vector-ref (cadr sample) 0)]
[pred (vector-ref output 0)])
(sqr (- target pred)))))])
(printf "Epoch ~a Loss: ~a\n" ep (/ loss (length data))))))
(print-nn net)
net)
;; Prediktera
(define (predict net input)
(let-values ([(output _) (forward net input)])
output))
;; ========================================
;; XOR-exempel
;; ========================================
(define xor-data
(list (list #(0 0) #(0))
(list #(0 1) #(1))
(list #(1 0) #(1))
(list #(1 1) #(0))))
(define net (make-nn 2 5 1)) ; 2 input, 5 dolda neuroner, 1 output
(define rounds 8000) ; 2000 är i minsta laget, 5000 eller hellre 8000 är bättre
(define backpr 0.7)
(printf "Tränar XOR-problemet i ~a rundor med backprop styrka ~a\n" rounds backpr)
(set! net (train net xor-data rounds backpr))
(printf "\n=== Testresultat ===\n")
(for ([sample xor-data])
(let ([pred (vector-ref (predict net (car sample)) 0)])
(printf "Input ~a → Förväntat: ~a Predikterat: ~a\n"
(car sample)
(vector-ref (cadr sample) 0)
(if (> pred 0.5) 1.0 0.0))))