¿Cómo organizar el código con numbers aleatorios en los sistemas de control de versiones para el análisis?

esto es principalmente una pregunta sobre la organización de files en VCS como git, pero para dar una buena idea de mi problema, aquí una rápida introducción al tema:

Descripción del proyecto

Estoy trabajando en un proyecto, donde los models probabilísticos similares a networkinges neuronales se implementan y testingn para diferentes sets de parameters. Actualmente estamos implementando en Python, aunque el problema también podría ser relevante para diferentes lenguajes de progtwigción. El resultado suele ser mediciones de error o charts o algo similar. Ahora, en este momento, nuestro proyecto es el siguiente:

  • varias personas están trabajando en la base de código del proyecto y están implementando nuevas características

  • algunas otras personas ya están tratando de explorar el comportamiento del model para diferentes sets de parameters, es decir, averiguar para qué ranges de parameters el model muestra un comportamiento cualitativamente diferente

En este momento usamos git con GitHub como VCS con una twig principal para la versión estable actual y una twig para cada miembro de nuestro equipo para el desarrollo activo. Intercambiamos código al fusionarnos entre sucursales y fusionarnos para dominar lo que parece una nueva característica estable.

Un gran problema en general es que se trata de un proyecto de investigación sin un esquema claro del proyecto. A veces estamos solucionando algunos errores o implementando algo planificado específicamente con twigs de características. Pero a veces no está claro, cuál será exactamente la siguiente característica, o si incluso es posible implementar lo que tenemos en mente. Algunos de nosotros básicamente exploramos el comportamiento de nuestro model de una manera más o less estructurada. Lo sé. Pero así es como es.

Controlar el comportamiento probabilístico

Nuestro model es probabilístico en muchos niveles. Varias partes se inicializan con numbers aleatorios y también se usan numbers aleatorios mientras se ejecuta la simulación del model.

Por supuesto, la mejor manera de explorar un model probabilístico es dejarlo correr muchas veces y analizar estadísticamente los resultados. Ahora, para fines de demostración o para explorar más específicamente un comportamiento específico, desea que los casos sean reproducibles. Actualmente hacemos esto, al establecer las semillas del generador de numbers aleatorios al comienzo, como en numpy para python con

import numpy as np np.random.seed(42) a = np.random.rand() # -> will always be 0.3745401188473625 b = np.random.rand() # -> will always be 0.9507143064099162 

Problemas de control de versiones

Identificamos dos problemas con nuestra configuration actual:

1) ¿Cómo almacenar las instantáneas de un comportamiento específico para una exploración posterior?

Para labelr las instantáneas de forma adecuada, pensamos en usar twigs y tags para experimentos específicos y encontramos sets de parameters, como este:

 * master | *--------------- |\ \ * * experiment1 * experiment2 | | | . * tag setting1 * tag setting1 . | | . * tag setting2 * tag setting2 

El problema aquí es que, por lo que sabemos, los commits con tags no están destinados a ser cambiados más tarde. Como podríamos trabajar en estas configuraciones más adelante, tendríamos que ramificar nuevamente desde la label específica.

Otra forma de hacerlo sería usar solo twigs, una para cada configuration encontrada, de modo que cada cabeza de bifurcación corresponda a un estado de trabajo del sistema. Pero esto llevaría a una gran cantidad de sucursales para todas estas cosas que identificamos.

Entonces, ¿cómo organizarías una estructura como esta? Especialmente con el siguiente problema en mente:

2) Cómo fusionar cambios en instantáneas almacenadas sin cambiar el comportamiento probabilístico

Supongamos que uno de nuestros desarrolladores encuentra un error en la implementación que teníamos hasta el momento, o implementa una característica muy útil y lo arregla en la twig principal. Ahora podría ser muy beneficioso usar estos cambios para un análisis posterior de uno de los comportamientos identificados del model. El problema es que si los cambios usan numbers aleatorios, es probable que el comportamiento del model sea completamente diferente después de la fusión.

 import numpy as np np.random.seed(42) a = np.random.rand() # -> will always be 0.3745401188473625 # fixing some stuff here c = np.random.rand() # -> will be 0.9507143064099162 as was previously 'b' b = np.random.rand() # -> will now be 0.7319939418114051 and not anymore 0.9507143064099162 # ... # code using 'b' will behave differently 

