Estoy trabajando en las lecciones sobre la creación de una red neuronal y no sé por qué se usa 512 para linear_relu_stack en el código de ejemplo:
class NeuralNetwork(nn.Module): def __init__(self): super(NeuralNetwork, self).__init__() self.flatten = nn.Flatten() self.linear_relu_stack = nn.Sequential( nn.Linear(28*28, 512), nn.ReLU(), nn.Linear(512, 512), nn.ReLU(), nn.Linear(512, 10), nn.ReLU() ) def forward(self, x): x = self.flatten(x) logits = self.linear_relu_stack(x) return logits Empecé a buscar en Google y vi muchos ejemplos de la función torch.nn.Linear usando varios valores de 2**N pero no me queda claro por qué usan potencias de 2 ni cómo eligen qué valor usar.
La razón es cómo el hardware hace el proceso. En el aprendizaje profundo, las operaciones matriciales son los principales cálculos y la fuente de las operaciones de coma flotante (FLOP).
Las operaciones de datos múltiples de instrucción única (SIMD) en las CPU ocurren en tamaños de lote, que son potencias de 2. Considere echar un vistazo si está interesado:
https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/37631.pdf
Y para las GPU:
https://docs.nvidia.com/cuda/cuda-c-best-practices-guide/index.html
Se garantiza que la memoria asignada a través de la API de tiempo de ejecución de CUDA, como a través de cudaMalloc(), esté alineada con al menos 256 bytes. Por lo tanto, elegir tamaños de bloque de subprocesos sensatos, como múltiplos del tamaño de deformación (es decir, 32 en las GPU actuales), facilita el acceso a la memoria mediante deformaciones que están correctamente alineadas. (Considere lo que sucedería con las direcciones de memoria a las que acceden el segundo, tercer y subsiguientes bloques de subprocesos si el tamaño del bloque de subprocesos no fuera un múltiplo del tamaño warp, por ejemplo).
Esto significa que cualquier múltiplo de 32 optimizará el acceso a la memoria y, por lo tanto, la velocidad de procesamiento, mientras usa una GPU.
Sobre el valor correcto, la forma piramidal generalmente funciona mejor, porque a medida que profundiza, la red neuronal tiende a crear representaciones internas de los datos transformados, en una forma jerárquica esperada, por lo tanto, piramidal. Entonces, una buena suposición es usar cantidades decrecientes de neuronas en cada capa a medida que se acerca a la salida, por ejemplo:
self.flatten = nn.Flatten() self.linear_relu_stack = nn.Sequential( nn.Linear(28*28, 512), nn.ReLU(), nn.Linear(512, 128), nn.ReLU(), nn.Linear(128, 10), nn.ReLU() )Pero no existe una regla general y puede encontrar campos completos de estudio (como la búsqueda de arquitectura neuronal) sobre cómo encontrar hiperparámetros óptimos para redes neuronales.
puede echar un vistazo aquí para obtener información más detallada:
Si bien hay afirmaciones sin fundamento de que las potencias de 2 ayudan a optimizar el rendimiento de varias partes de una red neuronal, es un método conveniente para seleccionar/probar/encontrar el orden de magnitud correcto para usar para varios parámetros/hiperparámetros.