3  Datasets de referencia

3.1 Mirar los datos antes de entrenar

Un dataset puede llegar como una tabla, un arreglo de píxeles, una colección de archivos o un grafo. Antes de elegir una arquitectura, necesitas saber qué representa una observación, dónde está su respuesta y cómo se conserva la correspondencia entre ambas.

Esta guía muestra trece datasets con datos reales. Verás encabezados, primeras filas, galerías de imágenes, un fragmento de texto, señales de audio y relaciones entre nodos. Cada vista se acompaña de una lectura de sus dimensiones, etiquetas y estadísticas. Los ejemplos no entrenan modelos: las cifras describen los datos y ayudan a preparar una evaluación posterior.

Las vistas de imágenes de MNIST, Fashion-MNIST y CIFAR-10 usan sus particiones de prueba únicamente para mostrar el formato del benchmark; no seleccionamos modelos, transformaciones ni hiperparámetros a partir de ellas. Cuando desarrolles un experimento, aparta la prueba y realiza la exploración que guía decisiones sobre entrenamiento.

3.2 Qué vas a poder reconocer

  • Qué contienen las columnas y cuáles son entradas, etiquetas o metadatos.
  • La diferencia entre un código de clase y un valor continuo.
  • Cómo se ve una imagen y cómo se representa en una matriz o tensor.
  • Por qué el texto y el audio tienen longitudes variables.
  • Cómo se representan el tiempo, los nodos y las aristas.
  • Qué revelan el balance de clases, los faltantes, las escalas y los rangos.
  • Qué comprobar sobre origen, particiones y permisos de uso antes de reutilizar un dataset.

El artículo de tensores sirve para interpretar las formas. Los generadores make_moons, make_circles y otros datos sintéticos se desarrollarán en Generación de datos.

3.3 Preparación y descargas

Usamos NumPy, pandas, matplotlib, scikit-learn, SciPy, Pillow, NetworkX y statsmodels. Las tablas pequeñas, Digits, el libro solar y el grafo vienen con las bibliotecas. Los demás ejemplos reutilizan la caché local de Keras cuando existe; de lo contrario descargan los archivos del proveedor y los conservan para futuras ejecuciones. No necesitas instalar TensorFlow ni entrenar una red para verlos.

MNIST, Fashion-MNIST e IMDB requieren archivos de unos pocos a algunas decenas de megabytes. CIFAR-10 y Flowers requieren aproximadamente 170 y 229 MB comprimidos, respectivamente, si no están en caché. Para audio descargamos solo tres grabaciones. Las figuras quedan embebidas en el notebook: puedes leer sus salidas guardadas sin volver a descargar los datasets.

from pathlib import Path
import os, json, gzip, struct, pickle, tarfile, urllib.request
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from PIL import Image
from scipy.io import wavfile
from scipy import signal
from sklearn.datasets import load_iris, load_wine, load_breast_cancer, load_diabetes, load_digits
import sklearn
import networkx as nx
import statsmodels.api as sm
from IPython.display import display, HTML, Audio

SEMILLA = 2026
CACHE = Path(os.environ.get('BIT_DATASETS_CACHE', Path.home()/'.cache'/'bit-datasets'))
CACHE.mkdir(parents=True, exist_ok=True)
KERAS = Path.home()/'.keras'/'datasets'
plt.rcParams.update({'font.size': 11, 'axes.spines.top': False, 'axes.spines.right': False})
pd.set_option('display.max_columns', 35)
pd.set_option('display.precision', 3)
estadisticas = {}
catalogo = []
print(f'scikit-learn {sklearn.__version__} | NumPy {np.__version__} | pandas {pd.__version__}')
print('Entorno preparado. Las tablas y las figuras muestran datos reales.')
scikit-learn 1.8.0 | NumPy 2.3.5 | pandas 2.3.3
Entorno preparado. Las tablas y las figuras muestran datos reales.

3.3.1 Utilidades de lectura y dibujo

La celda plegada reúne la descarga con caché, la lectura de archivos y el dibujo de galerías. En el resto del artículo las llamadas muestran qué datos recibe cada figura. Las tablas conservan los encabezados del proveedor y permiten desplazamiento horizontal cuando hay muchas columnas.

Utilidades para cargar archivos y mostrar tablas y galerías
def obtener(nombre, url, alternativas=()):
    for ruta in [CACHE/nombre, *alternativas]:
        if ruta.is_file():
            return ruta
    destino = CACHE/nombre
    destino.parent.mkdir(parents=True, exist_ok=True)
    temporal = destino.with_suffix(destino.suffix+'.part')
    request = urllib.request.Request(url, headers={'User-Agent': 'BIT-datasets-editorial'})
    with urllib.request.urlopen(request, timeout=120) as response, temporal.open('wb') as salida:
        while bloque := response.read(1024*1024):
            salida.write(bloque)
    temporal.replace(destino)
    return destino

def tabla(df, titulo):
    display(HTML('<p><strong>'+titulo+'</strong></p><div style="overflow-x:auto">'+
                 df.to_html(index=False, escape=True, float_format=lambda x:f'{x:.3f}')+'</div>'))

def galeria(imagenes, etiquetas, titulo, columnas=5, gris=False):
    filas = int(np.ceil(len(imagenes)/columnas))
    fig, axes = plt.subplots(filas, columnas, figsize=(2.2*columnas, 2.25*filas), squeeze=False)
    for i, ax in enumerate(axes.flat):
        ax.axis('off')
        if i < len(imagenes):
            ax.imshow(imagenes[i], cmap='gray' if gris else None, interpolation='nearest')
            ax.set_title(str(etiquetas[i]), fontsize=10)
    fig.suptitle(titulo, fontsize=13)
    plt.tight_layout(); plt.show()

def resumen_tabular(nombre, conjunto, tarea):
    df = conjunto.data.copy()
    df['target'] = conjunto.target
    tabla(df.head(5), nombre+': primeras cinco filas')
    tabla(pd.DataFrame({'columna': df.columns, 'dtype': [str(t) for t in df.dtypes],
                        'faltantes': df.isna().sum().to_numpy()}), nombre+': encabezados y tipos')
    print(f'{nombre}: X={conjunto.data.shape}, y={conjunto.target.shape}, faltantes={df.isna().sum().sum()}')
    catalogo.append({'dataset':nombre,'modalidad':'Tabla','observaciones_vistas':len(df),
                     'entrada_por_observación':f'{conjunto.data.shape[1]} variables','tarea':tarea})
    return df

def idx_gzip(ruta, imagenes):
    with gzip.open(ruta, 'rb') as f:
        contenido=f.read()
    if imagenes:
        magic,n,h,w=struct.unpack('>IIII',contenido[:16])
        assert magic==2051
        return np.frombuffer(contenido, dtype=np.uint8, offset=16).reshape(n,h,w)
    magic,n=struct.unpack('>II',contenido[:8]);assert magic==2049
    return np.frombuffer(contenido,dtype=np.uint8,offset=8).reshape(n)

def archivo_tar(ruta, nombre):
    with tarfile.open(ruta, 'r:gz') as archivo:
        return archivo.extractfile(nombre).read()

print('Utilidades listas: tablas con encabezados, galerías y lectura de archivos.')
Utilidades listas: tablas con encabezados, galerías y lectura de archivos.

3.4 Parte A — Tablas: una fila, una observación

3.4.1 Iris: cuatro medidas y una especie

Iris contiene medidas de sépalos y pétalos en centímetros. Cada fila es una flor; target codifica la especie. La descripción de las clases se conserva en target_names. Empieza mirando los encabezados: petal length (cm) expresa una magnitud y una unidad; target=2 es una categoría, no una medida mayor que target=1.

iris = load_iris(as_frame=True)
df_iris = resumen_tabular('Iris', iris, 'Clasificación multiclase')
conteo_iris = iris.target.value_counts().sort_index()
tabla(pd.DataFrame({'código': range(3), 'especie': iris.target_names, 'cantidad': conteo_iris.to_numpy()}), 'Iris: diccionario de etiquetas')
fig, axes = plt.subplots(1,2,figsize=(10,4))
for k,nombre in enumerate(iris.target_names):
    filas=iris.target==k
    axes[0].scatter(iris.data.loc[filas,'petal length (cm)'],iris.data.loc[filas,'petal width (cm)'],label=nombre,alpha=.7)
