Al terminar sabrás…
- Usar variables, números (int y float), booleanos y comentarios en Python
- Tomar decisiones con if, elif y else combinando and, or y not
- Escribir una función que reciba params y devuelva un float con return
- Leer datos de diccionarios y listas, como params['speed'] o waypoints[closest_waypoints[1]]
- Detectar los errores típicos y probar una función antes de entrenar con Validate
Vídeo: Python mínimo para DeepRacer en 5 minutos
Guion del vídeo (texto)
¿Por qué Python?. Para enseñar a tu coche a conducir tendrás que escribir una función de recompensa, y se escribe en Python. Tranquilidad: no hace falta ser programador. Con variables, decisiones, funciones, diccionarios y listas tienes de sobra. En este vídeo vas a ver justo eso, con ejemplos sacados de las funciones oficiales de DeepRacer, para que cuando las abras en la plataforma entiendas cada línea.
Variables y tipos de datos. Una variable es como una caja con una etiqueta: guardas un valor y luego lo usas por su nombre. Los números enteros se llaman int y los que tienen decimales se llaman float. Ojo, los decimales se escriben con punto, no con coma. Los booleanos solo pueden valer verdadero o falso, y en Python se escriben con la primera letra en mayúscula. Lo que va detrás de la almohadilla es un comentario y Python lo ignora.
Decisiones con if, elif y else. Para decidir usamos if, que significa si, elif, que significa si no pero si, y else, que significa en cualquier otro caso. Python mira las condiciones de arriba abajo y se queda con la primera que se cumple. Fíjate en dos detalles: cada condición termina con dos puntos, y lo que va dentro está desplazado cuatro espacios. Esa sangría no es decoración, es parte del lenguaje.
Combinar condiciones. A veces una condición no basta. Con and pides que se cumplan las dos a la vez, por ejemplo ir rápido y además con el volante recto. Con or te vale cualquiera de las dos. Y not le da la vuelta a un valor: si el coche no tiene todas las ruedas en la pista, la condición se cumple y le damos una recompensa casi nula.
Funciones y return. Una función es como una máquina de zumos: le metes algo, trabaja y te devuelve un resultado. Se crea con def, se le pone un nombre y entre paréntesis lo que recibe. En DeepRacer recibe un paquete de datos llamado params. La palabra return devuelve el resultado y termina la función. Este es el esqueleto oficial, y por eso todos los ejemplos acaban convirtiendo la recompensa en un número decimal.
Diccionarios, la ficha del coche. Un diccionario es como la ficha de un personaje de videojuego: cada dato tiene un nombre y un valor. El diccionario params trae la velocidad, el ángulo del volante, la distancia al centro y muchos datos más. Para leer uno escribes params, abres corchetes y pones el nombre entre comillas, exactamente igual que en la documentación. Si te equivocas en una sola letra, la función falla.
Listas e índices. Una lista es una fila de valores en orden, y lo más importante es que se empieza a contar en cero. El parámetro closest waypoints es una lista con dos números: la posición del punto de referencia que queda detrás del coche y la del que queda delante. Si lo usas dentro de la lista de waypoints, obtienes las coordenadas del siguiente punto. Se lee de dentro hacia fuera, como abrir una muñeca rusa.
Errores típicos y Validate. Casi todos los errores de principiante son los mismos: la sangría descolocada, olvidar los dos puntos, usar un igual cuando querías comparar, escribir true en minúscula o terminar sin return. Antes de gastar horas de entrenamiento, pulsa el botón Validate del editor. La plataforma revisa tu código y te avisa si hay un error de sintaxis o una importación no permitida. Arreglarlo ahí cuesta segundos.
Tu coche aprende a conducir gracias a una función de recompensa que escribes tú, y esa función se escribe en Python. La buena noticia es que no necesitas aprender todo Python: con media docena de ideas puedes leer y modificar cualquier ejemplo oficial de DeepRacer. Este módulo es exactamente esa media docena, con ejercicios para practicar. Si ya programas, échale un vistazo rápido a los errores típicos y salta a la actividad.
Variables, números y comentarios
Una variable es una caja con una etiqueta. Guardas un valor dentro y después lo usas por su nombre.
# Todo lo que va detrás de la almohadilla es un comentario: Python lo ignora
vueltas = 3 # int: número entero
velocidad = 1.5 # float: número con decimales
en_pista = True # booleano: solo puede ser True o False
casi_cero = 1e-3 # float escrito en notación científica: 0.001
Tres cosas que conviene grabarse:
- Los decimales se escriben con punto:
1.5, nunca1,5. TrueyFalsevan con la primera letra en mayúscula.truees un error.1e-3significa "1 por 10 elevado a menos 3", es decir,0.001. Los ejemplos oficiales lo usan como "recompensa casi nula" porque, según su propio comentario, el cero se reserva para cuando el coche se sale o choca.
Con los números puedes hacer las operaciones de siempre: +, -, * y /. Hay tres que verás en los ejemplos oficiales:
| Escribes | Significa | Ejemplo |
|---|---|---|
reward *= 0.5 |
Multiplica la variable por 0,5 y guárdala | Si reward valía 1.0, ahora vale 0.5 |
reward += 10.0 |
Súmale 10 a la variable | Si valía 1.0, ahora vale 11.0 |
steps % 100 |
Resto de dividir entre 100 | 300 % 100 da 0; 250 % 100 da 50 |
Los comentarios no son un adorno: dentro de dos semanas no recordarás por qué pusiste 0.8 y no 0.7. Escríbelo.
Comparaciones y decisiones
Las comparaciones dan como resultado un booleano (True o False):
| Operador | Pregunta que hace |
|---|---|
< y > |
¿Es menor? ¿Es mayor? |
<= y >= |
¿Es menor o igual? ¿Mayor o igual? |
== |
¿Son iguales? (dos signos de igual) |
!= |
¿Son distintos? |
Para tomar decisiones se usan if (si), elif (si no, pero si...) y else (en cualquier otro caso). Piensa en las notas de un examen: si sacas 9 o más es sobresaliente; si no, pero sacas 7 o más, notable; en otro caso, "a seguir trabajando".
distance_from_center = 0.2
track_width = 0.6
if distance_from_center <= 0.1 * track_width:
reward = 1.0
elif distance_from_center <= 0.5 * track_width:
reward = 0.5
else:
reward = 1e-3
Python mira las condiciones de arriba abajo y ejecuta solo el primer bloque cuya condición se cumple. Aquí 0.1 * 0.6 es 0,06 y 0.5 * 0.6 es 0,3; como 0,2 no es menor que 0,06 pero sí que 0,3, reward vale 0.5. El orden importa: si pones la condición más amplia primero, las demás nunca se ejecutarán.
Fíjate también en los dos puntos al final de cada if, elif y else, y en la sangría (cuatro espacios) de las líneas de dentro. La sangría le dice a Python qué líneas pertenecen a cada bloque.
and, or y not
| Operador | Se cumple cuando... | Ejemplo |
|---|---|---|
and |
se cumplen las dos condiciones | speed > 1.0 and all_wheels_on_track |
or |
se cumple al menos una | is_offtrack or is_crashed |
not |
la condición NO se cumple | not all_wheels_on_track |
El ejemplo oficial "quedarse entre los dos bordes" usa and y paréntesis para agrupar:
all_wheels_on_track = True
track_width = 0.6
distance_from_center = 0.2
if all_wheels_on_track and (0.5*track_width - distance_from_center) >= 0.05:
reward = 1.0
Funciones y return
Una función es una máquina de zumos: le metes algo, hace su trabajo y te devuelve un resultado.
def doble(numero):
resultado = numero * 2
return resultado
print(doble(4)) # muestra 8
defcrea la función;doblees su nombre ynumeroes lo que recibe.- Todo lo que pertenece a la función va sangrado.
returndevuelve el resultado y termina la función en ese momento. Lo que haya debajo de unreturnejecutado ya no se ejecuta.
DeepRacer llama a tu función en cada paso de la simulación, le pasa un diccionario llamado params y espera que le devuelvas un número decimal. Este es el esqueleto oficial:
def reward_function(params) :
reward = ...
return float(reward)
float(reward) convierte el resultado en float aunque hayas escrito reward = 1. Por eso casi todos los ejemplos oficiales terminan así: es un seguro barato.
Diccionarios y listas: cómo leer params
Diccionarios
Un diccionario guarda pares de clave y valor, como la ficha de un personaje de videojuego (nombre, vida, nivel...). Se escribe entre llaves y se lee con corchetes y el nombre de la clave entre comillas:
params = {
'speed': 1.2,
'all_wheels_on_track': True,
'steering_angle': -12.0,
'closest_waypoints': [7, 8],
}
speed = params['speed'] # 1.2
on_track = params['all_wheels_on_track'] # True
La plataforma te da un params con más de veinte claves (las verás todas en el módulo de la función de recompensa). El nombre de la clave tiene que ser exacto: params['Speed'] o params['velocidad'] fallan.
La costumbre de los ejemplos oficiales es leer todo al principio de la función y guardarlo en variables con el mismo nombre. Así el resto del código queda limpio.
Listas e índices
Una lista es una fila de valores en orden, entre corchetes. Cada valor tiene una posición o índice, y se empieza a contar en cero:
colores = ['rojo', 'verde', 'azul']
print(colores[0]) # rojo
print(colores[2]) # azul
print(colores[-1]) # azul: el -1 es el último
En DeepRacer hay dos listas que se usan juntas:
waypoints: los puntos de referencia de la línea central de la pista, cada uno con sus coordenadas(x, y).closest_waypoints: dos índices, el del waypoint que queda detrás del coche y el del que queda delante.
waypoints = [(0.0, 0.0), (1.0, 0.0), (2.0, 0.5), (3.0, 1.5)]
closest_waypoints = [1, 2]
next_point = waypoints[closest_waypoints[1]]
print(next_point) # (2.0, 0.5)
print(next_point[0]) # 2.0, la x
print(next_point[1]) # 0.5, la y
waypoints[closest_waypoints[1]] se lee de dentro hacia fuera, como abrir una muñeca rusa: primero closest_waypoints[1] vale 2, y después waypoints[2] es (2.0, 0.5).
Matemáticas mínimas: abs, atan2 y degrees
abs devuelve el número sin signo. En DeepRacer el ángulo de dirección es negativo al girar a la derecha y positivo a la izquierda. Si solo te importa cuánto gira, y no hacia dónde, usas abs:
steering_angle = -22.0
abs_steering = abs(steering_angle) # 22.0
math.atan2 y math.degrees sirven para calcular hacia dónde apunta la pista. Imagina una flecha que va del waypoint de atrás al de delante: math.atan2(dy, dx) te da el ángulo de esa flecha en radianes, y math.degrees lo pasa a grados para poder compararlo con heading, la orientación del coche. Estas líneas salen del ejemplo oficial de waypoints:
import math
prev_point = (1.0, 0.0)
next_point = (2.0, 1.0)
track_direction = math.atan2(next_point[1] - prev_point[1], next_point[0] - prev_point[0])
track_direction = math.degrees(track_direction)
print(track_direction) # 45.0
import math va fuera de la función, al principio del archivo. Las librerías permitidas en la plataforma son math, random, numpy, scipy y shapely.
Errores típicos y cómo probar tu función
Este código tiene cinco errores. Intenta encontrarlos antes de leer la lista:
def reward_function(params)
speed = params['speed']
if speed = 1.0:
reward = 1.0
else:
reward = 0.5
on_track = true
- Faltan los dos puntos tras
def reward_function(params). if speed = 1.0usa un igual; para comparar hacen falta dos (==).- El
elsetiene una sangría distinta de la delif: deben estar alineados. trueva con mayúscula:True.- No hay
return: la función devolveríaNone, que no es un número.
Otros clásicos: mezclar tabuladores y espacios, olvidar cerrar un paréntesis o un corchete, escribir mal una clave de params y poner el import dentro de la función.
Probar en un intérprete de Python online
Cualquier intérprete de Python online (o el que tengas en tu ordenador) sirve para comprobar la lógica. Pega tu función, inventa un params de prueba y muestra el resultado:
def reward_function(params):
if not params['all_wheels_on_track']:
return float(1e-3)
return float(1.0)
prueba = {'all_wheels_on_track': False}
print(reward_function(prueba)) # 0.001
Cambia los valores de prueba y comprueba que la función responde como esperas en cada caso.
Probar en la plataforma con Validate
En el último paso del asistente de creación de modelos, Customize reward function (personalizar la función de recompensa), hay un editor de Python con tres botones: Reward function examples (funciones de ejemplo), Validate (validar) y Reset (restablecer). Al pulsar Validate, la plataforma revisa tu código en un contenedor aislado y sin red. Si algo va mal, te indica un SYNTAX_ERROR (error de escritura), un IMPORT_ERROR (librería no permitida) o un TEST_FAILURE (la función falla al ejecutarse).
Actividad práctica
Necesitas unos 25 minutos, un intérprete de Python online y tu cuenta de la plataforma DeepRacer on AWS.
- Resuelve los ejercicios de abajo en papel o en el intérprete. Comprueba tus respuestas con las soluciones.
- Copia en el intérprete tu solución del ejercicio 5 y pruébala con al menos tres
paramsde prueba distintos. - Entra en la plataforma y ve a Your models (tus modelos) y después a Create model (crear modelo). Avanza por el asistente hasta el paso Customize reward function.
- Pega tu función del ejercicio 5 en el editor y pulsa Validate.
- Borra a propósito los dos puntos de una línea y vuelve a pulsar Validate. Lee el mensaje de error.
- Pulsa Reset para recuperar la función por defecto. Si no vas a entrenar ahora, sal del asistente sin pulsar Train your model.
Ejercicios
- ¿De qué tipo es cada valor:
3,3.0,True,1e-3? - Con
speed = 1.4, ¿qué valor tendrárewardtras este código?
speed = 1.4
if speed < 1.0:
reward = 0.2
elif speed < 1.5:
reward = 0.6
else:
reward = 1.0
- ¿Qué devuelve
abs(params['steering_angle'])siparams['steering_angle']vale-18.5? - Con
waypoints = [(0, 0), (2, 0), (4, 1), (5, 3)]yclosest_waypoints = [2, 3], ¿qué valewaypoints[closest_waypoints[0]]? ¿Ywaypoints[closest_waypoints[1]][1]? - Escribe una
reward_functioncompleta que devuelva1e-3si el coche no tiene las cuatro ruedas en la pista,0.5si gira más de 20 grados (hacia cualquier lado) y1.0en el resto de casos.
Soluciones
3es int,3.0es float,Truees booleano y1e-3es float (vale 0,001).0.6. La primera condición (1.4 < 1.0) es falsa; la segunda (1.4 < 1.5) es verdadera, así que se ejecuta ese bloque y elelseya no se mira.18.5.waypoints[2]es(4, 1).waypoints[3]es(5, 3)y su elemento[1](la y) es3.- Una solución posible:
def reward_function(params):
# Leer los parámetros que necesitamos
all_wheels_on_track = params['all_wheels_on_track']
abs_steering = abs(params['steering_angle'])
if not all_wheels_on_track:
reward = 1e-3
elif abs_steering > 20:
reward = 0.5
else:
reward = 1.0
return float(reward)
Resumen
- Una variable guarda un valor; los float llevan punto decimal y los booleanos son
TrueoFalse. if,elifyelsese evalúan de arriba abajo, con dos puntos al final y sangría dentro;and,orynotcombinan condiciones.- Tu función se llama
reward_function, recibeparamsy termina conreturn float(reward). params['clave']lee un dato del diccionario y las listas empiezan en el índice 0:waypoints[closest_waypoints[1]]es el siguiente punto de la pista.- Antes de entrenar, prueba la lógica en un intérprete y pulsa Validate en la plataforma.
Test final
Comprueba lo que has aprendido. Necesitas 4 aciertos de 5 para superar el módulo.