- PVSM.RU - https://www.pvsm.ru -
Иногда проводишь день в попытках без использования терминов «рекурсивный вызов» и «идиоты» объяснить главному бухгалтеру, почему на самом деле простое изменение учетной системы затягивается почти на неделю из-за орфографической ошибки, допущенной кем-то в коде в 2009 году. В такие дни хочется пооборвать руки тому умнику, который сотворил этот мир, и переписать все с ноля.
TL;DR
Под катом история о том, как я в качестве практики для изучения Python разрабатываю свою библиотеку для агентного моделирования [1] с машинным обучением и богами.
Ссылка [2]на github. Для работы из коробки нужен pygame [3]. Для ознакомительного примера понадобится sklearn [4].
Идея соорудить велосипед, о котором я буду рассказывать, появлялась постепенно. Во-первых, популярность темы машинного обучения не обошла меня стороной. Несколько курсов [5] на курсере дали обманчивое ощущение причастности. Несколько открытых конкурсов и регистрация на kaggle немного откорректировали самомнение, однако, не охладили энтузиазм.
Во-вторых, будучи представителем касты неприкасаемых [6]отечественного IT сообщества, я нечасто имею возможность попрактиковать нежно любимый мной Python. А от умных людей я слышал, что свой проект в этом плане — это как раз то, что надо.
Но толчком послужило разочарование No Man’s Sky. Технически шикарная идея, но процедурно сгенерированный мир оказался пустым. И как любой разочаровавшийся болельщик, я начал думать, что бы сделал я, если бы меня спрашивали. И придумал, что мир оказался пустым потому, что в нем на самом деле очень мало разумной жизни. Бескрайние просторы, привычка полагаться только на себя, радость первооткрывателя — это все, конечно, хорошо. Но не хватает возможности вернуться на базу, послоняться по рынку, узнать последние сплетни в забегаловке. Доставить посылку и получить за это свои 100 золотых, в конце концов. Понятно, что любой город, любой диалог или квест в играх — плод труда живого человека и населить жизнью такой огромный мир силами людей не представляется возможным. Но что если мы бы могли так же процедурно генерировать NPC с их потребностями, маленькими историями и квестами?
Так появилась идея некоторой библиотеки, или даже, если позволите, фреймворка, у которого были бы следующие сценарии использования:
Итак, для начала надо определиться с низкоуровневой физикой нашего мира. Она должна быть простой, но достаточно гибкой для моделирования разных ситуаций:
Это дает нам несколько необходимых базовых объектов: сам мир (Field), объект этого мира (Entity) и предмет (Substance). Здесь и далее код в статье — просто иллюстрация. Полностью его можно посмотреть в библиотеке [2] на github.
class Entity(object):
def __init__(self):
# home universe
self.board = None
# time-space coordinates
self.x = None
self.y = None
self.z = None
# lifecycle properties
self.age = 0
self.alive = False
self.time_of_death = None
# common properties
self.passable = False
self.scenery = True
self._container = []
# visualization properties
self.color = None
def contains(self, substance_type):
for element in self._container:
if type(element) == substance_type:
return True
return False
def live(self):
self.z += 1
self.age += 1
class Blank(Entity):
def __init__(self):
super(Blank, self).__init__()
self.passable = True
self.color = "#004400"
def live(self):
super(Blank, self).live()
if random.random() <= 0.0004:
self._container.append(substances.Substance())
if len(self._container) > 0:
self.color = "#224444"
else:
self.color = "#004400"
class Block(Entity):
def __init__(self):
super(Block, self).__init__()
self.passable = False
self.color = "#000000"
class Field(object):
def __init__(self, length, height):
self.__length = length
self.__height = height
self.__field = []
self.__epoch = 0
self.pause = False
for y in range(self.__height):
row = []
self.__field.append(row)
for x in range(self.__length):
if y == 0 or x == 0 or y == (height - 1) or x == (length - 1):
init_object = Block()
else:
init_object = Blank()
init_object.x = x
init_object.y = y
init_object.z = 0
row.append([init_object])
Класс Substance описывать смысла не имеет, в нем ничего нет.
За время у нас будет отвечать сам мир. Каждую эпоху он будет опрашивать все находящиеся в нем объекты и заставлять их делать ход. Как они проведут этот ход, уже их дело:
class Field(object):
...
def make_time(self):
if self.pause:
return
for y in range(self.height):
for x in range(self.length):
for element in self.__field[y][x]:
if element.z == self.epoch:
element.live()
self.__epoch += 1
...
Но зачем нам мир, да еще и с запланированной возможностью поместить в него протагониста, если мы не можем его увидеть? С другой стороны, если начать разбираться с графикой, можно сильно отвлечься, и правление миром отложится на неопределенный срок. Поэтому не тратя времени осваиваем вот эту замечательную статью [11] про написание платформера с помощью pygame [12] (на самом деле, нам понадобится только первая треть статьи), выдаем каждому объекту признак цвета, и вот у нас уже есть какое-то подобие карты.
class Field(object):
...
def list_obj_representation(self):
representation = []
for y in range(self.height):
row_list = []
for cell in self.__field[y]:
row_list.append(cell[-1])
representation.append(row_list)
return representation
....
def visualize(field):
pygame.init()
screen = pygame.display.set_mode(DISPLAY)
pygame.display.set_caption("Field game")
bg = Surface((WIN_WIDTH, WIN_HEIGHT))
bg.fill(Color(BACKGROUND_COLOR))
myfont = pygame.font.SysFont("monospace", 15)
f = field
tick = 10
timer = pygame.time.Clock()
go_on = True
while go_on:
timer.tick(tick)
for e in pygame.event.get():
if e.type == QUIT:
raise SystemExit, "QUIT"
if e.type == pygame.KEYDOWN:
if e.key == pygame.K_SPACE:
f.pause = not f.pause
elif e.key == pygame.K_UP:
tick += 10
elif e.key == pygame.K_DOWN and tick >= 11:
tick -= 10
elif e.key == pygame.K_ESCAPE:
go_on = False
screen.blit(bg, (0, 0))
f.integrity_check()
f.make_time()
level = f.list_obj_representation()
label = myfont.render("Epoch: {0}".format(f.epoch), 1, (255, 255, 0))
screen.blit(label, (630, 10))
stats = f.get_stats()
for i, element in enumerate(stats):
label = myfont.render("{0}: {1}".format(element, stats[element]), 1, (255, 255, 0))
screen.blit(label, (630, 25 + (i * 15)))
x = y = 0
for row in level:
for element in row:
pf = Surface((PLATFORM_WIDTH, PLATFORM_HEIGHT))
pf.fill(Color(element.color))
screen.blit(pf, (x, y))
x += PLATFORM_WIDTH
y += PLATFORM_HEIGHT
x = 0
pygame.display.update()
Конечно, позже можно будет написать несколько более вразумительный модуль визуализации, да не один. Но пока разноцветных бегающих квадратиков вполне хватает для того, чтобы погрузиться в атмосферу зарождающегося бытия. К тому же, это развивает фантазию.
Теперь надо продумать, как активные агенты будут действовать. Во-первых, все значимые действия будут объектами (объектами Python, не объектами мира, приношу извинения за неоднозначность). Так можно хранить историю, манипулировать их состоянием, отличать одно действие от другого, даже если они однотипные. Итак, действия будут выглядеть следующим образом:
Таким образом, с технической точки зрения объект действия (Action) представляет собой некоторое подобие функции, которой можно задать параметры, выполнить, получить результат, и которая при этом сама в себе хранит и переданные ей параметры, и результат, и все что угодно, что связано с ее выполнением, начиная с того, кто ее вызвал, и заканчивая состоянием мира вокруг во время ее выполнения. Поэтому мы можем в одно время ее создать, в другое задать параметры, в третье выполнить, получить возвращаемое значение и положить на полочку для последующего анализа.
class Action(object):
def __init__(self, subject):
self.subject = subject
self.accomplished = False
self._done = False
self.instant = False
def get_objective(self):
return {}
def set_objective(self, control=False, **kwargs):
valid_objectives = self.get_objective().keys()
for key in kwargs.keys():
if key not in valid_objectives:
if control:
raise ValueError("{0} is not a valid objective".format(key))
else:
pass # maybe need to print
else:
setattr(self, "_{0}".format(key), kwargs[key])
def action_possible(self):
return True
def do(self):
self.check_set_results()
self._done = True
def check_set_results(self):
self.accomplished = True
@property
def results(self):
out = {"done": self._done, "accomplished": self.accomplished}
return out
def do_results(self):
self.do()
return self.results
Если кто-нибудь кроме меня вдруг захочет создать себе уютный мирок с помощью этой библиотеки, то предполагается, что из коробки она будет содержать набор необходимых низкоуровневых действий — отправиться по координатам, следовать за объектом, найти определенный объект или предмет, подобрать предмет и т.д. Этими действиями можно будет пользоваться как самими по себе, так и комбинировать их для произведения каких-то сложных манипуляций. Пример таких комплексных действий будет далее, в описании первого эксперимента.
Во-вторых, каждый уважающий себя активный агент должен уметь планировать свои действия. Поэтому разделим фазу его активности в течение эпохи на 2 этапа: планирование и действие. В качестве инструмента планирования у нас будет простая очередь действий, которые мы собираемся последовательно выполнять. Однако если у нас уже есть план, то нечего лишний раз размышлять, надо действовать быстро, решительно. Получается, что в начале хода активный объект определяет, надо ли на этот ход планировать (для начала будем считать, что надо, когда очередь действий пуста), затем планирует, если решил, что это необходимо, и в конце выполняет действия. Должно ли планирование, как серьезный процесс, не терпящий спешки, занимать весь ход — вопрос дискуссионный. Для своих целей я пока остановился на том, что нет — мои агенты долго не размышляют и приступают к выполнению плана в тот же ход.
class Agent(Entity):
...
def live(self):
...
if self.need_to_update_plan():
self.plan()
if len(self.action_queue) > 0:
current_action = self.action_queue[0]
self.perform_action(current_action)
while len(self.action_queue) > 0 and self.action_queue[0].instant:
current_action = self.action_queue[0]
self.perform_action(current_action)
def need_to_update_plan(self):
return len(self.action_queue) == 0
def perform_action(self, action):
results = action.do_results()
if results["done"] or not action.action_possible():
self.action_log.append(self.action_queue.pop(0))
return results
...
...
Вдобавок к этому мне показалось удобным ввести такую сущность как состояние объекта, которое могло бы влиять на его действия. Ведь, агент может устать, быть не в настроении, намокнуть, отравиться или наоборот, быть веселым и полным сил. Иногда даже одновременно. Поэтому добавим нашим объектам массив состояний, каждое из которых будет влиять на объект в начале эпохи.
class State(object):
def __init__(self, subject):
self.subject = subject
self.duration = 0
def affect(self):
self.duration += 1
class Entity(object):
def __init__(self):
...
self._states_list = []
...
...
def get_affected(self):
for state in self._states_list:
state.affect()
def live(self):
self.get_affected()
self.z += 1
self.age += 1
...
Для моделирования и обучения необходимо иметь возможность оценить, насколько удачно мы написали алгоритм действий или выбрали модель обучения. Для этого добавим простой модуль симуляции и оценки с возможностью описывать способ определения конца симуляции и сбора результатов.
import copy
def run_simulation(initial_field, check_stop_function, score_function, times=5, verbose=False):
list_results = []
for iteration in range(times):
field = copy.deepcopy(initial_field)
while not check_stop_function(field):
field.make_time()
current_score = score_function(field)
list_results.append(current_score)
if verbose:
print "Iteration: {0} Score: {1})".format(iteration+1, current_score)
return list_results
На этом этапе все, в принципе, готово для закрытия первого сценария использования нашей библиотеки: моделирования, если мы не хотим обучать агентов, а хотим прописывать логику их действий самостоятельно. Порядок действий в таком случае следующий:
Итак, огласим условия первого эксперимента.
Нашей задачей будет написать процедуру планирования для существ истинного пола таким образом чтобы популяция существ размножалась как можно быстрее.
Дочитавших до этого места я из благодарности не буду утомлять длинными иллюстрациями, покажу только:
class GoMating(Action):
def __init__(self, subject):
super(GoMating, self).__init__(subject)
self.search_action = SearchMatingPartner(subject)
self.move_action = MovementToEntity(subject)
self.mate_action = Mate(subject)
self.current_action = self.search_action
def action_possible(self):
if not self.current_action:
return False
return self.current_action.action_possible()
def do(self):
if self.subject.has_state(states.NotTheRightMood):
self._done = True
return
if self.results["done"]:
return
if not self.action_possible():
self._done = True
return
first = True
while first or (self.current_action and self.current_action.instant) and not self.results["done"]:
first = False
current_results = self.current_action.do_results()
if current_results["done"]:
if current_results["accomplished"]:
if isinstance(self.current_action, SearchMatingPartner):
if current_results["accomplished"]:
self.current_action = self.move_action
self.current_action.set_objective(**{"target_entity": current_results["partner"]})
elif isinstance(self.current_action, MovementXY):
self.current_action = self.mate_action
self.current_action.set_objective(**{"target_entity": self.search_action.results["partner"]})
elif isinstance(self.current_action, Mate):
self.current_action = None
self.accomplished = True
self._done = True
else:
self.current_action = None
self._done = True
else:
break
def check_set_results(self):
self.accomplished = self._done
class Creature(Agent):
...
def plan(self):
nearest_partner = actions.SearchMatingPartner(self).do_results()["partner"]
if nearest_partner is None:
chosen_action = actions.HarvestSubstance(self)
chosen_action.set_objective(** {"target_substance_type": type(substances.Substance())})
self.queue_action(chosen_action)
else:
self_has_substance = self.count_substance_of_type(substances.Substance)
partner_has_substance = nearest_partner.count_substance_of_type(substances.Substance)
if partner_has_substance - self_has_substance > 2:
self.queue_action(actions.GoMating(self))
else:
chosen_action = actions.HarvestSubstance(self)
chosen_action.set_objective(**{"target_substance_type": type(substances.Substance())})
self.queue_action(chosen_action)
...
Удостоверившись, что простое моделирование работает, начнем повышать градус веселья и добавим возможность машинного бучения. На момент написания статьи не все из планируемых возможностей реализованы, однако, я обещал рассказать про богов.
Но для начала надо определиться с тем, как мы хотим обучать наших существ. Возьмем ту же самую задачу с поисками ресурса и спариванием. Если бы мы решали ее традиционным путем, то сначала нам бы надо было определиться с набором признаков, опираясь на которые мы планируем принимать решения. Затем, действуя случайным образом или еще как-то, собрать тренировочный и тестовый датасеты и сохранить их. Следом обучить на этих датасетах пару моделей, сравнить их и выбрать лучшую. Наконец, переписать процесс планирования с использованием этой модели, запустить симуляцию и посмотреть, что получается. И тут нам бы пришло в голову использовать новый признак, а это значит пересобрать данные, перетренировать модели, пересравнить их между собой и перезапустить, чтобы перепосмотреть, что переполучится.
А что бы нам хотелось в идеале? В идеале хотелось бы определить набор признаков, сконфигурировать модель обучения и запустить симуляцию, которая бы уже сама собрала датасеты, натренировала модель, подключила ее к процессу планирования и выдала нам готовые результаты нескольких прогонов, которые бы мы могли сравнивать с результатами других моделей или других наборов признаков.
И вот как я себе это представляю:
Тут нам потребуется несколько новых объектов. Во-первых, у существ должна быть какая-то память, в которую они будут складывать свои датасеты. Она должна уметь отдельно запоминать набор признаков. Отдельно присоединять к ним результат решения, принятого при этом наборе признаков. Возвращать нам датасет в удобном виде. Ну, и забывать все, чему нас учили в вузе.
class LearningMemory(object):
def __init__(self, host):
self.host = host
self.memories = {}
def save_state(self, state, action):
self.memories[action] = {"state": state}
def save_results(self, results, action):
if action in self.memories:
self.memories[action]["results"] = results
else:
pass
def make_table(self, action_type):
table_list = []
for memory in self.memories:
if isinstance(memory, action_type):
if "state" not in self.memories[memory] or "results" not in self.memories[memory]:
continue
row = self.memories[memory]["state"][:]
row.append(self.memories[memory]["results"])
table_list.append(row)
return table_list
def obliviate(self):
self.memories = {}
Во-вторых, нам надо научить агентов получать задания и запоминать обстановку и результаты своих действий.
class Agent(Entity):
def __init__(self):
...
self.memorize_tasks = {}
....
...
def set_memorize_task(self, action_types, features_list, target):
if isinstance(action_types, list):
for action_type in action_types:
self.memorize_tasks[action_type] = {"features": features_list,
"target": target}
else:
self.memorize_tasks[action_types] = {"features": features_list,
"target": target}
def get_features(self, action_type):
if action_type not in self.memorize_tasks:
return None
features_list_raw = self.memorize_tasks[action_type]["features"]
features_list = []
for feature_raw in features_list_raw:
if isinstance(feature_raw, dict):
if "kwargs" in feature_raw:
features_list.append(feature_raw["func"](**feature_raw["kwargs"]))
else:
features_list.append(feature_raw["func"]())
elif callable(feature_raw):
features_list.append(feature_raw())
else:
features_list.append(feature_raw)
return features_list
def get_target(self, action_type):
if action_type not in self.memorize_tasks:
return None
target_raw = self.memorize_tasks[action_type]["target"]
if callable(target_raw):
return target_raw()
elif isinstance(target_raw, dict):
if "kwargs" in target_raw:
return target_raw["func"](**target_raw["kwargs"])
else:
return target_raw["func"]()
else:
return target_raw
def queue_action(self, action):
if type(action) in self.memorize_tasks:
self.private_learning_memory.save_state(self.get_features(type(action)), action)
self.public_memory.save_state(self.get_features(type(action)), action)
self.action_queue.append(action)
def perform_action_save_memory(self, action):
self.chosen_action = action
if type(action) in self.memorize_tasks:
results = self.perform_action(action)
if results["done"]:
self.private_learning_memory.save_results(self.get_target(type(action)), action)
self.public_memory.save_results(self.get_target(type(action)), action)
else:
results = self.perform_action(action)
...
И, наконец, нам потребуется некий менеджер, по-отечески присматривающий за всеми агентами, раздающий им задания, устанавливающий модели обучения, объясняющий, как их использовать, избавляющий от лукавого, многомилостивый и всеблагой. Проще говоря, чем прописывать всю логику в агентах, а общие объекты хранить где-то в свойствах мира или еще где-нибудь, нам удобнее вклиниваться в момент появления агента в нашем мире и наделять его сознанием, определяя все необходимые свойства типа памяти, способа планирования и модели обучения.
Для этого создадим объект Demiurge, который будет подключаться к создаваемому миру. Все объекты, кроме изначальных, должны появляться в мире посредством метода insert_object. В него и будет вклиниваться наше божество, предварительно обрабатывая свое очередное творение:
class Demiurge(object):
def handle_creation(self, creation, refuse):
pass
class Field(object):
def __init__(self, length, height):
...
self.demiurge = None
...
def insert_object(self, x, y, entity_object, epoch_shift=0):
if self.demiurge is not None:
refuse = False
self.demiurge.handle_creation(entity_object, refuse)
if refuse:
return
assert x < self.length
assert y < self.height
self.__field[y][x][-1] = entity_object
entity_object.z = self.epoch + epoch_shift
entity_object.board = self
entity_object.x = x
entity_object.y = y
...
Такой подход дает много возможностей:
Дополнительно агентам была добавлена такая функция как использование публичных или приватных памяти и модели обучения. Публичная память — что-то вроде общедоступных описаний экспериментов. В нее описания своих действий и их результатов могут записать все агенты и прочитать тоже может любой. Публичная модель обучения — учебник с описанием того, что надо делать в той или иной ситуации, которым может воспользоваться кто угодно. Ну, а приватные память и модель обучения — это жизненный опыт и умения каждого конкретного агента. Их можно комбинировать — например, черпать примеры из общей памяти, но иметь свою приватную модель обучения.
Итак, попробуем решить ту же самую задачу с собиранием ресурсов и существами ложного пола. Так как задача достаточно простая, то у нас вряд ли получится решить ее с помощью машинного обучения лучше, чем можно было бы с помощью жестко заданного алгоритма. Однако для признания концепции рабочей нам достаточно того, чтобы наши собиратели не вымерли как вид от нежелания или неумения размножаться.
Ниже полный текст примера решения такой задачи:
# Create deity
class Priapus(field.Demiurge): # Create deity
def __init__(self):
self.public_memory = brain.LearningMemory(self)
self.public_decision_model = SGDClassifier(warm_start=True)
def handle_creation(self, creation, refuse):
if isinstance(creation, entities.Creature):
creation.public_memory = self.public_memory
creation.public_decision_model = self.public_decision_model
creation.memory_type = "public"
creation.model_type = "public"
creation.memory_batch_size = 20
if creation.sex:
def difference_in_num_substance(entity):
nearest_partner = actions.SearchMatingPartner(entity).do_results()["partner"]
if nearest_partner is None:
return 9e10
else:
self_has_substance = entity.count_substance_of_type(substances.Substance)
partner_has_substance = nearest_partner.count_substance_of_type(substances.Substance)
return partner_has_substance - self_has_substance
def possible_partners_exist(entity):
find_partner = actions.SearchMatingPartner(entity)
search_results = find_partner.do_results()
return float(search_results["accomplished"])
features = [{"func": lambda creation: float(creation.has_state(states.NotTheRightMood)),
"kwargs": {"creation": creation}},
{"func": difference_in_num_substance,
"kwargs": {"entity": creation}},
{"func": possible_partners_exist,
"kwargs": {"entity": creation}}]
creation.set_memorize_task(actions.GoMating, features,
{"func": lambda creation: creation.chosen_action.results["accomplished"],
"kwargs": {"creation": creation}})
def plan(creature):
if creature.sex:
try:
# raise NotFittedError
current_features = creature.get_features(actions.GoMating)
current_features = np.asarray(current_features).reshape(1, -1)
if creature.public_decision_model.predict(current_features):
go_mating = actions.GoMating(creature)
creature.queue_action(go_mating)
return
else:
harvest_substance = actions.HarvestSubstance(creature)
harvest_substance.set_objective(
**{"target_substance_type": type(substances.Substance())})
creature.queue_action(harvest_substance)
return
except NotFittedError:
chosen_action = random.choice(
[actions.GoMating(creature), actions.HarvestSubstance(creature)])
if isinstance(chosen_action, actions.HarvestSubstance):
chosen_action.set_objective(
**{"target_substance_type": type(substances.Substance())})
creature.queue_action(chosen_action)
return
else:
harvest_substance = actions.HarvestSubstance(creature)
harvest_substance.set_objective(**{"target_substance_type": type(substances.Substance())})
creature.queue_action(harvest_substance)
creation.plan_callable = plan
universe = field.Field(60, 40) # Create sample universe (length, height
universe.set_demiurge(Priapus()) # Assign deity to universe
# Fill universe with blanks, blocks, other scenery if necessary
for y in range(10, 30):
universe.insert_object(20, y, field.Block())
for x in range(21, 40):
universe.insert_object(x, 10, field.Block())
for y in range(10, 30):
universe.insert_object(40, y, field.Block())
universe.populate(entities.Creature, 20) # Populate universe with creatures
def check_stop_function(field):
return field.epoch >= 500
def score_function(field):
stats = field.get_stats()
if "Creature" not in stats:
return 0
else:
return stats["Creature"]
res = modelling.run_simulation(universe, check_stop_function, score_function, verbose=True, times=30)
print res
print np.asarray(res).mean()
В таком виде как она сейчас есть библиотека, конечно, представляет собой заготовку альфа-версии тестового варианта иллюстрации концепции. А третий сценарий использования (про погружение в созданный мир) вообще не готов. И вот что я намерен делать дальше:
Я, конечно, отдаю себе отчет в том, что это по факту локальный хобби-проект, вижу много багов и неоптимальных решений в своем коде и знаю, что наверняка есть еще что-то, чего я пока не вижу. Но я бы слукавил, если бы сказал, что где-то в потайных закоулках своей темной души не надеюсь, что эта библиотека может перерасти во что-то, что можно будет использовать для прикладных задач, хотя бы в виде идеи или подхода. Поэтому призываю всех заинтересовавшихся указывать мне на мои кривые руки, обзывать индусом и тыкать носом в книжки про паттерны, если это поможет сделать мир лучше. Или агентов умнее, или богов всемогущее.
Еще раз ссылка [2]на github. И напоминаю про pygame [3]и sklearn [4], если вдруг у вас еще нет.
Автор: 9_pm
Источник [14]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/python/210512
Ссылки в тексте:
[1] агентного моделирования: https://ru.wikipedia.org/wiki/%D0%90%D0%B3%D0%B5%D0%BD%D1%82%D0%BD%D0%BE%D0%B5_%D0%BC%D0%BE%D0%B4%D0%B5%D0%BB%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5
[2] Ссылка : https://github.com/pavmav/sandbox-learn
[3] pygame: http://www.pygame.org/download.shtml
[4] sklearn: http://scikit-learn.org/stable/install.html
[5] Несколько курсов: https://www.coursera.org/specializations/machine-learning
[6] неприкасаемых : https://1c.ru/
[7] агентное моделирование: https://en.wikipedia.org/wiki/Agent-based_model
[8] Обучение с подкреплением: https://ru.wikipedia.org/wiki/%D0%9E%D0%B1%D1%83%D1%87%D0%B5%D0%BD%D0%B8%D0%B5_%D1%81_%D0%BF%D0%BE%D0%B4%D0%BA%D1%80%D0%B5%D0%BF%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5%D0%BC
[9] reinforcement learning: https://en.wikipedia.org/wiki/Reinforcement_learning
[10] конкурс на эту тему: http://blackboxchallenge.com/
[11] вот эту замечательную статью: https://habrahabr.ru/post/193888/
[12] pygame: http://pygame.org/hifi.html
[13] SGDClassifier из библиотеки sklearn: http://scikit-learn.org/stable/modules/generated/sklearn.linear_model.SGDClassifier.html
[14] Источник: https://habrahabr.ru/post/315424/?utm_source=habrahabr&utm_medium=rss&utm_campaign=sandbox
Нажмите здесь для печати.