Análisis de ajedrez con información limitada

19

En este desafío, se le da una cantidad limitada de información sobre un juego de ajedrez en particular, y debe predecir quién ganó el juego .

Te dan dos conjuntos de datos:

  1. La pieza cuenta (qué piezas siguen vivas)
  2. Colores del tablero (el color de las piezas en el tablero)

Más importante aún, que no sabe dónde están ubicadas las piezas . Debes determinar quién crees que ganará.

Los juegos se seleccionan de todos los eventos enumerados en PGNMentor desde 2010 hasta ahora. He seleccionado el 10% de todas las posiciones del tablero de cada juego que termina en una victoria o una pérdida. Las posiciones del tablero siempre serán al menos 30 movimientos en el juego. Los casos de prueba se pueden encontrar aquí . (Las ganancias blancas se enumeran primero, seguidas de las ganancias negras)

Entrada

El recuento de piezas será una cadena que consta de un personaje para cada pieza: king, queen, rook, k night, bishop o pawn. Minúsculas significa negro, mayúsculas es blanco. El tablero será una cadena de 64 caracteres (8 filas por 8 columnas). Brepresenta una pieza negra, Wrepresenta una pieza blanca y .representa un lugar vacío. Muestra:

W..WB......W.BB....W..B..W.WWBBB..W...B....W..BBWWW...BB.W....B.,BBKPPPPPPPQRRbbkpppppppqrr

representaría el siguiente tablero

...B.BB.
.BBBBBBB
.B.B....
B..W....
WWWW.W..
....W.W.
...W..WW
W.....W.

y donde ambos colores tienen 2 obispos, 1 rey, 7 peones, 1 reina, 2 torres

Salida

Debe devolver un número de coma flotante entre 0 y 1 (inclusive) para determinar qué tan probable es que gane el blanco. Muestra:

0.3     (30% chance that white wins)

Más detalles:

  • Cada caso de prueba vale 1 punto. Su puntaje será 1 - (1-Output)^2si el blanco gana, o 1 - (Output)^2si el negro gana.
  • Su puntaje final será la suma en todos los casos de prueba.
  • Si siento que los envíos están codificando la entrada, me reservo el derecho de cambiar los casos de prueba. (Si los cambio, tendrán el hash SHA-256 893be4425529f40bb9a0a7632f7a268a087ea00b0eb68293d6c599c6c671cdee)
  • Su programa debe ejecutar casos de prueba de forma independiente. No guardar información de un caso de prueba al siguiente.
  • Si está utilizando el aprendizaje automático, le recomiendo encarecidamente entrenar el primer 80% de los datos y realizar pruebas con el 20% restante . (O cualquier porcentaje que use). Utilizo los juegos varias veces en los datos, pero organizo los mismos juegos de forma secuencial.
  • ACTUALIZACIÓN: he agregado más de un millón de casos de prueba con fines de prueba y aprendizaje. Se dividen en partes en blanco y negro debido a los límites de tamaño de repositorio de github.

¡Buena suerte y diviertete!

Nathan Merrill
fuente
Esta conversación se ha movido al chat .
Dennis
¿Los nuevos casos de prueba contienen los antiguos o los dos conjuntos son disjuntos?
Fatalize
No tengo idea. Los obtuve de diferentes sitios, por lo que es posible que ambos incluyan el mismo conjunto de juegos.
Nathan Merrill

Respuestas:

8

Java 8 + Weka, 6413 puntos, 94.5%

Esta respuesta utiliza un enfoque de aprendizaje automático. Necesita recuperar la biblioteca Weka , notablemente weka.jary PackageManager.jar.

Aquí, uso un perceptrón multicapa como clasificador; puede reemplazar mlpcon cualquier Classifierclase de Weka para comparar resultados.

No he jugado mucho con los parámetros del MLP, y simplemente los analicé (una capa oculta de 50 neuronas, 100 épocas, 0.2 tasa de aprendizaje, 0.1 ímpetu).

