תכנות מיקרו-בקרים בשפת MicroPython · פרק 24 — מצלמה ומודלי שפה מקומיים⌂📚🏠
פרק 24

מצלמה ומודלי שפה מקומיים

Camera and Local Language Models
3 שעות לימוד

כל חיישן שקראנו עד כה נתן לנו מספר אחד בכל פעם. מצלמה נותנת מאות אלפי מספרים בתמונה אחת, ומודל שפה נותן משפטים שנשמעים בטוחים גם כשהם שגויים. בפרק האחרון הזה נפעיל את שני הכלים, נלמד מתי לא להאמין להם, ומה אנחנו חייבים למי שמופיע בתמונה.

בפרק הזה:
  • נחשב את גודל הפריים הגולמי, ונבין למה הלוח שולח תמונות JPEG.
  • נבנה שרת שידור MJPEG, ונחבר אליו מצלמה על ESP32 בקושחה ייעודית.
  • נצלם תמונה ב-Raspberry Pi 4/5 בעזרת Picamera2, ונקרא שידור מרוחק בעזרת OpenCV.
  • נשאל מודל שפה מקומי דרך Ollama מן הלוח, ונבדוק את תשובתו לפני שהתוכנית מבצעת משהו.
  • נתעד את השימוש בבינה מלאכותית, ונכבד את הפרטיות וההסכמה של המצולמים.

24.1 התמונה היא הרבה מספרים (An Image is Many Numbers)

תמונה היא רשת של נקודות הנקראות פיקסלים, ופיקסל צבעוני הוא שלושה מספרים: אדום, ירוק וכחול, ובדרך כלל בית אחד לכל אחד מהם. כל תמונה בודדת מתוך רצף תמונות נקראת פריים.

גודל פריים גולמי
B = W · H · c
סמלפירושיחידה
W, Hרוחב הפריים וגובהו בפיקסלים—
cמספר הבתים לכל פיקסל: 3 בצבע ו-1 באפור—
Bגודל הפריים בבתים, לפני כל דחיסה—
דוגמה 24.1
מה גודלו של פריים צבעוני גולמי במידות 640 × 480?
פתרון:
B = W · H · c
B = 640 · 480 · 3
B = 921600 byte
B = 921600 byte

המספר הזה גדול מכל הזיכרון הפנימי בשבב ESP32 המקורי, כ-520 kB לפי היצרן. לכן הלוח אינו מחזיק את הפריים הגולמי: הוא מבקש מן המצלמה תמונה דחוסה בפורמט JPEG, ומסתייע בזיכרון חיצוני PSRAM אם יש. ל-Raspberry Pi 4/5, לעומת זאת, יש די זיכרון לפריים הגולמי, ולכן את התמונות מנתחים בו.

לוח עם מצלמהMJPEGהרשת המקומיתRaspberry PiOpenCV, Ollamaהדפדפן
המפה שאנחנו בונים: הלוח מצלם ומשדר, ה-Pi מנתח ומריץ את מודל השפה, והדפדפן מציג.
שאלה 24.1: מה גודלו של פריים אפור גולמי במידות 320 × 240?

24.2 מצלמה על ESP32 ושידור MJPEG (ESP32 Camera and MJPEG Streaming)

הקושחה הרשמית של מיקרו-פייתון אינה כוללת מודול בשם camera, ולכן הסעיף הזה נוגע רק ל-ESP32 עם מצלמה. דרושה קושחה ייעודית שנבנית יחד עם מנהל ההתקן של המצלמה. כאן אנחנו עוקבים אחרי התיעוד של פרויקט פתוח בשם micropython-camera-driver, שתומך בחיישן OV2640 ובלוחות כמו ESP32-CAM.

התוכנית הבאה נכתבה לפי התיעוד הזה, ולא הורצה בבניית הספר, כי ללוח המדומה אין מצלמה:

צילום תמונה אחת ושמירתה. הפקודות האלה שייכות לקושחה ייעודית, ולא הורצו בבניית הספר.
import camera

camera.init(0, format=camera.JPEG, fb_location=camera.PSRAM)
jpg = camera.capture()
print("bytes:", len(jpg))
with open("photo.jpg", "wb") as f:
    f.write(jpg)
אזהרה: הממשק של camera אינו חלק ממיקרו-פייתון הרשמית, ולכן השמות עשויים להיות שונים בין קושחה לקושחה, ומספרי ההדקים של המצלמה משתנים בין לוחות ומועברים ל-init. נעיין בתיעוד של הקושחה שהתקנו בפועל, ונוודא שהיא מתאימה ללוח שלנו ושהיא דורשת PSRAM. קושחה ייעודית עלולה גם לפגר אחרי הגרסה הרשמית האחרונה ולחסור תכונות: למשל, לפי התיעוד שלה היא נבנית בלי BLE.

איך התמונות מגיעות לדפדפן? הדרך הפשוטה ביותר היא MJPEG: השרת אינו מסיים את התשובה, אלא שולח תמונות JPEG זו אחר זו בתוך תשובת HTTP אחת, ומפריד ביניהן במפריד שאנחנו בוחרים. הדפדפן מחליף כל תמונה בזו שאחריה. זהו שידור זרם. את מבנה השרת כולו אפשר לנסות בלי מצלמה, אם מציבים מחרוזות קצרות במקום התמונות:

שני הפריימים כאן הם מחרוזות במקום תמונות. המפרידים והכותרות זהים לאלה של שידור אמיתי.
import socket
import wifi

HEAD = (b"HTTP/1.0 200 OK\r\n"
        b"Content-Type: multipart/x-mixed-replace;"
        b" boundary=frame\r\n\r\n")

FRAMES = (b"<jpeg-1>", b"<jpeg-2>")


def send_frame(conn, jpg):
    conn.sendall(b"--frame\r\nContent-Type: image/jpeg\r\n")
    conn.sendall(b"Content-Length: %d\r\n\r\n" % len(jpg))
    conn.sendall(jpg)
    conn.sendall(b"\r\n")


wlan = wifi.connect()
print("open http://%s/" % wlan.ifconfig()[0])

addr = socket.getaddrinfo("0.0.0.0", 80)[0][-1]
server = socket.socket()
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(addr)
server.listen(1)

while True:
    conn, peer = server.accept()
    conn.recv(1024)
    conn.sendall(HEAD)
    for jpg in FRAMES:
        send_frame(conn, jpg)
    conn.close()
ESP32 + Pico W · MicroPython 1.29 · simulated board
open http://192.168.1.50/
[http_sent]
HTTP/1.0 200 OK
Content-Type: multipart/x-mixed-replace; boundary=frame


--frame
Content-Type: image/jpeg

Content-Length: 8


<jpeg-1>


--frame
Content-Type: image/jpeg

Content-Length: 8


<jpeg-2>
...

כשיש קושחה ייעודית, מחליפים את לולאת for בפונקציה שמצלמת ברצף עד שהדפדפן סוגר את החיבור. כדי להציג את השידור בדף אינטרנט מספיק התג <img src="/">.

במקום לולאת for בשרת הקודם. לא הורץ בבניית הספר, כי הוא דורש מצלמה.
def stream(conn):
    conn.sendall(HEAD)
    try:
        while True:
            send_frame(conn, camera.capture())
    except OSError:
        conn.close()
אזהרה: השרת הזה בלי סיסמה ובלי הצפנה: כל מי שנמצא ברשת יכול לצפות בשידור. אסור לחשוף אותו לאינטרנט, ונחזור לכך בסעיף האחרון.
שאלה 24.2: נניח, לצורך חישוב בלבד ולא כמדידה, שגודל הפריים 20 kB ואנחנו שולחים 5 פריימים בשנייה. כמה קילובייטים עוברים בשנייה? וכמה קילוביטים?

