├── .gitignore ├── LICENSE ├── README.md ├── notes_technical.txt ├── reward_function.py └── sketch.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Falk Tandetzky 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # deepracer 2 | 3 | This is the code used by Falk Tandetzky during the Deepracer Championship 2019. 4 | 5 | ## See also 6 | 7 | Explanation of the reward function: https://medium.com/twodigits/aws-deepracer-how-to-train-a-model-in-15-minutes-3a0dca1175fb 8 | 9 | The story: https://medium.com/twodigits/the-aws-deepraceing-championship-at-re-invent-in-las-vegas-and-how-i-got-there-2595394427d1 10 | -------------------------------------------------------------------------------- /notes_technical.txt: -------------------------------------------------------------------------------- 1 | ToDo 2 | [x] build solid model -> 2005 / 2006 3 | [x] rebuild solid model with new interface 4 | - try out: 5 | [x] 7 steering angles 6 | [x] cut-corners increase 7 | [x] increase Entropie 8 | [ ] anealing Entropie 9 | [x] longer learning 10 | [ ] reduce discount-rate 11 | 12 | 13 | Championship Cup @ Las Vegas 14 | 15 | Vehicles 16 | ---- 17 | Accenture_005: (grey) 3 CNN, 1.0 m/s, 7 Angles, 1 Speed (mod from Accenture_003) 18 | Accenture_004: (white) 3 CNN, 1.5 m/s, 7 Angles, 1 Speed (mod from Accenture_101010) -> pretty fast 19 | Accenture_003: (orange) 3 CNN, 2.0 m/s, 5 Angles, 1 Speed (mod from Accenture 101010) -> too fast 20 | Accenture_002: (blue) 3 CNN, 1.0 m/s, 5 Angles, 1 Speed 21 | Accenture_001: (red) 3 CNN, 0.3 m/s, 5 Angles, 1 Speed 22 | Accenture_101010: (purple) 3 CNN, 3 m/s, 5 Angles, 1 Speed -> too fast 23 | Accenture 101010: (purple) 5 CNN, 3 m/s, 5 Angles, 1 Speed -> too fast 24 | 25 | 26 | 27 | Models 28 | ---- 29 | ->> !(8.5)(7 angles;) Model-2113: Accenture_005, discount-rate: 0.5, cut-corners: 0.9, update-each: 5, 20 min. 30 | !(8) (7 angles; good; maybe overfitted a little) Model-2112: Accenture_005, discount-rate: 0.5, cut-corners: 0.8, update-each: 5, 25 min. 31 | (7) (fast but good) Model-2111-001: +10 min. 32 | !(8) (fast but good) Model-2111: Accenture_004, discount-rate: 0.5, cut-corners: 0.8, update-each: 5, 25 min. 33 | (1) (too fast) Model-2110: Accenture_003, discount-rate: 0.5, cut-corners: 0.8, update-each: 5, 25 min. 34 | !(6) (clone does not achieve 100% track completion) Model-2109: Accenture_002, S=0.1, discount-rate: 0.5, cut-corners: 0.9, update-each: 5, 60 min. 35 | (8) (looks good; may be underconverged a little) Model-2108: Accenture_002, discount-rate: 0.5, cut-corners: 0.8, update-each: 5, 15 min. 36 | ->> !(9) (looks good; might cut corners too much?) Model-2107: Accenture_002, discount-rate: 0.5, cut-corners: 1.0, update-each: 7, 18 min. 37 | !(6) (may be overfitted) Model-2106: Accenture_002, discount-rate: 0.5, cut-corners: 1.0, update-each: 7, 30 min. 38 | (3) (slow vehicle) Model-2105: Accenture_001, discount-rate: 0.5, cut-corners: 1.0, update-each: 7, 30 min. 39 | (0) (not good because of too fast vehicle) Model-2104: Accenture_101010, Speed: 3.0, discount-rate: 0.5, cut-corners: 1.0, update-each: 7, 40 min. 40 | (0) (not good because of too fast vehicle) Model-2103: Accenture 101010, Speed: 3.0, discount-rate: 0.999, cut-corners: 1.0, update-each: 7, 40 min. 41 | (2) (good but old UI) Model-2006: Speed: 3.0, discount-rate: 0.5, cut-corners: 1.0, update-each: 7, 15 min 42 | (2) (good but old UI) Model-2005: Speed: 3.0, discount-rate: 0.5, cut-corners: 1.0, update-each: 7, 20 min 43 | Model-2003: Speed: 3.0, discount-rate: 0.5, cut-corners: 1.0, update-each: 7, 60 min. 44 | 45 | 46 | 47 | 48 | Kronberg 49 | --- 50 | 51 | submissions 52 | 53 | 1: Model100 54 | 2: Model103 55 | 3: Model104 <--- the model used for all runs that made it to the leader-board 56 | 4: Model107 57 | 5: Model108 58 | 6: ? didnt work when trying Model104-002 59 | 7: ? didnt work when trying again Model104-002 60 | 61 | 62 | Model104: Speed: 1.5, discount-rate: 0.5, cut-corners: 1.5, update-each:10, 20 min. 63 | Modell05: Speed: 6.0, discount-rate: 0.5, cut-corners: 1.3, update-each:10, 15 min. 64 | Modell06: Speed: 1.5, discount-rate: 0.5, cut-corners: 1.3, max-steering: 20, update-each:10, duration: 15 min. 65 | Model107: Speed: 1.5, discount-rate: 0.5, cut-corners: 1.5, max-steering: 20, steering-df: 3, update-each: 5, duration: 18 min. 66 | Model108: Speed: 1.5, discount-rate: 0.5, cut-corners: 1.5, max-steering: 15, steering-df: 3, update-each: 5, duration: 18 min. 67 | Model121: Speed: 1.5, discount-rate: 0.5, cut-corners: 1.3, update-each:10, 20 min. on London Loop 68 | model104-002: Speed: 1.5, discount-rate: 0.5, cut-corners: 1.5, update-each:10, 20 min. Re-Invent + 12 min. London 69 | 70 | 71 | Meaning of the parameters 72 | --- 73 | - parameters not mentioned are set to the default values 74 | - cut-corners: The factor used to determine the radius of the circle used to find the point the car should aim for. The Radius is the product of this and the width of the track 75 | - update-each: Number of epochs per learning cycle (the last in the list of hyper parameters) 76 | -------------------------------------------------------------------------------- /reward_function.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | 4 | def dist(point1, point2): 5 | return ((point1[0] - point2[0]) ** 2 + (point1[1] - point2[1]) ** 2) ** 0.5 6 | 7 | 8 | # thanks to https://stackoverflow.com/questions/20924085/python-conversion-between-coordinates 9 | def rect(r, theta): 10 | """ 11 | theta in degrees 12 | 13 | returns tuple; (float, float); (x,y) 14 | """ 15 | 16 | x = r * math.cos(math.radians(theta)) 17 | y = r * math.sin(math.radians(theta)) 18 | return x, y 19 | 20 | 21 | # thanks to https://stackoverflow.com/questions/20924085/python-conversion-between-coordinates 22 | def polar(x, y): 23 | """ 24 | returns r, theta(degrees) 25 | """ 26 | 27 | r = (x ** 2 + y ** 2) ** .5 28 | theta = math.degrees(math.atan2(y,x)) 29 | return r, theta 30 | 31 | 32 | def angle_mod_360(angle): 33 | """ 34 | Maps an angle to the interval -180, +180. 35 | 36 | Examples: 37 | angle_mod_360(362) == 2 38 | angle_mod_360(270) == -90 39 | 40 | :param angle: angle in degree 41 | :return: angle in degree. Between -180 and +180 42 | """ 43 | 44 | n = math.floor(angle/360.0) 45 | 46 | angle_between_0_and_360 = angle - n*360.0 47 | 48 | if angle_between_0_and_360 <= 180.0: 49 | return angle_between_0_and_360 50 | else: 51 | return angle_between_0_and_360 - 360 52 | 53 | 54 | def get_waypoints_ordered_in_driving_direction(params): 55 | # waypoints are always provided in counter clock wise order 56 | if params['is_reversed']: # driving clock wise. 57 | return list(reversed(params['waypoints'])) 58 | else: # driving counter clock wise. 59 | return params['waypoints'] 60 | 61 | 62 | def up_sample(waypoints, factor): 63 | """ 64 | Adds extra waypoints in between provided waypoints 65 | 66 | :param waypoints: 67 | :param factor: integer. E.g. 3 means that the resulting list has 3 times as many points. 68 | :return: 69 | """ 70 | p = waypoints 71 | n = len(p) 72 | 73 | return [[i / factor * p[(j+1) % n][0] + (1 - i / factor) * p[j][0], 74 | i / factor * p[(j+1) % n][1] + (1 - i / factor) * p[j][1]] for j in range(n) for i in range(factor)] 75 | 76 | 77 | def get_target_point(params): 78 | waypoints = up_sample(get_waypoints_ordered_in_driving_direction(params), 20) 79 | 80 | car = [params['x'], params['y']] 81 | 82 | distances = [dist(p, car) for p in waypoints] 83 | min_dist = min(distances) 84 | i_closest = distances.index(min_dist) 85 | 86 | n = len(waypoints) 87 | 88 | waypoints_starting_with_closest = [waypoints[(i+i_closest) % n] for i in range(n)] 89 | 90 | r = params['track_width'] * 0.9 91 | 92 | is_inside = [dist(p, car) < r for p in waypoints_starting_with_closest] 93 | i_first_outside = is_inside.index(False) 94 | 95 | if i_first_outside < 0: # this can only happen if we choose r as big as the entire track 96 | return waypoints[i_closest] 97 | 98 | return waypoints_starting_with_closest[i_first_outside] 99 | 100 | 101 | def get_target_steering_degree(params): 102 | tx, ty = get_target_point(params) 103 | car_x = params['x'] 104 | car_y = params['y'] 105 | dx = tx-car_x 106 | dy = ty-car_y 107 | heading = params['heading'] 108 | 109 | _, target_angle = polar(dx, dy) 110 | 111 | steering_angle = target_angle - heading 112 | 113 | return angle_mod_360(steering_angle) 114 | 115 | 116 | def score_steer_to_point_ahead(params): 117 | best_stearing_angle = get_target_steering_degree(params) 118 | steering_angle = params['steering_angle'] 119 | 120 | error = (steering_angle - best_stearing_angle) / 60.0 # 60 degree is already really bad 121 | 122 | score = 1.0 - abs(error) 123 | 124 | return max(score, 0.01) # optimizer is rumored to struggle with negative numbers and numbers too close to zero 125 | 126 | 127 | def reward_function(params): 128 | return float(score_steer_to_point_ahead(params)) 129 | 130 | 131 | def get_test_params(): 132 | return { 133 | 'x': 0.7, 134 | 'y': 1.05, 135 | 'heading': 160.0, 136 | 'track_width': 0.45, 137 | 'is_reversed': False, 138 | 'steering_angle': 0.0, 139 | 'waypoints': [ 140 | [0.75, -0.7], 141 | [1.0, 0.0], 142 | [0.7, 0.52], 143 | [0.58, 0.7], 144 | [0.48, 0.8], 145 | [0.15, 0.95], 146 | [-0.1, 1.0], 147 | [-0.7, 0.75], 148 | [-0.9, 0.25], 149 | [-0.9, -0.55], 150 | ] 151 | } 152 | 153 | 154 | def test_reward(): 155 | params = get_test_params() 156 | 157 | reward = reward_function(params) 158 | 159 | print("test_reward: {}".format(reward)) 160 | 161 | assert reward > 0.0 162 | 163 | 164 | def test_get_target_point(): 165 | result = get_target_point(get_test_params()) 166 | expected = [0.33, 0.86] 167 | eps = 0.1 168 | 169 | print("get_target_point: x={}, y={}".format(result[0], result[1])) 170 | 171 | assert dist(result, expected) < eps 172 | 173 | 174 | def test_get_target_steering(): 175 | result = get_target_steering_degree(get_test_params()) 176 | expected = 46 177 | eps = 1.0 178 | 179 | print("get_target_steering={}".format(result)) 180 | 181 | assert abs(result - expected) < eps 182 | 183 | 184 | def test_angle_mod_360(): 185 | eps = 0.001 186 | 187 | assert abs(-90 - angle_mod_360(270.0)) < eps 188 | assert abs(-179 - angle_mod_360(181)) < eps 189 | assert abs(0.01 - angle_mod_360(360.01)) < eps 190 | assert abs(5 - angle_mod_360(365.0)) < eps 191 | assert abs(-2 - angle_mod_360(-722)) < eps 192 | 193 | def test_upsample(): 194 | params = get_test_params() 195 | print(repr(up_sample(params['waypoints'], 2))) 196 | 197 | def test_score_steer_to_point_ahead(): 198 | params_l_45 = {**get_test_params(), 'steering_angle': +45} 199 | params_l_15 = {**get_test_params(), 'steering_angle': +15} 200 | params_0 = {**get_test_params(), 'steering_angle': 0.0} 201 | params_r_15 = {**get_test_params(), 'steering_angle': -15} 202 | params_r_45 = {**get_test_params(), 'steering_angle': -45} 203 | 204 | sc = score_steer_to_point_ahead 205 | 206 | # 0.828, 0.328, 0.078, 0.01, 0.01 207 | print("Scores: {}, {}, {}, {}, {}".format(sc(params_l_45), sc(params_l_15), sc(params_0), sc(params_r_15), sc(params_r_45))) 208 | 209 | 210 | def run_tests(): 211 | test_angle_mod_360() 212 | test_reward() 213 | test_upsample() 214 | test_get_target_point() 215 | test_get_target_steering() 216 | test_score_steer_to_point_ahead() 217 | 218 | print("All tests successful") 219 | 220 | 221 | # run_tests() 222 | -------------------------------------------------------------------------------- /sketch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwoDigits/deepracer/1b9ba6d62e53c5eb89a42cf970fbf2b4d33afa13/sketch.png --------------------------------------------------------------------------------