Saltar al contenido principal

Representacion del Estado del Tablero

Antes de disenar una IA de Go, primero debemos resolver un problema fundamental: ¿Como hacer que la computadora "entienda" el tablero?

Este problema parece simple, pero involucra estructuras de datos, algoritmos y diseno de entrada para redes neuronales en multiples niveles. Este articulo comenzara desde la matriz bidimensional mas basica, introduciendo gradualmente varios metodos de representacion del tablero utilizados en IA de Go, y finalmente explicara como AlphaGo codifica el tablero en un formato que las redes neuronales pueden procesar.


Representacion con Matriz Bidimensional: La Forma Mas Intuitiva

Concepto Basico

El tablero de Go es una cuadricula de 19×19, y la forma mas intuitiva de representarlo es usando una matriz bidimensional:

import numpy as np

# Constantes del tablero
BOARD_SIZE = 19
EMPTY = 0
BLACK = 1
WHITE = 2

# Inicializar tablero vacio
board = np.zeros((BOARD_SIZE, BOARD_SIZE), dtype=np.int8)

# Colocar piedra: negra en D4 (3, 3)
board[3, 3] = BLACK

# Colocar piedra: blanca en Q16 (15, 3)
board[15, 3] = WHITE

Sistema de Coordenadas

El Go usa dos sistemas de coordenadas comunes:

1. Coordenadas Numericas (para programacion)

    0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18
0 . . . . . . . . . . . . . . . . . . .
1 . . . . . . . . . . . . . . . . . . .
2 . . . . . . . . . . . . . . . . . . .
3 . . . ● . . . . . + . . . . . ○ . . .
...

2. Coordenadas Letra-Numero (para registros de partidas)

    A  B  C  D  E  F  G  H  J  K  L  M  N  O  P  Q  R  S  T
19 . . . . . . . . . . . . . . . . . . .
18 . . . . . . . . . . . . . . . . . . .
17 . . . . . . . . . . . . . . . . . . .
16 . . . ● . . . . . + . . . . . ○ . . .
...

Nota: Las coordenadas de letras omiten "I" (para evitar confusion con el numero 1).

Funciones de Conversion de Coordenadas

def coord_to_index(coord):
"""
Convertir coordenadas de partida a indices de matriz
Ejemplo: 'D4' -> (3, 15)
"""
letters = 'ABCDEFGHJKLMNOPQRST' # Omite I
col = letters.index(coord[0].upper())
row = BOARD_SIZE - int(coord[1:])
return row, col


def index_to_coord(row, col):
"""
Convertir indices de matriz a coordenadas de partida
Ejemplo: (3, 15) -> 'Q16'
"""
letters = 'ABCDEFGHJKLMNOPQRST'
return f"{letters[col]}{BOARD_SIZE - row}"

Analisis de Complejidad Espacial

Complejidad espacial de la representacion con matriz bidimensional:

RepresentacionTamano por celdaEspacio total
int81 byte361 bytes
int324 bytes1.4 KB
float648 bytes2.9 KB

Para las computadoras modernas, esta memoria no es un problema. Sin embargo, cuando se necesita almacenar muchos estados del tablero (como en el arbol de busqueda MCTS), usar int8 puede reducir significativamente el uso de memoria.

Operaciones Basicas

class Board:
def __init__(self, size=19):
self.size = size
self.board = np.zeros((size, size), dtype=np.int8)
self.current_player = BLACK
self.ko_point = None # Punto de ko prohibido
self.move_history = []

def is_valid_move(self, row, col):
"""Verificar si un movimiento es legal"""
# 1. La posicion esta dentro del tablero
if not (0 <= row < self.size and 0 <= col < self.size):
return False

# 2. La posicion esta vacia
if self.board[row, col] != EMPTY:
return False

# 3. No es un punto de ko prohibido
if self.ko_point == (row, col):
return False

# 4. No es suicidio (a menos que pueda capturar)
# ... necesita verificacion mas compleja

return True

def place_stone(self, row, col):
"""Colocar piedra"""
if not self.is_valid_move(row, col):
return False

self.board[row, col] = self.current_player
self.move_history.append((row, col))

# Procesar capturas
captures = self._remove_captured_stones(row, col)

# Actualizar punto de ko
self._update_ko(row, col, captures)

# Cambiar jugador
self.current_player = WHITE if self.current_player == BLACK else BLACK

return True

