Шаховий аналіз з обмеженою інформацією


19

У цьому виклику вам надається обмежена кількість інформації про певну гру в шахи, і вам потрібно передбачити, хто виграв гру .

Вам надано два набори даних:

  1. Кількість штук (Які шматки ще живі)
  2. Кольори дошки (колір шматочків на дошці)

Що ще важливіше, ви не знаєте, де знаходяться шматки . Вам потрібно визначити, хто, на вашу думку, переможе.

Ігри вибираються з усіх подій, перелічених у PGNMentor з 2010 року по теперішній час. Я вибрав 10% усіх позицій на дошці з кожної гри, яка закінчується виграшем або програшем. Позиції на дошці завжди будуть не менше 30 ходів у грі. Тестові приклади можна знайти тут . (Білі перемоги перераховуються першими, за ними - чорні)

Вхідні дані

Відлік частина буде рядок , що складається з символу для кожної частини: kІнг, queen, rООК, до nIGHT, bishop або pтентом. Малі значить чорні, великі - білі. Дошка буде рядком з 64 символів (8 рядків по 8 стовпців). Bявляє собою чорний шматок, Wявляє собою білий шматок і .являє собою порожнє місце. Зразок:

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

буде представляти наступну раду

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

і де обидва кольори мають 2 єпископа, 1 короля, 7 пішаків, 1 королеву, 2 грака

Вихідні дані

Вам потрібно повернути число з плаваючою комою між 0 і 1 (включно), щоб визначити, наскільки ймовірно, що білий виграє. Зразок:

0.3     (30% chance that white wins)

Детальніше:

  • Кожен тестовий випадок коштує 1 бал. Ваш рахунок буде, 1 - (1-Output)^2якщо перемагає білий, або 1 - (Output)^2якщо перемагає чорний.
  • Ваша підсумкова оцінка буде сумою для всіх тестових випадків.
  • Якщо я вважаю, що подання жорстко кодує вхід, я залишаю за собою право змінювати тестові випадки. (Якщо я їх зміню, вони матимуть хеш SHA-256 893be4425529f40bb9a0a7632f7a268a087ea00b0eb68293d6c599c6c671cdee)
  • Ваша програма повинна запускати тестові справи самостійно. Немає збереження інформації від одного тестового випадку до іншого.
  • Якщо ви використовуєте машинне навчання, я настійно рекомендую тренуватися на перших 80% даних, а також тестувати, використовуючи решту 20% . (Або які відсотки ви використовуєте). Я використовую ігри декілька разів у даних, але ті ж ігри збираю послідовно.
  • ОНОВЛЕННЯ: Я додав понад мільйон тестових випадків для тестування та навчання. Вони розділені на чорні та білі частини через обмеження розміру github repo.

Хай щастить!



Чи містять нові тестові приклади старі, чи два комплекти непересічні?
Фаталізувати

Я поняття не маю. Я отримував їх з різних сайтів, тому можливо, вони обидва включили один і той же набір ігор.
Натан Меррілл

Відповіді:


8

Java 8 + Weka, 6413 балів, 94,5%

У цій відповіді використовується підхід машинного навчання. Вам потрібно отримати бібліотеку Weka , зокрема weka.jarта PackageManager.jar.

Тут я використовую багатошаровий персептрон як класифікатор; ви можете замінити mlpбудь-який Classifierклас Weka для порівняння результатів.

Я не дуже задумався з параметрами MLP, а просто наклав на них очі (один прихований шар з 50 нейронів, 100 епох, 0,2 ступінь навчання, 0,1 імпульс).

Я порогово визначає значення виходу MLP, тому результат дійсно дорівнює 1 або 0, як визначено в виклику. Таким чином, кількість правильно класифікованих екземплярів, надрукованих Weka, є безпосередньо нашою оцінкою.

Особливості векторної побудови

Я перетворюю кожен екземпляр з рядка до вектора 76 елементів, де:

  • Перші 64 елементи представляють комірки дошки в тому ж порядку, що і в рядку, де 1є білий шматок, -1є чорним шматочком і 0є порожньою коміркою.
  • Останні 12 елементів представляють кожен тип твору (6 на гравця); цінність цих елементів - це кількість фрагментів цього типу на дошці ( 0"жоден фрагмент цього типу"). Можна застосувати нормалізацію для відновлення цих значень від -1 до 1, але це, мабуть, не дуже корисно тут.

Кількість навчальних екземплярів

Якщо я використовую всі тестові випадки, призначені для навчання мого класифікатора, мені вдалося отримати 6694 (тобто 98,6588%) правильно класифікованих випадків . Це, очевидно, не дивно, тому що тестувати модель на тих самих даних, які ви використовували для тренувань, це занадто просто (адже в такому випадку насправді добре, що модель перевершує).

