Ejecutando código en paralelo con el ESP32


El objetivo de esta post es en primer lugar comprender como se puede ejecutar la multitarea en el ESP32 para después ver como se implementa un algoritmo de cálculo de potencia simple en el ESP32 y probar la aceleración ejecutándolo en los dos núcleos del microcontrolador.

El ESP32 viene con 2 microprocesadores Xtensa LX6 de 32 bits: core 0 y core 1. Entonces, es dual core. Cuando ejecutamos código en Arduino IDE, de forma predeterminada, se ejecuta en el núcleo 1. En esta publicación, le mostraremos cómo ejecutar código en el segundo núcleo ESP32 mediante la creación de tareas. Puede ejecutar piezas de código simultáneamente en ambos núcleos y hacer que su ESP32 sea multitarea. Como nota imporante  no necesariamente necesita ejecutar doble núcleo para lograr la multitarea.

El ESP32 por tanto tiene dos núcleos Tensilica LX6 [1] que podemos usar para ejecutar código. Al momento de escribir, la forma más fácil de controlar la ejecución de código en los diferentes núcleos del ESP32 es usando FreeRTOS y asignar una tarea a cada CPU

Aunque de forma genérica son muchos los beneficios de tener más de un núcleo disponible para ejecutar código, uno de los más importantes es aumentar el rendimiento de nuestros programas. Entonces, aunque veremos en este post cómo ejecutar código en los dos núcleos del ESP32, también comprobaremos el aumento de rendimiento que podemos obtener de eso mediante una aplicación simple que sea capaz de calcular una potencia de los números de una matriz. Lo ejecutaremos en un solo núcleo y luego dividiremos el procesamiento por los dos núcleos y verificaremos si hay una ganancia en el tiempo de ejecución. Finalmente, solo para comparar, también dividiremos la ejecución entre cuatro tareas (dos asignadas a cada núcleo) solo para verificar si hay alguna aceleración para generar más tareas que núcleos.

Introducción

El ESP32 viene con 2 microprocesadores Xtensa LX6 de 32 bits, por lo que es de doble núcleo:

  • Núcleo 0
  • Núcleo 1

Cuando subimos el código al ESP32 usando el IDE de Arduino, simplemente se ejecuta; no tenemos que preocuparnos de qué núcleo ejecuta el código.

Hay una función que puede usar para identificar en qué núcleo se está ejecutando el código:

xPortGetCoreID()

Si usa esa función en un boceto de Arduino, verá que tanto setup() para la configuración() como el loopo()se ejecutan en el núcleo 1. Pruébelo usted mismo cargando el siguiente boceto en su ESP32.


void setup() {
  Serial.begin(115200);
  Serial.print("setup() running on core ");
  Serial.println(xPortGetCoreID());
}

void loop() {
  Serial.print("loop() running on core ");
  Serial.println(xPortGetCoreID());
}

Abra el Serial Monitor a una velocidad de transmisión de 115200 y verifique el núcleo en el que se ejecuta el boceto de Arduino.

Crear tareas

Arduino IDE es compatible con FreeRTOS para ESP32, que es un sistema operativo en tiempo real. Esto nos permite manejar varias tareas en paralelo que se ejecutan de forma independiente. Las tareas son fragmentos de código que ejecutan algo. Por ejemplo, puede hacer parpadear un LED, realizar una solicitud de red, medir lecturas de sensores, publicar lecturas de sensores, etc.

Para asignar partes específicas de código a un núcleo específico, debe crear tareas. Al crear una tarea, puede elegir en qué núcleo se ejecutará, así como su prioridad. Los valores de prioridad comienzan en 0, en el que 0 es la prioridad más baja. El procesador ejecutará primero las tareas con mayor prioridad.

Para crear tareas necesita seguir los siguientes pasos:

1. Crear un identificador de tarea. Un ejemplo para Task1:

TaskHandle_t Task1;

2. En la configuración()crear una tarea asignada a un núcleo específico usando elxTaskCreatePinnedToCorefunción. Esa función toma varios argumentos, incluida la prioridad y el núcleo donde se debe ejecutar la tarea (el último parámetro).

xTaskCreatePinnedToCore(
      Task1code, /* Function to implement the task */
      "Task1", /* Name of the task */
      10000,  /* Stack size in words */
      NULL,  /* Task input parameter */
      0,  /* Priority of the task */
      &Task1,  /* Task handle. */
      0); /* Core where the task should run */

3. Después de crear la tarea, debe crear una función que contenga el código para la tarea creada. En este ejemplo, debe crear la tarea e1código()función. Así es como se ve la función de tarea:

Void Task1code( void * parameter) {
  for(;;) {
    Code for task 1 - infinite loop
    (...)
  }
}

los for(;;)crean un bucle infinito. Por lo tanto, esta función se ejecuta de manera similar a la función loop(). Puede usarlo como un segundo ciclo en su código, por ejemplo.

Si durante la ejecución de su código desea eliminar la tarea creada, puede utilizar la función vTareaEliminar(), que acepta el identificador de tareas (Tarea 1) como argumento:

vTaskDelete(Task1);

Veamos cómo funcionan estos conceptos con un ejemplo sencillo.

Crear tareas en diferentes núcleos: ejemplo

Para seguir este ejemplo usaremos las siguientes partes:

Para crear diferentes tareas que se ejecuten en diferentes núcleos, crearemos dos tareas que parpadeen los LED con diferentes tiempos de retraso . Conectaremos mediante dos resistencias en serie de 330 ohmios el cátodo de un led rojo al puerto 4 (GPIO4) y el otro cátodo del led verde al GPIO2 uniendo ambas masas y conectando estas al ping GND del ESP32 .

El plano resultante seria similar al siguiente diagrama:

Crearemos dos tareas ejecutándose en diferentes núcleos:

  • Task1 se ejecuta en el núcleo 0;
  • Task2 se ejecuta en el núcleo 1;

Cargue el siguiente boceto en su ESP32 para hacer parpadear cada LED en un núcleo diferente:



TaskHandle_t Task1;
TaskHandle_t Task2;

// LED pins
const int led1 = 2;
const int led2 = 4;

void setup() {
  Serial.begin(115200); 
  pinMode(led1, OUTPUT);
  pinMode(led2, OUTPUT);

  //create a task that will be executed in the Task1code() function, with priority 1 and executed on core 0
  xTaskCreatePinnedToCore(
                    Task1code,   /* Task function. */
                    "Task1",     /* name of task. */
                    10000,       /* Stack size of task */
                    NULL,        /* parameter of the task */
                    1,           /* priority of the task */
                    &Task1,      /* Task handle to keep track of created task */
                    0);          /* pin task to core 0 */                  
  delay(500); 

  //create a task that will be executed in the Task2code() function, with priority 1 and executed on core 1
  xTaskCreatePinnedToCore(
                    Task2code,   /* Task function. */
                    "Task2",     /* name of task. */
                    10000,       /* Stack size of task */
                    NULL,        /* parameter of the task */
                    1,           /* priority of the task */
                    &Task2,      /* Task handle to keep track of created task */
                    1);          /* pin task to core 1 */
    delay(500); 
}

//Task1code: blinks an LED every 1000 ms
void Task1code( void * pvParameters ){
  Serial.print("Task1 running on core ");
  Serial.println(xPortGetCoreID());

  for(;;){
    digitalWrite(led1, HIGH);
    delay(1000);
    digitalWrite(led1, LOW);
    delay(1000);
  } 
}

//Task2code: blinks an LED every 700 ms
void Task2code( void * pvParameters ){
  Serial.print("Task2 running on core ");
  Serial.println(xPortGetCoreID());

  for(;;){
    digitalWrite(led2, HIGH);
    delay(700);
    digitalWrite(led2, LOW);
    delay(700);
  }
}

void loop() {
  
}

Cómo funciona el código

Nota: en el código creamos dos tareas y asignamos una tarea al núcleo 0 y otra al núcleo 1. Los bocetos de Arduino se ejecutan en el núcleo 1 de forma predeterminada. Por lo tanto, podría escribir el código para Task2 en el loop()(no hubo necesidad de crear otra tarea). En este caso, creamos dos tareas diferentes con fines de aprendizaje.

Sin embargo, según los requisitos de su proyecto, puede ser más práctico organizar su código en tareas como se muestra en este ejemplo.

El código comienza creando un identificador de tarea para Task1 y Task2 llamadoTarea 1yTarea 2.

TaskHandle_t Task1;
TaskHandle_t Task2;

Asigne GPIO 2 y GPIO 4 a los LED:

const int led1 = 2; 
const int led2 = 4;

En la configuración(), inicialice el monitor serie a una velocidad en baudios de 115200:

Serial.begin(115200);

Declare los LED como salidas:

pinMode(led1, OUTPUT); 
pinMode(led2, OUTPUT);

Luego, cree Task1 usando la función  xTaskCreatePinnedToCore() :

xTaskCreatePinnedToCore(
             Task1code, /* Task function. */
             "Task1",   /* name of task. */
             10000,     /* Stack size of task */
             NULL,      /* parameter of the task */
             1,         /* priority of the task */
             &Task1,    /* Task handle to keep track of created task */
             0);        /* pin task to core 0 */

Task1 se implementará con eltarea1código()función. Entonces, necesitamos crear esa función más adelante en el código. Le damos a la tarea la prioridad 1 y la anclamos al núcleo 0.

Creamos Task2 usando el mismo método:

xTaskCreatePinnedToCore(
             Task2code,  /* Task function. */
             "Task2",    /* name of task. */
             10000,      /* Stack size of task */
             NULL,       /* parameter of the task */
             1,          /* priority of the task */
             &Task2,     /* Task handle to keep track of created task */
             1);         /* pin task to core 0 */

Después de crear las tareas, necesitamos crear las funciones que ejecutarán esas tareas.

void Task1code( void * pvParameters ){
  Serial.print("Task1 running on core ");
  Serial.println(xPortGetCoreID());

  for(;;){
    digitalWrite(led1, HIGH);
    delay(1000);
    digitalWrite(led1, LOW);
    delay(1000);
  }
}

La función para Task1 se llamatarea1código()(Puedes llamarlo como quieras). Para fines de depuración, primero imprimimos el núcleo en el que se ejecuta la tarea:

Serial.print("Task1 running on core ");
Serial.println(xPortGetCoreID());

Entonces, tenemos un ciclo infinito similar alcírculo()en el boceto de Arduino. En ese ciclo, parpadeamos el LED1 cada segundo.

Lo mismo sucede con Task2, pero parpadeamos el LED con un tiempo de retraso diferente.

void Task2code( void * pvParameters ){
  Serial.print("Task2 running on core ");
  Serial.println(xPortGetCoreID());

  for(;;){
    digitalWrite(led2, HIGH);
    delay(700);
    digitalWrite(led2, LOW);
    delay(700);
  }
}

Finalmente, el bucle loop tiene ()la función está vacía:

void loop() { }

Nota: como se mencionó anteriormente, el bucle loop ()se ejecuta en el núcleo 1. Entonces, en lugar de crear una tarea para ejecutar en el núcleo 1, simplemente puede escribir su código dentro del loop().

Demostración

Suba el código a su ESP32. Asegúrese de tener la placa y el puerto COM correctos seleccionados.

Abra el monitor serie a una velocidad en baudios de 115200. Debería recibir los siguientes mensajes:

Como era de esperar, Task1 se ejecuta en el núcleo 0, mientras que Task2 se ejecuta en el núcleo 1.

En su circuito, un LED debe parpadear cada 1 segundo y el otro debe parpadear cada 700 milisegundos.

En resumen:

  • El ESP32 es de doble núcleo;
  • Los bocetos de Arduino se ejecutan en el núcleo 1 de forma predeterminada;
  • Para usar core 0 necesitas crear tareas;
  • Puede usar la función TaskCreatePinnedToCore() para fijar una tarea específica a un núcleo específico;
  • Con este método, puede ejecutar dos tareas diferentes de forma independiente y simultánea utilizando los dos núcleos.

Hemos visto un ejemplo simple con LED. La idea es utilizar este método con proyectos más avanzados con aplicaciones del mundo real. Por ejemplo, puede ser útil usar un núcleo para tomar lecturas de sensores y otro para publicar esas lecturas en un sistema de automatización del hogar. Ahora a continuación usando la multitarea vamos a calcular como aumenta la aceleración usando ambos núcleos


Cálculo de aceleración

Básicamente, lo que queremos comprobar es cuánto aumenta la velocidad de ejecución de nuestro programa cuando pasamos de una ejecución de un solo núcleo a una ejecución de doble núcleo. Entonces, una de las formas más fáciles de hacerlo es calcular la relación entre el tiempo de ejecución del programa que se ejecuta en un solo núcleo y el que se ejecuta en los dos núcleos del ESP32 [2].

Mediremos el tiempo de ejecución para cada enfoque utilizando la función Arduino micros , que devuelve la cantidad de microsegundos desde que la placa comenzó a ejecutar el programa [3].

Entonces, para medir un bloque de ejecución de código, haremos algo similar a lo que se indica a continuación.

start = micros();
//Run code
end = micros();
execTime = end - start;

Podríamos haber usado la función FreeRTOS xTaskGetTickCount  para una mayor precisión, pero la función micros será suficiente para lo que queremos mostrar y es una función muy conocida de Arduino.

Variables globales

Comenzaremos nuestro código declarando algunas variables globales auxiliares. No vamos a necesitar ningún include.

Como vamos a comparar aceleraciones en diferentes situaciones, vamos a especificar una matriz con múltiples exponentes para probar. Además, vamos a tener una variable con el tamaño de la matriz, para que podamos iterarla. Primero vamos a probar valores pequeños para el exponente y luego comenzaremos a aumentarlos mucho.

int n[10] = {2, 3, 4, 5, 10, 50, 100, 1000, 2000, 10000 };
int nArraySize = 10;

También vamos a tener algunas variables para almacenar el tiempo de ejecución del código en los diferentes casos de uso. Reutilizaremos las  variables de inicio y finalización  de la ejecución , pero usaremos una variable diferente para cada caso de uso: ejecución de una tarea, ejecución de dos tareas y ejecución de cuatro tareas. De esta forma, almacenaremos los valores para una última comparación.

unsigned long start;
unsigned long end;
unsigned long execTimeOneTask, execTimeTwoTask, execTimeFourTask ;

Luego, declararemos un semáforo de conteo para poder sincronizar la función de configuración (que se ejecuta en una tarea de FreeRTOS) con las tareas que vamos a ejecutar. Consulte esta publicación para obtener una explicación detallada sobre cómo lograr este tipo de sincronización.

Tenga en cuenta que la función xSemaphoreCreateCounting recibe como entrada el recuento máximo y el recuento inicial del semáforo. Como tendremos como máximo 4 tareas para sincronizar, su valor máximo será 4.

SemaphoreHandle_t barrierSemaphore = xSemaphoreCreateCounting( 4, 0 );

Finalmente, declararemos dos arreglos: uno con los valores iniciales (llamado bigArray ) y otro para almacenar los resultados (llamado resultArray ). Haremos sus tallas grandes.

int bigArray[10000], resultArray[10000];


La tarea de FreeRTOS

Definiremos la función que implementará nuestro algoritmo de cálculo de potencia, para ser lanzada como tarea. Si necesita ayuda con los detalles sobre cómo definir una tarea de FreeRTOS, consulte este tutorial anterior.

Lo primero que debemos tener en cuenta es que nuestra función recibirá algunos parámetros de configuración. Dado que en algunos de los casos de uso vamos a dividir la ejecución del algoritmo en varias tareas, necesitamos controlar la parte de la matriz que cubrirá cada tarea.

Para crear solo una tarea genérica que pueda responder a todos nuestros casos de uso, pasaremos como parámetro los índices de la matriz de la que será responsable la tarea. Además, vamos a pasar el exponente, así que ahora sabemos cuántas multiplicaciones necesitamos realizar. Puede consultar con más detalle cómo pasar un argumento a una tarea de FreeRTOS en este tutorial anterior.

Entonces, primero definiremos una estructura con estos 3 parámetros, para poder pasarla a nuestra función. Puedes leer más sobre estructuras aquí . Tenga en cuenta que esta estructura se declara fuera de cualquier función. Entonces, el código se puede colocar cerca de la declaración de variables globales.

struct argsStruct {
  int arrayStart;
  int arrayEnd;
  int n;
};

Como ya tenemos declarada nuestra estructura, dentro de la función declararemos una variable del tipo de esta estructura. Luego, asignaremos el parámetro de entrada de la función a esta variable. Recuerde que los parámetros se pasan a las funciones de FreeRTOS como un puntero a nulo ( void * ) y es nuestra responsabilidad devolverlo al tipo original.

Tenga en cuenta también que le pasamos a la función un puntero a la variable original y no a la variable real, por lo que necesitamos usar el puntero para acceder al valor. 

	
argsStruct myArgs = *((argsStruct*)parameters);

Ahora, implementaremos la función de cálculo de potencia. Tenga en cuenta que podríamos haber usado la función Arduino pow , pero luego explicaré por qué no lo hicimos. Entonces, implementaremos la función con un ciclo donde multiplicaremos un valor por sí mismo n veces.

Comenzamos declarando una variable para contener el producto parcial. Luego, haremos un ciclo for para iterar todos los elementos de la matriz asignada a la tarea. Recuerda que este era nuestro objetivo inicial. Luego, para cada elemento, calcularemos su potencia y al final le asignaremos el valor a la matriz de resultados. Esto asegura que usaremos los mismos datos en todas nuestras pruebas y no cambiaremos la matriz original.

Para un código más limpio, podríamos haber implementado el algoritmo pow en una función dedicada, pero se intenta minimizar las llamadas a funciones auxiliares para un código más compacto.

Verifique el código a continuación. Dejo ahí comentada una línea de código para calcular la potencia usando la función pow , así que puedes probarlo si quieres. Tenga en cuenta que los resultados de aceleración serán considerablemente diferentes. Además, para acceder a un elemento de una variable de estructura, usamos el nombre de la variable punto («.») el nombre del elemento.

int product;
for (int i = myArgs.arrayStart; i < myArgs.arrayEnd; i++) {
 
    product = 1;
    for (int j = 0; j < myArgs.n; j++) {
      product =  product * bigArray[i];
    }
    resultArray[i]=product;
    //resultArray [i] = pow(bigArray[i], myArgs.n);
}

Compruebe el código de función completo a continuación. Tenga en cuenta que al final del código estamos aumentando el semáforo de conteo global en una unidad. Esto se hace para asegurar la sincronización con la función de configuración, ya que contaremos el tiempo de ejecución a partir de ahí.  Además, al final, estamos eliminando la tarea.

void powerTask( void * parameters ) {
 
  argsStruct  myArgs = *((argsStruct*)parameters);
 
  int product;
  for (int i = myArgs.arrayStart; i < myArgs.arrayEnd; i++) {
 
    product = 1;
    for (int j = 0; j < myArgs.n; j++) {
      product =  product * bigArray[i];
    }
    resultArray[i]=product;
    //resultArray [i] = pow(bigArray[i], myArgs.n);
  }
 
  xSemaphoreGive(barrierSemaphore);
 
  vTaskDelete(NULL);
 
}


La función de configuración

Vamos a hacer todo el código restante en la función de configuración, por lo que nuestro ciclo principal estará vacío. Comenzaremos abriendo una conexión en serie para generar los resultados de nuestras pruebas.

Serial.begin(115200);
Serial.println();

Ahora, inicializaremos nuestra matriz con algunos valores para aplicar el cálculo. Tenga en cuenta que no nos preocupa el contenido de la matriz ni el resultao real, sino los tiempos de ejecución. Entonces, vamos a inicializar la matriz con valores aleatorios solo para mostrar esas funciones, ya que no vamos a imprimir la matriz que contendrá los resultados.

Primero vamos a llamar a la función randomSeed , por lo que los valores aleatorios generados diferirán en diferentes ejecuciones del programa [4]. Si el pin analógico está desconectado, devolverá un valor correspondiente a ruido aleatorio, lo cual es ideal para pasar como entrada de la función randomSeed .

Después de eso, podemos simplemente llamar a la función aleatoria  , pasando como argumentos los valores mínimo y máximo que se pueden devolver [4]. Nuevamente, estamos haciendo esto solo con fines ilustrativos, ya que no vamos a imprimir el contenido de las matrices.

randomSeed(analogRead(0));
for (int i = 0; i < 10000; i++) {
  bigArray[i] = random(1, 10);
}

Ahora, vamos a definir las variables que se utilizarán como argumentos para nuestras tareas. Recuerda la estructura declarada anteriormente, que contendrá los índices del arreglo que procesará cada tarea y el exponente.

Entonces, el primer elemento de la estructura es el índice inicial, el segundo es el índice final y el tercero es el exponente. Declararemos estructuras para cada caso de uso (una tarea, dos tareas y cuatro tareas) como se puede ver en el código a continuación. Tenga en cuenta que en el último elemento de la estructura estamos pasando un elemento de la matriz global que declaramos con los exponentes. Entonces, como veremos en el código final, iteraremos toda la matriz, pero por ahora mantengamos las cosas simples.

argsStruct oneTask = { 0 , 1000 , n[i] };
 
argsStruct twoTasks1 = { 0 , 1000 / 2 , n[i] };
argsStruct twoTasks2 = { 1000 / 2 , 1000 , n[i] };
 
argsStruct fourTasks1 = { 0 , 1000 / 4 , n[i] };
argsStruct fourTasks2 = { 1000 / 4 , 1000 / 4 * 2, n[i]};
argsStruct fourTasks3 = { 1000 / 4 * 2, 1000 / 4 * 3, n[i]};
argsStruct fourTasks4 = { 1000 / 4 * 3 , 1000, n[i]};

Ahora, vamos a hacer la prueba usando una sola tarea. Empezamos por obtener el tiempo de ejecución con la función micros. Luego, usaremos la función  xTaskCreatePinnedToCore para crear una tarea de FreeRTOS anclada a uno de los núcleos. Elegiremos el núcleo 1.

Vamos a pasar como parámetro la dirección de la variable de estructura oneTask , que contiene los argumentos necesarios para que se ejecute la función. No olvides el yeso al vacío* .

Una cosa muy importante a tener en cuenta es que aún no hemos analizado qué tareas pueden haber sido lanzadas por el núcleo de Arduino y que pueden influir en el tiempo de ejecución. Entonces, para garantizar que nuestra tarea se ejecutará con mayor prioridad, le asignaremos un valor de 20. Recuerde, los números más altos significan una mayor prioridad de ejecución para el programador de FreeRTOS.

Después de iniciar la tarea, solicitaremos una unidad del semáforo, asegurándonos de que la función de configuración se mantendrá hasta que la nueva tarea termine de ejecutarse. Finalmente, imprimiremos el tiempo de ejecución.

Serial.println("");
Serial.println("------One task-------");
 
start = micros();
 
xTaskCreatePinnedToCore(
  powerTask,               /* Function to implement the task */
  "powerTask",              /* Name of the task */
  10000,                   /* Stack size in words */
  (void*)&oneTask,         /* Task input parameter */
  20,                      /* Priority of the task */
  NULL,                    /* Task handle. */
  1);                      /* Core where the task should run */
 
xSemaphoreTake(barrierSemaphore, portMAX_DELAY);
 
end = micros();
execTimeOneTask = end - start;
Serial.print("Exec time: ");
Serial.println(execTimeOneTask);
Serial.print("Start: ");
Serial.println(start);
Serial.print("end: ");
Serial.println(end);

Esto es más o menos lo que vamos a hacer para el resto de los casos de uso. La única diferencia es que vamos a lanzar más tareas y vamos a intentar sacar más unidades del semáforo (tantas como tareas lanzadas para ese caso de uso).

Consulte el código fuente completo a continuación, que ya incluye los casos de uso para ejecutar el código con dos tareas (una por núcleo ESP32) y cuatro tareas (dos por núcleo ESP32). Además, al final de la función de configuración, incluye la impresión de los resultados de aceleración para cada iteración.

Tenga en cuenta que el parámetro extra de la función Serial.println indica el número de lugares decimales para el número de punto flotante.

int n[10] = {2, 3, 4, 5, 10, 50, 100, 1000, 2000, 10000  };
int nArraySize = 10;
 
struct argsStruct {
  int arrayStart;
  int arrayEnd;
  int n;
};
 
unsigned long start;
unsigned long end;
unsigned long execTimeOneTask, execTimeTwoTask, execTimeFourTask ;
 
SemaphoreHandle_t barrierSemaphore = xSemaphoreCreateCounting( 4, 0 );
 
int bigArray[10000], resultArray[10000];
 
void setup() {
 
  Serial.begin(115200);
  Serial.println();
 
  randomSeed(analogRead(0));
 
  for (int i = 0; i < 10000; i++) {
    bigArray[i] = random(1, 10);
  }
 
  for (int i = 0; i < nArraySize; i++) {
 
    Serial.println("#############################");
    Serial.print("Starting test for n= ");
    Serial.println(n[i]);
 
    argsStruct oneTask = { 0 , 1000 , n[i] };
 
    argsStruct twoTasks1 = { 0 , 1000 / 2 , n[i] };
    argsStruct twoTasks2 = { 1000 / 2 , 1000 , n[i] };
 
    argsStruct fourTasks1 = { 0 , 1000 / 4 , n[i] };
    argsStruct fourTasks2 = { 1000 / 4 , 1000 / 4 * 2,   n[i]};
    argsStruct fourTasks3 = { 1000 / 4 * 2, 1000 / 4 * 3, n[i]};
    argsStruct fourTasks4 = { 1000 / 4 * 3 , 1000,     n[i]};
 
    Serial.println("");
    Serial.println("------One task-------");
 
    start = micros();
 
    xTaskCreatePinnedToCore(
      powerTask,               /* Function to implement the task */
      "powerTask",              /* Name of the task */
      10000,                   /* Stack size in words */
      (void*)&oneTask,         /* Task input parameter */
      20,                      /* Priority of the task */
      NULL,                    /* Task handle. */
      1);                      /* Core where the task should run */
 
    xSemaphoreTake(barrierSemaphore, portMAX_DELAY);
 
    end = micros();
    execTimeOneTask = end - start;
    Serial.print("Exec time: ");
    Serial.println(execTimeOneTask);
    Serial.print("Start: ");
    Serial.println(start);
    Serial.print("end: ");
    Serial.println(end);
 
    Serial.println("");
    Serial.println("------Two tasks-------");
 
    start = micros();
 
    xTaskCreatePinnedToCore(
      powerTask,                /* Function to implement the task */
      "powerTask",              /* Name of the task */
      10000,                    /* Stack size in words */
      (void*)&twoTasks1,        /* Task input parameter */
      20,                       /* Priority of the task */
      NULL,                     /* Task handle. */
      0);                       /* Core where the task should run */
 
    xTaskCreatePinnedToCore(
      powerTask,               /* Function to implement the task */
      "coreTask",              /* Name of the task */
      10000,                   /* Stack size in words */
      (void*)&twoTasks2,       /* Task input parameter */
      20,                      /* Priority of the task */
      NULL,                    /* Task handle. */
      1);                      /* Core where the task should run */
 
    for (int i = 0; i < 2; i++) {
      xSemaphoreTake(barrierSemaphore, portMAX_DELAY);
    }
 
    end = micros();
    execTimeTwoTask = end - start;
    Serial.print("Exec time: ");
    Serial.println(execTimeTwoTask);
    Serial.print("Start: ");
    Serial.println(start);
    Serial.print("end: ");
    Serial.println(end);
 
    Serial.println("");
    Serial.println("------Four tasks-------");
 
    start = micros();
 
    xTaskCreatePinnedToCore(
      powerTask,                /* Function to implement the task */
      "powerTask",              /* Name of the task */
      10000,                    /* Stack size in words */
      (void*)&fourTasks1,       /* Task input parameter */
      20,                       /* Priority of the task */
      NULL,                     /* Task handle. */
      0);                       /* Core where the task should run */
 
    xTaskCreatePinnedToCore(
      powerTask,                /* Function to implement the task */
      "powerTask",              /* Name of the task */
      10000,                    /* Stack size in words */
      (void*)&fourTasks2,       /* Task input parameter */
      20,                       /* Priority of the task */
      NULL,                     /* Task handle. */
      0);                       /* Core where the task should run */
 
    xTaskCreatePinnedToCore(
      powerTask,                /* Function to implement the task */
      "powerTask",              /* Name of the task */
      10000,                    /* Stack size in words */
      (void*)&fourTasks3,       /* Task input parameter */
      20,                       /* Priority of the task */
      NULL,                     /* Task handle. */
      1);                       /* Core where the task should run */
 
    xTaskCreatePinnedToCore(
      powerTask,                /* Function to implement the task */
      "powerTask",              /* Name of the task */
      10000,                    /* Stack size in words */
      (void*)&fourTasks4,       /* Task input parameter */
      20,                       /* Priority of the task */
      NULL,                     /* Task handle. */
      1);                       /* Core where the task should run */
 
    for (int i = 0; i < 4; i++) {
      xSemaphoreTake(barrierSemaphore, portMAX_DELAY);
    }
 
    end = micros();
    execTimeFourTask = end - start;
    Serial.print("Exec time: ");
    Serial.println(execTimeFourTask);
    Serial.print("Start: ");
    Serial.println(start);
    Serial.print("end: ");
    Serial.println(end);
 
    Serial.println();
    Serial.println("------Results-------");
 
    Serial.print("Speedup two tasks: ");
    Serial.println((float) execTimeOneTask / execTimeTwoTask, 4 );
 
    Serial.print("Speedup four tasks: ");
    Serial.println((float)execTimeOneTask / execTimeFourTask, 4 );
 
    Serial.print("Speedup four tasks vs two tasks: ");
    Serial.println((float)execTimeTwoTask / execTimeFourTask, 4 );
 
    Serial.println("#############################");
    Serial.println();
  }
 
}
 
void loop() {
 
}
 
void powerTask( void * parameters ) {
 
  argsStruct  myArgs = *((argsStruct*)parameters);
 
  int product;
  for (int i = myArgs.arrayStart; i < myArgs.arrayEnd; i++) {
 
    product = 1;
    for (int j = 0; j < myArgs.n; j++) {
      product =  product * bigArray[i];
    }
 
    resultArray[i]=product;
    //resultArray [i] = pow(bigArray[i], myArgs.n);
  }
 
  xSemaphoreGive(barrierSemaphore);
 
  vTaskDelete(NULL);
 
}


Probando el código

Para probar el código, simplemente cárguelo con el IDE de Arduino y abra el monitor serie. Debería obtener un resultado similar a la figura 1. Naturalmente, los tiempos de ejecución pueden variar.

Análisis de aceleración de doble núcleo ESP32

Figura 1 : Salida del programa de prueba de aceleración.

Los resultados para cada exponente se muestran en la siguiente tabla, en la tabla 1.

Exponente1 tarea [µs]2 tareas [µs]4 tareas [µs]Acelerar 1 tarea frente a 2 tareasAcelerar 1 tarea frente a 4 tareasAcelerar 2 tareas vs 4 tareas
22291832961.25140.77360.6182
32712073251.30920.83380.6369
43122243401.39290.91760.6588
53542493671.42170.96460.6785
105563474511.60231.23280.7694
502235118813051.88131.71260.9103
1004331223423431.93871.84850.9535
10004207221108212121.99321.98340.9951
20008399242138421901.99331.99080.9988
100004194002102832103101.99451.99420.9999

Tabla 1 – Resultados de aceleración para los exponentes definidos en el código.


Analizando los resultados

Para comprender los resultados, primero debemos tener en cuenta que no se puede paralelizar completamente todo el programa. Entonces, siempre habrá partes del código que se ejecuten en un solo núcleo, como el lanzamiento de las tareas o los mecanismos de sincronización.

Aunque no teníamos este caso de uso, muchos algoritmos de paralelización también tienen una parte en la que cada resultado parcial se agrega secuencialmente (por ejemplo, si tenemos varias tareas calculando el valor máximo de su parte de la matriz y luego la tarea principal calcula el valor máximo entre todos los resultados parciales).

Entonces, hagamos lo que hagamos, teóricamente no podemos lograr una aceleración igual a la cantidad de núcleos (hay algunas excepciones en, por ejemplo, algoritmos que buscan un valor específico y salen cuando lo encuentran, pero no nos compliquemos).

Entonces, mientras más cómputos ejecutemos en paralelo versus la porción que ejecutamos en secuencia, más aceleración tendremos. Y eso es precisamente lo que vemos en nuestros resultados. Si comenzamos con un exponente de 2 , nuestra aceleración de la ejecución de una tarea a dos tareas es solo de  1.2514 .

Pero a medida que aumentamos el valor del exponente, hacemos más iteraciones del ciclo más interno y, por lo tanto, más cálculos podemos realizar en paralelo. Así, a medida que aumentamos el exponente, vemos un aumento de la aceleración. Por ejemplo, con un exponente de 1000 , nuestra aceleración es  1.9933 . Lo cual es un valor muy alto en comparación con el anterior.

Entonces, esto significa que la parte secuencial se diluye y para grandes exponentes la paralelización compensa. Tenga en cuenta que podemos lograr este tipo de valores altos porque el algoritmo es muy simple y fácil de paralelizar. Además, debido a la forma en que FreeRTOS maneja las prioridades y los cambios de contexto de ejecución de tareas, no hay mucha sobrecarga después de que se ejecutan las tareas. Una computadora normal tiende a tener más cambios de contexto entre subprocesos y, por lo tanto, la sobrecarga secuencial es mayor.

Tenga en cuenta que no usamos la función pow para mostrar esta progresión en los valores de aceleración. La función pow usa flotantes, lo que significa que su implementación es más intensiva en computación. Entonces, obtendríamos aceleraciones mucho mejores en exponentes más bajos porque la parte paralela sería mucho más relevante que la secuencial. Puede comentar nuestro algoritmo implementado y usar la función pow  para comparar los resultados.

Además, es importante tener en cuenta que lanzar más tareas que la cantidad de núcleos no aumenta la velocidad. De hecho, tiene el efecto contrario. Como podemos ver en los resultados, la aceleración de ejecutar con 4 tareas siempre es menor que la de ejecutar con 2 tareas. De hecho, para exponentes bajos, en realidad es más lento ejecutar el código con 4 tareas (aunque estén divididas entre los dos núcleos del ESP32) que ejecutar con una tarea en un solo núcleo.

Esto es normal porque cada CPU solo puede ejecutar una tarea en un momento dado y, por lo tanto, lanzar más tareas que CPU significa más tiempo secuencial para manejarlas y más sobrecarga en los puntos de sincronización.

Sin embargo, tenga en cuenta que esto no siempre es blanco y negro. Si las tareas tuvieran algún tipo de punto de rendimiento en el que dejarían de esperar algo, entonces tener más tareas que núcleos podría ser beneficioso para usar los ciclos de CPU libres. Sin embargo, en nuestro caso, las CPU nunca rinden y siempre se están ejecutando, por lo que no hay ningún beneficio en lanzar más de 2 tareas, en cuanto a la aceleración.


La ley de Amdhal

Para complementar los resultados, veremos la ley de Amdahl . Entonces, vamos a aplicar algunas transformaciones a la fórmula de aceleración que estábamos usando. La fórmula inicial era que la aceleración es igual al tiempo de ejecución secuencial (lo llamaremos T ) dividido por el tiempo de ejecución en paralelo (lo llamaremos T Paralelo ).

fórmula de aceleración

Pero, como vimos, hay una parte del tiempo de ejecución que siempre es secuencial y otra que puede ser paralelizada. Llamemos p a la fracción del programa que podemos ejecutar en paralelo. Como es una fracción, su valor estará entre 0 y 1.

Entonces, podemos representar el tiempo de ejecución secuencial T como la porción que puede ejecutarse en paralelo ( p*T ) más la porción que no puede (1-p)*T .

ejecución secuencial

Como solo podemos dividir la parte paralela entre los núcleos de nuestra máquina, el tiempo T Parallel es similar a la fórmula anterior, excepto que la parte paralela aparece dividida por la cantidad de núcleos que tenemos disponibles.

ejecución en paralelo

Entonces, nuestra fórmula de aceleración se convierte en:

Fórmula modificada de aceleración

Ahora tenemos la aceleración escrita en función de T , el tiempo de ejecución original sin mejoras. Entonces, podemos dividir cada término por T :

acelerar la ley de amdahl

Entonces, ahora tenemos la aceleración escrita en función de la parte que puede ejecutarse en paralelo y la cantidad de núcleos. Para terminar, supongamos que tenemos una cantidad infinita de núcleos disponibles para ejecutar (el caso de uso real sería un número muy grande, pero analizaremos matemáticamente la fórmula).

Acelera un número infinito de núcleos

Como una constante dividida por infinito es igual a 0, terminamos con:

Acelerar un número infinito de núcleos expresión final

Así que, aunque la aceleración aumenta con la cantidad de recursos disponibles para la paralelización (la cantidad de núcleos, en nuestro caso), lo cierto es que la máxima aceleración teórica posible está limitada por la parte no paralela, que no podemos optimizar.

Esta es una conclusión interesante para que decidamos si vamos o no a la paralelización. Algunas veces, los algoritmos pueden paralelizarse fácilmente y tenemos una tremenda aceleración, otras el esfuerzo necesario no justifica la ganancia.

Además, otra cosa importante a tener en cuenta es que el mejor algoritmo secuencial no siempre es el mejor después de la paralelización. Entonces, a veces, es mejor usar un algoritmo que tiene menos rendimiento en la ejecución secuencial, pero termina siendo mucho mejor que el mejor secuencial en la paralelización.


Resumen

Con este post, confirmamos que las funciones disponibles para la ejecución multinúcleo funcionan bien y podemos aprovecharlas para obtener beneficios de rendimiento.

La última parte más teórica tenía el objetivo de mostrar que la paralelización no es un tema trivial, y uno no puede saltar directamente a un enfoque paralelo sin un análisis previo y esperar una aceleración igual a la cantidad de núcleos disponibles. Este razonamiento se extiende más allá del alcance del ESP32 y se aplica a la computación paralela en general.

Entonces, para aquellos que van a comenzar con la computación paralela utilizando el ESP32 para mejorar el rendimiento, es un buen comienzo para aprender primero algunos de los conceptos más teóricos.

Finalmente, tenga en cuenta que el código está orientado a mostrar los resultados, por lo que se podrían haber hecho muchas optimizaciones, como lanzar las tareas en un ciclo en lugar de repetir el código o condensar el algoritmo en una función.

Además, no buscábamos los resultados de la ejecución para confirmar que la matriz de salida era la misma porque ese no era nuestro enfoque principal. Naturalmente, pasar de secuencial a paralelo requiere una implementación cuidadosa del código y la ejecución de muchas pruebas para garantizar que el resultado sea el mismo.

Mas información en

[1] https://espressif.com/en/products/hardware/esp32/overview

[2] http://www.dcc.fc.up.pt/~fds/aulas/PPD/1112/metrics_en.pdf

[3] https://www.arduino.cc/en/reference/micros

[4] https://www.arduino.cc/en/reference/random

[5] https://techtutorialsx.com/2017/05/16/esp32-dual-core-execution-speedup/

Anuncio publicitario

Como ejecutar un cuaderno de Jupyter Notebook en Visual Studio Code


En efecto gracias a la extensión de Jupyter para Visual Studio Code podemos ejecutar nuestros cuadernos en python de Jupyter Notebook desde el interfaz de Visual Studio Code, editor que como probablemente amigo lector sabrà es gratuito y uno de los más potentes IDEs hasta la fecha.

Jupyter Notebook es una herramienta increíblemente poderosa para desarrollar y presentar proyectos de ciencia de datos de forma interactiva. Este post intentara guiarle de cómo usar Jupyter Notebooks para proyectos de ciencia de datos y cómo configurarlo en su máquina local pero en vez de usar un navegador lo cual nos genera ciertos problemas ( versionado, restauración de código , etc.) lo usaremos desde el potente IDE de Visual Studio Code.

jupyter-notebook las mejores herramientas gratuitas de ciencia de datos

Pero primero: ¿qué es un “cuaderno”?

Un cuaderno integra código y su salida en un solo documento que combina visualizaciones, texto narrativo, ecuaciones matemáticas y otros medios enriquecidos. En otras palabras: es un documento único en el que puede ejecutar código, mostrar el resultado y también agregar explicaciones, fórmulas, gráficos y hacer que su trabajo sea más transparente, comprensible, repetible y compartible. 

El uso de Notebooks es ahora una parte importante del flujo de trabajo de la ciencia de datos en empresas de todo el mundo. Si su objetivo es trabajar con datos, el uso de una computadora portátil acelerará su flujo de trabajo y facilitará la comunicación y el intercambio de resultados. 

Lo mejor de todo es que, como parte del proyecto de código abierto  Jupyter , los cuadernos de Jupyter son completamente gratuitos. Puede descargar el software  solo o como parte del kit de herramientas de ciencia de datos de Anaconda .

Aunque es posible utilizar muchos lenguajes de programación diferentes en Jupyter Notebooks, nos centraremosen Python, ya que es el caso de uso más común. (Entre los usuarios de R, R Studio  tiende a ser una opción más popular).Jupyter Notebooks también puede actuar como una plataforma flexible para familiarizarse con los pandas e incluso con Python, como se verá en este tutorial.


Las extensiones de Visual Studio Code proporcionan soporte básico de cuaderno para los kernels de lenguaje que son compatibles con los cuadernos Jupyter hoy en día. Muchos kernels de lenguaje funcionarán sin ninguna modificación. Para habilitar las características avanzadas, pueden ser necesarias modificaciones en las extensiones de lenguaje de VS Code.

La extensión Jupyter incluye por defecto las extensiones Jupyter Keymaps y Jupyter Notebook Renderers. La extensión Jupyter Keymaps proporciona mapas de teclado consistentes con Jupyter y la extensión Jupyter Notebook Renderers proporciona renderizadores para tipos MIME como latex, plotly, vega, y similares. Ambas extensiones pueden deshabilitarse o desinstalarse. Y por cierto, la extensión está disponible en varios idiomas: de, en, es, fa, fr, it, ja, ko-kr, nl, pl, pt-br, ru, tr, zh-cn, zh-tw

La Extensión Jupyter utiliza el soporte de cuadernos incorporado de VS Code. Esta interfaz de usuario ofrece una serie de ventajas a los usuarios de cuadernos:

  • Soporte inmediato de la amplia gama de funciones básicas de edición de código de VS Code, como la salida en caliente, la búsqueda y el reemplazo, y el plegado de código.
  • Extensiones del editor como VIM, coloración de corchetes, linters y muchas más están disponibles mientras se edita una celda.
  • Profunda integración con el banco de trabajo general y con las funciones basadas en archivos de VS Code, como la vista de esquema (tabla de contenidos), las migas de pan y otras operaciones.
  • Tiempos de carga rápidos para los archivos Jupyter notebook (.ipynb). Cualquier archivo de cuaderno se carga y renderiza lo más rápidamente posible, mientras que las operaciones relacionadas con la ejecución se inicializan entre bastidores.
  • Incluye una herramienta de diferencias para cuadernos, que facilita la comparación y la visualización de las diferencias entre las celdas de código, los resultados y los metadatos.
  • Extensibilidad más allá de lo que proporciona la extensión Jupyter. Las extensiones ahora pueden añadir su propio lenguaje o tiempo de ejecución específico a los cuadernos, como los cuadernos interactivos de .NET y Gather
  • Aunque la extensión de Jupyter viene con un amplio conjunto de los renderizadores más utilizados para la salida, el mercado admite renderizadores personalizados instalables para que el trabajo con tus cuadernos sea aún más productivo. Para empezar a escribir los tuyos propios, consulta la documentación de la api de renderizadores de VS Code.

Trabajar con Python


Si quiere trabajar con Python sólo tiene que asegurarte de que utiliza la última versión de la Extensión Python para disfrutar de la asociación conjunta de las Extensiones Python y Juypter.

Por favor, siga las instrucciones de Léame de la Extensión Python para empezar y visite la Documentación de Python para aprender más sobre cómo la Extensión Python y Jupyter trabajan juntas para proporcionar una experiencia óptima de cuadernos Python.

Ejecutar por línea


Para iniciar una sesión de depuración ligera y ejecutar celdas de código línea por línea en cuadernos de Python, pulse F10 mientras selecciona una celda o haga clic en el botón Ejecutar por línea de la barra de herramientas de la celda. También admite núcleos remotos.

Una vez que inicies una sesión de Ejecutar por Línea, aparecerá el Explorador de Variables y los valores de las variables se actualizarán a medida que itera a través de su código.

Para recorrer el resto de la celda durante una sesión Run by Line pulse Ctrl+Enter. Para parar, puede hacer clic en el botón de interrupción en el lado izquierdo de la celda.


Por cierto como ya se ha comentado ,la Extensión Jupyter soporta otros lenguajes además de Python como Julia, R y C#.

Estsos serian los pasos a seguir para un Inicio rápido


Para crear un nuevo cuaderno, abra la paleta de comandos (Windows: Ctrl + Shift + P, macOS: Command + Shift + P) y seleccione el comando «Crear: Nuevo cuaderno Jupyter».

Selecciona su kernel haciendo clic en el selector de kernel en la parte superior derecha del cuaderno o invocando el comando «Notebook: Seleccionar núcleo de cuaderno».(Notebook: Select Notebook Kernel)

Cambie el idioma de la celda haciendo clic en el selector de idioma o invocando el comando «Cuaderno: Cambiar idioma de la celda». (Notebook: Change Cell Language)

Comandos útiles


Abre la paleta de comandos (Comando+Mayúsculas+P en macOS y Ctrl+Mayúsculas+P en Windows/Linux) y escribe uno de los siguientes comandos:

CommandDescription
Create: New Jupyter NotebookCreate: New Jupyter Notebook
Notebook: Select Notebook KernelSelect or switch kernels within your notebook
Notebook: Change Cell LanguageChange the language of the cell currently in focus
Jupyter: Export to HTML Jupyter: Export to PDFCreate a presentation-friendly version of your notebook in HTML or PDF

Para ver todos los comandos disponibles de Jupyter Notebook, abra la paleta de comandos y escriba Jupyter o Notebook.

Teclas de contexto para los enlaces de teclas


Puede utilizar las teclas de contexto de la extensión en cláusulas «cuando». He aquí un ejemplo:

  {
    "key": "ctrl+i",
    "command": "jupyter.runAndDebugCell",
    "when": "!jupyter.webExtension"
  }

Ese keybinding establece que el comando jupyter.runAndDebugCell debe asignarse a CTRL+I cuando no está en la jupyter.webExtension.

La lista completa de teclas de contexto se puede encontrar aquí: https://github.com/microsoft/vscode-jupyter/wiki/Extensibility-for-other-extensions#context-keys-for-keybindings

Mas información:


Aprenda más sobre las ricas características de la extensión Jupyter:

IntelliSense: Edite su código con autocompletado, navegación por el código, comprobación de sintaxis y mucho más. (mas info en https://code.visualstudio.com/docs/python/editing#_autocomplete-and-intellisense)

Cuadernos Jupyter: Cree y edite cuadernos Jupyter, añada y ejecute celdas de código/marcadas, haga trazados, cree versiones de su cuaderno aptas para la presentación exportándolas a HTML o PDF y mucho más. (mas info en https://code.visualstudio.com/docs/python/jupyter-support)

Descarga extension Jupyter para Visual Studio Code:Mas información en https://marketplace.visualstudio.com/items?itemName=ms-toolsai.jupyter

Como crear una aplicacion Blazor


En este post vamos a ver la creación de una aplicación web de un chat  en tiempo real mediante SignalR con Blazor WebAssembly. 

Los pasos a seguir son los siguientes:

  • Crear un proyecto de aplicación Blazor WebAssembly hospedado
  • Adición de la biblioteca cliente de SignalR
  • Agregar un concentrador de SignalR
  • Agregar servicios de SignalR y un punto de conexión para el concentrador de SignalR
  • Agregar código de componente de Razor para chat

Al final de este post, tendrá una aplicación de chat funcional

 

Antes de comenzar este post, se recomienda instalar un editor.

https://dotnet.microsoft.com/download

 

https://dotnet.microsoft.com/download/dotnet/thank-you/sdk-5.0.100-windows-x64-installer 

 

 

 

 

Creación de un proyecto de aplicación Blazor WebAssembly hospedado

Siga las instrucciones para su elección de herramientas:

Se requiere Visual Studio 16.8 o posterior y el SDK de .NET Core 5.0.0 o posterior.

  1. Cree un nuevo proyecto.
  2. Seleccione Aplicación Blazor y luego Siguiente.
  3. Escriba BlazorSignalRApp en el campo Nombre del proyecto. Confirme que la entrada de Ubicación es correcta o proporcione una ubicación para el proyecto. Seleccione Crear.
  4. Elija la plantilla Aplicación de Blazor WebAssembly .
  5. En Avanzado, active la casilla ASP.NET Core hospedado.
  6. Seleccione Crear.

Adición de la biblioteca cliente de SignalR

  • En el Explorador de soluciones, haga clic con el botón derecho en el proyecto BlazorSignalRApp.Client y seleccione Administrar paquetes NuGet.
  • En el cuadro de diálogo Administrar paquetes NuGet, confirme que Origen del paquete se ha establecido en nuget.org.
  • Con Examinar seleccionado, escriba Microsoft.AspNetCore.SignalR.Client en el cuadro de búsqueda.
  • En los resultados de la búsqueda, seleccione el paquete Microsoft.AspNetCore.SignalR.Client y, después, seleccione Instalar.
  • Si aparece el cuadro de diálogo Vista previa de los cambios, seleccione Aceptar.
  • Si aparece el cuadro de diálogo Aceptación de la licencia, seleccione Acepto si está de acuerdo con los términos de la licencia.

Agregar un concentrador de SignalR

En el proyecto BlazorSignalRApp.Server, cree una carpeta Hubs (plural) y agregue la siguiente clase ChatHub (Hubs/ChatHub.cs):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;

namespace BlazorSignalRApp.Server.Hubs
{
    public class ChatHub : Hub
    {
        public async Task SendMessage(string user, string message)
        {
            await Clients.All.SendAsync("ReceiveMessage", user, message);
        }
    }
}

Adición de servicios y de un punto de conexión para el concentrador de SignalR

  1. En el proyecto BlazorSignalRApp.Server, abra el archivo Startup.cs.
  2. Agregue el espacio de nombres para la clase ChatHub en la parte superior del archivo:C#Copiarusing BlazorSignalRApp.Server.Hubs;
  3. Agregue servicios de middleware de compresión de respuesta y SignalR a Startup.ConfigureServices:

 

public void ConfigureServices(IServiceCollection services)
{
    services.AddSignalR();
    services.AddControllersWithViews();
    services.AddRazorPages();
    services.AddResponseCompression(opts =>
    {
        opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
            new[] { "application/octet-stream" });
    });
}
  1. En Startup.Configure:
    • Use el middleware de compresión de respuesta de la parte superior de la configuración de la canalización de procesamiento.
    • Entre los puntos de conexión de los controladores y la reserva del lado cliente, agregue un punto de conexión para el concentrador.

 

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseResponseCompression();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseWebAssemblyDebugging();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseBlazorFrameworkFiles();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
        endpoints.MapControllers();
        endpoints.MapHub<ChatHub>("/chathub");
        endpoints.MapFallbackToFile("index.html");
    });
}

Agregar código de componente de Razor para chat

  1. En el proyecto BlazorSignalRApp.Client, abra el archivo Pages/Index.razor.
  2. Reemplace el marcado con el código siguiente:

 

@page "/"
@using Microsoft.AspNetCore.SignalR.Client
@inject NavigationManager NavigationManager
@implements IAsyncDisposable

<div class="form-group">
    <label>
        User:
        <input @bind="userInput" />
    </label>
</div>
<div class="form-group">
    <label>
        Message:
        <input @bind="messageInput" size="50" />
    </label>
</div>
<button @onclick="Send" disabled="@(!IsConnected)">Send</button>

<hr>

<ul id="messagesList">
    @foreach (var message in messages)
    {
        <li>@message</li>
    }
</ul>

@code {
    private HubConnection hubConnection;
    private List<string> messages = new List<string>();
    private string userInput;
    private string messageInput;

    protected override async Task OnInitializedAsync()
    {
        hubConnection = new HubConnectionBuilder()
            .WithUrl(NavigationManager.ToAbsoluteUri("/chathub"))
            .Build();

        hubConnection.On<string, string>("ReceiveMessage", (user, message) =>
        {
            var encodedMsg = $"{user}: {message}";
            messages.Add(encodedMsg);
            StateHasChanged();
        });

        await hubConnection.StartAsync();
    }

    Task Send() =>
        hubConnection.SendAsync("SendMessage", userInput, messageInput);

    public bool IsConnected =>
        hubConnection.State == HubConnectionState.Connected;
        
    public async ValueTask DisposeAsync()
    {
        await hubConnection.DisposeAsync();
    }
}

Ejecutar la aplicación

  1. En el Explorador de soluciones, seleccione el proyecto BlazorSignalRApp.Server. Presione F5 para ejecutar la aplicación con depuración o Ctrl+F5 para ejecutarla sin depuración.
  2. Copie la dirección URL de la barra de direcciones, abra otra instancia o pestaña del explorador, y pegue la dirección URL en la barra de direcciones.
  3. Elija cualquier explorador, escriba un nombre y un mensaje, y haga clic en el botón para enviar el mensaje. El nombre y el mensaje se muestran en ambas páginas al instante:

Cómo seleccionar columnas con Pandas


Mientras trabaja en proyectos de ciencia de datos, generalmente obtiene una gran cantidad de datos. Pero es posible que deba trabajar solo con un subconjunto de columnas de los datos. Esta selección de datos necesarios se realiza para simplificar las tareas. Además, las velocidades de cálculo también se mejoran al trabajar con un conjunto de datos más pequeño. Hay varias maneras de seleccionar columnas de trama de datos, incluyendo locilocfilter, etc. Este post cubre todos los métodos con ejemplos.

Crear un conjunto de datos de muestra

Comience creando un marco de datos de pandas simple como se muestra a continuación. Tiene 6 columnas.

# Import packages
import pandas as pd

# Create a dataframe
df = pd.DataFrame(
                   data= [['HR', 'Orange', 'Wheat', 30, 165, 4.6],
                        ['DL', 'Purple', 'Flour', 2, 70, 8.3],
                        ['MH', 'Red', 'Mango', 12, 120, 9.0],
                        ['AS', 'Black', 'Apple', 4, 80, 3.3],
                        ['GJ', 'Blue', 'Milk', 32, 180, 1.8],
                        ['KL', 'Green', 'Melon', 33, 172, 9.5],
                        ['PB', 'Magenta', 'Beans', 69, 150, 2.2]],

                    columns=['State', 'Color', 'Food', 'Average Age', 'Average Height', 'Score']
                )
df

Crear marco de datos

1. Selección de columna básica

Una de las formas más básicas en pandas para seleccionar columnas del marco de datos es pasando la lista de columnas al operador de indexación del objeto del marco de datos.

# Selecting columns by passing a list of desired columns
df[['Color', 'Score']]

Columna de selección de pandas

2. Selección de columnas mediante la lista de columnas

El dataframe_name.columnsdevuelve la lista de todas las columnas de la trama de datos. Puede usar esto como una de las formas de acceder a múltiples columnas en pandas. Debe pasar la lista modificada de columnas en el operador de indexación del marco de datos. Este método se puede realizar de dos formas:

A. Pasando la lista de columnas divididas

Puede dividir los nombres de columna deseados de la lista de todas las columnas devueltas dataframe_name.columnspor indexación.

# slicing df.columns to access the last two columns of the dataframe

df[df.columns[-2:]]
Pandas Seleccionar columna usando la lista

B. Búsqueda de columnas deseadas usando isin()

La función se usa para verificar si un elemento está presente en la lista. Aquí, la función se puede usar para verificar si el nombre de una columna está presente en la lista, así como la lista de columnas deseadas. Esto ayuda a seleccionar varias columnas de la lista de todas las columnas. La función devuelve una matriz booleana, donde valor denota que el nombre de la columna estaba presente en ambas listas.isin(), the isin()df.columnsTrue

# Creating the boolean mask
booleanMask = df.columns.isin(['State', 'Food'])

# saving the selected columns 
selectedCols = df.columns[booleanMask]

# selecting the desired columns
df[selectedCols]

Pandas Seleccionar columna usando la función isin

3. Usar .loc y .iloc para seleccionar columnas por nombre o posición

Los pandas y los indexadores se pueden usar con el marco de datos de pandas para reducir un marco de datos grande en un marco de datos pequeño. Estos indexadores se pueden utilizar para la selección de filas y columnas..loc.iloc

A. Usando indexador.loc

The loctoma nombres de columnas o listas de columnas y devuelve una fila o un marco de datos. El indexador toma tanto filas como columnas. Por lo tanto, para seleccionar solo columnas del marco de datos, puede dejar la división de filas como :, lo que seleccionará todas las filas del marco de datos.

Para la división de columnas, puede pasar el nombre de la columna que se seleccionará.

# .loc single column selection 
df.loc[:, 'Food' ]

0    Wheat
1    Flour
2    Mango
3    Apple
4     Milk
5    Melon
6    Beans
Name: Food, dtype: object

Si desea seleccionar varias columnas, pase una lista de columnas.

# .loc multiple column selection
df.loc[:, ['State', 'Food'] ]
Pandas Seleccionar columna usando loc

B. Usando indexador.iloc

The ilocindexador es similar al indexador. La única diferencia es que toma índices de columnas o índices de listas de columnas y devuelve una fila o marco de datos..lociloc

Aquí también, puede dejar la división de filas :para seleccionar todas las filas y para la división de columnas, puede pasar los índices de la columna o una lista de índices de las columnas que se seleccionarán.

# .iloc single column selection 
df.iloc[:, 0]

0    HR
1    DL
2    MH
3    AS
4    GJ
5    KL
6    PB
Name: State, dtype: object

# .iloc single column selection 
df.iloc[:, [0, 2, 3] ]
Pandas Seleccionar columna usando iloc

4. Usar métodos de filtro para seleccionar columnas que contienen ciertas palabras.

Puede usar la función de filtro del marco de datos de pandas para seleccionar columnas que contengan una cadena específica en los nombres de las columnas.

El parámetro likede la función define esta cadena específica. Si el nombre de una columna contiene la cadena especificada, esa columna se seleccionará y se devolverá el marco de datos..filter

# selecting columns where column name contains 'Average' string
df.filter(like='Average')

5. Pandas Selecciona columnas según su tipo de datos.

El marco de datos de Pandas tiene la función select_dtypes, que tiene un includeparámetro. Especifique el tipo de datos de las columnas que desea seleccionar usando este parámetro. Esto puede resultarle útil si desea seleccionar solo columnas de tipos de datos específicos del marco de datos.

# selecting integer valued columns
df.select_dtypes(include=['int64'])
Selección basada en el tipo de datos

Consejos prácticos

  • También puede seleccionar columnas basadas en reglas de expresiones regulares. Utilice el regexparámetro en la función de filtro para obtener columnas donde los nombres de columna se evalúan como Verdadero en una expresión dada.
  • La selección de columnas según los tipos de datos puede resultar útil al realizar un análisis exploratorio de datos. Tendrá diferentes tipos de datos separados, lo que eliminará el proceso de crear manualmente la lista de columnas para diferentes tipos de datos.

Instalar Pandas en Ubuntu


pandas y Ubuntu 20.04

Pandas es un marco de código abierto rápido, eficiente, modular y fácil de usar para el análisis y la manipulación de datos. Está diseñado sobre el lenguaje de programación Python y, por lo tanto, Pandas es pitónico.

Ubuntu 20.04 , cuyo nombre en código es Focal Fossa, es la versión más reciente de Ubuntu LTS. Saldrá en abril de 2020 y tendrá soporte durante 5 años hasta abril de 2025 (soporte estándar).

Ubuntu 20.04 también incluye Python 3 de forma predeterminada, por lo que no es necesario instalarlo también. Este es un movimiento de mandato porque la Fundación Python ya anunció  el EOL de Python 2 , que es el 1 de enero de 2020. En las versiones anteriores de Ubuntu, Python 2 es el predeterminado y escribir  python en la terminal lo llevará a Python 2, lo que significa tenemos que escribir  python3 para usar Python 3. Ahora puede instalar  python-is-python3, que se establecerá  python en  python3.

Método 1: instalar el python3-pandaspaquete del sistema

El primer método es instalar el paquete del sistema python3-pandas en Ubuntu 20.04. La versión puede estar desactualizada, pero generalmente viene con menos errores que pueden introducirse en versiones posteriores. Aquí está la información sobre python3-pandas:

Paquete: python3-pandas
Versión: 0.25.3 + dfsg-7
Prioridad: opcional
Sección: universe / python
Fuente: pandas
Origen: Ubuntu
Mantenedor: Desarrolladores de Ubuntu [email protected] Mantenedor 
original: Debian Science Team [email protected]
Errores: https://bugs.launchpad.net/ubuntu/+filebug
Tamaño instalado: 14.3 MB
Depende: python3 (<< 3.9), python3 (> = 3.8 ~), python3-dateutil, python3-numpy (> = 1: 1.15 ~), python3-tz, python3: any, python3-pandas-lib (> = 0.25 .3 + dfsg-7), python3-pkg-resources, python3-six
Recomienda: python3-scipy, python3-matplotlib, python3-numexpr, python3-tables, python3-xlrd, python3-openpyxl, python3-xlwt, python3-bs4, python3-html5lib, python3-lxml
Sugiere: python-pandas-doc, python3-statsmodels
Descansos: cnvkit (<< 0.9.6-1.1), python3-feather-format (<< 0.3.1 + dfsg1-2.1), python3-skbio (<< 0.5.5-2.1), python3-statsmodels (<< 0.10 .0 ~), tipos q2 (<< 2019.7.0-1.1)
Inicio: https://pandas.pydata.org/
Tamaño de descarga: 1,968 kB
Fuentes APT: http://archive.ubuntu.com/ubuntu focal / universe paquetes amd64
Descripción: estructuras de datos para datos "relacionales" o "etiquetados"
pandas es un paquete de Python que proporciona rapidez, flexibilidad y expresividad
estructuras de datos diseñadas para trabajar con "relacionales" o
datos "etiquetados" fáciles e intuitivos. Pretende ser el fundamental
bloque de construcción de alto nivel para hacer datos prácticos del mundo real
análisis en Python. pandas es adecuado para muchos tipos diferentes de
datos:
.
Datos tabulares con columnas de tipos heterogéneos, como en un SQL
tabla o hoja de cálculo de Excel
Tiempo ordenado y desordenado (no necesariamente de frecuencia fija)
datos de la serie.
Datos matriciales arbitrarios (homogéneamente tipificados o heterogéneos) con
etiquetas de fila y columna
Cualquier otra forma de conjuntos de datos observacionales / estadísticos. Los datos
en realidad, no es necesario etiquetarlo en absoluto para colocarlo en un pandas
estructura de datos
.
Este paquete contiene la versión Python 3.

Para instalar el paquete, ejecute el siguiente apt installcomando y presione «Y» para continuar con la instalación:

sudo apt install python3-pandas

Verá la salida como la captura de pantalla a continuación (todo el texto se pega después de la captura de pantalla). Python 3 y numpy también se instalarán si no lo tiene en su sistema. Como puede ver a continuación, ocupará 100 MB de su disco.

Ejecutando sudo apt install python3 pandas en Ubuntu 20.04

Instale el paquete de documentación de panda: python-pandas-doc

Una vez que haya instalado pandas, se recomienda instalar también el paquete de documentación python-pandas-doc. De esta manera, puede acceder fácilmente a la documentación de panda sin conexión sin tener que ir al sitio web de pandas cada vez.

Para instalarlo, ejecute el siguiente comando:

sudo apt install python-pandas-doc

Verá la salida como la captura de pantalla a continuación. Presione ‘Y’ para continuar.

Instalación de python pandas doc en Ubuntu 20.04 por sudo apt install python pandas doc
vh @ varhowto-com: ~ $ sudo apt install python-pandas-doc
Leyendo listas de paquetes… Listo
Construyendo árbol de dependencia
Leyendo información de estado… Listo
Se instalarán los siguientes paquetes adicionales:
fuentes-mathjax libjs-mathjax libjs-requirejs
Paquetes sugeridos:
fuentes-mathjax-extras fonts-stix libjs-mathjax-doc
Se instalarán los siguientes paquetes NUEVOS:
fuentes-mathjax libjs-mathjax libjs-requirejs
python-pandas-doc
0 actualizado, 4 recién instalados, 0 para eliminar y 49 sin actualizar.
Necesita obtener 14,8 MB de archivos.
Después de esta operación, se utilizarán 105 MB de espacio adicional en disco.
¿Quieres continuar? [Y / n] años
Obtenga: 1 http://archive.ubuntu.com/ubuntu focal / universe amd64 fonts-mathjax all 2.7.4 + dfsg-1 [2,208 kB]
Obtener: 2 http://archive.ubuntu.com/ubuntu focal / universe amd64 libjs-requirejs all 2.3.6-1 [29.9 kB]
Obtener: 3 http://archive.ubuntu.com/ubuntu focal / universe amd64 libjs-mathjax all 2.7.4 + dfsg-1 [5,654 kB]
Obtenga: 4 http://archive.ubuntu.com/ubuntu focal / universe amd64 python-pandas-doc all 0.25.3 + dfsg-7 [6,939 kB]
Obtenido 14,8 MB en 2 s (6886 kB / s)
Seleccionando el paquete fonts-mathjax previamente no seleccionado.
(Leyendo la base de datos… 287140 archivos y directorios instalados actualmente).
Preparándose para descomprimir… / fonts-mathjax_2.7.4 + dfsg-1_all.deb…
Desempaquetando fonts-mathjax (2.7.4 + dfsg-1)…
Seleccionando el paquete libjs-requirejs previamente no seleccionado.
Preparándose para descomprimir… / libjs-requirejs_2.3.6-1_all.deb…
Desempaquetando libjs-requirejs (2.3.6-1)…
Seleccionando el paquete libjs-mathjax no seleccionado previamente.
Preparándose para descomprimir… / libjs-mathjax_2.7.4 + dfsg-1_all.deb…
Desempaquetando libjs-mathjax (2.7.4 + dfsg-1)…
Seleccionando el paquete python-pandas-doc previamente no seleccionado.
Preparándose para descomprimir… / python-pandas-doc_0.25.3 + dfsg-7_all.deb…
Desempaquetando python-pandas-doc (0.25.3 + dfsg-7)…
Configurando fonts-mathjax (2.7.4 + dfsg-1)…
Configurando libjs-mathjax (2.7.4 + dfsg-1)…
Configurando libjs-requirejs (2.3.6-1)…
Configurando python-pandas-doc (0.25.3 + dfsg-7)…
Procesando activadores para fontconfig (2.13.1-2ubuntu3)…

Luego, puede hacer clic en este enlace o copiarlo en su navegador para ver la documentación de panda: /usr/share/doc/python-pandas-doc/html/index.html .

documentación de pandas en Ubuntu 20.04

Método 2: instalar pandas con pip en Ubuntu 20.04

A veces, se prefiere usar un administrador de paquetes de Python para instalar pandas, especialmente si desea usar la última versión de pandas. pandas se ha actualizado recientemente a la versión 1 y el del repositorio oficial de Ubuntu 20.04 sigue siendo 0.25.3.

Paso 1: instalar pip3(y Python3)

Hay dos administradores de paquetes de Python principales. El primero es el oficial llamado Pip, y otro es Conda (Anaconda o Miniconda). En caso de duda o para principiantes, el oficialpip se recomienda .

Instale Python 3 y pip para pandas

Pipes el administrador de paquetes nativo de Python. Lo usaremos para instalar pandas. Para instalar pip3, ejecute el siguiente comando. Porque pip3depende de Python 3, python3también se instalará si no está en su sistema Ubuntu 20.04.

sudo apt install python3-pip

Verá un resultado similar a la captura de pantalla a continuación. Presione «Y» para continuar. Como puede notar, también se instalará python-pip-whlpython3-wheel, donde wheel es el formato de paquete integrado para Python.

Instalar pip 3 para PyTorch

Aquí está el resultado completo de la pip3instalación

vh @ varhowto-com: ~ $ sudo apt install python3-pip
Leyendo listas de paquetes… Listo
Construyendo árbol de dependencia
Leyendo información de estado… Listo
Se instalarán los siguientes paquetes adicionales:
python-pip-whl python3-rueda
Se instalarán los siguientes paquetes NUEVOS:
python-pip-whl python3-pip python3-rueda
0 actualizado, 3 recién instalados, 0 para eliminar y 49 sin actualizar.
Necesita obtener 2.053 kB de archivos.
Después de esta operación, se utilizarán 3455 kB de espacio adicional en disco.
¿Quieres continuar? [Y / n] años
Obtenga: 1 http://archive.ubuntu.com/ubuntu focal / universe amd64 python-pip-whl all 20.0.2-5ubuntu1 [1,799 kB]
Obtener: 2 http://archive.ubuntu.com/ubuntu focal / universe amd64 python3-wheel all 0.34.2-1 [23.8 kB]
Obtener: 3 http://archive.ubuntu.com/ubuntu focal / universe amd64 python3-pip all 20.0.2-5ubuntu1 [230 kB]
Obtenido 2053 kB en 1 s (2104 kB / s)
Seleccionar el paquete python-pip-w previamente no seleccionado
hl.
(Leyendo la base de datos… 273191 archivos y directorios c
instalado actualmente.)
Preparándose para descomprimir… / python-pip-whl_20.0.2-5ubunt
u1_all.deb…
Desempaquetando python-pip-whl (20.0.2-5ubuntu1)…
Seleccionar el paquete python3-whee previamente no seleccionado
l.
Preparándose para descomprimir… / python3-wheel_0.34.2-1_all.d
eb ...
Desembalaje de python3-wheel (0.34.2-1)…
Seleccionando el paquete python3-pip no seleccionado previamente.
Preparándose para descomprimir… / python3-pip_20.0.2-5ubuntu1_
all.deb…
Desempaquetando python3-pip (20.0.2-5ubuntu1)…
Configurando python3-wheel (0.34.2-1)…
Configurando python-pip-whl (20.0.2-5ubuntu1)…
Configurando python3-pip (20.0.2-5ubuntu1)…
Procesando activadores para man-db (2.9.1-1)…

[Alternativa] Instalar Conda (Anoconda / Miniconda) para pandas

Paso 2: instalar pandausando pip

Para instalar pandasdesde PyPI (pip), ejecute el siguiente comando:

pip3 install pandas

Tenga en cuenta que si ha instalado pandas usando el primer método, necesitará desinstalar pandas usando sudo apt remove python3-pandas, de lo contrario verá el resultado: «Requisito ya satisfecho: pandas en / usr / lib / python3 / dist-packages (0.25.3)» , lo que significa que no está obteniendo la última versión de pandas.

la última versión de pandas no está instalada si está instalada la versión del sistema

Verá la siguiente salida en su terminal. Como puede ver en la última línea, acabo de instalar pandas 1.1.0 en mi sistema operativo Ubuntu 20.04. Su número de versión puede ser mayor a medida que pandas lance nuevas versiones.

Recolectando pandas
Descargando pandas-1.1.0-cp38-cp38-manylinux1_x86_64.whl (10,3 MB)
Requisito ya satisfecho: python-dateutil> = 2.7.3 en / usr / lib / python3 / dist-packages (de pandas) (2.7.3)
Requisito ya satisfecho: pytz> = 2017.2 en / usr / lib / python3 / dist-packages (de pandas) (2019.3)
Requisito ya satisfecho: numpy> = 1.15.4 en / usr / lib / python3 / dist-packages (de pandas) (1.17.4)
Instalación de paquetes recopilados: pandas
Pandas-1.1.0 instalado con éxito

Método 3: instalar pandas con conda (Miniconda / Anaconda)

Existe otra distribución popular de paquetes de Python llamada Anaconda o Miniconda. También puedes usarlo para instalar pandas.

Anteriormente hemos escrito un tutorial para instalar Miniconda , puedes leerlo si prefieres conda o simplemente quieres aprender más. Tenga en cuenta que conda distribuye Python en sí, por lo que no usará el sistema Python y no necesitará tener instalado Python antes de instalar conda.

Para instalar pandas con conda, ejecute el siguiente comando. Cuando se le pregunte “¿Continuar ([y] / n)?”, Presione y e ingrese para continuar.

conda install pandas

Verá el siguiente resultado:

Instalación de pandas usando conda en Ubuntu 20.04

Aquí está el resultado completo:

(base) vh @ varhowto-com: ~ $ conda instalar pandas
Recopilación de metadatos del paquete (current_repodata.json): hecho
Entorno de resolución: hecho

## Plan de paquete ##

  ubicación del entorno: / home / vh / miniconda3

  especificaciones agregadas / actualizadas:
    - pandas


Se descargarán los siguientes paquetes:

    paquete | construir
    --------------------------- | -----------------
    blas-1.0 | mkl 6 KB
    certificados-ca-2020.6.24 | 0 125 KB
    certifi-2020.6.20 | py38_0 156 KB
    intel-openmp-2020.1 | 217780 KB
    mkl-2020.1 | 217129,0 MB
    mkl-service-2.3.0 | py38he904b0f_0 62 KB
    mkl_fft-1.1.0 | py38h23d657b_0 150 KB
    mkl_random-1.1.1 | py38h0573a6f_0 341 KB
    numpy-1.19.1 | py38hbc911f0_0 21 KB
    numpy-base-1.19.1 | py38hfa32c7d_0 4.2 MB
    pandas-1.1.0 | py38he6710b0_0 8.4 MB
    python-dateutil-2.8.1 | py_0 215 KB
    pytz-2020.1 | py_0 184 KB
    -------------------------------------------------- ----------
                                           Total: 143,6 MB

Se INSTALARÁN los siguientes paquetes NUEVOS:

  blas pkgs / main / linux-64 :: blas-1.0-mkl
  intel-openmp pkgs / main / linux-64 :: intel-openmp-2020.1-217
  mkl pkgs / main / linux-64 :: mkl-2020.1-217
  mkl-service pkgs / main / linux-64 :: mkl-service-2.3.0-py38he904b0f_0
  mkl_fft pkgs / main / linux-64 :: mkl_fft-1.1.0-py38h23d657b_0
  mkl_random pkgs / main / linux-64 :: mkl_random-1.1.1-py38h0573a6f_0
  numpy pkgs / main / linux-64 :: numpy-1.19.1-py38hbc911f0_0
  numpy-base pkgs / main / linux-64 :: numpy-base-1.19.1-py38hfa32c7d_0
  pandas pkgs / main / linux-64 :: pandas-1.1.0-py38he6710b0_0
  python-dateutil pkgs / main / noarch :: python-dateutil-2.8.1-py_0
  pytz pkgs / main / noarch :: pytz-2020.1-py_0

Los siguientes paquetes serán ACTUALIZADOS:

  certificados ca 2020.1.1-0 -> 2020.6.24-0
  certifi 2020.4.5.1-py38_0 -> 2020.6.20-py38_0


Continuar ([y] / n)? y


Descarga y extracción de paquetes
blas-1.0 | 6 KB | #################################### | 100%
mkl-2020.1 | 129,0 MB | #################################### | 100%
pytz-2020.1 | 184 KB | #################################### | 100%
intel-openmp-2020.1 | 780 KB | #################################### | 100%
mkl-service-2.3.0 | 62 KB | #################################### | 100%
certificados-ca-2020 | 125 KB | #################################### | 100%
mkl_random-1.1.1 | 341 KB | #################################### | 100%
numpy-base-1.19.1 | 4,2 MB | #################################### | 100%
python-dateutil-2.8. | 215 KB | #################################### | 100%
pandas-1.1.0 | 8,4 MB | #################################### | 100%
mkl_fft-1.1.0 | 150 KB | #################################### | 100%
certifi-2020.6.20 | 156 KB | #################################### | 100%
numpy-1.19.1 | 21 KB | #################################### | 100%
Preparando transacción: hecho
Verificando transacción: hecho
Ejecutando transacción: hecho

Verificar la instalación de pandas

Ahora tienes pandas instalados en tu computadora con Ubuntu 20.04. ¿Cómo comprobar si los pandas están instalados correctamente? Ejecute python3y copie / pegue el siguiente código.

import pandas as pd
s = pd.Series([1, 6, 8, 10])
s

Debería poder ver algo similar a la siguiente captura de pantalla:

Comprobando si pandas está instalado en Ubuntu 20.04

Aquí está el texto completo de la terminal:

(base) vh @ varhowto-com: ~ $ python3
Python 3.8.3 (predeterminado, 19 de mayo de 2020, 18:47:26)
[GCC 7.3.0] :: Anaconda, Inc. en Linux
Escriba "ayuda", "derechos de autor", "créditos" o "licencia" para obtener más información.
importar pandas como pd
s = pd.Series ([1, 6, 8, 10])
s
0 1
dieciséis
2 8
3 10
dtype: int64

Instalar Pandas en windows


Resultado de imagen de pandas python

Pandas es una biblioteca de Python de código abierto que proporciona una herramienta de análisis y manipulación de datos de alto rendimiento utilizando sus poderosas estructuras de datos. El nombre Pandas se deriva de la palabra Panel Data, una econometría de datos multidimensionales.

En 2008, el desarrollador Wes McKinney comenzó a desarrollar pandas cuando necesitaba una herramienta flexible y de alto rendimiento para el análisis de datos.

Antes de Pandas, Python se usaba principalmente para la preparación y el procesamiento de datos. Contribuyó muy poco al análisis de datos. Pandas resolvió este problema. Con Pandas, podemos lograr cinco pasos típicos en el procesamiento y análisis de datos, independientemente del origen de los datos: cargar, preparar, manipular, modelar y analizar.

Python con Pandas se utiliza en una amplia gama de campos, incluidos los dominios académicos y comerciales, que incluyen finanzas, economía, estadísticas, análisis, etc.

Características clave de Pandas

  • Objeto DataFrame rápido y eficiente con indexación predeterminada y personalizada.
  • Herramientas para cargar datos en objetos de datos en memoria desde diferentes formatos de archivo.
  • Alineación de datos y manejo integrado de datos faltantes.
  • Remodelación y rotación de conjuntos de fechas.
  • División, indexación y subconjunto basado en etiquetas de grandes conjuntos de datos.
  • Las columnas de una estructura de datos se pueden eliminar o insertar.
  • Agrupar por datos para agregación y transformaciones.
  • Fusión y unión de datos de alto rendimiento.
  • Funcionalidad de series temporales.

1.1 ¿Cómo instalar pandas usando pip?

Si está utilizando la última versión de Pandas, ya tendrá pip instalado en su sistema. Por lo tanto, no es necesario que siga del paso 1 al 5. Para los usuarios que no tienen la última versión de Python (3.7.3), deben actualizarla.

Paso 1

Primero diríjase a https://www.python.org y haga clic en Descargas en la barra de navegación desde el

Paso 2

Asegúrese de descargar la última versión de Python . Versión 3.9.7, en este caso.

Paso 3

Al ejecutar el instalador descargado, obtendrá una nueva ventana. Haga clic en ‘ Instalar ahora ‘.

Paso 4

Después de finalizar la instalación, se recomienda elegir la opción para deshabilitar la longitud de la ruta para evitar problemas con la instalación de Python.

Paso 5

Ahora que Python está instalado, debe dirigirse a nuestra terminal o símbolo del sistema desde donde puede instalar Pandas. Así que vaya a la barra de búsqueda en su escritorio y busque cmd . Debería aparecer una aplicación llamada Símbolo del sistema . Haga clic para iniciarlo.

Menú Inicio: buscar cmd

Paso 6

Escriba el comando » administrador de instalación de pip «. Pip es un administrador de instalación de paquetes para Python y se instala junto con las nuevas distribuciones de Python.

Paso 7

Espere a que finalicen las descargas y, una vez que haya terminado, podrá ejecutar Pandas dentro de sus programas Python en Windows.

1.2. ¿Cómo instalar pandas usando Anaconda?

Se recomienda encarecidamente que los principiantes utilicen Anaconda para instalar Pandas en su sistema. Instalar Anaconda no solo es muy fácil, sino que también le brinda acceso a varias otras herramientas.

Paso 1

Dirígete a https://www.anaconda.com , una vez que estés allí, haz clic en el botón Descargar en la esquina superior derecha de la pantalla.

Paso 2

En la página de descargas, desplácese hacia abajo hasta que vea las opciones de descarga para Windows. Haga clic en el botón de descarga de Python 3.7 . Esto iniciará una descarga para el instalador de anaconda .

Paso 3

Siga las instrucciones de instalación que se muestran en las siguientes imágenes. Elija cualquier carpeta de destino según su gusto y desmarque » Agregar anaconda a mi variable de entorno PATH «.

Paso 4

Una vez finalizada la instalación, puede tener acceso a Pandas en su sistema . Anaconda instala todas las bibliotecas importantes para usted.

Paso 5

Cuaderno de Jupyter (opcional) : la mayoría de los proyectos de aprendizaje automático se tratan en los cuadernos de jupyter, por lo tanto, es importante saber cómo usarlo.

Primero, vaya a sus archivos de programa en el menú de inicio y busque “Anaconda Navigator”. Una vez que ingrese al programa, será recibido con una pantalla similar a la que se muestra a continuación. Inicie Jupyter Notebooks .

Paso 6

Una vez que haga clic en Iniciar para Jupyter Notebook, se abrirá automáticamente una ventana del navegador y mostrará la siguiente página. Haga clic en nuevo y luego en «Python 3 «

Interfaz de portátil Jupyter

Paso 7

Una vez que elija «python 3», lo llevará a una nueva pestaña, donde puede comenzar a codificar de una vez .

Keras


El aprendizaje profundo es uno de los principales subcampos del marco de aprendizaje automático. El aprendizaje automático es el estudio del diseño de algoritmos, inspirado en el modelo del cerebro humano. El aprendizaje profundo se está volviendo más popular en los campos de la ciencia de datos como la robótica, la inteligencia artificial (IA), el reconocimiento de audio y video y el reconocimiento de imágenes. La red neuronal artificial es el núcleo de las metodologías de aprendizaje profundo. El aprendizaje profundo es compatible con varias bibliotecas como Theano, TensorFlow, Caffe, Mxnet, etc., Keras es una de las bibliotecas de Python más potentes y fáciles de usar, que se basa en bibliotecas populares de aprendizaje profundo como TensorFlow, Theano, etc. , para crear modelos de aprendizaje profundo.

Descripción general de Keras

Keras se ejecuta sobre bibliotecas de máquinas de código abierto como TensorFlow, Theano o Cognitive Toolkit (CNTK). Theano es una biblioteca de Python que se utiliza para tareas de cálculo numérico rápido. TensorFlow es la biblioteca matemática simbólica más famosa que se utiliza para crear redes neuronales y modelos de aprendizaje profundo. TensorFlow es muy flexible y el beneficio principal es la computación distribuida. CNTK es un marco de aprendizaje profundo desarrollado por Microsoft. Utiliza bibliotecas como Python, C #, C ++ o kits de herramientas de aprendizaje automático independientes. Theano y TensorFlow son bibliotecas muy poderosas pero difíciles de entender para crear redes neuronales.

Keras se basa en una estructura mínima que proporciona una forma limpia y sencilla de crear modelos de aprendizaje profundo basados ​​en TensorFlow o Theano. Keras está diseñado para definir rápidamente modelos de aprendizaje profundo. Bueno, Keras es una opción óptima para aplicaciones de aprendizaje profundo.

Características

Keras aprovecha varias técnicas de optimización para hacer que la API de red neuronal de alto nivel sea más fácil y más eficiente. Es compatible con las siguientes funciones:

  • API consistente, simple y extensible.
  • Estructura mínima: fácil de lograr el resultado sin lujos.
  • Es compatible con múltiples plataformas y backends.
  • Es un marco fácil de usar que se ejecuta tanto en CPU como en GPU.
  • Gran escalabilidad de la computación.

Beneficios

Keras es un marco muy potente y dinámico y ofrece las siguientes ventajas:

  • Mayor apoyo de la comunidad.
  • Fácil de probar.
  • Las redes neuronales de Keras están escritas en Python, lo que simplifica las cosas.
  • Keras admite redes convolucionales y recurrentes.
  • Los modelos de aprendizaje profundo son componentes discretos, por lo que puede combinarlos de muchas formas.

mo instalar Keras en su máquina. Antes de pasar a la instalación, repasemos los requisitos básicos de Keras.

Prerrequisitos

Debe cumplir los siguientes requisitos:

  • Cualquier tipo de sistema operativo (Windows, Linux o Mac)
  • Python versión 3.5 o superior.

Python

Keras es una biblioteca de redes neuronales basada en Python, por lo que Python debe estar instalado en su máquina. Si python está instalado correctamente en su máquina, abra su terminal y escriba python, podría ver la respuesta similar a la que se especifica a continuación,

Python 3.6.5 (v3.6.5:f59c0932b4, Mar 28 2018, 17:00:18) 
[MSC v.1900 64 bit (AMD64)] on win32 
Type "help", "copyright", "credits" or "license" for more information. 
>>>

A partir de ahora, la última versión es ‘3.7.2’. Si Python no está instalado, visite el enlace oficial de Python – www.python.org y descargue la última versión basada en su sistema operativo e instálela inmediatamente en su sistema.

Pasos de instalación de Keras

La instalación de Keras es bastante sencilla. Siga los pasos a continuación para instalar correctamente Keras en su sistema.

Paso 1: Crear un entorno virtual

Virtualenv se utiliza para administrar paquetes de Python para diferentes proyectos. Esto será útil para evitar romper los paquetes instalados en los otros entornos. Por lo tanto, siempre se recomienda utilizar un entorno virtual al desarrollar aplicaciones Python.

Linux / Mac OS

Usuarios de Linux o Mac OS, vaya al directorio raíz de su proyecto y escriba el siguiente comando para crear un entorno virtual,

python3 -m venv kerasenv

Después de ejecutar el comando anterior, se crea el directorio «kerasenv» con bin, lib e incluye carpetas en su ubicación de instalación.

Ventanas

El usuario de Windows puede usar el siguiente comando,

py -m venv keras

Paso 2: Activa el medio

Este paso configurará los ejecutables de python y pip en su ruta de shell.

Linux / Mac OS

Ahora hemos creado un entorno virtual llamado «kerasvenv». Vaya a la carpeta y escriba el siguiente comando,

$ cd kerasvenv kerasvenv $ source bin/activate

Ventanas

Los usuarios de Windows se mueven dentro de la carpeta «kerasenv» y escriben el siguiente comando,

.\env\Scripts\activate

Paso 3: Bibliotecas de Python

Keras depende de las siguientes bibliotecas de Python.

  • Numpy
  • Pandas
  • Scikit-aprender
  • Matplotlib
  • Scipy
  • Seaborn

Con suerte, ha instalado todas las bibliotecas anteriores en su sistema. Si estas bibliotecas no están instaladas, utilice el siguiente comando para instalarlas una por una.

numpy

pip install numpy

podrías ver la siguiente respuesta,

Collecting numpy 
   Downloading 
https://files.pythonhosted.org/packages/cf/a4/d5387a74204542a60ad1baa84cd2d3353c330e59be8cf2d47c0b11d3cde8/ 
   numpy-3.1.1-cp36-cp36m-macosx_10_6_intel.
macosx_10_9_intel.macosx_10_9_x86_64. 
   macosx_10_10_intel.macosx_10_10_x86_64.whl (14.4MB) 
      |████████████████████████████████| 14.4MB 2.8MB/s

pandas

pip install pandas

Pudimos ver la siguiente respuesta,

Collecting pandas 
   Downloading 
https://files.pythonhosted.org/packages/cf/a4/d5387a74204542a60ad1baa84cd2d3353c330e59be8cf2d47c0b11d3cde8/ 
pandas-3.1.1-cp36-cp36m-macosx_10_6_intel.
macosx_10_9_intel.macosx_10_9_x86_64. 
   macosx_10_10_intel.macosx_10_10_x86_64.whl (14.4MB) 
      |████████████████████████████████| 14.4MB 2.8MB/s

matplotlib

pip install matplotlib

Pudimos ver la siguiente respuesta,

Collecting matplotlib 
   Downloading 
https://files.pythonhosted.org/packages/cf/a4/d5387a74204542a60ad1baa84cd2d3353c330e59be8cf2d47c0b11d3cde8/ 
matplotlib-3.1.1-cp36-cp36m-macosx_10_6_intel.
macosx_10_9_intel.macosx_10_9_x86_64. 
   macosx_10_10_intel.macosx_10_10_x86_64.whl (14.4MB) 
      |████████████████████████████████| 14.4MB 2.8MB/s

scipy

pip install scipy

Pudimos ver la siguiente respuesta,

Collecting scipy 
   Downloading 
https://files.pythonhosted.org/packages/cf/a4/d5387a74204542a60ad1baa84cd2d3353c330e59be8cf2d47c0b11d3cde8 
/scipy-3.1.1-cp36-cp36m-macosx_10_6_intel.
macosx_10_9_intel.macosx_10_9_x86_64. 
   macosx_10_10_intel.macosx_10_10_x86_64.whl (14.4MB) 
      |████████████████████████████████| 14.4MB 2.8MB/s

scikit-learn

Es una biblioteca de aprendizaje automático de código abierto. Se utiliza para algoritmos de clasificación, regresión y agrupamiento. Antes de pasar a la instalación, se requiere lo siguiente:

  • Python versión 3.5 o superior
  • NumPy versión 1.11.0 o superior
  • SciPy versión 0.17.0 o superior
  • joblib 0.11 o superior.

Ahora, instalamos scikit-learn usando el siguiente comando:

pip install -U scikit-learn

Seaborn

Seaborn es una biblioteca increíble que le permite visualizar fácilmente sus datos. Utilice el siguiente comando para instalar:

pip install seaborn

Puede ver un mensaje similar al que se especifica a continuación:

Collecting seaborn 
   Downloading 
https://files.pythonhosted.org/packages/a8/76/220ba4420459d9c4c9c9587c6ce607bf56c25b3d3d2de62056efe482dadc 
/seaborn-0.9.0-py3-none-any.whl (208kB) 100% 
   |████████████████████████████████| 215kB 4.0MB/s 
Requirement already satisfied: numpy> = 1.9.3 in 
./lib/python3.7/site-packages (from seaborn) (1.17.0) 
Collecting pandas> = 0.15.2 (from seaborn) 
   Downloading 
https://files.pythonhosted.org/packages/39/b7/441375a152f3f9929ff8bc2915218ff1a063a59d7137ae0546db616749f9/ 
pandas-0.25.0-cp37-cp37m-macosx_10_9_x86_64.
macosx_10_10_x86_64.whl (10.1MB) 100% 
   |████████████████████████████████| 10.1MB 1.8MB/s 
Requirement already satisfied: scipy>=0.14.0 in 
./lib/python3.7/site-packages (from seaborn) (1.3.0) 
Collecting matplotlib> = 1.4.3 (from seaborn) 
   Downloading 
https://files.pythonhosted.org/packages/c3/8b/af9e0984f
5c0df06d3fab0bf396eb09cbf05f8452de4e9502b182f59c33b/ 
matplotlib-3.1.1-cp37-cp37m-macosx_10_6_intel.
macosx_10_9_intel.macosx_10_9_x86_64 
.macosx_10_10_intel.macosx_10_10_x86_64.whl (14.4MB) 100% 
   |████████████████████████████████| 14.4MB 1.4MB/s 
...................................... 
...................................... 
Successfully installed cycler-0.10.0 kiwisolver-1.1.0 
matplotlib-3.1.1 pandas-0.25.0 pyparsing-2.4.2 
python-dateutil-2.8.0 pytz-2019.2 seaborn-0.9.0

Instalación de Keras usando Python

A partir de ahora, hemos completado los requisitos básicos para la instalación de Kera. Ahora, instale Keras usando el mismo procedimiento que se especifica a continuación:

pip install keras

Salir del entorno virtual

Después de finalizar todos los cambios en su proyecto, simplemente ejecute el siguiente comando para salir del entorno:

deactivate

Nube Anaconda

Creemos que ha instalado anaconda cloud en su máquina. Si anaconda no está instalado, visite el enlace oficial, www.anaconda.com/distribution y elija descargar según su sistema operativo.

Crear un nuevo entorno de conda

Inicie el indicador de anaconda, esto abrirá el entorno base de Anaconda. Creemos un nuevo entorno de conda. Este proceso es similar al virtualenv. Escriba el siguiente comando en su terminal conda –

conda create --name PythonCPU

Si lo desea, también puede crear e instalar módulos utilizando GPU. En este tutorial, seguimos las instrucciones de la CPU.

Activar entorno conda

Para activar el entorno, use el siguiente comando:

activate PythonCPU

Instalar spyder

Spyder es un IDE para ejecutar aplicaciones de Python. Instalemos este IDE en nuestro entorno conda usando el siguiente comando:

conda install spyder

Instalar bibliotecas de Python

Ya conocemos las bibliotecas de python numpy, pandas, etc., necesarias para keras. Puede instalar todos los módulos utilizando la siguiente sintaxis:

Sintaxis

conda install -c anaconda <module-name>

Por ejemplo, desea instalar pandas:

conda install -c anaconda pandas

Como el mismo método, pruébelo usted mismo para instalar los módulos restantes.

Instalar Keras

Ahora, todo se ve bien, por lo que puede iniciar la instalación de keras usando el siguiente comando:

conda install -c anaconda keras

Lanzar spyder

Finalmente, inicie spyder en su terminal conda usando el siguiente comando:

spyder

Para asegurarse de que todo se instaló correctamente, importe todos los módulos, agregará todo y, si algo salió mal, obtendrá un mensaje de error de módulo no encontrado .

TensorFlow

TensorFlow es una biblioteca de aprendizaje automático de código abierto que se utiliza para tareas computacionales numéricas desarrolladas por Google. Keras es una API de alto nivel construida sobre TensorFlow o Theano. Ya sabemos cómo instalar TensorFlow usando pip.

Si no está instalado, puede instalar usando el siguiente comando:

pip install TensorFlow

Una vez que ejecutamos keras, podríamos ver que el archivo de configuración está ubicado en su directorio de inicio dentro y vamos a .keras / keras.json.

keras.json

{ 
   "image_data_format": "channels_last", 
   "epsilon": 1e-07, "floatx": "float32", "backend": "tensorflow" 
}

Aquí,

  • image_data_format representan el formato de datos.
  • épsilon representa una constante numérica. Se utiliza para evitar el error DivideByZero .
  • float x representa el tipo de datos predeterminado float32 . También puede cambiarlo a float16 o float64 usando el método set_floatx () .
  • image_data_format representan el formato de datos.

Supongamos que, si el archivo no se crea, muévase a la ubicación y cree siguiendo los pasos a continuación:

> cd home 
> mkdir .keras 
> vi keras.json

Recuerde, debe especificar .keras como su nombre de carpeta y agregar la configuración anterior dentro del archivo keras.json. Podemos realizar algunas operaciones predefinidas para conocer las funciones de backend.

Theano

Theano es una biblioteca de aprendizaje profundo de código abierto que le permite evaluar matrices multidimensionales de manera efectiva. Podemos instalar fácilmente usando el siguiente comando:

pip install theano

De forma predeterminada, keras usa el backend de TensorFlow. Si desea cambiar la configuración de backend de TensorFlow a Theano, simplemente cambie backend = theano en el archivo keras.json. Se describe a continuación:

keras.json

{ 
   "image_data_format": "channels_last", 
   "epsilon": 1e-07, 
   "floatx": "float32", 
   "backend": "theano" 
}

Ahora guarde su archivo, reinicie su terminal e inicie keras, se cambiará su backend.

>>> import keras as k 
using theano backend.

El aprendizaje profundo es un subcampo en evolución del aprendizaje automático. El aprendizaje profundo implica analizar la entrada capa por capa, donde cada capa extrae progresivamente información de nivel superior sobre la entrada.

Tomemos un escenario simple de analizar una imagen. Supongamos que su imagen de entrada está dividida en una cuadrícula rectangular de píxeles. Ahora, la primera capa abstrae los píxeles. La segunda capa comprende los bordes de la imagen. La siguiente capa construye nodos a partir de los bordes. Luego, el siguiente encontraría ramas de los nodos. Finalmente, la capa de salida detectará el objeto completo. Aquí, el proceso de extracción de características va desde la salida de una capa hasta la entrada de la siguiente capa subsiguiente.

Al utilizar este enfoque, podemos procesar una gran cantidad de funciones, lo que hace que el aprendizaje profundo sea una herramienta muy poderosa. Los algoritmos de aprendizaje profundo también son útiles para el análisis de datos no estructurados. Repasemos los conceptos básicos del aprendizaje profundo en este capítulo.

Redes neuronales artificiales

El enfoque más popular y principal del aprendizaje profundo es el uso de una «red neuronal artificial» (ANN). Están inspirados en el modelo del cerebro humano, que es el órgano más complejo de nuestro cuerpo. El cerebro humano está formado por más de 90 mil millones de células diminutas llamadas «neuronas». Las neuronas están interconectadas a través de fibras nerviosas llamadas «axones» y «dendritas». La función principal del axón es transmitir información de una neurona a otra a la que está conectada.

Del mismo modo, la función principal de las dendritas es recibir la información que transmiten los axones de otra neurona a la que están conectadas. Cada neurona procesa una pequeña información y luego pasa el resultado a otra neurona y este proceso continúa. Este es el método básico utilizado por nuestro cerebro humano para procesar una gran cantidad de información como el habla, visual, etc., y extraer información útil de ella.

Basado en este modelo, la primera Red Neural Artificial (ANN) fue inventada por el psicólogo Frank Rosenblatt , en el año de 1958. Las ANN están formadas por múltiples nodos que son similares a las neuronas. Los nodos están estrechamente interconectados y organizados en diferentes capas ocultas. La capa de entrada recibe los datos de entrada y los datos pasan por una o más capas ocultas secuencialmente y finalmente la capa de salida predice algo útil sobre los datos de entrada. Por ejemplo, la entrada puede ser una imagen y la salida puede ser la cosa identificada en la imagen, digamos un «gato».

Una sola neurona (llamada perceptrón en ANN) se puede representar como se muestra a continuación:

Redes neuronales artificiales

Aquí,

  • La entrada múltiple junto con el peso representan dendritas.
  • La suma de la entrada junto con la función de activación representa las neuronas. En realidad, la suma significa que el valor calculado de todas las entradas y la función de activación representan una función, que modifica el valor de la suma en 0, 1 o 0 a 1.
  • La salida real representa el axón y la salida será recibida por la neurona en la siguiente capa.

Entendamos los diferentes tipos de redes neuronales artificiales en esta sección.

Perceptrón multicapa

El perceptrón multicapa es la forma más simple de ANN. Consiste en una sola capa de entrada, una o más capas ocultas y finalmente una capa de salida. Una capa consta de una colección de perceptrón. La capa de entrada es básicamente una o más características de los datos de entrada. Cada capa oculta consta de una o más neuronas y procesa cierto aspecto de la característica y envía la información procesada a la siguiente capa oculta. El proceso de la capa de salida recibe los datos de la última capa oculta y finalmente genera el resultado.

Perceptrón multicapa

Red neuronal convolucional (CNN)

La red neuronal convolucional es una de las ANN más populares. Es ampliamente utilizado en los campos del reconocimiento de imágenes y video. Se basa en el concepto de convolución, un concepto matemático. Es casi similar al perceptrón multicapa, excepto que contiene una serie de capas de convolución y una capa de agrupación antes de la capa de neuronas ocultas completamente conectadas. Tiene tres capas importantes:

  • Capa de convolución : es el bloque de construcción principal y realiza tareas computacionales basadas en la función de convolución.
  • Capa de agrupación : se organiza junto a la capa de convolución y se utiliza para reducir el tamaño de las entradas eliminando información innecesaria para que el cálculo se pueda realizar más rápido.
  • Capa completamente conectada : se organiza junto a una serie de capas de convolución y agrupación y clasifica la entrada en varias categorías.

Una CNN simple se puede representar de la siguiente manera:

CNN

Aquí,

  • Se utilizan 2 series de capas de convolución y agrupación, que reciben y procesan la entrada (por ejemplo, la imagen).
  • Se utiliza una única capa completamente conectada y se utiliza para generar los datos (por ejemplo, clasificación de la imagen)

Red neuronal recurrente (RNN)

Las redes neuronales recurrentes (RNN) son útiles para abordar la falla en otros modelos de ANN. Bueno, la mayoría de la RNA no recuerda los pasos de situaciones anteriores y aprendió a tomar decisiones basadas en el contexto en el entrenamiento. Mientras tanto, RNN almacena la información pasada y todas sus decisiones se toman de lo que ha aprendido del pasado.

Este enfoque es principalmente útil en la clasificación de imágenes. A veces, es posible que necesitemos mirar hacia el futuro para arreglar el pasado. En este caso, la RNN bidireccional es útil para aprender del pasado y predecir el futuro. Por ejemplo, tenemos muestras escritas a mano en múltiples entradas. Supongamos que tenemos confusión en una entrada y luego tenemos que volver a comprobar otras entradas para reconocer el contexto correcto que toma la decisión del pasado.

Flujo de trabajo de ANN

Primero entendamos las diferentes fases del aprendizaje profundo y luego, aprendamos cómo Keras ayuda en el proceso de aprendizaje profundo.

Recopile los datos requeridos

El aprendizaje profundo requiere una gran cantidad de datos de entrada para aprender y predecir con éxito el resultado. Por lo tanto, primero recopile la mayor cantidad de datos posible.

Analizar datos

Analice los datos y adquiera una buena comprensión de los datos. Se requiere una mejor comprensión de los datos para seleccionar el algoritmo ANN correcto.

Elija un algoritmo (modelo)

Elija un algoritmo que se adapte mejor al tipo de proceso de aprendizaje (por ejemplo, clasificación de imágenes, procesamiento de texto, etc.) y los datos de entrada disponibles. El algoritmo está representado por Model en Keras. El algoritmo incluye una o más capas. Cada capa en ANN puede ser representada por Keras Layer en Keras.

  • Preparar datos : procese, filtre y seleccione solo la información requerida de los datos.
  • Dividir datos : divida los datos en conjuntos de datos de entrenamiento y de prueba. Los datos de prueba se utilizarán para evaluar la predicción del algoritmo / modelo (una vez que la máquina aprenda) y para verificar la eficiencia del proceso de aprendizaje.
  • Compile el modelo : compile el algoritmo / modelo, de modo que se pueda usar más para aprender mediante el entrenamiento y finalmente hacer la predicción. Este paso nos obliga a elegir la función de pérdida y el Optimizador. La función de pérdida y el Optimizador se utilizan en la fase de aprendizaje para encontrar el error (desviación de la salida real) y realizar la optimización para minimizar el error.
  • Ajustar el modelo : el proceso de aprendizaje real se realizará en esta fase utilizando el conjunto de datos de entrenamiento.
  • Predecir el resultado para el valor desconocido : predice la salida para los datos de entrada desconocidos (que no sean los datos de prueba y entrenamiento existentes)
  • Evaluar modelo : evalúe el modelo mediante la predicción de la salida de los datos de prueba y la comparación cruzada de la predicción con el resultado real de los datos de prueba.
  • Congelar, modificar o elegir un nuevo algoritmo : compruebe si la evaluación del modelo es correcta. En caso afirmativo, guarde el algoritmo para fines de predicción futura. Si no es así, modifique o elija un nuevo algoritmo / modelo y, finalmente, entrene, prediga y evalúe nuevamente el modelo. Repita el proceso hasta encontrar el mejor algoritmo (modelo).


Los pasos anteriores se pueden representar utilizando el siguiente diagrama de flujo:

ANA

Arquitectura de Keras

La API de Keras se puede dividir en tres categorías principales:

  • Modelo
  • Capa
  • Módulos centrales

En Keras, cada RNA está representada por Keras Models . A su vez, cada modelo de Keras es una composición de capas de Keras y representa capas de ANN como entrada, capa oculta, capas de salida, capa de convolución, capa de agrupación, etc., el modelo de Keras y la capa de acceso a los módulos de Keras para función de activación, función de pérdida, función de regularización, etc., utilizando el modelo de Keras, la capa de Keras y los módulos de Keras, cualquier algoritmo ANN (CNN, RNN, etc.) se puede representar de una manera simple y eficiente.

El siguiente diagrama muestra la relación entre el modelo, la capa y los módulos centrales:

Arquitectura de Keras

Veamos la descripción general de los modelos de Keras, las capas de Keras y los módulos de Keras.

Modelo

Los modelos Keras son de dos tipos, como se menciona a continuación:

Modelo secuencial: el modelo secuencial es básicamente una composición lineal de capas de Keras. El modelo secuencial es sencillo, mínimo y tiene la capacidad de representar casi todas las redes neuronales disponibles.

Un modelo secuencial simple es el siguiente:

from keras.models import Sequential 
from keras.layers import Dense, Activation 

model = Sequential()  
model.add(Dense(512, activation = 'relu', input_shape = (784,)))

Dónde,

  • La línea 1 importa el modelo secuencial de los modelos de Keras

  • La línea 2 importa la capa densa y el módulo de activación

  • Line 4 crea un nuevo modelo secuencial usando API secuencial

  • La línea 5 agrega una capa densa (API densa) con la función de activación relu (usando el módulo de activación).

El modelo secuencial expone la clase Model para crear modelos personalizados también. Podemos utilizar el concepto de subclasificación para crear nuestro propio modelo complejo.

API funcional: la API funcional se utiliza básicamente para crear modelos complejos.

Capa

Cada capa de Keras en el modelo de Keras representa la capa correspondiente (capa de entrada, capa oculta y capa de salida) en el modelo de red neuronal propuesto. Keras proporciona muchas capas preconstruidas para que cualquier red neuronal compleja se pueda crear fácilmente. Algunas de las capas importantes de Keras se especifican a continuación,

  • Capas centrales
  • Capas de convolución
  • Capas de agrupación
  • Capas recurrentes

Un código Python simple para representar un modelo de red neuronal usando un modelo secuencial es el siguiente:

from keras.models import Sequential 
from keras.layers import Dense, Activation, Dropout model = Sequential() 

model.add(Dense(512, activation = 'relu', input_shape = (784,))) 
model.add(Dropout(0.2)) 
model.add(Dense(512, activation = 'relu')) model.add(Dropout(0.2)) 
model.add(Dense(num_classes, activation = 'softmax'))

Dónde,

  • La línea 1 importa el modelo secuencial de los modelos de Keras

  • La línea 2 importa la capa densa y el módulo de activación

  • Line 4 crea un nuevo modelo secuencial usando API secuencial

  • La línea 5 agrega una capa densa (API densa) con la función de activación relu (usando el módulo de activación).

  • La línea 6 agrega una capa de abandono (API de abandono) para manejar el sobreajuste.

  • La línea 7 agrega otra capa densa (API densa) con la función de activación relu (usando el módulo de activación).

  • La línea 8 agrega otra capa de abandono (API de abandono) para manejar el sobreajuste.

  • La línea 9 agrega una capa densa final (API densa) con la función de activación softmax (usando el módulo de activación).

Keras también ofrece opciones para crear nuestras propias capas personalizadas. La capa personalizada se puede crear subclasificando la clase Keras.Layer y es similar a la subclasificación de modelos Keras.

Módulos centrales

Keras también proporciona muchas funciones integradas relacionadas con la red neuronal para crear correctamente el modelo de Keras y las capas de Keras. Algunas de las funciones son las siguientes:

  • Módulo de activaciones: la función de activación es un concepto importante en ANN y los módulos de activación proporcionan muchas funciones de activación como softmax, relu, etc.

  • Módulo de pérdida: el módulo de pérdida proporciona funciones de pérdida como mean_squared_error, mean_absolute_error, poisson, etc.

  • Módulo optimizador: el módulo optimizador proporciona una función de optimización como adam, sgd, etc.

  • Regularizadores : el módulo Regularizador proporciona funciones como regularizador L1, regularizador L2, etc.

Mas información en https://www.tutorialspoint.com/keras/

Primeros pasos con Jupyter Notebook


¿Qué es Jupyter Notebook?

Jupyter Notebook es una herramienta increíblemente poderosa para desarrollar y presentar proyectos de ciencia de datos de forma interactiva. Este post intentara guiarle de cómo usar Jupyter Notebooks para proyectos de ciencia de datos y cómo configurarlo en su máquina local.

jupyter-notebook las mejores herramientas gratuitas de ciencia de datos

Pero primero: ¿qué es un “cuaderno”?

Un cuaderno integra código y su salida en un solo documento que combina visualizaciones, texto narrativo, ecuaciones matemáticas y otros medios enriquecidos. En otras palabras: es un documento único en el que puede ejecutar código, mostrar el resultado y también agregar explicaciones, fórmulas, gráficos y hacer que su trabajo sea más transparente, comprensible, repetible y compartible. 

El uso de Notebooks es ahora una parte importante del flujo de trabajo de la ciencia de datos en empresas de todo el mundo. Si su objetivo es trabajar con datos, el uso de una computadora portátil acelerará su flujo de trabajo y facilitará la comunicación y el intercambio de resultados. 

Lo mejor de todo es que, como parte del proyecto de código abierto  Jupyter , los cuadernos de Jupyter son completamente gratuitos. Puede descargar el software  solo o como parte del kit de herramientas de ciencia de datos de Anaconda .

Aunque es posible utilizar muchos lenguajes de programación diferentes en Jupyter Notebooks, este artículo se centrará en Python, ya que es el caso de uso más común. (Entre los usuarios de R, R Studio  tiende a ser una opción más popular).Jupyter Notebooks también puede actuar como una plataforma flexible para familiarizarse con los pandas e incluso con Python, como se verá en este tutorial.

Algunas cosas que se veran en este post:

  • Cubrir los conceptos básicos de la instalación de Jupyter y la creación de su primer cuaderno
  • Profundizar y aprender toda la terminología importante
  • Explorear la facilidad con la que se pueden compartir y publicar en línea los blocs de notas.

Ejemplo de análisis de datos en un cuaderno Jupyter

Primero, recorreremos la configuración y un análisis de muestra para responder una pregunta de la vida real. Esto demostrará cómo el flujo de una computadora portátil hace que las tareas de ciencia de datos sean más intuitivas para nosotros mientras trabajamos y para otros una vez que llega el momento de compartir nuestro trabajo.

Entonces, digamos que es analista de datos y se le ha encomendado la tarea de averiguar cómo cambiaron históricamente las ganancias de las empresas más grandes de los EE. UU. Encontrará un conjunto de datos de compañías Fortune 500 que abarcan más de 50 años desde la primera publicación de la lista en 1955, reunidos a partir del archivo público de  Fortune . Seguimos adelante y creamos un CSV de los datos que puede usar  aquí .

Como demostraremos, los cuadernos de Jupyter son perfectamente adecuados para esta investigación. Primero, sigamos e instalemos Jupyter.

Instalación

La forma más fácil para que un principiante comience con Jupyter Notebooks es instalando  Anaconda .

Anaconda es la distribución de Python más utilizada para la ciencia de datos y viene precargada con todas las bibliotecas y herramientas más populares. 

Algunas de las bibliotecas de Python más grandes incluidas en Anaconda incluyen NumPy ,  pandas y  Matplotlib , aunque la  lista completa de más de 1000 es exhaustiva.

Por lo tanto, Anaconda nos permite comenzar con un taller de ciencia de datos completamente equipado sin la molestia de administrar innumerables instalaciones o preocuparse por las dependencias y los problemas de instalación específicos del sistema operativo (léase: específicos de Windows).

Para obtener Anaconda, simplemente:

  1. Descargue la última versión de Anaconda para Python 3.8.
  2. Instale Anaconda siguiendo las instrucciones en la página de descarga y / o en el ejecutable.

Si es un usuario más avanzado con Python ya instalado y prefiere administrar sus paquetes manualmente, puede usar pip :

pip3 install jupyter

Creación de su primer cuaderno

En esta sección, aprenderemos a ejecutar y guardar blocs de notas, a familiarizarnos con su estructura y a comprender la interfaz. Nos familiarizaremos con alguna terminología básica que lo guiará hacia una comprensión práctica de cómo usar Jupyter Notebooks por su cuenta y nos preparará para la siguiente sección, que recorre un ejemplo de análisis de datos y da vida a todo lo que aprendemos aquí.

Ejecutando Jupyter

En Windows, puede ejecutar Jupyter a través del acceso directo que Anaconda agrega a su menú de inicio, que abrirá una nueva pestaña en su navegador web predeterminado que debería parecerse a la siguiente captura de pantalla.

Panel de control de Jupyter

Este no es un cuaderno por el momento, ¡pero que no cunda el pánico! No hay mucho que hacer. Este es el panel de control del portátil, diseñado específicamente para administrar sus portátiles Jupyter. Piense en ello como la plataforma de lanzamiento para explorar, editar y crear sus cuadernos.

Tenga en cuenta que el panel le dará acceso solo a los archivos y subcarpetas que se encuentran dentro del directorio de inicio de Jupyter (es decir, donde está instalado Jupyter o Anaconda). Sin embargo, el directorio de inicio se puede cambiar .

También es posible iniciar el tablero en cualquier sistema a través del símbolo del sistema (o terminal en sistemas Unix) ingresando el comando jupyter notebook; en este caso, el directorio de trabajo actual será el directorio de inicio.

Con Jupyter Notebook abierto en su navegador, es posible que haya notado que la URL del panel de control es algo así como https://localhost:8888/tree. Localhost no es un sitio web, pero indica que el contenido se sirve desde su   máquina local : su propia computadora.

Los cuadernos y el tablero de Jupyter son aplicaciones web, y Jupyter inicia un servidor Python local para servir estas aplicaciones en su navegador web, lo que lo hace esencialmente independiente de la plataforma y abre la puerta para compartir más fácilmente en la web.

(Si aún no comprende esto, no se preocupe; el punto importante es que, aunque Jupyter Notebooks se abre en su navegador, está alojado y se ejecuta en su máquina local. Sus blocs de notas no están realmente en la web hasta que decide compartirlos.)

La interfaz del tablero se explica por sí misma, aunque volveremos a ella brevemente más adelante. entonces que estamos esperando ‘ Busque la carpeta en la que le gustaría crear su primer cuaderno, haga clic en el botón desplegable «Nuevo» en la parte superior derecha y seleccione «Python 3»:

Nuevo menú de cuaderno

¡Oye presto, aquí estamos! Su primer cuaderno de Jupyter se abrirá en una nueva pestaña; cada cuaderno usa su propia pestaña porque puede abrir varios cuadernos simultáneamente.

Si regresa al tablero, verá el nuevo archivo Untitled.ipynb y debería ver un texto verde que le indica que su computadora portátil se está ejecutando.

¿Qué es un archivo ipynb?

La respuesta corta: cada  archivo .ipynb es un cuaderno, por lo que cada vez que cree un nuevo cuaderno, se creará un nuevo   archivo .ipynb.  

La respuesta más larga: cada .ipynb archivo es un archivo de texto que describe el contenido de su cuaderno en un formato llamado  JSON . Cada celda y su contenido, incluidos los archivos adjuntos de imágenes que se han convertido en cadenas de texto, se enumeran allí junto con algunos  metadatos .

Puede editarlo usted mismo, ¡si sabe lo que está haciendo! – seleccionando «Editar> Editar metadatos del cuaderno» en la barra de menú del cuaderno. También puede ver el contenido de los archivos de su cuaderno seleccionando «Editar» en los controles del panel.

Sin embargo, la palabra clave puede. En la mayoría de los casos, no hay ninguna razón por la que deba editar manualmente los metadatos de su cuaderno.

La interfaz del portátil

Ahora que tiene un cuaderno abierto frente a usted, es de esperar que su interfaz no se vea del todo extraña. Después de todo, Jupyter es esencialmente un procesador de texto avanzado.

¿Por qué no echar un vistazo? Consulte los menús para familiarizarse con ellos, especialmente tómese unos minutos para desplazarse hacia abajo en la lista de comandos en la paleta de comandos, que es el botón pequeño con el ícono del teclado (o Ctrl + Shift + P).

Nuevo cuaderno de Jupyter

Hay dos términos bastante prominentes que debe notar, que probablemente sean nuevos para usted: las  células  y los  núcleos  son clave tanto para comprender Jupyter como para lo que lo convierte en algo más que un procesador de texto. Afortunadamente, estos conceptos no son difíciles de entender.

  • Un kernel es un «motor computacional» que ejecuta el código contenido en un documento de cuaderno.
  • Una celda es un contenedor para el texto que se mostrará en el cuaderno o el código que ejecutará el kernel del cuaderno.

Celdas

Regresaremos a los núcleos un poco más tarde, pero primero hablemos de las celdas. Las células forman el cuerpo de un cuaderno. En la captura de pantalla de un nuevo cuaderno en la sección anterior, ese cuadro con el contorno verde es una celda vacía. Hay dos tipos de células principales que cubriremos:

  • Una  celda de código contiene código que se ejecutará en el kernel. Cuando se ejecuta el código, el portátil muestra el resultado debajo de la celda de código que lo generó.
  • Una  celda de Markdown contiene texto formateado con Markdown y muestra su salida en el lugar cuando se ejecuta la celda de Markdown.

La primera celda de un cuaderno nuevo es siempre una celda de código.

Probémoslo con un ejemplo clásico de Hello World: escriba print('Hello World!') en la celda y haga clic en el botón Ejecutar  Botón de ejecución del cuaderno en la barra de herramientas de arriba o presione  Ctrl + Enter.

El resultado debería verse así:

print('Hello World!')
Hello World!

Cuando ejecutamos la celda, su salida se muestra a continuación y la etiqueta a su izquierda habrá cambiado de In [ ] a  In [1].

La salida de una celda de código también forma parte del documento, por lo que puede verla en este artículo. Siempre puede notar la diferencia entre el código y las celdas de Markdown porque las celdas de código tienen esa etiqueta a la izquierda y las celdas de Markdown no.

La parte «En» de la etiqueta es simplemente la abreviatura de «Entrada», mientras que el número de etiqueta indica cuándo se ejecutó la celda en el kernel; en este caso, la celda se ejecutó primero.

Ejecute la celda nuevamente y la etiqueta cambiará a In [2] porque ahora la celda fue la segunda en ejecutarse en el kernel. Será más claro por qué esto es tan útil más adelante cuando echemos un vistazo más de cerca a los núcleos.

En la barra de menú, haga clic en  Insertar  y seleccione  Insertar celda debajo  para crear una nueva celda de código debajo de la primera y pruebe el siguiente código para ver qué sucede. ¿Notas algo diferente?

import time
time.sleep(3)

Esta celda no produce ningún resultado, pero tarda tres segundos en ejecutarse. Observe cómo Jupyter significa cuando la celda se está ejecutando actualmente cambiando su etiqueta a In [*]

En general, la salida de una celda proviene de cualquier dato de texto impreso específicamente durante la ejecución de la celda, así como del valor de la última línea de la celda, ya sea una variable solitaria, una llamada de función u otra cosa. Por ejemplo:

def say_hello(recipient):
    return 'Hello, {}!'.format(recipient)

say_hello('Tim')
'Hello, Tim!'

Te encontrarás usando esto casi constantemente en tus propios proyectos, y veremos más de eso más adelante.

Atajos de teclado

Una última cosa que puede haber observado al ejecutar sus celdas es que su borde se vuelve azul, mientras que era verde mientras estaba editando. En un Jupyter Notebook, siempre hay una celda «activa» resaltada con un borde cuyo color denota su modo actual:

  • Contorno verde : la celda está en «modo de edición»
  • Contorno azul : la celda está en «modo de comando»

Entonces, ¿qué podemos hacer con una celda cuando está en modo comando? Hasta ahora, hemos visto cómo ejecutar una celda  Ctrl + Enter, pero hay muchos otros comandos que podemos usar. La mejor manera de usarlos es con atajos de teclado.

Los atajos de teclado son un aspecto muy popular del entorno de Jupyter porque facilitan un flujo de trabajo rápido basado en celdas. Muchas de estas son acciones que puede realizar en la celda activa cuando está en modo de comando.

A continuación, encontrará una lista de algunos de los atajos de teclado de Jupyter. No es necesario que los memorice todos de inmediato, pero esta lista debería darle una buena idea de lo que es posible.

  • Alternar entre los modos de edición y comando con  Esc y  Enter, respectivamente.
  • Una vez en el modo de comando:
    • Desplácese hacia arriba y hacia abajo en sus celdas con las   teclas Up y  Down.
    • Presione  A o  B para insertar una nueva celda encima o debajo de la celda activa.
    • M transformará la celda activa en una celda de Markdown.
    • Y establecerá la celda activa en una celda de código.
    • D + D ( D dos veces) eliminará la celda activa.
    • Z deshará la eliminación de la celda.
    • Mantenga  Shift presionado y presione  Up o  Downpara seleccionar varias celdas a la vez. Con varias celdas seleccionadas, Shift + M fusionará su selección.
  • Ctrl + Shift + -, en el modo de edición, dividirá la celda activa en el cursor.
  • También puede hacer clic Shift + Click en y  en el margen a la izquierda de sus celdas para seleccionarlas.

Continúe y pruébelos en su propio cuaderno. Una vez que esté listo, cree una nueva celda de Markdown y aprenderemos a formatear el texto en nuestros cuadernos.

Reducción

Markdown es un lenguaje de marcado ligero y fácil de aprender para formatear texto sin formato. Su sintaxis tiene una correspondencia uno a uno con las etiquetas HTML, por lo que algunos conocimientos previos aquí serían útiles, pero definitivamente no son un requisito previo.

Recuerda que este artículo fue escrito en un cuaderno de Jupyter, por lo que todo el texto narrativo y las imágenes que has visto hasta ahora se lograron escribiendo en Markdown. Cubramos los conceptos básicos con un ejemplo rápido:

# This is a level 1 heading

## This is a level 2 heading

This is some plain text that forms a paragraph. Add emphasis via **bold** and __bold__, or *italic* and _italic_. 

Paragraphs must be separated by an empty line. 

* Sometimes we want to include lists. 
* Which can be bulleted using asterisks. 

1. Lists can also be numbered. 
2. If we want an ordered list.

[It is possible to include hyperlinks](https://www.example.com)

Inline code uses single backticks: `foo()`, and code blocks use triple backticks: 
```
bar()
``` 
Or can be indented by 4 spaces: 

    foo()
    
And finally, adding images is easy: ![Alt text](https://www.example.com/image.jpg)

Así es como se vería Markdown una vez que ejecute la celda para renderizarlo:

(Tenga en cuenta que el texto alternativo de la imagen se muestra aquí porque en realidad no usamos una URL de imagen válida en nuestro ejemplo)

Al adjuntar imágenes, tiene tres opciones:

  • Utilice una URL para una imagen en la web.
  • Use una URL local para una imagen que mantendrá junto a su cuaderno, como en el mismo repositorio de git.
  • Agregue un archivo adjunto a través de «Editar> Insertar imagen»; esto convertirá la imagen en una cadena y la almacenará dentro de su .ipynbarchivo de cuaderno  . ¡Tenga en cuenta que esto hará que su .ipynb archivo sea mucho más grande!

Hay mucho más en Markdown, especialmente en torno a los hipervínculos, y también es posible incluir simplemente HTML simple. Una vez que se encuentre superando los límites de los conceptos básicos anteriores, puede consultar la guía oficial  del creador de Markdown, John Gruber, en su sitio web.

Granos

Detrás de cada portátil hay un kernel. Cuando ejecuta una celda de código, ese código se ejecuta dentro del kernel. Cualquier salida se devuelve a la celda para que se muestre. El estado del kernel persiste a lo largo del tiempo y entre celdas; pertenece al documento como un todo y no a celdas individuales.

Por ejemplo, si importa bibliotecas o declara variables en una celda, estarán disponibles en otra. Probemos esto para sentirlo. Primero, importaremos un paquete de Python y definiremos una función:

import numpy as np
def square(x):
    return x * x

Una vez que hayamos ejecutado la celda de arriba, podemos hacer referencia a np y  squareen cualquier otra celda. 

x = np.random.randint(1, 10)
y = square(x)
print('%d squared is %d' % (x, y))
1 squared is 1

Esto funcionará independientemente del orden de las celdas de su cuaderno. Siempre que se haya ejecutado una celda, las variables que declaró o las bibliotecas que importó estarán disponibles en otras celdas.

Puede probarlo usted mismo, imprimamos nuestras variables nuevamente.

print('Is %d squared %d?' % (x, y))
Is 1 squared 1?

¡No hay sorpresas aquí! Pero, ¿qué pasa si cambiamos el valor de  y?

y = 10
print('Is %d squared is %d?' % (x, y))

Si ejecutamos la celda de arriba, ¿qué crees que pasaría?

Obtendremos una salida como:  Is 4 squared 10?. Esto se debe a que una vez que hemos ejecutado la  y = 10celda de código, yya no es igual al cuadrado de x en el kernel. 

La mayoría de las veces, cuando crea un cuaderno, el flujo será de arriba hacia abajo. Pero es común volver atrás para realizar cambios. Cuando necesitamos realizar cambios en una celda anterior, el orden de ejecución que podemos ver a la izquierda de cada celda, por ejemplo In [6], puede ayudarnos a diagnosticar problemas al ver en qué orden se han ejecutado las celdas. 

Y si alguna vez deseamos restablecer las cosas, hay varias opciones increíblemente útiles en el menú Kernel:

  • Reiniciar: reinicia el kernel, borrando así todas las variables, etc.que fueron definidas.
  • Reiniciar y borrar la salida: igual que el anterior, pero también borrará la salida que se muestra debajo de las celdas de código.
  • Reiniciar y ejecutar todo: igual que el anterior, pero también ejecutará todas sus celdas en orden de la primera a la última.

Si su kernel se atasca alguna vez en un cálculo y desea detenerlo, puede elegir la opción Interrumpir.

Elegir un kernel

Es posible que haya notado que Jupyter le brinda la opción de cambiar el kernel y, de hecho, hay muchas opciones diferentes para elegir. Cuando creó un nuevo cuaderno desde el panel de control seleccionando una versión de Python, en realidad estaba eligiendo qué kernel usar.

Hay kernels para diferentes versiones de Python, y también para más de 100 lenguajes,  incluidos Java, C e incluso Fortran. Los científicos de datos pueden estar particularmente interesados ​​en los núcleos para  R  y  Julia , así como en  imatlab  y  Calysto MATLAB Kernel  para Matlab.

El kernel de SoS  proporciona compatibilidad con varios idiomas en un solo portátil.

Cada kernel tiene sus propias instrucciones de instalación, pero probablemente requiera que ejecute algunos comandos en su computadora.

¿Aprendiendo Python para la ciencia de datos?

¡Prueba una de nuestras más de 60 misiones gratuitas hoy!Empiece >>

Análisis de ejemplo

Ahora que hemos visto  qué es  un Jupyter Notebook, es hora de ver  cómo se utilizan en la práctica, lo que debería darnos una comprensión más clara de por  qué son tan populares.

Finalmente es hora de comenzar con ese conjunto de datos de Fortune 500 mencionado anteriormente. Recuerde, nuestro objetivo es averiguar cómo han cambiado históricamente las ganancias de las empresas más grandes de EE . UU .

Vale la pena señalar que todos desarrollarán sus propias preferencias y estilo, pero los principios generales aún se aplican. Si lo desea, puede seguir esta sección en su propio cuaderno o utilizarla como guía para crear su propio enfoque.

Nombrar sus cuadernos

Antes de comenzar a escribir su proyecto, probablemente querrá darle un nombre significativo. nombre de archivo Untitleden la parte superior izquierda de la pantalla para ingresar un nuevo nombre de archivo, y presione el icono Guardar (que parece un disquete) debajo de él para guardar.

Tenga en cuenta que cerrar la pestaña del cuaderno en su navegador no “cerrará” su cuaderno de la forma en que lo haría cerrar un documento en una aplicación tradicional. El kernel del portátil seguirá ejecutándose en segundo plano y debe cerrarse antes de que se «cierre» realmente, aunque esto es muy útil si cierra accidentalmente la pestaña o el navegador.

Si el kernel se apaga, puede cerrar la pestaña sin preocuparse de si todavía se está ejecutando o no. 

La forma más sencilla de hacerlo es seleccionar «Archivo> Cerrar y detener» en el menú de la libreta. Sin embargo, también puede apagar el kernel yendo a «Kernel> Shutdown» desde la aplicación de la computadora portátil o seleccionando la computadora portátil en el panel de control y haciendo clic en «Apagar» (vea la imagen a continuación).

Un cuaderno para correr

Configuración

Es común comenzar con una celda de código específicamente para las importaciones y la configuración, de modo que si elige agregar o cambiar algo, simplemente puede editar y volver a ejecutar la celda sin causar efectos secundarios.

%matplotlib inline
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns sns.set(style="darkgrid")

Importaremos pandas  para trabajar con nuestros datos,  Matplotlib  para trazar gráficos y  Seaborn  para hacer que nuestros gráficos sean más bonitos. También es común importar  NumPy, pero en este caso, pandas lo importa por nosotros.

Esa primera línea no es un comando de Python, pero usa algo llamado magia de línea para indicarle a Jupyter que capture los diagramas de Matplotlib y los represente en la salida de la celda. Hablaremos un poco más sobre la magia de líneas más adelante, y también se tratan en nuestro tutorial avanzado de Jupyter Notebooks .

Por ahora, sigamos adelante y carguemos nuestros datos.

df = pd.read_csv('fortune500.csv')

Es sensato hacer esto también en una sola celda, en caso de que necesitemos recargarlo en algún momento.

Guardar y comprobar

Ahora que hemos comenzado, lo mejor es ahorrar con regularidad. Al presionar  Ctrl + Sguardará nuestro cuaderno llamando al comando «Guardar y punto de control», pero ¿qué es este punto de control?

Cada vez que creamos un nuevo cuaderno, se crea un archivo de punto de control junto con el archivo del cuaderno. Se encuentra dentro de un subdirectorio oculto de su ubicación de guardado llamado .ipynb_checkpoints y también es un  .ipynb archivo.

De forma predeterminada, Jupyter guardará automáticamente su cuaderno cada 120 segundos en este archivo de punto de control sin alterar el archivo de su cuaderno principal. Cuando «Guardar y punto de control», se actualizan tanto el cuaderno como los archivos del punto de control. Por lo tanto, el punto de control le permite recuperar su trabajo no guardado en caso de un problema inesperado.

Puede volver al punto de control desde el menú a través de «Archivo> Volver al punto de control».

Investigando nuestro conjunto de datos

¡Ahora estamos realmente rodando! Nuestro cuaderno se guarda de forma segura y hemos cargado nuestro conjunto de datos  df en la estructura de datos de pandas más utilizada, que se llama  DataFrame ay básicamente se parece a una tabla. ¿Qué aspecto tiene el nuestro?

df.head()
AñoRangoEmpresaIngresos (en millones)Beneficio (en millones)
019551Motores generales9823.5806
119552Exxon Mobil5661.4584,8
219553Acero de EE. UU.3250,4195,4
319554Energia General2959,1212,6
419555Esmark2510,819,1
df.tail()
AñoRangoEmpresaIngresos (en millones)Beneficio (en millones)
254952005496Wm. Wrigley Jr.3648,6493
254962005497Energía Peabody3631,6175,4
254972005498Wendy’s internacional3630,457,8
254982005499Kindred Healthcare3616,670,6
254992005500Cincinnati Financial3614.0584

Luciendo bien. Tenemos las columnas que necesitamos y cada fila corresponde a una sola empresa en un solo año.

Cambiemos el nombre de esas columnas para poder consultarlas más adelante.

df.columns = ['year', 'rank', 'company', 'revenue', 'profit']

A continuación, necesitamos explorar nuestro conjunto de datos. Esta completo? ¿Lo leyeron los pandas como se esperaba? ¿Falta algún valor?

len(df)
25500

De acuerdo, eso se ve bien, son 500 filas por cada año desde 1955 hasta 2005, inclusive.

Comprobemos si nuestro conjunto de datos se ha importado como era de esperar. Una simple verificación es ver si los tipos de datos (o dtypes) se han interpretado correctamente.

df.dtypes
year int64 rank int64 company object revenue float64 profit object dtype: object

UH oh. Parece que hay algún problema con la columna de ganancias; esperaríamos que fuera  float64 similar a la columna de ingresos. Esto indica que probablemente contiene algunos valores que no son enteros, así que echemos un vistazo.

non_numberic_profits = df.profit.str.contains('[^0-9.-]')
df.loc[non_numberic_profits].head()
añorangoempresaingresoslucro
2281955229Norton135,0N / A
2901955291Elaboración de cerveza Schlitz100,0N / A
2941955295Aceite vegetal del Pacífico97,9N / A
2961955297Cervecerías Liebmann96,0N / A
3521955353Minneapolis-Moline77,4N / A

¡Tal como sospechábamos! Algunos de los valores son cadenas, que se han utilizado para indicar datos faltantes. ¿Hay otros valores que se hayan infiltrado?

set(df.profit[non_numberic_profits])
{'N.A.'}

Eso hace que sea fácil de interpretar, pero ¿qué debemos hacer? Bueno, eso depende de cuántos valores falten.

len(df.profit[non_numberic_profits])
369

Es una pequeña fracción de nuestro conjunto de datos, aunque no del todo intrascendente, ya que todavía está en torno al 1,5%.

Si las filas que contienen N.A. están distribuidas de manera aproximada y uniforme a lo largo de los años, la solución más sencilla sería eliminarlas. Así que echemos un vistazo rápido a la distribución.

bin_sizes, _, _ = plt.hist(df.year[non_numberic_profits], bins=range(1955, 2006))
Falta distribución de valor

De un vistazo, podemos ver que los valores más inválidos en un solo año son menos de 25, y como hay 500 puntos de datos por año, eliminar estos valores representaría menos del 4% de los datos de los peores años. De hecho, aparte de un aumento en torno a los 90, la mayoría de los años tienen menos de la mitad de los valores faltantes del pico.

Para nuestros propósitos, digamos que esto es aceptable y continúe y elimine estas filas.

df = df.loc[~non_numberic_profits]
df.profit = df.profit.apply(pd.to_numeric)

Deberíamos comprobar que funcionó.

len(df)
25131
df.dtypes
year int64 rank int64 company object revenue float64 profit float64 dtype: object

¡Estupendo! Hemos terminado la configuración de nuestro conjunto de datos.

Si tuviéramos que presentar su cuaderno como un informe, podríamos deshacernos de las celdas de investigación que creamos, que se incluyen aquí como una demostración del flujo de trabajo con los cuadernos, y fusionar las celdas relevantes (consulte la sección Funcionalidad avanzada a continuación para más sobre esto) para crear una celda de configuración de un solo conjunto de datos.

Esto significaría que si alguna vez estropeamos nuestro conjunto de datos en otro lugar, podemos simplemente volver a ejecutar la celda de configuración para restaurarlo.

Trazando con matplotlib

A continuación, podemos llegar a abordar la cuestión en cuestión trazando el beneficio promedio por año. También podríamos trazar los ingresos, así que primero podemos definir algunas variables y un método para reducir nuestro código.

group_by_year = df.loc[:, ['year', 'revenue', 'profit']].groupby('year')
avgs = group_by_year.mean()
x = avgs.index
y1 = avgs.profit
def plot(x, y, ax, title, y_label):
    ax.set_title(title)
    ax.set_ylabel(y_label)
    ax.plot(x, y)
    ax.margins(x=0, y=0)

¡Ahora tramemos!

fig, ax = plt.subplots()
plot(x, y1, ax, 'Increase in mean Fortune 500 company profits from 1955 to 2005', 'Profit (millions)')
Aumento de las ganancias medias de las empresas incluidas en la lista Fortune 500 de 1955 a 2005

Vaya, eso parece exponencial, pero tiene algunas caídas enormes. Deben corresponder a la recesión de principios de la  década de 1990  y a la  burbuja de las puntocom . Es bastante interesante ver eso en los datos. Pero, ¿cómo es que las ganancias se recuperaron a niveles aún más altos después de cada recesión?

Quizás los ingresos puedan decirnos más.

y2 = avgs.revenue
fig, ax = plt.subplots()
plot(x, y2, ax, 'Increase in mean Fortune 500 company revenues from 1955 to 2005', 'Revenue (millions)')
Aumento de los ingresos medios de las empresas incluidas en la lista Fortune 500 de 1955 a 2005

Eso agrega otro lado a la historia. Los ingresos no se vieron tan afectados; ese es un gran trabajo contable de los departamentos de finanzas.

Con un poco de ayuda  de Stack Overflow , podemos superponer estos gráficos con +/- sus desviaciones estándar.

def plot_with_std(x, y, stds, ax, title, y_label):
    ax.fill_between(x, y - stds, y + stds, alpha=0.2)
    plot(x, y, ax, title, y_label)
fig, (ax1, ax2) = plt.subplots(ncols=2)
title = 'Increase in mean and std Fortune 500 company %s from 1955 to 2005'
stds1 = group_by_year.std().profit.values
stds2 = group_by_year.std().revenue.values
plot_with_std(x, y1.values, stds1, ax1, title % 'profits', 'Profit (millions)')
plot_with_std(x, y2.values, stds2, ax2, title % 'revenues', 'Revenue (millions)')
fig.set_size_inches(14, 4)
fig.tight_layout()
jupyter-notebook-tutorial_48_0

Eso es asombroso, ¡las desviaciones estándar son enormes! Algunas empresas de Fortune 500 ganan miles de millones mientras que otras pierden miles de millones, y el riesgo ha aumentado junto con el aumento de las ganancias a lo largo de los años.

Quizás algunas empresas se desempeñen mejor que otras; ¿Son las ganancias del 10% superior más o menos volátiles que las del 10% inferior?

Hay muchas preguntas que podríamos analizar a continuación, y es fácil ver cómo el flujo de trabajo en un cuaderno puede coincidir con el propio proceso de pensamiento. Para los propósitos de este tutorial, detendremos nuestro análisis aquí, ¡pero siéntase libre de continuar investigando los datos por su cuenta!

Este flujo nos ayudó a investigar fácilmente nuestro conjunto de datos en un solo lugar sin cambiar de contexto entre aplicaciones, y nuestro trabajo se puede compartir y reproducir de inmediato. Si quisiéramos crear un informe más conciso para una audiencia en particular, podríamos refactorizar rápidamente nuestro trabajo fusionando celdas y eliminando el código intermediario.

Compartir sus cuadernos

Cuando la gente habla de compartir sus cuadernos, generalmente hay dos paradigmas que pueden estar considerando.

La mayoría de las veces, las personas comparten el resultado final de su trabajo, al igual que este artículo, lo que significa compartir versiones no interactivas y pre-renderizadas de sus cuadernos. Sin embargo, también es posible colaborar en portátiles con la ayuda de sistemas de control de versiones como Git o plataformas online como Google Colab .

Antes de compartir

Un cuaderno compartido aparecerá exactamente en el estado en el que se encontraba cuando lo exporta o lo guarda, incluida la salida de las celdas de código. Por lo tanto, para asegurarse de que su computadora portátil esté lista para compartir, por así decirlo, hay algunos pasos que debe seguir antes de compartir:

  1. Haga clic en «Celda> Todos los resultados> Borrar».
  2. Haga clic en «Kernel> Reiniciar y ejecutar todo».
  3. Espere a que las celdas de su código terminen de ejecutarse y verifique que se haya ejecutado como se esperaba

Esto asegurará que sus cuadernos no contengan salida intermedia, tengan un estado obsoleto y se ejecuten en orden en el momento de compartir.

Exportación de sus cuadernos

Jupyter tiene soporte integrado para exportar a HTML y PDF, así como a varios otros formatos, que puede encontrar en el menú en «Archivo> Descargar como».

Si desea compartir sus cuadernos con un pequeño grupo privado, esta funcionalidad puede ser todo lo que necesite. De hecho, como a muchos investigadores de instituciones académicas se les proporciona un espacio web público o interno, y debido a que puede exportar un cuaderno a un archivo HTML, Jupyter Notebooks puede ser una forma especialmente conveniente para que los investigadores compartan sus resultados con sus pares.

Pero si compartir archivos exportados no es suficiente para usted, también existen algunos métodos inmensamente populares para compartir  .ipynb archivos más directamente en la web.

GitHub

Con la  cantidad de cuadernos públicos en GitHub que  excedieron los 1.8 millones a principios de 2018, seguramente es la plataforma independiente más popular para compartir proyectos de Jupyter con el mundo. GitHub tiene soporte integrado para renderizar  .ipynb archivos directamente tanto en repositorios como en esencias en su sitio web. Si aún no lo sabe,  GitHub  es una plataforma de alojamiento de código para el control de versiones y la colaboración para repositorios creados con  Git. . Necesitará una cuenta para utilizar sus servicios, pero las cuentas estándar son gratuitas.

Una vez que tenga una cuenta de GitHub, la forma más fácil de compartir un cuaderno en GitHub no requiere Git en absoluto. Desde 2008, GitHub ha proporcionado su servicio Gist para alojar y compartir fragmentos de código, cada uno de los cuales tiene su propio repositorio. Para compartir un cuaderno usando Gists:

  1. Inicie sesión y navegue hasta gist.github.com .
  2. Abra su  .ipynb archivo en un editor de texto, seleccione todo y copie el JSON dentro.
  3. Pegue el JSON del cuaderno en la esencia.
  4. Dale a tu Gist un nombre de archivo, recordando agregarlo  .iypnb o esto no funcionará.
  5. Haga clic en «Crear esencia secreta» o «Crear esencia pública».

Esto debería tener un aspecto similar al siguiente:

Creando una esencia

Si creó un Gist público, ahora podrá compartir su URL con cualquier persona, y otros podrán  bifurcar y clonar  su trabajo.

Crear tu propio repositorio de Git y compartirlo en GitHub está más allá del alcance de este tutorial, pero  GitHub proporciona muchas guías  para que comiences por tu cuenta.

Un consejo adicional para aquellos que usan git es  agregar una excepción  a su  .gitignore para los .ipynb_checkpoints directorios ocultos que  crea Jupyter, para no enviar archivos de puntos de control innecesariamente a su repositorio.

Nbviewer

Después de haber crecido hasta renderizar  cientos de miles  de cuadernos cada semana en 2015, NBViewer es el renderizador de cuadernos más popular en la web. Si ya tiene un lugar para alojar sus cuadernos de Jupyter en línea, ya sea en GitHub o en otro lugar, NBViewer renderizará su cuaderno y proporcionará una URL para compartir junto con él. Se proporciona como un servicio gratuito como parte del Proyecto Jupyter y está disponible en  nbviewer.jupyter.org .

Desarrollado inicialmente antes de la integración de Jupyter Notebook de GitHub, NBViewer permite que cualquier persona ingrese una URL, ID de Gist o nombre de usuario / repositorio / archivo de GitHub y mostrará el cuaderno como una página web. La ID de un Gist es el número único al final de su URL; por ejemplo, la cadena de caracteres después de la última barra invertida  https://gist.github.com/username/50896401c23e0bf417e89cd57e89e1de. Si ingresa un nombre de usuario o nombre de usuario / repositorio de GitHub, verá un explorador de archivos mínimo que le permite explorar los repositorios de un usuario y su contenido.

La URL que se muestra en NBViewer cuando se muestra un cuaderno es una constante basada en la URL del cuaderno que se está procesando, por lo que puede compartirla con cualquier persona y funcionará siempre que los archivos originales permanezcan en línea; NBViewer no almacena en caché los archivos durante mucho tiempo. largo.

Si no le gusta Nbviewer, existen otras opciones similares: aquí hay un hilo con algunos para considerar de nuestra comunidad.

Extras: Extensiones de Jupyter Notebook

Ya hemos cubierto todo lo que necesita para empezar a trabajar en Jupyter Notebooks.

¿Qué son las extensiones?

Las extensiones son precisamente lo que parecen: características adicionales que amplían la funcionalidad de Jupyter Notebooks. Si bien un Jupyter Notebook básico puede hacer mucho, las extensiones ofrecen algunas características adicionales que pueden ayudar con flujos de trabajo específicos o que simplemente mejoran la experiencia del usuario.

Por ejemplo, una extensión llamada «Tabla de contenido» genera una tabla de contenido para su cuaderno, para que los cuadernos grandes sean más fáciles de visualizar y navegar. 

Otro, llamado Inspector de variables, le mostrará el valor, el tipo, el tamaño y la forma de cada variable en su cuaderno para una fácil referencia rápida y depuración. 

Otro, llamado ExecuteTime, le permite saber cuándo y durante cuánto tiempo se ejecutó cada celda; esto puede ser particularmente conveniente si está tratando de acelerar un fragmento de su código.

Estos son solo la punta del iceberg; hay muchas extensiones disponibles.

¿Dónde puede obtener extensiones?

Para obtener las extensiones, debe instalar Nbextensions. Puede hacer esto usando pip y la línea de comando. Si tiene Anaconda, puede ser mejor hacerlo a través de Anaconda Prompt en lugar de la línea de comandos normal.

Cerrar Jupyter cuadernos, abierta Anaconda Prompt y ejecute el siguiente comando: pip install jupyter_contrib_nbextensions && jupyter contrib nbextension install.

Una vez que haya hecho eso, inicie un cuaderno y debería ver una pestaña Nbextensions. Al hacer clic en esta pestaña, se le mostrará una lista de extensiones disponibles. Simplemente marque las casillas de las extensiones que desea habilitar y ¡listo para las carreras!

Instalación de extensiones

Una vez que se ha instalado Nbextensions, no es necesario realizar una instalación adicional de cada extensión. Sin embargo, si ya instaló Nbextensons pero no ve la pestaña, no está solo. Este hilo en Github detalla algunos problemas y soluciones comunes.

Extras: Line Magics en Jupyter

Anteriormente mencionamos los comandos mágicos cuando solíamos %matplotlib inlinehacer que los gráficos de Matplotlib se renderizaran directamente en nuestro cuaderno. También hay muchas otras magias que podemos usar.

Cómo usar magia en Jupyter

Un buen primer paso es abrir un Jupyter Notebook, escribir %lsmagicen una celda y ejecutar la celda. Esto generará una lista de las magias de línea y de celda disponibles, y también te dirá si «automagic» está activado. 

  • Las magias de línea operan en una sola línea de una celda de código
  • La magia celular opera en toda la celda de código en la que se llaman

Si automagic está activado, puede ejecutar una magia simplemente escribiéndola en su propia línea en una celda de código y ejecutando la celda. Si está desactivado, deberá poner   %antes de las magias de línea y   %%  antes de las magias de células para usarlas.

Muchas magias requieren información adicional (al igual que una función requiere un argumento) para decirles cómo operar. Veremos un ejemplo en la siguiente sección, pero puede ver la documentación de cualquier magia ejecutándola con un signo de interrogación, así:

%matplotlib?

Cuando ejecute la celda anterior en un cuaderno, aparecerá una larga cadena de documentos en la pantalla con detalles sobre cómo puede usar la magia.

Algunos comandos mágicos útiles

Cubrimos más en el tutorial avanzado de Jupyter , pero aquí hay algunos para comenzar:

Mando mágicoQue hace
%correrEjecuta un archivo de secuencia de comandos externo como parte de la celda que se está ejecutando.
Por ejemplo, si aparece % run myscript.py en una celda de código, el kernel ejecutará myscript.py como parte de esa celda.
%cronométraloCuenta bucles, mide e informa cuánto tiempo tarda en ejecutarse una celda de código.
% archivo de escrituraGuarde el contenido de una celda en un archivo.
Por ejemplo,  % savefile myscript.py guardaría la celda de código como un archivo externo llamado myscript.py.
%TiendaGuarde una variable para usarla en un cuaderno diferente.
% pwdImprima la ruta del directorio en el que está trabajando actualmente.
%% javascriptEjecuta la celda como código JavaScript.

Hay mucho más de donde vino eso. Ingrese a Jupyter Notebooks y comience a explorar usando%lsmagic !

Pensamientos finales

Comenzando desde cero, nos hemos familiarizado con el flujo de trabajo natural de Jupyter Notebooks, profundizamos en las funciones más avanzadas de IPython y finalmente aprendimos cómo compartir nuestro trabajo con amigos, colegas y el mundo. ¡Y logramos todo esto desde un propio cuaderno!

Debe quedar claro cómo los cuadernos promueven una experiencia de trabajo productiva al reducir el cambio de contexto y emular un desarrollo natural de pensamientos durante un proyecto. El poder de usar Jupyter Notebooks también debería ser evidente, y cubrimos muchos clientes potenciales para que comience a explorar más funciones avanzadas en sus propios proyectos.

Si desea obtener más inspiración para sus propios cuadernos, Jupyter ha creado  una galería de cuadernos de Jupyter interesantes  que pueden resultarle útiles y la  página de inicio de Nbviewer.  enlaces a algunos ejemplos realmente elegantes de cuadernos de calidad.

Estimación de máxima verosimilitud en Python


¿Qué pasa si una relación lineal no es un supuesto apropiado para nuestro modelo?

Una alternativa ampliamente propuesta por Thomas J. Sargent y John Stachurski es la estimación de máxima verosimilitud, que implica especificar una clase de distribuciones, indexadas por parámetros desconocidos, y luego usar los datos para precisar los valores de estos parámetros.

El beneficio relativo a la regresión lineal es que permite una mayor flexibilidad en las relaciones probabilísticas entre variables.

Aquí ilustramos la máxima probabilidad replicando el artículo de Daniel Treisman (2016) sobre los multimillonarios de Rusia , que conecta la cantidad de multimillonarios en un país con sus características económicas concluyendo que Rusia tiene una mayor cantidad de multimillonarios de lo que predicen factores económicos como el tamaño del mercado y la tasa impositiva.

Requeriremos las siguientes importaciones:

%matplotlib inline
import matplotlib.pyplot as plt
plt.rcParams["figure.figsize"] = (11, 5)  #set default figure size
import numpy as np
from numpy import exp
from scipy.special import factorial
import pandas as pd
from mpl_toolkits.mplot3d import Axes3D
import statsmodels.api as sm
from statsmodels.api import Poisson
from scipy import stats
from scipy.stats import norm
from statsmodels.iolib.summary2 import summary_col

 Configuración y suposiciones 

Consideremos los pasos que debemos seguir en la estimación de máxima verosimilitud y cómo pertenecen a este estudio.

Flujo de ideas 

El primer paso con la estimación de máxima verosimilitud es elegir la distribución de probabilidad que se cree que genera los datos.

Más precisamente, necesitamos suponer qué clase de distribución paramétrica está generando los datos.

  • por ejemplo, la clase de todas las distribuciones normales o la clase de todas las distribuciones gamma.

Cada una de estas clases es una familia de distribuciones indexadas por un número finito de parámetros.

  • Por ejemplo, la clase de distribuciones normales es una familia de distribuciones indexadas por su media. μ∈(−∞,∞) y desviación estándar σ∈(0,∞).

Dejaremos que los datos seleccionen un elemento particular de la clase definiendo los parámetros.

Las estimaciones de parámetros así producidas se denominarán estimaciones de máxima verosimilitud .

 Contando multimillonarios 

Treisman está interesado en estimar el número de multimillonarios en diferentes países.El número de multimillonarios se valora en números enteros.Por lo tanto, consideramos distribuciones que toman valores solo en los enteros no negativos.

(Esta es una de las razones por las que la regresión por mínimos cuadrados no es la mejor herramienta para el problema actual, ya que la variable dependiente en la regresión lineal no está restringida a valores enteros)

Una distribución entera es la distribución de Poisson , cuya función de masa de probabilidad (pmf) esf(y)=μyy!e−μ,y=0,1,2,…,∞

Podemos trazar la distribución de Poisson sobre y para diferentes valores de μ como sigue

poisson_pmf = lambda y, μ: μ**y / factorial(y) * exp(-μ)
y_values = range(0, 25)

fig, ax = plt.subplots(figsize=(12, 8))

for μ in [1, 5, 10]:
    distribution = []
    for y_i in y_values:
        distribution.append(poisson_pmf(y_i, μ))
    ax.plot(y_values,
            distribution,
            label=f'$\mu$={μ}',
            alpha=0.5,
            marker='o',
            markersize=8)

ax.grid()
ax.set_xlabel('$y$', fontsize=14)
ax.set_ylabel('$f(y \mid \mu)$', fontsize=14)
ax.axis(xmin=0, ymin=0)
ax.legend(fontsize=14)

plt.show()

_images / mle_3_0.png

Observe que la distribución de Poisson comienza a parecerse a una distribución normal como la media de y aumenta.

Echemos un vistazo a la distribución de los datos con los que trabajaremos en esta conferencia.

La principal fuente de datos de Treisman son las clasificaciones anuales de multimillonarios de Forbes y su patrimonio neto estimado.

El conjunto de datos mle/fp.dtase puede descargar desde aquí o desde su página AER .

pd.options.display.max_columns = 10

# Load in data and view
df = pd.read_stata('https://github.com/QuantEcon/lecture-python/blob/master/source/_static/lecture_specific/mle/fp.dta?raw=true')
df.head()

paísccodeañoañoentumecidotopint08rintrnoyrsroflawnrrents
0Estados Unidos2.01990.021990.0Yaya39.7999994.98840520,01,61Yaya
1Estados Unidos2.01991.021991.0Yaya39.7999994.98840520,01,61Yaya
2Estados Unidos2.01992,021992.0Yaya39.7999994.98840520,01,61Yaya
3Estados Unidos2.01993.021993.0Yaya39.7999994.98840520,01,61Yaya
4Estados Unidos2.01994.021994.0Yaya39.7999994.98840520,01,61Yaya

5 filas × 36 columnas

Usando un histograma, podemos ver la distribución del número de multimillonarios por país numbil0, en 2008 (los Estados Unidos se descartan para fines de trazado)

numbil0_2008 = df[(df['year'] == 2008) & (
    df['country'] != 'United States')].loc[:, 'numbil0']

plt.subplots(figsize=(12, 8))
plt.hist(numbil0_2008, bins=30)
plt.xlim(left=0)
plt.grid()
plt.xlabel('Number of billionaires in 2008')
plt.ylabel('Count')
plt.show()

_images / mle_7_0.png

A partir del histograma, parece que la suposición de Poisson no es irrazonable (aunque con un valor muy bajo μ y algunos valores atípicos).

Distribuciones condicionales 

En el artículo de Treisman, la variable dependiente: el número de multimillonarios yi en el pais i – se modela en función del PIB per cápita, el tamaño de la población y los años de pertenencia al GATT y la OMC.

Por tanto, la distribución de yi necesita estar condicionado al vector de variables explicativas xi.

La formulación estándar, el llamado modelo de regresión de Poisson , es la siguiente:(58.1)f(yi∣xi)=μiyiyi!e−μi;yi=0,1,2,…,∞.where μi=exp⁡(xi′β)=exp⁡(β0+β1xi1+…+βkxik)

Para ilustrar la idea de que la distribución de yi depende de xi ejecutemos una simulación simple.

Usamos nuestra poisson_pmffunción de arriba y valores arbitrarios para β y xi

y_values = range(0, 20)

# Define a parameter vector with estimates
β = np.array([0.26, 0.18, 0.25, -0.1, -0.22])

# Create some observations X
datasets = [np.array([0, 1, 1, 1, 2]),
            np.array([2, 3, 2, 4, 0]),
            np.array([3, 4, 5, 3, 2]),
            np.array([6, 5, 4, 4, 7])]


fig, ax = plt.subplots(figsize=(12, 8))

for X in datasets:
    μ = exp(X @ β)
    distribution = []
    for y_i in y_values:
        distribution.append(poisson_pmf(y_i, μ))
    ax.plot(y_values,
            distribution,
            label=f'$\mu_i$={μ:.1}',
            marker='o',
            markersize=8,
            alpha=0.5)

ax.grid()
ax.legend()
ax.set_xlabel('$y \mid x_i$')
ax.set_ylabel(r'$f(y \mid x_i; \beta )$')
ax.axis(xmin=0, ymin=0)
plt.show()

_images / mle_9_0.png

Podemos ver que la distribución de yi está condicionado a xi (μi ya no es constante).

Estimación de máxima verosimilitud 

En nuestro modelo para el número de multimillonarios, la distribución condicional contiene 4 (k=4) parámetros que necesitamos estimar.

Etiquetaremos todo nuestro vector de parámetros como β dóndeβ=[β0β1β2β3]

Para estimar el modelo usando MLE, queremos maximizar la probabilidad de que nuestra estimación β^ es el verdadero parámetro β.

Intuitivamente, queremos encontrar el β^ que mejor se adapte a nuestros datos.

Primero, necesitamos construir la función de verosimilitud L(β), que es similar a una función de densidad de probabilidad conjunta.

Supongamos que tenemos algunos datos yi={y1,y2} y yi∼f(yi).

Si y1 y y2 son independientes, la PMF conjunta de estos datos es f(y1,y2)=f(y1)⋅f(y2).

Si yi sigue una distribución de Poisson con λ=7, podemos visualizar el PMF conjunto así

def plot_joint_poisson(μ=7, y_n=20):
    yi_values = np.arange(0, y_n, 1)

    # Create coordinate points of X and Y
    X, Y = np.meshgrid(yi_values, yi_values)

    # Multiply distributions together
    Z = poisson_pmf(X, μ) * poisson_pmf(Y, μ)

    fig = plt.figure(figsize=(12, 8))
    ax = fig.add_subplot(111, projection='3d')
    ax.plot_surface(X, Y, Z.T, cmap='terrain', alpha=0.6)
    ax.scatter(X, Y, Z.T, color='black', alpha=0.5, linewidths=1)
    ax.set(xlabel='$y_1$', ylabel='$y_2$')
    ax.set_zlabel('$f(y_1, y_2)$', labelpad=10)
    plt.show()

plot_joint_poisson(μ=7, y_n=20)

_images / mle_11_0.png

De manera similar, la pmf conjunta de nuestros datos (que se distribuye como una distribución condicional de Poisson) se puede escribir comof(y1,y2,…,yn∣x1,x2,…,xn;β)=∏i=1nμiyiyi!e−μi

yi está condicionado a ambos valores de xi y los parámetros β.

La función de verosimilitud es la misma que la pmf conjunta, pero trata el parámetro β como una variable aleatoria y toma las observaciones (yi,xi) como se indicaL(β∣y1,y2,…,yn ; x1,x2,…,xn)=∏i=1nμiyiyi!e−μi=f(y1,y2,…,yn∣ x1,x2,…,xn;β)

Ahora que tenemos nuestra función de verosimilitud, queremos encontrar el β^ que produce el valor de máxima verosimilitudmaxβL(β)

Al hacerlo, generalmente es más fácil maximizar la probabilidad logarítmica (considere diferenciar f(x)=xexp⁡(x) vs. f(x)=log⁡(x)+x).

Dado que tomar un logaritmo es una transformación creciente monótona, un maximizador de la función de verosimilitud también será un maximizador de la función logarítmica de verosimilitud.

En nuestro caso, la probabilidad logarítmica eslog⁡L(β)= log⁡(f(y1;β)⋅f(y2;β)⋅…⋅f(yn;β))=∑i=1nlog⁡f(yi;β)=∑i=1nlog⁡(μiyiyi!e−μi)=∑i=1nyilog⁡μi−∑i=1nμi−∑i=1nlog⁡y!

El MLE del Poisson al Poisson para β^ se puede obtener resolviendomaxβ(∑i=1nyilog⁡μi−∑i=1nμi−∑i=1nlog⁡y!)

Sin embargo, no existe una solución analítica para el problema anterior: para encontrar el MLE necesitamos usar métodos numéricos.

 MLE con métodos numéricos 

Muchas distribuciones no tienen buenas soluciones analíticas y, por lo tanto, requieren métodos numéricos para resolver las estimaciones de los parámetros.

Uno de esos métodos numéricos es el algoritmo de Newton-Raphson.

Nuestro objetivo es encontrar la estimación de máxima verosimilitud β^.

A β^, la primera derivada de la función logarítmica de verosimilitud será igual a 0.

Ilustremos esto suponiendolog⁡L(β)=−(β−10)2−10

β = np.linspace(1, 20)
logL = -(β - 10) ** 2 - 10
dlogL = -2 * β + 20

fig, (ax1, ax2) = plt.subplots(2, sharex=True, figsize=(12, 8))

ax1.plot(β, logL, lw=2)
ax2.plot(β, dlogL, lw=2)

ax1.set_ylabel(r'$log \mathcal{L(\beta)}$',
               rotation=0,
               labelpad=35,
               fontsize=15)
ax2.set_ylabel(r'$\frac{dlog \mathcal{L(\beta)}}{d \beta}$ ',
               rotation=0,
               labelpad=35,
               fontsize=19)
ax2.set_xlabel(r'$\beta$', fontsize=15)
ax1.grid(), ax2.grid()
plt.axhline(c='black')
plt.show()

_images / mle_13_0.png

La gráfica muestra que el valor de máxima verosimilitud (la gráfica superior) ocurre cuando dlog⁡L(β)dβ=0 (la trama inferior).

Por lo tanto, la probabilidad se maximiza cuando β=10.

También podemos asegurarnos de que este valor sea un máximo (en lugar de un mínimo) comprobando que la segunda derivada (pendiente del gráfico inferior) sea negativa.

El algoritmo de Newton-Raphson encuentra un punto donde la primera derivada es 0.

Para usar el algoritmo, hacemos una estimación inicial del valor máximo, β0 (las estimaciones de los parámetros de MCO pueden ser una suposición razonable), entonces

  1. Utilice la regla de actualización para iterar el algoritmoβ(k+1)=β(k)−H−1(β(k))G(β(k))dónde:G(β(k))=dlog⁡L(β(k))dβ(k)H(β(k))=d2log⁡L(β(k))dβ(k)dβ(k)′
  2. Compruebe si β(k+1)−β(k)<tol
    • Si es verdadero, deje de iterar y configure β^=β(k+1)
    • Si es falso, actualice β(k+1)

Como puede verse en la ecuación de actualización, β(k+1)=β(k) sólo cuando G(β(k))=0es decir. donde la primera derivada es igual a 0.

(En la práctica, dejamos de iterar cuando la diferencia está por debajo de un pequeño umbral de tolerancia)

Intentemos implementar el algoritmo de Newton-Raphson.

Primero, crearemos una clase llamada PoissonRegressionpara que podamos volver a calcular fácilmente los valores de probabilidad de registro, gradiente y hessiano para cada iteración.

class PoissonRegression:

    def __init__(self, y, X, β):
        self.X = X
        self.n, self.k = X.shape
        # Reshape y as a n_by_1 column vector
        self.y = y.reshape(self.n,1)
        # Reshape β as a k_by_1 column vector
        self.β = β.reshape(self.k,1)

    def μ(self):
        return np.exp(self.X @ self.β)

    def logL(self):
        y = self.y
        μ = self.μ()
        return np.sum(y * np.log(μ) - μ - np.log(factorial(y)))

    def G(self):
        y = self.y
        μ = self.μ()
        return X.T @ (y - μ)

    def H(self):
        X = self.X
        μ = self.μ()
        return -(X.T @ (μ * X))

Nuestra función newton_raphsontomará un PoissonRegressionobjeto que tiene una suposición inicial del vector de parámetrosβ0.

El algoritmo actualizará el vector de parámetros de acuerdo con la regla de actualización y volverá a calcular el gradiente y las matrices hessianas en las estimaciones de los nuevos parámetros.

La iteración terminará cuando:

  • La diferencia entre el parámetro y el parámetro actualizado está por debajo de un nivel de tolerancia.
  • Se ha alcanzado el número máximo de iteraciones (lo que significa que no se logra la convergencia).

Para que podamos tener una idea de lo que sucede mientras se ejecuta el algoritmo, display=Truese agrega una opción para imprimir valores en cada iteración.

def newton_raphson(model, tol=1e-3, max_iter=1000, display=True):

    i = 0
    error = 100  # Initial error value

    # Print header of output
    if display:
        header = f'{"Iteration_k":<13}{"Log-likelihood":<16}{"θ":<60}'
        print(header)
        print("-" * len(header))

    # While loop runs while any value in error is greater
    # than the tolerance until max iterations are reached
    while np.any(error > tol) and i < max_iter:
        H, G = model.H(), model.G()
        β_new = model.β - (np.linalg.inv(H) @ G)
        error = β_new - model.β
        model.β = β_new

        # Print iterations
        if display:
            β_list = [f'{t:.3}' for t in list(model.β.flatten())]
            update = f'{i:<13}{model.logL():<16.8}{β_list}'
            print(update)

        i += 1

    print(f'Number of iterations: {i}')
    print(f'β_hat = {model.β.flatten()}')

    # Return a flat array for β (instead of a k_by_1 column vector)
    return model.β.flatten()

Probemos nuestro algoritmo con un pequeño conjunto de datos de 5 observaciones y 3 variables en X.

X = np.array([[1, 2, 5],
              [1, 1, 3],
              [1, 4, 2],
              [1, 5, 2],
              [1, 3, 1]])

y = np.array([1, 0, 1, 1, 0])

# Take a guess at initial βs
init_β = np.array([0.1, 0.1, 0.1])

# Create an object with Poisson model values
poi = PoissonRegression(y, X, β=init_β)

# Use newton_raphson to find the MLE
β_hat = newton_raphson(poi, display=True)

Iteration_k  Log-likelihood  θ                                                           
-----------------------------------------------------------------------------------------
0            -4.3447622      ['-1.49', '0.265', '0.244']
1            -3.5742413      ['-3.38', '0.528', '0.474']
2            -3.3999526      ['-5.06', '0.782', '0.702']
3            -3.3788646      ['-5.92', '0.909', '0.82']
4            -3.3783559      ['-6.07', '0.933', '0.843']
5            -3.3783555      ['-6.08', '0.933', '0.843']
Number of iterations: 6
β_hat = [-6.07848205  0.93340226  0.84329625]

Como se trataba de un modelo simple con pocas observaciones, el algoritmo logró la convergencia en solo 6 iteraciones.

Puede ver que con cada iteración, el valor de probabilidad logarítmica aumentó.

Recuerde, nuestro objetivo era maximizar la función de probabilidad logarítmica, que el algoritmo ha trabajado para lograr.

Además, tenga en cuenta que el aumento en log⁡L(β(k)) se vuelve más pequeño con cada iteración.

Esto se debe a que el gradiente se acerca a 0 a medida que alcanzamos el máximo y, por lo tanto, el numerador de nuestra ecuación de actualización se hace más pequeño.

El vector de gradiente debe estar cerca de 0 en β^

poi.G()

array([[-3.95169228e-07],
       [-1.00114805e-06],
       [-7.73114562e-07]])

El proceso iterativo se puede visualizar en el siguiente diagrama, donde el máximo se encuentra en β=10

logL = lambda x: -(x - 10) ** 2 - 10

def find_tangent(β, a=0.01):
    y1 = logL(β)
    y2 = logL(β+a)
    x = np.array([[β, 1], [β+a, 1]])
    m, c = np.linalg.lstsq(x, np.array([y1, y2]), rcond=None)[0]
    return m, c

β = np.linspace(2, 18)
fig, ax = plt.subplots(figsize=(12, 8))
ax.plot(β, logL(β), lw=2, c='black')

for β in [7, 8.5, 9.5, 10]:
    β_line = np.linspace(β-2, β+2)
    m, c = find_tangent(β)
    y = m * β_line + c
    ax.plot(β_line, y, '-', c='purple', alpha=0.8)
    ax.text(β+2.05, y[-1], f'$G({β}) = {abs(m):.0f}$', fontsize=12)
    ax.vlines(β, -24, logL(β), linestyles='--', alpha=0.5)
    ax.hlines(logL(β), 6, β, linestyles='--', alpha=0.5)

ax.set(ylim=(-24, -4), xlim=(6, 13))
ax.set_xlabel(r'$\beta$', fontsize=15)
ax.set_ylabel(r'$log \mathcal{L(\beta)}$',
               rotation=0,
               labelpad=25,
               fontsize=15)
ax.grid(alpha=0.3)
plt.show()

_images / mle_23_0.png

Tenga en cuenta que nuestra implementación del algoritmo de Newton-Raphson es bastante básica; para implementaciones más sólidas, consulte, por ejemplo, scipy.optimize .

Estimación de máxima verosimilitud con statsmodels

Ahora que sabemos lo que está pasando bajo el capó, podemos aplicar MLE a una aplicación interesante.

Usaremos el modelo de regresión de Poisson statsmodelspara obtener una salida más rica con errores estándar, valores de prueba y más.

statsmodels utiliza el mismo algoritmo anterior para encontrar las estimaciones de máxima verosimilitud.

Antes de comenzar, volvamos a estimar nuestro modelo simple con statsmodels para confirmar que obtenemos los mismos coeficientes y valor logarítmico de verosimilitud.

X = np.array([[1, 2, 5],
              [1, 1, 3],
              [1, 4, 2],
              [1, 5, 2],
              [1, 3, 1]])

y = np.array([1, 0, 1, 1, 0])

stats_poisson = Poisson(y, X).fit()
print(stats_poisson.summary())

Optimization terminated successfully.
         Current function value: 0.675671
         Iterations 7
                          Poisson Regression Results                          
==============================================================================
Dep. Variable:                      y   No. Observations:                    5
Model:                        Poisson   Df Residuals:                        2
Method:                           MLE   Df Model:                            2
Date:                Thu, 15 Jul 2021   Pseudo R-squ.:                  0.2546
Time:                        00:14:01   Log-Likelihood:                -3.3784
converged:                       True   LL-Null:                       -4.5325
Covariance Type:            nonrobust   LLR p-value:                    0.3153
==============================================================================
                 coef    std err          z      P>|z|      [0.025      0.975]
------------------------------------------------------------------------------
const         -6.0785      5.279     -1.151      0.250     -16.425       4.268
x1             0.9334      0.829      1.126      0.260      -0.691       2.558
x2             0.8433      0.798      1.057      0.291      -0.720       2.407
==============================================================================

Ahora repliquemos los resultados del artículo de Daniel Treisman, Los multimillonarios de Rusia , mencionado anteriormente en la conferencia.

Treisman comienza estimando la ecuación (58.1) , donde:

  • yi es number of billionairesi
  • xi1 es log⁡GDP per capitai
  • xi2 es log⁡populationi
  • xi3 es years in GATTi – años de membresía en el GATT y la OMC (para poder acceder a los mercados internacionales)

El documento solo considera el año 2008 para la estimación.

Configuraremos nuestras variables para la estimación de esa manera (debe tener los datos asignados dfdesde antes en la lección)

# Keep only year 2008
df = df[df['year'] == 2008]

# Add a constant
df['const'] = 1

# Variable sets
reg1 = ['const', 'lngdppc', 'lnpop', 'gattwto08']
reg2 = ['const', 'lngdppc', 'lnpop',
        'gattwto08', 'lnmcap08', 'rintr', 'topint08']
reg3 = ['const', 'lngdppc', 'lnpop', 'gattwto08', 'lnmcap08',
        'rintr', 'topint08', 'nrrents', 'roflaw']

Entonces podemos usar la Poissonfunción de statsmodelspara ajustar el modelo.

Usaremos errores estándar robustos como en el artículo del autor.

# Specify model
poisson_reg = sm.Poisson(df[['numbil0']], df[reg1],
                         missing='drop').fit(cov_type='HC0')
print(poisson_reg.summary())

Optimization terminated successfully.
         Current function value: 2.226090
         Iterations 9
                          Poisson Regression Results                          
==============================================================================
Dep. Variable:                numbil0   No. Observations:                  197
Model:                        Poisson   Df Residuals:                      193
Method:                           MLE   Df Model:                            3
Date:                Thu, 15 Jul 2021   Pseudo R-squ.:                  0.8574
Time:                        00:14:01   Log-Likelihood:                -438.54
converged:                       True   LL-Null:                       -3074.7
Covariance Type:                  HC0   LLR p-value:                     0.000
==============================================================================
                 coef    std err          z      P>|z|      [0.025      0.975]
------------------------------------------------------------------------------
const        -29.0495      2.578    -11.268      0.000     -34.103     -23.997
lngdppc        1.0839      0.138      7.834      0.000       0.813       1.355
lnpop          1.1714      0.097     12.024      0.000       0.980       1.362
gattwto08      0.0060      0.007      0.868      0.386      -0.008       0.019
==============================================================================

¡Éxito! El algoritmo pudo lograr la convergencia en 9 iteraciones.

Nuestro resultado indica que el PIB per cápita, la población y los años de membresía en el Acuerdo General sobre Aranceles Aduaneros y Comercio (GATT) están relacionados positivamente con la cantidad de multimillonarios que tiene un país, como se esperaba.

Estimemos también los modelos más completos del autor y los mostraremos en una sola tabla.

regs = [reg1, reg2, reg3]
reg_names = ['Model 1', 'Model 2', 'Model 3']
info_dict = {'Pseudo R-squared': lambda x: f"{x.prsquared:.2f}",
             'No. observations': lambda x: f"{int(x.nobs):d}"}
regressor_order = ['const',
                   'lngdppc',
                   'lnpop',
                   'gattwto08',
                   'lnmcap08',
                   'rintr',
                   'topint08',
                   'nrrents',
                   'roflaw']
results = []

for reg in regs:
    result = sm.Poisson(df[['numbil0']], df[reg],
                        missing='drop').fit(cov_type='HC0',
                                            maxiter=100, disp=0)
    results.append(result)

results_table = summary_col(results=results,
                            float_format='%0.3f',
                            stars=True,
                            model_names=reg_names,
                            info_dict=info_dict,
                            regressor_order=regressor_order)
results_table.add_title('Table 1 - Explaining the Number of Billionaires \
                        in 2008')
print(results_table)

Table 1 - Explaining the Number of Billionaires                         in 2008
=================================================
                  Model 1    Model 2    Model 3  
-------------------------------------------------
const            -29.050*** -19.444*** -20.858***
                 (2.578)    (4.820)    (4.255)   
lngdppc          1.084***   0.717***   0.737***  
                 (0.138)    (0.244)    (0.233)   
lnpop            1.171***   0.806***   0.929***  
                 (0.097)    (0.213)    (0.195)   
gattwto08        0.006      0.007      0.004     
                 (0.007)    (0.006)    (0.006)   
lnmcap08                    0.399**    0.286*    
                            (0.172)    (0.167)   
rintr                       -0.010     -0.009    
                            (0.010)    (0.010)   
topint08                    -0.051***  -0.058*** 
                            (0.011)    (0.012)   
nrrents                                -0.005    
                                       (0.010)   
roflaw                                 0.203     
                                       (0.372)   
Pseudo R-squared 0.86       0.90       0.90      
No. observations 197        131        131       
=================================================
Standard errors in parentheses.
* p<.1, ** p<.05, ***p<.01

El resultado sugiere que la frecuencia de multimillonarios se correlaciona positivamente con el PIB per cápita, el tamaño de la población, la capitalización del mercado de valores y se correlaciona negativamente con la tasa de impuesto sobre la renta marginal máxima.

Para analizar nuestros resultados por país, podemos trazar la diferencia entre los valores predichos y reales, luego ordenar de mayor a menor y trazar los primeros 15

data = ['const', 'lngdppc', 'lnpop', 'gattwto08', 'lnmcap08', 'rintr',
        'topint08', 'nrrents', 'roflaw', 'numbil0', 'country']
results_df = df[data].dropna()

# Use last model (model 3)
results_df['prediction'] = results[-1].predict()

# Calculate difference
results_df['difference'] = results_df['numbil0'] - results_df['prediction']

# Sort in descending order
results_df.sort_values('difference', ascending=False, inplace=True)

# Plot the first 15 data points
results_df[:15].plot('country', 'difference', kind='bar',
                    figsize=(12,8), legend=False)
plt.ylabel('Number of billionaires above predicted level')
plt.xlabel('Country')
plt.show()

_images / mle_33_0.png

Como podemos ver, Rusia tiene, con mucho, el mayor número de multimillonarios por encima de lo que predice el modelo (alrededor de 50 más de lo esperado).

Treisman usa este resultado empírico para discutir las posibles razones del exceso de multimillonarios de Rusia, incluido el origen de la riqueza en Rusia, el clima político y la historia de la privatización en los años posteriores a la URSS.

Fuente https://python.quantecon.org/mle.html

Introducción a python científico


Muchas áreas de carácter científico-técnico la adecuada elección del software y/o lenguaje de programación empleado es determinante, de cara a la potencia, versatilidad, facilidad de uso y acceso por parte de todos los usuarios en sus propios dispositivos, de manera generalizada y gratuita.

Dentro del software libre, uno de los que últimamente ha tenido una mejora sustancial, con la inclusión de potentes y versátiles nuevos módulos de cálculo simbólico (SymPy), numérico (NumPy, SciPy) y gráfico (PyPlot y Matplotlib) ha sido sin duda Python, y de ahí su vertiginosa evolución y expansión a nivel mundial, no sólo en el ámbito académico, sino también en el científico e industrial. De hecho, basta con echar un vistazo a las numerosas propuestas, tanto de comunidades de desarrolladores como de empresas privadas, surgidas a raíz de la versión de base inicial de Python, como por ejemplo IPython (interface interactivo de fácil uso, que gracias a Jupyter Notebook permite una versión HTML similar a los notebooks de Mathematica o Mapple) o Spyder (entorno integrado para cálculo científico parecido al de Matlab u Octave).

Por otro lado existen versiones completas de desarrollo, integrando Python como soporte de cálculo, pero con editores avanzados de texto para la programación y la depuración de código, ventanas de gráficos y datos, etc. La mayoría de estas plataformas integradas están disponibles para los distintos sistemas operativos Linux, MacOS X y Windows. Entre ellas cabría destacar Enthought Python Distribution (EPD), PyCharm y Anaconda CE (de Continuum Analytics).

Aunque no podamos abarcar todos los aspectos «básicos» del python científico, intentaremos en este resumen dar una idea de las principales librerías un funciones que podemos usar para NILM (Non-Intrusive Load Monitoring) sin olvidar los fundamentos de :Matplotlib y Numpy

Matplotlib: visualización con Python 

Matplotlib es una biblioteca completa para crear visualizaciones estáticas, animadas e interactivas en Python haciendo que las cosas fáciles sean fáciles y las difíciles posibles.

Nos permite crear :

  • Desarrollando gráficos de calidad de publicación con solo unas pocas líneas de código
  • Utilizando figuras interactivas que puedan hacer zoom, desplazarse, actualizar …

Personalizar

  • Tomando el control total de los estilos de línea, las propiedades de la fuente, las propiedades de los ejes …
  • Exportando e incrustando en varios formatos de archivo y entornos interactivos

Ampliar

  • Explorando la funcionalidad personalizada proporcionada por paquetes de terceros
  • Obteniendo más información sobre Matplotlib a través de los numerosos recursos de aprendizaje externos

Matplotlib es en resumen la librería de python para dibujar (equivalente al plot en matlab).

matplotlib

Puede encontrar mas información en el sitio oficial https://matplotlib.org/

Numpy

NumPy es una biblioteca para el lenguaje de programación Python que da soporte para crear vectores y matrices grandes multidimensionales, junto con una gran colección de funciones matemáticas de alto nivel para operar con ellas.

Numpy es pues una librería especializada para operaciones con matrices y vectores

Puede encontrar mas información en l sitio oficial https://numpy.org/

La imagen tiene un atributo ALT vacío; su nombre de archivo es image-11.png

Primeros pasos

Primero, es necesario importarlas al workspace

import numpy as np
import matplotlib.pyplot as plt

Opciones de visualizacion de matplotlib para un notebook

%matplotlib inline
plt.rcParams['figure.figsize'] = (13, 6)
plt.style.use('ggplot')

Otras importaciones:

import warnings
warnings.filterwarnings('ignore')

Crear arrays en python es muy sencillo y se puede hacer de forma nativa usando un tipo list. Sin embargo, aquí consideramos arrays del tipo numpy pues esto arrays incluyen funciones que facilitan las operaciones matemáticas y su manipulación

v=[1,2,3] # tipo list
v=np.array([1,2,3]) # array numpy
print (v)
print ("Dimensiones: " + str(v.ndim)) # numero de dimensiones
print ("Elementos: " + str(v.size)) # numero de elementos
print ("Longitud de las dimensiones: " + str(v.shape)) # longitud de cada dimensión
[1 2 3]
Dimensiones: 1
Elementos: 3
Longitud de las dimensiones: (3,)

Crear una matriz de 2 x 3:

v=np.array([[1,2,3], [4,5,6]])
print (v)
print ('Dimensiones: ' + str(v.ndim)) # numero de dimensiones
print ('Elementos: '+str(v.size)) # numero de elementos
print ('Longitud de las dimensiones: '+str(v.shape)) # longitud de cada dimensión
[[1 2 3]
 [4 5 6]]
Dimensiones: 2
Elementos: 6
Longitud de las dimensiones: (2, 3)

Crear una Matriz triple de 2 x 3 x 2 :

v=np.array([[[1,2], [3,4]],[[5,6],  [7,8]]])
print (v)
print ("Dimensiones: " + str(v.ndim)) # numero de dimensiones
print ("Elementos: "+str(v.size)) # numero de elementos
print ("Longitud de las dimensiones: "+str(v.shape) )# longitud de cada dimensión

[[[1 2], 
      [3 4]],

 [[5 6], 
      [7 8]]]
Dimensiones: 3
Elementos: 8
Longitud de las dimensiones: (2, 2, 2)

Utilizamos la función reshape para redimensionar los arrays

1 dimension

print (v.reshape(8,))
[1 2 3 4 5 6 7 8]

2 dimensiones

print (v.reshape(2,4))
[[1 2 3 4]
 [5 6 7 8]]

Matriz Identidad de 5×5

print (np.identity(5))
[[1. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0.]
 [0. 0. 1. 0. 0.]
 [0. 0. 0. 1. 0.]
 [0. 0. 0. 0. 1.]]

Matriz de unos de 5×5

print ( np.ones([5,5]))
[[1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1.]]

Matriz de ceros de 5×5:

print (np.zeros([5,5]))
[[0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]]

Las operaciones por definición son elementwise

a=np.arange(5)
b=10*np.ones(5)
print ("vector a: "+str(a))
print ("vector b: "+str(b))
print ("suma b+a: "+str(b-a))
print ("resta b-a: "+str(b+a))
print ("producto b*a: "+str(b*a))
vector a: [0 1 2 3 4]
vector b: [10. 10. 10. 10. 10.]
suma b+a: [10.  9.  8.  7.  6.]
resta b-a: [10. 11. 12. 13. 14.]
producto b*a: [ 0. 10. 20. 30. 40.]

El producto de los vectores es:

a.dot(b)
100.0

Para las matrices tenemos que:

a=np.identity(3)
b=np.array([[1,2,3],[4,5,6],[7,8,9]])
print ("matriz a:\n"+str(a))
print ("matriz b:\n"+str(b))
print ("producto a*b:\n"+str(a.dot(b)))
print ("producto elementwise a.*b:\n"+str(a*b))
matriz a:
[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]
matriz b:
[[1 2 3]
 [4 5 6]
 [7 8 9]]
producto a*b:
[[1. 2. 3.]
 [4. 5. 6.]
 [7. 8. 9.]]
producto elementwise a.*b:
[[1. 0. 0.]
 [0. 5. 0.]
 [0. 0. 9.]]

Vector formado por un rango de valores:

print ("De 0 a 10: " + str(np.arange(10)))
print ("De 10 a 20 de paso 0.5: "+str(np.arange(10,20,0.5)))
De 0 a 10: [0 1 2 3 4 5 6 7 8 9]
De 10 a 20 de paso 0.5: [10.  10.5 11.  11.5 12.  12.5 13.  13.5 14.  14.5 15.  15.5 16.  16.5
 17.  17.5 18.  18.5 19.  19.5]

Función linspace:

np.linspace(0,2*np.pi,10) # de 0 a 2*pi en 10 puntos equidistantes
array([0.        , 0.6981317 , 1.3962634 , 2.0943951 , 2.7925268 ,
       3.4906585 , 4.1887902 , 4.88692191, 5.58505361, 6.28318531])

función random:

np.random.rand(10)
array([0.63623588, 0.83924558, 0.35833155, 0.33835148, 0.53247758,
       0.0950348 , 0.2805706 , 0.47285484, 0.8696919 , 0.78361161])

Dibujar una función seno

t = np.arange(0.0, 2.0, 0.01)
s = np.sin(2*np.pi*t)
plt.plot(t, s)
plt.xlabel('time (s)')
plt.ylabel('voltage (mV)')
plt.title('Sinusoidal')
plt.grid(True)

Dibujar una función chirp

x=np.linspace(0,3*np.pi,500)
plt.plot(x,np.sin(x**2))
plt.title("A simple chirp")
Text(0.5, 1.0, 'A simple chirp')

Definir una función en Python

En python, las funciones pueden estar definidas en cualquier parte pero siempre antes de su llamada. En python, las anidaciones (bucles, if conditions, functions, etc.) se realizan mediante indentación, no existe el statement end. Las funciones se definen así:

def funcion_suma(x): 
    suma=0
    for i in x: 
        suma=suma+i
    return suma 
v=np.arange(10)
print (funcion_suma(v))
45

Aunque, como hemos dicho antes, numpy facilita las operaciones matemáticas y ya incluye una serie de operaciones:

print (v.sum())
print (v.cumsum())
print (v.mean())
45
[ 0  1  3  6 10 15 21 28 36 45]
4.5

Para saber más sobre numpy:

https://docs.scipy.org/doc/numpy-dev/user/quickstart.html

http://www.sam.math.ethz.ch/~raoulb/teaching/PythonTutorial/intro_numpy.html

(O simplemente «googleando»: numpy tutorial)

Pandas

Pandas (Python Data Analysis Library) es una librería de python para el análisis y manipulación de una gran cantidad de datos. También facilita el uso de «timeseries»

La llamada a la librería es:

import pandas as pd

Dado un archivo csv, la función read_csv carga los datos en un dataframe

# El parámetro parse_dates indica a pandas que al cargar este csv la primera columna [0] es de tipo datetime
df=pd.read_csv('data/events.csv',parse_dates=[0])

Las primeras N filas del dataFrame se puede visualizar de la siguiente forma

N=4
df.head(N)
timestamplabelphase
02011-10-20 12:22:01.473111A
12011-10-20 12:37:40.507111A
22011-10-20 13:23:55.390111A
32011-10-20 13:39:08.157111A

Y las N últimas columnas

df.tail(N)
timestamplabelphase
24812011-10-27 12:57:17.079111A
24822011-10-27 13:10:45.112111A
24832011-10-27 13:54:08.862111A
24842011-10-27 14:07:21.612111A

Podemos filtar por un cierto valor

df[df.phase=='B'].head()
timestamplabelphase
122011-10-20 15:45:54.590204B
132011-10-20 15:47:31.223204B
142011-10-20 16:09:00.424204B
182011-10-20 17:42:00.657155B
192011-10-20 17:42:04.407157B

Y hacer agrupaciones

df2=df.sort_values(['label','phase']).groupby(['label','phase']).count()
df2
timestamp
labelphase
101B26
102B25
103B24
108A16
111A619
1125A1
1126A1
1127A1
1200A1
1201A1

161 rows × 1 columns

Las nuevas columnas se crean fácilmente. Compatible con numpy.

df['x']=25*np.random.rand(len(df))
df['y']=100*np.sin(2*np.pi*np.linspace(0,2*np.pi,len(df)))
df['z']=df.x+df.y
df.head(5)
timestamplabelphasexyz
02011-10-20 12:22:01.473111A0.0167760.0000000.016776
12011-10-20 12:37:40.507111A5.6400571.5892417.229298
22011-10-20 13:23:55.390111A5.6322523.1780818.810333
32011-10-20 13:39:08.157111A9.2871414.76611914.053259
42011-10-20 14:25:51.473111A9.3135696.35295215.666521

Para dibujar sólo necesitamos usar la función plot

df.z.plot()
<matplotlib.axes._subplots.AxesSubplot at 0x7f6e3ba88080>

Existen ciertas funciones predefinidas que facilitan los cálculos

df.z.cumsum().plot()
<matplotlib.axes._subplots.AxesSubplot at 0x7f6e3b801e80>

Y se pueden concatenar

# Si integramos y derivamos obtenemos la misma señal
df.z.cumsum().diff().plot()
<matplotlib.axes._subplots.AxesSubplot at 0x7f6e32625320>

Unas de las herramientas más potentes de pandas es la manipulación de timeseries

df.index=df.timestamp
df.z.plot()
<matplotlib.axes._subplots.AxesSubplot at 0x7f6e325e6518>

Podemos filtar por fechas

d1='2011-10-21'
d2='2011-10-23'
df[(df.index>d1)&(df.index<d2)].z.plot()
<matplotlib.axes._subplots.AxesSubplot at 0x7f6e325d5588>

Existe una gran flexibilidad a la hora de resamplear un dataframe

# Cada día 
df.resample('1D',how='sum')
labelxyz
timestamp
2011-10-20233182044.60316112047.56801914092.171180
2011-10-21507894364.168081-4531.116500-166.948419
2011-10-221356456971.7315141811.9787858783.710300
2011-10-231021845445.726210-4029.5292421416.196968
2011-10-24462593554.5631237284.89716310839.460286
2011-10-25525523933.715959-4712.890577-779.174618
2011-10-26590843503.958137-7682.254604-4178.296467
2011-10-27181921325.9193697454.6146868780.534055
# Cada 6 horas
df.resample('6H',how='count')
timestamplabelphasexyz
timestamp
2011-10-20 12:00:00373737373737
2011-10-20 18:00:00135135135135135135
2011-10-21 00:00:00160160160160160160
2011-10-21 06:00:00676767676767
2011-10-21 12:00:00262626262626
2011-10-21 18:00:00828282828282
2011-10-22 00:00:00242424242424
2011-10-22 06:00:00107107107107107107
2011-10-22 12:00:00215215215215215215
2011-10-22 18:00:00203203203203203203
2011-10-23 00:00:00636363636363
2011-10-23 06:00:00646464646464
2011-10-23 12:00:00737373737373
2011-10-23 18:00:00237237237237237237
2011-10-24 00:00:00363636363636
2011-10-24 06:00:00393939393939
2011-10-24 12:00:00494949494949
2011-10-24 18:00:00162162162162162162
2011-10-25 00:00:00333333333333
2011-10-25 06:00:00919191919191
2011-10-25 12:00:00373737373737
2011-10-25 18:00:00152152152152152152
2011-10-26 00:00:00232323232323
2011-10-26 06:00:00616161616161
2011-10-26 12:00:00161616161616
2011-10-26 18:00:00196196196196196196
2011-10-27 00:00:00363636363636
2011-10-27 06:00:00555555555555
2011-10-27 12:00:00666666

Para aprender más sobre pandas:

http://pandas.pydata.org/pandas-docs/stable/tutorials.html

http://pandas.pydata.org/pandas-docs/stable/10min.html

Detector de eventos

Vamos a crear un detector de eventos.

Dado el consumo eléctrico de una vivienda (voltage y corriente) queremos detectar en que momento se produce una conexión de un dispositivo. Para ello, filtraremos la señal sinusoidal obteniendo el valor eficaz de la corriente cada cierto intervalo. Los cambios en el valor eficaz van a determinar las conexiones y desconexiones de los distintos dispositivos. Derivando este valor eficaz, obtenemos picos en los que existe un cambio en el valor eficaz y, por lo tanto, posibles candidatos a eventos de conexión/desconexión. Finalmente, usando un detector de picos filtraremos los eventos reales del resto.

Mediremos nuestros resultados usando métricas estándar de NILM.

Paso por paso

Importar pandas, numpy y matplotlib tal y como se ha visto anteriormente

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

Definir una funcion llamada rms_function que devuelva un valor rms y que tenga como parámetro de entrada un vector de valores

# función rms 
def rms_function(x): 
    return np.sqrt(np.mean(np.square(x)))

Usar el siguiente path para cargar los datos en un dataframe df de pandas. Como parámetros: el índice es la columna 0 (index_col) y la fecha está en la columna 1 (parse_dates)

path='data/smart_meter_data.csv'
df= ...
path='data/smart_meter_data.csv'
df=pd.read_csv(path, parse_dates=[1],index_col=[0])

Mostrar las 5 primeras columnas del dataframe

df.head(5)
datetimeivlabelappl_namephase
02011-10-20 12:21:58.9730000.444955159.194375111RefrigeratorA
12011-10-20 12:21:58.9730830.402501160.677554111RefrigeratorA
22011-10-20 12:21:58.9731660.444955161.845163111RefrigeratorA
32011-10-20 12:21:58.9732491.102993163.107443111RefrigeratorA
42011-10-20 12:21:58.9733321.952074164.243495111RefrigeratorA

Imprimir mínimo y máximo de datetime y la diferencia de ambos

print (df.datetime.min())
print (df.datetime.max())
print (df.datetime.max()-df.datetime.min())
2011-10-20 12:21:58.973000
2011-10-20 12:23:03.713996
0 days 00:01:04.740996

Seleccionar datetime como índice del dataframe df

df.index=df.datetime

Periodo y frequencia de muestreo

# frecuencia
ts=df.datetime.diff().mean().total_seconds()
print (str(ts)+' seconds')
fs=1/ts
print ( str(fs)+' Hz')
8.3e-05 seconds
12048.192771084337 Hz

Dibujar Voltage (v) haciendo zoom en el intervalo de 100ms (6 periodos aproximadamente)

d1='2011-10-20 12:22:29.9'
d2='2011-10-20 12:22:30'
df[(df.index>d1)&(df.index<d2)].v.plot()
<matplotlib.axes._subplots.AxesSubplot at 0x7f6e2b9f86d8>
df.i.plot()
<matplotlib.axes._subplots.AxesSubplot at 0x7f6e2e274d30>

Resamplear mediante la función resample de pandas a 50ms (’50L’). La función rms_function se pasará como parámetro para obtener cada valor del resampleado. El resultado debe de guardarse en un dataframe nuevo llamado rms . Dibujar el resultado.

rms=pd.DataFrame(df.i.resample(....))
rms=pd.DataFrame(df.i.resample('50L',how=rms_function))
rms.plot()
<matplotlib.axes._subplots.AxesSubplot at 0x7f6e2f8848d0>

Hacer la derivada del dataframe rms y guardar el resultado en rms_diff.

rms_diff=rms.diff()

Dibujar el resultado (rms_diff)

rms_diff.plot()
<matplotlib.axes._subplots.AxesSubplot at 0x7f6e2f7f9fd0>

Guardar los valores de la columna «i» en una variable «y» en forma de array

y=rms_diff.i.values
Modifica los parámetros th_noise y dist de la función detect_peaks para obener los índices de los eventos y evaluar las métricas. Realizar el proceso 3 veces. ¿ Con qué valores de th_noise y dist se obtienen mejores resultados en las métricas?
th_noise=5
dist=5
from detect_peaks import detect_peaks
indexes=detect_peaks(y,mph=th_noise,mpd=dist)
dates=rms_diff.ix[indexes].index

Cuantos eventos hemos detectado

print (str(len(indexes))+' eventos detectados')
8 eventos detectados

Dibujamos los eventos y la corriente en una misma gráfica

plt.vlines(dates,-80,80)
df.i.plot()
<matplotlib.axes._subplots.AxesSubplot at 0x7f6e2f79ca58>

Métricas

from IPython.display import Image
Image(filename='metricas1.png')
Image(filename='metricas2.png')

Obtener las métricas: recall, precision y F1

FP=0.
P=9.
N=len(df)-P
TN=N-FP
P=9.
N=len(df)-P
TP=8.
FP=0.
FN=1.
TN=N-FP
recall=TP/(TP+FN)
precision=TP/(TP+FP)
F1=2*precision*recall/(precision+recall)
print (recall)
print (precision)
print (F1)
0.8888888888888888
1.0
0.9411764705882353

*Parámetros optimizados: * th_noise=0.1 y dist=5

*Con esto obtenemos : * recall=1, precision=1 y F1=1;

Instalación de Anaconda en Ubuntu


Diseñado para los flujos de trabajo de ciencia de los datos y aprendizaje automático, Anaconda es un gestor de paquetes de código abierto, gestor de entornos y distribución de los lenguajes de programación Python y R.

En efecto Conda es un gestor de paquetes y un sistema de gestión de entornos de código abierto,multiplataforma y de lenguaje agnóstico publicado bajo la licencia BSD. Está escrito en el lenguaje de programación Python, pero puede gestionar proyectos que contengan código escrito en otros lenguajes, como R, así como proyecto multilenguaje. Conda puede instalar la versión de Python que se necesite en el entorno de desarrollo, al contrario que otros gestores de paquetes basados en Python, como pip o wheel.

Cuenta con «canales» (channels), que son las ubicaciones de los repositorios en los que Conda busca paquetes. Debido a que los canales se organizan jerárquicamente, al instalar un paquete Conda comprobará qué canal tiene el mayor índice de prioridad; este orden de prioridad se puede cambiar, así como también añadir nuevos canales. Los canales que se establecen por defecto son los repositorios de Continuum. Existen canales más generales, que ofrecen una amplia gama de paquetes, como conda-forge; y otros más específicos, como Bioconda, que proporciona paquetes especializados en bioinformática.

Conda está incluido en todas las versiones de Anaconda, Miniconda ​ y Anaconda Repository.

Obtención de la versión más reciente de Anaconda

Prerrequisitos

  • Una cuenta de usuario con  privilegios de sudo 
  • Acceso a una línea de comando / ventana de terminal (Ctrl-Alt-T)

Actualice el Administrador de paquetes local

Empiece por actualizar el administrador de paquetes local. Abra una ventana de terminal e ingrese lo siguiente:

sudo apt-get update

Si su sistema no tiene curl , instálelo ingresando:

sudo apt-get install curl

Descargue la última versión de Anaconda

En el momento en que se escribió este artículo, la última versión de Anaconda es 2020.02. Consulte la página de descarga del desarrollador para ver la versión más reciente.

https://www.anaconda.com/distribution/

Busque la versión más reciente para Linux y copie la secuencia de comandos bash del instalador.

Anote la URL y utilícela para descargar la versión correcta.

Cambie al directorio / tmp y use curl para descargar el instalador usando su terminal de comando:

cd /tmp
curl –O https://repo.anaconda.com/archive/Anaconda3-2020.02-Linux-x86_64.sh

Esta versión está diseñada para Python 3.7. Si está utilizando Python 2.7, utilice la URL adecuada.

Verificar la suma de comprobación de descarga

Checksum es una herramienta de seguridad que se utiliza para verificar la autenticidad e integridad de un script descargado.

Introduzca la siguiente:

sha256sum Anaconda3–2020.02–Linux–x86_64.sh

Su sistema mostrará una serie de letras y números:

69581cf739365ec7fb95608eef694ba959d7d33b36eb961953f2b82cb25bdf5a Anaconda3-2019.07-Linux-x86_64.sh

Compárelos con la suma de comprobación apropiada (o hash ) en la documentación de Anaconda . Si ha elegido una versión diferente, asegúrese de consultar la documentación para ver la suma de comprobación de esa versión.

Ejecución de la secuencia de comandos de Anaconda

Una vez que acepte la licencia, se le pedirá que seleccione la ubicación de la instalación. Puede pulsar ENTER para aceptar la ubicación predeterminada o especificar una ubicación diferente.


bash Anaconda3-2019.03-Linux-x86_64.sh

Recibirá el siguiente resultado para revisar el acuerdo de licencia pulsando ENTER hasta llegar al final.

Output
Welcome to Anaconda3 2019.03

In order to continue the installation process, please review the license
agreement.
Please, press ENTER to continue
>>>
...
Do you approve the license terms? [yes|no]

Cuando llegue al final de la licencia, escriba yes, si acepta la licencia, para completar la instalación.

En este momento, se procederá con la instalación. Tenga en cuenta que el proceso puede tardar un tiempo.

Cuando se complete la instalación, recibirá el siguiente resultado:

Output...
installation finished.
Do you wish the installer to prepend the Anaconda3 install location
to PATH in your /home/sammy/.bashrc ? [yes|no]
[no] >>> 

Se recomienda que escriba yes para usar el comando conda.

Activación de la instalación

Ahora, puede activar la instalación con el siguiente comando:

source ~/.bashrc

Instalación de prueba

Utilice el comando conda para probar la instalación y la activación:

conda list

Recibirá el resultado de todos los paquetes que tiene disponibles a través de la instalación de Anaconda.

Configuración de los entornos de Anaconda

Puede crear entornos de Anaconda con el comando conda create. Por ejemplo, se puede crear un entorno de Python 3 llamado my_envcon el siguiente comando:

conda create --name my_env python=3

Active el nuevo entorno de esta manera:

conda activate my_env

El prefijo de instrucción de su comando cambiará para reflejar que está en un entorno de Anaconda activo, y que, ahora, está listo para empezar a trabajar en un proyecto.

Cómo importar módulos en Python 3


En este post vamos a ver como haciendo uso de módulos nos permite hacer que nuestros programas sean más robustos y poderosos a medida que aprovechamos el código existente. También podemos crear nuestros propios módulos para nosotros y para que otros programadores los utilicen en programas futuros.

El lenguaje de programación Python viene con una variedad de funciones integradas . Entre estas se encuentran varias funciones comunes, que incluyen:

  • print() que imprime expresiones
  • abs() que devuelve el valor absoluto de un número
  • int() que convierte otro tipo de datos en un entero
  • len() que devuelve la longitud de una secuencia o colección

Sin embargo, estas funciones integradas son limitadas y podemos hacer uso de módulos para hacer programas más sofisticados.

Los módulos son archivos con extension .py de Python que constan lógicamente de código Python. Se puede hacer referencia a cualquier archivo de Python como módulo. Un archivo de Python llamado hello.py tiene el nombre de módulo hello que puede importarse a otros archivos de Python o usarse en el intérprete de línea de comandos de Python.

Los módulos pueden definir funciones , clases y variables a las que puede hacer referencia en otros archivos .py de Python o mediante el intérprete de línea de comandos de Python.

En Python, se accede a los módulos mediante la instrucción import . Cuando hacemos esto, ejecutamos el código del módulo, manteniendo los alcances de las definiciones para que tus archivos actuales puedan hacer uso de estos.

Cuando Python importa un módulo llamado, hello por ejemplo, el intérprete primero buscará un módulo incorporado llamado hello. Si no se encuentra un módulo integrado, el intérprete de Python buscará un archivo nombrado hello.py en una lista de directorios que recibe de la variable sys.path.

Vamos a ver la verificación e instalación de módulos, la importación de módulos y los módulos de alias.

Comprobación e instalación de módulos

Hay varios módulos que están integrados en la biblioteca estándar de Python , que contiene muchos módulos que brindan acceso a la funcionalidad del sistema o brindan soluciones estandarizadas. La biblioteca estándar de Python es parte de cada instalación de Python.

Para comprobar que estos módulos de Python están listos para funcionar, ingrese a su entorno de programación Python 3 local o al entorno de programación basado en servidor e inicie el intérprete de Python en su línea de comando así:


 python 

Desde dentro del intérprete, puede ejecutar la declaración import para asegurarse de que el módulo dado esté listo para ser llamado, como en:


 import math 

Dado que math es un módulo integrado, su intérprete debe completar la tarea sin comentarios, volviendo a la indicación. Esto significa que no necesita hacer nada para comenzar a usar el módulo math.

Ejecutemos la declaración import con un módulo que quizás no haya instalado, como la biblioteca de trazado 2D matplotlib:


 import matplotlib 

Si matplotlib no está instalado, recibirá un error como este:

       
        Output
       ImportError: No module named 'matplotlib'

Puede desactivar el intérprete de Python con CTRL + D y luego instalarlo matplotlibcon pip.

A continuación, podemos utilizar pip para instalar el módulo matplotlib:


 pip install matplotlib 

Una vez que esté instalado, puede importar matplotlib en el intérprete de Python usando import matplotlib, y se completará sin errores.

Importación de módulos

Para hacer uso de las funciones de un módulo, deberá importar el módulo con una declaración import

Una declaracion import se compone de la palabra clave import junto con el nombre del módulo.

En un archivo de Python, esto se declarará en la parte superior del código, debajo de las líneas shebang o comentarios generales.

Entonces, en el archivo de programa de Python my_rand_int.py importaríamos el modulo random para generar números aleatorios de esta manera: my_rand_int.py

import random

Cuando importamos un módulo, lo ponemos a nuestra disposición en nuestro programa actual como un espacio de nombres separado. Esto significa que tendremos que referirnos a la función en notación de puntos, como en [module].[function].

En la práctica, con el ejemplo del módulo random, esto puede parecer una función como:

  • random.randint() que llama a la función para devolver un entero aleatorio, o
  • random.randrange() que llama a la función para devolver un elemento aleatorio de un rango especificado.

Creemos un ciclo for para mostrar cómo llamaremos a una función del módulo random dentro de nuestro programa: my_rand_int.py

import random


for i in range(10):
    print(random.randint(1, 25))

Este pequeño programa primero importa el módulo random en la primera línea, luego se mueve a un ciclo for que trabajará con 10 elementos. Dentro del ciclo, el programa imprimirá un número entero aleatorio dentro del rango de 1 a 25 (inclusive). Los enteros 1 y 25se pasan a random.randint()como sus parámetros.

Cuando ejecutamos el programa con python my_rand_int.py, recibiremos 10 enteros aleatorios como salida. Debido a que estos son aleatorios, es probable que obtenga diferentes enteros cada vez que ejecute el programa, pero se verán así:


       
        Output
       6
9
1
14
3
22
10
1
15
9

Los números enteros nunca deben estar por debajo de 1 o por encima de 25.

Si desea utilizar funciones de más de un módulo, puede hacerlo agregando varias declaraciones import a my_rand_int.py

import random
import math

Es posible que vea programas que importan varios módulos con comas que los separan, como en import random, math, pero esto no es coherente con la Guía de estilo de PEP 8 .

Para hacer uso de nuestro módulo adicional, podemos agregar la constante pi de math a nuestro programa y disminuir el número de enteros aleatorios impresos: my_rand_int.py

import random
import math


for i in range(5):
    print(random.randint(1, 25))

print(math.pi)

Ahora, cuando ejecutamos nuestro programa, recibiremos una salida que se ve así, con una aproximación de pi como nuestra última línea de salida:


       
        Output
       18
10
7
13
10
3.141592653589793

La declaración import le permite importar uno o más módulos a su programa Python, permitiéndole hacer uso de las definiciones construidas en esos módulos.

Usando fromimport

Para hacer referencia a elementos de un módulo dentro del espacio de nombres de su programa, puede usar la declaración fromimport. Cuando importa módulos de esta manera, puede hacer referencia a las funciones por su nombre en lugar de mediante la notación de puntos

En esta construcción, puede especificar qué definiciones hacer referencia directamente.

En otros programas, puede ver que la declaración import toma referencias a todo lo definido dentro del módulo mediante el uso de un asterisco ( *) como comodín, pero PEP 8 lo desaconseja. .

Primero veamos la importación de una función específica, randint()desde el módulo random en my_rand_int.py

from random import randint

Aquí, primero llamamos a la palabra clave from, luego al módulo random. A continuación, usamos la palabra clave import y llamamos a la función específica que nos gustaría usar.

Ahora, cuando implementemos esta función dentro de nuestro programa, ya no escribiremos la función en notación de puntos como, random.randint()sino que simplemente escribiremos randint() en my_rand_int.py

from random import randint


for i in range(10):
    print(randint(1, 25))

Cuando ejecute el programa, recibirá un resultado similar al que recibimos anteriormente.

Usando la construcción fromimport nos permite hacer referencia a los elementos definidos de un módulo dentro del espacio de nombres de nuestro programa, lo que nos permite evitar la notación de puntos.

Módulos de aliasing

Es posible modificar los nombres de los módulos y sus funciones dentro de Python usando la palabra clave as.

Es posible que desee cambiar un nombre porque ya ha usado el mismo nombre para otra cosa en su programa, otro módulo que ha importado también usa ese nombre, o puede abreviar un nombre más largo que está usando mucho.

La construcción de esta declaración se ve así:

import [module] as [another_name]

Modificaremos el nombre del módulo math en nuestro archivo de programa my_math.py. Cambiaremos el nombre del módulo de matha m para abreviarlo. Nuestro programa modificado se verá así: my_math.py

import math as m


print(m.pi)
print(m.e)

Dentro del programa, ahora nos referimos a la constante pi como m.pi en lugar de math.pi .

Para algunos módulos, es habitual utilizar alias. La documentación oficial del módulomatplotlib.pyplotr equiere el uso de pltcomo alias:

import matplotlib.pyplot as plt

Esto permite a los programadores agregar la palabra más corta plt a cualquiera de las funciones disponibles dentro del módulo, como en plt.show().

.

Programación MPI


Diseño de MPI para el modelo de transmisión de mensajes

Antes de comenzar que es MPI vermos un par de conceptos clásicos detrás del diseño de MPI del modelo de transmisión de mensajes de programación paralela. El primer concepto es la noción de comunicador . Un comunicador define un grupo de procesos que tienen la capacidad de comunicarse entre sí. En este grupo de procesos, a cada uno se le asigna un rango único y se comunican explícitamente entre sí por sus rangos.

La base de la comunicación se basa en operaciones de envío y recepción entre procesos. Un proceso puede enviar un mensaje a otro proceso proporcionando el rango del proceso y una etiqueta única para identificar el mensaje. El receptor puede publicar una recepción para un mensaje con una etiqueta determinada (o puede que ni siquiera le importe la etiqueta) y luego manejar los datos en consecuencia. Las comunicaciones como ésta, que involucran a un remitente y un receptor, se conocen como comunicaciones punto a punto .

Hay muchos casos en los que los procesos pueden necesitar comunicarse con todos los demás. Por ejemplo, cuando un proceso de administrador necesita transmitir información a todos sus procesos de trabajo. En este caso, sería engorroso escribir código que haga todos los envíos y recibos. De hecho, a menudo no utilizaría la red de forma óptima. MPI puede manejar una amplia variedad de estos tipos de comunicaciones colectivas que involucran todos los procesos.

Se pueden utilizar mezclas de comunicaciones colectivas y punto a punto para crear programas paralelos muy complejos. De hecho, esta funcionalidad es tan poderosa que ni siquiera es necesario comenzar a describir los mecanismos avanzados de MPI.

Hello World en MPI

Vamos a sumergirnos en el código de esta lección que se encuentra en mpi_hello_world.c . A continuación se muestran algunos extractos del código.

Notará que el primer paso para crear un programa MPI es incluir los archivos de encabezado MPI con #include <mpi.h>. Después de esto, el entorno MPI debe inicializarse con:

MPI_Init(
    int* argc,
    char*** argv)

Durante MPI_Init, se construyen todas las variables globales e internas de MPI. Por ejemplo, se forma un comunicador en torno a todos los procesos que se generaron y se asignan rangos únicos a cada proceso. Actualmente, MPI_Inittoma dos argumentos que no son necesarios, y los parámetros adicionales simplemente se dejan como espacio adicional en caso de que futuras implementaciones los necesiten.

#include <mpi.h> #include <stdio.h> 
int main(int argc, char** argv) {
    // Initialize the MPI environment
    MPI_Init(NULL, NULL);

    // Get the number of processes
    int world_size;
    MPI_Comm_size(MPI_COMM_WORLD, &world_size);

    // Get the rank of the process
    int world_rank;
    MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);

    // Get the name of the processor
    char processor_name[MPI_MAX_PROCESSOR_NAME];
    int name_len;
    MPI_Get_processor_name(processor_name, &name_len);

    // Print off a hello world message
    printf("Hello world from processor %s, rank %d out of %d processors\n",
           processor_name, world_rank, world_size);

    // Finalize the MPI environment.
    MPI_Finalize();
}

Después MPI_Init, hay dos funciones principales que se llaman. Estas dos funciones se utilizan en casi todos los programas MPI que escribirá.

MPI_Comm_size(
    MPI_Comm communicator,
    int* size)

MPI_Comm_sizedevuelve el tamaño de un comunicador. En nuestro ejemplo, MPI_COMM_WORLD(que MPI construye para nosotros) incluye todos los procesos en el trabajo, por lo que esta llamada debe devolver la cantidad de procesos que se solicitaron para el trabajo.

MPI_Comm_rank(
    MPI_Comm communicator,
    int* rank)

MPI_Comm_rankdevuelve el rango de un proceso en un comunicador. A cada proceso dentro de un comunicador se le asigna un rango incremental a partir de cero. Los rangos de los procesos se utilizan principalmente con fines de identificación al enviar y recibir mensajes.

Una función miscelánea y menos utilizada en este programa es:

MPI_Get_processor_name(
    char* name,
    int* name_length)

MPI_Get_processor_nameobtiene el nombre real del procesador en el que se está ejecutando el proceso. La última convocatoria de este programa es:

MPI_Finalize()

MPI_Finalizese utiliza para limpiar el entorno MPI. No se pueden realizar más llamadas MPI después de esta.

Ejecución de la aplicación MPI hello world

Ahora revise el código y examine la carpeta del código. En él hay un archivo MAKE.

>>> git clone https://github.com/mpitutorial/mpitutorial
>>> cd mpitutorial/tutorials/mpi-hello-world/code
>>> cat makefile
EXECS=mpi_hello_world
MPICC?=mpicc

all: ${EXECS}

mpi_hello_world: mpi_hello_world.c
    ${MPICC} -o mpi_hello_world mpi_hello_world.c

clean:
    rm ${EXECS}

Mi archivo MAKE busca la variable de entorno MPICC. Si instaló MPICH2 en un directorio local, configure su variable de entorno MPICC para que apunte a su binario mpicc. El programa mpicc en su instalación es realmente un envoltorio alrededor de gcc, y hace que compilar y vincular todas las rutinas MPI necesarias sea mucho más fácil.

>>> export MPICC=/home/kendall/bin/mpicc
>>> make
/home/kendall/bin/mpicc -o mpi_hello_world mpi_hello_world.c

Una vez compilado el programa, estará listo para ejecutarse. Ahora viene la parte en la que es posible que deba realizar alguna configuración adicional. Si está ejecutando programas MPI en un grupo de nodos, deberá configurar un archivo de host. Si simplemente está ejecutando MPI en una computadora portátil o en una sola máquina, ignore la siguiente información.

El archivo de host contiene los nombres de todas las computadoras en las que se ejecutará su trabajo MPI. Para facilitar la ejecución, debe asegurarse de que todas estas computadoras tengan acceso SSH, y también debe configurar un archivo de claves autorizadas para evitar una solicitud de contraseña para SSH. Mi archivo de host se ve así.

>>> cat host_file
cetus1
cetus2
cetus3
cetus4

Para el script de ejecución que proporcioné en la descarga, debe establecer una variable de entorno llamada MPI_HOSTS y hacer que apunte a su archivo de hosts. Mi script lo incluirá automáticamente en la línea de comandos cuando se inicie el trabajo MPI. Si no necesita un archivo de hosts, simplemente no configure la variable de entorno. Además, si tiene una instalación local de MPI, debe configurar la variable de entorno MPIRUN para que apunte al binario mpirun de la instalación.

Una vez hecho esto, puede usar el script de python run.py que se incluye en el repositorio principal. Se almacena en el directorio de tutoriales y puede ejecutar cualquier programa en todos los tutoriales (también intenta compilar los ejecutables antes de que se ejecuten). Intente lo siguiente desde la carpeta raíz mpitutorial.

>>> export MPIRUN=/home/kendall/bin/mpirun
>>> export MPI_HOSTS=host_file
>>> cd tutorials
>>> ./run.py mpi_hello_world
/home/kendall/bin/mpirun -n 4 -f host_file ./mpi_hello_world
Hello world from processor cetus2, rank 1 out of 4 processors
Hello world from processor cetus1, rank 0 out of 4 processors
Hello world from processor cetus4, rank 3 out of 4 processors
Hello world from processor cetus3, rank 2 out of 4 processors

Como era de esperar, el programa MPI se inició en todos los hosts de mi archivo de host. A cada proceso se le asignó un rango único, que se imprimió junto con el nombre del proceso. Como se puede ver en mi salida de ejemplo, la salida de los procesos está en un orden arbitrario ya que no hay sincronización involucrada antes de la impresión.

Observe cómo el script se llama mpirun. Este es el programa que utiliza la implementación de MPI para iniciar el trabajo. Los procesos se generan en todos los hosts del archivo de host y el programa MPI se ejecuta en cada proceso. Mi script proporciona automáticamente el indicador -n para establecer el número de procesos MPI en cuatro. ¡Intente cambiar el script de ejecución y lanzar más procesos! Sin embargo, no bloquee accidentalmente su sistema. 🙂

Ahora puede estar preguntando: “Mis hosts son en realidad máquinas de doble núcleo. ¿Cómo puedo hacer que MPI genere procesos en los núcleos individuales primero antes que en las máquinas individuales? » La solución es bastante sencilla. Simplemente modifique su archivo de hosts y coloque dos puntos y el número de núcleos por procesador después del nombre de host. Por ejemplo, especifiqué que cada uno de mis hosts tiene dos núcleos.

>>> cat host_file
cetus1:2
cetus2:2
cetus3:2
cetus4:2

Cuando vuelva a ejecutar el script de ejecución, ¡voilá! , el trabajo MPI genera dos procesos en solo dos de mis hosts.

>>> ./run.py mpi_hello_world
/home/kendall/bin/mpirun -n 4 -f host_file ./mpi_hello_world
Hello world from processor cetus1, rank 0 out of 4 processors
Hello world from processor cetus2, rank 2 out of 4 processors
Hello world from processor cetus2, rank 3 out of 4 processors
Hello world from processor cetus1, rank 1 out of 4 processors

Enviar y recibir son los dos conceptos fundamentales de MPI. Casi todas las funciones de MPI se pueden implementar con llamadas básicas de envío y recepción. En esta lección, discutiré cómo usar las funciones de envío y recepción de bloqueo de MPI, y también describiré otros conceptos básicos asociados con la transmisión de datos usando MPI.

Descripción general de envío y recepción con MPI

Las llamadas de envío y recepción de MPI funcionan de la siguiente manera. En primer lugar, el proceso A decide que necesita un mensaje que se enviará al proceso B . El proceso A luego empaqueta todos sus datos necesarios en un búfer para el proceso B. Estos búferes a menudo se denominan sobres, ya que los datos se empaquetan en un solo mensaje antes de la transmisión (similar a cómo las cartas se empaquetan en sobres antes de la transmisión al oficina de correos). Una vez que los datos se empaquetan en un búfer, el dispositivo de comunicación (que a menudo es una red) es responsable de enrutar el mensaje a la ubicación adecuada. La ubicación del mensaje está definida por el rango del proceso.

Aunque el mensaje se enruta a B, el proceso B todavía tiene que reconocer que desea recibir los datos de A. Una vez que hace esto, los datos se han transmitido. El proceso A reconoce que los datos se han transmitido y puede volver a funcionar.

A veces, hay casos en los que A podría tener que enviar muchos tipos diferentes de mensajes a B. En lugar de que B tenga que pasar por medidas adicionales para diferenciar todos estos mensajes, MPI permite a los remitentes y receptores especificar también las ID de mensaje con el mensaje (conocidas como etiquetas ). Cuando el proceso B solo solicita un mensaje con un determinado número de etiqueta, la red almacenará en búfer los mensajes con etiquetas diferentes hasta que B esté listo para recibirlos.

Con estos conceptos en mente, veamos los prototipos de las funciones de envío y recepción de MPI.

MPI_Send(
    void* data,
    int count,
    MPI_Datatype datatype,
    int destination,
    int tag,
    MPI_Comm communicator)

MPI_Recv(
    void* data,
    int count,
    MPI_Datatype datatype,
    int source,
    int tag,
    MPI_Comm communicator,
    MPI_Status* status)

Aunque esto puede parecer un bocado al leer todos los argumentos, se vuelven más fáciles de recordar ya que casi todas las llamadas MPI usan una sintaxis similar. El primer argumento es el búfer de datos. El segundo y tercer argumento describen el recuento y el tipo de elementos que residen en el búfer. MPI_Sendenvía el recuento exacto de elementos y MPI_Recvrecibirá como máximo el recuento de elementos (más sobre esto en la próxima lección). Los argumentos cuarto y quinto especifican el rango del proceso de envío / recepción y la etiqueta del mensaje. El sexto argumento especifica el comunicador y el último argumento ( MPI_Recvsolo para ) proporciona información sobre el mensaje recibido.

Tipos de datos MPI elementales

Las funciones MPI_SendMPI_Recvutilizan tipos de datos MPI como un medio para especificar la estructura de un mensaje en un nivel superior. Por ejemplo, si el proceso desea enviar un número entero a otro, usaría un recuento de uno y un tipo de datos de MPI_INT. Los otros tipos de datos MPI elementales se enumeran a continuación con sus tipos de datos C equivalentes.

Tipo de datos MPIEquivalente de C
MPI_SHORTint corto
MPI_INTEn t
MPI_LONGint largo
MPI_LONG_LONGlargo largo int
MPI_UNSIGNED_CHARchar sin firmar
MPI_UNSIGNED_SHORTint corto sin firmar
MPI_UNSIGNEDint sin firmar
MPI_UNSIGNED_LONGunsigned long int
MPI_UNSIGNED_LONG_LONGunsigned long long int
MPI_FLOATflotador
MPI_DOUBLEdoble
MPI_LONG_DOUBLEdoble largo
MPI_BYTEcarbonizarse

Por ahora, solo haremos uso de estos tipos de datos en los siguientes tutoriales de MPI en la categoría de principiantes. Una vez que hayamos cubierto suficientes conceptos básicos, aprenderá a crear sus propios tipos de datos MPI para caracterizar tipos de mensajes más complejos.

Programa de envío / recepción MPI

El primer ejemplo del código del tutorial está en send_recv.c . Algunas de las partes principales del programa se muestran a continuación.

// Find out rank, size
int world_rank;
MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
int world_size;
MPI_Comm_size(MPI_COMM_WORLD, &world_size);

int number;
if (world_rank == 0) {
    number = -1;
    MPI_Send(&number, 1, MPI_INT, 1, 0, MPI_COMM_WORLD);
} else if (world_rank == 1) {
    MPI_Recv(&number, 1, MPI_INT, 0, 0, MPI_COMM_WORLD,
             MPI_STATUS_IGNORE);
    printf("Process 1 received number %d from process 0\n",
           number);
}

MPI_Comm_rankMPI_Comm_sizese utilizan primero para determinar el tamaño del mundo junto con el rango del proceso. Luego, el proceso cero inicializa un número con el valor de uno negativo y envía este valor al proceso uno. Como puede ver en la else ifdeclaración, el proceso uno está llamando MPI_Recvpara recibir el número. También imprime el valor recibido. Dado que estamos enviando y recibiendo exactamente un número entero, cada proceso solicita que MPI_INTse envíe / reciba uno. Cada proceso también usa un número de etiqueta cero para identificar el mensaje. Los procesos también podrían haber utilizado la constante predefinida MPI_ANY_TAGpara el número de etiqueta, ya que solo se estaba transmitiendo un tipo de mensaje.

Puede ejecutar el código de ejemplo comprobándolo en GitHub y usando el run.pyscript.

>>> git clone https://github.com/mpitutorial/mpitutorial
>>> cd mpitutorial/tutorials
>>> ./run.py send_recv
mpirun -n 2 ./send_recv
Process 1 received number -1 from process 0

Como era de esperar, el proceso uno recibe uno negativo del proceso cero.

Programa de ping pong MPI

El siguiente ejemplo es un programa de ping pong. En este ejemplo, los procesos usan MPI_SendMPI_Recvpara rebotar continuamente mensajes entre sí hasta que deciden detenerse. Eche un vistazo a ping_pong.c . Las partes principales del código se ven así.

int ping_pong_count = 0;
int partner_rank = (world_rank + 1) % 2;
while (ping_pong_count < PING_PONG_LIMIT) {
    if (world_rank == ping_pong_count % 2) {
        // Increment the ping pong count before you send it
        ping_pong_count++;
        MPI_Send(&ping_pong_count, 1, MPI_INT, partner_rank, 0,
                 MPI_COMM_WORLD);
        printf("%d sent and incremented ping_pong_count "
               "%d to %d\n", world_rank, ping_pong_count,
               partner_rank);
    } else {
        MPI_Recv(&ping_pong_count, 1, MPI_INT, partner_rank, 0,
                 MPI_COMM_WORLD, MPI_STATUS_IGNORE);
        printf("%d received ping_pong_count %d from %d\n",
               world_rank, ping_pong_count, partner_rank);
    }
}

Este ejemplo está destinado a ejecutarse con solo dos procesos. Los procesos primero determinan a su pareja con algo de aritmética simple. A ping_pong_countse inicia a cero y se incrementa en cada paso de ping pong por el proceso de envío. A medida que ping_pong_countse incrementa, los procesos se turnan para ser emisor y receptor. Finalmente, después de que se alcanza el límite (diez en mi código), los procesos dejan de enviar y recibir. La salida del código de ejemplo se verá así.

>>> ./run.py ping_pong
0 sent and incremented ping_pong_count 1 to 1
0 received ping_pong_count 2 from 1
0 sent and incremented ping_pong_count 3 to 1
0 received ping_pong_count 4 from 1
0 sent and incremented ping_pong_count 5 to 1
0 received ping_pong_count 6 from 1
0 sent and incremented ping_pong_count 7 to 1
0 received ping_pong_count 8 from 1
0 sent and incremented ping_pong_count 9 to 1
0 received ping_pong_count 10 from 1
1 received ping_pong_count 1 from 0
1 sent and incremented ping_pong_count 2 to 0
1 received ping_pong_count 3 from 0
1 sent and incremented ping_pong_count 4 to 0
1 received ping_pong_count 5 from 0
1 sent and incremented ping_pong_count 6 to 0
1 received ping_pong_count 7 from 0
1 sent and incremented ping_pong_count 8 to 0
1 received ping_pong_count 9 from 0
1 sent and incremented ping_pong_count 10 to 0

La salida de los programas en otras máquinas probablemente será diferente debido a la programación del proceso. Sin embargo, como puede ver, el proceso cero y uno se turnan para enviar y recibir el contador de ping pong entre sí.

Programa de timbre

He incluido un ejemplo más MPI_Sendy el MPI_Recvuso de más de dos procesos. En este ejemplo, todos los procesos pasan un valor en forma de anillo. Echar un vistazo a ring.c . La mayor parte del código se ve así.

int token;
if (world_rank != 0) {
    MPI_Recv(&token, 1, MPI_INT, world_rank - 1, 0,
             MPI_COMM_WORLD, MPI_STATUS_IGNORE);
    printf("Process %d received token %d from process %d\n",
           world_rank, token, world_rank - 1);
} else {
    // Set the token's value if you are process 0
    token = -1;
}
MPI_Send(&token, 1, MPI_INT, (world_rank + 1) % world_size,
         0, MPI_COMM_WORLD);

// Now process 0 can receive from the last process.
if (world_rank == 0) {
    MPI_Recv(&token, 1, MPI_INT, world_size - 1, 0,
             MPI_COMM_WORLD, MPI_STATUS_IGNORE);
    printf("Process %d received token %d from process %d\n",
           world_rank, token, world_size - 1);
}

El programa de anillo inicializa un valor a partir del proceso cero, y el valor se pasa alrededor de cada proceso. El programa termina cuando el proceso cero recibe el valor del último proceso. Como puede ver en el programa, se tiene especial cuidado para asegurar que no se bloquee. En otras palabras, el proceso cero se asegura de que haya completado su primer envío antes de intentar recibir el valor del último proceso. Todos los demás procesos simplemente llaman MPI_Recv(recibiendo de su proceso inferior vecino) y luego MPI_Send(enviando el valor a su proceso superior vecino) para pasar el valor a lo largo del anillo. MPI_SendyMPI_Recvse bloqueará hasta que se transmita el mensaje. Debido a esto, printfs debería ocurrir en el orden en que se pasa el valor. Usando cinco procesos, la salida debería verse así.

>>> ./run.py ring
Process 1 received token -1 from process 0
Process 2 received token -1 from process 1
Process 3 received token -1 from process 2
Process 4 received token -1 from process 3
Process 0 received token -1 from process 4

Como podemos ver, el proceso cero primero envía un valor negativo uno al proceso uno. Este valor se pasa por el anillo hasta que vuelve al cero del proceso.

Aunque es posible enviar la longitud del mensaje como una operación de envío / recepción separada, MPI admite mensajes dinámicos de forma nativa con solo unas pocas llamadas de función adicionales. En esta lección, repasaré cómo usar estas funciones.

La estructura MPI_Status

La funcion  MPI_Recvoperación toma la dirección de una MPI_Statusestructura como argumento (que se puede ignorar con MPI_STATUS_IGNORE). Si pasamos una MPI_Statusestructura a la MPI_Recvfunción, se completará con información adicional sobre la operación de recepción después de que se complete. Los tres elementos principales de información incluyen:

  1. El rango del remitente . El rango del remitente se almacena en el MPI_SOURCEelemento de la estructura. Es decir, si declaramos una MPI_Status statvariable, se puede acceder al rango con stat.MPI_SOURCE.
  2. La etiqueta del mensaje . Se puede acceder a la etiqueta del mensaje mediante el MPI_TAGelemento de la estructura (similar a MPI_SOURCE).
  3. La longitud del mensaje . La longitud del mensaje no tiene un elemento predefinido en la estructura de estado. En cambio, tenemos que averiguar la longitud del mensaje con MPI_Get_count.
MPI_Get_count(
    MPI_Status* status,
    MPI_Datatype datatype,
    int* count)

En MPI_Get_count, el usuario pasa la MPI_Statusestructura, la datatypedel mensaje y countse devuelve. La countvariable es el número total de datatypeelementos que se recibieron.

¿Por qué sería necesaria esta información? Resulta que se MPI_Recvpuede tomar MPI_ANY_SOURCEpor el rango del remitente y MPI_ANY_TAGpor la etiqueta del mensaje. En este caso, la MPI_Statusestructura es la única forma de averiguar el remitente real y la etiqueta del mensaje. Además, MPI_Recvno se garantiza que reciba la cantidad total de elementos pasados ​​como argumento para la llamada a la función. En cambio, recibe la cantidad de elementos que se le enviaron (y devuelve un error si se enviaron más elementos que la cantidad de recepción deseada). La función MPI_Get_count se utiliza para determinar la cantidad recibida real.

Un ejemplo de consulta de la estructura MPI_Status

El programa que consulta la MPI_Statusestructura está en check_status.c . El programa envía una cantidad aleatoria de números a un receptor, y el receptor averigua cuántos números se enviaron. La parte principal del código se ve así.

const int MAX_NUMBERS = 100;
int numbers[MAX_NUMBERS];
int number_amount;
if (world_rank == 0) {
    // Pick a random amount of integers to send to process one
    srand(time(NULL));
    number_amount = (rand() / (float)RAND_MAX) * MAX_NUMBERS;

    // Send the amount of integers to process one
    MPI_Send(numbers, number_amount, MPI_INT, 1, 0, MPI_COMM_WORLD);
    printf("0 sent %d numbers to 1\n", number_amount);
} else if (world_rank == 1) {
    MPI_Status status;
    // Receive at most MAX_NUMBERS from process zero
    MPI_Recv(numbers, MAX_NUMBERS, MPI_INT, 0, 0, MPI_COMM_WORLD,
             &status);

    // After receiving the message, check the status to determine
    // how many numbers were actually received
    MPI_Get_count(&status, MPI_INT, &number_amount);

    // Print off the amount of numbers, and also print additional
    // information in the status object
    printf("1 received %d numbers from 0. Message source = %d, "
           "tag = %d\n",
           number_amount, status.MPI_SOURCE, status.MPI_TAG);
}

Como podemos ver, el proceso cero envía aleatoriamente hasta MAX_NUMBERSnúmeros enteros para procesar uno. El proceso uno luego requiere MPI_Recvun total de MAX_NUMBERSnúmeros enteros. Aunque el proceso uno pasa MAX_NUMBERScomo argumento MPI_Recv, el proceso uno recibirá como máximo esta cantidad de números. En el código, procese una llamada MPI_Get_countcon MPI_INTcomo tipo de datos para averiguar cuántos enteros se recibieron realmente. Además de imprimir el tamaño del mensaje recibido, el proceso uno también imprime la fuente y la etiqueta del mensaje accediendo a los elementos MPI_SOURCEMPI_TAGde la estructura de estado.

Como aclaración, el valor de retorno de MPI_Get_countes relativo al tipo de datos que se pasa. Si el usuario lo usara MPI_CHARcomo tipo de datos, la cantidad devuelta sería cuatro veces mayor (asumiendo que un número entero tiene cuatro bytes y un carácter es un byte). Si ejecuta el programa check_status desde el directorio de tutoriales del repositorio , la salida debería verse similar a esta.

>>> cd tutorials
>>> ./run.py check_status
mpirun -n 2 ./check_status
0 sent 92 numbers to 1
1 received 92 numbers from 0. Message source = 0, tag = 0

Como se esperaba, el proceso cero envía una cantidad aleatoria de números enteros para procesar uno, que imprime información sobre el mensaje recibido.

Usando MPI_Probe para averiguar el tamaño del mensaje

Ahora que comprende cómo funciona el MPI_Statusobjeto, podemos usarlo un poco más en nuestro beneficio. En lugar de publicar una recepción y simplemente proporcionar un búfer realmente grande para manejar todos los tamaños posibles de mensajes (como hicimos en el último ejemplo), puede usar MPI_Probepara consultar el tamaño del mensaje antes de recibirlo. El prototipo de la función se ve así.

MPI_Probe(
    int source,
    int tag,
    MPI_Comm comm,
    MPI_Status* status)

MPI_Probeparece bastante similar a MPI_Recv. De hecho, se puede pensar en MPI_Probecomo MPI_Recvque lo hace todo, pero recibir el mensaje. Similar a MPI_RecvMPI_Probese bloqueará para un mensaje con una etiqueta y un remitente coincidentes. Cuando el mensaje esté disponible, llenará la estructura de estado con información. El usuario puede utilizar MPI_Recvpara recibir el mensaje real.

Así es como se ve el código fuente principal.

int number_amount;
if (world_rank == 0) {
    const int MAX_NUMBERS = 100;
    int numbers[MAX_NUMBERS];
    // Pick a random amount of integers to send to process one
    srand(time(NULL));
    number_amount = (rand() / (float)RAND_MAX) * MAX_NUMBERS;

    // Send the random amount of integers to process one
    MPI_Send(numbers, number_amount, MPI_INT, 1, 0, MPI_COMM_WORLD);
    printf("0 sent %d numbers to 1\n", number_amount);
} else if (world_rank == 1) {
    MPI_Status status;
    // Probe for an incoming message from process zero
    MPI_Probe(0, 0, MPI_COMM_WORLD, &status);

    // When probe returns, the status object has the size and other
    // attributes of the incoming message. Get the message size
    MPI_Get_count(&status, MPI_INT, &number_amount);

    // Allocate a buffer to hold the incoming numbers
    int* number_buf = (int*)malloc(sizeof(int) * number_amount);

    // Now receive the message with the allocated buffer
    MPI_Recv(number_buf, number_amount, MPI_INT, 0, 0,
             MPI_COMM_WORLD, MPI_STATUS_IGNORE);
    printf("1 dynamically received %d numbers from 0.\n",
           number_amount);
    free(number_buf);
}

Al igual que en el último ejemplo, el proceso cero elige una cantidad aleatoria de números para enviar al proceso uno. Lo que es diferente en este ejemplo es que el proceso uno ahora llama MPI_Probepara averiguar cuántos elementos el proceso cero está tratando de enviar (usando MPI_Get_count). El proceso uno luego asigna un búfer del tamaño adecuado y recibe los números. Ejecutar el código se verá similar a esto.

>>> ./run.py probe
mpirun -n 2 ./probe
0 sent 93 numbers to 1
1 dynamically received 93 numbers from 0

Aunque este ejemplo es trivial, MPI_Probeconstituye la base de muchas aplicaciones MPI dinámicas. Por ejemplo, los programas de administrador / trabajador a menudo harán un uso intensivo de los MPI_Probemensajes de trabajadores de tamaño variable. 

Es hora de pasar por un ejemplo de aplicación utilizando algunos de los conceptos introducidos en el tutorial de envío y recepción y la lección MPI_Probe y MPI_Status . La aplicación simula un proceso al que me refiero como «caminar al azar».

La definición básica del problema de una caminata aleatoria es la siguiente. Dada una Min , Max , y al azar Walker W , hacen Walker W tomar S aleatoria de longitud arbitraria camina hacia la derecha. Si el proceso se sale de los límites, se reinicia. W solo puede mover una unidad hacia la derecha o hacia la izquierda a la vez.

Ilustración de paseo aleatorio

Aunque la aplicación en sí misma es muy básica, la paralelización de la marcha aleatoria puede simular el comportamiento de una amplia variedad de aplicaciones paralelas. Más sobre eso más tarde. Por ahora, repasemos cómo paralelizar el problema del paseo aleatorio.

Paralelización del problema de la marcha aleatoria

Nuestra primera tarea, que es pertinente para muchos programas paralelos, es dividir el dominio entre procesos. El problema de la caminata aleatoria tiene un dominio unidimensional de tamaño Max – Min + 1 (ya que Max y Min son inclusivos para el caminante). Suponiendo que los caminantes solo pueden tomar pasos de tamaño entero, podemos dividir fácilmente el dominio en fragmentos de tamaño casi igual en todos los procesos. Por ejemplo, si Min es 0 y Max es 20 y tenemos cuatro procesos, el dominio se dividiría así.

Ejemplo de descomposición de dominio

Los primeros tres procesos poseen cinco unidades del dominio, mientras que el último proceso toma las últimas cinco unidades más la unidad restante. Una vez que se ha particionado el dominio, la aplicación inicializará los caminantes. Como se explicó anteriormente, un caminante realizará caminatas S con un tamaño total de caminata aleatorio. Por ejemplo, si el caminante da un paseo de tamaño seis en el proceso cero (usando la descomposición de dominio anterior), la ejecución del caminante será así:

  1. El caminante comienza a dar pasos incrementales. Sin embargo, cuando alcanza el valor cuatro, ha alcanzado el final de los límites del proceso cero. El proceso cero ahora tiene que comunicar al caminante que procese uno.
  2. El proceso uno recibe el andador y continúa caminando hasta que alcanza su tamaño total de caminata de seis. Luego, el caminante puede continuar con una nueva caminata aleatoria.
Caminata aleatoria, paso uno

En este ejemplo, W solo tuvo que comunicarse una vez desde el proceso cero al proceso uno. Sin embargo, si W tuvo que caminar más, es posible que haya tenido que pasar por más procesos a lo largo de su camino a través del dominio.

Codificación de la aplicación usando MPI_Send y MPI_Recv

Esta aplicación se puede codificar con MPI_SendMPI_Recv. Antes de comenzar a mirar el código, establezcamos algunas características y funciones preliminares del programa:

  • Cada proceso determina su parte del dominio.
  • Cada proceso inicializa exactamente N caminantes, todos los cuales comienzan en el primer valor de su dominio local.
  • Cada andador tiene dos valores enteros asociados: la posición actual del andador y el número de pasos que quedan por dar.
  • Los caminantes comienzan a atravesar el dominio y pasan a otros procesos hasta que completan su recorrido.
  • Los procesos terminan cuando todos los caminantes terminan.

Comencemos escribiendo código para la descomposición del dominio. La función tomará el tamaño total del dominio y encontrará el subdominio apropiado para el proceso MPI. También dará cualquier resto del dominio al proceso final. Para simplificar, solo pido MPI_Abortlos errores que se encuentren. La función, llamada decompose_domain, se ve así:

void decompose_domain(int domain_size, int world_rank,
                      int world_size, int* subdomain_start,
                      int* subdomain_size) {
    if (world_size > domain_size) {
        // Don't worry about this special case. Assume the domain
        // size is greater than the world size.
        MPI_Abort(MPI_COMM_WORLD, 1);
    }
    *subdomain_start = domain_size / world_size * world_rank;
    *subdomain_size = domain_size / world_size;
    if (world_rank == world_size - 1) {
        // Give remainder to last process
        *subdomain_size += domain_size % world_size;
    }
  }

Como puede ver, la función divide el dominio en partes pares, ocupándose del caso cuando hay un resto presente. La función devuelve un inicio de subdominio y un tamaño de subdominio.

A continuación, necesitamos crear una función que inicialice a los caminantes. Primero definimos una estructura de andador que se ve así:

typedef struct {
    int location;
    int num_steps_left_in_walk;
} Walker;

Nuestra función de inicialización, llamada initialize_walkers, toma los límites del subdominio y agrega caminantes a un incoming_walkersvector (por cierto, esta aplicación está en C ++).

void initialize_walkers(int num_walkers_per_proc, int max_walk_size,
                        int subdomain_start, int subdomain_size,
                        vector<Walker>* incoming_walkers) {
    Walker walker;
    for (int i = 0; i < num_walkers_per_proc; i++) {
        // Initialize walkers in the middle of the subdomain
        walker.location = subdomain_start;
        walker.num_steps_left_in_walk =
            (rand() / (float)RAND_MAX) * max_walk_size;
        incoming_walkers->push_back(walker);
    }
}

Después de la inicialización, es hora de que los caminantes progresen. Comencemos haciendo una función de caminar. Esta función se encarga de que el caminante avance hasta que haya terminado su caminata. Si se sale de los límites locales, se agrega al outgoing_walkersvector.

void walk(Walker* walker, int subdomain_start, int subdomain_size,
          int domain_size, vector<Walker>* outgoing_walkers) {
    while (walker->num_steps_left_in_walk > 0) {
        if (walker->location == subdomain_start + subdomain_size) {
            // Take care of the case when the walker is at the end
            // of the domain by wrapping it around to the beginning
            if (walker->location == domain_size) {
                walker->location = 0;
            }
            outgoing_walkers->push_back(*walker);
            break;
        } else {
            walker->num_steps_left_in_walk--;
            walker->location++;
        }
    }
}

Ahora que hemos establecido una función de inicialización (que llena una lista de caminantes entrantes) y una función de caminar (que llena una lista de caminantes salientes), solo necesitamos dos funciones más: una función que envía caminantes salientes y una función que recibe caminantes entrantes. La función de envío se ve así:

void send_outgoing_walkers(vector<Walker>* outgoing_walkers, 
                           int world_rank, int world_size) {
    // Send the data as an array of MPI_BYTEs to the next process.
    // The last process sends to process zero.
    MPI_Send((void*)outgoing_walkers->data(), 
             outgoing_walkers->size() * sizeof(Walker), MPI_BYTE,
             (world_rank + 1) % world_size, 0, MPI_COMM_WORLD);

    // Clear the outgoing walkers
    outgoing_walkers->clear();
}

La función que recibe los caminantes entrantes debe usarla MPI_Probeya que no sabe de antemano cuántos caminantes recibirá. Esto es lo que parece:

void receive_incoming_walkers(vector<Walker>* incoming_walkers,
                              int world_rank, int world_size) {
    MPI_Status status;

    // Receive from the process before you. If you are process zero,
    // receive from the last process
    int incoming_rank =
        (world_rank == 0) ? world_size - 1 : world_rank - 1;
    MPI_Probe(incoming_rank, 0, MPI_COMM_WORLD, &status);

    // Resize your incoming walker buffer based on how much data is
    // being received
    int incoming_walkers_size;
    MPI_Get_count(&status, MPI_BYTE, &incoming_walkers_size);
    incoming_walkers->resize(
        incoming_walkers_size / sizeof(Walker));
    MPI_Recv((void*)incoming_walkers->data(), incoming_walkers_size,
             MPI_BYTE, incoming_rank, 0, MPI_COMM_WORLD,
             MPI_STATUS_IGNORE); 
}

Ahora hemos establecido las funciones principales del programa. Tenemos que unir todas estas funciones de la siguiente manera:

  1. Inicialice los caminantes.
  2. Progrese a los caminantes con la walkfunción.
  3. Envíe cualquier caminante en el outgoing_walkersvector.
  4. Reciba nuevos caminantes y colóquelos en el incoming_walkersvector.
  5. Repita los pasos del dos al cuatro hasta que todos los caminantes hayan terminado.

El primer intento de escribir este programa se encuentra a continuación. Por ahora, no nos preocuparemos de cómo determinar cuándo han terminado todos los caminantes. Antes de mirar el código, debo advertirle: ¡este código es incorrecto! Con esto en mente, echemos un vistazo a mi código y, con suerte, podrá ver qué podría estar mal en él.

// Find your part of the domain
decompose_domain(domain_size, world_rank, world_size,
                 &subdomain_start, &subdomain_size);

// Initialize walkers in your subdomain
initialize_walkers(num_walkers_per_proc, max_walk_size,
                   subdomain_start, subdomain_size,
                   &incoming_walkers);

while (!all_walkers_finished) { // Determine walker completion later
    // Process all incoming walkers
    for (int i = 0; i < incoming_walkers.size(); i++) {
        walk(&incoming_walkers[i], subdomain_start, subdomain_size,
             domain_size, &outgoing_walkers); 
    }

    // Send all outgoing walkers to the next process.
    send_outgoing_walkers(&outgoing_walkers, world_rank,
                          world_size);

    // Receive all the new incoming walkers
    receive_incoming_walkers(&incoming_walkers, world_rank,
                             world_size);
}

Todo parece normal, pero el orden de las llamadas a funciones ha introducido un escenario muy probable: punto muerto .

Interbloqueo y prevención

Según Wikipedia, el punto muerto “se refiere a una condición específica cuando dos o más procesos están esperando que el otro libere un recurso, o más de dos procesos están esperando recursos en una cadena circular. ”En nuestro caso, el código anterior resultará en una cadena circular de MPI_Sendllamadas.

Punto muerto

Vale la pena señalar que el código anterior en realidad no se bloqueará la mayor parte del tiempo. Aunque MPI_Sendes una llamada de bloqueo, la especificación MPI dice que MPI_Send se bloquea hasta que se pueda recuperar el búfer de envío. Esto significa que MPI_Sendvolverá cuando la red pueda almacenar el mensaje en búfer. Si los envíos finalmente no pueden ser almacenados en búfer por la red, se bloquearán hasta que se publique una recepción coincidente. En nuestro caso, hay suficientes envíos pequeños y recepciones coincidentes frecuentes como para no preocuparse por el punto muerto, sin embargo, nunca se debe suponer un búfer de red lo suficientemente grande.

Dado que solo nos estamos enfocando en MPI_SendMPI_Recven esta lección, la mejor manera de evitar el posible punto muerto de envío y recepción es ordenar los mensajes de manera que los envíos tengan recepciones coincidentes y viceversa. Una forma fácil de hacer esto es cambiar nuestro ciclo de modo que los procesos pares envíen caminantes salientes antes de recibir caminantes y los procesos impares hagan lo contrario. Dadas dos etapas de ejecución, el envío y la recepción ahora se verán así:

Prevención de interbloqueo

Nota : la ejecución de esto con un proceso aún puede estancarse. Para evitar esto, simplemente no realice envíos y recepciones cuando utilice un proceso.

Es posible que se esté preguntando, ¿esto todavía funciona con un número impar de procesos? Podemos volver a pasar por un diagrama similar con tres procesos:

Solución de interbloqueo

Como puede ver, en las tres etapas, hay al menos una publicación MPI_Sendque coincide con una publicada MPI_Recv, por lo que no tenemos que preocuparnos por la aparición de un punto muerto.

Determinar la finalización de todos los caminantes

Ahora viene el paso final del programa: determinar cuándo ha terminado cada caminante. Dado que los caminantes pueden caminar una longitud aleatoria, pueden terminar su viaje en cualquier proceso. Debido a esto, es difícil para todos los procesos saber cuándo han terminado todos los caminantes sin algún tipo de comunicación adicional. Una posible solución es hacer que el proceso cero realice un seguimiento de todos los caminantes que han terminado y luego les diga a todos los demás procesos cuándo terminar. Sin embargo, esta solución es bastante engorrosa ya que cada proceso tendría que informar cualquier caminante completado para procesar cero y luego también manejar diferentes tipos de mensajes entrantes.

Para esta lección, mantendremos las cosas simples. Dado que conocemos la distancia máxima que puede viajar cualquier caminante y el tamaño total más pequeño que puede viajar para cada par de envíos y recepciones (el tamaño del subdominio), podemos calcular la cantidad de envíos y recepciones que debe realizar cada proceso antes de la terminación. Usando esta característica del programa junto con nuestra estrategia para evitar el punto muerto, la parte principal final del programa se ve así:

// Find your part of the domain
decompose_domain(domain_size, world_rank, world_size,
                 &subdomain_start, &subdomain_size);

// Initialize walkers in your subdomain
initialize_walkers(num_walkers_per_proc, max_walk_size,
                  subdomain_start, subdomain_size,
                  &incoming_walkers);

// Determine the maximum amount of sends and receives needed to 
// complete all walkers
int maximum_sends_recvs =
    max_walk_size / (domain_size / world_size) + 1;
for (int m = 0; m < maximum_sends_recvs; m++) {
    // Process all incoming walkers
    for (int i = 0; i < incoming_walkers.size(); i++) {
        walk(&incoming_walkers[i], subdomain_start, subdomain_size,
             domain_size, &outgoing_walkers); 
    }

    // Send and receive if you are even and vice versa for odd
    if (world_rank % 2 == 0) {
        send_outgoing_walkers(&outgoing_walkers, world_rank,
                              world_size);
        receive_incoming_walkers(&incoming_walkers, world_rank,
                                 world_size);
    } else {
        receive_incoming_walkers(&incoming_walkers, world_rank,
                                 world_size);
        send_outgoing_walkers(&outgoing_walkers, world_rank,
                              world_size);
    }
}

Ejecutando la aplicación

 A diferencia de las los apartados anteriores este código usa C ++. Al instalar MPICH2 , también instaló el compilador MPI de C ++ (a menos que lo haya configurado explícitamente de otra manera). Si instaló MPICH2 en un directorio local, asegúrese de haber configurado su variable de entorno MPICXX para que apunte al compilador mpicxx correcto para usar mi archivo MAKE.

En el código, se debe configurar el script de ejecución de la aplicación para proporcionar valores predeterminados para el programa: 100 para el tamaño del dominio, 500 para el tamaño máximo de caminata y 20 para la cantidad de caminantes por proceso. Si ejecuta el programa random_walk desde el directorio de tutoriales del repositorio , debería generar 5 procesos y producir una salida similar a esta.

>>> cd tutorials
>>> ./run.py random_walk
mpirun -n 5 ./random_walk 100 500 20
Process 2 initiated 20 walkers in subdomain 40 - 59
Process 2 sending 18 outgoing walkers to process 3
Process 3 initiated 20 walkers in subdomain 60 - 79
Process 3 sending 20 outgoing walkers to process 4
Process 3 received 18 incoming walkers
Process 3 sending 18 outgoing walkers to process 4
Process 4 initiated 20 walkers in subdomain 80 - 99
Process 4 sending 18 outgoing walkers to process 0
Process 0 initiated 20 walkers in subdomain 0 - 19
Process 0 sending 17 outgoing walkers to process 1
Process 0 received 18 incoming walkers
Process 0 sending 16 outgoing walkers to process 1
Process 0 received 20 incoming walkers

La salida continúa hasta que los procesos terminan de enviar y recibir todos los caminantes.

Puntos de comunicación y sincronización colectivos

Una de las cosas a recordar sobre la comunicación colectiva es que implica un punto de sincronización entre procesos. Esto significa que todos los procesos deben llegar a un punto en su código antes de que todos puedan comenzar a ejecutarse nuevamente.

Antes de entrar en detalles sobre las rutinas de comunicación colectiva, examinemos la sincronización con más detalle. Resulta que MPI tiene una función especial que se dedica a sincronizar procesos:

MPI_Barrier(MPI_Comm communicator)

El nombre de la función es bastante descriptivo: la función forma una barrera y ningún proceso en el comunicador puede traspasar la barrera hasta que todos llaman a la función. He aquí una ilustración. Imagine que el eje horizontal representa la ejecución del programa y los círculos representan diferentes procesos:

Ejemplo de MPI_Barrier

Procese cero primeras llamadas MPI_Barrieren la primera instantánea (T 1). Mientras el proceso cero está colgado en la barrera, los procesos uno y tres eventualmente lo logran (T 2). Cuando el proceso dos finalmente llega a la barrera (T 3), todos los procesos comienzan a ejecutarse nuevamente (T 4).

MPI_Barrierpuede ser útil para muchas cosas. Uno de los usos principales de MPI_Barrieres sincronizar un programa para que las partes del código paralelo puedan cronometrarse con precisión.

¿Quieres saber cómo MPI_Barrierse implementa? Seguro que sí 🙂 ¿Recuerdas el programa de timbre del tutorial de envío y recepción ? Para refrescar su memoria, escribimos un programa que pasaba un token por todos los procesos en forma de anillo. Este tipo de programa es uno de los métodos más simples para implementar una barrera, ya que un token no se puede pasar por completo hasta que todos los procesos funcionen juntos.

Una nota final sobre la sincronización: recuerde siempre que cada llamada colectiva que realiza está sincronizada. En otras palabras, si no puede completar con éxito una MPI_Barrier, tampoco podrá completar con éxito ninguna llamada colectiva. Si intenta llamar MPI_Barrieru otras rutinas colectivas sin asegurarse de que todos los procesos en el comunicador también lo llamarán, su programa quedará inactivo. Esto puede resultar muy confuso para los principiantes, ¡así que ten cuidado!

Transmitiendo con MPI_Bcast

Una transmisión es una de las técnicas de comunicación colectiva estándar. Durante una transmisión, un proceso envía los mismos datos a todos los procesos en un comunicador. Uno de los usos principales de la radiodifusión es enviar la entrada del usuario a un programa paralelo o enviar parámetros de configuración a todos los procesos.

El patrón de comunicación de una transmisión se ve así:

Patrón MPI_Bcast

En este ejemplo, el proceso cero es el proceso raíz y tiene la copia inicial de datos. Todos los demás procesos reciben la copia de los datos.

En MPI, la transmisión se puede lograr usando MPI_Bcast. El prototipo de función se ve así:

MPI_Bcast(
    void* data,
    int count,
    MPI_Datatype datatype,
    int root,
    MPI_Comm communicator)

Aunque el proceso raíz y los procesos del receptor realizan trabajos diferentes, todos llaman a la misma MPI_Bcastfunción. Cuando el proceso raíz (en nuestro ejemplo, era el proceso cero) llama MPI_Bcast, la datavariable se enviará a todos los demás procesos. Cuando todos los procesos del receptor llaman MPI_Bcast, la datavariable se completará con los datos del proceso raíz.

Transmitiendo con MPI_Send y MPI_Recv

Al principio, podría parecer que MPI_Bcastes solo un simple envoltorio alrededor de MPI_SendMPI_Recv. De hecho, podemos hacer que este contenedor funcione ahora mismo. Nuestra función, llamada, my_bcastse encuentra en bcast.c . Toma los mismos argumentos MPI_Bcasty se ve así:

void my_bcast(void* data, int count, MPI_Datatype datatype, int root,
              MPI_Comm communicator) {
  int world_rank;
  MPI_Comm_rank(communicator, &world_rank);
  int world_size;
  MPI_Comm_size(communicator, &world_size);

  if (world_rank == root) {
    // If we are the root process, send our data to everyone
    int i;
    for (i = 0; i < world_size; i++) {
      if (i != world_rank) {
        MPI_Send(data, count, datatype, i, 0, communicator);
      }
    }
  } else {
    // If we are a receiver process, receive the data from the root
    MPI_Recv(data, count, datatype, root, 0, communicator,
             MPI_STATUS_IGNORE);
  }
}

El proceso raíz envía los datos a todos los demás, mientras que los demás los reciben del proceso raíz. Fácil, ¿verdad? Si ejecuta el programa my_bcast desde el directorio de tutoriales del repositorio , la salida debería verse similar a esta.

>>> cd tutorials
>>> ./run.py my_bcast
mpirun -n 4 ./my_bcast
Process 0 broadcasting data 100
Process 2 received data 100 from root process
Process 3 received data 100 from root process
Process 1 received data 100 from root process

Lo crea o no, ¡nuestra función es realmente muy ineficiente! Imagine que cada proceso tiene solo un enlace de red saliente / entrante. Nuestra función es usar solo un enlace de red desde el proceso cero para enviar todos los datos. Una implementación más inteligente es un algoritmo de comunicación basado en árboles que puede utilizar más enlaces de red disponibles a la vez. Por ejemplo:

Árbol MPI_Bcast

En esta ilustración, el proceso cero comienza con los datos y los envía al proceso uno. Al igual que en nuestro ejemplo anterior, el proceso cero también envía los datos al proceso dos en la segunda etapa. La diferencia con este ejemplo es que el proceso uno ahora está ayudando al proceso raíz reenviando los datos al proceso tres. Durante la segunda etapa, se utilizan dos conexiones de red a la vez. La utilización de la red se duplica en cada etapa posterior de la comunicación del árbol hasta que todos los procesos hayan recibido los datos.

Comparación de MPI_Bcast con MPI_Send y MPI_Recv

La MPI_Bcastimplementación utiliza un algoritmo de transmisión de árbol similar para una buena utilización de la red. ¿Cómo se compara nuestra función de transmisión MPI_Bcast? Podemos ejecutar compare_bcastun programa de ejemplo incluido en el código de la lección ( compare_bcast.c ). Antes de mirar el código, primero vamos a ir más de una de las funciones de sincronización de MPI – MPI_WtimeMPI_Wtimeno toma argumentos y simplemente devuelve un número de segundos en coma flotante desde un tiempo establecido en el pasado. Similar a la timefunción de C , puede llamar a múltiples MPI_Wtimefunciones a lo largo de su programa y restar sus diferencias para obtener la sincronización de los segmentos de código.

Echemos un vistazo a nuestro código que compara my_bcast con MPI_Bcast.

for (i = 0; i < num_trials; i++) {
  // Time my_bcast
  // Synchronize before starting timing
  MPI_Barrier(MPI_COMM_WORLD);
  total_my_bcast_time -= MPI_Wtime();
  my_bcast(data, num_elements, MPI_INT, 0, MPI_COMM_WORLD);
  // Synchronize again before obtaining final time
  MPI_Barrier(MPI_COMM_WORLD);
  total_my_bcast_time += MPI_Wtime();

  // Time MPI_Bcast
  MPI_Barrier(MPI_COMM_WORLD);
  total_mpi_bcast_time -= MPI_Wtime();
  MPI_Bcast(data, num_elements, MPI_INT, 0, MPI_COMM_WORLD);
  MPI_Barrier(MPI_COMM_WORLD);
  total_mpi_bcast_time += MPI_Wtime();
}

En este código, num_trialses una variable que indica cuántos experimentos de sincronización se deben ejecutar. Realizamos un seguimiento del tiempo acumulado de ambas funciones en dos variables diferentes. Los tiempos medios se imprimen al final del programa. Para ver el código completo, solo mire compare_bcast.c en el código de la lección .

Si ejecuta el programa compare_bcast desde el directorio de tutoriales del repositorio , la salida debería verse similar a esta.

>>> cd tutorials
>>> ./run.py compare_bcast
/home/kendall/bin/mpirun -n 16 -machinefile hosts ./compare_bcast 100000 10
Data size = 400000, Trials = 10
Avg my_bcast time = 0.510873
Avg MPI_Bcast time = 0.126835

El script de ejecución ejecuta el código utilizando 16 procesadores, 100.000 enteros por transmisión y 10 ejecuciones de prueba para obtener resultados de tiempo. Como puede ver, mi experimento con 16 procesadores conectados a través de Ethernet muestra diferencias de tiempo significativas entre nuestra implementación ingenua y la implementación de MPI. Aquí están los resultados de cronometraje a diferentes escalas.

Procesadoresmy_bcastMPI_Bcast
20.03440.0344
40.10250.0817
80.23850.1084
dieciséis0.51090.1296

Como puede ver, no hay diferencia entre las dos implementaciones en dos procesadores. Esto se debe a que MPI_Bcastla implementación del árbol no proporciona ningún uso de red adicional cuando se utilizan dos procesadores. Sin embargo, las diferencias se pueden observar claramente cuando se llega a tan solo 16 procesadores.

Introducción a MPI_Scatter

MPI_Scatteres una rutina colectiva muy similar a MPI_Bcast(Si no está familiarizado con estos términos, lea la lección anterior ). MPI_Scatterimplica un proceso raíz designado que envía datos a todos los procesos en un comunicador. La principal diferencia entre MPI_BcastMPI_Scatteres pequeña pero importante. MPI_Bcastenvía la misma pieza de datos a todos los procesos mientras MPI_Scatterenvía fragmentos de una matriz a diferentes procesos. Consulte la ilustración a continuación para obtener más aclaraciones.

MPI_Bcast frente a MPI_Scatter

En la ilustración, MPI_Bcasttoma un solo elemento de datos en el proceso raíz (el cuadro rojo) y lo copia en todos los demás procesos. MPI_Scattertoma una matriz de elementos y distribuye los elementos en el orden de rango de proceso. El primer elemento (en rojo) va a procesar cero, el segundo elemento (en verde) va a procesar uno, y así sucesivamente. Aunque el proceso raíz (proceso cero) contiene toda la matriz de datos, MPI_Scattercopiará el elemento apropiado en el búfer receptor del proceso. Así es como se ve el prototipo de función de MPI_Scatter.

MPI_Scatter(
    void* send_data,
    int send_count,
    MPI_Datatype send_datatype,
    void* recv_data,
    int recv_count,
    MPI_Datatype recv_datatype,
    int root,
    MPI_Comm communicator)

Sí, la función parece grande y aterradora, pero examinémosla con más detalle. El primer parámetro, send_dataes una matriz de datos que reside en el proceso raíz. El segundo y tercer parámetro, send_countsend_datatype, dictan cuántos elementos de un tipo de datos MPI específico se enviarán a cada proceso. Si send_countes uno y send_datatypees MPI_INT, entonces el proceso cero obtiene el primer número entero de la matriz, el proceso uno obtiene el segundo número entero, y así sucesivamente. Si send_countes dos, el proceso cero obtiene el primer y el segundo enteros, el proceso uno obtiene el tercero y el cuarto, y así sucesivamente. En la práctica,send_counta menudo es igual al número de elementos de la matriz dividido por el número de procesos. ¿Qué es lo que dices? ¿El número de elementos no es divisible por el número de procesos? No se preocupe, lo cubriremos en una lección posterior 🙂

Los parámetros de recepción del prototipo de función son casi idénticos con respecto a los parámetros de envío. El recv_dataparámetro es un búfer de datos que puede contener recv_countelementos que tienen un tipo de datos de recv_datatype. Los últimos parámetros, rootcommunicator, indican el proceso raíz que está dispersando la matriz de datos y el comunicador en el que residen los procesos.

Una introducción a MPI_Gather

MPI_Gatheres el inverso de MPI_Scatter. En lugar de distribuir elementos de un proceso a muchos procesos, MPI_Gathertoma elementos de muchos procesos y los reúne en un solo proceso. Esta rutina es muy útil para muchos algoritmos paralelos, como la clasificación y la búsqueda paralelas. A continuación se muestra una ilustración simple de este algoritmo.

MPI_Gather

Similar a MPI_ScatterMPI_Gathertoma elementos de cada proceso y los reúne en el proceso raíz. Los elementos están ordenados por el rango del proceso del que fueron recibidos. El prototipo de función para MPI_Gatheres idéntico al de MPI_Scatter.

MPI_Gather(
    void* send_data,
    int send_count,
    MPI_Datatype send_datatype,
    void* recv_data,
    int recv_count,
    MPI_Datatype recv_datatype,
    int root,
    MPI_Comm communicator)

En MPI_Gather, solo el proceso raíz debe tener un búfer de recepción válido. Todos los demás procesos de llamadas pueden pasar NULLpor recv_data. Además, no olvide que el parámetro recv_count es el recuento de elementos recibidos por proceso , no la suma total de recuentos de todos los procesos. Esto a menudo puede confundir a los programadores MPI principiantes.

Calcular el promedio de números con MPI_Scatter y MPI_Gather

Aunque el programa que vamos a ver podría parecer bastante simple, demuestra cómo se puede usar MPI para dividir el trabajo entre procesos, realizar cálculos en subconjuntos de datos y luego agregar las piezas más pequeñas en la respuesta final. El programa sigue los siguientes pasos:

  1. Genere una matriz aleatoria de números en el proceso raíz (proceso 0).
  2. Distribuya los números a todos los procesos, dando a cada proceso la misma cantidad de números.
  3. Cada proceso calcula el promedio de su subconjunto de números.
  4. Reúna todos los promedios del proceso raíz. El proceso raíz luego calcula el promedio de estos números para obtener el promedio final.

La parte principal del código con las llamadas MPI se ve así:

if (world_rank == 0) {
  rand_nums = create_rand_nums(elements_per_proc * world_size);
}

// Create a buffer that will hold a subset of the random numbers
float *sub_rand_nums = malloc(sizeof(float) * elements_per_proc);

// Scatter the random numbers to all processes
MPI_Scatter(rand_nums, elements_per_proc, MPI_FLOAT, sub_rand_nums,
            elements_per_proc, MPI_FLOAT, 0, MPI_COMM_WORLD);

// Compute the average of your subset
float sub_avg = compute_avg(sub_rand_nums, elements_per_proc);
// Gather all partial averages down to the root process
float *sub_avgs = NULL;
if (world_rank == 0) {
  sub_avgs = malloc(sizeof(float) * world_size);
}
MPI_Gather(&sub_avg, 1, MPI_FLOAT, sub_avgs, 1, MPI_FLOAT, 0,
           MPI_COMM_WORLD);

// Compute the total average of all numbers.
if (world_rank == 0) {
  float avg = compute_avg(sub_avgs, world_size);
}

Al comienzo del código, el proceso raíz crea una matriz de números aleatorios. Cuando MPI_Scatterse llama, cada proceso ahora contiene elements_per_procelementos de los datos originales. Cada proceso calcula el promedio de su subconjunto de datos y luego el proceso raíz recopila cada promedio individual. El promedio total se calcula sobre esta matriz de números mucho más pequeña.

Si ejecuta el programa avg desde el directorio de tutoriales del repositorio , la salida debería verse similar a esta. Tenga en cuenta que los números se generan aleatoriamente, por lo que su resultado final puede ser diferente al mío.

>>> cd tutorials
>>> ./run.py avg
/home/kendall/bin/mpirun -n 4 ./avg 100
Avg of all elements is 0.478699
Avg computed across original data is 0.478699

MPI_Allgather y modificación de programa medio

Hasta ahora, hemos cubierto dos rutinas MPI que realizan patrones de comunicación de muchos a uno o de uno a muchos , lo que simplemente significa que muchos procesos envían / ​​reciben a un proceso. A menudo, es útil poder enviar muchos elementos a muchos procesos (es decir, un patrón de comunicación de muchos a muchos ). MPI_Allgathertiene esta característica.

Dado un conjunto de elementos distribuidos en todos los procesos, MPI_Allgatherreunirá todos los elementos de todos los procesos. En el sentido más básico, MPI_Allgatherva MPI_Gatherseguido de un MPI_Bcast. La siguiente ilustración muestra cómo se distribuyen los datos después de una llamada a MPI_Allgather.

MPI_Allgather

Al igual que MPI_Gather, los elementos de cada proceso se recopilan en orden de su rango, excepto que esta vez los elementos se recopilan en todos los procesos. Bastante fácil, ¿verdad? La declaración de función para MPI_Allgatheres casi idéntica a MPI_Gatherla diferencia de que no hay un proceso raíz en MPI_Allgather.

MPI_Allgather(
    void* send_data,
    int send_count,
    MPI_Datatype send_datatype,
    void* recv_data,
    int recv_count,
    MPI_Datatype recv_datatype,
    MPI_Comm communicator)

He modificado el código de cálculo promedio para usar MPI_Allgather. Puede ver la fuente en all_avg.c desde el código de esta lección . La principal diferencia en el código se muestra a continuación.

// Gather all partial averages down to all the processes
float *sub_avgs = (float *)malloc(sizeof(float) * world_size);
MPI_Allgather(&sub_avg, 1, MPI_FLOAT, sub_avgs, 1, MPI_FLOAT,
              MPI_COMM_WORLD);

// Compute the total average of all numbers.
float avg = compute_avg(sub_avgs, world_size);

Los promedios parciales ahora se recopilan para todos los que usan MPI_Allgather. Los promedios ahora se imprimen de todos los procesos. La salida de ejemplo del programa debería tener el siguiente aspecto:

>>> ./run.py all_avg
/home/kendall/bin/mpirun -n 4 ./all_avg 100
Avg of all elements from proc 1 is 0.479736
Avg of all elements from proc 3 is 0.479736
Avg of all elements from proc 0 is 0.479736
Avg of all elements from proc 2 is 0.479736

Como habrá notado, la única diferencia entre all_avg.cy avg.c es que all_avg.c imprime el promedio de todos los procesos con MPI_Allgather.

Clasificación paralela: descripción general del problema

Cuando todos los procesos tienen un solo número almacenado en su memoria local, puede ser útil saber en qué orden está su número con respecto al conjunto completo de números que contienen todos los procesos. Por ejemplo, un usuario puede estar comparando los procesadores en un clúster MPI y quiere saber el orden de la velocidad de cada procesador en relación con los demás. Esta información se puede utilizar para programar tareas, etc. Como puede imaginar, es bastante difícil averiguar el orden de un número en el contexto de todos los demás números si están distribuidos entre procesos. Este problema, el problema de los rangos paralelos, es lo que vamos a resolver en esta lección.

A continuación se muestra una ilustración de la entrada y salida del rango paralelo:

Rango paralelo

Los procesos en la ilustración (etiquetados del 0 al 3) comienzan con cuatro números: 5, 2, 7 y 4. El algoritmo de rango paralelo calcula que el proceso 1 tiene rango 0 en el conjunto de números (es decir, el primer número), proceso 3 tiene rango 1, el proceso 0 tiene rango 2 y el proceso 2 tiene el último rango en el conjunto de números. Bastante simple, ¿verdad?

Definición de API de rango paralelo

Antes de sumergirnos en la solución del problema de rangos paralelos, primero decidamos cómo se comportará nuestra función. Nuestra función necesita tomar un número en cada proceso y devolver su rango asociado con respecto a todos los demás números en todos los procesos. Junto con esto, necesitaremos otra información miscelánea, como el comunicador que se está utilizando y el tipo de datos del número que se está clasificando. Dada esta definición de función, nuestro prototipo para la función de rango se ve así:

TMPI_Rank(
    void *send_data,
    void *recv_data,
    MPI_Datatype datatype,
    MPI_Comm comm)

TMPI_Ranktoma un send_databúfer que contiene un número de datatypetipo. El recv_datarecibe exactamente un número entero en cada proceso que contiene el valor de rango para send_data. La commvariable es el comunicador en el que se realiza la clasificación.

Nota – El estándar MPI dice explícitamente que los usuarios no deben nombrar sus propias funciones MPI_<something>para evitar confundir las funciones del usuario con funciones en el propio estándar MPI. Por lo tanto, agregaremos el prefijo a las funciones en estos tutoriales T.

Resolver el problema de rangos paralelos

Ahora que tenemos nuestra definición de API, podemos profundizar en cómo se resuelve el problema de rango paralelo. El primer paso para resolver el problema de rangos paralelos es ordenar todos los números en todos los procesos. Esto debe lograrse para que podamos encontrar el rango de cada número en el conjunto completo de números. Hay varias formas de lograr esto. La forma más sencilla es reunir todos los números en un proceso y ordenarlos. En el código de ejemplo ( tmpi_rank.c ), la gather_numbers_to_rootfunción es responsable de recopilar todos los números del proceso raíz.

// Gathers numbers for TMPI_Rank to process zero. Allocates space for
// the MPI datatype and returns a void * buffer to process 0.
// It returns NULL to all other processes.
void *gather_numbers_to_root(void *number, MPI_Datatype datatype,
                             MPI_Comm comm) {
  int comm_rank, comm_size;
  MPI_Comm_rank(comm, &comm_rank);
  MPI_Comm_size(comm, &comm_size);

  // Allocate an array on the root process of a size depending
  // on the MPI datatype being used.
  int datatype_size;
  MPI_Type_size(datatype, &datatype_size);
  void *gathered_numbers;
  if (comm_rank == 0) {
    gathered_numbers = malloc(datatype_size * comm_size);
  }

  // Gather all of the numbers on the root process
  MPI_Gather(number, 1, datatype, gathered_numbers, 1,
             datatype, 0, comm);

  return gathered_numbers;
}

La gather_numbers_to_rootfunción toma el número (es decir, la send_datavariable) que se recopilará, el datatypedel número y el commcomunicador. El proceso raíz debe recopilar comm_sizenúmeros en esta función, por lo que malloca una matriz de datatype_size * comm_sizelongitud. La datatype_sizevariable se recogió mediante el uso de una nueva función MPI en este tutorial – MPI_Type_size. Aunque nuestro código solo admite MPI_INTMPI_FLOATcomo tipo de datos, este código podría ampliarse para admitir tipos de datos de diferentes tamaños. Una vez que se han recopilado los números en el proceso raíz con MPI_Gather, los números deben ordenarse en el proceso raíz para poder determinar su clasificación.

Ordenar números y mantener la propiedad

Ordenar números no es necesariamente un problema difícil en nuestra función de clasificación. La biblioteca estándar de C nos proporciona algoritmos de clasificación populares como qsort. La dificultad de ordenar con nuestro problema de rango paralelo es que debemos mantener los rangos que enviaron los números al proceso raíz. Si tuviéramos que ordenar la lista de números recopilados para el proceso raíz sin adjuntar información adicional a los números, ¡el proceso raíz no tendría idea de cómo enviar las filas de los números a los procesos solicitantes!

Para facilitar la vinculación del proceso de propiedad a los números, creamos una estructura en el código que contiene esta información. Nuestra definición de estructura es la siguiente:

// Holds the communicator rank of a process along with the
// corresponding number. This struct is used for sorting
// the values and keeping the owning process information
// intact.
typedef struct {
  int comm_rank;
  union {
    float f;
    int i;
  } number;
} CommRankNumber;

La CommRankNumberestructura contiene el número que vamos a ordenar (recuerde que puede ser un flotante o un int, por lo que usamos una unión) y tiene el rango de comunicador del proceso que posee el número. La siguiente parte del código, la get_ranksfunción, es responsable de crear estas estructuras y ordenarlas.

// This function sorts the gathered numbers on the root process and
// returns an array of ordered by the process's rank in its
// communicator. Note - this function is only executed on the root
// process.
int *get_ranks(void *gathered_numbers, int gathered_number_count,
               MPI_Datatype datatype) {
  int datatype_size;
  MPI_Type_size(datatype, &datatype_size);

  // Convert the gathered number array to an array of CommRankNumbers.
  // This allows us to sort the numbers and also keep the information
  // of the processes that own the numbers intact.
  CommRankNumber *comm_rank_numbers = malloc(
    gathered_number_count * sizeof(CommRankNumber));
  int i;
  for (i = 0; i < gathered_number_count; i++) {
    comm_rank_numbers[i].comm_rank = i;
    memcpy(&(comm_rank_numbers[i].number),
           gathered_numbers + (i * datatype_size),
           datatype_size);
  }

  // Sort the comm rank numbers based on the datatype
  if (datatype == MPI_FLOAT) {
    qsort(comm_rank_numbers, gathered_number_count,
          sizeof(CommRankNumber), &compare_float_comm_rank_number);
  } else {
    qsort(comm_rank_numbers, gathered_number_count,
          sizeof(CommRankNumber), &compare_int_comm_rank_number);
  }

  // Now that the comm_rank_numbers are sorted, make an array of rank
  // values for each process. The ith element of this array contains
  // the rank value for the number sent by process i.
  int *ranks = (int *)malloc(sizeof(int) * gathered_number_count);
  for (i = 0; i < gathered_number_count; i++) {
    ranks[comm_rank_numbers[i].comm_rank] = i;
  }

  // Clean up and return the rank array
  free(comm_rank_numbers);
  return ranks;
}

La get_ranksfunción primero crea una matriz de CommRankNumberestructuras y adjunta el rango de comunicador del proceso al que pertenece el número. Si el tipo de datos es MPI_FLOATqsortse llama con una función de clasificación especial para nuestra matriz de estructuras (consulte tmpi_rank.c para obtener el código). Del mismo modo, usamos una función de clasificación diferente si el tipo de datos es MPI_INT.

Una vez ordenados los números, debemos crear una matriz de rangos en el orden correcto para que puedan distribuirse de nuevo a los procesos solicitantes. Esto se logra haciendo la ranksmatriz y completando los valores de rango adecuados para cada una de las CommRankNumberestructuras ordenadas .

Poniendolo todo junto

Ahora que tenemos nuestras dos funciones principales, podemos ponerlas todas juntas en nuestra TMPI_Rankfunción. Esta función reúne los números del proceso raíz, ordena los números para determinar sus rangos y luego los dispersa de nuevo a los procesos solicitantes. El código se muestra a continuación:

// Gets the rank of the recv_data, which is of type datatype. The rank
// is returned in send_data and is of type datatype.
int TMPI_Rank(void *send_data, void *recv_data, MPI_Datatype datatype,
             MPI_Comm comm) {
  // Check base cases first - Only support MPI_INT and MPI_FLOAT for
  // this function.
  if (datatype != MPI_INT && datatype != MPI_FLOAT) {
    return MPI_ERR_TYPE;
  }

  int comm_size, comm_rank;
  MPI_Comm_size(comm, &comm_size);
  MPI_Comm_rank(comm, &comm_rank);

  // To calculate the rank, we must gather the numbers to one
  // process, sort the numbers, and then scatter the resulting rank
  // values. Start by gathering the numbers on process 0 of comm.
  void *gathered_numbers = gather_numbers_to_root(send_data, datatype,
                                                  comm);

  // Get the ranks of each process
  int *ranks = NULL;
  if (comm_rank == 0) {
    ranks = get_ranks(gathered_numbers, comm_size, datatype);
  }

  // Scatter the rank results
  MPI_Scatter(ranks, 1, MPI_INT, recv_data, 1, MPI_INT, 0, comm);

  // Do clean up
  if (comm_rank == 0) {
    free(gathered_numbers);
    free(ranks);
  }
}

La TMPI_Rankfunción usa las dos funciones que acabamos de crear gather_numbers_to_rootget_ranks, para obtener los rangos de los números. Luego, la función realiza la final MPI_Scatterpara dispersar los rangos resultantes a los procesos.

Si ha tenido problemas para seguir la solución al problema de rango paralelo, he incluido una ilustración de todo el flujo de datos de nuestro problema utilizando un conjunto de datos de ejemplo:

Rango paralelo

¿Tiene alguna pregunta sobre cómo funciona el algoritmo de rango paralelo? ¡Déjalos abajo!

Ejecutando nuestro algoritmo de rango paralelo

He incluido un pequeño programa en el código de ejemplo para ayudar a probar nuestro algoritmo de rango paralelo. El código se puede ver en el archivo de archivo random_rank.c en el código de la lección .

La aplicación de ejemplo simplemente crea un número aleatorio en cada proceso y llama TMPI_Rankpara obtener el rango de cada número. Si ejecuta el programa random_rank desde el directorio de tutoriales del repositorio , la salida debería verse similar a esta.

>>> cd tutorials
>>> ./run.py random_rank
mpirun -n 4  ./random_rank 100
Rank for 0.242578 on process 0 - 0
Rank for 0.894732 on process 1 - 3
Rank for 0.789463 on process 2 - 2
Rank for 0.684195 on process 3 - 1

Una introducción para reducir

Reducir es un concepto clásico de la programación funcional. La reducción de datos implica reducir un conjunto de números a un conjunto más pequeño de números a través de una función. Por ejemplo, digamos que tenemos una lista de números [1, 2, 3, 4, 5]. Reducir esta lista de números con la función de suma produciría sum([1, 2, 3, 4, 5]) = 15. De manera similar, la reducción de la multiplicación cedería multiply([1, 2, 3, 4, 5]) = 120.

Como puede haber imaginado, puede resultar muy engorroso aplicar funciones de reducción en un conjunto de números distribuidos. Junto con eso, es difícil programar de manera eficiente reducciones no conmutativas, es decir, reducciones que deben ocurrir en un orden establecido. Afortunadamente, MPI tiene una función útil llamada MPI_Reduceque manejará casi todas las reducciones comunes que un programador necesita hacer en una aplicación paralela.

MPI_Reduce

Similar a MPI_GatherMPI_Reducetoma una matriz de elementos de entrada en cada proceso y devuelve una matriz de elementos de salida al proceso raíz. Los elementos de salida contienen el resultado reducido. El prototipo de MPI_Reducetiene este aspecto:

MPI_Reduce(
    void* send_data,
    void* recv_data,
    int count,
    MPI_Datatype datatype,
    MPI_Op op,
    int root,
    MPI_Comm communicator)

El send_dataparámetro es una matriz de elementos de tipo datatypeque cada proceso quiere reducir. El recv_datasólo es relevante en el proceso con un rango de root. La recv_datamatriz contiene el resultado reducido y tiene un tamaño de sizeof(datatype) * count. El opparámetro es la operación que desea aplicar a sus datos. MPI contiene un conjunto de operaciones de reducción comunes que se pueden utilizar. Aunque se pueden definir operaciones de reducción personalizadas, está más allá del alcance de esta lección. Las operaciones de reducción definidas por MPI incluyen:

  • MPI_MAX – Devuelve el elemento máximo.
  • MPI_MIN – Devuelve el elemento mínimo.
  • MPI_SUM – Suma los elementos.
  • MPI_PROD – Multiplica todos los elementos.
  • MPI_LAND– Realiza un proceso lógico y transversal a los elementos.
  • MPI_LOR– Realiza una lógica o transversal a los elementos.
  • MPI_BAND– Realiza un bit a bit y a través de los bits de los elementos.
  • MPI_BOR– Realiza un bit a bit oa través de los bits de los elementos.
  • MPI_MAXLOC – Devuelve el valor máximo y el rango del proceso al que pertenece.
  • MPI_MINLOC – Devuelve el valor mínimo y el rango del proceso al que pertenece.

A continuación se muestra una ilustración del patrón de comunicación de MPI_Reduce.

MPI_Reduce

En lo anterior, cada proceso contiene un número entero. MPI_Reducese llama con un proceso raíz de 0 y se usa MPI_SUMcomo operación de reducción. Los cuatro números se suman al resultado y se almacenan en el proceso raíz.

También es útil para ver qué sucede cuando los procesos contienen múltiples elementos. La siguiente ilustración muestra la reducción de varios números por proceso.

MPI_Reduce

Cada uno de los procesos de la ilustración anterior tiene dos elementos. La suma resultante ocurre por elemento. En otras palabras, en lugar de sumar todos los elementos de todas las matrices en un elemento, el i- ésimo elemento de cada matriz se suma al i- ésimo elemento en la matriz de resultados del proceso 0.

Ahora que comprende cómo se MPI_Reduceve, podemos pasar a algunos ejemplos de código.

Calcular el promedio de números con MPI_Reduce

En la lección anterior , le mostré cómo calcular el promedio usando MPI_ScatterMPI_Gather. El uso MPI_Reducesimplifica bastante el código de la última lección. A continuación se muestra un extracto de reduce_avg.c en el código de ejemplo de esta lección.

float *rand_nums = NULL;
rand_nums = create_rand_nums(num_elements_per_proc);

// Sum the numbers locally
float local_sum = 0;
int i;
for (i = 0; i < num_elements_per_proc; i++) {
  local_sum += rand_nums[i];
}

// Print the random numbers on each process
printf("Local sum for process %d - %f, avg = %f\n",
       world_rank, local_sum, local_sum / num_elements_per_proc);

// Reduce all of the local sums into the global sum
float global_sum;
MPI_Reduce(&local_sum, &global_sum, 1, MPI_FLOAT, MPI_SUM, 0,
           MPI_COMM_WORLD);

// Print the result
if (world_rank == 0) {
  printf("Total sum = %f, avg = %f\n", global_sum,
         global_sum / (world_size * num_elements_per_proc));
}

En el código anterior, cada proceso crea números aleatorios y realiza un local_sumcálculo. El local_sumse reduce entonces al proceso de raíz utilizando MPI_SUM. El promedio global es entonces global_sum / (world_size * num_elements_per_proc). Si ejecuta el programa reduce_avg desde el directorio de tutoriales del repositorio , la salida debería verse similar a esta.

>>> cd tutorials
>>> ./run.py reduce_avg
mpirun -n 4  ./reduce_avg 100
Local sum for process 0 - 51.385098, avg = 0.513851
Local sum for process 1 - 51.842468, avg = 0.518425
Local sum for process 2 - 49.684948, avg = 0.496849
Local sum for process 3 - 47.527420, avg = 0.475274
Total sum = 200.439941, avg = 0.501100

Ahora es el momento de pasar al hermano de MPI_Reduce– MPI_Allreduce.

MPI_Allreduce

Muchas aplicaciones paralelas requerirán acceder a los resultados reducidos en todos los procesos en lugar del proceso raíz. En un estilo complementario similar de MPI_AllgatherMPI_GatherMPI_Allreducereducirá los valores y distribuirá los resultados a todos los procesos. El prototipo de función es el siguiente:

MPI_Allreduce(
    void* send_data,
    void* recv_data,
    int count,
    MPI_Datatype datatype,
    MPI_Op op,
    MPI_Comm communicator)

Como habrás notado, MPI_Allreducees idéntico MPI_Reducecon la excepción de que no necesita una identificación de proceso raíz (ya que los resultados se distribuyen a todos los procesos). A continuación se ilustra el patrón de comunicación de MPI_Allreduce:

MPI_Allreduce

MPI_Allreducees el equivalente a hacer MPI_Reduceseguido de un MPI_Bcast. Bastante simple, ¿verdad?

Calcular la desviación estándar con MPI_Allreduce

Muchos problemas computacionales requieren hacer múltiples reducciones para resolver problemas. Uno de esos problemas es encontrar la desviación estándar de un conjunto distribuido de números. Para aquellos que lo hayan olvidado, la desviación estándar es una medida de la dispersión de los números de su media. Una desviación estándar más baja significa que los números están más juntos y viceversa para desviaciones estándar más altas.

Para encontrar la desviación estándar, primero se debe calcular el promedio de todos los números. Una vez calculado el promedio, se calculan las sumas de la diferencia al cuadrado de la media. La raíz cuadrada del promedio de las sumas es el resultado final. Dada la descripción del problema, sabemos que habrá al menos dos sumas de todos los números, lo que se traduce en dos reducciones. Un extracto de reduce_stddev.c en el código de la lección muestra cómo se ve esto en MPI.

rand_nums = create_rand_nums(num_elements_per_proc);

// Sum the numbers locally
float local_sum = 0;
int i;
for (i = 0; i < num_elements_per_proc; i++) {
  local_sum += rand_nums[i];
}

// Reduce all of the local sums into the global sum in order to
// calculate the mean
float global_sum;
MPI_Allreduce(&local_sum, &global_sum, 1, MPI_FLOAT, MPI_SUM,
              MPI_COMM_WORLD);
float mean = global_sum / (num_elements_per_proc * world_size);

// Compute the local sum of the squared differences from the mean
float local_sq_diff = 0;
for (i = 0; i < num_elements_per_proc; i++) {
  local_sq_diff += (rand_nums[i] - mean) * (rand_nums[i] - mean);
}

// Reduce the global sum of the squared differences to the root
// process and print off the answer
float global_sq_diff;
MPI_Reduce(&local_sq_diff, &global_sq_diff, 1, MPI_FLOAT, MPI_SUM, 0,
           MPI_COMM_WORLD);

// The standard deviation is the square root of the mean of the
// squared differences.
if (world_rank == 0) {
  float stddev = sqrt(global_sq_diff /
                      (num_elements_per_proc * world_size));
  printf("Mean - %f, Standard deviation = %f\n", mean, stddev);
}

En el código anterior, cada proceso calcula la cantidad local_sumde elementos y los suma usando MPI_Allreduce. Una vez que la suma global está disponible en todos los procesos, meanse calcula para que local_sq_diffse pueda calcular. Una vez que se calculan todas las diferencias cuadradas locales, se calcula global_sq_diffutilizando MPI_Reduce. El proceso de raíz puede entonces calcular la desviación estándar tomando la raíz cuadrada de la media de las diferencias cuadradas globales.

La ejecución del código de ejemplo con el script de ejecución produce un resultado similar al siguiente:

>>> ./run.py reduce_stddev
mpirun -n 4  ./reduce_stddev 100
Mean - 0.501100, Standard deviation = 0.301126

Resumen de comunicadores

Como hemos visto al aprender sobre rutinas colectivas, MPI le permite hablar con todos los procesos en un comunicador a la vez para hacer cosas como distribuir datos de un proceso a muchos procesos usando MPI_Scattero realizar una reducción de datos usando MPI_Reduce. Sin embargo, hasta ahora, sólo hemos utilizado el comunicador por defecto, MPI_COMM_WORLD.

Para aplicaciones simples, no es inusual hacer todo usando MPI_COMM_WORLD, pero para casos de uso más complejos, puede ser útil tener más comunicadores. Un ejemplo podría ser si desea realizar cálculos en un subconjunto de los procesos en una cuadrícula. Por ejemplo, es posible que todos los procesos de cada fila quieran sumar un valor. Esto nos lleva a la primera y más común función utilizada para crear nuevos comunicadores:

MPI_Comm_split(
	MPI_Comm comm,
	int color,
	int key,
	MPI_Comm* newcomm)

Como su nombre lo indica, MPI_Comm_splitcrea nuevos comunicadores al «dividir» un comunicador en un grupo de subcomunicadores en función de los valores de entrada colorkey. Es importante señalar aquí que el comunicador original no desaparece, pero se crea un nuevo comunicador en cada proceso. El primer argumento, commes el comunicador que se utilizará como base para los nuevos comunicadores. Esto podría ser MPI_COMM_WORLD, pero también podría ser cualquier otro comunicador. El segundo argumento, colordetermina a qué nuevo comunicador pertenecerá cada proceso. Todos los procesos que pasan el mismo valor para colorse asignan al mismo comunicador. Si colores así MPI_UNDEFINED, ese proceso no se incluirá en ninguno de los nuevos comunicadores. El tercer argumento,key, determina el orden (rango) dentro de cada nuevo comunicador. El proceso que pasa en el valor más pequeño keyserá el rango 0, el siguiente más pequeño será el rango 1, y así sucesivamente. Si hay un empate, el proceso que tuvo el rango más bajo en el comunicador original será el primero. El argumento final newcommes cómo MPI devuelve el nuevo comunicador al usuario.

Ejemplo de uso de varios comunicadores

Ahora veamos un ejemplo simple en el que intentamos dividir un solo comunicador global en un conjunto de comunicadores más pequeños. En este ejemplo, imaginaremos que hemos dispuesto lógicamente nuestro comunicador original en una cuadrícula 4×4 de 16 procesos y queremos dividir la cuadrícula por fila. Para hacer esto, cada fila obtendrá su propio color. En la imagen de abajo, puedes ver cómo cada grupo de procesos con el mismo color a la izquierda termina en su propio comunicador a la derecha.

Ejemplo de MPI_Comm_split

Veamos el código para esto.

// Get the rank and size in the original communicator
int world_rank, world_size;
MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
MPI_Comm_size(MPI_COMM_WORLD, &world_size);

int color = world_rank / 4; // Determine color based on row

// Split the communicator based on the color and use the
// original rank for ordering
MPI_Comm row_comm;
MPI_Comm_split(MPI_COMM_WORLD, color, world_rank, &row_comm);

int row_rank, row_size;
MPI_Comm_rank(row_comm, &row_rank);
MPI_Comm_size(row_comm, &row_size);

printf("WORLD RANK/SIZE: %d/%d \t ROW RANK/SIZE: %d/%d\n",
	world_rank, world_size, row_rank, row_size);

MPI_Comm_free(&row_comm);

Las primeras líneas obtienen el rango y tamaño para el comunicador original, MPI_COMM_WORLD. La siguiente línea hace la importante operación de determinar el «color» del proceso local. Recuerde que el color decide a qué comunicador pertenecerá el proceso después de la división. A continuación, vemos la importante operación de división. Lo nuevo aquí es que estamos usando el rango original ( world_rank) como clave para la operación de división. Dado que queremos que todos los procesos en el nuevo comunicador estén en el mismo orden en que estaban en el comunicador original, usar el valor de rango original tiene más sentido aquí, ya que ya estará ordenado correctamente. Después de eso, imprimimos el nuevo rango y tamaño solo para asegurarnos de que funcione. Su salida debería verse así:

WORLD RANK/SIZE: 0/16 	 ROW RANK/SIZE: 0/4
WORLD RANK/SIZE: 1/16 	 ROW RANK/SIZE: 1/4
WORLD RANK/SIZE: 2/16 	 ROW RANK/SIZE: 2/4
WORLD RANK/SIZE: 3/16 	 ROW RANK/SIZE: 3/4
WORLD RANK/SIZE: 4/16 	 ROW RANK/SIZE: 0/4
WORLD RANK/SIZE: 5/16 	 ROW RANK/SIZE: 1/4
WORLD RANK/SIZE: 6/16 	 ROW RANK/SIZE: 2/4
WORLD RANK/SIZE: 7/16 	 ROW RANK/SIZE: 3/4
WORLD RANK/SIZE: 8/16 	 ROW RANK/SIZE: 0/4
WORLD RANK/SIZE: 9/16 	 ROW RANK/SIZE: 1/4
WORLD RANK/SIZE: 10/16 	 ROW RANK/SIZE: 2/4
WORLD RANK/SIZE: 11/16 	 ROW RANK/SIZE: 3/4
WORLD RANK/SIZE: 12/16 	 ROW RANK/SIZE: 0/4
WORLD RANK/SIZE: 13/16 	 ROW RANK/SIZE: 1/4
WORLD RANK/SIZE: 14/16 	 ROW RANK/SIZE: 2/4
WORLD RANK/SIZE: 15/16 	 ROW RANK/SIZE: 3/4

No se alarme si el suyo no está en el orden correcto. Cuando imprime cosas en un programa MPI, cada proceso tiene que enviar su salida al lugar donde inició su trabajo MPI antes de que pueda imprimirse en la pantalla. Esto tiende a significar que el orden se confunde, por lo que nunca puede suponer que solo porque imprime las cosas en un orden de clasificación específico, la salida terminará en el mismo orden que espera. La salida se reorganizó aquí para que se vea bien.

Finalmente, liberamos el comunicador con MPI_Comm_free. Parece que no es un paso importante, pero es tan importante como liberar tu memoria cuando termines con cualquier otro programa. Cuando un objeto MPI ya no se utilizará, debe liberarse para poder reutilizarlo más tarde. MPI tiene un número limitado de objetos que puede crear a la vez y no liberar sus objetos podría resultar en un error de tiempo de ejecución si MPI se queda sin objetos asignables.

Otras funciones de creación de comunicadores

Si bien MPI_Comm_splites la función de creación de comunicadores más común, existen muchas otras. MPI_Comm_dupes el más básico y crea un duplicado de un comunicador. Puede parecer extraño que exista una función que solo crea una copia, pero esto es muy útil para aplicaciones que usan bibliotecas para realizar funciones especializadas, como bibliotecas matemáticas. En este tipo de aplicaciones, es importante que los códigos de usuario y los códigos de biblioteca no interfieran entre sí. Para evitar esto, lo primero que debe hacer toda aplicación es crear un duplicado de MPI_COMM_WORLD, lo que evitará el problema de que otras bibliotecas también lo utilicen MPI_COMM_WORLD. Las propias bibliotecas también deberían hacer duplicados MPI_COMM_WORLDpara evitar el mismo problema.

Otra función es MPI_Comm_create. A primera vista, esta función se parece mucho a MPI_Comm_create_group. Su firma es casi idéntica:

MPI_Comm_create(
	MPI_Comm comm,
	MPI_Group group,
    MPI_Comm* newcomm)

Sin embargo, la diferencia clave (además de la falta del tagargumento), es que MPI_Comm_create_groupes solo colectivo sobre el grupo de procesos contenido en group, donde MPI_Comm_createes colectivo sobre cada proceso en comm. Ésta es una distinción importante ya que el tamaño de los comunicadores crece mucho. Si intenta crear un subconjunto de MPI_COMM_WORLDcuando se ejecuta con 1,000,000 de procesos, es importante realizar la operación con la menor cantidad de procesos posible, ya que el colectivo se vuelve muy costoso en tamaños grandes.

Hay otras características más avanzadas de los comunicadores que no cubrimos aquí, como las diferencias entre intercomunicadores e intracomunicadores y otras funciones avanzadas de creación de comunicadores. Estos solo se utilizan en tipos de aplicaciones muy específicos que pueden tratarse en un tutorial futuro.

Resumen de grupos

Si bien MPI_Comm_splites la forma más sencilla de crear un nuevo comunicador, no es la única forma de hacerlo. Hay formas más flexibles para crear comunicadores, sino que utilizan un nuevo tipo de objeto MPI, MPI_Group. Antes de entrar en muchos detalles sobre los grupos, veamos un poco más qué es realmente un comunicador. Internamente, MPI tiene que mantenerse al día (entre otras cosas) con dos partes principales de un comunicador, el contexto (o ID) que diferencia a un comunicador de otro y el grupo de procesos que contiene el comunicador. El contexto es lo que evita que una operación en un comunicador coincida con una operación similar en otro comunicador. MPI mantiene una identificación para cada comunicador internamente para evitar confusiones. El grupo es un poco más simple de entender ya que es solo el conjunto de todos los procesos en el comunicador. ParaMPI_COMM_WORLD, estos son todos los procesos iniciados por mpiexec. Para otros comunicadores, el grupo será diferente. En el código de ejemplo anterior, el grupo es todos los procesos que transmiten en el mismo colorMPI_Comm_split.

MPI usa estos grupos de la misma manera que generalmente funciona la teoría de conjuntos. No es necesario que esté familiarizado con toda la teoría de conjuntos para comprender las cosas, pero es útil saber qué significan dos operaciones. Aquí, en lugar de referirnos a «conjuntos», usaremos el término «grupos» como se aplica a MPI. Primero, la operación de unión crea un nuevo conjunto (potencialmente) más grande a partir de otros dos conjuntos. El nuevo conjunto incluye todos los miembros de los dos primeros conjuntos (sin duplicados). En segundo lugar, la operación de intersección crea un nuevo conjunto (potencialmente) más pequeño a partir de otros dos conjuntos. El nuevo conjunto incluye todos los miembros que están presentes en los dos conjuntos originales. Puede ver ejemplos de ambas operaciones gráficamente a continuación.

Ejemplos de operaciones grupales

En el primer ejemplo, la unión de los dos grupos {0, 1, 2, 3}{2, 3, 4, 5}se {0, 1, 2, 3, 4, 5}debe a que cada uno de esos elementos aparece en cada grupo. En el segundo ejemplo, la intersección de los dos grupos {0, 1, 2, 3}, y {2, 3, 4, 5}se {2, 3}debe a que solo esos elementos aparecen en cada grupo.

Usar grupos MPI

Ahora que entendemos los fundamentos de cómo funcionan los grupos, veamos cómo se pueden aplicar a las operaciones de MPI. En MPI, es fácil de obtener el conjunto de procesos en un comunicador con la llamada a la API, MPI_Comm_group.

MPI_Comm_group(
	MPI_Comm comm,
	MPI_Group* group)

Como se mencionó anteriormente, un comunicador contiene un contexto o ID y un grupo. La llamada MPI_Comm_groupobtiene una referencia a ese objeto de grupo. El objeto de grupo funciona de la misma manera que un objeto de comunicador, excepto que no puede usarlo para comunicarse con otros rangos (porque no tiene ese contexto adjunto). Aún puede obtener el rango y el tamaño del grupo ( MPI_Group_rankyMPI_Group_size, respectivamente). Sin embargo, lo que puede hacer con los grupos que no puede hacer con los comunicadores es usarlo para construir nuevos grupos localmente. Es importante recordar aquí la diferencia entre una operación local y una remota. Una operación remota implica la comunicación con otros rangos donde una operación local no lo hace. La creación de un nuevo comunicador es una operación remota porque todos los procesos deben decidir sobre el mismo contexto y grupo, donde la creación de un grupo es local porque no se usa para la comunicación y, por lo tanto, no necesita tener el mismo contexto para cada proceso. Puede manipular un grupo todo lo que quiera sin realizar ninguna comunicación en absoluto.

Una vez que tenga un grupo o dos, realizar operaciones en ellos es sencillo. Conseguir la unión se ve así:

MPI_Group_union(
	MPI_Group group1,
	MPI_Group group2,
	MPI_Group* newgroup)

Y probablemente puedas adivinar que la intersección se ve así:

MPI_Group_intersection(
	MPI_Group group1,
	MPI_Group group2,
	MPI_Group* newgroup)

En ambos casos, la operación se realiza en group1group2y el resultado se almacena en newgroup.

Hay muchos usos de grupos en MPI. Puede comparar grupos para ver si son iguales, restar un grupo de otro, excluir rangos específicos de un grupo o usar un grupo para traducir los rangos de un grupo a otro grupo. Sin embargo, una de las adiciones recientes a MPI que tiende a ser más útil es MPI_Comm_create_group. Esta es una función para crear un nuevo comunicador, pero en lugar de hacer cálculos sobre la marcha para decidir la composición, como MPI_Comm_split, esta función toma un MPI_Groupobjeto y crea un nuevo comunicador que tiene todos los mismos procesos que el grupo.

MPI_Comm_create_group(
	MPI_Comm comm,
	MPI_Group group,
	int tag,
	MPI_Comm* newcomm)

Ejemplo de uso de grupos

Veamos un ejemplo rápido de cómo se ven los grupos de uso. A continuación, vamos a utilizar otra nueva función que le permite elegir filas específicas en un grupo y construir un nuevo grupo que contiene sólo aquellas filas, MPI_Group_incl.

MPI_Group_incl(
	MPI_Group group,
	int n,
	const int ranks[],
	MPI_Group* newgroup)

Con esta función, newgroupcontiene los procesos groupcon rangos contenidos en ranks, que es de tamaño n. ¿Quieres ver cómo funciona? Intentemos crear un comunicador que contenga los primeros rangos de MPI_COMM_WORLD.

// Get the rank and size in the original communicator
int world_rank, world_size;
MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
MPI_Comm_size(MPI_COMM_WORLD, &world_size);

// Get the group of processes in MPI_COMM_WORLD
MPI_Group world_group;
MPI_Comm_group(MPI_COMM_WORLD, &world_group);

int n = 7;
const int ranks[7] = {1, 2, 3, 5, 7, 11, 13};

// Construct a group containing all of the prime ranks in world_group
MPI_Group prime_group;
MPI_Group_incl(world_group, 7, ranks, &prime_group);

// Create a new communicator based on the group
MPI_Comm prime_comm;
MPI_Comm_create_group(MPI_COMM_WORLD, prime_group, 0, &prime_comm);

int prime_rank = -1, prime_size = -1;
// If this rank isn't in the new communicator, it will be
// MPI_COMM_NULL. Using MPI_COMM_NULL for MPI_Comm_rank or
// MPI_Comm_size is erroneous
if (MPI_COMM_NULL != prime_comm) {
	MPI_Comm_rank(prime_comm, &prime_rank);
	MPI_Comm_size(prime_comm, &prime_size);
}

printf("WORLD RANK/SIZE: %d/%d \t PRIME RANK/SIZE: %d/%d\n",
	world_rank, world_size, prime_rank, prime_size);

MPI_Group_free(&world_group);
MPI_Group_free(&prime_group);
MPI_Comm_free(&prime_comm);

En este ejemplo, construimos un comunicador seleccionando solo los primeros rangos MPI_COMM_WORLD. Esto se hace con MPI_Group_incly da como resultado prime_group. A continuación, pasamos ese grupo MPI_Comm_create_groupa crear prime_comm. Al final, tenemos que tener cuidado de no usar prime_commen procesos que no lo tienen, por lo tanto, verificamos para asegurarnos de que el comunicador no MPI_COMM_NULLlo esté, que se devuelve MPI_Comm_create_groupen los rangos no incluidos en ranks.

¿Quiere contribuir?

Este resumen de MPI está alojado completamente en GitHub . El autor original (Wes Kendall) ya no contribuye activamente a este sitio, pero se colocó en GitHub con la esperanza de que otros escribieran tutoriales de MPI de alta calidad. Haga clic aquí para obtener más información sobre cómo puede contribuir.

Instalacion de Grafana en Windows


Grafana es un software libre basado en licencia de Apache 2.0,​ que permite la visualización y el formato de datos métricos. Permite crear cuadros de mando y gráficos a partir de múltiples fuentes, incluidas bases de datos de series de tiempo como Graphite, InfluxDB y OpenTSDB​ Originalmente comenzó como un componente de Kibana y que luego le fue realizado una bifurcación.​

Lanzado en 2013 por Grafana Labs,Grafana es una de las soluciones de monitoreo de más rápido crecimiento en uso en 2019. Es multiplataforma sin ninguna dependencia y también se puede implementar con Docker. Está escrito en lenguaje Go y tiene un HTTP API completo. Además de administrar cuadros de mando clásicos (adiciones, eliminaciones, favoritos), Grafana ofrece compartir un cuadro de mando actual mediante la creación de un enlace o una instantánea estática del mismo.

Todos los paneles de control y las fuentes de datos están vinculados a una organización, y los usuarios de la aplicación están vinculados a organizaciones a través de roles.Evita que los usuarios sobrescriban accidentalmente un panel de control. Existe una protección similar cuando se crea un nuevo panel de control cuyo nombre ya existe. La herramienta tambien ofrece la posibilidad de configurar alertas.

Es una solución de dashboarding que conecta a una amplia variedad de orígenes de datos (la mayoría de las bases de datos de series temporales) con el fin de visualizar datos casi en tiempo real siendo uilizada por muchas grandes empresas como el CERN, Digital Ocean o PayPal,Grafana también se utiliza en pequeñas y medianas corporaciones dispuestas a tener retroalimentación sobre la salud de su infraestructura.

Descarga del archivo Grafana desde el sitio web oficial

Para Windows, abra su navegador web y vaya a la página de descargas de Grafana.

Seleccione «Windows» en la lista de sistemas operativos disponibles y haga clic en «Descargar el instalador«.

Grafana for Windows download option

La descarga de MSI debe comenzar.

Cuando haya terminado, simplemente ejecute el instalador msi.

Grafana MSI on Windows

Instalar Grafana en Windows

Al ejecutar el MSI, esto es lo que debería ver.

Grafana installation first step

Haga clic en «Siguiente«.

Grafana installation second step

Acepte los términos del contrato de licencia y haga clic en«Siguiente».

Grafana installation third  step

Asegúrese de que el sistema operativo Grafana (el servidor Grafana) y la opción Grafana como servicio estén correctamente seleccionados.

Si este es el caso, haga clic en «Siguiente» y en «Instalar«

Grafana installation fourth step

En este punto, la instalación de Grafana debe comenzar.

Si en algún momento se le solicita una excepción de firewall, asegúrese de autorizar a Grafana a realizar cambios en el sistema.

Cuando se realiza la instalación, esta es la pantalla que debería ver.

Grafana installation last step

¡Impresionante! Acaba de instalar Grafana en Windows.

Compruebe que su servicio Grafana se está ejecutando

Antes de ir más lejos, debe comprobar que el servidor Grafana se está ejecutando correctamente como un servicio de Windows.

En muchos casos, es posible que el puerto predeterminado de Grafana (3000) ya esté tomado, lo que impide que Grafana se inicie correctamente.

Como consecuencia, así es como puede comprobar que se está ejecutando.

En el menú de búsqueda de Windows, escriba «Servicios» y abra la ventana Servicios.

Windows services panel

En la ventana Servicios, desplácese hasta llegar al servicio Grafana.

Grafana service on Windows

 El servicio Grafana puede comprobar que ya está en funcionamiento.

Iniciar Grafana v6 Web UI

Si el servicio se está ejecutando correctamente, debería poder acceder a la interfaz de usuario de Grafana v6.

Como recordatorio, Grafana se ejecuta de forma predeterminada en el puerto 3000.

Como consecuencia, abra un navegador web y vaya a http://localhost:3000.

Esta es la pantalla que debería ver.

Grafana v6.3 default web UI on Windows

En Grafana, las credenciales predeterminadas son admin (como nombre de usuario) y admin (como contraseña) de forma predeterminada.

En la siguiente ventana, se le pedirá que cambie su contraseña. Elija una contraseña segura para evitar brechas de seguridad.

Grafana change password window

Cuando lo haya hecho pulsar en “Save“.

Ahora debería ver la pantalla predeterminada para Grafana v6.3 en Windows.

Grafana welcome default screen

Antes de crear sus propios paneles, hay algunos pasos de configuración que debe realizar para una nueva instancia.

Definición de su propio archivo de configuración

En las últimas distribuciones grafana para Windows, el servicio es lanzado por NSSM (que es un administrador de servicios para Windows).

De forma predeterminada, Grafana se basa en los archivos de configuración ubicados en la carpeta conf del directorio de instalación.

Mine se encuentra en C:\Program Files\GrafanaLabs\grafana como ejemplo.

Este es el contenido de la carpeta conf.

Grafana configuration folder on Windows

De forma predeterminada, Grafana va a usar el contenido del archivo .ini predeterminado, pero vamos a sobrescribir eso para tener nuestro propio archivo de configuración personalizado.

En caso de que estemos teniendo algunos problemas con nuestro propio archivo de configuración, podemos volver al archivo predeterminado fácilmente.

Haga una copia del archivo predeterminado y asígnele el nombre «custom.ini»

Adding a custom configuration file

Al ejecutar la instalación MSI, Grafana va a almacenar un ejecutable de NSSM en la carpeta GrafanaLabs de la carpeta de instalación.

NSSM executable on Windows

Abra una instancia de Powershell en el equipo como administradory vaya a esta carpeta.

No olvide las comillas alrededor de la ruta de acceso, de lo contrario obtendrá una excepción de Powershell.

$ cd "C:\Program Files\GrafanaLabs"
$ .\nssm.exe edit grafana

Se debe abrir una ventana similar a esta.

NSSM window edit service

Al final de la variable path, agregue una marca –config con el nombre del archivo de configuración que acabamos de crear.

Adding a custom argument on NSSM
Argumentos: --config conf\custom.ini

pulsar en “Edit Service” y debderia ver el siguinte mensajesuccess message.

Service Grafana edit successfully

Reinicie el servicio y asegúrese de que Grafana sigue ejecutándose correctamente (http://localhost:3000)

Avtivar sign-up en Grafana

De forma predeterminada, la creación de la cuenta se encuentra en http://localhost:3000/signup en la instancia de Grafana.

En Grafana v6.3, el registro de la cuenta está deshabilitado de forma predeterminada.

Sign up option on Grafana

Sin embargo, es posible que desee habilitar esta opción en su servidor Grafana, si autoriza a los invitados a tener una cuenta para mostrar algunos paneles especiales.

Para habilitar el registro, diríjase al archivo de configuración personalizado.ini (ubicado en la carpeta conf) y vaya a la sección [usuarios] del archivo.

Modifique la entrada allow_sign_up a true..

Allowing user sign up on Grafana

Para comprobar que todo funciona correctamente, vaya a http://localhost:3000/signup e intente crear una cuenta.

Grafana sign up page

Haga clic en «Registrarse» y debe ser redirigido a la página principal.

Grafana user panel

Activar acceso anonimo

De forma predeterminada, el acceso anónimo está deshabilitado en Grafana.

El acceso anónimo significa que cualquier usuario no autenticado puede examinar la instancia de Grafana, al menos para la organización predeterminada que se les ha asignado.

Grafana anonymous access option

Si va http://localhost:3000, se le redirigirá a la pantalla de inicio de sesión si es un usuario anónimo.

Para cambiar eso, edite el archivo de configuración personalizado.ini y edite la sección [auth.anonymous].

Grafana anonymous access configuration file

Reinicie el servicio Grafana y busque http://localhost:3000 como usuario anónimo (en modo privado, por ejemplo).

Ahora debe tener un permiso viewer para los paneles permitidos.

Access allowed for anonymous users on Grafana on Windows

Aquí hay una selección de artículos que debe leer para mejorar con Grafana :

Configuración de Visual Studio Code y WSL para el desarrollo MPI


Muchos planes de estudios requieren que se realicen pequeños programas con  MPI como parte del curso de Computación paralela, por lo que en este post   intentaremos ver como hacer la configuración de WSL (Subsistema de Windows para Linux) y Visual Studio Code para el desarrollo de MPI, que se ejecuta en Ubuntu WSL Distro de Microsoft Store

Configuración de WSL

En las versiones recientes de Windows, WSL está habilitado de forma predeterminada y todo lo que necesita hacer es instalar una distribución debajo de él.

Comenzaremos descargando la distribución de Ubuntu de Microsoft Store. Abra la tienda desde el menú de inicio y busque Ubuntu 18.04 o haga clic aquí

Imagen para publicación
Toca «Obtener» para instalar la distribución de Ubuntu de WSL

después de  instalarlo, ábralo y siga todas las instrucciones para ingresar su nombre de usuario y contraseña predeterminados, etcétera.

Instalación de OpenMPI y herramientas de compilación para OpenMPI

Ahora debería estar mirando una ventana de línea de comandos, ejecutando bash, con el logotipo de ubuntu como el icono de la aplicación en la barra de tareas. Si ve esto, significa que ha instalado con éxito la distribución de Ubuntu en WSL

Ahora escriba el siguiente comando para instalar OpenMPI. En el caso de la siguiente captura de pantalla, ya se ha instalado.

sudo apt update && sudo apt install openmpi-bin libopenmpi-dev

Después de esto, inmediatamente podrá usar mpicc y mpirun para ejecutar y compilar programas que usen MPI, pero pronto verá este mensaje.

WARNING: Linux kernel CMA support was requested via thebtl_vader_single_copy_mechanism MCA variable, but CMA support isnot available due to restrictive ptrace settings.

Esto se debe a uno de los parámetros con los que WSL está configurado de forma predeterminada. Para resolver esto, necesitamos ejecutar este comando

  echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope

Después de esto, la advertencia simplemente desaparecerá.

Para compilar con MPI  simplemente debemos usr el compilador mpicc  , como por ejemplo:

>mpicc -o ejemplo ejemplo.c

Y para ejecutarlo usaremos mpirun  pasándole con el argumento -n el numero de procesadores:

mpirun -n 2 ./ejemplo

Ahora veamos un ejemplo sencillo para probar mpi :

 

#include<stdio.h>

#include<mpi.h>

int main( int argc, char* argv[] ) {

    int rank, size;

  

    // Paralelismo

    MPI_Init( &argc, &argv );

    // Indice del proceso

    MPI_Comm_size( MPI_COMM_WORLD, &size );

    // Tamano del comunicador seleccionado

    MPI_Comm_rank( MPI_COMM_WORLD, &rank );

    printf( "Hola mundo! Soy el proceso numero %d. En total somos %d procesos.\n", rank, size );




    MPI_Finalize();

}

 

 Para poder ejecutar con más procesadores  de los que físicamente tiene el equipo al usar  mpirun  pasándole con el argumento -n (el numero de procesadores) , nos dará un error :
>mpirun -n 4 ./ejemplo
There are not enough slots available in the system to satisfy the 3slots that were requested by the application:
  ./Step6-SendReceive
Either request fewer slots for your application, or make more slotsavailable for use.
A "slot" is the Open MPI term for an allocatable unit where we canlaunch a process.  The number of slots available are defined by theenvironment in which Open MPI processes are run:
  1. Hostfile, via "slots=N" clauses (N defaults to number of     processor cores if not provided)  2. The --host command line parameter, via a ":N" suffix on the     hostname (N defaults to 1 if not provided)  3. Resource manager (e.g., SLURM, PBS/Torque, LSF, etc.)  4. If none of a hostfile, the --host command line parameter, or an     RM is present, Open MPI defaults to the number of processor cores
In all the above cases, if you want Open MPI to default to the numberof hardware threads instead of the number of processor cores, use the--use-hwthread-cpus option.
Alternatively, you can use the --oversubscribe option to ignore thenumber of available slots when deciding the number of processes tolaunch.

Como no es difícil de adivinar el error advierte de que intentamos ejecutar  en mas procesadores los que realmente se tienen en nuestra máquina .Para simular los procesadores que no se tiene podemos utilizar el flag –oversubscribe

 
Sería así:
mpirun -np 4 –oversubscribe ./programa

 

 

Si solo desea ejecutar programas en el terminal y no necesitar configurar Visual Studio Code, ahora puede hacerlo usando mpicc y mpirun desde la consola .De lo contrario, siga leyendo para descubrir cómo configurar VSCode para la tarea.

Configuración de VSCode

Suponemos que ya tiene VSCode instalado, si no, descárguelos de su sitio web .

Necesitaremos instalar WSL Remote desde su página de Extensiones de Visual Studio , después de eso, haga clic en el nuevo ícono en la parte inferior izquierda para iniciar una nueva sesión de WSL.

Imagen para publicación

Después de eso, le pedirá que elija un directorio que desea abrir. Continúe y abra el directorio en el que planea realizar su desarrollo.

Para habilitar el resaltado de sintaxis, intellisense y otras características conscientes del lenguaje, también necesitamos configurar el soporte C / C ++ en VSCode. Para eso, vaya al panel de extensiones e instálelo

Imagen para publicación

Ahora, por defecto, VSCode no va a poder encontrar mpi.h, lo cual es fundamental para poder brindar sugerencias para Interfaces MPI, para eso necesitaremos configurar el includePath y la ruta ejecutable en el C / C ++ Ajustes.

 

Probablemente tenga  un archivo llamado c_ccp_properties.json en la carpeta .vscode en su directorio del programa 
    "configurations": [

        {

            "name": "Win32",

            "includePath": [

                "${workspaceFolder}/**", "C:\\Program Files (x86)\\Microsoft SDKs\\MPI\\Lib\\"

            ],

            "defines": [

                "_DEBUG",

                "UNICODE",

                "_UNICODE"

            ],

            "compilerPath": "C:\\MinGW\\bin\\gcc.exe",

            "cStandard": "gnu11",

            "cppStandard": "gnu++14",

            "intelliSenseMode": "windows-gcc-x86"

        }

    ],

    "version": 4

}

 Modifique ese archivo llamado c_ccp_properties.json en la carpeta .vscode en su directorio del programa MPI para poderlo  ejecutar desde Linux :

{

    "configurations": [

        {

            "name": "Linux",

            "includePath": [

                "${workspaceFolder}/**",

                "/usr/lib/x86_64-linux-gnu/openmpi/include"

            ],

            "defines": [],

            "compilerPath": "/usr/bin/mpicc",

            "cStandard": "c11",

            "cppStandard": "c++17",

            "intelliSenseMode": "clang-x64"

        }

    ],

    "version": 4

}
 
Se puede conseguir auto construcción y ejecución  por lo que además  de lo anteriror, si desea poder compilar y ejecutar directamente desde VSCode, debemos agregar las siguientes tareas de compilación y lanzamiento
 
fichero=launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "(mpirun) Launch",
"type": "cppdbg",
"request": "launch",
"program": "/usr/bin/mpirun",
"args": ["-np", "4", "${fileDirname}/${fileBasenameNoExtension}.out"],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,

}
]
}

fichero task.json

 
{

    "version": "2.0.0",

    "tasks": [

        {

            "type": "shell",

            "label": "gcc build active file",

            "command": "/usr/bin/gcc",

            "args": [

                "-g",

                "${file}",

                "-o",

                "${fileDirname}/${fileBasenameNoExtension}"

            ],

            "options": {

                "cwd": "/usr/bin"

            },

            "problemMatcher": [

                "$gcc"

            ],

            "group": "build"

        },

        {

            "type": "shell",

            "label": "mpicc build active file",

            "command": "/usr/bin/mpicc",

            "args": [

                "-g",

                "${file}",

                "-o",

                "${fileDirname}/${fileBasenameNoExtension}.out"

            ],

            "options": {

                "cwd": "/usr/bin"

            },

            "problemMatcher": [

                "$gcc"

            ],

            "group": {

                "kind": "build",

                "isDefault": true

            }

        },

    ]

}

Con esa configuración, debería poder usar sus atajos de teclado predeterminados para Ejecutar sin depurar (Ctrl + F5) y Generar (Ctrl + Shift + B) para construir y ejecutar sus programas MPI.

Generación manual de una clave SSH en Windows


 

En Windows se puede crear claves SSH de muchas formas. En este post debería aclaranos un poco como utilizar dos aplicaciones SSH, PuTTY y Git Bash .

Muchos proveedores recomiendan claves RSA porque los programas CLI de node-manta funcionan con claves RSA tanto localmente como con el agente ssh y las claves DSA solo funcionarán si la clave privada está en el mismo sistema que la CLI y no está protegida por contraseña.

Putty

PuTTY es un cliente SSH para Windows. Puede utilizar PuTTY para generar claves SSH. PuTTY es un emulador de terminal de código abierto gratuito que funciona de manera muy similar a la aplicación Terminal en macOS en un entorno Windows. Esta sección le muestra cómo generar y cargar manualmente una clave SSH cuando trabaja con PuTTY en el entorno de Windows.

 

PuTTY es un cliente SSH para Windows que utilizará para generar sus claves SSH. Puede descargar PuTTY desde www.chiark.greenend.org.uk .

Cuando instala el cliente PuTTY, también instala la utilidad PuTTYgen. PuTTYgen es lo que usará para generar su clave SSH para una VM de Windows.

Generando una clave SSH

Para generar una clave SSH con PuTTYgen, siga estos pasos:

  1. Abra el programa PuTTYgen.
  2. Para Tipo de clave a generar , seleccione SSH-2 RSA .
  3. Haga clic en el botón Generar .
  4. Mueva el mouse en el área debajo de la barra de progreso. Cuando la barra de progreso está llena, PuTTYgen genera su par de claves.
  5. Escriba una frase de contraseña en el campo Frase de contraseña de clave . Escriba la misma contraseña en el campo Confirmar contraseña . Puede utilizar una clave sin una frase de contraseña, pero no se recomienda.
  6. Haga clic en el botón Guardar clave privada para guardar la clave privada. ¡Advertencia!Usted debe guardar la clave privada. Lo necesitará para conectarse a su máquina.
  7. Haga clic con el botón derecho en el campo de texto etiquetado como Clave pública para pegar en el archivo de claves autorizadas de OpenSSH y elija Seleccionar todo .
  8. Vuelva a hacer clic derecho en el mismo campo de texto y elija Copiar .

Importando su clave SSH

Ahora debe importar la clave SSH copiada al portal.

  1. Después de copiar la clave SSH al portapapeles, regrese a la página de su cuenta de su proveedor .
  2. Elija Importar clave pública y pegue su clave SSH en el campo Clave pública.
  3. En el campo Nombre de clave , proporcione un nombre para la clave. Nota : aunque proporcionar un nombre de clave es opcional, es una buena práctica para facilitar la administración de varias claves SSH.
  4. Agrega la clave. Ahora aparecerá en su tabla de claves bajo SSH.
La tabla de claves SSH

¡Advertencia!PuTTY y OpenSSH usan diferentes formatos de claves SSH públicas. Si el texto que pegó en la clave SSH comienza con —— BEGIN SSH2 PUBLIC KEY, está en el formato incorrecto. Asegúrese de seguir las instrucciones cuidadosamente. Tu clave debe comenzar con ssh-rsa AAAA….

Una vez que cargue su clave SSH en el portal, puede conectarse a su máquina virtual desde Windows a través de una sesión PuTTY.

Git Bash

El paquete de instalación de Git viene con SSH. Con Git Bash, que es la herramienta de línea de comandos de Git, puede generar pares de claves SSH. Git Bash tiene un cliente SSH que le permite conectarse e interactuar con los contenedores de Triton en Windows.

Para instalar Git:

  1. (Descargue e inicie el instalador de Git] ( https://git-scm.com/download/win ).
  2. Cuando se le solicite, acepte los componentes predeterminados haciendo clic en Siguiente .
  3. Elija el editor de texto predeterminado. Si tiene Notepad ++ instalado, seleccione Notepad ++ y haga clic en Siguiente .
  4. Seleccione Usar Git en el símbolo del sistema de Windows y haga clic en Siguiente .
  5. Seleccione Usar la biblioteca OpenSSL y haga clic en Siguiente .
  6. Seleccione Checkout Windows-style, confirme los finales de línea de estilo Unix y haga clic en Siguiente .
  7. Seleccione Usar MinTTY (el terminal predeterminado de mYSYS2) y haga clic en Siguiente .
  8. Acepte la configuración de opción adicional predeterminada haciendo clic en Instalar .

Cuando se complete la instalación, es posible que deba reiniciar Windows.

Lanzamiento de GitBash

Para abrir Git Bash, recomendamos iniciar la aplicación desde el símbolo del sistema de Windows:

  1. En Windows, presione Inicio + R para abrir el cuadro de diálogo Ejecutar .
  2. Escribe C:\Program Files\Git\bin\bash.exey presiona Enter .

Generando claves SSH

Primero, cree el directorio SSH y luego genere el par de claves SSH.

Una suposición es que el perfil de Windows que está utilizando está configurado con privilegios administrativos. Dado esto, creará el directorio SSH en la raíz de su perfil, por ejemplo:

C:\Users\joetest
  1. En la línea de comando de Git Bash, cambie a su directorio raíz y escriba.
mkdir .ssh
  1. Cambie al directorio .ssh C:\Users\joetest\.ssh
  2. Para crear las claves, escriba:
ssh-keygen.exe
  1. Cuando se le solicite una contraseña, escriba una contraseña para completar el proceso. Cuando termine, el resultado será similar a:
Ssh-keygen.exe
Generating public/private rsa key pair.
Enter file in which to save the key (/c/Users/joetest/.ssh/id_rsa): /c/Users/joetest/.ssh/
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /c/Users/joetest/.ssh/
Your public key has been saved in /c/Users/joetest/.ssh/
The key fingerprint is:
SHA256:jieniOIn20935n0awtn04n002HqEIOnTIOnevHzaI5nak [email protected]
The key's randomart image is:

 +---[RSA 2048]----+
 |*= =+.           |
 |O*=.B            |
 |+*o* +           |
 |o +o.  .         |
 | ooo  + S        |
 | .o.ooo* o       |
 |  .+o+*oo .      |
 |   .=+..         |
 |   Eo            |
 +----[SHA256]-----+

$ dir .ssh
id_rsa  id_rsa.pub   

Subiendo una clave SSH

Para cargar la clave SSH pública en su cuenta de  su proveedor :

  1. Abra el portal del servicio , seleccione Cuenta para abrir la página Resumen de cuenta .
  2. En la sección SSH , seleccione Importar clave pública .
  3. Ingrese un nombre de clave . Aunque nombrar una clave es opcional, las etiquetas son una práctica recomendada para administrar varias claves SSH.
  4. Agrega tu clave SSH pública.

Cuando Triton finaliza el proceso de agregar o cargar, la clave SSH pública aparece en la lista de claves SSH.

Autenticacion de usuarios en asp.net


En ciertas ocasiones, necesitamos que nuestra aplicación web ASP.NET disponga de un sistema de autenticación básica de usuarios para realizar, por ejemplo, tareas administrativas de configuración a través de un módulo de administración integrado que solo sea accesible para usuarios autenticados. Para este escenario en concreto, bastaría con implementar un simple sistema de Login para que el o los encargados de administrar la aplicación puedan autenticarse mediante su usuario y contraseña en el sistema.

En este Post veremos cómo crear un sistema de Login basado en FormsAuthentication con encriptado de contraseña, y como integrarlo en una aplicación web ASP.NET MVC. También veremos como solucionar los problemas derivados de publicar nuestra aplicación en un Web hosting en clúster con balanceo de carga.

Nota: Antes de comenzar a desarrollar el Post, comentar que este ejemplo está desarrollado en ASP.NET MVC4 Framework 4.0 aunque también es perfectamente compatible con aplicaciones ASP.NET MVC5  Framework 4.5.

FormsAuthentication (Web.config)

En primer lugar crearemos el o los usuarios administradores de la aplicación. Este proceso lo realizamos en el elemento <authentication/>, dentro de la sección <system.web/> del archivo de configuración Web.config de la aplicación.

<authentication mode="Forms">
  <forms name=".ASPXAUTH" loginUrl="~/Login" timeout="10000">
    <credentials passwordFormat="SHA1">
      <user name="usuario" password="contraseña-encriptada" />
      <!-- Otros usuarios aquí... -->          
    </credentials>
  </forms>
</authentication>

mode="Forms": Habilita la autenticación mediante formularios.

name=".ASPXAUTH": Nombre de la Cookie que contendrá el Ticket de autenticación.

loginUrl="~/Login":  URL de redirección, en el caso de que ASP.NET no encuentre la Cookie de autenticación.

timeout="10000": Tiempo de expiración de la Cookie de autenticación expresado en minutos.

passwordFormat="SHA1": Algoritmo de encriptación de las contraseñas de usuario (SHA1 hash).

name="admin": Nombre del usuario.

password="contraseña-encriptada": Contraseña del usuario encriptada en formato SHA1 (más adelante veremos como realizar el proceso de encriptación).

El Controlador y las Vistas

A continuación crearemos la infraestructura necesaria para desarrollar el sistema de autenticación y administración de la aplicación.

Creamos un Controlador básico con las Acciones necesarias para el sistema de Login y las tareas administrativas (posteriormente implementaremos el código para cada una de estas Acciones).

    public class AdminController : Controller
    {
        // GET: Admin
        public ActionResult Index()
        {
            // Aquí acceso al módulo de administración...
            return View();
        }

        // GET: Admin/Login
        public ActionResult Login()
        {
            // Aquí acceso a la 'Vista' de Login...
            return View();
        }

        // POST: Admin/Login
        [HttpPost]
        public ActionResult Login(string usr, string pwd, string rme)
        {
            // Aquí lógica de autenticación...
            return View();
        }

        // GET: Admin/Logout
        public ActionResult Logout()
        {
            // Aquí lógica de desconexión...
            return View();
        }
    }

Creamos también las Vistas (por supuesto siempre dentro del directorio de referencia del ControladorViews\Admin\). Para este ejemplo necesitaremos 2 Vistas: Login.cshtml e Index.cshtml.

Login.cshtml: Vista que contendrá el formulario de autenticación de usuarios. Este sería un ejemplo básico de Login mediante usuario y contraseña con la opción Remember me, basado en Bootstrap 3.2:

<div class="container-fluid">
    <div class="row">
        <div class="col-sm-6 col-md-4 col-md-offset-4">
            <div class="text-center">
                <div class="text-center">
                    <img class="img-thumbnail" src="~/Images/admin.png" width="128">
                </div>
                <br />
                @using (Html.BeginForm("Login","Admin"))
                {                
                    <input type="text" class="form-control" name="usr" 
                           placeholder="Usuario" required autofocus>
                    <br />
                    <input type="password" class="form-control" name="pwd" 
                           placeholder="Contraseña" required>
                    <br />
                    <button class="btn btn-lg btn-primary btn-block" type="submit">
                        Iniciar sesión
                    </button>
                    <label class="checkbox pull-left">
                        <input type="checkbox" name="rme" checked="">
                        <label>Remember me</label>
                    </label>                
                }
            </div>
        </div>
    </div>
</div>

Index.cshtml: Esta sería la Vista principal del módulo de administración. En un caso práctico podría ser un panel de control al estilo AdminLTE.

El código para la autenticación

Por último, solo quedaría codificar las Acciones del Controlador para dar la funcionalidad de autenticación al sistema de Login.

Esto lo haremos a través de la clase FormsAuthentication del espacio de nombres System.Web.Security. Esta clase proporciona una serie de métodos estáticos que interactuan con la configuración anteriormente establecida en el Web.config <authentication/>.

A continuación el código:

    public class AdminController : Controller
    {
        // GET: Admin
        public ActionResult Index()
        {
            if (this.Request.IsAuthenticated)
            {
                // Si el usuario está autenticado 
                // retorna la Vista de administración.
                return View();
            }
            else
            {
                // Si no, retorna la Vista de Login.
                return RedirectToAction("Login", "Admin");
            }
        }

        // GET: Admin/Login
        public ActionResult Login()
        {
            // Retorna la Vista de Login
            return View();
        }

        // POST: Admin/Login
        [HttpPost]
        public ActionResult Login(string usr, string pwd, string rme)
        {
            if (string.IsNullOrEmpty(usr) || string.IsNullOrEmpty(pwd))
            {
                return View();
            }
            else
            {
                bool _rememberMe = rme == "on" ? true : false;

                // Valida el usuario con los registrados en la
                // seccion <authentication/> del Web.config
                if (FormsAuthentication.Authenticate(usr, pwd))
                {
                    // Crea la Cookie con el Ticket de autenticación 
                    // para el usuario.
                    FormsAuthentication.SetAuthCookie(usr, _rememberMe);
                    // Redirige a la Accion 'Index'
                    return RedirectToAction("Index");
                }
                else
                {
                    // Si no es un usuario válido, retorna la
                    // Vista de Login.
                    return View();
                }
            }
        }

        // GET: Admin/Logout
        public ActionResult Logout()
        {
            // Elimina la Cookie con el Ticket de autenticación 
            // para el usuario.
            FormsAuthentication.SignOut();
            // Redirige a la Accion 'Index'
            return this.RedirectToAction("Index");
        }
    }

Encriptado de contraseña

Siempre es una buena práctica encriptar las contraseñas y datos sensibles de nuestra aplicación, a la hora de publicarla en un Web hosting externo a nuestra infraestructura corporativa. En el caso que nos ocupa, habíamos configurado la sección <authentication/> para utilizar contraseñas de usuario en formato SHA1 hash. Un ejemplo real con nombre de usuario ‘admin‘ y contraseña ‘admin‘, sería el siguiente:

<credentials passwordFormat="SHA1">
   <user name="admin" password="D033E22AE348AEB5660FC2140AEC35850C4DA997" />
</credentials>

Existen infinidad de páginas en Internet que generan online la cadena encriptada de caracteres SHA1 a partir de un texto dado, http://www.sha1-online.com es un ejemplo de estas.

Publicando nuestra aplicación (Web hosting)

En este punto ya hemos integrado el sistema de autenticación a nuestra aplicación ASP.NET MVC y todo funciona correctamente en nuestro servidor IIS local o de desarrollo. Es la hora entonces de publicar la aplicación en los servidores web de nuestro proveedor de hosting

Comenzamos a testear la aplicación, nos autenticamos en el sistema (Login), navegamos a través de las páginas que requieren usuarios autenticados, y en pocos minutos (o segundos) vemos como hemos perdido la autenticación y la aplicación nos redirige a la página de Login. Afortunadamente este es un problema muy común y de fácil solución.

El problema

Los proveedores de servicios de hosting funcionan con granjas de servidores web (Clustering) que implementan balanceo de carga para maximizar el rendimiento de las peticiones http y minimizar la carga de trabajo.

Cuando nos autenticamos en nuestra aplicación (Login), el servidor que atiende la petición crea un Ticket de autenticación que se almacena en una cookie que es enviada a nuestro navegador y nos permite navegar por las páginas que requieren usuarios autenticados.

Este Ticket de autenticación es creado a partir de que lo se llama un machineKey (clave de máquina). Cuando nuestra aplicación arranca por primera vez en un servidor web, un machineKey es creado automáticamente. Al autenticarnos, este machineKey es utilizado para firmar digitalmente el Ticket de autenticación. Por otra parte, cuando navegamos a una página que requiere usuarios autenticados, el servidor utiliza el machineKey para desencriptar el Ticket de autenticación de la Cookie para validar el usuario. 

El problema surge cuando es un servidor diferente (por el balanceo de carga) el que atiende la petición de autenticación, e intenta desencriptar el Ticket de autenticación con un machineKey diferente. El resultado de esta acción causaría la invalidación del Ticket de autenticación original (y de la Cookie), y la redirección del usuario a la página de Login.

La solución

ASP.NET nos permite definir un machineKey personalizado para cada una de nuestras aplicaciones web. Esto implica que los servidores donde se ejecute la aplicación, no auto-generen uno propio y utilicen el que la aplicación les propone.

La configuración del machineKey se realiza dentro de la sección <system.web/> del archivo de configuración Web.config de la aplicación:

<machineKey validationKey="B2A3A7ED71A85AC9BCBBBAD8407F9642EEFA3B17FD2583CFD66A3BAF407900D2C45A85379FA2B4BE2397BECF14650306B6292C96418B772055F01E1EE751E434"
decryptionKey="E60EC68AB3752322D236E5727370620398818355316FB5FDFBF00EE4344AE7B7"
validation="SHA1" decryption="AES" /> 

Es recomendable generar un machineKey aleatorio (y diferente) para cada una de la aplicaciones que queramos publicar en nuestro Web hostingDesde este sitio web: ASP.Net MachineKey Generator podemos generar nuestros machineKey en función del Framework ASP.NET que estemos utilizando.

Jupyter Notebook sin instalar y gratis


Colaboratory, también llamado «Colab», permite ejecutar y programar en Python en un navegador con las siguientes ventajas:

  • No requiere configuración
  • Da acceso gratuito a GPUs
  • Permite compartir contenido fácilmente

Colab puede facilita pues  su trabajo, ya sea estudiante, científico de datos o investigador de IA pues como vemos es una palataforma «en la nube » que nos permite  implementar y probar código  sin tener que instalar nada  en nuestro equipo ademas con la certeza de que la plataforma siempres esta actualizada

Los cuadernos de Colab  permiten combinar código ejecutable y texto enriquecido en un mismo documento, además de imágenes, HTML, LaTeX y mucho más. Los cuadernos que creas en Colab se almacenan en su cuenta de Google Drive de modo qu e  puedes compartir sus cuadernos de Colab fácilmente con compañeros de trabajo o amigos, lo que les permite comentarlos o incluso editarlos (consulte más información en Información general sobre Colab).

Colab ademas es una herramienta muy utilizada en la comunidad de aprendizaje automático. Estos son algunos ejemplos de las aplicaciones que tiene Colab:

  •  Dar los primeros pasos con TensorFlow
  •  Desarrollar y entrenar redes neuronales
  •  Experimentar con TPUs
  •  Divulgar datos de investigación sobre IA
  •  Crear tutoriales

 

Primeros pasos

Para crear un cuaderno de Colab, puedes usar el menú Archivo que aparece arriba o bien acceder al enlace para crear un cuaderno de Colab.Los cuadernos de Colab son cuadernos de Jupyter alojados en Colab (puede obtener más información sobre el proyecto Jupyter en  jupyter.org).

 Un cuaderno es una lista de celdas que contienen texto explicativo o código ejecutable y su salida: simplemente tenemos que hacer clic en una celda para seleccionarla.

 Una vez que el botón de la barra de herramientas indique CONECTADO, haga clic en la celda para seleccionarla y ejecutar el contenido de las siguientes maneras:

  • Haga clic en el icono Reproducir en el margen izquierdo de la celda;
  • Escriba Cmd / Ctrl + Enter para ejecutar la celda en su lugar;
  • Escriba Shift + Enter para ejecutar la celda y mover el foco a la siguiente celda (agregando una si no existe ninguna);
  • Escriba Alt + Enter para ejecutar la celda e inserte una nueva celda de código inmediatamente debajo de ella.
  • Hay opciones adicionales para ejecutar algunas o todas las celdas en el menú Runtime.

En las celdas de texto puede hacer doble clic para editar esta celda. Las celdas de texto usan sintaxis  reducida . También puede agregar matemáticas a las celdas de texto usando LaTeX para ser renderizadas por MathJax. Simplemente coloque la declaración dentro de un par de signos $. Por ejemplo, $ \ sqrt {3x-1} + (1 + x) ^ 2 $ se convierte en 3x − 1 −−−−− √ + (1 + x) 2.

 

 Agregar y mover celdas

Puede agregar nuevas celdas usando los botones ** + CÓDIGO ** y ** + TEXTO ** que se muestran cuando se desplaza entre las celdas. Estos botones también se encuentran en la barra de herramientas sobre el cuaderno, donde se pueden usar para agregar una celda debajo de la celda seleccionada actualmente. Puede mover una celda seleccionándola y haciendo clic en ** Celda arriba ** o ** Celda abajo ** en la barra de herramientas superior. Las celdas consecutivas se pueden seleccionar mediante «selección de lazo» arrastrando desde fuera de una celda y a través del grupo. Las celdas no adyacentes se pueden seleccionar simultáneamente haciendo clic en una y luego manteniendo presionada la tecla Ctrl mientras hace clic en otra. De manera similar, usar Shift en lugar de Ctrl seleccionará todas las celdas intermedias.

Alias del sistema Jupyter incluye atajos para operaciones comunes, como ls:

Eso probablemente generó una gran salida. Puede seleccionar la celda y borrar la salida mediante las dos siguintes acciones:

  •  Haciendo clic en el botón borrar salida (x) en la barra de herramientas sobre la celda;
  • Haga clic con el botón derecho en el margen izquierdo del área de salida y seleccione «Borrar salida» en el menú contextual.

Ejecute cualquier otro proceso usando! con interpolación de cadenas de variables de Python, y tenga en cuenta que el resultado se puede asignar a una variable:

Colaboratory comparte la noción de magia de Jupyter. Hay anotaciones abreviadas que cambian la forma en que se ejecuta el texto de una celda. Para obtener más información, consulte la página de magia de Jupyter.

 

Finalizaciones automáticas y exploración de código

Colab proporciona finalizaciones automáticas para explorar atributos de objetos de Python, así como para ver rápidamente cadenas de documentación. Como ejemplo, primero ejecute la siguiente celda para importar el módulo numpy.

import numpy as np

Si ahora inserta el cursor después de np y presiona Punto (.), Verá la lista de finalizaciones disponibles dentro del módulo np. Las terminaciones se pueden abrir nuevamente usando Ctrl + Espacio.

np

Si escribe un paréntesis abierto después de cualquier función o clase en el módulo, verá una ventana emergente de su cadena de documentación:

np.ndarray

La documentación se puede abrir nuevamente usando Ctrl + Shift + Espacio o puede ver la documentación del método colocando el mouse sobre el nombre del método. Al pasar el cursor sobre el nombre del método, el enlace Abrir en pestaña abrirá la documentación en un panel persistente.

El enlace Ver fuente navegará hasta el código fuente del método.

Formato de excepción

Las excepciones están bien formateadas en las salidas de Colab:

Comentar  en una celda

Puede comentar en un cuaderno de Colaboratory como lo haría en un documento de Google. Los comentarios se adjuntan a las celdas y se muestran junto a la celda a la que hacen referencia.

Si tiene permisos de solo comentarios, verá un botón de comentario en la parte superior derecha de la celda cuando pase el cursor sobre él.

Si tiene permisos de edición o comentario, puede comentar en una celda de una de estas tres formas:

  • Seleccione una celda y haga clic en el botón de comentario en la barra de herramientas sobre la esquina superior derecha de la celda.
  • Haga clic con el botón derecho en una celda de texto y seleccione
  • Agregar un comentario en el menú contextual. Use el atajo Ctrl + Shift + M para agregar un comentario a la celda seleccionada actualmente.

Puede resolver y responder a los comentarios, y puede orientar los comentarios a colaboradores específicos escribiendo + [dirección de correo electrónico] (por ejemplo, [email protected]). Se enviará un correo electrónico a los colaboradores a los que se dirija. El botón Comentar en la esquina superior derecha de la página muestra todos los comentarios adjuntos al cuaderno.

 

Resultados ricos e interactivos

Hasta ahora, todos los resultados generados han sido texto, pero pueden ser más interesantes, como el cuadro a continuación.

 

Integración con Drive

Colaboratory está integrado con Google Drive. Le permite compartir, comentar y colaborar en el mismo documento con varias personas:

El botón COMPARTIR (arriba a la derecha de la barra de herramientas) le permite compartir la libreta y controlar los permisos establecidos en ella.

  • Archivo-> Hacer una copia crea una copia del cuaderno en Drive.
  • Archivo-> Guardar guarda el archivo en la unidad.
  • Archivo-> Guardar y el punto de control fija la versión para que no se elimine del historial de revisión.
  • Archivo-> Historial de revisiones muestra el historial de revisiones del portátil.

Algunos enlaces sobre  Colaboratory  para  mostrar la potencia  que tiene esta potente herramienta incluso corriendo en la red: 

 

Ciencia de datos

Con Colab, tambien puede aprovechar toda la potencia de las bibliotecas más populares de Python para analizar y visualizar datos. La celda de código de abajo utiliza NumPy para generar datos aleatorios y Matplotlib para visualizarlos. Para editar el código, solo tiene que hacer clic en la celda.

 

Puede importar tus propios datos a los cuadernos de Colab desde su cuenta de Google Drive, incluidas las hojas de cálculo, y también desde GitHub y muchas fuentes más.

Aqui algunos enlaces sobre uso de los datos:

 Aprendizaje automático

Con Colab, puedesimportar un conjunto de datos de imágenes, entrenar un clasificador de imágenes con dicho conjunto de datos y evaluar el modelo con tan solo usar unas pocas líneas de código. Los cuadernos de Colab ejecutan código en los servidores en la nube de Google, lo que te permite aprovechar la potencia del hardware de Google, incluidas las GPU y TPU, independientemente de la potencia de su equipo pues lo único que necesita es un navegador.

A continuación, se muestran algunos cuadernos del curso online de Google sobre aprendizaje automático. Para obtener más información, consulta el sitio web del curso completo.

 

Por cierto , toda esta informacion de este post podemos verla en ingles directamente en los propios notebook  gratuitos de Google  (observe que la mayoria de los enalces  suelen tener  extension  ipynb tipica de los noetbooks de Jupiter ) 

Instalar Pyenv en W10


pyenv para python es una gran herramienta pero, como rbenv para desarrolladores ruby, pero no es compatible con Windows directamente. Después de un poco de investigación y comentarios de los desarrolladores de Python, descubrí que querían una característica similar para los sistemas Windows.

El autor que ha desarrollado esta herrmienta se inspiró en el problema de pyenv para el soporte de Windows auque el usa Mac y Linux conpyenv , pero algunas empresas todavía usan Windows para el desarrollo. Esta biblioteca es para ayudar a los usuarios de Windows a administrar múltiples versiones de Python.

El autor encontro un sistema similar para rbenv-win para desarrolladores ruby. Este proyecto se bifurcó de rbenv-win y se modificó para pyenv . pyenv-win está madurando cada día gracias por los contribuyentes y apoyos.

 

pyenv

pyenv es una sencilla herramienta de gestión de versiones de Python. Le permite cambiar fácilmente entre varias versiones de Python. Es simple, discreto y sigue la tradición de UNIX de herramientas de un solo propósito que hacen una cosa bien.

Estos son algunos de los camandos mas usados:

 

   commands     List all available pyenv commands
   local        Set or show the local application-specific Python version
   global       Set or show the global Python version
   shell        Set or show the shell-specific Python version
   install      Install 1 or more versions of Python 
   uninstall    Uninstall 1 or more versions of Python
   update       Update the cached version DB
   rehash       Rehash pyenv shims (run this after switching Python versions)
   vname        Show the current Python version
   version      Show the current Python version and its origin
   versions     List all Python versions available to pyenv
   exec         Runs an executable by first preparing PATH so that the selected Python
   which        Display the full path to an executable
   whence       List all Python versions that contain the given executable

 

 

 

 

Instalación

 

Obtener pyenv-win

Obtenga pyenv-win mediante uno de los siguientes métodos:

  • Con pip (para admitir usuarios de Python existentes)
    • Powershell o Git Bash: pip install pyenv-win --target "$HOME\.pyenv"
    • cmd.exe: pip install pyenv-win --target "%USERPROFILE%\.pyenv"
  • Con archivo zip
    1. Enlace de descarga: pyenv-win
    2. Cree un .pyenvdirectorio si no existe bajo $HOMEo%USERPROFILE%
    3. Extraiga y mueva archivos a
    • Powershell o Git Bash: $HOME/.pyenv/
    • cmd.exe: %USERPROFILE%\.pyenv\
    1. Asegúrese de ver la bincarpeta debajo%USERPROFILE%\.pyenv\pyenv-win
  • Con Git
    • Powershell o Git Bash: git clone https://github.com/pyenv-win/pyenv-win.git "$HOME/.pyenv"
    • cmd.exe: git clone https://github.com/pyenv-win/pyenv-win.git "%USERPROFILE%\.pyenv"
  • Con chocolate
    • choco install pyenv-win (esto también instala todas las variables de entorno)

 

Terminar la instalación

NOTA: Si está ejecutando Windows 10 1905 o más reciente, debe deshabilitar el iniciador de Python incorporado a través deshabilitado los alias de «Instalador de aplicaciones» para Python,para ello  invoquea la configuración predeterminada de Windows 10 en la pantalla de su PC usando las teclas de acceso directo de Windows + I.

La imagen tiene un atributo ALT vacío; su nombre de archivo es alias.jpg

Haga clic en Aplicaciones . Se abrirán las opciones adicionales para las aplicaciones en una nueva página y puede desactivar ahi los alias para Python.

 

Si lo instaló con Chocolatey, puede pasar al paso 3.

  1. Agregue PYENV y PYENV_HOME a sus variables de entorno
    1. Utilizando PowerShell o Windows 8 / ejecución de Terminal superior[System.Environment]::SetEnvironmentVariable('PYENV',$env:USERPROFILE + "\.pyenv\pyenv-win\","User") [System.Environment]::SetEnvironmentVariable('PYENV_HOME',$env:USERPROFILE + "\.pyenv\pyenv-win\","User") Nota: PYENV_HOME es compatible con pipenv
  2. Ahora agregue las siguientes rutas a su variable USER PATH para acceder al comando pyenv. Ejecute lo siguiente en PowerShell o Windows 8 / Terminal superior: [System.Environment]::SetEnvironmentVariable('path', $HOME + "\.pyenv\pyenv-win\bin;" + $HOME + "\.pyenv\pyenv-win\shims;" + $env:Path,"User")
  3. Cierre y vuelva a abrir su aplicación de terminal y ejecute pyenv --version
    1. Si el valor de retorno es la versión instalada de pyenv, continúe con el Paso 4
    2. Si recibe un error de comando no encontrado, asegúrese de que las variables de entorno estén configuradas correctamente a través de la GUI: Esta PC → Propiedades → Configuración avanzada del sistema → Avanzado → Variables de entorno … → RUTA
    3. Si recibe un error de comando no encontrado y está utilizando Visual Studio Code u otro IDE con un terminal integrado, reinícielo e intente nuevamente
  4. Ahora ejecute el pyenv rehashdirectorio de inicio
    • Si recibe un error, repita los pasos nuevamente. .
  5. Ejecutar pyenvpara ver la lista de comandos que admite.

La instalación está terminada.

 

Soporte  de 32 bits

  • Con Git
    • cambiar directorio a %USERPROFILE%\.pyenvviacd
    • ejecutar el siguiente comando git checkout -b 32bit-train origin/32bit-train
    • ahora ejecuta pyenv --versionnecesitas ver 2.32.x
  • Con pepita
    • Powershell o Git Bash: pip install pyenv-win==2.32.x --target $HOME\.pyenv
    • cmd.exe: pip install pyenv-win==2.32.x --target %USERPROFILE%\.pyenv
  • Con archivo zip
    1. Enlace de descarga: pyenv-win
    2. Cree un .pyenvdirectorio si no existe bajo $HOMEo%USERPROFILE%
    3. Extraiga y mueva archivos a
    • Powershell o Git Bash: $HOME/.pyenv/
    • cmd.exe: %USERPROFILE%\.pyenv\
    1. Asegúrese de ver la bincarpeta debajo%USERPROFILE%\.pyenv\pyenv-win

 

 

Uso

  • Actualice la lista de versiones de Python pyenv updatedetectables usando: comando para pyenv-win 2.64.xy 2.32.xversiones

PS C:\Users\carlo> pyenv update
:: [Info] :: Mirror: https://www.python.org/ftp/python
:: [Info] :: Scanned 141 pages and found 479 installers.
PS C:\Users\carlo>

  • Para ver una lista de las versiones de Python compatibles con pyenv windows: pyenv install -l
  • Para instalar una versión de Python: pyenv install 3.8.5

PS C:\Users\carlo> pyenv install 3.8.5
:: [Info] :: Mirror: https://www.python.org/ftp/python
:: [Downloading] :: 3.8.5 …
:: [Downloading] :: From https://www.python.org/ftp/python/3.8.5/python-3.8.5-amd64-webinstall.exe
:: [Downloading] :: To C:\Users\carlo.pyenv\pyenv-win\install_cache\python-3.8.5-amd64-webinstall.exe
:: [Installing] :: 3.8.5 …
:: [Info] :: completed! 3.8.5
PS C:\Users\carlo>

  • Nota: Es posible que aparezca un asistente de instalación para algunas instalaciones no silenciosas. Deberá hacer clic en el asistente durante la instalación. No es necesario cambiar ninguna opción en él. o puede usar -q para bastante instalación
    • También puede instalar varias versiones en un solo comando: pyenv install 2.4.3 3.6.8
  • Para configurar una versión de Python como la versión global: pyenv global 3.8.5
    • Esta es la versión de Python que se usará de forma predeterminada si no se establece una versión local (ver más abajo).
    • Nota: Primero se debe instalar la versión.
  • Para establecer una versión Python como la versión local: pyenv local 3.8.5.
    • La versión proporcionada se utilizará siempre que pythonse llame desde esta carpeta. Esto es diferente a un entorno virtual, que debe activarse explícitamente.
    • Nota: Primero se debe instalar la versión.
  • Después de (des) instalar cualquier biblioteca usando pip o modificar los archivos en la carpeta de una versión, debe ejecutar pyenv rehashpara actualizar pyenv con nuevos shims para Python y los ejecutables de las bibliotecas.
    • Nota: Esto debe ejecutarse fuera de la .pyenvcarpeta.
  • Para desinstalar una versión de Python: pyenv uninstall 3.8.5
  • Para ver qué Python está usando y su ruta: pyenv version

PS C:\Users\carlo> pyenv version
3.8.5 (set by C:\Users\carlo.python-version)
PS C:\Users\carlo>

  • Para ver todas las versiones de Python instaladas en este sistema: pyenv versions

PS C:\Users\carlo> pyenv versions

  • 3.8.5 (set by C:\Users\carlo.python-version)
    PS C:\Users\carlo>

 

Cómo recibir actualizaciones

  • Si se instala a través de pip
    • Agregue la ruta instalada de pyenv-win al easy_install.ptharchivo que se encuentra en site-package. Ahora pyenv-win es reconocido por pip
    • Recibe actualizaciones a través de pip pip install --upgrade pyenv-win
  • Si se instala a través de Git
    • Vaya a %USERPROFILE%\.pyenv\pyenv-win(que es su ruta instalada) y ejecutegit pull
  • Si se instala a través de zip
    • Descarga el zip más reciente y extráelo
    • Ir a %USERPROFILE%\.pyenv\pyenv-winy reemplazar las carpetas libexecy bincon los nuevos que acaba de descargar

 

Preguntas más frecuentes

  • Pregunta: ¿pyenv para windows es compatible con python2?
    • Respuesta: Sí, admitimos python2 desde la versión 2.4+ hasta que python.org lo elimine oficialmente.
    • Las versiones inferiores a 2.4 utilizan instaladores Wise obsoletos y tienen problemas para instalar varias versiones de parches, a diferencia de Windows MSI y los nuevos instaladores Python3 que admiten instalaciones de «extracción».
  • Pregunta: ¿pyenv para windows es compatible con python3?
    • Respuesta: Sí, admitimos python3 desde la versión 3.0. Lo apoyamos desde 3.0 hasta que python.org lo elimine oficialmente.
  • Pregunta: Tengo el problema batch file cannot be found.al instalar Python, ¿qué debo hacer?
    • Respuesta: puede ignorarlo. Está llamando al pyenv rehashcomando antes de crear el archivo bat en algunos dispositivos.
  • Pregunta: El sistema se atasca al desinstalar la versión de Python, ¿qué hacer?
    • Respuesta: Navegue a la ubicación donde instaló pyenv, abra su carpeta de ‘versiones’ (generalmente %USERPROFILE%\.pyenv\pyenv-win\versions) y elimine la carpeta de la versión que desea eliminar.
  • Pregunta: Instalé pyenv-win usando pip. ¿Cómo puedo desinstalarlo?
  • Pregunta: pyenv-win no se reconoce, pero he configurado ENV PATH?
    • Respuesta: De acuerdo con Windows, al agregar la ruta en las variables Usuario o Sistema, para la variable Usuario debe cerrar la sesión y volver a iniciar sesión para reflejar los cambios. Para la variable del sistema, no es necesario.

 

 

Fuente https://github.com/pyenv-win/pyenv-win

Instalación de python en Linux


Muchos proyectos que manejan elementos multimedia están escritos actualmente en Python como por ejemplo el proyecto PRMC, que es una colección de módulos de Python escritos en Python [ 3 ] ), por lo lo tanto, necesitará un intérprete y saber cómo desarrollar / ejecutar programas Python ( módulos y paquetes ). .

La mayoría de los sistemas operativos actuales basados ​​en Unix (Linux, FreeBSD y OSX) utilizan Python para ejecutar algunas de sus «tareas diarias», lo que significa que ya hay disponible un intérprete de Python. Sin embargo, normalmente es mejor utilizar nuestro propio intérprete porque:

  1. Podemos elegir la versión de Python y los paquetes.
  2. Podemos optimizar la compilación del intérprete en función de nuestras necesidades (por ejemplo, incluyendo soporte Tk o no).
  3. Por defecto, todos los paquetes de Python se instalarán en un repositorio diferente de los paquetes del sistema, lo que facilita el aislamiento de Python del sistema / usuario y la eliminación del intérprete.

En Windows, debe instalar Python, sí o sí, desde el sitio web oficial . Sin embargo, tenga en cuenta que esta «guía» solo contempla la instalación de Python en máquinas con sistema operativo basado en Unix.

Instalación de Python.

         sudo apt-get install -y build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev xz-utils tk-dev libffi-dev liblzma-dev python-openssl git
         cd
         curl https://pyenv.run | bash
         cat << EOF >> ~/.bashrh
         export PATH="$HOME/.pyenv/bin:$PATH"
         eval "$(pyenv init -)"
         EOF
         source ~/.bashrc
         pyenv install -v 3.8.5
         pyenv virtualenv 3.8.5 tm

 

  • Recuerde que necesitará activarlo cuando quiera trabajar en este proyecto:   
        pyenv activate tm

 Es una buena idea agregar esto al archivo ~ / .bashrc .

  • Instale un IDE para programar con Python. Recomiendo a Thonny si no está acostumbrado a ningún otro.
     pip instalar thonny  

Recursos de programación Python.

Si ni domina Python para seguir este curso puede  seguir algún tutorial de programación de Python, como The Python Tutorial 1 ] sobre todo si se da cuenta de que el lenguaje es un revés para ti.

Si necesita comenzar con Python desde cero, una introducción a Python como este taller de YAPT 2 ] también podría ser útil. 

También puede consultar también el tutorial de Python de ZetCode .

Aquí otros enlaces interesantes sobre Python:

[1]   El tutorial de Python .

[2]   YAPT .

[3]   Sitio web de Python .