Zobrist Hashing: Identificacion Rapida de Posiciones Duplicadas

Contexto del Problema

En la IA de Go, frecuentemente necesitamos determinar "si dos posiciones son iguales":

  1. Regla de ko: No se puede devolver la posicion al estado del movimiento anterior
  2. Triple ko: Manejo de reglas especiales
  3. Tabla de transposicion: Recordar posiciones ya buscadas
  4. Cache de red neuronal: Evitar calculos repetidos

Si comparamos celda por celda cada vez, necesitamos tiempo O(361). ¿Hay un metodo mas rapido?

Principio de Zobrist Hashing

Zobrist Hashing (tambien llamado Zobrist Keys) es un metodo de hash ingenioso, propuesto por Albert Zobrist en 1970. Su idea central es:

  1. Pregenerar un numero aleatorio para cada combinacion de "posicion × color"
  2. El valor hash del tablero = XOR de los numeros aleatorios de todas las posiciones con piedras
  3. Colocar/capturar piedras solo requiere una operacion XOR para actualizar el valor hash

Tabla de Numeros Aleatorios

import numpy as np

# Generar tabla de numeros aleatorios
# Usar semilla fija para reproducibilidad
np.random.seed(42)

# Un numero aleatorio para cada posicion, cada color
# zobrist[color][row][col]
zobrist_table = np.random.randint(
0, 2**64,
size=(3, BOARD_SIZE, BOARD_SIZE), # 3: EMPTY, BLACK, WHITE
dtype=np.uint64
)

# Numero aleatorio para quien juega
zobrist_player = np.random.randint(0, 2**64, dtype=np.uint64)

Propiedades de la Operacion XOR

La operacion XOR tiene varias propiedades importantes que la hacen muy adecuada para esta aplicacion:

  1. Reflexividad: A ⊕ A = 0 (el mismo numero XOR consigo mismo es 0)
  2. Reversibilidad: A ⊕ B ⊕ B = A (XOR dos veces es igual a nada)
  3. Conmutatividad: A ⊕ B = B ⊕ A
  4. Asociatividad: (A ⊕ B) ⊕ C = A ⊕ (B ⊕ C)

Esto significa:

  • Colocar piedra: hash ^= zobrist[color][row][col]
  • Capturar piedra: hash ^= zobrist[color][row][col] (¡la misma operacion!)

Implementacion

class ZobristBoard:
def __init__(self, size=19):
self.size = size
self.board = np.zeros((size, size), dtype=np.int8)
self.current_player = BLACK
self.hash = np.uint64(0)

# Inicializar tabla de numeros aleatorios
self._init_zobrist_table()

def _init_zobrist_table(self):
np.random.seed(12345)
self.zobrist = np.random.randint(
0, 2**64,
size=(3, self.size, self.size),
dtype=np.uint64
)
self.zobrist_player = np.random.randint(0, 2**64, dtype=np.uint64)

def place_stone(self, row, col, color):
"""Colocar piedra y actualizar valor hash"""
# Remover estado anterior (si hay piedra)
old_color = self.board[row, col]
if old_color != EMPTY:
self.hash ^= self.zobrist[old_color, row, col]

# Agregar nueva piedra
self.board[row, col] = color
self.hash ^= self.zobrist[color, row, col]

def remove_stone(self, row, col):
"""Capturar piedra y actualizar valor hash"""
color = self.board[row, col]
if color != EMPTY:
self.hash ^= self.zobrist[color, row, col]
self.board[row, col] = EMPTY

def switch_player(self):
"""Cambiar jugador y actualizar valor hash"""
self.hash ^= self.zobrist_player
self.current_player = WHITE if self.current_player == BLACK else BLACK

def get_hash(self):
"""Obtener valor hash de la posicion actual"""
return self.hash

Ventaja de la Actualizacion Incremental

Usando Zobrist Hashing, actualizar el valor hash solo requiere tiempo O(1):

OperacionComparacion tradicionalZobrist
Comparar dos posicionesO(361)O(1)
Actualizar despues de colocarO(361)O(1)
Actualizar despues de capturarO(361)O(k), k es el numero de capturas

Probabilidad de Colision

Usando hash de 64 bits, la probabilidad de colision es extremadamente baja:

P(colision) ≈ n² / 2^65

Donde n es el numero de posiciones diferentes. Incluso con mil millones de posiciones, la probabilidad de colision es solo aproximadamente 10^-3.