axes[0].set(xlabel='Largo del pétalo (cm)',ylabel='Ancho del pétalo (cm)',title='Dos variables, tres etiquetas')
axes[0].legend(fontsize=9)
axes[1].bar(iris.target_names,conteo_iris.to_numpy(),color=['tab:blue','tab:orange','tab:green'])
axes[1].set(ylabel='Número de flores',title='Balance de clases')
plt.tight_layout();plt.show()
estadisticas['iris_n']=len(df_iris)

Iris: primeras cinco filas

sepal length (cm) sepal width (cm) petal length (cm) petal width (cm) target
5.100 3.500 1.400 0.200 0
4.900 3.000 1.400 0.200 0
4.700 3.200 1.300 0.200 0
4.600 3.100 1.500 0.200 0
5.000 3.600 1.400 0.200 0

Iris: encabezados y tipos

columna dtype faltantes
sepal length (cm) float64 0
sepal width (cm) float64 0
petal length (cm) float64 0
petal width (cm) float64 0
target int64 0
Iris: X=(150, 4), y=(150,), faltantes=0

Iris: diccionario de etiquetas

código especie cantidad
0 setosa 50
1 versicolor 50
2 virginica 50

3.4.2 Cómo leer Iris

La tabla tiene 150 observaciones, cuatro variables de entrada y una columna de respuesta añadida para mostrarla. Las tres clases tienen 50 flores cada una. Las primeras cinco filas pertenecen a la misma clase: head() muestra el comienzo del archivo, no una muestra representativa.

En la proyección de pétalos, setosa forma un grupo separado y las otras especies muestran una separación menos clara. Eso describe estas dos variables y estas observaciones; todavía no es una evaluación predictiva. El gráfico de barras confirma equilibrio de clases, algo que la primera pantalla de filas no permite reconocer.

3.4.3 Wine: más columnas y escalas diferentes

Wine contiene mediciones químicas de vinos con tres códigos de clase. Observa los nombres originales: alcohol, malic_acid y proline no son variables intercambiables. Dibujaremos sus distribuciones por separado para que una escala grande no oculte las demás.

wine=load_wine(as_frame=True)
df_wine=resumen_tabular('Wine',wine,'Clasificación multiclase')
cuentas_wine=wine.target.value_counts().sort_index()
tabla(pd.DataFrame({'código':range(3),'etiqueta':wine.target_names,'cantidad':cuentas_wine.to_numpy()}),'Wine: clases')
fig,axes=plt.subplots(1,3,figsize=(11,3.5))
for ax,columna in zip(axes,['alcohol','malic_acid','proline']):
    ax.hist(wine.data[columna],bins=16,color='tab:purple',alpha=.75)
    ax.set(xlabel=columna,ylabel='Observaciones',title=columna)
plt.tight_layout();plt.show()

Wine: primeras cinco filas

alcohol malic_acid ash alcalinity_of_ash magnesium total_phenols flavanoids nonflavanoid_phenols proanthocyanins color_intensity hue od280/od315_of_diluted_wines proline target
14.230 1.710 2.430 15.600 127.000 2.800 3.060 0.280 2.290 5.640 1.040 3.920 1065.000 0
13.200 1.780 2.140 11.200 100.000 2.650 2.760 0.260 1.280 4.380 1.050 3.400 1050.000 0
13.160 2.360 2.670 18.600 101.000 2.800 3.240 0.300 2.810 5.680 1.030 3.170 1185.000 0
14.370 1.950 2.500 16.800 113.000 3.850 3.490 0.240 2.180 7.800 0.860 3.450 1480.000 0
13.240 2.590 2.870 21.000 118.000 2.800 2.690 0.390 1.820 4.320 1.040 2.930 735.000 0

Wine: encabezados y tipos

columna dtype faltantes
alcohol float64 0
malic_acid float64 0
ash float64 0
alcalinity_of_ash float64 0
magnesium float64 0
total_phenols float64 0
flavanoids float64 0
nonflavanoid_phenols float64 0
proanthocyanins float64 0
color_intensity float64 0
hue float64 0
od280/od315_of_diluted_wines float64 0
proline float64 0
target int64 0
Wine: X=(178, 13), y=(178,), faltantes=0

Wine: clases

código etiqueta cantidad
0 class_0 59
1 class_1 71
2 class_2 48

3.4.4 Cómo leer Wine

Hay 178 observaciones y 13 variables; las clases reúnen 59, 71 y 48 observaciones. La cantidad de columnas de la tabla mostrada incluye también target, que no debe entrar como predictor.

Los histogramas conservan sus escalas originales. Una columna con números mayores no es automáticamente más importante. Algunas operaciones, como distancias o penalizaciones sobre coeficientes, dependen de la escala. Si estandarizas para entrenar, estima la transformación con el entrenamiento y aplícala sin reajustarla a las otras particiones.

3.4.5 Breast Cancer: una etiqueta necesita su diccionario

Este dataset contiene características calculadas a partir de imágenes de muestras celulares. Las columnas son mediciones resumidas, no las imágenes originales. Sus nombres distinguen medidas mean, error y worst.

Aquí es especialmente importante leer target_names: en esta versión 0 corresponde a malignant y 1 a benign. La guía describe el formato de un benchmark; no ofrece una herramienta de diagnóstico ni una interpretación clínica.

cancer=load_breast_cancer(as_frame=True)
df_cancer=resumen_tabular('Breast Cancer',cancer,'Clasificación binaria')
cuentas_cancer=cancer.target.value_counts().sort_index()
tabla(pd.DataFrame({'código':range(2),'etiqueta':cancer.target_names,'cantidad':cuentas_cancer.to_numpy()}),'Breast Cancer: diccionario de etiquetas')
fig,axes=plt.subplots(1,2,figsize=(10,4))
axes[0].bar(cancer.target_names,cuentas_cancer.to_numpy(),color=['tab:red','tab:blue'])
axes[0].set(ylabel='Observaciones',title='Frecuencias de las etiquetas')
bordes_radius=np.histogram_bin_edges(cancer.data['mean radius'],bins=18)
for k,nombre in enumerate(cancer.target_names):
    axes[1].hist(cancer.data.loc[cancer.target==k,'mean radius'],bins=bordes_radius,alpha=.5,label=nombre)
axes[1].set(xlabel='mean radius',ylabel='Observaciones',title='Una variable por etiqueta')
axes[1].legend();plt.tight_layout();plt.show()
mayoritaria=cuentas_cancer.max()/len(cancer.target)
print(f'Proporción de la clase mayoritaria: {100*mayoritaria:.2f}%')
estadisticas['mayoritaria']=mayoritaria

Breast Cancer: primeras cinco filas

mean radius mean texture mean perimeter mean area mean smoothness mean compactness mean concavity mean concave points mean symmetry mean fractal dimension radius error texture error perimeter error area error smoothness error compactness error concavity error concave points error symmetry error fractal dimension error worst radius worst texture worst perimeter worst area worst smoothness worst compactness worst concavity worst concave points worst symmetry worst fractal dimension target
17.990 10.380 122.800 1001.000 0.118 0.278 0.300 0.147 0.242 0.079 1.095 0.905 8.589 153.400 0.006 0.049 0.054 0.016 0.030 0.006 25.380 17.330 184.600 2019.000 0.162 0.666 0.712 0.265 0.460 0.119 0
20.570 17.770 132.900 1326.000 0.085 0.079 0.087 0.070 0.181 0.057 0.543 0.734 3.398 74.080 0.005 0.013 0.019 0.013 0.014 0.004 24.990 23.410 158.800 1956.000 0.124 0.187 0.242 0.186 0.275 0.089 0
19.690 21.250 130.000 1203.000 0.110 0.160 0.197 0.128 0.207 0.060 0.746 0.787 4.585 94.030 0.006 0.040 0.038 0.021 0.022 0.005 23.570 25.530 152.500 1709.000 0.144 0.424 0.450 0.243 0.361 0.088 0
11.420 20.380 77.580 386.100 0.142 0.284 0.241 0.105 0.260 0.097 0.496 1.156 3.445 27.230 0.009 0.075 0.057 0.019 0.060 0.009 14.910 26.500 98.870 567.700 0.210 0.866 0.687 0.258 0.664 0.173 0
20.290 14.340 135.100 1297.000 0.100 0.133 0.198 0.104 0.181 0.059 0.757 0.781 5.438 94.440 0.011 0.025 0.057 0.019 0.018 0.005 22.540 16.670 152.200 1575.000 0.137 0.205 0.400 0.163 0.236 0.077 0