Este es realmente un gran problema, porque significa que:

  • o bien no podemos (o solo cuando no cambiamos los numbers aleatorios) usar nuevas funciones o aplicar correcciones de errores para el análisis de sets de parameters ya identificados y condiciones aleatorias

  • o tenemos que identificar estas configuraciones una y otra vez después de cada cambio que usa numbers aleatorios

Por supuesto, el problema sigue siendo fácil para el código que se muestra aquí, que implica solo unas pocas llamadas random. Pero en los models que tenemos, los numbers aleatorios se generarán muchas veces y, a menudo, el número de iteraciones está nuevamente influenciado por la salida de cálculos de otras partes que involucran numbers aleatorios.

¿Tiene alguna recomendación sobre este tema?

Así es como manejamos este tipo de desarrollo y flujo de trabajo experimental.

Código y experimentos separados

Realmente, esto es lo más importante a tener en count. Esto es como simulaciones informáticas mal diseñadas, donde el model simulado y el algorithm de simulación están completamente entrelazados. Tener una separación de model y código es muy importante por muchas razones.

Del mismo modo, separe su proyecto en:

  • un repository para el desarrollo del código real de los algorithms centrales

  • un repository para cada model

    Por supuesto, el model puede contener un código específico del model, por ejemplo, cómo configurar las neuronas en una ANN y el código para analizar los datos de entrenamiento, etc.

  • un repository para cada experimento

    Un experimento es cada análisis que realiza con una pregunta científica clara, como "¿Cómo influye la optimization x en la precisión de la sorting?". Tener una pregunta de investigación tan clara lo ayuda a estructurar su investigación, y luego a publicar su investigación.

    Cada experimento puede, por supuesto, implicar múltiples models y sets de datos múltiples.

( Nota : estoy en contra de almacenar sets de datos en el software de control de versiones. Sin duda, se necesita una copy de security, pero los sets de datos siempre deben almacenarse sin modificaciones, y los datos preprocesados ​​deben almacenarse en diferentes carpetas y nunca sobrescribir los datos originales).

Use submodules para asociación y reproducibilidad

Si usa git , puede usar submodules para su repository experimental. Usamos una estructura de directorys similar a la siguiente para experimentos:

 X-<date>-<title>/ # repository for experiment |___ models/ | |___ M1/ # submodule for M1's repository | |___ M2/ # submodule for M2's repository | | | ... |___ code/ # submodule for your core algorithms |___ data/ # a copy or link to your data sets |___ experiment1.sh # script to run your experiment |___ experiment2.sh # possibly some more sub-experiments 

Los submodules le permiten consultar un compromiso específico (no una twig o label, literalmente una identificación de confirmación estable) para sus models y código. De esta manera, asegura la reproducibilidad de sus experimentos.

Los submodules son en realidad parte de la confirmación de su experimento. Es decir, cada vez que realiza cambios en su experimento (por ejemplo, experiment1.sh ), crea una nueva confirmación. Cada vez que actualiza sus models o su código, actualiza el submodule específico y crea un nuevo compromiso. ¿Desea volver a una versión anterior? Simplemente ejecute git checkout && git submodule update . Todos sus submodules se revierten automáticamente a la confirmación en ese estado.

Esto responde su primera pregunta.

Hacer experimentos reproducibles

Lo primero y más importante, también como consecuencia de la estructura experimental anterior, nunca establecer semillas aleatorias directamente en nuestro código. En cambio, páselos como arguments de línea de command. Por ejemplo, en su experiment1.sh , llame al code/my-tool --seed=42 models/M1/model.ann < input.dat . Como experiment1.sh está bajo control de versión, automáticamente garantiza la reproducibilidad. Esto también cumple con el principio de separar el código y los datos.

Con respecto a su segunda pregunta, es imposible introducir nuevas características ( cambiando así el comportamiento ) y simultáneamente mantener el comportamiento anterior. Tienes que decidir: ¿se ve afectado tu experimento por la corrección de la function / error?

Recuerde que los experimentos tienen una pregunta de investigación específica. Si introdujo una nueva característica que desea probar, puede valer la pena un nuevo experimento (o al less un sub-experimento). Si corrigió un error, pero quiere asegurarse de que los resultados sean consistentes, corrigió un error y ese error causó que sus resultados no fueran válidos.

Si introduce una nueva function, no desea sobrescribir los resultados anteriores. ¡Estás simplemente tirando algunos de tus activos! En cambio, use sus resultados para comparar cómo el cambio los afectó. Quizás esto te brinde una nueva percepción de tu model o algorithms.