En la practica, muchos programas de Go usan Zobrist Hashing de 64 bits sin verificar colisiones, porque la probabilidad de colision es lo suficientemente baja como para ignorarla.


Representacion de Cadenas (Union-Find): Problema de Conectividad

Concepto de Cadena

En Go, una cadena (String o Chain) se refiere a un grupo de piedras del mismo color conectadas. Las "libertades" de la cadena determinan su vida o muerte.

En la figura anterior, cuatro piedras negras forman una cadena, y el numero de libertades de esta cadena determina su vida o muerte.

¿Por Que Se Necesita Gestion Eficiente de Cadenas?

Cada movimiento puede:

  1. Crear una nueva cadena
  2. Fusionar multiples cadenas
  3. Reducir las libertades de cadenas enemigas
  4. Capturar cadenas sin libertades

Estas operaciones necesitan ejecutarse eficientemente, porque en MCTS, pueden necesitarse decenas de miles de simulaciones de movimientos por segundo.

Estructura de Datos Union-Find

Union-Find (tambien llamado Disjoint Set Union, DSU) es una estructura de datos clasica para manejar problemas de "conectividad":

class UnionFind:
def __init__(self, size):
# parent[i] apunta al padre de i
# Si parent[i] = i, entonces i es raiz
self.parent = list(range(size))
self.rank = [0] * size

def find(self, x):
"""
Encontrar el nodo raiz del conjunto al que pertenece x (con compresion de camino)
"""
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x]) # Compresion de camino
return self.parent[x]

def union(self, x, y):
"""
Fusionar los conjuntos de x e y (con union por rango)
"""
root_x = self.find(x)
root_y = self.find(y)

if root_x == root_y:
return # Ya estan en el mismo conjunto

# Union por rango: conectar el arbol mas pequeno al mas grande
if self.rank[root_x] < self.rank[root_y]:
self.parent[root_x] = root_y
elif self.rank[root_x] > self.rank[root_y]:
self.parent[root_y] = root_x
else:
self.parent[root_y] = root_x
self.rank[root_x] += 1

Complejidad Temporal

La complejidad temporal de Union-Find se expresa usando la funcion de Ackermann inversa α(n):

OperacionComplejidad temporal
findO(α(n)) ≈ O(1)
unionO(α(n)) ≈ O(1)

α(n) crece extremadamente lento, para cualquier n practico, α(n) ≤ 4. Por lo tanto, puede considerarse tiempo constante.

Aplicacion de Union-Find en Go

class GoStringManager:
def __init__(self, size=19):
self.size = size
num_points = size * size

# Estructura Union-Find
self.parent = list(range(num_points))
self.rank = [0] * num_points

# Libertades de cada cadena (indexado por raiz)
self.liberties = [set() for _ in range(num_points)]

# Piedras de cada cadena (indexado por raiz)
self.stones = [set() for _ in range(num_points)]

def _index(self, row, col):
return row * self.size + col

def _neighbors(self, row, col):
"""Obtener los cuatro vecinos"""
neighbors = []
if row > 0: neighbors.append((row - 1, col))
if row < self.size - 1: neighbors.append((row + 1, col))
if col > 0: neighbors.append((row, col - 1))
if col < self.size - 1: neighbors.append((row, col + 1))
return neighbors

def find(self, idx):
if self.parent[idx] != idx:
self.parent[idx] = self.find(self.parent[idx])
return self.parent[idx]

def add_stone(self, row, col, color, board):
"""
Agregar una piedra, manejar conexiones y capturas
"""
idx = self._index(row, col)

# Crear nueva cadena
self.stones[idx] = {idx}
self.liberties[idx] = set()

# Calcular libertades de esta piedra
for nr, nc in self._neighbors(row, col):
nidx = self._index(nr, nc)
if board[nr, nc] == 0: # Punto vacio es libertad
self.liberties[idx].add(nidx)

# Fusionar con vecinos del mismo color
for nr, nc in self._neighbors(row, col):
nidx = self._index(nr, nc)
if board[nr, nc] == color:
self._merge_strings(idx, nidx)

# Reducir libertades de cadenas enemigas
opponent = 3 - color # BLACK=1, WHITE=2
captured = []
for nr, nc in self._neighbors(row, col):
nidx = self._index(nr, nc)
if board[nr, nc] == opponent:
root = self.find(nidx)
self.liberties[root].discard(idx)
if len(self.liberties[root]) == 0:
captured.append(root)