Breast Cancer: encabezados y tipos

columna dtype faltantes
mean radius float64 0
mean texture float64 0
mean perimeter float64 0
mean area float64 0
mean smoothness float64 0
mean compactness float64 0
mean concavity float64 0
mean concave points float64 0
mean symmetry float64 0
mean fractal dimension float64 0
radius error float64 0
texture error float64 0
perimeter error float64 0
area error float64 0
smoothness error float64 0
compactness error float64 0
concavity error float64 0
concave points error float64 0
symmetry error float64 0
fractal dimension error float64 0
worst radius float64 0
worst texture float64 0
worst perimeter float64 0
worst area float64 0
worst smoothness float64 0
worst compactness float64 0
worst concavity float64 0
worst concave points float64 0
worst symmetry float64 0
worst fractal dimension float64 0
target int64 0
Breast Cancer: X=(569, 30), y=(569,), faltantes=0

Breast Cancer: diccionario de etiquetas

código etiqueta cantidad
0 malignant 212
1 benign 357

Proporción de la clase mayoritaria: 62.74%

3.4.6 Cómo leer las etiquetas y el balance

Las 569 observaciones tienen 30 variables. Los conteos son 212 malignant y 357 benign. La clase mayoritaria representa 62.74 % de la colección. Ese porcentaje es descriptivo: no es una exactitud de prueba obtenida por un modelo.

El histograma muestra diferencias y solapamientos en una variable. No permite asignar diagnósticos a nuevos casos. Para una tarea de clasificación posterior tendrás que distinguir los errores por clase y el significado de su código, además de estudiar las condiciones de evaluación.

3.4.7 Diabetes: la respuesta es una cantidad

Diabetes contiene diez variables de entrada y una medida cuantitativa de progresión un año después. El nombre puede inducir a pensar en una clasificación de presencia o ausencia de diabetes, pero la respuesta de este benchmark es continua.

El cargador aplica por defecto una transformación particular: centra cada columna y la divide por su desviación estándar multiplicada por \(\sqrt n\). Los valores de age de esa versión no están en años. Cargaremos también scaled=False para observar la diferencia de escala sin confundirla con cambios de paciente.

diabetes=load_diabetes(as_frame=True)
df_diabetes=resumen_tabular('Diabetes',diabetes,'Regresión')
diabetes_original=load_diabetes(as_frame=True,scaled=False)
tabla(pd.DataFrame({'age_transformada':diabetes.data['age'].head(),
                    'age_original':diabetes_original.data['age'].head(),
                    'bmi_transformado':diabetes.data['bmi'].head(),
                    'bmi_original':diabetes_original.data['bmi'].head(),
                    'target':diabetes.target.head()}),'Diabetes: mismas filas, dos escalas')
fig,axes=plt.subplots(1,2,figsize=(10,4))
axes[0].hist(diabetes.target,bins=20,color='tab:orange',alpha=.8)
axes[0].set(xlabel='Respuesta cuantitativa',ylabel='Observaciones',title='Distribución de target')
axes[1].scatter(diabetes_original.data['bmi'],diabetes.target,s=16,alpha=.55)
axes[1].set(xlabel='bmi, escala original',ylabel='Respuesta cuantitativa',title='Relación visual con una variable')
plt.tight_layout();plt.show()
std_default=diabetes.data.std(ddof=0).iloc[0]
print(f'Desviación de age transformada: {std_default:.6f}; 1/sqrt(n): {1/np.sqrt(len(diabetes.target)):.6f}')
print(f'Target: mínimo={diabetes.target.min():.1f}, máximo={diabetes.target.max():.1f}')
estadisticas['diabetes_std']=std_default

Diabetes: primeras cinco filas

age sex bmi bp s1 s2 s3 s4 s5 s6 target
0.038 0.051 0.062 0.022 -0.044 -0.035 -0.043 -0.003 0.020 -0.018 151.000
-0.002 -0.045 -0.051 -0.026 -0.008 -0.019 0.074 -0.039 -0.068 -0.092 75.000
0.085 0.051 0.044 -0.006 -0.046 -0.034 -0.032 -0.003 0.003 -0.026 141.000
-0.089 -0.045 -0.012 -0.037 0.012 0.025 -0.036 0.034 0.023 -0.009 206.000
0.005 -0.045 -0.036 0.022 0.004 0.016 0.008 -0.003 -0.032 -0.047 135.000

Diabetes: encabezados y tipos

columna dtype faltantes
age float64 0
sex float64 0
bmi float64 0
bp float64 0
s1 float64 0
s2 float64 0
s3 float64 0
s4 float64 0
s5 float64 0
s6 float64 0
target float64 0
Diabetes: X=(442, 10), y=(442,), faltantes=0

Diabetes: mismas filas, dos escalas

age_transformada age_original bmi_transformado bmi_original target
0.038 59.000 0.062 32.100 151.000
-0.002 48.000 -0.051 21.600 75.000
0.085 72.000 0.044 30.500 141.000
-0.089 24.000 -0.012 25.300 206.000
0.005 50.000 -0.036 23.000 135.000

Desviación de age transformada: 0.047565; 1/sqrt(n): 0.047565
Target: mínimo=25.0, máximo=346.0

3.4.8 Cómo leer Diabetes

Hay 442 observaciones y diez variables. La desviación de age en la versión transformada es 0.047565, próxima a \(1/\sqrt{442}\); no es uno. La transformación se realizó sobre la colección completa antes de la partición que tú puedas crear. Para practicar un pipeline sin información de la prueba, parte de scaled=False y estima tu escalado solo con entrenamiento.

El histograma presenta una cantidad, no dos categorías. La nube de bmi y respuesta muestra una relación con dispersión; no identifica un efecto causal. Además, estos datos ya son una selección numérica preparada: sus columnas no representan toda la información de una historia clínica.

3.5 Parte B — Imágenes: ver el objeto y sus números

3.5.1 Digits: pequeño, local y diferente de MNIST

load_digits() incluye imágenes de dígitos de 8×8 píxeles. images conserva la cuadrícula; data aplana cada imagen en 64 columnas. Son dos vistas de los mismos valores. Seleccionamos una imagen por etiqueta para construir una galería equilibrada, no una muestra aleatoria del dataset.

digits=load_digits()
seleccion=[np.flatnonzero(digits.target==k)[0] for k in range(10)]
galeria(digits.images[seleccion],digits.target[seleccion],'Digits: una imagen por etiqueta',gris=True)
tabla(pd.DataFrame(digits.data[:3,:12],columns=[f'pixel_{j:02}' for j in range(12)]).assign(target=digits.target[:3]),'Digits: primeras 12 de las 64 columnas de píxeles')
print(f'images={digits.images.shape}, data={digits.data.shape}, target={digits.target.shape}')
print(f'Rango de píxeles: {digits.images.min():.0f} a {digits.images.max():.0f}; dtype={digits.images.dtype}')
print('Vista aplanada coincide con la cuadrícula:',np.array_equal(digits.data[0],digits.images[0].reshape(-1)))
catalogo.append({'dataset':'Digits','modalidad':'Imagen','observaciones_vistas':len(digits.target),'entrada_por_observación':'8×8, un canal','tarea':'10 clases'})

Digits: primeras 12 de las 64 columnas de píxeles