Umbralizo el valor de salida del MLP, por lo que la salida realmente es 1 o 0 como se define en el desafío. De esa forma, el número de instancias clasificadas correctamente, según lo impreso por Weka, es directamente nuestro puntaje.

Construcción de vectores de características

Convierto cada instancia de una cadena a un vector de 76 elementos, donde:

  • Los primeros 64 elementos representan las celdas del tablero, en el mismo orden que en la cadena, donde 1es una pieza blanca, -1es una pieza negra y 0es una celda vacía.
  • Los últimos 12 elementos representan cada tipo de pieza (6 por jugador); el valor de esos elementos es el número de piezas de ese tipo en el tablero ( 0siendo "ninguna pieza de ese tipo"). Uno podría aplicar la normalización para reajustar esos valores entre -1 y 1, pero esto probablemente no sea muy útil aquí.

Número de instancias de entrenamiento

Si uso todos los casos de prueba para entrenar mi clasificador, he logrado obtener 6694 (es decir, 98.6588%) instancias correctamente clasificadas . Obviamente, esto no es sorprendente porque probar un modelo con los mismos datos que usó para entrenarlo es demasiado fácil (porque en ese caso es realmente bueno que el modelo se sobreajuste).

Usando un subconjunto aleatorio del 80% de las instancias como datos de entrenamiento, obtenemos la cifra de instancias clasificadas correctamente de 6413 (es decir, 94.5173%) informadas en el encabezado (por supuesto, dado que el subconjunto es aleatorio, puede obtener resultados ligeramente diferentes). Estoy seguro de que el modelo funcionaría decentemente bien en nuevos datos, porque las pruebas en el 20% restante de las instancias (que no se utilizaron para la capacitación) dan una clasificación correcta del 77.0818% , lo que demuestra que los modelos se generalizan decentemente bien (suponiendo que los casos que se nos dan aquí son representativos de los nuevos casos de prueba que se nos darían).

Usando la mitad de las instancias para el entrenamiento y la otra mitad para las pruebas, obtenemos el 86.7502% en los datos de entrenamiento y prueba, y el 74.4988% solo en los datos de la prueba.

Implementación

Como he dicho, este código requiere weka.jary PackageManager.jarde Weka.

Se puede controlar el porcentaje de datos utilizados en el conjunto de entrenamiento con TRAIN_PERCENTAGE.

Los parámetros de MLP se pueden cambiar justo debajo TRAIN_PERCENTAGE. Uno puede probar otros clasificadores de Weka (por ejemplo, SMOpara SVM) simplemente reemplazando mlpcon otro clasificador.

Este programa imprime en conjuntos de resultados, el primero en todo el conjunto (incluidos los datos utilizados para el entrenamiento), que es el puntaje definido en este desafío, y el segundo en solo los datos que no se utilizaron para el entrenamiento.

Uno ingresa los datos pasando la ruta del archivo que los contiene como argumento al programa.

import weka.classifiers.Classifier;
import weka.classifiers.Evaluation;
import weka.classifiers.functions.MultilayerPerceptron;
import weka.core.Attribute;
import weka.core.DenseInstance;
import weka.core.Instance;
import weka.core.Instances;

import java.io.BufferedReader;
import java.io.FileReader;
import java.util.ArrayList;

public class Test {

