浏览代码
Merge pull request #1003 from dericp/develop-curriculum-learning-rework
Merge pull request #1003 from dericp/develop-curriculum-learning-rework
Curriculum learning now supports multiple brains./develop-generalizationTraining-TrainerController
GitHub
6 年前
当前提交
322d2bbe
共有 21 个文件被更改,包括 536 次插入 和 204 次删除
-
5.gitignore
-
124docs/Training-Curriculum-Learning.md
-
53python/tests/test_unitytrainers.py
-
4python/unityagents/environment.py
-
1python/unitytrainers/__init__.py
-
107python/unitytrainers/curriculum.py
-
5python/unitytrainers/exception.py
-
8python/unitytrainers/trainer.py
-
89python/unitytrainers/trainer_controller.py
-
3python/curricula/wall-jump/BigWallBrain.json
-
93python/tests/test_curriculum.py
-
109python/tests/test_meta_curriculum.py
-
105python/unitytrainers/meta_curriculum.py
-
12python/curricula/push-block/PushBlockBrain.json
-
10python/curricula/wall-jump/SmallWallBrain.json
-
12python/curricula/push.json
-
0/python/curricula/test/TestBrain.json
-
0/python/curricula/wall-jump/BigWallBrain.json
|
|||
import pytest |
|||
import json |
|||
from unittest.mock import patch, mock_open |
|||
|
|||
from unitytrainers.exception import CurriculumError |
|||
from unitytrainers import Curriculum |
|||
|
|||
|
|||
dummy_curriculum_json_str = ''' |
|||
{ |
|||
"measure" : "reward", |
|||
"thresholds" : [10, 20, 50], |
|||
"min_lesson_length" : 3, |
|||
"signal_smoothing" : true, |
|||
"parameters" : |
|||
{ |
|||
"param1" : [0.7, 0.5, 0.3, 0.1], |
|||
"param2" : [100, 50, 20, 15], |
|||
"param3" : [0.2, 0.3, 0.7, 0.9] |
|||
} |
|||
} |
|||
''' |
|||
|
|||
|
|||
bad_curriculum_json_str = ''' |
|||
{ |
|||
"measure" : "reward", |
|||
"thresholds" : [10, 20, 50], |
|||
"min_lesson_length" : 3, |
|||
"signal_smoothing" : false, |
|||
"parameters" : |
|||
{ |
|||
"param1" : [0.7, 0.5, 0.3, 0.1], |
|||
"param2" : [100, 50, 20], |
|||
"param3" : [0.2, 0.3, 0.7, 0.9] |
|||
} |
|||
} |
|||
''' |
|||
|
|||
@pytest.fixture |
|||
def location(): |
|||
return 'TestBrain.json' |
|||
|
|||
|
|||
@pytest.fixture |
|||
def default_reset_parameters(): |
|||
return {"param1": 1, "param2": 1, "param3": 1} |
|||
|
|||
|
|||
@patch('builtins.open', new_callable=mock_open, read_data=dummy_curriculum_json_str) |
|||
def test_init_curriculum_happy_path(mock_file, location, default_reset_parameters): |
|||
curriculum = Curriculum(location, default_reset_parameters) |
|||
|
|||
assert curriculum._brain_name == 'TestBrain' |
|||
assert curriculum.lesson_num == 0 |
|||
assert curriculum.measure == 'reward' |
|||
|
|||
|
|||
@patch('builtins.open', new_callable=mock_open, read_data=bad_curriculum_json_str) |
|||
def test_init_curriculum_bad_curriculum_raises_error(mock_file, location, default_reset_parameters): |
|||
with pytest.raises(CurriculumError): |
|||
Curriculum(location, default_reset_parameters) |
|||
|
|||
|
|||
@patch('builtins.open', new_callable=mock_open, read_data=dummy_curriculum_json_str) |
|||
def test_increment_lesson(mock_file, location, default_reset_parameters): |
|||
curriculum = Curriculum(location, default_reset_parameters) |
|||
assert curriculum.lesson_num == 0 |
|||
|
|||
curriculum.lesson_num = 1 |
|||
assert curriculum.lesson_num == 1 |
|||
|
|||
curriculum.increment_lesson(10) |
|||
assert curriculum.lesson_num == 1 |
|||
|
|||
curriculum.increment_lesson(30) |
|||
curriculum.increment_lesson(30) |
|||
assert curriculum.lesson_num == 1 |
|||
assert curriculum.lesson_length == 3 |
|||
|
|||
curriculum.increment_lesson(30) |
|||
assert curriculum.lesson_length == 0 |
|||
assert curriculum.lesson_num == 2 |
|||
|
|||
|
|||
@patch('builtins.open', new_callable=mock_open, read_data=dummy_curriculum_json_str) |
|||
def test_get_config(mock_file): |
|||
curriculum = Curriculum('TestBrain.json', {"param1": 1, "param2": 1, "param3": 1}) |
|||
assert curriculum.get_config() == {"param1": 0.7, "param2": 100, "param3": 0.2} |
|||
|
|||
curriculum.lesson_num = 2 |
|||
assert curriculum.get_config() == {'param1': 0.3, 'param2': 20, 'param3': 0.7} |
|||
assert curriculum.get_config(0) == {"param1": 0.7, "param2": 100, "param3": 0.2} |
|
|||
import pytest |
|||
from unittest.mock import patch, call, Mock |
|||
|
|||
from unitytrainers.meta_curriculum import MetaCurriculum |
|||
from unitytrainers.exception import MetaCurriculumError |
|||
|
|||
|
|||
class MetaCurriculumTest(MetaCurriculum): |
|||
"""This class allows us to test MetaCurriculum objects without calling |
|||
MetaCurriculum's __init__ function. |
|||
""" |
|||
def __init__(self, brains_to_curriculums): |
|||
self._brains_to_curriculums = brains_to_curriculums |
|||
|
|||
|
|||
@pytest.fixture |
|||
def default_reset_parameters(): |
|||
return {'param1' : 1, 'param2' : 2, 'param3' : 3} |
|||
|
|||
|
|||
@pytest.fixture |
|||
def more_reset_parameters(): |
|||
return {'param4' : 4, 'param5' : 5, 'param6' : 6} |
|||
|
|||
|
|||
@pytest.fixture |
|||
def progresses(): |
|||
return {'Brain1' : 0.2, 'Brain2' : 0.3} |
|||
|
|||
|
|||
@patch('unitytrainers.Curriculum.get_config', return_value={}) |
|||
@patch('unitytrainers.Curriculum.__init__', return_value=None) |
|||
@patch('os.listdir', return_value=['Brain1.json', 'Brain2.json']) |
|||
def test_init_meta_curriculum_happy_path(listdir, mock_curriculum_init, |
|||
mock_curriculum_get_config, |
|||
default_reset_parameters): |
|||
meta_curriculum = MetaCurriculum('test/', default_reset_parameters) |
|||
|
|||
assert len(meta_curriculum.brains_to_curriculums) == 2 |
|||
|
|||
assert 'Brain1' in meta_curriculum.brains_to_curriculums |
|||
assert 'Brain2' in meta_curriculum.brains_to_curriculums |
|||
|
|||
calls = [call('test/Brain1.json', default_reset_parameters), |
|||
call('test/Brain2.json', default_reset_parameters)] |
|||
|
|||
mock_curriculum_init.assert_has_calls(calls) |
|||
|
|||
|
|||
@patch('os.listdir', side_effect=NotADirectoryError()) |
|||
def test_init_meta_curriculum_bad_curriculum_folder_raises_error(listdir): |
|||
with pytest.raises(MetaCurriculumError): |
|||
MetaCurriculum('test/', default_reset_parameters) |
|||
|
|||
|
|||
@patch('unitytrainers.Curriculum') |
|||
@patch('unitytrainers.Curriculum') |
|||
def test_set_lesson_nums(curriculum_a, curriculum_b): |
|||
meta_curriculum = MetaCurriculumTest({'Brain1' : curriculum_a, |
|||
'Brain2' : curriculum_b}) |
|||
|
|||
meta_curriculum.lesson_nums = {'Brain1' : 1, 'Brain2' : 3} |
|||
|
|||
assert curriculum_a.lesson_num == 1 |
|||
assert curriculum_b.lesson_num == 3 |
|||
|
|||
|
|||
|
|||
@patch('unitytrainers.Curriculum') |
|||
@patch('unitytrainers.Curriculum') |
|||
def test_increment_lessons(curriculum_a, curriculum_b, progresses): |
|||
meta_curriculum = MetaCurriculumTest({'Brain1' : curriculum_a, |
|||
'Brain2' : curriculum_b}) |
|||
|
|||
meta_curriculum.increment_lessons(progresses) |
|||
|
|||
curriculum_a.increment_lesson.assert_called_with(0.2) |
|||
curriculum_b.increment_lesson.assert_called_with(0.3) |
|||
|
|||
|
|||
@patch('unitytrainers.Curriculum') |
|||
@patch('unitytrainers.Curriculum') |
|||
def test_set_all_curriculums_to_lesson_num(curriculum_a, curriculum_b): |
|||
meta_curriculum = MetaCurriculumTest({'Brain1' : curriculum_a, |
|||
'Brain2' : curriculum_b}) |
|||
|
|||
meta_curriculum.set_all_curriculums_to_lesson_num(2) |
|||
|
|||
assert curriculum_a.lesson_num == 2 |
|||
assert curriculum_b.lesson_num == 2 |
|||
|
|||
|
|||
@patch('unitytrainers.Curriculum') |
|||
@patch('unitytrainers.Curriculum') |
|||
def test_get_config(curriculum_a, curriculum_b, default_reset_parameters, |
|||
more_reset_parameters): |
|||
curriculum_a.get_config.return_value = default_reset_parameters |
|||
curriculum_b.get_config.return_value = default_reset_parameters |
|||
meta_curriculum = MetaCurriculumTest({'Brain1' : curriculum_a, |
|||
'Brain2' : curriculum_b}) |
|||
|
|||
assert meta_curriculum.get_config() == default_reset_parameters |
|||
|
|||
curriculum_b.get_config.return_value = more_reset_parameters |
|||
|
|||
new_reset_parameters = dict(default_reset_parameters) |
|||
new_reset_parameters.update(more_reset_parameters) |
|||
|
|||
assert meta_curriculum.get_config() == new_reset_parameters |
|
|||
"""Contains the MetaCurriculum class.""" |
|||
|
|||
import os |
|||
from unitytrainers.curriculum import Curriculum |
|||
from unitytrainers.exception import MetaCurriculumError |
|||
|
|||
import logging |
|||
|
|||
logger = logging.getLogger('unitytrainers') |
|||
|
|||
|
|||
class MetaCurriculum(object): |
|||
"""A MetaCurriculum holds curriculums. Each curriculum is associated to a particular |
|||
brain in the environment. |
|||
""" |
|||
|
|||
def __init__(self, curriculum_folder, default_reset_parameters): |
|||
"""Initializes a MetaCurriculum object. |
|||
|
|||
Args: |
|||
curriculum_folder (str): The relative or absolute path of the |
|||
folder which holds the curriculums for this environment. |
|||
The folder should contain JSON files whose names are the |
|||
brains that the curriculums belong to. |
|||
default_reset_parameters (dict): The default reset parameters |
|||
of the environment. |
|||
""" |
|||
used_reset_parameters = set() |
|||
self._brains_to_curriculums = {} |
|||
|
|||
try: |
|||
for curriculum_filename in os.listdir(curriculum_folder): |
|||
brain_name = curriculum_filename.split('.')[0] |
|||
curriculum_filepath = \ |
|||
os.path.join(curriculum_folder, curriculum_filename) |
|||
curriculum = Curriculum(curriculum_filepath, default_reset_parameters) |
|||
|
|||
# Check if any two curriculums use the same reset params. |
|||
if any([(parameter in curriculum.get_config().keys()) for parameter in used_reset_parameters]): |
|||
logger.warning('Two or more curriculums will ' |
|||
'attempt to change the same reset ' |
|||
'parameter. The result will be ' |
|||
'non-deterministic.') |
|||
|
|||
used_reset_parameters.update(curriculum.get_config().keys()) |
|||
self._brains_to_curriculums[brain_name] = curriculum |
|||
except NotADirectoryError: |
|||
raise MetaCurriculumError(curriculum_folder + ' is not a ' |
|||
'directory. Refer to the ML-Agents ' |
|||
'curriculum learning docs.') |
|||
|
|||
|
|||
@property |
|||
def brains_to_curriculums(self): |
|||
"""A dict from brain_name to the brain's curriculum.""" |
|||
return self._brains_to_curriculums |
|||
|
|||
@property |
|||
def lesson_nums(self): |
|||
"""A dict from brain name to the brain's curriculum's lesson number.""" |
|||
lesson_nums = {} |
|||
for brain_name, curriculum in self.brains_to_curriculums.items(): |
|||
lesson_nums[brain_name] = curriculum.lesson_num |
|||
|
|||
return lesson_nums |
|||
|
|||
@lesson_nums.setter |
|||
def lesson_nums(self, lesson_nums): |
|||
for brain_name, lesson in lesson_nums.items(): |
|||
self.brains_to_curriculums[brain_name].lesson_num = lesson |
|||
|
|||
def increment_lessons(self, progresses): |
|||
"""Increments all the lessons of all the curriculums in this MetaCurriculum. |
|||
|
|||
Args: |
|||
progresses (dict): A dict of brain name to progress. |
|||
""" |
|||
for brain_name, progress in progresses.items(): |
|||
self.brains_to_curriculums[brain_name].increment_lesson(progress) |
|||
|
|||
|
|||
def set_all_curriculums_to_lesson_num(self, lesson_num): |
|||
"""Sets all the curriculums in this meta curriculum to a specified lesson number. |
|||
|
|||
Args: |
|||
lesson_num (int): The lesson number which all the curriculums will |
|||
be set to. |
|||
""" |
|||
for _, curriculum in self.brains_to_curriculums.items(): |
|||
curriculum.lesson_num = lesson_num |
|||
|
|||
|
|||
def get_config(self): |
|||
"""Get the combined configuration of all curriculums in this MetaCurriculum. |
|||
|
|||
Returns: |
|||
A dict from parameter to value. |
|||
""" |
|||
config = {} |
|||
|
|||
for _, curriculum in self.brains_to_curriculums.items(): |
|||
curr_config = curriculum.get_config() |
|||
config.update(curr_config) |
|||
|
|||
return config |
|
|||
{ |
|||
"measure" : "reward", |
|||
"thresholds" : [0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75], |
|||
"min_lesson_length" : 2, |
|||
"signal_smoothing" : true, |
|||
"parameters" : |
|||
{ |
|||
"goal_size" : [3.5, 3.25, 3.0, 2.75, 2.5, 2.25, 2.0, 1.75, 1.5, 1.25, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], |
|||
"block_size": [1.5, 1.4, 1.3, 1.2, 1.1, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], |
|||
"x_variation":[1.5, 1.55, 1.6, 1.65, 1.7, 1.75, 1.8, 1.85, 1.9, 1.95, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5] |
|||
} |
|||
} |
|
|||
{ |
|||
"measure" : "progress", |
|||
"thresholds" : [0.1, 0.3, 0.5], |
|||
"min_lesson_length" : 2, |
|||
"signal_smoothing" : true, |
|||
"parameters" : |
|||
{ |
|||
"small_wall_height" : [1.5, 2.0, 2.5, 4.0] |
|||
} |
|||
} |
|
|||
{ |
|||
"measure" : "reward", |
|||
"thresholds" : [0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75], |
|||
"min_lesson_length" : 2, |
|||
"signal_smoothing" : true, |
|||
"parameters" : |
|||
{ |
|||
"goal_size" : [3.5, 3.25, 3.0, 2.75, 2.5, 2.25, 2.0, 1.75, 1.5, 1.25, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], |
|||
"block_size": [1.5, 1.4, 1.3, 1.2, 1.1, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], |
|||
"x_variation":[1.5, 1.55, 1.6, 1.65, 1.7, 1.75, 1.8, 1.85, 1.9, 1.95, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5] |
|||
} |
|||
} |
撰写
预览
正在加载...
取消
保存
Reference in new issue