Ejercicio obligatorio 2

Fecha de entrega: Lunes 7 de octubre

Nota

Nunca es buena idea empezar por un ejercicio integrador antes de tener practicados los temas que el trabajo integra.

Se sugiere antes de desarrollar este trabajo resolver, al menos, los siguientes ejercicios de la guía de Memoria dinámica:

  • Ejercicio 12 (combinar vectores).

  • Ejercicio 14.a (matriz identidad).

  • Ejercicio 16 con el 1.b y 1.c de la guía de estructuras (dirección y persona).

Introducción

Queremos modelar una matriz, para ello definimos la estructura:

typedef struct {
    float **m;
    size_t filas, columnas;
} matriz_t;

que permite almacenar en memoria una matriz según el siguiente esquema:

../_images/20242_ej2.png

Trabajo

Creación y destrucción

Se pide implementar la función auxiliar matriz_t *_matriz_crear(size_t n, size_t m); que genere la memoria necesaria para almacenar una matriz de n x m valores. Los valores no deben ser inicializados. La función debe devolver la matriz creada o NULL en caso de falla.

Se pide implementar la función void matriz_destruir(matriz_t *matriz); que libere la memoria asociada a la matriz previamente creada.

Acceso a los datos

Se pide implementar una función size_t matriz_filas(const matriz_t *matriz); que devuelve el número de filas de la matriz.

Se pide implementar una función size_t matriz_columnas(const matriz_t *matriz); que devuelve el número de columnas de la matriz.

Se pide implementar una función void matriz_dimensiones(const matriz_t *matriz, size_t *filas, size_t *columnas); que devuelva la cantidad de filas y columnas de la matriz.

Se pide implementar una función float matriz_obtener(const matriz_t *matriz, size_t fila, size_t columna); que devuelva el valor almacenado en la posición fila, columna de la matriz.

Generación de matrices

Implementar una función matriz_t *matriz_leer(size_t n, size_t m); que cree una matriz de n x m leyendo sus valores desde stdin. Los valores se leerán como n líneas de entrada donde cada línea tendrá m valores separados por caracteres blancos. La función debe retornar la matriz creada o NULL en caso de falla.

Nota

Sugerencia: valerse de la función strtod() para poder realizar la lectura de múltiples valores por línea.

Otra sugerencia: Utilizar un buffer de lectura de tamaño razonable, una sola línea puede ocupar muchos caracteres.

Extensión de matrices

Implementar una función bool matriz_extender(matriz_t *matriz); que extienda en una columna a la matriz. Los elementos de la nueva columna deben ser inicializados a 1. La función devuelve true si puede realizar la operación.

Nota

Notar que esto cambia las dimensiones de la matriz, la misma debe reflejar eso, por ejemplo si se llamara a matriz_columnas().

Algunas operaciones sueltas del EJ1

Implementar una función matriz_t *matriz_multiplicar(const matriz_t *a, const matriz_t *b); que devuelva \(A \times B\). En caso de falla o de no ser compatibles las matrices recibidas debe devolver NULL.

Implementar una función void matriz_aplicar(matriz_t *matriz); que le aplique a cada uno de los elementos de la matriz la función sigmoidea.

Aplicación

Implementar un programa que lea de stdin tres números, de a uno por línea, los cuales representan la cantidad de valores de entrada \(n_i\), la cantidad de neuronas en la capa H \(n_h\) y la cantidad de valores de salida \(n_o\). Luego se leerán de stdin la matriz \(W\) de tamaño \(n_i + 1 \times n_h\) y la matriz \(V\) de tamaño \(n_h + 1 \times n_o\). A partir de ese momento el programa leerá líneas que contengan \(n_i\) valores cada una e imprimirá las \(n_o\) respuestas correspondientes a aplicar la red neuronal definida por \(W\) y \(V\).

Notar que el programa es casi idéntico a la aplicación 1 del EJ1 con la diferencia de que ahora se lee la cantidad de neuronas (que en el EJ1 eran 2, 2 y 2). Eso y el formato de lectura de las matrices que ahora está fijado.

Se proveen matrices de prueba.

Dos entradas, 2 neuronas H y tres salidas. Implementa or, and y xor lógicos:

2
2
3
6.56501722  7.78683090
6.56502056  7.78690529
-9.92721844 -3.81731486
3.38883948  12.47597790     -12.61389351
12.05436039 2.26307559      12.19962597
-5.83038235 -8.66410637     -5.81197023

Una entrada, 11 neuronas H y 4 salidas. Convierte los números del 1 al 10 en binario:

1
11
4
-5.30903816 7.05562210      6.21564150      6.97264194      6.91205835      -5.19426727     -4.47014189     4.65599775      3.93545413      3.58155107      3.64056873
29.06749725 -10.61930656    -21.03014374    -3.50132489     -3.52002954     38.90851593     11.63465214     -20.50225067    -25.48287964    -30.60029602    -35.00047302
-1.09161687 -2.17958021     -13.26340580    14.44618607
0.36545485  -2.89235520     12.05211067     -14.59816265
0.81871516  10.85333633     -14.98445129    -17.38402557
0.40498984  -3.21998858     -0.36098400     6.07753468
-0.01866573 -2.99007010     0.43762803      5.94211388
-12.18182278        11.69031906     17.91842461     15.41529465
-2.19953322 -9.84567356     -2.13680530     -18.18881798
1.58187580  1.64355898      0.33712316      14.09165287
3.21212530  -3.41997361     0.55070335      15.25783634
4.10961294  -5.31011677     5.06973982      17.36598206
5.69332218  -3.86532736     13.95511246     -18.34716797
-0.41404390 -5.60833073     -8.81380272     -17.24401283

Una entrada, 10 neuronas H y 1 salida. \(\sin(x)\) para \(0 \leq x \leq \pi\):

1
10
1
2.71614861  -1.40574467     4.68761539      5.74467134      10.74507427     0.30282176      -2.26366472     0.33558899      -5.11997700     0.41550291
-5.12603045 0.85895282      -7.40913248     -7.95125866     0.89139485      2.05528688      1.13089824      1.85756946      17.09574318     1.31795645
-5.69753170
-4.20246935
-7.05508184
8.87130547
8.72417450
-4.49026251
-4.55723953
-4.41047955
12.11481094
-4.32116795
-4.75244141

Tres entradas, 10 neuronas H y 8 salidas. Demultiplexa tres bits binarios:

3
10
8
5.51550627  -3.51880479     -4.17497206     4.22070169      6.12060881      -1.18278348     -4.63216162     4.55842161      -1.07078624     -1.24543619
-4.79343891 4.00000000      -3.64086080     3.27972960      5.00428200      5.67627478      4.38318682      -3.69111633     -1.35822213     3.25151992
-5.03890371 1.92246139      -2.55883217     3.81176400      -5.15021420     -0.72366685     -5.00612831     -0.50028527     7.01664734      -2.76531053
2.02090907  0.64594269      5.47741365      -4.95161104     -2.81993341     -1.70793676     2.46618867      1.73334968      -1.66325605     1.86461556
-5.42306423 4.13318586      2.88013625      3.53634501      -4.43884277     -4.97382593     -5.77240849     3.11494040
0.54221803  -0.26094633     -3.92973781     -6.64205408     -0.46005663     0.86798143      1.78058851      -1.86206388
-5.82063723 -8.24880314     -4.02215099     0.11427451      -2.83595467     0.63945079      4.46044064      1.58927715
0.85516322  2.81080627      4.14660931      -2.65783143     2.74227810      -4.77726746     -4.24465466     -6.74732018
6.25260544  0.90993214      -1.32604277     5.49917841      -6.72818708     2.63580370      -4.98032236     -7.92228127
2.16425943  2.35775399      -4.18162060     -4.09303427     3.46843743      2.42040920      -3.55587602     -2.12844801
-6.72060490 3.40531945      -3.65683985     -2.72658372     1.04992628      3.16677976      -3.50867558     5.27786732
-0.16477565 0.02259529      0.95917386      1.60429752      -7.18169880     -4.97218418     -0.77544403     0.41598016
1.83146882  -7.00004196     4.51100159      -6.02566099     3.18566704      -6.72487783     3.72872019      -3.59594154
-0.64314932 -0.43060106     -5.62132931     -1.49000144     0.72938317      0.17222236      -3.15672135     0.59198356
-3.54587221 -5.38373423     -3.05469370     -1.50358605     -2.16013718     -1.49478805     -0.22054017     -1.60520017

Nota

NO se pide en este enunciado. Pero si te interesara entrenar tus propias redes, las ecuaciones para el backpropagation son las mismas que las del EJ1. Tené en cuenta que cambian las dimensiones nomás:

  • Los índices \(w_{ij}\) tienen \(i = [0 \mathrel{{.}\,{.}} n_i + 1)\), \(j = [0 \mathrel{{.}\,{.}} n_h)\).

  • Los índices \(v_{ij}\) tienen \(i = [0 \mathrel{{.}\,{.}} n_h + 1)\), \(j = [0 \mathrel{{.}\,{.}} n_o)\).

  • En el cálculo de \(\frac{\partial L}{\partial w_{ij}}\) la sumatoria tiene \(k = [0 \mathrel{{.}\,{.}} n_o)\).

Se insiste en que no es parte de este trabajo.

Entrega

Deberá entregarse el código fuente del programa desarrollado.

El programa debe:

  1. Compilar correctamente con los flags:

    -Wall -Werror -std=c99 -pedantic
    
  2. pasar Valgrind correctamente,

La entrega se realiza a través del sistema de entregas.

El ejercicio es de entrega individual.