ZX Spectrum проходит тест Тьюринга: учим 8-битный процессор решать CAPTCHA. Assembler.. Assembler. bCNN.. Assembler. bCNN. binary neural networks.. Assembler. bCNN. binary neural networks. captcha.. Assembler. bCNN. binary neural networks. captcha. cnn.. Assembler. bCNN. binary neural networks. captcha. cnn. mnist.. Assembler. bCNN. binary neural networks. captcha. cnn. mnist. turing.. Assembler. bCNN. binary neural networks. captcha. cnn. mnist. turing. z80.. Assembler. bCNN. binary neural networks. captcha. cnn. mnist. turing. z80. zx spectrum.

Или как я потратила выходные на доказательство временного парадокса: Z80 1976 года решает CAPTCHA 2010-х в 2025 году

Вступление

Представьте: вы открываете сундук и находите пыльный ZX Spectrum. «В музей Яндекса», — думаете вы. А что если я скажу, что эта железка с 48 килобайтами памяти может с 95.5% точностью распознавать рукописные цифры и проходить те самые CAPTCHA-тесты «Я не робот» из 2010-х?

Более того: технически она могла это делать с момента выпуска в 1982 году.

<cut />

Временной парадокс в трёх актах

1976: Рождение героя

Компания Zilog выпускает процессор Z80. 8-битный, 3.5 МГц, набор инструкций включает AND, XOR, ADD. Этого достаточно для нейросетей.

2010-2015: Появление врага

Веб-сайты начинают использовать CAPTCHA с искажёнными цифрами. «Докажите, что вы человек». Порог прохождения — около 70% точности распознавания.

2025: Разрешение парадокса

Любой компьютер с Z80 (ZX Spectrum, Amstrad CPC, MSX) может проходить эти тесты. Железо было готово с 1976 года. Не хватало только пары ингредиентов.

Путешествие: от 9.3% к 95.5%

График эволюции точности выглядит как американские горки:

Точность | Что произошло
---------|--------------------------------------------------
  9.3%   | Наивные правила: "много пикселей внизу = цифра 2"
 50.1%   | Прорыв: обучение с учителем заработало
 65.6%   | Sparse binary features (автоматические AND-комбинации)
 70.9%   | Больше данных + L2-регуляризация  
 75.5%   | Сделала Z80-совместимой (самый сложный этап!)
 83.0%   | Революция: fuzzy matching через XOR+popcount
 95.5%   | Финал: простое голосование 9 перспектив

Каждый процент — это куча экспериментов. Всего получилось 70 статей документации (да, я немного ёкнулась на документировании процесса).

