Мой сценарий машинного обучения дает много данных (миллионы BTree
s содержится в одном корне BTree
) и сохраняет его в FileStorage
ZODB, главным образом потому, что все это не поместится в ОЗУ. Скрипт также часто изменяет ранее добавленные данные.Как повысить производительность скрипта, работающего на большом количестве данных?
Когда я увеличил сложность проблемы, и, следовательно, необходимо сохранить данные, я заметил проблемы с производительностью - скрипт теперь вычисляет данные в среднем от двух до десяти раз медленнее (единственное, что изменилось, это количество данные, которые будут сохранены и впоследствии восстановлены для изменения).
Я попытался установить cache_size
на различные значения между 1000 и 50000. Честно говоря, различия в скорости были незначительными.
Я думал о переключении на RelStorage
, но, к сожалению, в the docs они упоминают только, как настроить фреймворки, такие как Zope или Plone. Я использую только ZODB.
Интересно, будет ли RelStorage
быстрее в моем случае.
Вот как я в настоящее время установки ZODB подключение:
import ZODB
connection = ZODB.connection('zodb.fs', ...)
dbroot = connection.root()
Это для меня ясно, что ZODB в настоящее время является узким местом моего сценария. Я ищу совет, как я мог решить эту проблему.
Я выбрал ZODB beacuse Я думал, что база данных NoSQL лучше подходит для моего случая, и мне понравилась идея интерфейса, подобная Python dict
.
кода и структуры данных:
корневые структуры данных:
if not hasattr(dbroot, 'actions_values'): dbroot.actions_values = BTree() if not hasattr(dbroot, 'games_played'): dbroot.games_played = 0
actions_values
концептуально построены следующим образом:actions_values = { # BTree str(state): { # BTree # contiains actions (coulmn to pick to be exact, as I'm working on agent playing Connect 4) # and their values(only actions previously taken by the angent are present here), e.g.: 1: 0.4356 5: 0.3456 }, # other states }
state
является простой 2D-массив, представляющий игровое поле. Возможные долины это полое1
,2
илиNone
:board = [ [ None ] * cols for _ in xrange(rows) ]
(в моем случае
rows = 6
иcols = 7
)основная петля:
should_play = 10000000 transactions_freq = 10000 packing_freq = 50000 player = ReinforcementPlayer(dbroot.actions_values, config) while dbroot.games_played < should_play: # max_epsilon at start and then linearly drops to min_epsilon: epsilon = max_epsilon - (max_epsilon - min_epsilon) * dbroot.games_played/(should_play - 1) dbroot.games_played += 1 sys.stdout.write('\rPlaying game %d of %d' % (dbroot.games_played, should_play)) sys.stdout.flush() board_state = player.play_game(epsilon) if(dbroot.games_played % transactions_freq == 0): print('Commiting...') transaction.commit() if(dbroot.games_played % packing_freq == 0): print('Packing DB...') connection.db().pack()
(
pack
ИНГИ также занимает много времени, но это не основная проблема, я могу упаковать базу данных после завершения программы)код работает на
dbroot
(внутриReinforcementPlayer
):def get_actions_with_values(self, player_id, state): if player_id == 1: lookup_state = state else: lookup_state = state.switch_players() lookup_state_str = str(lookup_state) if lookup_state_str in self.actions_values: return self.actions_values[lookup_state_str] mirror_lookup_state_str = str(lookup_state.mirror()) if mirror_lookup_state_str in self.actions_values: return self.mirror_actions(self.actions_values[mirror_lookup_state_str]) return None def get_value_of_action(self, player_id, state, action, default=0): actions = self.get_actions_with_values(player_id, state) if actions is None: return default return actions.get(action, default) def set_value_of_action(self, player_id, state, action, value): if player_id == 1: lookup_state = state else: lookup_state = state.switch_players() lookup_state_str = str(lookup_state) if lookup_state_str in self.actions_values: self.actions_values[lookup_state_str][action] = value return mirror_lookup_state_str = str(lookup_state.mirror()) if mirror_lookup_state_str in self.actions_values: self.actions_values[mirror_lookup_state_str][self.mirror_action(action)] = value return self.actions_values[lookup_state_str] = BTree() self.actions_values[lookup_state_str][action] = value
(функции с зеркалом названия просто поменять местами столбцы (действия).Это делается beacuse Connect 4 доски, которые являются вертикальными отражениями друг друга эквивалентны.)
После 550000 игр len(dbroot.actions_values)
является 6018450.
Согласно iotop
операции ввода-вывод принимает 90% времени.
Ваш * размер данных * является узким местом. Здесь только ZODB может сделать это. Я не уверен, что RelStorage поможет здесь; вам нужно создать адаптер (выберите один из них, соответствующий вашей выбранной базе данных из пакета '' адаптеры '' (https://github.com/zodb/relstorage/tree/master/relostorage/adapters)) и передайте его ['RelStorage()' instance] (https://github.com/zodb/relstorage/blob/master/relstorage/storage.py#L78); используйте этот экземпляр вместо объекта FileStorage. –
@MartijnPieters Что может здесь помочь? Это первый раз, когда мне приходится иметь дело с этим большим количеством данных. Есть ли какая-нибудь другая душа, которая подойдет лучше? Я немного ошеломлен перспективой ждать неделю, пока мой алгоритм не закончится. :) – Luke
Микко вы там накрыли. –