pixel_00 pixel_01 pixel_02 pixel_03 pixel_04 pixel_05 pixel_06 pixel_07 pixel_08 pixel_09 pixel_10 pixel_11 target
0.000 0.000 5.000 13.000 9.000 1.000 0.000 0.000 0.000 0.000 13.000 15.000 0
0.000 0.000 0.000 12.000 13.000 5.000 0.000 0.000 0.000 0.000 0.000 11.000 1
0.000 0.000 0.000 4.000 15.000 12.000 0.000 0.000 0.000 0.000 3.000 16.000 2
images=(1797, 8, 8), data=(1797, 64), target=(1797,)
Rango de píxeles: 0 a 16; dtype=float64
Vista aplanada coincide con la cuadrícula: True

3.5.2 Cómo leer la galería y el encabezado

Digits tiene 1,797 imágenes. El rango observado va de 0 a 16, no de cero a 255. images tiene forma (1797,8,8) y data (1797,64). Los encabezados pixel_00, pixel_01 y siguientes se añadieron para mostrar la tabla; indican posiciones, no variables semánticas como distancia o peso.

Una galería muestra el patrón espacial que la tabla dificulta reconocer. Aplanar conserva los valores y su orden, pero una capa densa ya no recibe explícitamente los dos ejes espaciales. No identifiques este dataset como MNIST: tienen resolución, colección y escala de píxeles distintas.

3.5.3 MNIST: la misma tarea, otra representación

Cargamos el archivo distribuido para MNIST y mostramos una imagen por clase de su prueba oficial. El archivo contiene por separado entradas y etiquetas de entrenamiento y prueba. No necesitamos unirlas para construir una galería.

mnist_file=obtener('mnist.npz','https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz',[KERAS/'mnist.npz'])
with np.load(mnist_file,allow_pickle=False) as z:
    mnist=z['x_test'];y_mnist=z['y_test'];forma_mnist_train=z['x_train'].shape
seleccion=[np.flatnonzero(y_mnist==k)[0] for k in range(10)]
galeria(mnist[seleccion],y_mnist[seleccion],'MNIST: una imagen por etiqueta de prueba',gris=True)
print('Entrenamiento:',forma_mnist_train,'| Prueba:',mnist.shape,'| Etiquetas de prueba:',y_mnist.shape)
print('dtype:',mnist.dtype,'| rango observado:',int(mnist.min()),int(mnist.max()))
fig,axes=plt.subplots(1,2,figsize=(10,4))
axes[0].imshow(mnist[seleccion[7]],cmap='gray',interpolation='nearest');axes[0].set_title('MNIST: etiqueta 7');axes[0].axis('off')
recorte=mnist[seleccion[7],10:18,10:18]
im=axes[1].imshow(recorte,cmap='gray',vmin=0,vmax=255,interpolation='nearest')
for i in range(8):
    for j in range(8):axes[1].text(j,i,str(recorte[i,j]),ha='center',va='center',fontsize=8,color='black' if recorte[i,j]>125 else 'white')
axes[1].set_title('Recorte 8×8: intensidades reales');axes[1].set_xlabel('Columnas 10–17');axes[1].set_ylabel('Filas 10–17')
plt.tight_layout();plt.show()
catalogo.append({'dataset':'MNIST','modalidad':'Imagen','observaciones_vistas':len(y_mnist),'entrada_por_observación':'28×28, un canal','tarea':'10 clases; vista de prueba'})

Entrenamiento: (60000, 28, 28) | Prueba: (10000, 28, 28) | Etiquetas de prueba: (10000,)
dtype: uint8 | rango observado: 0 255

3.5.4 Cómo leer los píxeles de MNIST

Las formas guardadas son (60000,28,28) en entrenamiento y (10000,28,28) en prueba. Cada imagen contiene 784 valores uint8, con rango observado 0–255. El número que aparece sobre una imagen es su etiqueta; los del recorte son intensidades de píxel. Una intensidad de siete no representa la clase siete.

Dividir por 255 lleva las intensidades a \([0,1]\), pero no equivale a centrar y estandarizar. Para una CNN de PyTorch, una imagen de un canal suele convertirse a forma (1,28,28) y el lote a (B,1,28,28). Aquí no añadimos esos ejes porque solo estamos inspeccionando los archivos.

3.5.5 Fashion-MNIST: forma igual, objetos diferentes

Fashion-MNIST conserva la resolución y organización de MNIST, pero sus diez etiquetas representan prendas y accesorios. Leeremos los archivos comprimidos de imágenes y etiquetas. El encabezado del formato IDX indica cuántas imágenes hay y sus dimensiones; no es una fila de nombres de columnas como en CSV.

base_fashion='https://raw.githubusercontent.com/zalandoresearch/fashion-mnist/master/data/fashion/'
img_f=obtener('fashion-t10k-images.gz',base_fashion+'t10k-images-idx3-ubyte.gz',[KERAS/'fashion-mnist'/'t10k-images-idx3-ubyte.gz'])
lab_f=obtener('fashion-t10k-labels.gz',base_fashion+'t10k-labels-idx1-ubyte.gz',[KERAS/'fashion-mnist'/'t10k-labels-idx1-ubyte.gz'])
fashion=idx_gzip(img_f,True);y_fashion=idx_gzip(lab_f,False)
nombres_fashion=['T-shirt/top','Trouser','Pullover','Dress','Coat','Sandal','Shirt','Sneaker','Bag','Ankle boot']
seleccion=[np.flatnonzero(y_fashion==k)[0] for k in range(10)]
galeria(fashion[seleccion],[f'{k}: {nombres_fashion[k]}' for k in range(10)],'Fashion-MNIST: diez categorías de prueba',gris=True)
conteos_fashion=np.bincount(y_fashion,minlength=10)
tabla(pd.DataFrame({'código':range(10),'categoría':nombres_fashion,'cantidad_prueba':conteos_fashion}),'Fashion-MNIST: diccionario y frecuencias')
print('Imágenes:',fashion.shape,'| Etiquetas:',y_fashion.shape,'| dtype:',fashion.dtype)
catalogo.append({'dataset':'Fashion-MNIST','modalidad':'Imagen','observaciones_vistas':len(y_fashion),'entrada_por_observación':'28×28, un canal','tarea':'10 clases; vista de prueba'})

Fashion-MNIST: diccionario y frecuencias

código categoría cantidad_prueba
0 T-shirt/top 1000
1 Trouser 1000
2 Pullover 1000
3 Dress 1000
4 Coat 1000
5 Sandal 1000
6 Shirt 1000
7 Sneaker 1000
8 Bag 1000
9 Ankle boot 1000
Imágenes: (10000, 28, 28) | Etiquetas: (10000,) | dtype: uint8

3.5.6 Cómo leer Fashion-MNIST

La prueba contiene 10,000 imágenes, con 1,000 por categoría. En ambas galerías una imagen tiene forma (28,28), pero el diccionario de etiquetas cambió: 7 representa sneaker, no un dígito escrito. Reutilizar una arquitectura no permite reutilizar sin revisión el significado de sus salidas.

Las siluetas de prendas pueden compartir rasgos. Esa observación ayuda a plantear una tarea, pero esta galería seleccionada no mide su dificultad ni demuestra qué clases confundirá un modelo. La evaluación necesitará predicciones y una matriz de confusión, no solo inspección visual.

3.5.7 CIFAR-10: color, fondo y tres canales

CIFAR-10 contiene pequeñas imágenes RGB de diez clases. El formato Python del proveedor guarda los tres canales aplanados. Vamos a reconstruir alto, ancho y canales antes de dibujar: el orden de ejes determina si la imagen conserva sus colores.

cifar_dir=KERAS/'cifar-10-batches-py-target'/'cifar-10-batches-py'
if (cifar_dir/'test_batch').is_file():
    contenido=(cifar_dir/'test_batch').read_bytes()
else:
    paquete=obtener('cifar-10-python.tar.gz','https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz',
                    [KERAS/'cifar-10-batches-py-target_archive'])
    contenido=archivo_tar(paquete,'cifar-10-batches-py/test_batch')