Використовуючи випадкові підмножини 80% екземплярів в якості навчальних даних, ми отримуємо 6413 (тобто 94,5173%) правильно класифікованих фігур екземплярів, повідомлених у заголовку (звичайно, оскільки підмножина є випадковим, ви можете отримати дещо інші результати). Я впевнений, що модель буде добре працювати над новими даними, оскільки тестування на решті 20% екземплярів (які не використовувались для навчання) дає 77,0818% правильної класифікації, що свідчить про те, що моделі узагальнюють гідно (якщо вважати випадки, які ми тут наводимо, є репрезентативними для нових тестових випадків, які ми б нам дали).

Використовуючи половину екземплярів для тренінгу, а іншу половину для тестування, ми отримуємо 86,7502% як на дані тренувань, так і на тестування, а 74,4988% - лише на дані тесту.

Впровадження

Як я вже казав, цей код вимагає weka.jarі PackageManager.jarвід Weka.

Можна контролювати відсоток даних, використовуваних у навчальному наборі TRAIN_PERCENTAGE.

Параметри MLP можна змінити трохи нижче TRAIN_PERCENTAGE. Можна спробувати інші класифікатори Weka (наприклад, SMOдля SVM), просто замінивши mlpїх іншим класифікатором.

Ця програма друкує набори результатів, перший з них - на весь набір (включаючи дані, що використовуються для тренувань), який є балом, визначеним у цьому виклику, а другий - лише на ті дані, які не використовувались для навчання.

Один вводить дані, передаючи шлях до файлу, що містить його як аргумент до програми.

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;
    }
}

Як ви кодуєте вхід?
Натан Меррілл

@NathanMerrill Я не впевнений, що розумію ваше запитання
Fatalize

Як ви передаєте тестовий випадок як вхід до нейронної мережі? Ви просто проходите в необробленому рядку?
Натан Меррілл

@NathanMerrill Відредаговано з абзацом про побудову векторних функцій.
Фаталізувати

Як ми знаємо, що ти намагаєшся передбачити переможця?
user1502040

8

GNU sed + bc, 4336 5074,5 балів, 64 75%

Оновлення: ОП дало новий спосіб розрахунку оцінки прогнозу для окремого тестового випадку. Використовуючи Wolfram Alpha , я побудував обидва набори формул, щоб побачити відмінності.

Нинішній спосіб приносить сильний стимул виводити фактичні ймовірності, а не лише крайнощі, 0 і 1, для яких нові формули дають той же максимальний бал, що і раніше. Ось чому незмінний алгоритм, наведений нижче, тепер має кращу швидкість прогнозування, насправді велику швидкість з огляду на його простоту.

Однак є і недолік, пов’язаний з новими формулами, як це пояснено у "Редагувати 1".


Це проста оцінка, що базується лише на матеріальній перевазі / недоліку, ігноруючи фактичне розміщення деталей. Мені було цікаво, як це виступить. Причина, що я використовую sed, а не якусь мову, яка може це зробити в одному рядку, полягає в тому, що це моя улюблена езотерична мова.

/:/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

Використовувані стандартні значення штук:

  • 9 - Королева
  • 5 - Грак
  • 3 - Лицар
  • 3 - єпископ
  • 1 - пішак
  • 0 - король

Я обчислюю матеріал для обох сторін і віднімаю матеріал чорного від білого. Вихід для кожного тестового випадку базується на цій різниці наступним чином:

  • якщо різниця> 0, то вихід = 1 (потенційний виграш білого)
  • якщо різниця = 0, то вихід = 0,5 (потенційний нічия).

Це мій єдиний дробовий результат, отже, причина поліпшення, як пояснено вище.

  • якщо різниця <0, то вихід = 0 (потенційний чорний виграш)

Швидкість прогнозування цього методу становила 64%. Зараз це 75% за новими формулами.

Спочатку я очікував, що він буде вище, скажімо, на 70%, але як шахіст я сам розумію результат, оскільки програв стільки ігор, коли був +1 / +2, і виграв стільки, коли я був вниз матеріал. Це все про фактичну позицію. (Ну, тепер я отримав своє бажання!)

Правка 1: недолік

Тривіальне рішення - вивести 0,5 для кожного тестового випадку, адже таким чином ви набрали пів бала незалежно від того, хто виграв. Для наших тестових випадків це означало загальну оцінку 3392,5 балів (50%).

Але за новими формулами 0,5 (що є результатом, який ви дасте, якщо ви не визначитесь, хто виграє) перетворюється на 0,75 бала. Пам'ятайте, що максимальний бал, який ви можете отримати за тестовий випадок, становить 1, на 100% впевненість у переможцю. Таким чином, новий загальний бал за постійний вихід 0,5 становить 5088,75 балів, або 75%! На мою думку, стимул занадто сильний для даного випадку.

Цей бал кращий, хоча незначно, ніж алгоритм, заснований на моїх матеріальних перевагах. Причина цього полягає в тому, що алгоритм дає ймовірність 1 або 0 (відсутність стимулу), припускаються виграші чи програші, більше разів (3831), ніж дає 0,5 (стимул), припущені нічиї (2954). Зрештою, метод простий, і як такий він не має високого відсотка правильних відповідей. Підвищення від нової формули до постійної 0,5 вдається штучно досягти цього відсотка.

