Изучая Python3, я портировал (как смог) библиотечку PyBrain. Об этом я уже писал здесь.

Теперь же я хочу немного «поиграть» с данной библиотечкой. Как я уже говорил в предыдущем посте, питон я только начал изучать, так что все написанное в этой статье не стоит воспринимать как Истину. Изучение — это путь, и он извилист.
Задачу поставим перед искусственной нейронной сетью (ИНС) весьма простую — классификацию, а именно: распознавание букв латинского алфавита.
Вроде бы классический пример, про него уже писали на хабре неоднократно: «Что такое искусственные нейронные сети?», «Нейронные сети и распознавание символов» и т.д.
Но моей целью стоит изучение питона на не самых простых примерах. Т.е. учимся сразу на сложном и незнакомом. Так мы найдем в два раза больше граблей, что позволит нам копнуть в глубины языка, разбираясь с «почему не работает?».
Под хабракатом вас ждёт: описание способа подготовки данных на PyQt4, использование модуля argparse, ну и конечно же PyBrain!
Почитав статьи здесь на хабре и не только, понимаешь, что сложно не написать/создать/спроектировать ИНС, а подготовить для неё набор обучающих и тестовых данных. Поэтому наша задача разбивается на две подзадачи:
Делать будем именно в этом порядке. Так сказать по нарастающей.
Давайте уточним задание: размер картинки с изображением буквы будет, скажем, 64 на 64 пиксела (итого 4096 входа у ИНС).
Для этого нам потребуется написать генератор этих картинок. И писать его мы будем, естесственно, на python'е.
Данные для обучения будут включать в себя:
Исходя из этого напишем генератор, которому на вход подаются параметры:
Для написания генератора нам понадобится информация по методам обработки изображений в python'е. Google нам помог не сильно. Предложил использовать либо Python Imaging Library — сокращённо PIL, либо PyGame.
Только вот незадача. Первый — только для python2, и последний релиз был в 2009 году. Хотя на github.com есть его форк под третий питон. Почитав мануал я понял, что не все так просто.
PyGame — вариант более интересный, даже мануал его почитал подольше. Понял, что надо конкретно разбираться в библиотеке, а с наскока что-то сделать не получится. Да и использовать микроскоп для забивания гвоздей — тоже не вариант. Не для того эта библиотека предназначена.
Погуглил ещё. Есть ещё pythonmagick, но он только для UNIX-like систем. И тут меня осенило! PyQt4!
C Qt4 я неплохо знаком, на С++/Qt много писал. Да библиотечка эта — как швейцарский нож. Хочешь — бутылку пива откроет, хочешь — из куска деревяшки красивую фигурку вырежет. Главное — уметь ножом пользоваться. На Qt и остановимся.
Поиск по хабру дал нам совсем немного информации по PyQt. Ну да ничего — разберёмся.
Первое, что требуется — это установить PyQt4. С этим, я надеюсь, читатель справится — останавливаться на этом не буду. Перейду сразу к использованию.
Сделаем импорт необходимых модулей, и приготовим «рыбу» для программы на PyQt4.
Строка с
Теперь займемся наполнением «рыбы» рабочей логикой. Добавим функцию save, которая будет сохранять картинку с заданными параметрами.
С параметрами функции — всё ясно. А вот по содержимому поясню.
Сначала создается объект класса QImage, который позволяет создавать/обрабатывать свои изображения у себя в программе. Очень мощный и гибкий инструмент. Производится закраска белым цветом всего изображения размером 64 на 64 пиксела.
Затем создается ��бъект типа QPainter, которому передается ссылка img. Этот класс позволяет рисовать на контексте устройства или, если точнее, на канве любого класса, унаследованного от QPaintDevice. А как раз таким классом и явлется QImage.
Устанавливаем черное перо, шрифт и рисуем буковку. По умолчанию — размером 40 (что почти занимает всё поле изображения) и с размещением по центру.
Ну а затем сохраняем изображение в файл. Всё просто и очевидно.
Осталось малое. Разбор параметров командной строки.
Это можно делать в лоб (с кучей if, либо жёстко задав формат входных данных), а можно с использованием всяких продвинутых модулей типа getopt или argparse. Последний, думаю, мы и изучим.
Программа наша на вход будет получать следующие параметры: шрифт, размер шрифта и директория, куда будут сваливаться готовые картинки.
Выравнивание пока оставим до лучших времен.
Чтение этого мануала как бы подсказывает нам, что надо просто использовать вот такой кусок кода:
Таким образом мы описываем наши параметры, обо всём остальном позаботится модуль argparse. Что мне понравилось, так это автоматический показ usage и автоматическая генерация помощи по параметрам. При чём argparse ещё один аргумент (-h) в наш список сам добавил. За что ему большое спасибо. Как настоящий и ленивый программист, я очень не люблю писать хелпы и прочую документацию. Это очко в пользу argparse. Буду чаще им пользоваться.
Справка по программе у нас получается такой:
Теперь добавим проверку на существование пути директории и разворачивание диапазона букв. Для этого воспользуемся регулярными выражениями. Они не особо нужны в данном случае, но надо же программу посолидней сделать! Для этого нам потребуются модули os, os.path и re.
Ну вот. Осталось организовать цикл и передать все буковки по очереди на отрисовку.
Последний штрих — сделаем обвязочку поверх функции save() и назовём её saveWrap(). Как оригинально, не правда ли? Собственно она не делает ничего сверхестесственного, просто генерирует имя для файла исходя из передавваемых в функцию save() параметров.
Итого весь генератор занял у нас всего 55 строк (код приведён в конце статьи). Это ли не прекрасно?
Причем я уверен, что гуру питона наверняка найдут ещё кучу возможностей для оптимизации. Но зачем? Всё работает, код достаточно прост и лаконичен. Прям глаз радуется.
Теперь начнем работу над ИНС. Для начала ознакомимся с возможностями PyBrain.
На вход у нас подается изображение (в серых тонах), задаваемое значениями яркости каждого пикселя от 0 до 1.
Для начала сделаем ИНС, которая будет распознавать ограниченный набор символов и без разных регистров. Обучим на данных с одним шрифтом и посмотрим, как сеть распознает эти символы с другим шрифтом (тестовый набор). Возьмем, например, символы A, B, C, D, Z.
Поскольку у нас сеть будет учиться буковкам, изображения которых имеют размер 64 на 64 пиксела, то количество входов нашей сети будет равно 4096.
Самих распознаваемых букв у нас будет только 5, соответственно, и количество выходов из сети равно пяти.
Теперь встаёт вопрос: нужны ли нам скрытые слои? И если да, то сколько?
Я решил обойтись без скрытых слоёв, поэтому для создания объекта сети делаю следующий вызов:
Для создания одного скрытого слоя размером в 64 нейрона и типом скрытого слоя SoftmaxLayer надо выполнить следующий вызов:
К сожалению, в статье было сказано про данную функцию, но не было дано описание. Исправлю этот недочёт.
Те, кому интересно, могут выбрать другие варианты, исправив/раскомментировав вызов функции buildNetwork() в файле brain.py.
Итак, пришло время взяться за обучение. При помощи нашей программки gen_pic.py генерируем нужные буквы.
Я сделал это так:
Процесс загрузки из картинки данных и преобразование RGB-цвета в тона серого позвольте оставить за кадром. Там ничего особо интересного. Кому всё-таки жутко интересно как же это сделано — может увидеть сам в файле brain.py в функции get_data().
Само обучение производится в функции init_brain(). В эту функцию передается обучающая выборка, максимальное количество эпох для обучения и опционально тип Trainer'a, а сама функция возвращает объект уже обученной сети.
Ключевые строки создания и обучения сети выглядят так (полный код приведён к конце статьи):
Кратко поясню, что где.
ClassificationDataSet — специальный вид датасетов для целей классификации. Ему достаточно исходных данных и порядкового номера класса (trans[out]) для составления выборки.
Функция _convertToOneOfMany() переводит эти самые номера классов в значения выходного слоя.
Далее передаём «учителю» сеть и говорим, что нас интересует вывод дополнительной информации (библиотека будет печатать в консоль промежуточные вычисления).
Даём учителю датасет с обучающей выборкой (setData()) и запускаем обучение (trainUntilConvergence()), которое будет обучать либо пока сеть не сойдётся, либо пока не выйдет максимальное количество эпох обучения.
Итак, цель достигнута.
Код написан и работает. Генератор, правда, может намного больше, чем сеть, нами сегодня построенная, в текущем её виде. Но зато осталось непаханное поле для Вас, дорогой %username%! Есть чего поправить, где подредактировать, что переписать…
Добавлю ещё, что я потестировал два Trainer'a — BackpropTrainer и RPropMinusTrainer.
Скорость работы у алгоритма обратного распространения ошибки (BackpropTrainer) плохая, сходится очень медленно. Из-за чего обучение занимает много времени.
Поменяв одну строку в brain.py можно посмотреть на работу RPropMinusTrainer. Он значительно шустрее и показывает довольно неплохие результаты.
Добавлю ещё, что добиться 100% распознавания даже для обучающей выборки мне не удалось, может надо было подбирать количество слоёв и количество нейронов в каждом — не знаю. Практического смылса в данной программе особо нету, но для изучения Python3 задача весьма неплоха: здесь и работа со списками, и со словарями, и обработка параметров командной строки, работа с изображениями, регулярные выражения, работа с файловой системой (модули os и os.path).
Для желающих поиграться скажу лишь одно — программа brain.py потребует доработки, если вы захотите изменить количество букв или поменять их на другие. Доработки небольшие и несложные.
Если будут возникать вопросы — пишите в личку, но думаю, что вы и сами разберётесь что, где и как.
Будет время, может перепишу код посимпатичнее и сделаю его более настраиваемым, введу больше параметров.
Исходные тексты Вы можете взять в спойлерах ниже.
На этом знакомство с PyBrain на сегодня считаю законченным. До новых встреч!
upd: по просьбе monolithed исправил регулярное выражение.