    public static void main(String[] arg) {

        final double TRAIN_PERCENTAGE = 0.5;

        final String HIDDEN_LAYERS = "50";
        final int NB_EPOCHS = 100;
        final double LEARNING_RATE = 0.2;
        final double MOMENTUM = 0.1;

        Instances instances = parseInstances(arg[0]);
        instances.randomize(new java.util.Random(0));
        Instances trainingSet = new Instances(instances, 0, (int) Math.floor(instances.size() * TRAIN_PERCENTAGE));
        Instances testingSet = new Instances(instances, (int) Math.ceil(instances.size() * TRAIN_PERCENTAGE), (instances.size() - (int) Math.ceil(instances.size() * TRAIN_PERCENTAGE)));

        Classifier mlp = new MultilayerPerceptron();
        ((MultilayerPerceptron) mlp).setHiddenLayers(HIDDEN_LAYERS);
        ((MultilayerPerceptron) mlp).setTrainingTime(NB_EPOCHS);
        ((MultilayerPerceptron) mlp).setLearningRate(LEARNING_RATE);
        ((MultilayerPerceptron) mlp).setMomentum(MOMENTUM);


        try {
            // Training phase
            mlp.buildClassifier(trainingSet);
            // Test phase
            System.out.println("### CHALLENGE SCORE ###");
            Evaluation test = new Evaluation(trainingSet);
            test.evaluateModel(mlp, instances);
            System.out.println(test.toSummaryString());
            System.out.println();
            System.out.println("### TEST SET SCORE ###");
            Evaluation test2 = new Evaluation(trainingSet);
            test2.evaluateModel(mlp, testingSet);
            System.out.println(test2.toSummaryString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Instances parseInstances(String filePath) {
        ArrayList<Attribute> attrs = new ArrayList<>(); // Instances constructor only accepts ArrayList
        for(int i = 0 ; i < 76 ; i++) {
            attrs.add(new Attribute("a" + String.valueOf(i)));
        }
        attrs.add(new Attribute("winner", new ArrayList<String>(){{this.add("white");this.add("black");}}));
        Instances instances = new Instances("Rel", attrs, 10);
        instances.setClassIndex(76);

        try {
            BufferedReader r = new BufferedReader(new FileReader(filePath));
            String line;
            String winner = "white";
            while((line = r.readLine()) != null) {
                if(line.equals("White:")) {
                    winner = "white";
                } else if(line.equals("Black:")) {
                    winner = "black";
                } else {
                    Instance instance = new DenseInstance(77);
                    instance.setValue(attrs.get(76), winner);
                    String[] values = line.split(",");
                    for(int i = 0 ; i < values[0].length() ; i++) {
                        if(values[0].charAt(i) == 'B') {
                            instance.setValue(attrs.get(i), -1);
                        } else if(values[0].charAt(i) == 'W') {
                            instance.setValue(attrs.get(i), 1);
                        } else {
                            instance.setValue(attrs.get(i), 0);
                        }
                    }
                    // Ugly as hell
                    instance.setValue(attrs.get(64), values[1].length() - values[1].replace("k", "").length());
                    instance.setValue(attrs.get(65), values[1].length() - values[1].replace("q", "").length());
                    instance.setValue(attrs.get(66), values[1].length() - values[1].replace("r", "").length());
                    instance.setValue(attrs.get(67), values[1].length() - values[1].replace("n", "").length());
                    instance.setValue(attrs.get(68), values[1].length() - values[1].replace("b", "").length());
                    instance.setValue(attrs.get(69), values[1].length() - values[1].replace("p", "").length());
                    instance.setValue(attrs.get(70), values[1].length() - values[1].replace("K", "").length());
                    instance.setValue(attrs.get(71), values[1].length() - values[1].replace("Q", "").length());
                    instance.setValue(attrs.get(72), values[1].length() - values[1].replace("R", "").length());
                    instance.setValue(attrs.get(73), values[1].length() - values[1].replace("N", "").length());
                    instance.setValue(attrs.get(74), values[1].length() - values[1].replace("B", "").length());
                    instance.setValue(attrs.get(75), values[1].length() - values[1].replace("P", "").length());

                    instances.add(instance);
                }
            }
        } catch (Exception e) { // who cares
            e.printStackTrace();
        }
        return instances;
    }
}
Fatalizar
fuente
¿Cómo codifica la entrada?
Nathan Merrill
@NathanMerrill No estoy seguro de entender tu pregunta
Fatalize
¿Cómo está pasando el caso de prueba como entrada a la red neuronal? ¿Estás pasando la cuerda cruda?
Nathan Merrill
@NathanMerrill Editado con un párrafo sobre la construcción del vector de características.
Fatalize
¿Cómo sabe Weka que está tratando de predecir el ganador?
user1502040
8

GNU sed + bc, 4336 5074.5 puntos, 64 75%

Actualización: el OP dio una nueva forma de calcular el puntaje de la predicción para un caso de prueba individual. Usando Wolfram Alpha , tracé ambos conjuntos de fórmulas para ver las diferencias.

La forma actual ofrece un fuerte incentivo para generar probabilidades reales, y no solo los extremos, 0 y 1, para los cuales las nuevas fórmulas dan el mismo puntaje máximo que antes. Es por eso que el algoritmo sin cambios a continuación, ahora tiene una mejor tasa de predicción, de hecho, una gran tasa dada su simplicidad.

Sin embargo, también hay un inconveniente asociado con las nuevas fórmulas, como se explica en 'Editar 1'.


Esta es una estimación simple basada solo en la ventaja / desventaja del material, ignorando la ubicación real de las piezas. Tenía curiosidad por cómo funcionará esto. La razón por la que uso sed, y no un lenguaje que pueda hacer esto en una línea, es porque es mi lenguaje esotérico favorito.

/:/d                                             # delete the two headers
s:.*,::                                          # delete board positions
s:$:;Q9,R5,B3,N3,P1,K0,q-9,r-5,b-3,n-3,p-1,k-0:  # add relative piece value table
:r                                               # begin replacement loop
s:([a-Z])((.*)\1([^,]+)):\4+\2:                  # table lookup: letter-value repl.
tr                                               # repeat till last piece
s:;.*::                                          # delete value table
s:.*:echo '&0'|bc:e                              # get material difference: bc call
/^0$/c0.5                                        # print potential draw score
/-/c0                                            # print potential black win score
c1                                               # print potential white win score

Valores de pieza estándar utilizados:

  • 9 - reina
  • 5 - Torre
  • 3 - Caballero
  • 3 - obispo
  • 1 - Peón
  • 0 - Rey

Calculo el material para ambos lados y resta el material negro del blanco. La salida para cada caso de prueba se basa en esa diferencia de la siguiente manera:

  • si diferencia> 0, entonces salida = 1 (potencial ganancia blanca)
  • si diferencia = 0, entonces salida = 0.5 (potencial de extracción).

Este es mi único resultado fraccional, de ahí la razón de la mejora como se explicó anteriormente.

  • si diferencia <0, entonces salida = 0 (potencial ganancia negra)

La tasa de predicción para este método fue del 64%. Ahora es el 75% con las nuevas fórmulas.

Inicialmente esperaba que fuera más alto, digamos 70%, pero como jugador de ajedrez puedo entender el resultado, ya que perdí muchos juegos cuando tenía +1 / +2, y gané tantos cuando estaba abajo en material. Se trata de la posición real. (Bueno, ahora tengo mi deseo!)

Edición 1: el inconveniente

La solución trivial es generar 0.5 para cada caso de prueba, porque de esta manera usted obtuvo medio punto independientemente de quién ganó. Para nuestros casos de prueba, esto significó un puntaje total de 3392.5 puntos (50%).

Pero con las nuevas fórmulas, 0.5 (que es una salida que daría si no está decidido quién gana) se convierte en 0.75 puntos. Recuerde que el puntaje máximo que puede recibir para un caso de prueba es 1, para una confianza del 100% en el ganador. Como tal, el nuevo puntaje total para una salida constante de 0.5 es 5088.75 puntos, ¡o 75%! En mi opinión, el incentivo es demasiado fuerte para este caso.

Esa puntuación es mejor, aunque marginalmente, que mi algoritmo basado en la ventaja material. La razón de esto es porque el algoritmo da una probabilidad de 1 o 0 (sin incentivo), ganancias o pérdidas supuestas, más veces (3831) de lo que da 0.5 (incentivo), sorteos asumidos (2954). El método es simple al final y, como tal, no tiene un alto porcentaje de respuestas correctas. El impulso de la nueva fórmula a 0.5 constante, logra alcanzar ese porcentaje, artificialmente.

Edición 2:

Es un hecho conocido, mencionado en los libros de ajedrez, que generalmente es mejor tener una pareja de obispos que una pareja de caballeros. Esto es especialmente cierto en la etapa media a final del juego, donde se encuentran los casos de prueba, ya que es más probable que tenga una posición abierta donde se aumenta el rango de un obispo.

Por lo tanto, hice una segunda prueba, pero esta vez reemplacé el valor de los obispos de 3 a 3.5. El valor del caballero se mantuvo 3. Esta es una preferencia personal, por lo que no lo hice mi envío predeterminado. La puntuación total en este caso fue de 4411 puntos (65%). Solo se observó un aumento de 1 punto porcentual.

Con las nuevas fórmulas, el puntaje total es de 4835 puntos (71%). Ahora el obispo ponderado tiene un rendimiento inferior. Pero el efecto se explica porque el método ponderado ahora da incluso más victorias o pérdidas supuestas (5089) que los sorteos supuestos (1696).

seshoumara
fuente
1
+1 por proporcionar una solución de referencia razonable. También me preguntaba qué tan bien funcionaría esto.
Martin Ender
@ MartinDender Gracias. Mi idea de aumentar el valor del obispo, mencionada la última vez, produjo solo un aumento del 1% en la tasa de éxito (ver Actualización 2). Creo que los valores estándar incluyeron ese efecto después de todo.
seshoumara
Oye, según el comentario de xnor, ¿te importaría si cambio la puntuación para que sea la diferencia absoluta al cuadrado?
Nathan Merrill
1
Increíble. Además, gracias por responder! Siempre me preocupa que mis preguntas más difíciles nunca reciban una respuesta.
Nathan Merrill
@NathanMerrill Actualicé mi respuesta para usar la nueva puntuación según lo solicitado. Perdón por el largo análisis, pero tenía mucha curiosidad.
seshoumara
4

Python 3 - 84.6%, 5275 puntos en un conjunto de validación

Si hacemos trampa y usamos todos los datos, podemos lograr una precisión del 99,3% y una puntuación de 6408

Solo un MLP grande y simple con deserción usando Keras

import collections
import numpy as np
import random

import keras
from keras.models import Sequential
from keras.layers import Dense, Dropout
from keras.layers.noise import GaussianDropout
from keras.optimizers import Adam

np.random.seed(0)
random.seed(0)

def load_data():
    with open('test_cases.txt', 'r') as f:
        for line in f:
            yield line.split(',')

def parse_data(rows):
    black_pieces = "kpbkrq"
    white_pieces = black_pieces.upper()
    for i, row in enumerate(rows):
        if len(row) >= 2:
            board = row[0]
            board = np.array([1 if c == 'W' else -1 if c == 'B' else 0 for c in board], dtype=np.float32)
            pieces = row[1]
            counts = collections.Counter(pieces)
            white_counts = np.array([counts[c] for c in white_pieces], dtype=np.float32)
            black_counts = np.array([counts[c] for c in black_pieces], dtype=np.float32)
            yield (outcome, white_counts, black_counts, board)
        else:
            if 'White' in row[0]:
                outcome = 1
            else:
                outcome = 0

data = list(parse_data(load_data()))
random.shuffle(data)
data = list(zip(*data))
y = np.array(data[0])
x = list(zip(*data[1:]))
x = np.array([np.concatenate(xi) for xi in x])

i = len(y) // 10

x_test, x_train = x[:i], x[i:]
y_test, y_train = y[:i], y[i:]

model = Sequential()
model.add(Dense(512, activation='relu', input_shape=(76,)))
model.add(GaussianDropout(0.5))
model.add(Dense(512, activation='relu'))
model.add(GaussianDropout(0.5))
model.add(Dense(512, activation='relu'))
model.add(GaussianDropout(0.5))
model.add(Dense(1, activation='sigmoid'))

model.compile(loss='mean_squared_error', optimizer=Adam())

use_all_data = False

x_valid, y_valid = x_test, y_test

if use_all_data:
    x_train, y_train = x_test, y_test = x, y
    validation_data=None
else:
    validation_data=(x_test, y_test)

batch_size = 128

history = model.fit(x_train, y_train, batch_size=batch_size, epochs=50, verbose=1, validation_data=validation_data)

y_pred = model.predict_on_batch(x_test).flatten()
y_class = np.round(y_pred)
print("accuracy: ", np.sum(y_class == y_test) / len(y_test))

score = np.sum((y_pred - (1 - y_test)) ** 2) * (len(y) / len(y_test))
print("score: ", score)
user1502040
fuente
¿Cuántos datos utilizas para entrenar para obtener la cifra del 84,6%?
Fatalize
Usé una división 90-10 como se muestra en el código
user1502040
Oye, he agregado un montón más de casos de prueba si estás interesado.
Nathan Merrill
2

Python 3: 94,3% de precisión, 6447 puntos en un conjunto de validación del 20% de los datos

Utiliza 3 redes neuronales, un regresor de vecinos más cercano, un bosque aleatorio y un aumento de gradiente. Estas predicciones se combinan con un bosque aleatorio que también tiene acceso a los datos.

import collections
import numpy as np
import numpy.ma as ma
import random

import keras
from keras.models import Sequential
from keras.layers import Dense, Dropout, BatchNormalization, Activation, Conv2D, Flatten
from keras.layers.noise import GaussianDropout
from keras.callbacks import EarlyStopping
from keras.optimizers import Adam
from sklearn.neighbors import KNeighborsRegressor
from sklearn.ensemble import RandomForestRegressor
from sklearn.ensemble import GradientBoostingRegressor
import tensorflow

tensorflow.set_random_seed(1)
np.random.seed(1)
random.seed(1)

def load_data():
    with open('test_cases.txt', 'r') as f:
        for line in f:
            yield line.split(',')

def parse_data(rows):
    black_pieces = "kqrnbp"
    white_pieces = black_pieces.upper()
    for i, row in enumerate(rows):
        if len(row) >= 2:
            board = row[0]
            board = np.array([1 if c == 'W' else -1 if c == 'B' else 0 for c in board], dtype=np.float32)
            pieces = row[1]
            counts = collections.Counter(pieces)
            white_counts = np.array([counts[c] for c in white_pieces], dtype=np.float32)
            black_counts = np.array([counts[c] for c in black_pieces], dtype=np.float32)
            yield (outcome, white_counts, black_counts, board)
        else:
            if 'White' in row[0]:
                outcome = 1
            else:
                outcome = 0

data = list(parse_data(load_data()))
random.shuffle(data)
data = list(zip(*data))
y = np.array(data[0])
x = list(zip(*data[1:]))
conv_x = []
for white_counts, black_counts, board in x:
    board = board.reshape((1, 8, 8))
    white_board = board > 0
    black_board = board < 0
    counts = [white_counts, black_counts]
    for i, c in enumerate(counts):
        n = c.shape[0]
        counts[i] = np.tile(c, 64).reshape(n, 8, 8)
    features = np.concatenate([white_board, black_board] + counts, axis=0)
    conv_x.append(features)
conv_x = np.array(conv_x)
x = np.array([np.concatenate(xi) for xi in x])
s = x.std(axis=0)
u = x.mean(axis=0)
nz = s != 0
x = x[:,nz]
u = u[nz]
s = s[nz]
x = (x - u) / s

i = 2 * len(y) // 10

x_test, x_train = x[:i], x[i:]
conv_x_test, conv_x_train = conv_x[:i], conv_x[i:]
y_test, y_train = y[:i], y[i:]

model = Sequential()

def conv(n, w=3, shape=None):
    if shape is None:
        model.add(Conv2D(n, w, padding="same"))
    else:
        model.add(Conv2D(n, w, padding="same", input_shape=shape))
    model.add(BatchNormalization())
    model.add(Activation('relu'))

conv(128, shape=conv_x[0].shape) 
conv(128)
conv(128)
conv(128)
conv(128)
conv(128)
conv(128)
conv(128)
conv(128)
conv(128)
conv(2, w=1)
model.add(Flatten())
model.add(GaussianDropout(0.5))
model.add(Dense(256))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(GaussianDropout(0.5))
model.add(Dense(1))
model.add(BatchNormalization())
model.add(Activation('sigmoid'))

model.compile(loss='mse', optimizer=Adam())

model5 = model

model = Sequential()
model.add(Dense(50, input_shape=(x.shape[1],)))
model.add(Activation('sigmoid'))
model.add(Dense(1))
model.add(Activation('sigmoid'))

model.compile(loss='mse', optimizer=Adam())

model0 = model

model = Sequential()
model.add(Dense(1024, input_shape=(x.shape[1],)))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(GaussianDropout(0.5))
model.add(Dense(1024))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(GaussianDropout(0.5))
model.add(Dense(1024))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(GaussianDropout(0.5))
model.add(Dense(1))
model.add(Activation('sigmoid'))

model.compile(loss='mse', optimizer=Adam())

model4 = model

use_all_data = False

x_valid, y_valid = x_test, y_test

if use_all_data:
    x_train, y_train = x_test, y_test = x, y
    validation_data=None
else:
    validation_data=(x_test, y_test)

def subsample(x, y, p=0.9, keep_rest=False):
    m = np.random.binomial(1, p, size=len(y)).astype(np.bool)
    r = (x[m,:], y[m])
    if not keep_rest:
        return r
    m = ~m
    return r + (x[m,:], y[m])

epochs=100

x0, y0, x_valid, y_valid = subsample(conv_x_train, y_train, keep_rest=True)
model5.fit(x0, y0, epochs=epochs, verbose=1, validation_data=(x_valid, y_valid), callbacks=[EarlyStopping(patience=1)])

x0, y0, x_valid, y_valid = subsample(x_train, y_train, keep_rest=True)
model0.fit(x0, y0, epochs=epochs, verbose=1, validation_data=(x_valid, y_valid), callbacks=[EarlyStopping(patience=1)])

x0, y0, x_valid, y_valid = subsample(x_train, y_train, keep_rest=True)
model4.fit(x0, y0, epochs=epochs, verbose=1, validation_data=(x_valid, y_valid), callbacks=[EarlyStopping(patience=1)])

model1 = RandomForestRegressor(n_estimators=400, n_jobs=-1, verbose=1)
model1.fit(*subsample(x_train, y_train))

model2 = GradientBoostingRegressor(learning_rate=0.2, n_estimators=5000, verbose=1)
model2.fit(*subsample(x_train, y_train))

model3 = KNeighborsRegressor(n_neighbors=2, weights='distance', p=1)
model3.fit(*subsample(x_train, y_train))

models = (model0, model1, model2, model3, model4, model5)

model_names = [
    "shallow neural net",
    "random forest",
    "gradient boosting",
    "k-nearest neighbors",
    "deep neural net",
    "conv-net",
    "ensemble"
]

def combine(predictions):
    clip = lambda x: np.clip(x, 0, 1)
    return clip(np.array([y.flatten() for y in predictions]).T)

def augment(x, conv_x):
    p = combine([m.predict(x) for m in models[:-1]] + [models[-1].predict(conv_x)])
    return np.concatenate((x, p), axis=1)

model = RandomForestRegressor(n_estimators=200, n_jobs=-1, verbose=1)
model.fit(augment(x_train, conv_x_train), y_train)

def accuracy(prediction):
    class_prediction = np.where(prediction > 0.5, 1, 0)
    return np.sum(class_prediction == y_test) / len(y_test)

predictions = [m.predict(x_test).flatten() for m in models[:-1]] + [models[-1].predict(conv_x_test).flatten()]+ [model.predict(augment(x_test, conv_x_test))]

for s, p in zip(model_names, predictions):
    print(s + " accuracy: ", accuracy(p))

def evaluate(prediction):
    return np.sum(1 - (prediction - y_test) ** 2) * (len(y) / len(y_test))

for s, p in zip(model_names, predictions):
    print(s + " score: ", evaluate(p))
user1502040
fuente
Oye, he agregado un montón más de casos de prueba si estás interesado.
Nathan Merrill
Woah, saliste en esto.
Robert Fraser
Tenga en cuenta que la respuesta de Java aquí que "supera" la suya parece informar% sobre el conjunto de datos completo y solo obtiene 77% sobre los datos con los que no entrenó.
Robert Fraser
0

Python 3 - 4353.25 / 6785 puntos - 64%

Así que trabajé en esto principalmente ayer. Mi primera publicación de golf, y solo he estado usando Python aproximadamente una semana, así que perdóname si no todo está optimizado.

def GetWhiteWinPercent(a):
finalWhiteWinPercent=0
i=a.index(',')

#position
board=a[:i]
blackBoardScore=0
whiteBoardScore=0
for r in range(i):
    if board[r] == 'B': blackBoardScore += abs(7 - (r % 8))
    if board[r] == 'W': whiteBoardScore += r % 8
if   whiteBoardScore > blackBoardScore: finalWhiteWinPercent += .5
elif whiteBoardScore < blackBoardScore: finalWhiteWinPercent += .0
else: finalWhiteWinPercent+=.25

#pieces
pieces=a[i:]
s = {'q':-9,'r':-5,'n':-3,'b':-3,'p':-1,'Q':9,'R':5,'N':3,'B':3,'P':1}
pieceScore = sum([s.get(z) for z in pieces if s.get(z) != None])
if   pieceScore < 0: finalWhiteWinPercent += 0
elif pieceScore > 0: finalWhiteWinPercent += .5
else: finalWhiteWinPercent += .25

return finalWhiteWinPercent

Terminé por el mismo camino que la respuesta de seshoumara para empezar. Pero la gran cantidad de casos de prueba que incluso contaban piezas me dejó insatisfecho.

Así que busqué en Google los rasgos que dictan quién está ganando en el ajedrez (no juego el juego yo mismo) y noté que la posición del tablero, específicamente el control central, es grande. Ahí es donde entra esta parte.

for r in range(i):
    if board[r] == 'B': blackBoardScore += abs(7 - (r % 8))
    if board[r] == 'W': whiteBoardScore += r % 8
if   whiteBoardScore > blackBoardScore: finalWhiteWinPercent += .5
elif whiteBoardScore < blackBoardScore: finalWhiteWinPercent += .0
else: finalWhiteWinPercent+=.25

Ambas mitades combinadas se usan para encontrar el puntaje (0.0, 0.25, 0.50, 0.75, 1.0)

Muy interesante que esta posición extra en el tablero no parece aumentar la posibilidad de adivinar el ganador.

Si coloca los casos de prueba en algunos archivos, aquí está la prueba.

whiteWins=0
blackWins=0
totalWins=0
for line in open('testcases2.txt','r'):
    totalWins += 1
    blackWins += 1 - GetWhiteWinPercent(line)
for line in open('testcases.txt','r'):
    totalWins += 1
    whiteWins += GetWhiteWinPercent(line)

print(str(whiteWins+blackWins) +'/'+str(totalWins))

Sé que esto no es un desafío de golf, ¡pero cualquier consejo o consejo al respecto es apreciado!

Flujo de datos
fuente
¿Mi respuesta? ¿Te refieres a la respuesta de seshoumara? Además, no necesita jugar golf (a menos que quiera). Este no es un desafío de código de golf .
Nathan Merrill
Puede guardar muchos bytes utilizando solo nombres de variables de un carácter. (Aunque realmente no importa porque esto no es código golf)
HyperNeutrino
Woops! Editando eso ahora. ¡En el trabajo, esto es lo que obtengo por rozar!
Datastream
2
Por favor no juegues al golf. Es mejor mantener el código legible cuando no es code-golf.
mbomb007
El control del medio del tablero no significa ocupar el medio del tablero, sino atacar el medio del tablero. Si desea agregar algo de complejidad en torno a eso, puede mejorar su puntaje.
No es que Charles