24.3 מצלמה ב-Raspberry Pi: Picamera2 (Picamera2)

ב-Raspberry Pi 4/5 אין צורך בקושחה ייעודית. Picamera2 היא הספרייה הרשמית בפייתון, והיא פועלת מעל שכבת libcamera במערכת. מחברים את מודול המצלמה בכבל הסרט שלו לשקע המצלמה כשהמתח מנותק, ושמים לב שהשקע ב-Pi 5 קטן יותר ודורש כבל מתאים. הספרייה מותקנת מראש בגרסאות המערכת עם שולחן עבודה, ואינה מותקנת בגרסאות Lite:

התקנה, ואחריה בדיקה שהמערכת רואה את המצלמה. שם הפקודה השנייה נכון בזמן כתיבת הפרק. לא הורץ בבניית הספר.
sudo apt install python3-picamera2 --no-install-recommends
rpicam-hello --list-cameras
צילום תמונה לקובץ ולמערך. נכתב לפי מדריך הספרייה, ולא הורץ בבניית הספר כי אין מצלמה.
import time
from picamera2 import Picamera2

picam2 = Picamera2()
config = picam2.create_still_configuration(
    main={"size": (1280, 720), "format": "RGB888"})
picam2.configure(config)
picam2.start()
time.sleep(1)
picam2.capture_file("photo.jpg")
array = picam2.capture_array("main")
print(array.shape)
picam2.stop()
פקודהמה היא עושה
create_still_configuration(main=...)הגדרת צילום סטילס: המידות וסדר הבתים.
picam2.start()הפעלת המצלמה. ממתינים רגע עד שמצב התאורה מתייצב.
capture_file("photo.jpg")שמירת התמונה בקובץ. הסיומת קובעת את הפורמט.
capture_array("main")התמונה כמערך NumPy לניתוח.
טעות נפוצה: שמות הפורמטים מטעים: לפי מדריך הספרייה, הפורמט RGB888 מסדר את הבתים כחול, ירוק, אדום, וזה הסדר ש-OpenCV מצפה לו. את הספרייה אין להתקין בפקודה pip; המדריך ממליץ על apt כדי שתתאים ל-libcamera. מי שעובד בסביבה וירטואלית יוצר אותה עם --system-site-packages.

כדי לשדר מה-Pi עצמו אין צורך לכתוב שרת מאפס: בדוגמאות הספרייה הרשמית יש קובץ mjpeg_server.py שמשתמש במקודד JpegEncoder כדי לבנות את אותו שידור.

24.4 ניתוח שידור מרוחק (Analysing a Remote Stream)

כשהלוח אינו מסוגל לנתח, ה-Pi מנתח במקומו: הוא קורא את השידור של הלוח ופועל על כל פריים. הניתוח השימושי הפשוט ביותר הוא זיהוי תנועה, והוא אינו דורש למידת מכונה: משווים כל פיקסל לפיקסל המקביל לו בפריים הקודם.

מדד ההפרש בין שני פריימים
D = SN
סמלפירושיחידה
Sסכום ההפרשים המוחלטים בין כל שני פיקסלים מקבילים—
Nמספר הפיקסלים—
Dההפרש הממוצע לפיקסל—
דוגמה 24.2
פריים ייחוס של 9 פיקסלים, וערך כל אחד מהם 10. בפריים החדש הפיקסל האמצעי הוא 200, ושאר הפיקסלים נשארו כפי שהיו. נחשב את D.
פתרון:
D = SN
D = 1909
D = 21.11
D = 21.11

מכיוון שלמצלמה יש רעש, אף פריים אינו זהה לקודם בדיוק. לכן משווים את D לסף, כמו שעשינו במדידת הפוטנציומטר. נבדוק זאת על שני פריימים קטנים שכתובים בתוכנית עצמה, כך שהיא רצה בשני הלוחות בלי מצלמה:

הפריים הראשון כולל רעש בלבד, ובשני יש שינוי ברור. הסף 5 הוא דוגמה למספרים הקטנים האלה.
THRESHOLD = 5.0

REF = ((10, 10, 10), (10, 10, 10), (10, 10, 10))
NOISY = ((11, 10, 9), (10, 12, 10), (10, 9, 11))
MOVED = ((10, 10, 10), (10, 200, 10), (10, 10, 10))


def score(a, b):
    total = 0
    count = 0
    for row_a, row_b in zip(a, b):
        for p, q in zip(row_a, row_b):
            total += abs(p - q)
            count += 1
    return total / count


for name, frame in (("noisy", NOISY), ("moved", MOVED)):
    d = score(REF, frame)
    print("%s: D=%.2f motion=%s" % (name, d, d > THRESHOLD))
ESP32 + Pico W · MicroPython 1.29 · simulated board
noisy: D=0.67 motion=False
moved: D=21.11 motion=True

התוכנית הבאה עושה את אותו הדבר בעזרת OpenCV על פריימים אמיתיים, וקוראת את השידור של הלוח מסעיף 24.2. מתקינים אותה בפקודה sudo apt install python3-opencv; קריאה מכתובת HTTP דורשת גרסה שנבנתה עם תמיכה בניגון שידורים. התוכנית לא הורצה בבניית הספר, כי היא דורשת שידור אמיתי:

זיהוי תנועה בשידור מרוחק. את הסף קובעים בניסוי: מדפיסים קודם את D בסצנה נייחת, ובוחרים ערך גבוה ממנו.
import cv2

URL = "http://192.168.1.50/"
THRESHOLD = 5.0

cap = cv2.VideoCapture(URL)
previous = None
while True:
    ok, frame = cap.read()
    if not ok:
        print("no frame")
        break
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    if previous is not None:
        d = float(cv2.absdiff(gray, previous).mean())
        if d > THRESHOLD:
            print("motion: D=%.2f" % d)
    previous = gray
cap.release()
שאלה 24.3: למה התוכנית עלולה לדווח על תנועה כשמנורת החדר נכבית, ואיש אינו זז?

24.5 זיהוי תמונה ו-TinyML (Image Recognition and TinyML)

ההפרש בין שני פריימים אומר שמשהו השתנה, ואינו אומר מה. זיהוי תמונה דורש מודל: קובץ של מספרים רבים שכוונו באימון על דוגמאות, וזוהי למידת מכונה. הרצת המודל המאומן על קלט חדש נקראת הסקה.

למידת מכונה זעירה (TinyML): למידת מכונה מוקטנת: מאמנים מודל במחשב חזק, מקטינים אותו, ומריצים אותו על בקר, למשימה צרה כמו מילה מדוברת אחת, דפוס רעידות במכונה או נוכחות אדם בתמונה.

הרעיון הוא שהאימון נשאר במחשב, ורק ההסקה יורדת לשבב. איננו מתכנתים זאת בספר הזה, כי מיקרו-פייתון הרשמית אינה כוללת מודול מוכן לכך, והפרטים רחבים מפרק אחד. הטבלה מסכמת את הבחירה בתיאור מילולי. אין בה מספרי דיוק או זמן, כי הם תלויים במודל ובחומרה, ומה שנכון היום משתנה מחר, ולכן נמדוד אותם בעצמנו בפרויקט שלנו.

היכן המודל רץלמה הוא מתאיםהמחיר
על הבקר (TinyML)שאלה צרה מאוד ותשובה מיידית בלי רשתמודל קטן ומוגבל
על ה-Piניתוח תמונות ומודל שפה קטן בתוך הביתאיטי יותר ממחשב חזק, וצורך הרבה זיכרון
על מחשב ברשתמודלים גדולים יותר, בלי להוציא נתונים מהרשתמכשיר נוסף שפועל כל הזמן
שירות ענןהמודלים הגדולים ביותר בלי חומרההנתונים עוזבים את הבית, ונדרש אינטרנט

