Por qué falló al principio
El primer modelo respondía con demasiada frecuencia coordenadas de la esquina inferior derecha del tablero. Parecía falta de partidas, pero la causa real era el formato de entrada. Los datos de entrenamiento usaban un tablero alineado con números, mientras que el servidor real enviaba un tablero con un espaciado distinto. En concreto, a partir del punto en que los números de columna pasaban a tener dos cifras, el modelo leía la posición de las piedras desplazada una casilla.
El primer modelo, Gemma 4 E4B con LoRA, no acertó ninguna de las 16 preguntas de la prueba de coordenadas. Incluso después de cambiar a Qwen3-VL-8B, un adaptador entrenado brevemente con solo 400 ejemplos de jugadas cayó hasta el 2,1%. Por eso primero creamos un adaptador dedicado a las coordenadas y, sobre él, entrenamos las jugadas.

Paso 1: Fijar el ancho de las coordenadas
Las filas y columnas se escriben con dos cifras, como 00, 01, 02, en lugar de 0, 1, 2. Los puntos vacíos, las piedras negras y las piedras blancas se fijan también a un carácter cada uno, y el número de espacios de separación nunca cambia. Aunque la función que genera los datos de entrenamiento y la que genera las peticiones de las partidas reales estén separadas, lo más seguro es que compartan un único generador de la cadena final.
- Imprimir las cabeceras de columna de 00 a 18.
- Poner también un número de 00 a 18 delante de cada fila.
- Igualar el ancho de los símbolos de punto vacío, negras y blancas.
- Escribir el turno actual y las coordenadas prohibidas en una posición fija debajo del tablero.
Paso 2: Superar primero la prueba de coordenadas
Antes de entrenar la fuerza de juego, se coloca una piedra en un punto aleatorio y se pide al modelo que responda su coordenada. No pruebes solo el centro: crea el mismo número de preguntas en cada una de las cuatro zonas, arriba a la izquierda, arriba a la derecha, abajo a la izquierda y abajo a la derecha. Aunque la precisión global sea alta, si solo la zona inferior derecha es baja, en las partidas reales aparecerá un sesgo de dirección.
Incluso el Qwen3-VL-8B base tenía su precisión más baja de las cuatro zonas, un 66,7%, en la zona inferior derecha.

La prueba de coordenadas se vuelve a ejecutar cada vez que se reentrena el modelo o se cambia el prompt. Junto con la tasa de aciertos, se guarda también la dirección de las respuestas erróneas. Según si falla repetidamente por una casilla a la derecha o si intercambia filas y columnas, se pueden revisar por separado la alineación de la entrada y el analizador de la salida.
Paso 3: Acotar el formato de salida
Cuando el modelo responde con una explicación larga, el analizador de coordenadas se vuelve inestable. La salida normal se limita a una sola coordenada o a PASS. Usar en silencio solo la primera coordenada cuando viene acompañada de una explicación puede ocultar jugadas erróneas, así que se registra como error de formato y se vuelve a pedir solo un número fijo de veces.
Mientras no se asegure la precisión de las coordenadas, añadir más partidas solo puede reforzar el hábito de leer mal.
El código real de este paso
Este es el generador de la cadena del tablero, que ajusta a dos caracteres de ancho las cabeceras de columna, los números de fila y los símbolos de las piedras. Tanto la generación de datos de entrenamiento como el servidor real de recomendación de jugadas usan esta función, para que los formatos de entrada no se desalineen.
vlm_server/dataset_baduk.py
def board_text_aligned(cells: bytes, size: int = BOARD_SIZE) -> str:
"""v3 권장: 열·행·셀이 모두 두 글자 토큰으로 정렬.
셀 기호는 **왼쪽 정렬** (``X `` / ``. ``). 오른쪽 정렬(`` X``)은
모델이 열을 한 칸 왼쪽(col-1)으로 읽는 오차를 유발했다.
"""
col_header = " " + " ".join(f"{c:02d}" for c in range(size))
lines: List[str] = [col_header]
for r in range(size):
cells_s: List[str] = []
for c in range(size):
v = cells[r * size + c]
ch = "." if v == 0 else ("X" if v == 1 else "O")
cells_s.append(f"{ch:<2}")
lines.append(f"{r:02d} " + " ".join(cells_s))
return "\n".join(lines)La salida correcta del modelo se limita a una línea de JSON que contiene solo una coordenada o un pase.
vlm_server/dataset_baduk.py
def assistant_text(point: Optional[Tuple[int, int]]) -> str:
if point is None:
return '{"pass": true}'
r, c = point
return f'{{"row": {r}, "col": {c}}}'Modelo utilizado: Qwen3-VL-8B-Instruct + adaptador LoRA (lora_mix_qwen_r5). El código fuente completo y la descripción del modelo están en la entrada "8. El código fuente completo y los modelos utilizados".
COMMENTS · CHAPTER 03
Convertir el tablero de 19×19 en una entrada que la IA pueda leer 0 comentarios
Todavía no hay comentarios. Deja una pregunta o algo que quieras añadir.
Para publicar un comentario, empieza con Google y registra un apodo y un correo.
Empezar con Google