# Formato Python del archivo oficial del proveedor.
lote=pickle.loads(contenido,encoding='bytes')
cifar=lote[b'data'].reshape(-1,3,32,32).transpose(0,2,3,1)
y_cifar=np.asarray(lote[b'labels'])
nombres_cifar=['airplane','automobile','bird','cat','deer','dog','frog','horse','ship','truck']
seleccion=[np.flatnonzero(y_cifar==k)[0] for k in range(10)]
galeria(cifar[seleccion],[f'{k}: {nombres_cifar[k]}' for k in range(10)],'CIFAR-10: categorías con color y contexto')
ejemplo=cifar[seleccion[3]]
fig,axes=plt.subplots(1,4,figsize=(11,3))
axes[0].imshow(ejemplo,interpolation='nearest');axes[0].set_title('RGB combinado')
for j,color in enumerate(['R','G','B']):
    axes[j+1].imshow(ejemplo[:,:,j],cmap='gray',vmin=0,vmax=255,interpolation='nearest');axes[j+1].set_title(f'Intensidad del canal {color}')
for ax in axes:ax.axis('off')
plt.tight_layout();plt.show()
print('Archivo aplanado:',lote[b'data'].shape,'| Imágenes reconstruidas:',cifar.shape)
print('dtype:',cifar.dtype,'| rango observado:',int(cifar.min()),int(cifar.max()))
tabla(pd.DataFrame({'código':range(10),'clase':nombres_cifar,'cantidad_prueba':np.bincount(y_cifar)}),'CIFAR-10: etiquetas de prueba')
catalogo.append({'dataset':'CIFAR-10','modalidad':'Imagen','observaciones_vistas':len(y_cifar),'entrada_por_observación':'32×32×3, RGB','tarea':'10 clases; vista de prueba'})

Archivo aplanado: (10000, 3072) | Imágenes reconstruidas: (10000, 32, 32, 3)
dtype: uint8 | rango observado: 0 255

CIFAR-10: etiquetas de prueba

código clase cantidad_prueba
0 airplane 1000
1 automobile 1000
2 bird 1000
3 cat 1000
4 deer 1000
5 dog 1000
6 frog 1000
7 horse 1000
8 ship 1000
9 truck 1000

3.5.8 Cómo leer los canales

La prueba tiene 10,000 imágenes con forma (32,32,3). Cada una contiene 3,072 valores; el archivo aplanado tiene esa cantidad de columnas por fila. La galería muestra una imagen por categoría, mientras que la tabla confirma 1,000 imágenes por clase en esta partición.

Los paneles de R, G y B se muestran en gris para comparar intensidades. No son tres observaciones independientes: forman una sola imagen. Para una CNN con canales primero, el lote pasaría de (B,32,32,3) a (B,3,32,32). La reconstrucción del archivo usa ese mismo cuidado por los ejes.

3.5.9 Flowers: las carpetas también pueden guardar etiquetas

Flowers organiza fotografías en directorios llamados daisy, dandelion, roses, sunflowers y tulips. Los archivos tienen tamaños variables. Construiremos un manifiesto: una tabla que relaciona cada ruta con su etiqueta. Mostraremos dos fotografías por categoría para observar variación dentro de una clase.

Las fotografías del archivo se distribuyen con atribuciones individuales. La tabla posterior acredita las imágenes mostradas según su LICENSE.txt; la licencia de esas fotografías es CC BY 2.0.

flowers_dir=KERAS/'flower_photos'/'flower_photos'
if not flowers_dir.is_dir():
    paquete=obtener('flower_photos.tgz','https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz',
                    [KERAS/'flower_photos_archive'])
    flowers_dir=CACHE/'flower_photos'
    with tarfile.open(paquete,'r:gz') as archivo:
        for member in archivo.getmembers():
            if member.isfile() and (member.name.endswith('.jpg') or member.name.endswith('LICENSE.txt')):
                partes=Path(member.name).parts
                assert partes[0]=='flower_photos' and '..' not in partes
                destino=CACHE/member.name;destino.parent.mkdir(parents=True,exist_ok=True)
                destino.write_bytes(archivo.extractfile(member).read())
clases_flowers=sorted(p.name for p in flowers_dir.iterdir() if p.is_dir())
registros=[]
for clase in clases_flowers:
    for p in sorted((flowers_dir/clase).glob('*.jpg')):
        registros.append({'ruta':p.relative_to(flowers_dir).as_posix(),'etiqueta':clase})
manifiesto=pd.DataFrame(registros)
tabla(manifiesto.head(8),'Flowers: manifiesto de archivos')
seleccion_flowers=[]
for clase in clases_flowers:
    seleccion_flowers.extend(manifiesto.loc[manifiesto.etiqueta==clase,'ruta'].iloc[:2].tolist())
fotos=[];dimensiones=[]
for ruta in seleccion_flowers:
    with Image.open(flowers_dir/ruta) as im:
        arr=np.asarray(im.convert('RGB'));fotos.append(arr)
        dimensiones.append({'ruta':ruta,'alto':arr.shape[0],'ancho':arr.shape[1],'canales':arr.shape[2]})
galeria(fotos,[p.split('/')[0] for p in seleccion_flowers],'Flowers: dos fotografías por clase',columnas=5)
tabla(pd.DataFrame(dimensiones),'Flowers: dimensiones originales de las fotografías mostradas')
licencias=(flowers_dir/'LICENSE.txt').read_text(encoding='utf-8')
creditos=[]
for ruta in seleccion_flowers:
    linea=next(line for line in licencias.splitlines() if line.startswith(ruta+' '))
    resto=linea.split(' CC-BY by ',1)[1];autor,url=resto.rsplit(' - ',1)
    creditos.append({'fotografía':ruta,'autor':autor,'fuente':url,'licencia':'CC BY 2.0'})
tabla(pd.DataFrame(creditos),'Atribución de las fotografías mostradas (sin recorte)')
conteos_flowers=manifiesto.etiqueta.value_counts().reindex(clases_flowers)
fig,ax=plt.subplots(figsize=(8,3.5));ax.bar(conteos_flowers.index,conteos_flowers.values,color='tab:green')
ax.set(ylabel='Fotografías',title='Flowers: conteo de archivos por clase');plt.tight_layout();plt.show()
print('Fotografías encontradas:',len(manifiesto),'| Clases:',len(clases_flowers))
catalogo.append({'dataset':'Flowers','modalidad':'Imagen','observaciones_vistas':len(manifiesto),'entrada_por_observación':'RGB, dimensiones variables','tarea':'5 clases; sin partición aquí'})

Flowers: manifiesto de archivos

ruta etiqueta
daisy/100080576_f52e8ee070_n.jpg daisy
daisy/10140303196_b88d3d6cec.jpg daisy
daisy/10172379554_b296050f82_n.jpg daisy
daisy/10172567486_2748826a8b.jpg daisy
daisy/10172636503_21bededa75_n.jpg daisy
daisy/102841525_bd6628ae3c.jpg daisy
daisy/1031799732_e7f4008c03.jpg daisy
daisy/10391248763_1d16681106_n.jpg daisy

Flowers: dimensiones originales de las fotografías mostradas

ruta alto ancho canales
daisy/100080576_f52e8ee070_n.jpg 263 320 3
daisy/10140303196_b88d3d6cec.jpg 313 500 3
dandelion/10043234166_e6dd915111_n.jpg 213 320 3
dandelion/10200780773_c6051a7d71_n.jpg 218 320 3
roses/10090824183_d02c613f10_m.jpg 240 179 3
roses/102501987_3cdb8e5394_n.jpg 240 320 3
sunflowers/1008566138_6927679c8a.jpg 330 500 3
sunflowers/1022552002_2b93faf9e7_n.jpg 240 320 3
tulips/100930342_92e8746431_n.jpg 209 320 3
tulips/10094729603_eeca3f2cb6.jpg 335 500 3

Atribución de las fotografías mostradas (sin recorte)