return captured

def _merge_strings(self, idx1, idx2):
"""Fusionar dos cadenas"""
root1 = self.find(idx1)
root2 = self.find(idx2)

if root1 == root2:
return

# Union por rango
if self.rank[root1] < self.rank[root2]:
root1, root2 = root2, root1

self.parent[root2] = root1
if self.rank[root1] == self.rank[root2]:
self.rank[root1] += 1

# Fusionar conjuntos de libertades y piedras
self.liberties[root1] |= self.liberties[root2]
self.stones[root1] |= self.stones[root2]

# Remover su propia posicion de libertades (uno no es su propia libertad)
for stone_idx in self.stones[root2]:
self.liberties[root1].discard(stone_idx)

Calculo de Libertades

Calcular el numero de libertades de una cadena es una de las operaciones mas comunes en Go:

def count_liberties(self, row, col, board):
"""Calcular el numero de libertades de la cadena a la que pertenece una piedra"""
idx = self._index(row, col)
root = self.find(idx)
return len(self.liberties[root])

Usando Union-Find, esta operacion es O(1) (asumiendo que find es O(1)).


Evolucion de la Codificacion de Caracteristicas

Desde caracteristicas manuales tradicionales hasta las caracteristicas simplificadas de AlphaGo Zero, la codificacion de caracteristicas de la IA de Go ha experimentado una evolucion significativa.

Caracteristicas Manuales de GNU Go

Los primeros programas de Go (como GNU Go) usaban muchas caracteristicas disenadas manualmente:

def extract_features_gnugo(board, point):
"""
Extraccion de caracteristicas estilo GNU Go
"""
features = {}

# 1. Patrones de forma (formas locales predefinidas)
features['pattern'] = match_pattern(board, point)

# 2. Caracteristicas tacticas
features['can_capture'] = check_capture(board, point)
features['can_connect'] = check_connect(board, point)
features['creates_eye'] = check_eye_creation(board, point)

# 3. Caracteristicas locales
features['liberties_after'] = count_liberties_after_move(board, point)
features['enemy_liberties'] = count_enemy_liberties(board, point)

# 4. Caracteristicas globales
features['distance_to_edge'] = min(
point[0], point[1],
18 - point[0], 18 - point[1]
)

# ... mas caracteristicas

return features

Problemas con este enfoque:

  • Requiere mucho conocimiento de expertos humanos
  • Las caracteristicas pueden estar incompletas
  • Dificil descubrir nuevos patrones

Los 48 Planos de Caracteristicas de AlphaGo

AlphaGo (version Nature 2016) uso 48 planos de caracteristicas como entrada de la red neuronal:

def encode_alphago_features(board, player, history):
"""
48 planos de caracteristicas de AlphaGo 2016

Returns:
Tensor de (19, 19, 48)
"""
features = np.zeros((19, 19, 48), dtype=np.float32)

# Plano 1: Posiciones de piedras negras
features[:, :, 0] = (board == BLACK)

# Plano 2: Posiciones de piedras blancas
features[:, :, 1] = (board == WHITE)

# Plano 3: Puntos vacios
features[:, :, 2] = (board == EMPTY)

# Plano 4: Todo unos (termino de sesgo)
features[:, :, 3] = 1

# Planos 5-12: Codificacion del numero de libertades (un plano para 1-8 libertades)
for i, num_libs in enumerate(range(1, 9)):
for r in range(19):
for c in range(19):
if board[r, c] != EMPTY:
libs = count_liberties(board, r, c)
if libs == num_libs:
features[r, c, 4 + i] = 1

# Planos 13-20: Libertades del oponente despues de captura
# ...

# Planos 21-28: Nuestras libertades despues de jugar
# ...

# Planos 29-36: Historial de los ultimos 8 movimientos (un plano por movimiento)
for i, (r, c) in enumerate(history[-8:]):
features[r, c, 28 + i] = 1

# Planos 37-44: Juicio de escalera
# ...

# Planos 45-48: Relacionados con simetria
# ...

# Plano 49: A quien le toca jugar (todo 1 para negro, todo 0 para blanco)
if player == BLACK:
features[:, :, 47] = 1

return features

Clasificacion de los 48 Planos