24.6 מודל שפה מקומי דרך Ollama (A Local Language Model with Ollama)

מודל שפה: מודל שאומן על טקסטים רבים ומייצר את המילה הבאה לפי הסתברות, ולכן כותב משפטים שנשמעים מובנים. הטקסט ששולחים אליו נקרא פרומפט.

Ollama היא תוכנה שמריצה מודלי שפה במחשב שלנו ופותחת להם ממשק HTTP. כלומר, הלוח שלנו משוחח איתה בפקודות שאנחנו מכירים מפרק הבקשות, בלי ספרייה חדשה. המודל עצמו אינו רץ על הלוח לעולם, אלא על Raspberry Pi 4/5 או על מחשב. מתקינים את Ollama לפי ההוראות הרשמיות באתר שלה, ואז מורידים מודל:

במקום model-name שמים מודל קטן מספריית האתר. השמות משתנים בקביעות, ולכן איננו קובעים אחד. לא הורץ בבניית הספר.
ollama pull model-name
ollama list
שימו לב: תשובה מדומה: התשובה במסך הזה מדומה, ושום מודל לא כתב אותה. הספר נבנה בלי Ollama, ולכן כתבנו בעצמנו את טקסט התשובה בתוך השרת המדומה, והתחלנו אותו במילה SIMULATED. מה שקרה בפועל הוא התוכנית עצמה: הבקשה ששלחה וקריאת התשובה. תשובה של מודל אמיתי תהיה שונה.
תשובה מדומה שהספר כתב, ולא תשובה של מודל אמיתי. הכתובת היא דוגמה: שמים את כתובת המחשב שלנו.
import requests
import wifi

URL = "http://192.168.1.20:11434/api/generate"
MODEL = "model-name"

body = {"model": MODEL,
        "prompt": "Explain PWM in one short sentence.",
        "stream": False}

wifi.connect()
r = requests.post(URL, json=body, timeout=60)
try:
    if r.status_code == 200:
        print(r.json()["response"])
    else:
        print("server said", r.status_code)
finally:
    r.close()
ESP32 + Pico W · MicroPython 1.29 · simulated board
SIMULATED ANSWER: written by the book, no model ran.
אזהרה: הלוח מחזיק את התשובה כולה בזיכרון שלו. אם התשובה ארוכה, התוכנית מסתיימת בשגיאת זיכרון, ולכן מבקשים תשובה קצרה. ההמתנה של 60 שניות היא דוגמה בלבד: מודל גדול על מכשיר קטן עלול להתעכב מאוד, ולכן קובעים אותה בניסוי.

מה אם נרצה שהמודל יחליט משהו במעגל? כאן הכלל החשוב ביותר בפרק: תשובת המודל היא קלט לא מהימן, כמו כל קלט מחוץ לתוכנית. היא אינה מגיעה למפעיל לפני בדיקה. מבקשים ממנו פורמט JSON בעזרת השדה format, מקבלים רק ערכים מרשימה צרה, וכל מה שמחוץ לה נדחה והמצב נשאר כמות שהוא:

החיישןהפרומפטהמודלOllamaבדיקת התשובההמפעילדחייה
המודל מציע והתוכנית מחליטה: אין דרך אל המפעיל אלא דרך הבדיקה.
גם זו תשובה מדומה, והשדה note שבה אומר זאת. שום מודל לא רץ כאן.
import json
import requests
import wifi

URL = "http://192.168.1.20:11434/api/generate"
MODEL = "model-name"
ALLOWED = ("on", "off")
PROMPT = ('Temperature is %.1f C. Reply only with JSON: '
          '{"fan":"on"} or {"fan":"off"}.')


def ask(prompt):
    body = {"model": MODEL, "prompt": prompt,
            "format": "json", "stream": False}
    r = requests.post(URL, json=body, timeout=60)
    try:
        if r.status_code != 200:
            return None
        return r.json()["response"]
    finally:
        r.close()