fotografía autor fuente licencia
daisy/100080576_f52e8ee070_n.jpg mario https://www.flickr.com/photos/64738468@N00/100080576/ CC BY 2.0
daisy/10140303196_b88d3d6cec.jpg Irene Grassi https://www.flickr.com/photos/sun_sand_sea/10140303196/ CC BY 2.0
dandelion/10043234166_e6dd915111_n.jpg Vlastimil Koutecký https://www.flickr.com/photos/vlastimil_koutecky/10043234166/ CC BY 2.0
dandelion/10200780773_c6051a7d71_n.jpg waferboard https://www.flickr.com/photos/waferboard/10200780773/ CC BY 2.0
roses/10090824183_d02c613f10_m.jpg Von Fumetti https://www.flickr.com/photos/104286287@N06/10090824183/ CC BY 2.0
roses/102501987_3cdb8e5394_n.jpg Elaine with Grey Cats https://www.flickr.com/photos/elainegreycats/102501987/ CC BY 2.0
sunflowers/1008566138_6927679c8a.jpg liz west https://www.flickr.com/photos/calliope/1008566138/ CC BY 2.0
sunflowers/1022552002_2b93faf9e7_n.jpg emerson12 https://www.flickr.com/photos/emerson12/1022552002/ CC BY 2.0
tulips/100930342_92e8746431_n.jpg Jackie Curry https://www.flickr.com/photos/octoberdog/100930342/ CC BY 2.0
tulips/10094729603_eeca3f2cb6.jpg Jocelyn Kinghorn https://www.flickr.com/photos/joceykinghorn/10094729603/ CC BY 2.0

Fotografías encontradas: 3670 | Clases: 5

3.5.10 Cómo leer Flowers

El manifiesto reúne 3,670 fotografías y cinco categorías. Las primeras filas pertenecen a daisy porque ordenamos las carpetas y las rutas: otra vez, el orden de almacenamiento no es una distribución de clases.

La galería muestra variaciones de encuadre, fondo y número de flores. La tabla de dimensiones confirma que no puedes apilar directamente todas las imágenes como un único arreglo rectangular. Redimensionar permitiría hacerlo, pero implica decidir cómo tratar proporciones y contenido; ese desarrollo corresponde a los pipelines de visión.

Aquí no creamos una partición de entrenamiento y prueba. El nombre train usado por algunos catálogos para toda la colección no significa que ya exista una prueba independiente para tu experimento. Tendrás que definirla y revisar imágenes relacionadas o duplicadas antes de afirmar generalización.

3.6 Parte C — Texto: palabras, etiquetas y longitudes

3.6.1 IMDB: una reseña no es una fila de números equivalentes

La versión de IMDB distribuida por Keras guarda reseñas codificadas como secuencias de identificadores de palabras. El archivo original permite observar esas secuencias sin instalar Keras. Su diccionario recupera palabras; imprimiremos solo un fragmento breve de una reseña para mostrar el formato.

Los identificadores no son cantidades: que una palabra tenga código 500 no la hace más intensa que una de código 20. Esta lectura usa los índices del archivo original, antes de que keras.datasets.imdb.load_data() añada sus desplazamientos y tokens especiales. Si usas esa API, debes seguir su convenio de decodificación.

imdb_file=obtener('imdb.npz','https://storage.googleapis.com/tensorflow/tf-keras-datasets/imdb.npz',[KERAS/'imdb.npz'])
dic_file=obtener('imdb_word_index.json','https://storage.googleapis.com/tensorflow/tf-keras-datasets/imdb_word_index.json',[KERAS/'imdb_word_index.json'])
# Las secuencias del archivo oficial se almacenan como arreglos de objetos.
with np.load(imdb_file,allow_pickle=True) as z:
    textos_train=z['x_train'];etiquetas_texto=z['y_train'];n_imdb_test=len(z['y_test'])
diccionario=json.loads(dic_file.read_text(encoding='utf-8'))
palabra_por_id={codigo:palabra for palabra,codigo in diccionario.items()}
fragmento=' '.join(palabra_por_id.get(int(t),'[desconocido]') for t in textos_train[0][:20])
tabla(pd.DataFrame({'campo':['secuencia','etiqueta','n_tokens'],
                    'contenido':[str(list(textos_train[0][:12]))+' …',str(etiquetas_texto[0]),str(len(textos_train[0]))]}),
      'IMDB: estructura de una observación del archivo original')
print('Fragmento de la primera reseña (20 palabras):',fragmento)
print('Etiqueta de esa reseña:',int(etiquetas_texto[0]),'(0=negativa, 1=positiva)')
longitudes=np.array([len(t) for t in textos_train])
tabla(pd.DataFrame({'etiqueta':[0,1],'significado':['negativa','positiva'],
                    'cantidad_entrenamiento':np.bincount(etiquetas_texto)}),'IMDB: distribución de etiquetas')
fig,axes=plt.subplots(1,2,figsize=(10,4))
axes[0].hist(longitudes,bins=40,color='tab:blue',alpha=.8);axes[0].set(xlabel='Tokens por reseña',ylabel='Reseñas',title='Longitud de las secuencias de entrenamiento')
limite_tokens=200
fraccion_larga=np.mean(longitudes>limite_tokens)
axes[0].axvline(limite_tokens,color='tab:red',ls='--',label='Umbral ilustrativo: 200');axes[0].legend(fontsize=8)
axes[1].bar(['≤200','>200'],[(longitudes<=200).sum(),(longitudes>200).sum()],color=['tab:green','tab:red'])
axes[1].set(ylabel='Reseñas',title='Qué afectaría un truncamiento a 200')
plt.tight_layout();plt.show()
print(f'Entrenamiento={len(textos_train)}, prueba={n_imdb_test}; mediana de longitud={np.median(longitudes):.1f}')
print(f'Reseñas de entrenamiento con más de 200 tokens: {100*fraccion_larga:.2f}%')
estadisticas['fraccion_larga']=fraccion_larga
catalogo.append({'dataset':'IMDB','modalidad':'Texto','observaciones_vistas':len(textos_train),'entrada_por_observación':'Secuencia de longitud variable','tarea':'Sentimiento; entrenamiento'})

IMDB: estructura de una observación del archivo original

campo contenido
secuencia [23022, 309, 6, 3, 1069, 209, 9, 2175, 30, 1, 169, 55] …
etiqueta 1
n_tokens 138
Fragmento de la primera reseña (20 palabras): bromwell high is a cartoon comedy it ran at the same time as some other programs about school life such
Etiqueta de esa reseña: 1 (0=negativa, 1=positiva)

IMDB: distribución de etiquetas

etiqueta significado cantidad_entrenamiento
0 negativa 12500
1 positiva 12500

Entrenamiento=25000, prueba=25000; mediana de longitud=177.0
Reseñas de entrenamiento con más de 200 tokens: 42.40%

3.6.2 Cómo leer IMDB

El archivo contiene 25,000 reseñas de entrenamiento y 25,000 de prueba. En entrenamiento hay 12,500 por etiqueta. La primera secuencia y su fragmento muestran el enlace entre números y palabras; el contenido impreso es solo un extracto y no debe usarse para juzgar toda la reseña.

Las longitudes varían. Un límite de 200 tokens, escogido aquí solo para ilustrar una decisión, afectaría al 42.40 % de las reseñas de entrenamiento. Esa fracción cuantifica cuántas serían truncadas, no cuánto perdería la exactitud de un modelo. El padding añadiría posiciones a las secuencias cortas; el truncamiento eliminaría posiciones de las largas.

La galería textual y las longitudes pertenecen a entrenamiento. El desarrollo de tokenización y NLP especializado queda en su libro propio; aquí interesa reconocer la estructura que reciben las redes de secuencias.

3.7 Parte D — Audio: muestras en el tiempo

3.7.1 Dígitos hablados: archivo, forma de onda y espectrograma

Usamos tres grabaciones de Free Spoken Digit Dataset (FSDD), fijando la versión del repositorio. Sus nombres tienen la forma dígito_hablante_repetición.wav. Las tres seleccionadas pertenecen al hablante jackson y representan cero, uno y dos; no son el dataset completo ni una muestra representativa de sus hablantes.

