Запуск проекта Python с разными аргументами в кластере - PullRequest
0 голосов
/ 04 декабря 2018

У меня есть python3 проект со следующей структурой каталогов:

project/
    run.py
    package/
        a.py
        b.py
        constants.py

Модули a и b используют различные общие переменные / гиперпараметры.Мне нужно запустить несколько экземпляров проекта в кластере с разными гиперпараметрами.Я отправляю задания в кластер, который затем планирует их.

Я пробовал следующее:

1. У меня был constants.py внутри package, которыйЯ изменил перед отправкой каждой работы.Допустим, я хочу запустить 5 разных наборов гиперпараметров.Проблема с этим подходом состоит в том, что кластеру требуется некоторое время для планирования моих заданий, и когда он, наконец, выполняется, все задания будут использовать последние измененные параметры (т. Е. Для 5-го запуска), хранящиеся в constants.py, а не в 5 разныхнаборы, которые я хотел.

2. Затем я использовал argparse в run.py, но не смог передать аргументы a и b внутри пакетанесмотря на попытки использования различных подходов, подобных тем, что описаны в этом SO потоке .

Так что взломать, к которому мне пришлось прибегнуть, было использовать argparse в run.py, импортировать «константы» в run.py, заново инициализировать их, а затем импортировать константы туда, где они мне нужныв a и b.Таким образом, я могу написать несколько sh сценариев с различными аргументами командной строки для run.py и запланировать их все в кластере.

Я уверен, что должно быть лучше (и больше pythonic) способ сделать это.Предложения?Благодарю.

Ответы [ 2 ]

0 голосов
/ 05 декабря 2018

Я бы предложил использовать переменные окружения, которые могут быть указаны для каждого экземпляра.

Возможно, у вас нет фактических констант, или, по крайней мере, некоторые config.py

import os 

my_val=os.environ.get('MY_VAL', 'default value')

Затем, когда вы запуститекод на нескольких экземплярах, вам нужно будет экспортировать соответствующие переменные между каждым выполнением.

Если бы вы контейнировали свое приложение, то с помощью Docker вы могли бы передать -e MY_VAL="some value", и оно будет загружено в код как

.
0 голосов
/ 04 декабря 2018

Так как я не совсем слежу, вот, по крайней мере, начало MCVE .Структура каталога проекта:

project/
    __init__.py
    run.py
    package/
        __init__.py
        a.py
        b.py
        constants.py

Начиная с package каталога (внутреннего) у меня есть:

__ init __. Py

from .a import ModelA
from .b import ModelB

a.py

from . import constants

class ModelA:
    def __init__(self, a, b):
        print("Instantiating Model A with {0} {1}".format(a, b))
        print("    Pi:{0} hbar{1}".format(constants.pi, constants.hbar))

b.py

from . import constants

class ModelB:
    def __init__(self, a, b):
        print("Instantiating Model B with {0} {1}".format(a, b))
        print("    Pi:{0} hbar{1}".format(constants.hbar, constants.pi))

constants.py

hbar = 1
pi = 3.14

Обратите внимание, что init содержит содержимое исключительно для удобства импорта project в виде пакета и немедленного использования под ним имен ModelA и ModelB.Я так же легко оставил пустой файл __init__.py, а затем from project.package.a import ModelA.

Очень похоже на то, как это делается в project dir.Верхний каталог (project) содержит пустой файл __ init __. Py и:

run.py

#!/usr/bin/evn python

import argparse
import package

parser = argparse.ArgumentParser(description='Process some integers.')
parser.add_argument('--param1', dest='param1')
parser.add_argument('--param2', dest='param2')

args = parser.parse_args()

package.ModelA(args.param1, args.param2)
package.ModelB(args.param2, args.param1)

Обратите внимание, чтоссылка на shebang может не понадобиться и зависит от ситуации при работе в кластере, где управление средой может сыграть свою роль.

Запустив это из терминала, вы получите

$:> python3 project/run.py --param1 10 --param2 100
Instantiating Model A with 10 100
    Pi:3.14 hbar1
Instantiating Model B with 100 10
    Pi:1 hbar3.14

Теперь возьмите это и улучшите то, что у вас есть, или восстановите то, что вы пытаетесь сделать, в упрощенном виде, как это (надеюсь, что вы нарушите пример), а затем опубликуйте, какая часть не работает и почему.

Редактировать

Позвольте мне предвосхитить решение утверждением, что подобные действия настраивают себя на неудачу.Мне кажется, что у вас есть файл run.py, в котором вы хотите проанализировать аргументы, передаваемые через терминал, и использовать их для установки глобального состояния выполнения вашей программы.Вы не должны делать это почти всегда (единственное исключение, о котором я знаю, это то, что иногда и только иногда можно использовать для установки глобального состояния для движка или сеанса при подключении кбазы данных, но даже тогда это обычно не единственное или лучшее решение).
Именно по этой причине вам нужны модули и пакеты.Не должно быть никаких причин, по которым вы не сможете анализировать свои входные данные в run.py и **, вызывая ** функциональность любого из ваших подмодулей.Сколько параметров принимают функции в a и b или принимают ли они и используют ли все или ни один из отправленных параметров, буквально не имеет значения.Вы можете отредактировать приведенный выше пример так, чтобы классам A и B требовались только 1, 3 или 10, или A 5 и B ни один из параметров, и он все равно работал бы.

a.py

from . import constants
from project.run import args

print("from A", args)

class ModelA:
    def __init__(self, a, b):
        print("Instantiating Model A with {0} {1}".format(a, b))
        print("    Pi:{0} hbar{1}".format(constants.pi, constants.hbar))

b.py

from . import constants
from project.run import args

print("from B", args)

class ModelB:
    def __init__(self, a, b):
        print("Instantiating Model B with {0} {1}".format(a, b))
        print("    Pi:{0} hbar{1}".format(constants.hbar, constants.pi))

run.py

#!/usr/bin/evn python
import argparse
from . import package

parser = argparse.ArgumentParser(description='Process some integers.')
parser.add_argument('--param1', dest='param1')
parser.add_argument('--param2', dest='param2')

args = parser.parse_args()

if __name__ == "__main__":
    package.ModelA(args.param1, args.param2)
    package.ModelB(args.param2, args.param1)

И затем вызвать его как

$:> python3 -m project.run --param1 10 --param2 100
from A Namespace(param1='10', param2='100')
from B Namespace(param1='10', param2='100')
Instantiating Model A with 10 100
    Pi:3.14 hbar1
Instantiating Model B with 100 10
    Pi:1 hbar3.14

Обратите внимание, что мало что изменилось.Практически все, что мы действительно импортировали, args из run.py, используя абсолютные пути импорта вместо относительных, и затем мы переместили код выполнения в if __name__ == "__main__":, чтобы он не вызывался при каждом импорте - только тот, который делает этоскрипт "основной" программы.Единственным большим и важным изменением было обращение к сценарию.Терминальная команда python3 -m project.run импортирует модуль и затем запускает его как скрипт, тогда как ранее использованный python3 project/run.py просто попытается запустить run.py как скрипт.Когда модуль впервые импортируется, устанавливается значение __package__;когда установлен __package__, он разрешает явный относительный импорт, поэтому операторы from project.run import ... работают, потому что теперь python знает, куда он должен обратиться, чтобы найти эти значения.Если run.py запускается точно так же, как скрипт, то когда в старом run.py Python вызывается оператор import package, он переходит в каталог package/, но не знает, что может потребоваться вернуться на один уровень вверхrun.py) для поиска значений, определенных там, и импорта их на более низкий уровень в package/ dir.

...