{"id":16694392,"url":"https://github.com/artiomn/life","last_synced_at":"2026-04-15T14:38:28.399Z","repository":{"id":106861675,"uuid":"147023635","full_name":"artiomn/life","owner":"artiomn","description":"Life game for the test task","archived":false,"fork":false,"pushed_at":"2018-09-21T20:33:21.000Z","size":27966,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-20T18:42:24.628Z","etag":null,"topics":["cpp","cpp17","game","gol","keras","keras-tensorflow","life","neural-network","python","test-task"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/artiomn.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-09-01T18:42:12.000Z","updated_at":"2023-04-19T19:16:08.000Z","dependencies_parsed_at":null,"dependency_job_id":"42dcb5ce-fd7e-433b-be93-915be72967e9","html_url":"https://github.com/artiomn/life","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/artiomn%2Flife","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/artiomn%2Flife/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/artiomn%2Flife/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/artiomn%2Flife/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/artiomn","download_url":"https://codeload.github.com/artiomn/life/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243495736,"owners_count":20299979,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cpp","cpp17","game","gol","keras","keras-tensorflow","life","neural-network","python","test-task"],"created_at":"2024-10-12T16:45:25.153Z","updated_at":"2025-12-29T14:18:47.466Z","avatar_url":"https://github.com/artiomn.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"Тестовые задания в \"Лабораторию Касперского\"\n============================================\n\nОглавление:\n\n- [Реализация игры \"Жизнь\"](#Реализация-игры-Жизнь).\n- [Нейронная сеть, предсказывающая ход](#Нейронная-сеть-предсказывающая-ход).\n\n\nРеализация игры \"Жизнь\"\n-----------------------\n\n![](life.gif)\n\n\n### Описание\n\nНеобходимо, используя подход ООП, разработать интерфейс и реализацию класса, который будет представлять собой состояние игрового поля и контролировать его изменение в соответствии с правилами.\nСледует считать, что края игрового поля циклически замкнуты.\n[Оригинальное описание в PDF](test_problem_C++.pdf).\n\n**Сейчас реализованы только ходы вперёд и проверка на завершение только по счётчику живых клеток и по количеству изменений на шаге**.\n\n\n#### Начальные условия игры\n\n- Предполагается, что пользователь имеет замкнутое поле заданного размера.\n- На заданном пространстве распределены клетки так, как это задано пользователем.\n\n\n#### Требования\n\nИсходя из задачи и возможного развития проекта, были выдвинуты следующие требования к движку:\n\n- Пользователь может задавать размер поля.\n- Пользователь может задавать распределение на поле.\n- Пользователь может контролировать ходы игры, т.е. иметь возможность сделать ход вперёд и ход назад.\n\n\n### Интерфейс класса\n\nИсходя из требований, класс должен:\n\n- Иметь конструкторы, принимающие распределение в том или ином виде.\n- Иметь функции, позволяющие сделать шаги в одном из направлений.\n- Быть способен визуализировать поле.\n\n\n#### Конструкторы\n\nВозможны два конструктора:\n\n- Конструктор, которому передаётся, сформированное пользователем поле.\n- Конструктор, которому передаются размеры поля и функция распределения.\n\nИз них я использую только второй, чтобы не привязываться к формату хранения поля.\n\n\n#### Функция распределения\n\nСчитаю, что поле двумерно, потому предполагается, что функции распределения будут передаваться координаты и ссылка на текущий объект класса.\nТекущий элемент будет установлен в то значение, которое вернёт функция распределения.\n\n\n#### Метод хода\n\n- Шаг может передаваться числовым параметром, либо может иметься два метода, позволяющие делать шаги вперёд и назад.\n- В качестве интерфейса для реализации хода выбран один метод с целочисленным параметром, как более универсальный.\n- Метод возвращает true, если ещё возможны ходы, либо false, если игра завершена.\n\n\n#### Получение состояния поля\n\nПоле должно быть возвращено обработчику, либо в виде ссылки, либо в виде возможности его обхода через Visitor.\n\n- Был проведён опрос потенциальных пользователей библиотеки (в виде одного разработчика),\n  100% пользователей выразили желание работать со ссылкой на поле, а не с Visitor.\n- Плюсом же паттерна Visitor будет изоляция метода обработки данных, от структуры хранилища игрового поля.\n- Существует компромиссный вариант: считать, что поле всегда двумерно и иметь метод для получения ячейки по заданным координатам.\n\nБыло принято решение использовать вариант с методом, возвращающим ячейку по координатам и вариант с Visitor.\n\n\n### Реализация класса\n\nНа текущий момент, для каждой клетки достаточно флага, указывающего чёрная она или белая.\nХранить дополнительные данные не требуется.\nПотому, для хранения поля выбран std::vector\u003cbool\u003e.\nПреобразование из двумерных координат в одномерные производится \"на лету\".\nСуществует внутренний метод, который реализует логику хода.\n\n\n#### Правила игры\n\nХод игры:\n\n- В пустой (мёртвой) клетке, рядом с которой ровно три живые клетки, зарождается жизнь.\n- Если у живой клетки есть две или три живые соседки, то эта клетка продолжает жить.\n- Если соседей у клетки меньше двух или больше трёх, клетка умирает.\n\nУсловия завершения игры:\n\n- На поле не останется ни одной живой клетки.\n- Конфигурация на очередном шаге в точности (без сдвигов и поворотов) повторит себя же на одном из более ранних шагов\n  (складывается периодическая конфигурация).\n- При очередном шаге ни одна из клеток не меняет своего состояния (складывается стабильная конфигурация; предыдущее правило, вырожденное до одного шага назад).\n\n\n#### Алгоритм работы\n\nПростейший вариант - формировать второй массив, содержащий поле, но он требует двойного расхода памяти.\nЧтобы этого избежать, я буду использовать модифицированный алгоритм:\n\n- Состояние клетки зависит только от числа соседей.\n- Получаю количество соседей.\n- Если клетка меняет своё состояние, заношу её индекс в список изменений.\n- Когда все клетки пройдены, применяю список изменений к полю.\n\n\n### Сборка\n\nДля сборки требуется CMake версии не ниже 3.1 и компилятор, поддерживающий C++14.\nСборка проверялась на gcc. Реализация проверялась на Debian Linux 9.\n\n```\n$ cmake . \u0026\u0026 make\n$ ./life 40 30\n```\n\n\nНейронная сеть, предсказывающая ход\n-----------------------------------\n\n### Постановка условия\n\n[По условию задачи](python_life/Task.txt) необходимо создать нейронную сеть, которая сможет предсказывать следующий шаг в игре \"Жизнь\".\n[Данный в задаче генератор](python_life/gol_dataset.py) создаёт массив состояний, в котором каждому случайно сгенерированному состоянию, соответствует состояние, сгенерированное по правилам игры.\n\nФактически, сеть должна вывести правила игры.\nЕсли знать, что состояние клетки определяется её окрестностью Мура порядка 1, возможно разбить поле на квадраты 3x3 и обучить классификатор на данных квадратах.\nВ таком случае, задача сводится к задаче классификации при фиксированном числе классов (2^9).\n\nТем не менее, предполагается, что я этого не знаю. И задача сводится к более общей: \"На основе вектора состояния и примеров переходов между векторами, породить следующий вектор состояния\".\nПри этом, \"вектор состояния\" описывается матрицей поля. И задача является задачей прогнозирования.\n\n\n### Выбор алгоритма работы и архитектуры сети\n\n![](python_life/model.png)\n\nПредварительно были исследованы сети прямого распространения.\nСеть подобной конфигурации в Keras на 20 циклах обучения и выборке из 9000 \"полей\" даёт точность порядка 0.73:\n\n```python\nmodel = Sequential()\n\nmodel.add(Dense(height, input_shape=(width, height), activation='relu'))\nmodel.add(Dense(width * height, init='uniform', activation='relu'))\nmodel.add(Dense(height, init='uniform', activation='sigmoid'))\nmodel.summary()\nmodel.compile(loss='binary_crossentropy',\n              optimizer='adam',\n              metrics=['accuracy'])\n...\n\nmodel.fit(x_train, y_train, epochs=20, verbose=1, validation_split=0.1)\n```\n\nПроблема в том, что это не 0.75 верно предсказанных результатов, а 0.73 поверхности поля.\nМаксимум, которого удалось достичь - 0.8 при следующей конфигурации сети:\n\n```python\nmodel.add(Dense(height, input_shape=(width, height), activation='selu'))\nmodel.add(Dense(width * height, init='uniform', activation='relu'))\nmodel.add(Dense(width * height * 10, init='uniform', activation='relu'))\nmodel.add(Dense(height, init='uniform', activation='sigmoid'))\n```\n\nЭтого явно недостаточно для уверенного предсказания хода на поле размерностью 20x30.\n\nС задачей прогнозирования лучше всего справляются сети с памятью, т.е. рекуррентные.\nЕсли бы переходы из состояния в состояние представляли собой серии событий, то рекуррентные модули, сохраняющие состояние в течение длительного периода, подошли бы лучше всего.\nНапример, [LSTM](https://en.wikipedia.org/wiki/Long_short-term_memory) или [GRU](https://en.wikipedia.org/wiki/Gated_recurrent_unit) блоки.\nКроме того, в этом случае возможно было бы применить обучение с подкреплением (чем выше точность предсказания, тем выше значение Q-функции).\n\nПроблема заключается в том, что генератор создаёт не связанные между собой состояния, а лишь набор независимых переходов: предыдущее состояние - следующее состояние.\nТ.е., предыдущее состояние не учитывается, при генерации следующего.\nЕсли использовать такой рекуррентный модуль, он будет считать, что все сэмплы связаны и запоминать переходы, которые таковыми не являются и не соответствуют правилам.\n\nОдин из вариантов - предварительная группировка состояний с целью поиска последовательностей переходов и дальнейшее использование RNN.\n\nТем не менее, не хочется делать предобработку, основываясь на дополнительном знании о данных (например, о распределении ГСЧ).\nТаких последовательностей может не быть. И при возрастании размерности поля, вероятность их обнаружить уменьшается нелинейно.\n\nКроме того, задачу возможно считать задачей построения ассоциативной памяти, для повышения ёмкости которой используются также рекуррентные сети.\nKeras содержит [SimpleRNN блок](https://keras.io/layers/recurrent/#simplernn), \"проблемой\" которого является то, что он забывает ранние состояния.\nИменно такой блок требуется.\nС данным блоком удалось достигнуть максимальной точности прогноза более 0.97, при использовании враппера [Bidirectional](https://keras.io/layers/wrappers/#bidirectional).\n\n\n\n### Реализация\n\nПараметры поля не указываются, а берутся из переданного HDF файла.\nВ качестве библиотеки для построения нейросети был использован Keras. Топология сети, функции и коэффициенты подобраны, в основном, экспериментально.\nДля того, чтобы не дублировать код, генератор был переделан, потому из него производится импорт функции для генерации тестовых данных.\n\nК модели добавлены некоторые улучшения:\n\n- С целью ускорения обучения, лучшие веса сохраняются в файле в виде чек-поинта, который загружается при следующем старте.\n  По окончании обучения, также будут загружены веса, с которыми модель выполняет прогноз лучше всего.\n- Когда начинается \"плато\" обучения (по `val_loss`), т.е. обучение замедляется, скорость обучения будет снижена.\n- Как только `val_acc` начинает значительно уменьшаться, обучение автоматически останавливается.\n\n\n### Запуск\n\nКод находится в каталоге [`python_life`](python_life).\nДолжен быть доступен интерпретатор Python3, а также установлены пакеты:\n\n- [Keras](https://keras.io/) и какой-либо бэкэнд (я использовал TensorFlow) - используется для построения сети.\n- [H5py](https://www.h5py.org/) - для работы с файлами в формате HDF5.\n- [Tqdm](https://github.com/tqdm/tqdm) - прогресс-бар.\n\nПосле этого надо сгененрировать файл с данными и запустить сеть:\n\n```\n$ ./gol_dataset.py\n$ ./neuro_life.py dataset_20x30x10000.h5\n```\n\nПосле прохождения обучения сетью, она выполнит предсказания на случайно сгенерированных данных.\nБудет выведен результат, указывающий то, насколько точно, в среднем, сеть предсказывает ход:\n\n```\nUsing TensorFlow backend.\nTest task implementation\nReading \"dataset_20x30x10000.h5\"...\n_________________________________________________________________\nLayer (type)                 Output Shape              Param #   \n=================================================================\ndense_1 (Dense)              (None, 20, 600)           18600     \n_________________________________________________________________\nbidirectional_1 (Bidirection (None, 20, 651)           1630104   \n_________________________________________________________________\nsimple_rnn_2 (SimpleRNN)     (None, 20, 600)           751200    \n_________________________________________________________________\ndense_2 (Dense)              (None, 20, 651)           391251    \n_________________________________________________________________\ndense_3 (Dense)              (None, 20, 30)            19560     \n=================================================================\nTotal params: 2,810,715\nTrainable params: 2,810,715\nNon-trainable params: 0\n_________________________________________________________________\nTrying to load weights...\nIncorrect checkpoint: ...\nEpoch 1/15\n2018-09-21 22:56:02.875055: I tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA\n9500/9500 [==============================] - 97s 10ms/step - loss: 0.4362 - acc: 0.7694 - val_loss: 0.3653 - val_acc: 0.8096\n\nEpoch 00001: val_acc improved from -inf to 0.80963, saving model to checkpoint.hd5\nEpoch 2/15\n9500/9500 [==============================] - 96s 10ms/step - loss: 0.2969 - acc: 0.8612 - val_loss: 0.2896 - val_acc: 0.8638\n\nEpoch 00002: val_acc improved from 0.80963 to 0.86376, saving model to checkpoint.hd5\nEpoch 3/15\n9500/9500 [==============================] - 99s 10ms/step - loss: 0.2030 - acc: 0.9135 - val_loss: 0.2064 - val_acc: 0.9083\n\n...\n\nEpoch 00008: val_acc did not improve from 0.96953\nEpoch 9/15\n9500/9500 [==============================] - 97s 10ms/step - loss: 0.1350 - acc: 0.9506 - val_loss: 0.1332 - val_acc: 0.9440\n\nEpoch 00009: ReduceLROnPlateau reducing learning rate to 0.001.\n\nEpoch 00009: val_acc did not improve from 0.96953\nEpoch 10/15\n9500/9500 [==============================] - 95s 10ms/step - loss: 0.1311 - acc: 0.9485 - val_loss: 0.0722 - val_acc: 0.9671\n\nEpoch 00010: val_acc did not improve from 0.96953\nEpoch 00010: early stopping\nLoading the best weights...\nGenerate x_train\n100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 20000/20000 [00:00\u003c00:00, 74720.29it/s]\nGenerate y_train\n100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 20000/20000 [00:03\u003c00:00, 6464.79it/s]\nPrediction started...\nPredictions testing started...\n100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 20000/20000 [00:01\u003c00:00, 14389.95it/s]\nStep's correct prediction rate = 0.9698408333333125\nCorrect predicted steps = 0\n```\n\n\n### Результаты\n\nСеть учится и предсказывает ход с достаточно высокой точностью. Хотя, этого и не вполне достаточно для полностью корректного предсказания хода на поле 20x30.\nСкорость обучения составляет порядка 1.5 минуты на итерацию на Core-i7 с 8 ядрами, без использования AVX и FMA расширений.\nПри обучении за 20 эпох, была достигнута максимальная точность предсказания хода порядка 0.97.\nТ.е., на ход сейчас приходится около 3% ошибок.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fartiomn%2Flife","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fartiomn%2Flife","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fartiomn%2Flife/lists"}