Теперь же я хочу немного «поиграть» с данной библиотечкой. Как я уже говорил в предыдущем посте, питон я только начал изучать, так что все написанное в этой статье не стоит воспринимать как Истину. Изучение — это путь, и он извилист.
Задачу поставим перед искусственной нейронной сетью (ИНС) весьма простую — классификацию, а именно: распознавание букв латинского алфавита.
Вроде бы классический пример, про него уже писали на хабре неоднократно: «Что такое искусственные нейронные сети?», «Нейронные сети и распознавание символов» и т.д.
Но моей целью стоит изучение питона на не самых простых примерах. Т.е. учимся сразу на сложном и незнакомом. Так мы найдем в два раза больше граблей, что позволит нам копнуть в глубины языка, разбираясь с «почему не работает?».
Под хабракатом вас ждёт: описание способа подготовки данных на PyQt4, использование модуля argparse, ну и конечно же PyBrain!
Почитав статьи здесь на хабре и не только, понимаешь, что сложно не написать/создать/спроектировать ИНС, а подготовить для неё набор обучающих и тестовых данных. Поэтому наша задача разбивается на две подзадачи:
- подготовить данные для обучения;
- спроектировать и обучить ИНС.
Делать будем именно в этом порядке. Так сказать по нарастающей.
Подготовка данных
Техническое задание
Давайте уточним задание: размер картинки с изображением буквы будет, скажем, 64 на 64 пиксела (итого 4096 входа у ИНС).
Для этого нам потребуется написать генератор этих картинок. И писать его мы будем, естесственно, на python'е.
Данные для обучения будут включать в себя:
- буквы латиницы в нижнем регистре
- буквы латиницы в верхнем регистре
- буквы могут быть разного размера (опционально)
- начертание может быть различным (могут использоваться разные шрифты)
Исходя из этого напишем генератор, которому на вход подаются параметры:
- список букв, например, abc или f-x(диапазон)
- размер шрифта, например, 40
- используемый шрифт
- путь к папке, куда будут складываться сгенерированные изображения
Поиск метода работы с изображениями
Для написания генератора нам понадобится информация по методам обработки изображений в python'е. Google нам помог не сильно. Предложил использовать либо Python Imaging Library — сокращённо PIL, либо PyGame.
Только вот незадача. Первый — только для python2, и последний релиз был в 2009 году. Хотя на github.com есть его форк под третий питон. Почитав мануал я понял, что не все так просто.
PyGame — вариант более интересный, даже мануал его почитал подольше. Понял, что надо конкретно разбираться в библиотеке, а с наскока что-то сделать не получится. Да и использовать микроскоп для забивания гвоздей — тоже не вариант. Не для того эта библиотека предназначена.
Погуглил ещё. Есть ещё pythonmagick, но он только для UNIX-like систем. И тут меня осенило! PyQt4!
C Qt4 я неплохо знаком, на С++/Qt много писал. Да библиотечка эта — как швейцарский нож. Хочешь — бутылку пива откроет, хочешь — из куска деревяшки красивую фигурку вырежет. Главное — уметь ножом пользоваться. На Qt и остановимся.
Поиск по хабру дал нам совсем немного информации по PyQt. Ну да ничего — разберёмся.
Пишем генерацию и сохранение изображения
Первое, что требуется — это установить PyQt4. С этим, я надеюсь, читатель справится — останавливаться на этом не буду. Перейду сразу к использованию.
Сделаем импорт необходимых модулей, и приготовим «рыбу» для программы на PyQt4.
#!/usr/bin/env python3
import sys
from PyQt4.QtGui import *
from PyQt4.Qt import *
def main():
app = QApplication([])
# some code here
if __name__ == "__main__":
sys.exit(main())
Строка с
app = QApplication([]) очень важна. Не забывайте её. У меня без неё python вылетает с SIGFAULT'ом и не выдает никаких предупреждений и ошибок.Теперь займемся наполнением «рыбы» рабочей логикой. Добавим функцию save, которая будет сохранять картинку с заданными параметрами.
def save(png_file, letter = 'A', font = "Arial", size = 40, align = Qt.AlignCenter):
img = QImage(64,64, QImage.Format_RGB32)
img.fill(Qt.white)
p = QPainter(img)
p.setPen(Qt.black)
p.setFont(QFont(font,size))
p.drawText(img.rect(), align, letter)
p.end()
img.save(png_file)
С параметрами функции — всё ясно. А вот по содержимому поясню.
Сначала создается объект класса QImage, который позволяет создавать/обрабатывать свои изображения у себя в программе. Очень мощный и гибкий инструмент. Производится закраска белым цветом всего изображения размером 64 на 64 пиксела.
Затем создается ��бъект типа QPainter, которому передается ссылка img. Этот класс позволяет рисовать на контексте устройства или, если точнее, на канве любого класса, унаследованного от QPaintDevice. А как раз таким классом и явлется QImage.
Устанавливаем черное перо, шрифт и рисуем буковку. По умолчанию — размером 40 (что почти занимает всё поле изображения) и с размещением по центру.
Ну а затем сохраняем изображение в файл. Всё просто и очевидно.
Последние штрихи
Осталось малое. Разбор параметров командной строки.
Это можно делать в лоб (с кучей if, либо жёстко задав формат входных данных), а можно с использованием всяких продвинутых модулей типа getopt или argparse. Последний, думаю, мы и изучим.
Программа наша на вход будет получать следующие параметры: шрифт, размер шрифта и директория, куда будут сваливаться готовые картинки.
Выравнивание пока оставим до лучших времен.
Чтение этого мануала как бы подсказывает нам, что надо просто использовать вот такой кусок кода:
p = argparse.ArgumentParser(description='Symbols image generator')
p.add_argument('-f','--font', default='Arial', help='Font name, default=Arial')
p.add_argument('-s','--size', type=int, default=40, help='Font size, default=40')
p.add_argument('-d','--dir', default='.', help='Output directory, default=current')
p.add_argument('letters', help='Array of letters(abc) or range (a-z)')
args = p.parse_args()
Таким образом мы описываем наши параметры, обо всём остальном позаботится модуль argparse. Что мне понравилось, так это автоматический показ usage и автоматическая генерация помощи по параметрам. При чём argparse ещё один аргумент (-h) в наш список сам добавил. За что ему большое спасибо. Как настоящий и ленивый программист, я очень не люблю писать хелпы и прочую документацию. Это очко в пользу argparse. Буду чаще им пользоваться.
Справка по программе у нас получается такой:
usage: gen_pic.py [-h] [-f FONT] [-s SIZE] [-d DIR] letters Symbols image generator positional arguments: letters Array of letters(abc) or range (a-z) optional arguments: -h, --help show this help message and exit -f FONT, --font FONT Font name, default=Arial -s SIZE, --size SIZE Font size, default=40 -d DIR, --dir DIR Output directory, default=current
Теперь добавим проверку на существование пути директории и разворачивание диапазона букв. Для этого воспользуемся регулярными выражениями. Они не особо нужны в данном случае, но надо же программу посолидней сделать! Для этого нам потребуются модули os, os.path и re.
if os.path.exists(args.dir):
os.mkdir(args.dir)
if re.match('^([a-z]-[a-z])|([A-Z]-[A-Z])$', args.letters):
begin = args.letters[0]
end = args.letters[2]
if (ord(end)-ord(begin))>26:
print("Error using letters. Only A-Z or a-z available, not A-z.")
p.print_help()
return
letters = [chr(a) for a in range(ord(begin),ord(end)+1)]
else:
letters = args.letters
Ну вот. Осталось организовать цикл и передать все буковки по очереди на отрисовку.
Последний штрих — сделаем обвязочку поверх функции save() и назовём её saveWrap(). Как оригинально, не правда ли? Собственно она не делает ничего сверхестесственного, просто генерирует имя для файла исходя из передавваемых в функцию save() параметров.
Итого весь генератор занял у нас всего 55 строк (код приведён в конце статьи). Это ли не прекрасно?
Причем я уверен, что гуру питона наверняка найдут ещё кучу возможностей для оптимизации. Но зачем? Всё работает, код достаточно прост и лаконичен. Прям глаз радуется.
Разработка ИНС
Теперь начнем работу над ИНС. Для начала ознакомимся с возможностями PyBrain.
Лирическое отступление про PyBrain и Python3
Хочу уточнить, что я тестировал программу только под Python3 и пользовался портом PyBrain, который вы можете найти здесь. Пока отлаживал программу нашёл пару косяков в самом порте библиотеки.
Очень порадовал комментарий в том месте, где вываливалась библиотека:
Видимо этот хак в Python3 не сработал.
Очень порадовал комментарий в том месте, где вываливалась библиотека:
# FIXME: the next line keeps arac from producing NaNs. I don't # know why that is, but somehow the __str__ method of the # ndarray class fixes something, # str(outerr)
Видимо этот хак в Python3 не сработал.
На вход у нас подается изображение (в серых тонах), задаваемое значениями яркости каждого пикселя от 0 до 1.
Для начала сделаем ИНС, которая будет распознавать ограниченный набор символов и без разных регистров. Обучим на данных с одним шрифтом и посмотрим, как сеть распознает эти символы с другим шрифтом (тестовый набор). Возьмем, например, символы A, B, C, D, Z.
Поскольку у нас сеть будет учиться буковкам, изображения которых имеют размер 64 на 64 пиксела, то количество входов нашей сети будет равно 4096.
Самих распознаваемых букв у нас будет только 5, соответственно, и количество выходов из сети равно пяти.
Теперь встаёт вопрос: нужны ли нам скрытые слои? И если да, то сколько?
Я решил обойтись без скрытых слоёв, поэтому для создания объекта сети делаю следующий вызов:
net = buildNetwork(64 * 64, 5)
Для создания одного скрытого слоя размером в 64 нейрона и типом скрытого слоя SoftmaxLayer надо выполнить следующий вызов:
net = buildNetwork(64 * 64, 8 * 8, 5, hiddenclass=SoftmaxLayer)
К сожалению, в статье было сказано про данную функцию, но не было дано описание. Исправлю этот недочёт.
Ликбез про buildNetwork()
Функция buildNetwork() предназначена для быстрого создания FeedForward-сети и имеет следующий формат:
layers — список или кортеж целых чисел, котоый содержит количество нейронов в каждом слое
Опции записываются в виде "name = val" и включают в себя:
bias (default = True) — начилие смещения в скрытых слоях
outputbias (default = True) — начилие смещения в выходном слое
hiddenclass и outclass — задают типы для скрытых слоёв и выходного слоя соответственно. Должны быть потомком класса NeuronLayer. Предопределенные значения — это GaussianLayer, LinearLayer, LSTMLayer, MDLSTMLayer, SigmoidLayer, SoftmaxLayer, TanhLayer.
Если установлен флаг recurrent, то будет создана сеть RecurrentNetwork, иначе FeedForwardNetwork.
Если установлен флаг fast, то будет использоваться более быстрые сети arac, в противном же случае — это будет собственная реализация PyBrain сети на питоне.
pybrain.tools.shortcuts.buildNetwork(*layers, **options)
layers — список или кортеж целых чисел, котоый содержит количество нейронов в каждом слое
Опции записываются в виде "name = val" и включают в себя:
bias (default = True) — начилие смещения в скрытых слоях
outputbias (default = True) — начилие смещения в выходном слое
hiddenclass и outclass — задают типы для скрытых слоёв и выходного слоя соответственно. Должны быть потомком класса NeuronLayer. Предопределенные значения — это GaussianLayer, LinearLayer, LSTMLayer, MDLSTMLayer, SigmoidLayer, SoftmaxLayer, TanhLayer.
Если установлен флаг recurrent, то будет создана сеть RecurrentNetwork, иначе FeedForwardNetwork.
Если установлен флаг fast, то будет использоваться более быстрые сети arac, в противном же случае — это будет собственная реализация PyBrain сети на питоне.
Те, кому интересно, могут выбрать другие варианты, исправив/раскомментировав вызов функции buildNetwork() в файле brain.py.
Обучение ИНС
Итак, пришло время взяться за обучение. При помощи нашей программки gen_pic.py генерируем нужные буквы.
Я сделал это так:
./gen_pic.py -d ./learn -f FreeMono ABCDZ ./gen_pic.py -d ./learn -f Times ABCDZ ./gen_pic.py -d ./learn -f Arial ABCDZ ./gen_pic.py -d ./test -f DroidMono ABCDZ ./gen_pic.py -d ./test -f Sans ABCDZ
Процесс загрузки из картинки данных и преобразование RGB-цвета в тона серого позвольте оставить за кадром. Там ничего особо интересного. Кому всё-таки жутко интересно как же это сделано — может увидеть сам в файле brain.py в функции get_data().
Само обучение производится в функции init_brain(). В эту функцию передается обучающая выборка, максимальное количество эпох для обучения и опционально тип Trainer'a, а сама функция возвращает объект уже обученной сети.
Ключевые строки создания и обучения сети выглядят так (полный код приведён к конце статьи):
def init_brain(learn_data, epochs, TrainerClass=BackpropTrainer):
...
net = buildNetwork(64 * 64, 5, hiddenclass=LinearLayer)
# fill dataset with learn data
ds = ClassificationDataSet(4096, nb_classes=5, class_labels=['A', 'B', 'C', 'D', 'Z'])
for inp, out in learn_data:
ds.appendLinked(inp, [trans[out]])
...
ds._convertToOneOfMany(bounds=[0, 1])
...
trainer = TrainerClass(net, verbose=True)
trainer.setData(ds)
trainer.trainUntilConvergence(maxEpochs=epochs)
return net
Кратко поясню, что где.
ClassificationDataSet — специальный вид датасетов для целей классификации. Ему достаточно исходных данных и порядкового номера класса (trans[out]) для составления выборки.
Функция _convertToOneOfMany() переводит эти самые номера классов в значения выходного слоя.
Далее передаём «учителю» сеть и говорим, что нас интересует вывод дополнительной информации (библиотека будет печатать в консоль промежуточные вычисления).
Даём учителю датасет с обучающей выборкой (setData()) и запускаем обучение (trainUntilConvergence()), которое будет обучать либо пока сеть не сойдётся, либо пока не выйдет максимальное количество эпох обучения.
Выводы
Итак, цель достигнута.
Код написан и работает. Генератор, правда, может намного больше, чем сеть, нами сегодня построенная, в текущем её виде. Но зато осталось непаханное поле для Вас, дорогой %username%! Есть чего поправить, где подредактировать, что переписать…
Добавлю ещё, что я потестировал два Trainer'a — BackpropTrainer и RPropMinusTrainer.
Скорость работы у алгоритма обратного распространения ошибки (BackpropTrainer) плохая, сходится очень медленно. Из-за чего обучение занимает много времени.
Поменяв одну строку в brain.py можно посмотреть на работу RPropMinusTrainer. Он значительно шустрее и показывает довольно неплохие результаты.
Добавлю ещё, что добиться 100% распознавания даже для обучающей выборки мне не удалось, может надо было подбирать количество слоёв и количество нейронов в каждом — не знаю. Практического смылса в данной программе особо нету, но для изучения Python3 задача весьма неплоха: здесь и работа со списками, и со словарями, и обработка параметров командной строки, работа с изображениями, регулярные выражения, работа с файловой системой (модули os и os.path).
Для желающих поиграться скажу лишь одно — программа brain.py потребует доработки, если вы захотите изменить количество букв или поменять их на другие. Доработки небольшие и несложные.
Если будут возникать вопросы — пишите в личку, но думаю, что вы и сами разберётесь что, где и как.
Будет время, может перепишу код посимпатичнее и сделаю его более настраиваемым, введу больше параметров.
Исходные тексты Вы можете взять в спойлерах ниже.
Код файла gen_pic.py
#!/usr/bin/env python3
import sys
import argparse
import re
import os
import os.path
from PyQt4.QtGui import *
from PyQt4.Qt import *
def saveWrap(dir='.', letter='A', font="Arial", size=40, align=Qt.AlignCenter):
png_file = dir + "/" + font + "_" + letter + "_" + str(size) + ".png"
save(png_file, letter, font, size, align)
def save(png_file, letter='A', font="Arial", size=40, align=Qt.AlignCenter):
img = QImage(64, 64, QImage.Format_RGB32)
img.fill(Qt.white)
p = QPainter(img)
p.setPen(Qt.black)
p.setFont(QFont(font, size))
p.drawText(img.rect(), align, letter)
p.end()
img.save(png_file)
def main():
app = QApplication([])
p = argparse.ArgumentParser(description='Symbols image generator')
p.add_argument('-f', '--font', default='Arial', help='Font name, default=Arial')
p.add_argument('-s', '--size', type=int, default=40, help='Font size, default=40')
p.add_argument('-d', '--dir', default='.', help='Output directory, default=current')
p.add_argument('letters', help='Array of letters(abc) or range (a-z)')
args = p.parse_args()
path = os.path.abspath(args.dir)
if not os.path.exists(path):
print("Directory not exists, created!")
os.makedirs(path)
if re.match('^([a-z]-[a-z])|([A-Z]-[A-Z])$', args.letters):
begin = args.letters[0]
end = args.letters[2]
if (ord(end) - ord(begin)) > 26:
print("Error using letters. Only A-Z or a-z available, not A-z.")
p.print_help()
return
letters = [chr(a) for a in range(ord(begin), ord(end) + 1)]
else:
letters = args.letters
for lett in letters:
saveWrap(path, lett, args.font, args.size)
return 0
if __name__ == "__main__":
sys.exit(main())
Код файла brain.py
#!/usr/bin/env python3
import sys
import argparse
import re
import os
import os.path
from PyQt4.QtGui import *
from PyQt4.Qt import *
from pybrain.tools.shortcuts import buildNetwork
from pybrain.datasets import ClassificationDataSet
from pybrain.structure.modules import SigmoidLayer, SoftmaxLayer, LinearLayer
from pybrain.supervised.trainers import BackpropTrainer
from pybrain.supervised.trainers import RPropMinusTrainer
def init_brain(learn_data, epochs, TrainerClass=BackpropTrainer):
if learn_data is None:
return None
print ("Building network")
# net = buildNetwork(64 * 64, 8 * 8, 5, hiddenclass=TanhLayer)
# net = buildNetwork(64 * 64, 32 * 32, 8 * 8, 5)
net = buildNetwork(64 * 64, 5, hiddenclass=LinearLayer)
# fill dataset with learn data
trans = {
'A': 0, 'B': 1, 'C': 2, 'D': 3, 'Z': 4
}
ds = ClassificationDataSet(4096, nb_classes=5, class_labels=['A', 'B', 'C', 'D', 'Z'])
for inp, out in learn_data:
ds.appendLinked(inp, [trans[out]])
ds.calculateStatistics()
print ("\tNumber of classes in dataset = {0}".format(ds.nClasses))
print ("\tOutput in dataset is ", ds.getField('target').transpose())
ds._convertToOneOfMany(bounds=[0, 1])
print ("\tBut after convert output in dataset is \n", ds.getField('target'))
trainer = TrainerClass(net, verbose=True)
trainer.setData(ds)
print("\tEverything is ready for learning.\nPlease wait, training in progress...")
trainer.trainUntilConvergence(maxEpochs=epochs)
print("\tOk. We have trained our network.")
return net
def loadData(dir_name):
list_dir = os.listdir(dir_name)
list_dir.sort()
list_for_return = []
print ("Loading data...")
for filename in list_dir:
out = [None, None]
print("Working at {0}".format(dir_name + filename))
print("\tTrying get letter name.")
lett = re.search("\w+_(\w)_\d+\.png", dir_name + filename)
if lett is None:
print ("\tFilename not matches pattern.")
continue
else:
print("\tFilename matches! Letter is '{0}'. Appending...".format(lett.group(1)))
out[1] = lett.group(1)
print("\tTrying get letter picture.")
out[0] = get_data(dir_name + filename)
print("\tChecking data size.")
if len(out[0]) == 64 * 64:
print("\tSize is ok.")
list_for_return.append(out)
print("\tInput data appended. All done!")
else:
print("\tData size is wrong. Skipping...")
return list_for_return
def get_data(png_file):
img = QImage(64, 64, QImage.Format_RGB32)
data = []
if img.load(png_file):
for x in range(64):
for y in range(64):
data.append(qGray(img.pixel(x, y)) / 255.0)
else:
print ("img.load({0}) failed!".format(png_file))
return data
def work_brain(net, inputs):
rez = net.activate(inputs)
idx = 0
data = rez[0]
for i in range(1, len(rez)):
if rez[i] > data:
idx = i
data = rez[i]
return (idx, data, rez)
def test_brain(net, test_data):
for data, right_out in test_data:
out, rez, output = work_brain(net, data)
print ("For '{0}' our net said that it is '{1}'. Raw = {2}".format(right_out, "ABCDZ"[out], output))
pass
def main():
app = QApplication([])
p = argparse.ArgumentParser(description='PyBrain example')
p.add_argument('-l', '--learn-data-dir', default="./learn", help="Path to dir, containing learn data")
p.add_argument('-t', '--test-data-dir', default="./test", help="Path to dir, containing test data")
p.add_argument('-e', '--epochs', default="1000", help="Number of epochs for teach, use 0 for learning until convergence")
args = p.parse_args()
learn_path = os.path.abspath(args.learn_data_dir) + "/"
test_path = os.path.abspath(args.test_data_dir) + "/"
if not os.path.exists(learn_path):
print("Error: Learn directory not exists!")
sys.exit(1)
if not os.path.exists(test_path):
print("Error: Test directory not exists!")
sys.exit(1)
learn_data = loadData(learn_path)
test_data = loadData(test_path)
# net = init_brain(learn_data, int(args.epochs), TrainerClass=RPropMinusTrainer)
net = init_brain(learn_data, int(args.epochs), TrainerClass=BackpropTrainer)
print ("Now we get working network. Let's try to use it on learn_data.")
print("Here comes a tests on learn-data!")
test_brain(net, learn_data)
print("Here comes a tests on test-data!")
test_brain(net, test_data)
return 0
if __name__ == "__main__":
sys.exit(main())
На этом знакомство с PyBrain на сегодня считаю законченным. До новых встреч!
upd: по просьбе monolithed исправил регулярное выражение.
Only registered users can participate in poll. Log in, please.
Что вам интереснее больше? На чём сконцентрироваться при написании следующей статьи?
45.25%PyQt119
59.32%PyBrain156
32.7%Какие-то особые приёмы при программировании на Python3 (если что-то конкретное, то опишите в комментариях)86
23.95%Туториалы по модулям Python3 (какие именно модули — пишите в комментариях)63
263 users voted. 70 users abstained.