Редагувати 2:

Відомий факт, згаданий у шахових книгах, що зазвичай краще мати єпископську пару, ніж рицарську пару. Особливо це стосується середньої та кінцевої стадії гри, де є тестові справи, оскільки, швидше за все, відкрита позиція, де діапазон єпископа збільшується.

Тому я зробив друге випробування, але цього разу я замінив значення єпископів з 3 на 3,5. Значення лицаря залишилося 3. Це особисте уподобання, тому я не зробив це моїм поданням за замовчуванням. Загальний бал у цьому випадку склав 4411 балів (65%). Спостерігалось лише збільшення на 1 відсотковий пункт.

За новими формулами загальний бал становить 4835 балів (71%). Тепер зважений єпископ не працює. Але ефект пояснюється тим, що тепер зважений метод дає ще більше припущених виграшів або програшів (5089), ніж припускається нічия (1696).


1
+1 за надання розумного базового рішення. Мені також було цікаво, наскільки це буде добре.
Мартін Ендер

@MartinEnder Дякую Моя ідея збільшити цінність єпископа, згадана минулого разу, призвела лише до зростання на 1% успішності (див. Оновлення 2). Я думаю, що стандартні значення все-таки включали цей ефект.
seshoumara

Гей, за коментарем xnor, ви б заперечували, якщо я зміню бал на абсолютну різницю у квадраті?
Натан Меррілл

1
Дивовижно. Також дякую за відповідь! Я завжди переживаю, що на мої більш жорсткі запитання ніколи не отримає відповіді.
Натан Меррілл

@NathanMerrill Я оновив свою відповідь, щоб використати нове бал за запитом. Вибачте за довгий аналіз, але мені було справді цікаво.
seshoumara

4

Python 3 - 84,6%, 5275 балів на валідаційному наборі

Якщо ми обдуримо і використаємо всі дані, ми зможемо досягти точності 99,3% і балу 6408

Просто простий великий MLP з випадом за допомогою 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)

Скільки даних ви використовуєте для тренувань, щоб отримати цифру 84,6%?
Фаталізувати

Я використав 90-10 спліт, як показано в коді
user1502040

Гей, я додав ще тонну тестових випадків, якщо вас цікавить.
Натан Меррілл

2

Python 3 - 94,3% точності, 6447 балів на валідаційному наборі 20% даних

Використовує 3 нейронні мережі, найближчий регресор сусідів, випадковий ліс та збільшення градієнта. Ці прогнози поєднуються з випадковим лісом, який також має доступ до даних.

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))

Гей, я додав ще тонну тестових випадків, якщо вас цікавить.
Натан Меррілл

Вау, ви вийшли на це.
Роберт Фрейзер

Зауважте, що у відповіді java, що "б'є" вашу, здається, повідомляє% про весь набір даних і отримує лише 77% за дані, з якими не тренувався.
Роберт Фрейзер

0

Python 3 - 4353,25 / 6785 балів - 64%

Тож я працював над цим переважно вчора. Мій перший пост з гольфу, і я використовую пітон лише тиждень або близько того, тому вибачте мене, якщо не все оптимізовано.

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

Я закінчився таким же шляхом, як і відповідь Seshoumara для початку. Але велика кількість тестових справ, які мали навіть кількість штук, залишила мене незадоволеною.

Тож я в googled рисах, які диктують, хто виграє в шахах (я сам не граю в гру), і помітив, що позиція на дошці, зокрема центральне управління, є великою. Ось де цей шматочок входить.

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

Обидві ці половинки у поєднанні використовуються для пошуку балу (0,0, 0,25, 0,50, 0,75, 1,0)

Дуже цікаво, що ця додаткова позиція на дошці зовсім не збільшує шансів здогадатися про переможця.

Якщо ви перекладете тестові приклади в деякі файли, ось тестування.

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))

Я знаю, що це не проблема для гольфу, але будь-які поради та поради з цього приводу цінуються!


Моя відповідь? Ви маєте на увазі відповідь seshoumara? Крім того, вам не потрібно займатися цим гольфом (якщо ви цього не хочете). Це не проблема з кодовим гольфом .
Натан Меррілл

Ви можете зберегти багато байт, використовуючи лише одноіменні імена змінних. (Хоча це насправді не має значення, оскільки це не кодовий гольф)
HyperNeutrino

Вупи! Відредагуйте це зараз. На роботі це те, що я отримую за скимінг!
Datastream

2
Будь ласка, не гольфуйте. Краще тримати код читабельним, коли це не код-гольф.
mbomb007

Контроль середини дошки означає не зайняття середини дошки, а напад на середину дошки. Якщо ви хочете додати певну складність навколо цього, це може покращити ваш результат.
Не те, що Чарльз
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.