def fan_command(text):
    try:
        cmd = json.loads(text)["fan"]
    except (ValueError, KeyError, TypeError):
        return None
    return cmd if cmd in ALLOWED else None


wifi.connect()
answer = ask(PROMPT % 31.5)
print("model said:", answer)
cmd = fan_command(answer) if answer else None
if cmd is None:
    print("rejected: fan unchanged")
else:
    print("fan ->", cmd)
ESP32 + Pico W · MicroPython 1.29 · simulated board
model said: {"fan":"on","note":"simulated"}
fan -> on

כעת מחליפים את הטקסט שהשרת המדומה מחזיר, ומשאירים את התוכנית כמות שהיא. כל התשובות בטבלה הזאת נכתבו בספר כדי לבדוק את התוכנית:

התשובה המדומההשורה האחרונה בפלט
{"fan":"on","note":"simulated"}fan -> on
{"fan":"max","note":"simulated"}rejected: fan unchanged
SIMULATED: I think the fan should run.rejected: fan unchanged
הערה: "מקומי" מתאר היכן המודל רץ, ועלינו לוודא זאת. לפי התיעוד של Ollama, מודלים שרצים מקומית אינם שולחים את הטקסטים שלנו לחברה, אבל הורדת המודלים עוברת דרך האינטרנט, ובזמן כתיבת הפרק התוכנה מאפשרת גם מודלי ענן. אפשר לכבות אותם בעזרת המשתנה OLLAMA_NO_CLOUD=1.
שאלה 24.4: למה בודקים את התשובה בתוכנית, אם ביקשנו מהמודל פורמט מוגדר?

24.7 חשיבה ביקורתית ותיעוד שימוש ב-AI (Critical Thinking and Documenting AI Use)

מודל שפה אינו מחפש אמת אלא מייצר טקסט סביר. לכן הוא עלול להמציא בביטחון מלא פונקציה שאינה קיימת, מספר של הדק או מקור שלא היה ולא נברא. זה נקרא הזיה. הסכנה גדולה יותר בחשמל, כי חיבור שגוי עלול להרוס את הלוח או לפגוע במי שנוגע בו.

התיעוד הוא חלק מהיושר ומההנדסה גם יחד: מי שיקרא את הפרויקט שלנו בעוד שנה צריך לדעת מה כתבנו בעצמנו ומה בא מכלי. שומרים יומן קצר בקובץ בתוך הפרויקט:

סעיף ביומןמה כותבים
הכלי והמודלהשם והגרסה כפי שהם מופיעים בהפעלה, והתאריך.
הפרומפטמה בדיוק שאלנו, או תקציר נאמן שלו.
מה לקחנוהקבצים והשורות שבאו מן התשובה, ומה שינינו בהם.
איך וידאנוהתיעוד שעיינו בו, והניסוי שערכנו ותוצאתו.
מה דחינוהטעות שמצאנו בתשובה. זה הסעיף היקר ביותר.

לפני שמשתמשים בכלי כלשהו בעבודה שמוערכת, קוראים את הכללים של בית הספר שלנו או של הגוף שמגישים לו, ופועלים לפיהם.

שאלה 24.5: מודל הציע לנו פונקציה ב-machine שלא ראינו קודם. ציינו שתי דרכים לוודא אותה לפני השימוש.

24.8 מצלמה ופרטיות (Cameras and Privacy)

המעגל היחיד בספר הזה שעלול לפגוע במי שאינו קשור לפרויקט הוא המצלמה. תמונה חושפת מי היה במקום, מתי ועם מי. פרטיות היא אילוץ תכנון, כמו מגבלת המתח על הדק, וחושבים עליה לפני כתיבת התוכנית.