CategoriaNumero de planosDescripcion
Posiciones de piedras3Piedras negras, piedras blancas, puntos vacios
Constante1Todo unos
Numero de libertades81-8 libertades
Libertades despues de captura8Libertades asumiendo que jugamos
Libertades despues de jugar8Libertades asumiendo que jugamos
Historial8Ultimos 8 movimientos
Escalera8Analisis complejo de escalera
Otros4Turno, simetria, etc.

Los 17 Planos de Caracteristicas de AlphaGo Zero

AlphaGo Zero (2017) simplifico drasticamente la codificacion de caracteristicas, usando solo 17 planos:

def encode_alphago_zero_features(board_history, player):
"""
17 planos de caracteristicas de AlphaGo Zero

Args:
board_history: Ultimas 8 posiciones (incluyendo la actual)
player: Jugador actual

Returns:
Tensor de (19, 19, 17)
"""
features = np.zeros((19, 19, 17), dtype=np.float32)

# Planos 1-8: Historial de piedras negras (ultimos 8 pasos)
for i, board in enumerate(board_history[-8:]):
features[:, :, i] = (board == BLACK)

# Planos 9-16: Historial de piedras blancas (ultimos 8 pasos)
for i, board in enumerate(board_history[-8:]):
features[:, :, 8 + i] = (board == WHITE)

# Plano 17: A quien le toca jugar (todo 1 para negro, todo 0 para blanco)
if player == BLACK:
features[:, :, 16] = 1

return features

La Simplicidad de los 17 Planos

CategoriaNumero de planosDescripcion
Historial de piedras negras8Posiciones de piedras negras en los ultimos 8 pasos
Historial de piedras blancas8Posiciones de piedras blancas en los ultimos 8 pasos
Jugador actual1Todo unos o todo ceros

¿Por que puede ser tan simple?

Idea clave: Dejar que la red neuronal aprenda las caracteristicas por si misma.

AlphaGo Zero demostro que:

  • No se necesita calcular manualmente el numero de libertades (la red puede inferirlo de las posiciones de las piedras)
  • No se necesita analisis de escalera (la red puede aprender a reconocer escaleras)
  • No se necesita prediccion compleja de capturas (la red puede entender las consecuencias de los movimientos)

Esto refleja la ventaja central del aprendizaje profundo: aprendizaje de extremo a extremo.

Visualizacion de Planos de Caracteristicas

Veamos como lucen realmente estos planos de caracteristicas:

Tablero original:         Plano de negras:        Plano de blancas:
. . . . . . 0 0 0 0 0 0 0 0 0 0 0 0
. ● ○ . . . 0 1 0 0 0 0 0 0 1 0 0 0
. . ● ○ . . -> 0 0 1 0 0 0 + 0 0 0 1 0 0
. . . ● . . 0 0 0 1 0 0 0 0 0 0 0 0
. . . . . . 0 0 0 0 0 0 0 0 0 0 0 0

Cada plano es una matriz binaria (0 o 1), la red neuronal puede extraer patrones de estos planos a traves de operaciones de convolucion.


Procesamiento de Simetria: 8 Transformaciones

Simetrias del Go

El tablero de Go tiene 8 simetrias:

  1. 4 rotaciones: 0°, 90°, 180°, 270°
  2. 4 reflexiones con rotacion: Reflexion horizontal + 4 rotaciones

Esto forma un grupo diedrico D4.

Representacion Matematica

Sea (x, y) una coordenada en el tablero (centro en (9, 9)), las 8 transformaciones pueden expresarse como:

TransformacionFormula
Identidad(x, y)
Rotacion 90°(-y, x)
Rotacion 180°(-x, -y)
Rotacion 270°(y, -x)
Reflexion horizontal(-x, y)
Reflexion vertical(x, -y)
Reflexion diagonal(y, x)
Reflexion anti-diagonal(-y, -x)

Implementacion del Codigo

def get_symmetries(board):
"""
Obtener las 8 transformaciones simetricas del tablero

Returns:
Lista de 8 tableros
"""
symmetries = []

# Original
symmetries.append(board.copy())

# Rotacion 90°
symmetries.append(np.rot90(board, k=1))

# Rotacion 180°
symmetries.append(np.rot90(board, k=2))

# Rotacion 270°
symmetries.append(np.rot90(board, k=3))

# Reflexion horizontal
symmetries.append(np.fliplr(board))

# Reflexion vertical
symmetries.append(np.flipud(board))

# Reflexion diagonal
symmetries.append(board.T)

# Reflexion anti-diagonal
symmetries.append(np.rot90(board.T, k=2))

return symmetries


