Изучая 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.