כלל מחייב: הסכמה היא חובה: כל מי שעלול להופיע בתמונה חייב לדעת שיש מצלמה ולמה, ולהסכים לפני הצילום. זה כולל עמיתים, בני משפחה ואורחים. צילום בלי הסכמה עלול להיות גם בניגוד לחוק, ולכן נברר מהם הכללים החלים במקום שבו אנחנו פועלים. בבית הספר דרוש לפני כן אישור מההנהלה, במיוחד כשהמצולמים קטינים.
פעילות: קחו את הפרויקט שלכם, או דמיינו פעמון דלת עם מצלמה, וכתבו עמוד אחד: מי עלול להופיע בתמונה? איך הוא יודע על כך ומסכים? מה נשמר, איפה ומתי נמחק? מי יכול לגשת אליו? אחר כך שנו את התכנון כך שייאסף פחות.
שאלה 24.6: מערכת מזהה תנועה במחסן בית הספר, ודרושה לה רק התרעה. הציעו תכנון שמכבד פרטיות.

רעיונות עיקריים בפרק:

סיכום הכללים בפרק:

הכללהנוסחה
גודל פריים גולמיB = W · H · c
מדד ההפרשD = SN

פקודות הפרק:

פקודהמה היא עושה
camera.init(0, format=camera.JPEG)אתחול המצלמה בקושחה ייעודית.
camera.capture()תמונת JPEG בצורת בתים, בקושחה ייעודית.
multipart/x-mixed-replaceסוג התוכן בשידור MJPEG.
picam2.capture_file(name)צילום תמונה לקובץ ב-Pi.
picam2.capture_array("main")צילום תמונה למערך ב-Pi.
cv2.VideoCapture(url)פתיחת שידור מרוחק בעזרת OpenCV.
cv2.absdiff(a, b)ההפרש המוחלט בין שני פריימים.
requests.post(url, json=body)בקשה לשרת Ollama.
r.json()["response"]טקסט התשובה מ-Ollama.

מילון מונחים:

עבריתEnglishערבית
פיקסלPixelالبكسل
פרייםFrameالإطار
קושחהFirmwareالبرنامج الثابت
שידור זרםStreamingالبثّ
זיהוי תמונהImage Recognitionالتعرّف على الصور
למידת מכונהMachine Learningالتعلّم الآلي
מודלModelالنموذج
הסקהInferenceالاستدلال
מודל שפהLanguage Modelنموذج اللغة
פרומפטPromptنصّ الطلب
הזיהHallucinationالهلوسة
פרטיותPrivacyالخصوصية
הסכמהConsentالموافقة
התשובות הסופיות לשאלות הפרק
24.1 B = 320 · 240 · 1 = 76800 byte.
24.2 20 × 5 = 100 kB/s, כלומר 800 kbit/s. המספרים האמיתיים תלויים בסצנה, בהגדרות וברשת, ולכן נמדוד אותם.
24.3 כי D מודד שינוי בערכי הפיקסלים, ולא תנועה עצמה. כיבוי המנורה משנה את כל הפיקסלים, ולכן D חוצה את הסף.
24.4 כי הבקשה אינה מבטיחה את המשמעות: המודל עלול להחזיר ערך שנכתב בפורמט תקין אבל אינו מותר, כמו "max". רק התוכנית יודעת מה בטוח למעגל.
24.5 מחפשים אותה ב-docs.micropython.org לגרסת הקושחה שלנו, ובודקים אותה על הלוח ב-REPL בפקודה dir(machine). אך קיומה אינה מספיקה: קוראים גם מה היא עושה.
24.6 הניתוח רץ במכשיר המקומי, התמונות אינן נשמרות ואינן משודרות, ונשלחים רק "יש תנועה" והשעה, ברזולוציה נמוכה, באישור הנהלת בית הספר ועם שלט גלוי לנכנסים.

שאלות לתרגול:

רמה ראשונה — בסיס
1.
מה גודלו של פריים צבעוני גולמי במידות 1280 × 720?
תשובות: B = 1280 · 720 · 3 = 2764800 byte.
2.
למה התוכנית cam_esp32.py אינה פועלת על הקושחה הרשמית של מיקרו-פייתון?
תשובות: כי אין בה את המודול camera, ולכן היא נעצרת בשורת הייבוא.
3.
באיזה שדה נמצא טקסט התשובה בתשובת Ollama? ומה קורה אם לא שולחים "stream": False?
תשובות: בשדה response. בלי זה התשובה מגיעה בחלקים עוקבים, וכל חלק הוא JSON עצמאי, ולכן אי אפשר לקרוא את התשובה כולה בבת אחת.
רמה שנייה — יישום
4.
בתוכנית frame_diff.py: מהו הערך הקטן ביותר של הפיקסל האמצעי בפריים MOVED שעושה את D גדול מ-5? הערכים האחרים אינם משתנים, והפיקסל הוא מספר שלם.
תשובות: דרוש S > 45, כלומר הפרש גדול מ-45: הערך 56.
5.
הפונקציה ask מסתיימת בשגיאת OSError אם השרת כבוי. כתבו פונקציה שעוטפת אותה ומחזירה None במקרה הזה.
תשובות:
def safe_ask(prompt):
    try:
        return ask(prompt)
    except OSError:
        print("server unreachable")
        return None
6.
כתבו יומן תיעוד לשימוש אמיתי אחד בכלי בינה מלאכותית בפרויקט שלכם, לפי חמשת הסעיפים מסעיף 24.7.
תשובות: התשובה אישית. בודקים: האם קורא אחר יכול לדעת אילו שורות באו מהכלי, ואיך וידאתם אותן?
רמה שלישית — העמקה
7.
עמית רוצה להכניס את תשובת המודל ישירות ל-exec() בלוח, "כדי שהמודל יכתוב את התוכנית בעצמו". הסבירו את הסכנה והציעו חלופה.
תשובות: כל טקסט שנוצר או נפרץ הופך לקוד שרץ על מעגל אמיתי, ועלול להפעיל מפעיל או למחוק קובץ. החלופה היא רשימת פקודות מותרות וערכים מוגדרים, ובדיקה כמו ב-fan_command.
8.
תכננו פעמון דלת: לוח עם מצלמה ששולח תמונה בלחיצה על כפתור.
(א) מה רץ על הלוח ומה על ה-Pi?
(ב) היכן נכנסת חובת ההסכמה?
(ג) מה אנחנו מודדים בעצמנו לפני שמבטיחים זמן תגובה כלשהו?
תשובות: (א) הלוח קורא את הכפתור, מצלם JPEG ושולח; ה-Pi מקבל ומאחסן לזמן מוגדר. (ב) המבקר יודע בזכות שלט גלוי, ותחום המצלמה אינו חורג מסף הדלת אל הדרך או אל השכנים. (ג) זמן הצילום, השליחה והאחסון ברשת שלנו, ולא לוקחים מספר מן האינטרנט.

מהנורית הראשונה עד כאן

התחלנו את הספר בנורית מהבהבת ובשורה אחת שכותבת 1 על הדק. אחר כך קראנו כפתור ומתח, ולמדנו שהזמן וההפסקות הם חלק מן התוכנית. שוחחנו עם שבבים דרך אפיקים וכתבנו ספריות וקבצים. משם יצאנו אל הרשת, בנינו מערכת בקרה ופרויקט שלם, הפכנו את ה-Pi לשרת שלו, וסיימנו בתמונה ובמודל שפה. הכלים גדלו מאוד, אבל ההרגל נשאר כפי שהיה מאז השורה הראשונה: יודעים מאין בא כל מספר, ולא מאמינים למה שלא בדקנו. את ההרגל הזה ניקח איתנו לכל לוח ולכל כלי שיבואו אחרי אלה.

נוסחאון הספר: כל נוסחאות הפרק הזה מרוכזות, עם סרטוטיהן וטבלאות הסמלים שלהן, בפרק 25 — נוסחאון הספר.