;; The first three lines of this file were inserted by DrRacket. They record metadata
;; about the language level of this file in a form that our tools can easily process.
#reader(lib "htdp-intermediate-lambda-reader.ss" "lang")((modname line) (read-case-sensitive #t) (teachpacks ()) (htdp-settings #(#t constructor repeating-decimal #f #t none #f () #f)))
(require 2htdp/image)
(require 2htdp/universe)
;====== Graphics utilities ======
; convert : Number Number Number Number Number -> Number
; if a1 becomes b1 and a2 becomes b2, then what should a become?
; *Assumption*: a1 and a2 are different
(check-expect (convert 0 32 100 212 0) 32)
(check-expect (convert 0 32 100 212 100) 212)
(check-expect (convert 0 32 100 212 50) 122)
(define (convert a1 b1 a2 b2 a)
(/ (+ (* b1 (- a2 a)) (* b2 (- a a1))) (- a2 a1)))
; draw-line : Line -> Image
(define xmin -15)
(define xmax 35)
(define ymin -5)
(define ymax 45)
(define size 300)
(define (convert-x x) (convert xmin 0 xmax size x))
(define (convert-y y) (convert ymin size ymax 0 y))
(define (draw-line L)
(scene+line (scene+line (scene+line (square size "solid" "white")
(convert-x xmin) (convert-y (run L xmin))
(convert-x xmax) (convert-y (run L xmax))
(pen "red" 3 "solid" "butt" "bevel"))
(convert-x xmin) (convert-y 0)
(convert-x xmax) (convert-y 0)
"black")
(convert-x 0) (convert-y ymin)
(convert-x 0) (convert-y ymax)
"black"))
; draw-line-with-examples : [ListOf Posn] Line -> Image
(define (draw-line-with-examples lop L)
(cond [(empty? lop) (draw-line L)]
[else (place-image (circle 4 "solid" "black")
(convert-x (posn-x (first lop)))
(convert-y (posn-y (first lop)))
(draw-line-with-examples (rest lop) L))]))
; draw : Line -> Image
(define (draw L)
(above/align "left"
(beside (place-image/align
(text (string-append
"(make-Line\n "
(number->string (exact->inexact (Line-intercept L)))
"\n "
(number->string (exact->inexact (Line-slope L)))
")")
40 "black")
20 150 "left" "center"
(rectangle 500 300 "solid" "white"))
(draw-line-with-examples (list (make-posn 0 26)
(make-posn 10 31)
(make-posn 20 40))
L))
(text (number->string (exact->inexact (total-line-loss L))) 20 "black")))
; key : Line KeyEvent -> Line
(define (key L ke)
(cond [(string=? ke "1") (make-Line 20 1)]
[(string=? ke "2") (make-Line 10 2)]
[(string=? ke "r") (random-line "whatever")]
[(string=? ke "z") (make-Line (- (Line-intercept L) .1) (Line-slope L))]
[(string=? ke "x") (make-Line (+ (Line-intercept L) .1) (Line-slope L))]
[(string=? ke "a") (make-Line (Line-intercept L) (- (Line-slope L) .1))]
[(string=? ke "s") (make-Line (Line-intercept L) (+ (Line-slope L) .1))]
[(string=? ke " ") (line-step L)]
[else L]))
; edit : Line -> Line
(define (edit L)
(big-bang L [to-draw draw] [on-key key]))
;====== Designing a Line ======
;;; Say we want the function below, and we did design recipe steps 1-4,
;;; but we can't be bothered to figure out the definition.
; f : Number -> Number
; convert size labels to waist inches
(check-within (f 0) 26 1.5)
(check-within (f 10) 31 1.5)
(check-within (f 20) 40 1.5)
(define (f x)
(+ 20 x))
;;; We establish a fairly limited format for the definition,
;;; (define (f x)
;;; (+ Number (* Number x)))
;;; so that we can represent every possible program by data:
; A Line is (make-Line Number Number)
(define-struct Line [intercept slope])
; run : Line Number -> Number
; run the given Line as a function that takes a Number and returns a Number
(define (run L x)
(+ (Line-intercept L) (* (Line-slope L) x)))
;;; We try out different Lines. We could just do so randomly and see
;;; what looks good. We can also tweak numbers slightly.
; random-line : Anything -> Line
; generate a random Line
(define (random-line whatever)
(make-Line (+ (random 30) 10)
(/ (random 15) 10)))
; Try this in the Interaction Window:
; (edit (make-Line 10 2))
;;; How do we decide if one line is better than another? We define some
;;; grading scheme, which like all grading schemes is somewhat arbitrary.
;;; That's the loss, in the lower-left corner of the big-bang.
; line-loss : Line Number Number -> Number
; quantify how bad the given Line is at a single example
(define (line-loss L x y)
(sqr (- (run L x) y)))
; total-line-loss : Line -> Number
; quantify how bad the given Line is across all 3 examples
(define (total-line-loss L)
(+ (line-loss L 0 26)
(line-loss L 10 31)
(line-loss L 20 40)))
;;; Once we decide on a grading scheme, it turns out that we can
;;; automate the tweaking.
; grad-line-loss : Line Number Number -> Line
; gradient of line-loss with respect to Line
(define (grad-line-loss L x y)
(local [(define backprop
; the derivative of (line-loss L x y) with respect to (run L x)
(exact->inexact (* 2 (- (run L x) y))))]
; use the chain rule to compute the gradient of (line-loss L x y)
; with respect to L
(make-Line backprop (* backprop x))))
; line-step : Line -> Line
; tweak the given Line in the direction of negative grad-line-loss
(define (line-step L)
(foldr (lambda (grad L)
(make-Line (- (Line-intercept L) (* 0.1 (Line-intercept grad)))
(- (Line-slope L) (* 0.001 (Line-slope grad)))))
L
(list (grad-line-loss L 0 26)
(grad-line-loss L 10 31)
(grad-line-loss L 20 40))))
;;; How did we automate this tweaking? The basic idea is to move from
;;; drawing one picture per Line to drawing one overall map in which
;;; each point on the surface represents one line.
(require racket/base)
(require (only-in plot surface3d plot3d))
(plot3d (list (surface3d (lambda (intercept slope)
(total-line-loss (make-Line intercept slope)))
24.5 26 0.65 0.75)))
;;; This surface is shaped like a bowl. We want to get as low as possible.
;;; We can automate tweaking as rolling a marble into the bowl.
;;; This is gradient descent, and computing the gradient to know which way
;;; to descend requires differentiating.