Copyright 2021 Los autores de TF-Agents.
![]() | ![]() | ![]() | ![]() |
Introducción
Este ejemplo muestra cómo entrenar a un agente DQN (Deep Q Networks) en el entorno Cartpole usando la biblioteca TF-Agents.
Lo guiará a través de todos los componentes de una canalización de aprendizaje reforzado (RL) para capacitación, evaluación y recopilación de datos.
Para ejecutar este código en vivo, haga clic en el enlace 'Ejecutar en Google Colab' arriba.
Configuración
Si no ha instalado las siguientes dependencias, ejecute:
sudo apt-get install -y xvfb ffmpeg
pip install -q 'imageio==2.4.0'
pip install -q pyvirtualdisplay
pip install -q tf-agents
from __future__ import absolute_import, division, print_function
import base64
import imageio
import IPython
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import PIL.Image
import pyvirtualdisplay
import tensorflow as tf
from tf_agents.agents.dqn import dqn_agent
from tf_agents.drivers import dynamic_step_driver
from tf_agents.environments import suite_gym
from tf_agents.environments import tf_py_environment
from tf_agents.eval import metric_utils
from tf_agents.metrics import tf_metrics
from tf_agents.networks import sequential
from tf_agents.policies import random_tf_policy
from tf_agents.replay_buffers import tf_uniform_replay_buffer
from tf_agents.trajectories import trajectory
from tf_agents.specs import tensor_spec
from tf_agents.utils import common
# Set up a virtual display for rendering OpenAI gym environments.
display = pyvirtualdisplay.Display(visible=0, size=(1400, 900)).start()
tf.version.VERSION
'2.4.1'
Hiperparámetros
num_iterations = 20000 # @param {type:"integer"}
initial_collect_steps = 100 # @param {type:"integer"}
collect_steps_per_iteration = 1 # @param {type:"integer"}
replay_buffer_max_length = 100000 # @param {type:"integer"}
batch_size = 64 # @param {type:"integer"}
learning_rate = 1e-3 # @param {type:"number"}
log_interval = 200 # @param {type:"integer"}
num_eval_episodes = 10 # @param {type:"integer"}
eval_interval = 1000 # @param {type:"integer"}
Ambiente
En el aprendizaje por refuerzo (RL), un entorno representa la tarea o problema a resolver. Se pueden crear entornos estándar en TF-Agents utilizando tf_agents.environments
suites. TF-Agents tiene suites para cargar entornos de fuentes como OpenAI Gym, Atari y DM Control.
Cargue el entorno CartPole desde la suite OpenAI Gym.
env_name = 'CartPole-v0'
env = suite_gym.load(env_name)
Puede renderizar este entorno para ver cómo se ve. Un poste de oscilación libre está sujeto a un carro. El objetivo es mover el carro hacia la derecha o hacia la izquierda para mantener el poste apuntando hacia arriba.
env.reset()
PIL.Image.fromarray(env.render())
El método environment.step
realiza una action
en el entorno y devuelve una tupla TimeStep
contiene la siguiente observación del entorno y la recompensa por la acción.
El método time_step_spec()
devuelve la especificación de la tupla TimeStep
. Su atributo de observation
muestra la forma de las observaciones, los tipos de datos y los rangos de valores permitidos. El atributo de reward
muestra los mismos detalles para la recompensa.
print('Observation Spec:')
print(env.time_step_spec().observation)
Observation Spec: BoundedArraySpec(shape=(4,), dtype=dtype('float32'), name='observation', minimum=[-4.8000002e+00 -3.4028235e+38 -4.1887903e-01 -3.4028235e+38], maximum=[4.8000002e+00 3.4028235e+38 4.1887903e-01 3.4028235e+38])
print('Reward Spec:')
print(env.time_step_spec().reward)
Reward Spec: ArraySpec(shape=(), dtype=dtype('float32'), name='reward')
El método action_spec()
devuelve la forma, los tipos de datos y los valores permitidos de las acciones válidas.
print('Action Spec:')
print(env.action_spec())
Action Spec: BoundedArraySpec(shape=(), dtype=dtype('int64'), name='action', minimum=0, maximum=1)
En el entorno de Cartpole:
-
observation
es una matriz de 4 flotadores:- la posición y velocidad del carro
- la posición angular y la velocidad del polo
-
reward
es un valor flotante escalar -
action
es un entero escalar con solo dos valores posibles:-
0
- "mover a la izquierda" -
1
- "mover a la derecha"
-
time_step = env.reset()
print('Time step:')
print(time_step)
action = np.array(1, dtype=np.int32)
next_time_step = env.step(action)
print('Next time step:')
print(next_time_step)
Time step: TimeStep(step_type=array(0, dtype=int32), reward=array(0., dtype=float32), discount=array(1., dtype=float32), observation=array([-0.03850375, -0.01932749, 0.00336228, -0.04097027], dtype=float32)) Next time step: TimeStep(step_type=array(1, dtype=int32), reward=array(1., dtype=float32), discount=array(1., dtype=float32), observation=array([-0.03889029, 0.17574608, 0.00254287, -0.3325905 ], dtype=float32))
Por lo general, se crean instancias de dos entornos: uno para entrenamiento y otro para evaluación.
train_py_env = suite_gym.load(env_name)
eval_py_env = suite_gym.load(env_name)
El entorno Cartpole, como la mayoría de los entornos, está escrito en Python puro. Esto se convierte a TensorFlow mediante el contenedor TFPyEnvironment
.
La API del entorno original utiliza matrices Numpy. TFPyEnvironment
convierte en Tensors
para que sean compatibles con los agentes y las políticas de Tensorflow.
train_env = tf_py_environment.TFPyEnvironment(train_py_env)
eval_env = tf_py_environment.TFPyEnvironment(eval_py_env)
Agente
El algoritmo utilizado para resolver un problema de RL está representado por un Agent
. TF-Agents proporciona implementaciones estándar de una variedad de Agents
, que incluyen:
El agente DQN se puede utilizar en cualquier entorno que tenga un espacio de acción discreto.
En el corazón de un agente DQN se encuentra un QNetwork
, un modelo de red neuronal que puede aprender a predecir QValues
(retornos esperados) para todas las acciones, dada una observación del entorno.
Usaremos tf_agents.networks.
para crear una QNetwork
. La red estará formada por una secuencia de tf.keras.layers.Dense
Capas tf.keras.layers.Dense
, donde la capa final tendrá 1 salida para cada posible acción.
fc_layer_params = (100, 50)
action_tensor_spec = tensor_spec.from_spec(env.action_spec())
num_actions = action_tensor_spec.maximum - action_tensor_spec.minimum + 1
# Define a helper function to create Dense layers configured with the right
# activation and kernel initializer.
def dense_layer(num_units):
return tf.keras.layers.Dense(
num_units,
activation=tf.keras.activations.relu,
kernel_initializer=tf.keras.initializers.VarianceScaling(
scale=2.0, mode='fan_in', distribution='truncated_normal'))
# QNetwork consists of a sequence of Dense layers followed by a dense layer
# with `num_actions` units to generate one q_value per available action as
# it's output.
dense_layers = [dense_layer(num_units) for num_units in fc_layer_params]
q_values_layer = tf.keras.layers.Dense(
num_actions,
activation=None,
kernel_initializer=tf.keras.initializers.RandomUniform(
minval=-0.03, maxval=0.03),
bias_initializer=tf.keras.initializers.Constant(-0.2))
q_net = sequential.Sequential(dense_layers + [q_values_layer])
Ahora use tf_agents.agents.dqn.dqn_agent
para instanciar un DqnAgent
. Además de time_step_spec
, action_spec
y QNetwork, el constructor del agente también requiere un optimizador (en este caso, AdamOptimizer
), una función de pérdida y un contador de pasos de números enteros.
optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate)
train_step_counter = tf.Variable(0)
agent = dqn_agent.DqnAgent(
train_env.time_step_spec(),
train_env.action_spec(),
q_network=q_net,
optimizer=optimizer,
td_errors_loss_fn=common.element_wise_squared_loss,
train_step_counter=train_step_counter)
agent.initialize()
Políticas
Una política define la forma en que actúa un agente en un entorno. Normalmente, el objetivo del aprendizaje por refuerzo es entrenar el modelo subyacente hasta que la política produzca el resultado deseado.
En este tutorial:
- El resultado deseado es mantener el poste en equilibrio sobre el carro.
- La política devuelve una acción (izquierda o derecha) para cada observación
time_step
.
Los agentes contienen dos políticas:
-
agent.policy
: la política principal que se utiliza para la evaluación y la implementación. -
agent.collect_policy
: una segunda política que se utiliza para la recopilación de datos.
eval_policy = agent.policy
collect_policy = agent.collect_policy
Las políticas se pueden crear independientemente de los agentes. Por ejemplo, use tf_agents.policies.random_tf_policy
para crear una política que seleccionará aleatoriamente una acción para cada time_step
.
random_policy = random_tf_policy.RandomTFPolicy(train_env.time_step_spec(),
train_env.action_spec())
Para obtener una acción de una política, llame al policy.action(time_step)
. time_step
contiene la observación del entorno. Este método devuelve un PolicyStep
, que es una tupla con nombre con tres componentes:
-
action
: la acción que se va a realizar (en este caso,0
o1
) -
state
: se utiliza para políticas con estado (es decir, basadas en RNN) -
info
: datos auxiliares, como el registro de probabilidades de acciones
example_environment = tf_py_environment.TFPyEnvironment(
suite_gym.load('CartPole-v0'))
time_step = example_environment.reset()
random_policy.action(time_step)
PolicyStep(action=<tf.Tensor: shape=(1,), dtype=int64, numpy=array([0])>, state=(), info=())
Métricas y evaluación
La métrica más común utilizada para evaluar una póliza es el rendimiento promedio. El retorno es la suma de las recompensas obtenidas al ejecutar una política en un entorno para un episodio. Se ejecutan varios episodios, lo que genera un rendimiento medio.
La siguiente función calcula el rendimiento promedio de una política, dados la política, el entorno y una serie de episodios.
def compute_avg_return(environment, policy, num_episodes=10):
total_return = 0.0
for _ in range(num_episodes):
time_step = environment.reset()
episode_return = 0.0
while not time_step.is_last():
action_step = policy.action(time_step)
time_step = environment.step(action_step.action)
episode_return += time_step.reward
total_return += episode_return
avg_return = total_return / num_episodes
return avg_return.numpy()[0]
# See also the metrics module for standard implementations of different metrics.
# https://github.com/tensorflow/agents/tree/master/tf_agents/metrics
La ejecución de este cálculo en la random_policy
muestra un rendimiento de referencia en el entorno.
compute_avg_return(eval_env, random_policy, num_eval_episodes)
20.7
Búfer de reproducción
El búfer de reproducción realiza un seguimiento de los datos recopilados del entorno. Este tutorial usa tf_agents.replay_buffers.tf_uniform_replay_buffer.TFUniformReplayBuffer
, ya que es el más común.
El constructor requiere las especificaciones de los datos que recopilará. Está disponible en el agente mediante el método collect_data_spec
. También se requieren el tamaño del lote y la longitud máxima del búfer.
replay_buffer = tf_uniform_replay_buffer.TFUniformReplayBuffer(
data_spec=agent.collect_data_spec,
batch_size=train_env.batch_size,
max_length=replay_buffer_max_length)
Para la mayoría de los agentes, collect_data_spec
es una tupla con nombre llamada Trajectory
, que contiene las especificaciones de observaciones, acciones, recompensas y otros elementos.
agent.collect_data_spec
Trajectory(step_type=TensorSpec(shape=(), dtype=tf.int32, name='step_type'), observation=BoundedTensorSpec(shape=(4,), dtype=tf.float32, name='observation', minimum=array([-4.8000002e+00, -3.4028235e+38, -4.1887903e-01, -3.4028235e+38], dtype=float32), maximum=array([4.8000002e+00, 3.4028235e+38, 4.1887903e-01, 3.4028235e+38], dtype=float32)), action=BoundedTensorSpec(shape=(), dtype=tf.int64, name='action', minimum=array(0), maximum=array(1)), policy_info=(), next_step_type=TensorSpec(shape=(), dtype=tf.int32, name='step_type'), reward=TensorSpec(shape=(), dtype=tf.float32, name='reward'), discount=BoundedTensorSpec(shape=(), dtype=tf.float32, name='discount', minimum=array(0., dtype=float32), maximum=array(1., dtype=float32)))
agent.collect_data_spec._fields
('step_type', 'observation', 'action', 'policy_info', 'next_step_type', 'reward', 'discount')
Recopilación de datos
Ahora ejecute la política aleatoria en el entorno durante algunos pasos, registrando los datos en el búfer de reproducción.
def collect_step(environment, policy, buffer):
time_step = environment.current_time_step()
action_step = policy.action(time_step)
next_time_step = environment.step(action_step.action)
traj = trajectory.from_transition(time_step, action_step, next_time_step)
# Add trajectory to the replay buffer
buffer.add_batch(traj)
def collect_data(env, policy, buffer, steps):
for _ in range(steps):
collect_step(env, policy, buffer)
collect_data(train_env, random_policy, replay_buffer, initial_collect_steps)
# This loop is so common in RL, that we provide standard implementations.
# For more details see the drivers module.
# https://www.tensorflow.org/agents/api_docs/python/tf_agents/drivers
El búfer de reproducción ahora es una colección de trayectorias.
# For the curious:
# Uncomment to peel one of these off and inspect it.
# iter(replay_buffer.as_dataset()).next()
El agente necesita acceso al búfer de reproducción. Esto se proporciona mediante la creación de una canalizacióntf.data.Dataset
iterable que proporcionará datos al agente.
Cada fila del búfer de reproducción solo almacena un único paso de observación. Pero como el agente DQN necesita tanto la observación actual como la siguiente para calcular la pérdida, la canalización del conjunto de datos muestreará dos filas adyacentes para cada elemento del lote ( num_steps=2
).
Este conjunto de datos también se optimiza ejecutando llamadas paralelas y obteniendo datos previamente.
# Dataset generates trajectories with shape [Bx2x...]
dataset = replay_buffer.as_dataset(
num_parallel_calls=3,
sample_batch_size=batch_size,
num_steps=2).prefetch(3)
dataset
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/autograph/operators/control_flow.py:1218: ReplayBuffer.get_next (from tf_agents.replay_buffers.replay_buffer) is deprecated and will be removed in a future version. Instructions for updating: Use `as_dataset(..., single_deterministic_pass=False) instead. <PrefetchDataset shapes: (Trajectory(step_type=(64, 2), observation=(64, 2, 4), action=(64, 2), policy_info=(), next_step_type=(64, 2), reward=(64, 2), discount=(64, 2)), BufferInfo(ids=(64, 2), probabilities=(64,))), types: (Trajectory(step_type=tf.int32, observation=tf.float32, action=tf.int64, policy_info=(), next_step_type=tf.int32, reward=tf.float32, discount=tf.float32), BufferInfo(ids=tf.int64, probabilities=tf.float32))>
iterator = iter(dataset)
print(iterator)
<tensorflow.python.data.ops.iterator_ops.OwnedIterator object at 0x7fd45c8c1d30>
# For the curious:
# Uncomment to see what the dataset iterator is feeding to the agent.
# Compare this representation of replay data
# to the collection of individual trajectories shown earlier.
# iterator.next()
Entrenando al agente
Deben suceder dos cosas durante el ciclo de entrenamiento:
- recopilar datos del medio ambiente
- usar esos datos para entrenar las redes neuronales del agente
Este ejemplo también evalúa periódicamente la política e imprime la puntuación actual.
Lo siguiente tardará ~ 5 minutos en ejecutarse.
try:
%%time
except:
pass
# (Optional) Optimize by wrapping some of the code in a graph using TF function.
agent.train = common.function(agent.train)
# Reset the train step
agent.train_step_counter.assign(0)
# Evaluate the agent's policy once before training.
avg_return = compute_avg_return(eval_env, agent.policy, num_eval_episodes)
returns = [avg_return]
for _ in range(num_iterations):
# Collect a few steps using collect_policy and save to the replay buffer.
collect_data(train_env, agent.collect_policy, replay_buffer, collect_steps_per_iteration)
# Sample a batch of data from the buffer and update the agent's network.
experience, unused_info = next(iterator)
train_loss = agent.train(experience).loss
step = agent.train_step_counter.numpy()
if step % log_interval == 0:
print('step = {0}: loss = {1}'.format(step, train_loss))
if step % eval_interval == 0:
avg_return = compute_avg_return(eval_env, agent.policy, num_eval_episodes)
print('step = {0}: Average Return = {1}'.format(step, avg_return))
returns.append(avg_return)
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/util/dispatch.py:201: calling foldr_v2 (from tensorflow.python.ops.functional_ops) with back_prop=False is deprecated and will be removed in a future version. Instructions for updating: back_prop=False is deprecated. Consider using tf.stop_gradient instead. Instead of: results = tf.foldr(fn, elems, back_prop=False) Use: results = tf.nest.map_structure(tf.stop_gradient, tf.foldr(fn, elems)) step = 200: loss = 7.1376953125 step = 400: loss = 10.289593696594238 step = 600: loss = 19.545475006103516 step = 800: loss = 36.43363952636719 step = 1000: loss = 5.419467449188232 step = 1000: Average Return = 54.20000076293945 step = 1200: loss = 34.57895278930664 step = 1400: loss = 46.99689483642578 step = 1600: loss = 40.32734298706055 step = 1800: loss = 47.736488342285156 step = 2000: loss = 49.739967346191406 step = 2000: Average Return = 190.0 step = 2200: loss = 844.77099609375 step = 2400: loss = 4119.0439453125 step = 2600: loss = 255.4365234375 step = 2800: loss = 143.8880157470703 step = 3000: loss = 225.29849243164062 step = 3000: Average Return = 200.0 step = 3200: loss = 599.4447021484375 step = 3400: loss = 1019.076171875 step = 3600: loss = 1293.42919921875 step = 3800: loss = 2320.72216796875 step = 4000: loss = 41184.2109375 step = 4000: Average Return = 200.0 step = 4200: loss = 4963.77880859375 step = 4400: loss = 5559.41552734375 step = 4600: loss = 5049.33203125 step = 4800: loss = 78073.1328125 step = 5000: loss = 36796.953125 step = 5000: Average Return = 200.0 step = 5200: loss = 9767.94140625 step = 5400: loss = 22039.0625 step = 5600: loss = 22485.873046875 step = 5800: loss = 388653.09375 step = 6000: loss = 216484.203125 step = 6000: Average Return = 200.0 step = 6200: loss = 25190.736328125 step = 6400: loss = 34655.4296875 step = 6600: loss = 46708.58984375 step = 6800: loss = 53557.33984375 step = 7000: loss = 32926.9296875 step = 7000: Average Return = 200.0 step = 7200: loss = 45613.109375 step = 7400: loss = 37991.77734375 step = 7600: loss = 69465.1796875 step = 7800: loss = 82083.4140625 step = 8000: loss = 45056.90234375 step = 8000: Average Return = 200.0 step = 8200: loss = 82084.65625 step = 8400: loss = 123847.4375 step = 8600: loss = 78031.5 step = 8800: loss = 67286.640625 step = 9000: loss = 227665.625 step = 9000: Average Return = 200.0 step = 9200: loss = 189473.8125 step = 9400: loss = 175909.5625 step = 9600: loss = 219817.296875 step = 9800: loss = 402358.21875 step = 10000: loss = 295174.6875 step = 10000: Average Return = 200.0 step = 10200: loss = 297499.125 step = 10400: loss = 328237.8125 step = 10600: loss = 6439973.5 step = 10800: loss = 196471.09375 step = 11000: loss = 412917.0 step = 11000: Average Return = 200.0 step = 11200: loss = 224290.40625 step = 11400: loss = 215624.25 step = 11600: loss = 428307.0625 step = 11800: loss = 979577.5625 step = 12000: loss = 192212.125 step = 12000: Average Return = 200.0 step = 12200: loss = 395367.75 step = 12400: loss = 24781564.0 step = 12600: loss = 10260239.0 step = 12800: loss = 540727.875 step = 13000: loss = 638093.25 step = 13000: Average Return = 200.0 step = 13200: loss = 774659.4375 step = 13400: loss = 725100.875 step = 13600: loss = 759670.625 step = 13800: loss = 650109.0625 step = 14000: loss = 714944.5 step = 14000: Average Return = 200.0 step = 14200: loss = 765532.1875 step = 14400: loss = 559825.3125 step = 14600: loss = 1272693.375 step = 14800: loss = 1612590.75 step = 15000: loss = 1986240.25 step = 15000: Average Return = 200.0 step = 15200: loss = 1077601.125 step = 15400: loss = 1231381.125 step = 15600: loss = 2455884.5 step = 15800: loss = 23788086.0 step = 16000: loss = 1422684.0 step = 16000: Average Return = 200.0 step = 16200: loss = 2394509.0 step = 16400: loss = 4006896.0 step = 16600: loss = 1646453.625 step = 16800: loss = 1340689.125 step = 17000: loss = 26381168.0 step = 17000: Average Return = 200.0 step = 17200: loss = 2301365.75 step = 17400: loss = 589999.5 step = 17600: loss = 1951961.5 step = 17800: loss = 717706.3125 step = 18000: loss = 1537313.875 step = 18000: Average Return = 200.0 step = 18200: loss = 2864821.0 step = 18400: loss = 1825217.5 step = 18600: loss = 2375406.75 step = 18800: loss = 4396603.0 step = 19000: loss = 2847116.0 step = 19000: Average Return = 200.0 step = 19200: loss = 2379508.5 step = 19400: loss = 785688.625 step = 19600: loss = 2396494.0 step = 19800: loss = 3612146.75 step = 20000: loss = 3842961.75 step = 20000: Average Return = 200.0
Visualización
Parcelas
Utilice matplotlib.pyplot
para trazar cómo mejoró la política durante el entrenamiento.
Una iteración de Cartpole-v0
consta de 200 pasos de tiempo. El entorno otorga una recompensa de +1
por cada paso que la pole se mantiene, por lo que el rendimiento máximo para un episodio es 200. Los gráficos muestran el rendimiento aumentando hacia ese máximo cada vez que se evalúa durante el entrenamiento. (Puede ser un poco inestable y no aumentar monótonamente cada vez).
iterations = range(0, num_iterations + 1, eval_interval)
plt.plot(iterations, returns)
plt.ylabel('Average Return')
plt.xlabel('Iterations')
plt.ylim(top=250)
(1.3400002002716054, 250.0)
Videos
Los gráficos son agradables. Pero lo más emocionante es ver a un agente realizando una tarea en un entorno.
Primero, cree una función para incrustar videos en el cuaderno.
def embed_mp4(filename):
"""Embeds an mp4 file in the notebook."""
video = open(filename,'rb').read()
b64 = base64.b64encode(video)
tag = '''
<video width="640" height="480" controls>
<source src="data:video/mp4;base64,{0}" type="video/mp4">
Your browser does not support the video tag.
</video>'''.format(b64.decode())
return IPython.display.HTML(tag)
Ahora repite algunos episodios del juego Cartpole con el agente. El entorno de Python subyacente (el que está "dentro" del contenedor de entorno de TensorFlow) proporciona un método render()
, que genera una imagen del estado del entorno. Estos se pueden recopilar en un video.
def create_policy_eval_video(policy, filename, num_episodes=5, fps=30):
filename = filename + ".mp4"
with imageio.get_writer(filename, fps=fps) as video:
for _ in range(num_episodes):
time_step = eval_env.reset()
video.append_data(eval_py_env.render())
while not time_step.is_last():
action_step = policy.action(time_step)
time_step = eval_env.step(action_step.action)
video.append_data(eval_py_env.render())
return embed_mp4(filename)
create_policy_eval_video(agent.policy, "trained-agent")
WARNING:root:IMAGEIO FFMPEG_WRITER WARNING: input image is not divisible by macro_block_size=16, resizing from (400, 600) to (400, 608) to ensure video compatibility with most codecs and players. To prevent resizing, make your input image divisible by the macro_block_size or set the macro_block_size to None (risking incompatibility). You may also see a FFMPEG warning concerning speedloss due to data not being aligned.
Para divertirse, compare al agente entrenado (arriba) con un agente que se mueve al azar. (No funciona tan bien).
create_policy_eval_video(random_policy, "random-agent")
WARNING:root:IMAGEIO FFMPEG_WRITER WARNING: input image is not divisible by macro_block_size=16, resizing from (400, 600) to (400, 608) to ensure video compatibility with most codecs and players. To prevent resizing, make your input image divisible by the macro_block_size or set the macro_block_size to None (risking incompatibility). You may also see a FFMPEG warning concerning speedloss due to data not being aligned.