Взлом капчи — полученные результаты и сделанные выводы
16 мая 2014
Перед вашими глазами находится заключительная заметка из серии постов о взломе капчи. В предыдущей части мы успешно обучили многослойную нейронную сеть, распознающую буквы на капче по отдельности. Сегодня мы наконец-то соберем все полученные результаты воедино и узнаем, насколько хорошо все это хозяйство работает.
Результирующая программа выглядит следующим образом:
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 определена так:
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 с очевидным содержанием, которые при желании можно использовать для собственных экспериментов с нейросетями. Архив, содержащий декодированные капчи и их нарезку на отдельные буквы, можно скачать здесь.
На этом у меня все. Как всегда, если у вас есть вопросы или дополнения, не стесняйтесь пользоваться комментариями.
Метки: Haskell, Безопасность, Искусственный интеллект, Функциональное программирование.
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.