(Карта для навигации по этим статьям: https://github.com/oisee/mnist-z80/blob/master/META_JOURNEY_MAP.md )

Главная проблема: Z80 не умеет умножать

Традиционные нейросети используют логистическую регрессию:

probability = 1 / (1 + exp(-score))  # Z80: "Что такое exp()?"

У Z80 нет инструкций для:

  • Умножения (MUL)

  • Деления (DIV)

  • Экспоненты (EXP)

  • Логарифма (LOG)

Решение: ансамбль линейных регрессий

Вместо одной логистической модели я создала 10 линейных (по одной на цифру):

# Традиционный подход (нужны умножения):
score = w0*x0 + w1*x1 + w2*x2 + ... + b

# Мой подход (только сложения):
score = b
for i in range(len(features)):
    if features[i] == 1:  # Бинарный признак
        score += weights[i]

Использование исключительно бинарных признаков (0 или 1) превращает умножение в условное сложение!

Архитектура: как уместить нейросеть в 48КБ

Структура сети

Вход: 16×16 бинарное изображение  
    ↓ [Скользящие окна]
Слой 1: 594 признака
    • 169 окон 4×4 
    • 196 окон 3×3
    ↓ [Магическое соотношение]
Слой 2: 384 признака (55% AND + 45% XOR пар)
    ↓ [Только AND]
Слой 3: 256 признаков
    ↓ [Только AND]  
Слой 4: 128 признаков
    ↓ [Линейный классификатор]
Выход: 10 оценок → argmax

Итого: 1,362 бинарных признака, веса в int16, всё помещается в 28КБ.

«Совиный алгоритм»

Вдохновившись тем, как совы поворачивают голову для лучшего обзора, я реализовала просмотр с 9 ракурсов:

(-1,-1) (-1,0) (-1,+1)
( 0,-1) ( 0,0) ( 0,+1)
(+1,-1) (+1,0) (+1,+1)

Каждый сдвиг голосует за свою цифру. Побеждает большинство. Удивительно, но простое голосование работает лучше взвешенного!

Ключевые трюки для Z80

1. Popcount через таблицу поиска

; Подсчёт единичных битов за O(1)
; Вход: A = байт
; Выход: A = количество единиц

POPCOUNT_LUT: EQU $C000  ; Выровнено на границу страницы

popcount:
    LD   H,POPCOUNT_LUT>>8  ; Старший байт адреса
    LD   L,A                ; Байт как индекс  
    LD   A,(HL)             ; Результат одной командой!
    RET

; Таблица 256 байт с предвычисленными значениями
; Адрес $C000 выбран для скорости доступа

2. Fuzzy matching (нечёткое сравнение)

; Традиционно: паттерн совпал, если ВСЕ биты равны
; Fuzzy: паттерн совпал, если различаются ≤2 бита

check_pattern:
    LD   A,(window)     ; Текущее окно 4×4
    XOR  (HL)         ; XOR с эталонным паттерном
    CALL popcount       ; Сколько битов отличается?
    CP   3              ; Сравнить с порогом+1
    RET  C              ; C=1 если ≤2 различия (совпадение!)

3. Линейная регрессия без умножений

; score = intercept + sum(weights[i] где features[i]==1)
; Веса хранятся как int16 с масштабом 1024

compute_score:
    LD   HL,(intercept)     ; Начальное смещение
    LD   IX,features        ; Указатель на признаки
    LD   IY,weights         ; Указатель на веса
    LD   BC,1362            ; Количество признаков

.loop:
    LD   A,(IX+0)           ; Загрузить признак
    OR   A                  ; Это 0?
    JR   Z,.skip            ; Да - пропустить вес
    
    ; Добавить вес к счёту (16 бит)
    LD   E,(IY+0)
    LD   D,(IY+1)  
    ADD  HL,DE              ; score += weight
    
.skip:
    INC  IX                 ; Следующий признак
    INC  IY
    INC  IY                 ; Следующий вес (16 бит)
    DEC  BC
    LD   A,B
    OR   C
    JR   NZ,.loop
    
    ; HL = финальная оценка для цифры
    RET

Результаты: Давид vs Голиаф

Параметр

SGI Octane 1998

ZX Spectrum 1982

Процессор

MIPS R10000 @ 250МГц

Z80 @ 3.5МГц

RAM

512МБ

48КБ

Цена

$30,000

£175

Точность MNIST

98%

95.5%*

Может пройти CAPTCHA

Конечно

Тоже да!

Потребление

~100Вт

<2Вт

*На проверочном наборе из 3000 примеров (всего набор MNIST-z80 состоит из 15k образцов).

Философский вопрос

Если компьютер 1982 года может доказать, что он «не робот» сайтам 2010 года… что вообще означает слово «интеллект»?

Получается, тест Тьюринга — это не о том, как машины становятся людьми. Это о том, как мы обнаруживаем, что они всегда ими могли быть ^_^

Как повторить эксперимент

Требования

  • Python 3.8+ с NumPy и scikit-learn

  • Golang

  • Эмулятор Spectrum (Fuse, SpecEmu) или реальное железо

  • sjasmplus для сборки Z80 кода

Быстрый старт

# Клонировать репозиторий
git clone https://github.com/oisee/mnist-z80
cd mnist-z80

go build z80_mnist_demo.go
./z80_mnist_demo

Структура проекта

mnist-z80/
│   ├── META_JOURNEY_MAP.md      # 70 статей - вся история
│   └── ALGORITHM_DETAILED.md    # Подробности алгоритмов
├── src/training/
│   ├── train_fuzzy_majority.py  # Обучение модели
│   └── validate_accuracy.py     # Проверка точности
├── z80/
│   ├── fuzzy_match.asm         # Нечёткое сравнение
│   ├── majority_vote.asm       # Голосование
│   └── popcount_lut.asm        # Таблица popcount
*...

Что дальше?

Работа идёт над портированием на другие 8-битные системы, а также над интерактивной демой для zx:

  • ZX Spectrum (z80) — интерактивная демонстрация алгоритма.

  • Apple II (6502) — другая архитектура, те же принципы

  • Commodore 64 (6510) — 64КБ для экспериментов

  • БК-0010 (К1801ВМ1) — советская 16-битная PDP-11 совместимая

  • Атари 7800 (6502C) — игровая консоль как ИИ-платформа

Каждый порт доказывает, что все домашние компьютеры конца 70-х были ИИ-способными. (Особенно при условии наличия достаточного объёма памяти.)


Исходники: github.com/oisee/mnist-z80

Хотите портировать на свою любимую ретро-систему? Welcome to pull requests!

P.S. Просьба помочь с проверкой и тестированием результатов на разных датасетах =)

Claude Code – великолепный ускоритель экспериментов, проверка гипотез занимает минуты.

Документация экспериментов также осуществлена с помощью LLM.

N.B. Оригинал статьи на Английском:

https://github.com/oisee/mnist-z80/blob/master/071_ZX_SPECTRUM_PASSES_TURING_TEST.md

N.B. В первоначальном варианте статьи присутствовали артефакты машинного перевода с неточностями и ошибками. Спасибо активным комментаторам, в первую очередь @purplesyringa за помощь в исправлениях ^_^

P.P.S. Кажется результат с 95% был локальным максимумом на подмножестве примеров. Более разнообразная выборка показывает стабилизацию в районе 85%, что звучит не так круто как 95%, но всё равно круто :) ¯_(ツ)_/¯

Автор: oisee

Источник

Rambler's Top100