def apply_symmetry(board, sym_index):
"""
Aplicar la transformacion de simetria sym_index
"""
return get_symmetries(board)[sym_index]


def inverse_symmetry(move, sym_index, board_size=19):
"""
Convertir coordenadas de movimiento transformadas de vuelta a coordenadas originales
"""
row, col = move

if sym_index == 0: # Identidad
return row, col
elif sym_index == 1: # Rotacion 90°
return col, board_size - 1 - row
elif sym_index == 2: # Rotacion 180°
return board_size - 1 - row, board_size - 1 - col
elif sym_index == 3: # Rotacion 270°
return board_size - 1 - col, row
elif sym_index == 4: # Reflexion horizontal
return row, board_size - 1 - col
elif sym_index == 5: # Reflexion vertical
return board_size - 1 - row, col
elif sym_index == 6: # Reflexion diagonal
return col, row
elif sym_index == 7: # Reflexion anti-diagonal
return board_size - 1 - col, board_size - 1 - row

Aumento de Datos

Al entrenar redes neuronales, se puede usar la simetria para aumento de datos:

def augment_training_data(board, policy, value):
"""
Expandir una muestra de entrenamiento a 8
"""
augmented = []

for sym_index in range(8):
# Transformar tablero
aug_board = apply_symmetry(board, sym_index)

# Transformar politica (vector de 361 dimensiones)
policy_2d = policy.reshape(19, 19)
aug_policy_2d = apply_symmetry(policy_2d, sym_index)
aug_policy = aug_policy_2d.flatten()

# El valor no cambia
aug_value = value

augmented.append((aug_board, aug_policy, aug_value))

return augmented

Esto aumenta los datos de entrenamiento 8 veces, y es completamente gratis (no necesita recopilar nuevos datos).

Uso de Simetria en Inferencia

AlphaGo tambien usa simetria en partidas reales:

def predict_with_symmetry(model, board):
"""
Usar simetria para mejorar la prediccion
"""
policies = []
values = []

for sym_index in range(8):
# Transformar entrada
aug_board = apply_symmetry(board, sym_index)

# Prediccion de red neuronal
policy, value = model.predict(aug_board)

# Transformar politica de vuelta al sistema de coordenadas original
policy_2d = policy.reshape(19, 19)
original_policy = inverse_symmetry_2d(policy_2d, sym_index)
policies.append(original_policy.flatten())

values.append(value)

# Promediar todas las predicciones
avg_policy = np.mean(policies, axis=0)
avg_value = np.mean(values)

return avg_policy, avg_value

Este enfoque puede mejorar ligeramente la precision y estabilidad de las predicciones.


Resumen de Metodos de Representacion del Tablero

MetodoUsoComplejidadCaracteristicas
Matriz bidimensionalAlmacenamiento basicoEspacio O(361)Simple e intuitivo
Zobrist HashingIdentificacion de posicionesConsulta O(1)Hash eficiente
Union-FindGestion de cadenasOperacion O(α(n))Maneja conectividad
Planos de caracteristicasEntrada de red neuronalO(361×num_planos)Codifica informacion rica
Transformaciones de simetriaAumento de datos8× volumen de datosAmpliacion gratuita

Estas tecnicas trabajan juntas para formar la infraestructura de la IA de Go moderna.


Correspondencia con Animaciones

Conceptos centrales cubiertos en este articulo y numeros de animacion:

NumeroConceptoCorrespondencia Fisica/Matematica
🎬 A1Representacion con matriz bidimensionalMatrices, datos dispersos
🎬 A2Zobrist HashingFunciones hash, operacion XOR
🎬 A8Codificacion de planos de caracteristicasTensores, entrada multicanal
🎬 A5Procesamiento de simetriaTeoria de grupos, grupo diedrico

Lecturas Adicionales


Referencias

  1. Zobrist, A. L. (1970). "A new hashing method with application for game playing." ICCA journal.
  2. Tarjan, R. E. (1975). "Efficiency of a Good But Not Linear Set Union Algorithm." Journal of the ACM, 22(2), 215-225.
  3. Silver, D., et al. (2016). "Mastering the game of Go with deep neural networks and tree search." Nature, 529, 484-489. - 48 planos de caracteristicas de AlphaGo
  4. Silver, D., et al. (2017). "Mastering the game of Go without human knowledge." Nature, 550, 354-359. - 17 planos de caracteristicas de AlphaGo Zero