Una forma de onda muestra amplitud frente al tiempo. Un espectrograma descompone ventanas sucesivas para mostrar cómo cambia la energía por frecuencia. En las figuras no hay predicciones de un modelo: son dos representaciones de los mismos sonidos.

FSDD_VERSION='26eb9aaf76e81b692f806f9140c2d2777410d7a1'
grabaciones=[];datos_audio=[]
for digito in [0,1,2]:
    nombre=f'{digito}_jackson_0.wav'
    url=f'https://raw.githubusercontent.com/Jakobovski/free-spoken-digit-dataset/{FSDD_VERSION}/recordings/{nombre}'
    ruta=obtener(nombre,url)
    fs,pcm=wavfile.read(ruta)
    audio=pcm.astype(np.float64)/np.iinfo(pcm.dtype).max
    datos_audio.append((fs,audio))
    grabaciones.append({'archivo':nombre,'dígito':digito,'hablante':'jackson','frecuencia_Hz':fs,
                        'muestras':len(audio),'duración_s':len(audio)/fs,'canales':1 if audio.ndim==1 else audio.shape[1]})
tabla(pd.DataFrame(grabaciones),'FSDD: metadatos de las tres grabaciones')
fig,axes=plt.subplots(3,1,figsize=(9,6),sharex=True)
for ax,(fs,audio),fila in zip(axes,datos_audio,grabaciones):
    ax.plot(np.arange(len(audio))/fs,audio,lw=.6)
    ax.set(ylabel='Amplitud',title=f"{fila['archivo']} | {fila['duración_s']:.3f} s")
axes[-1].set_xlabel('Tiempo (segundos)');plt.tight_layout();plt.show()
fs,audio=datos_audio[0]
f,t,S=signal.spectrogram(audio,fs=fs,nperseg=256,noverlap=192,scaling='density')
SdB=10*np.log10(np.maximum(S,1e-12))
fig,ax=plt.subplots(figsize=(9,3.5))
im=ax.pcolormesh(t,f,SdB,shading='auto',cmap='magma',vmin=-90,vmax=-20)
ax.set(xlabel='Tiempo (s)',ylabel='Frecuencia (Hz)',title='Espectrograma: 0_jackson_0.wav')
fig.colorbar(im,ax=ax,label='Densidad espectral (dB re 1 unidad²/Hz)');plt.tight_layout();plt.show()
display(Audio(audio,rate=fs,normalize=False))
print('Fuente: Free Spoken Digit Dataset, Jakobovski y colaboradores. Licencia: CC BY-SA 4.0.')
print('Versión fijada:',FSDD_VERSION)
catalogo.append({'dataset':'FSDD (selección)','modalidad':'Audio','observaciones_vistas':3,'entrada_por_observación':'Señal mono de duración variable','tarea':'Dígito hablado; solo inspección'})

FSDD: metadatos de las tres grabaciones

archivo dígito hablante frecuencia_Hz muestras duración_s canales
0_jackson_0.wav 0 jackson 8000 5148 0.643 1
1_jackson_0.wav 1 jackson 8000 4138 0.517 1
2_jackson_0.wav 2 jackson 8000 3990 0.499 1

Fuente: Free Spoken Digit Dataset, Jakobovski y colaboradores. Licencia: CC BY-SA 4.0.
Versión fijada: 26eb9aaf76e81b692f806f9140c2d2777410d7a1

3.7.2 Cómo leer el audio

La tabla muestra 8,000 muestras por segundo y un canal en estas grabaciones. Su duración se obtiene dividiendo el número de muestras por esa frecuencia; no es el número de ventanas del espectrograma. Las formas de onda tienen longitudes distintas, aunque cada archivo represente un solo dígito.

El color del espectrograma expresa una densidad espectral en escala logarítmica respecto a la referencia indicada, no una probabilidad de clase ni un nivel acústico calibrado en dB SPL. El límite superior de frecuencia es 4,000 Hz, la mitad de la frecuencia de muestreo. Cambiar ventana y solapamiento modifica la resolución temporal y frecuencial.

Estas tres observaciones son insuficientes para entrenar o comparar modelos. Si quieres medir generalización a voces nuevas, separa hablantes entre entrenamiento y prueba. Si sigues una partición publicada, documenta qué pregunta evalúa: reconocer grabaciones nuevas de hablantes conocidos difiere de reconocer a hablantes no vistos.

3.8 Parte E — Tiempo y relaciones

3.8.1 Sunspots: el orden de las filas forma parte del dato

La serie de actividad solar incluida en statsmodels contiene un registro por año. Conservamos los encabezados YEAR y SUNACTIVITY: fecha y variable observada cumplen funciones diferentes. No hay una columna target separada; para pronosticar tendrás que definir qué valor futuro se desea predecir a partir del pasado.

solar=sm.datasets.sunspots.load_pandas().data
tabla(solar.head(8),'Sunspots: encabezados y primeras observaciones')
fig,ax=plt.subplots(figsize=(10,3.8))
ax.plot(solar.YEAR,solar.SUNACTIVITY,lw=1.2)
corte=solar.YEAR.iloc[int(.8*len(solar))]
ax.axvline(corte,color='tab:red',ls='--',label='Corte temporal ilustrativo')
ax.set(xlabel='Año',ylabel='Actividad solar anual',title='Sunspots: conservar el eje temporal')
ax.legend();plt.tight_layout();plt.show()
valores=solar.SUNACTIVITY.to_numpy()
ventana=12
ventanas=np.lib.stride_tricks.sliding_window_view(valores,ventana+1)
X_ventanas=ventanas[:,:ventana]
y_futuro=ventanas[:,-1]
print('Serie:',solar.shape,'| Primer año:',int(solar.YEAR.min()),'| Último año:',int(solar.YEAR.max()))
print('Ventanas de 12 años:',X_ventanas.shape,'| Respuesta del año siguiente:',y_futuro.shape)
print('Primera ventana:',X_ventanas[0].tolist(),'| Siguiente valor:',y_futuro[0])
catalogo.append({'dataset':'Sunspots','modalidad':'Serie temporal','observaciones_vistas':len(solar),'entrada_por_observación':'Un año; ventanas para modelar','tarea':'Pronóstico, si se define horizonte'})

Sunspots: encabezados y primeras observaciones

YEAR SUNACTIVITY
1700.000 5.000
1701.000 11.000
1702.000 16.000
1703.000 23.000
1704.000 36.000
1705.000 58.000
1706.000 29.000
1707.000 20.000

Serie: (309, 2) | Primer año: 1700 | Último año: 2008
Ventanas de 12 años: (297, 12) | Respuesta del año siguiente: (297,)
Primera ventana: [5.0, 11.0, 16.0, 23.0, 36.0, 58.0, 29.0, 20.0, 10.0, 8.0, 3.0, 0.0] | Siguiente valor: 0.0

3.8.2 Cómo leer el libro y sus ventanas

Hay 309 observaciones anuales, de 1700 a 2008. Al usar doce años para predecir el siguiente, aparecen 297 ventanas de longitud doce. El aumento de filas de entrenamiento no crea nuevos años independientes: las ventanas consecutivas comparten once valores.

La línea vertical es un corte ilustrativo, no una evaluación realizada. Para simular una predicción futura, define la separación por fecha de la respuesta. Construir ventanas a ambos lados puede usar historia anterior al corte como entrada de prueba, lo cual es legítimo si estaba disponible; lo que no puedes usar es información posterior al instante de predicción. La actividad visible tiene ciclos, pero la figura no demuestra que un modelo pueda anticiparlos con precisión.

3.8.3 Karate Club: una tabla de relaciones y un grafo

El grafo de Karate Club describe relaciones entre miembros de un club. Cada nodo tiene un identificador y un atributo club; las aristas conectan pares de nodos. Los identificadores indican entidades, no cantidades ordenadas por importancia. Veremos lista de nodos, lista de aristas, dibujo y matriz de adyacencia.

