Взлом капчи — полученные результаты и сделанные выводы

16 мая 2014

Перед вашими глазами находится заключительная заметка из серии постов о взломе капчи. В предыдущей части мы успешно обучили многослойную нейронную сеть, распознающую буквы на капче по отдельности. Сегодня мы наконец-то соберем все полученные результаты воедино и узнаем, насколько хорошо все это хозяйство работает.

Результирующая программа выглядит следующим образом:

import System.Environment
import Captcha.Leprosorium.Recognizer

process [infile] = do
  code <- recognizeLeprosoriumCaptcha infile
  putStrLn $ "RESULT: " ++ code

process _ = do
  prog <- getProgName
  putStrLn $ "Usage: " ++ prog ++ " +RTS -N -RTS captcha.png"

main = do
  args <- getArgs
  process args

… где функция recognizeLeprosoriumCaptcha определена так:

module Captcha.Leprosorium.Recognizer where

import Captcha.Leprosorium.Recognizer.Preprocess
import Captcha.Leprosorium.Recognizer.GeneticAlgorithm
import Captcha.Leprosorium.Recognizer.Compress (cropAndCompress)
import Captcha.Leprosorium.Recognizer.NeuralNetwork
import Codec.Picture
import Codec.Picture.RGBA8

recognizeLeprosoriumCaptcha :: FilePath -> IO String
recognizeLeprosoriumCaptcha infile = do
  Right dynimg <- readImage infile
  wbimg <- imageToWhiteAndBlack $ fromDynamicImage dynimg
  img <- removeNoise wbimg
  (letters, _) <- splitImageOnLetters img
  let letters' = map cropAndCompress letters
  return $ map recognizeLetter letters'

Заметьте, как наша задача естественным образом разбивается на независимые модули и функции.

Так насколько это работает? Прогон на 3112 капчах показал, что из них полностью правильно распознается 106, то есть, примерно 3.4%. Или 1 капча из 29.36, против 1 из 11 миллионов, если просто угадывать. Много это или мало? Оказывается, что в случае с капчами 3.4% — это на самом деле очень много. Можно сказать, что угадывание семизначного числа было сведено к угадыванию числа от 1 до 30. Совсем неплохо, учитывая, что задачу я решал, что называется, «в лоб», и потратил на поиск решения всего лишь пару вечеров. На практике правильное распознавание капчи даже в 0.1% случаев уже означает, что капча сломана.

Почему так?

Допустим, мы написали робота, рассылающего спам по форумам. Пускай он работает в 100 потоков, каждый из которых размещает по одному сообщению на форуме в секунду. При правильном распознавании капчи в 3% случаев робот будет размещать по 3 сообщения каждую секунду. Что же касается оставшихся 97 неудачных попыток, робот просто вернется к этим форумам чуть позже.

Другой пример. Мы собрали базу из 10 миллионов пользователей некоторого крупного почтового сервиса и решили попробовать зайти под каждым из этих пользователей, используя какой-нибудь распространенный пароль, скажем, «qwerty». Если не ошибаюсь, это называется обратный брутфорс. Не во всех сервисах предусмотрена адекватная защита от него. Так вот, правильно угадывая капчу в 3% случаев мы успешно совершим 300 000 попыток входа, что не так уж и мало. К тому же, мы не ограничены в количестве повторных попыток.

Ну хорошо, а что делать, если внутри нас с вами сидит маленький перфекционист и говорит, что 3% — это слишком мало? Какой точности распознавания вообще можно добиться при помощи нейронных сетей? Рассмотрим стандартный dataset от MNIST с изображениями рукописных цифр. Нейронные сети без особого труда распознают этот набор с точностью 95% и более (напоминаю, мы получили всего лишь 55%). А ансамбль из 25 сетей 784-800-10 при дополнительной обработке входных данных достигает точности 99.61%.

Чтобы добиться более высокой точности, неплохой идеей, по всей видимости, будет более точно определять границы символов при нарезании картинки на буквы. Используя центр масс и процентили или еще как-то. Можно попробовать использовать сети свертки (хабр). Также у Хайкина описано несколько способов усилений нейронных сетей — усреднение по ансамблю и усиление за счет фильтрации. В частности, рекурсивное применение последнего приема позволяет сделать из сети, работающей чуть лучше, чем простое угадывание, сеть со сколь угодно малой ошибкой распознавания. Также можно поэкспериментировать с обучением отдельных сетей для каждой буквы и/или разбиением изображения на 4-9 частей и обучением отдельной сети для каждой этой части, с дальнейшим объединением выходов полученных сетей. В общем, можно распознавать капчи с куда большей точностью, чем 3%. Все зависит только от количества времени, которым вы располагаете, и желания перебирать варианты.

На всякий случай отмечу, что я никого не призываю к рассылке спама или чему-то подобному. В действительности, тут куда проще и эффективнее использовать распознавание при помощи «толпы китайцев». Поэтому всякие там нейросети и генетические алгоритмы не представляют для спамеров и прочих нехороших людей особой ценности. Если я к чему-то и призываю, так это к тому, чтобы вы интересовались алгоритмами искусственного интеллекта. Не ради конечного результата «смотрите, вот мы тут такие классные поломали Лепру», а ради экспиренса и левелапов, которые ждут вас на пути к получению этого результата.

Всего-навсего задавшись вопросом «интересно, а как можно написать программу, распознающую капчу» я:

  • Лишний раз попрактиковался в функциональном программировании и благодаря этому узнал кое-что новое, в частности, о том, сколько памяти в Haskell выделяется под какие типы;
  • Посмотрел, какие бывают GUI для FANN, правда, я пришел к выводу, что все они ни на что не годятся, и по этой причине решил писать свою реализацию МНС;
  • Вспомнил, как работают генетические алгоритмы, а также написал параллелизуемую реализацию ГА;
  • По книжкам разобрался в работе алгоритма обратного распространения ошибки, поэкспериментировал с обучением нейросетей при помощи ГА, написал параллелизуемую реализацию МНС;
  • Выложил на Hackage три пакета (первый, второй, третий);
  • Подготовил собственные обучающие и проверочные наборы данных для дальнейших экспериментов с нейронными сетями;
  • Написал в блог в общей сложности штук восемь постов, которые, надеюсь, вам было интересно читать;

Могу ошибаться, но, кажется, я интересно и с пользой провел время. Воистину, взлом капчи оказался замечательным code kata.

Все исходники к этой заметке вы найдете в этом репозитории на GitHub. В этих же исходниках можно найти полученную в итоге нейронную сеть, а также файлы Trainset.hs и Testset.hs с очевидным содержанием, которые при желании можно использовать для собственных экспериментов с нейросетями. Архив, содержащий декодированные капчи и их нарезку на отдельные буквы, можно скачать здесь.

На этом у меня все. Как всегда, если у вас есть вопросы или дополнения, не стесняйтесь пользоваться комментариями.

Метки: , , , .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.