G=nx.karate_club_graph()
nodos=pd.DataFrame([{'nodo':n,'club':G.nodes[n]['club'],'grado':G.degree(n)} for n in G.nodes])
aristas=pd.DataFrame([{'origen':u,'destino':v,'peso':d['weight']} for u,v,d in G.edges(data=True)])
tabla(nodos.head(8),'Karate Club: nodos y atributos')
tabla(aristas.head(8),'Karate Club: primeras aristas')
pos=nx.spring_layout(G,seed=SEMILLA)
fig,axes=plt.subplots(1,2,figsize=(11,4.5))
colores=['tab:blue' if G.nodes[n]['club']=='Mr. Hi' else 'tab:orange' for n in G.nodes]
nx.draw_networkx(G,pos=pos,ax=axes[0],node_color=colores,node_size=140,font_size=7,width=.6)
axes[0].set_title('Azul: Mr. Hi | naranja: Officer');axes[0].axis('off')
A=nx.to_numpy_array(G,nodelist=list(G.nodes),weight=None,dtype=int)
axes[1].imshow(A,cmap='Blues',interpolation='nearest',vmin=0,vmax=1)
axes[1].set(xlabel='Nodo destino',ylabel='Nodo origen',title='Adyacencia binaria: una celda por par')
plt.tight_layout();plt.show()
print('Nodos:',G.number_of_nodes(),'| Aristas no dirigidas:',G.number_of_edges())
print('Unos en la matriz:',int(A.sum()),'| Forma:',A.shape)
print('Grado medio:',round(np.mean([G.degree(n) for n in G.nodes]),4))
catalogo.append({'dataset':'Karate Club','modalidad':'Grafo','observaciones_vistas':G.number_of_nodes(),'entrada_por_observación':'Nodo y relaciones; un solo grafo','tarea':'Análisis relacional; etiquetas de nodo'})

Karate Club: nodos y atributos

nodo club grado
0 Mr. Hi 16
1 Mr. Hi 9
2 Mr. Hi 10
3 Mr. Hi 6
4 Mr. Hi 3
5 Mr. Hi 4
6 Mr. Hi 4
7 Mr. Hi 4

Karate Club: primeras aristas

origen destino peso
0 1 4
0 2 5
0 3 3
0 4 3
0 5 3
0 6 3
0 7 2
0 8 2

Nodos: 34 | Aristas no dirigidas: 78
Unos en la matriz: 156 | Forma: (34, 34)
Grado medio: 4.5882

3.8.4 Cómo leer las relaciones

El grafo contiene 34 nodos y 78 aristas no dirigidas. La matriz tiene forma (34,34) y 156 unos: cada relación aparece en dos posiciones simétricas. Sus pesos originales se conservaron en la tabla de aristas, pero la matriz mostrada es binaria porque usamos weight=None.

La posición del dibujo se calculó para visualizar conexiones: una distancia corta en la figura no es una distancia geográfica medida. Tampoco 34 nodos equivalen a 34 grafos independientes. Si club fuera la etiqueta a predecir, no debería entrar como atributo predictor; la forma de dividir nodos y conservar relaciones depende del protocolo de evaluación de grafos.

3.9 Comparar antes de elegir

La tabla siguiente resume lo cargado en esta guía, no el tamaño total de todos los benchmarks. En imágenes distingue las particiones de prueba mostradas; en FSDD conserva la selección de tres archivos. En Flowers todavía no definimos una partición y en Karate Club contamos nodos de un solo grafo.

tabla(pd.DataFrame(catalogo),'Mapa de los trece datasets inspeccionados')
assert len(catalogo)==13
print('Datasets inspeccionados:',len(catalogo))

Mapa de los trece datasets inspeccionados

dataset modalidad observaciones_vistas entrada_por_observación tarea
Iris Tabla 150 4 variables Clasificación multiclase
Wine Tabla 178 13 variables Clasificación multiclase
Breast Cancer Tabla 569 30 variables Clasificación binaria
Diabetes Tabla 442 10 variables Regresión
Digits Imagen 1797 8×8, un canal 10 clases
MNIST Imagen 10000 28×28, un canal 10 clases; vista de prueba
Fashion-MNIST Imagen 10000 28×28, un canal 10 clases; vista de prueba
CIFAR-10 Imagen 10000 32×32×3, RGB 10 clases; vista de prueba
Flowers Imagen 3670 RGB, dimensiones variables 5 clases; sin partición aquí
IMDB Texto 25000 Secuencia de longitud variable Sentimiento; entrenamiento
FSDD (selección) Audio 3 Señal mono de duración variable Dígito hablado; solo inspección
Sunspots Serie temporal 309 Un año; ventanas para modelar Pronóstico, si se define horizonte
Karate Club Grafo 34 Nodo y relaciones; un solo grafo Análisis relacional; etiquetas de nodo
Datasets inspeccionados: 13

3.10 Qué pedirle a un dataset

Pregunta Qué mirar Error que ayuda a evitar
¿Qué es una observación? Fila, imagen, documento, archivo, año o nodo Confundir registros con unidades independientes
¿Cuál es la respuesta? Nombre, tipo y diccionario de etiquetas Tratar códigos como magnitudes o incluir la respuesta en X
¿Cómo está almacenado? Encabezados, shape, dtype y orden de ejes Asociar etiquetas e imágenes equivocadas
¿Qué escala tiene? Unidades, mínimos, máximos y transformaciones previas Normalizar con una regla que pertenece a otro dataset
¿Está equilibrado? Frecuencias por clase y por grupo Interpretar exactitud sin una referencia adecuada
¿Hay ausencias o duplicados? Valores faltantes, identificadores y archivos relacionados Confundir ausencia de NA con calidad completa
¿Cómo se separa? Particiones oficiales, fechas, personas o hablantes Filtrar información entre entrenamiento y prueba
¿Puedo reutilizarlo? Fuente, versión, licencia y atribuciones Dar por supuesto que acceso público permite cualquier uso

La ausencia de faltantes en las tablas cargadas no demuestra que el proceso original estuviera completo o que no se hayan filtrado observaciones. Tampoco una muestra visual seleccionada reemplaza un análisis de cobertura del dataset.

3.11 Ejercicios visuales

  1. En Iris, compara head() con cinco filas elegidas mediante una semilla fija. Explica qué cambia en la vista y qué no cambia en los conteos de clases.
  2. En Wine, dibuja una variable por clase y conserva las mismas escalas y bins en los paneles. Describe el solapamiento sin convertirlo en una exactitud predictiva.
  3. En Digits, elige un píxel, localízalo en la imagen y encuentra su columna en data. Verifica que ambos valores coinciden.
  4. En CIFAR-10, reconstruye una imagen siguiendo el orden correcto de canales. Explica por qué un reshape sin transposición no representa el mismo orden de ejes.
  5. En Flowers, prepara una tabla con rutas, dimensiones y etiquetas de diez fotografías adicionales. Conserva sus atribuciones y describe qué cambiaría al redimensionarlas.
  6. En IMDB, prueba otros umbrales de longitud y reporta la fracción de reseñas que cada uno truncaría, sin afirmar un efecto sobre una métrica predictiva que no calculaste.
  7. En audio, compara las duraciones y espectrogramas de otro hablante del mismo dataset. Distingue variación entre grabaciones de evidencia de generalización.
  8. En el libro solar, identifica el año objetivo de cada ventana. Propón una separación temporal que no utilice respuestas futuras en el entrenamiento.

3.12 Continuación

Las mismas dimensiones pueden representar tareas diferentes: MNIST y Fashion-MNIST comparten resolución, pero cambian sus etiquetas; una tabla puede tener clases o una respuesta continua; un tensor temporal puede mezclar unidades relacionadas si ignoras las ventanas o los hablantes.

En Generación de datos construiremos problemas donde podamos controlar ruido, desbalance y estructura. En Pipelines organizaremos lectura, transformaciones y lotes. Esta guía queda como referencia para reconocer lo que llega antes de entrenar.

3.13 Fuentes y atribuciones

Las licencias y condiciones deben comprobarse en la fuente de cada dataset; la licencia de una biblioteca no determina automáticamente la de los datos que distribuye.