From a527a1f1a9f251db1dadb27a493bc51048c90b7d Mon Sep 17 00:00:00 2001 From: Orad Reshef Date: Wed, 23 Aug 2023 20:31:07 -0400 Subject: [PATCH 1/5] Reorganized python code into package structure --- README.md | 5 ++ training/pyproject.toml | 42 ++++++++++++ training/requirements-dev.txt | 4 ++ training/requirements.txt | 2 + training/src/train-on-rollout-data.py | 95 +++++++++++++++++++++++++++ training/train-on-rollout-data.py | 86 ------------------------ 6 files changed, 148 insertions(+), 86 deletions(-) create mode 100644 training/pyproject.toml create mode 100644 training/requirements-dev.txt create mode 100644 training/requirements.txt create mode 100644 training/src/train-on-rollout-data.py delete mode 100644 training/train-on-rollout-data.py diff --git a/README.md b/README.md index eeae972..1d6f6b3 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,11 @@ Currently, most needed is: - Documentation of backgammon metrics / neural net inputs: https://github.com/carsten-wenderdel/wildbg/issues/4 - Implementation of a bearoff database: https://github.com/carsten-wenderdel/wildbg/issues/1 +### Installation of python environment + +Make an editable install by targeting the training directory: +``` pip install -e "training/[dev]"``` + ## License Licensed under either of diff --git a/training/pyproject.toml b/training/pyproject.toml new file mode 100644 index 0000000..69824dd --- /dev/null +++ b/training/pyproject.toml @@ -0,0 +1,42 @@ +[project] +name = "wildbg" +description = "Rust-based backgammon engine based on neural networks." +readme = "README.md" +requires-python = ">=3.11" +license = {file = "../LICENSES-MIT"} +classifiers = [ + "Programming Language :: Python :: 3", + "Environment :: Console", + "Development Status :: 2 - Pre-Alpha", + "License :: OSI Approved :: MIT License", + "License :: OSI Approved :: Apache Software License", + "Natural Language :: English", + "Topic :: Games/Entertainment :: Board Games", + "Typing :: Typed" +] +dynamic = ["dependencies", "optional-dependencies", "version"] + +[tool.setuptools] +license-files = ["../LICENSES*"] + + +[project.urls] +Repository = "https://github.com/carsten-wenderdel/wildbg/" + +[tool.setuptools.packages.find] +where = ["src"] +exclude = ["test"] + +[tool.setuptools.dynamic] +dependencies = { file = ["requirements.txt"] } + +[tool.setuptools.dynamic.optional-dependencies] +dev = { file = ["requirements-dev.txt"] } + +[tool.ruff] +line-length = 100 +target-version = "py311" + +[tool.black] +line-length = 100 +target-version = ['py311'] diff --git a/training/requirements-dev.txt b/training/requirements-dev.txt new file mode 100644 index 0000000..c2e9ad2 --- /dev/null +++ b/training/requirements-dev.txt @@ -0,0 +1,4 @@ +black +docformatter +mypy +ruff \ No newline at end of file diff --git a/training/requirements.txt b/training/requirements.txt new file mode 100644 index 0000000..95e7545 --- /dev/null +++ b/training/requirements.txt @@ -0,0 +1,2 @@ +pandas +torch diff --git a/training/src/train-on-rollout-data.py b/training/src/train-on-rollout-data.py new file mode 100644 index 0000000..f1a7c67 --- /dev/null +++ b/training/src/train-on-rollout-data.py @@ -0,0 +1,95 @@ +from pathlib import Path + +import pandas as pd +import torch +from torch import nn +from torch.utils.data import DataLoader, Dataset + +# "mps" takes more time than "cpu" on Macs, so let's ignore it for now. +device = ( + "cuda" + if torch.cuda.is_available() + # else "mps" + # if torch.backends.mps.is_available() + else "cpu" +) +print(f"Using {device} device") + + +class WildBgDataSet(Dataset): + def __init__(self): + self.data = pd.read_csv("../training-data/rollouts.csv", sep=";") + + def __len__(self): + return self.data.shape[0] + + def __getitem__(self, idx: int): + # First 6 columns are outputs, last 202 columns are inputs + output = self.data.iloc[idx, 0:6] + input_ = self.data.iloc[idx, 6:208] + return torch.FloatTensor(input_).to(device), torch.FloatTensor(output).to(device) + + +class Network(nn.Module): + """Stolen from https://towardsdatascience.com/building-neural-network-using-pytorch-84f6e75f9a + Number of input/output adapted to our use case""" + + def __init__(self): + super().__init__() + + # Inputs to hidden layer linear transformation + self.hidden = nn.Linear(202, 150) + # Output layer, 6 outputs for win/lose - normal/gammon/bg + self.output = nn.Linear(150, 6) + + # Define sigmoid activation and softmax output + self.sigmoid = nn.Sigmoid() + self.softmax = nn.Softmax(dim=1) + + def forward(self, x): + """Pass the input tensor through each of our operations.""" + x = self.hidden(x) + x = self.sigmoid(x) + x = self.output(x) + x = self.softmax(x) + return x + + +def main(): + model = Network().to(device) + + traindata = WildBgDataSet() + trainloader = DataLoader(traindata, batch_size=64, shuffle=True) + + # Define loss function, L1Loss and MSELoss are good choices + criterion = nn.MSELoss() + + # Optimizer based on model, adjust the learning rate + optimizer = torch.optim.SGD(model.parameters(), lr=4.0) + + epochs = 20 + + for epoch in range(1, epochs + 1): + running_loss = 0.0 + for i, data in enumerate(trainloader, start=1): + inputs, labels = data + # set optimizer to zero grad to remove previous epoch gradients + optimizer.zero_grad() + # forward propagation + outputs = model(inputs) + loss = criterion(outputs, labels) + # backward propagation + loss.backward() + # optimize + optimizer.step() + running_loss += loss.item() + print(f"[{epoch}, {i:5d}] loss: {running_loss / 2000:.5f}") + + neural_net_path = Path("../neural-nets") + neural_net_path.mkdir(exist_ok=True) + dummy_input = torch.randn(1, 202, requires_grad=True, device=device) + model_onnx = torch.onnx.export(model, dummy_input, neural_net_path / "wildbg.onnx") + + +if __name__ == "__main__": + main() diff --git a/training/train-on-rollout-data.py b/training/train-on-rollout-data.py deleted file mode 100644 index 42ba5c3..0000000 --- a/training/train-on-rollout-data.py +++ /dev/null @@ -1,86 +0,0 @@ -from pathlib import Path -import torch -from torch import nn -from torch.utils.data import DataLoader -from torch.utils.data import Dataset -import pandas as pd - -# "mps" takes more time than "cpu" on Macs, so let's ignore it for now. -device = ( - "cuda" - if torch.cuda.is_available() - # else "mps" - # if torch.backends.mps.is_available() - else "cpu" -) -print(f"Using {device} device") - -class WildBgDataSet(Dataset): - def __init__(self): - self.data = pd.read_csv("../training-data/rollouts.csv", sep = ';') - - def __len__(self): - return self.data.shape[0] - - def __getitem__(self, idx): - # First 6 columns are outputs, last 202 columns are inputs - output = self.data.iloc[idx, 0:6] - input = self.data.iloc[idx, 6:208] - return torch.FloatTensor(input).to(device), torch.FloatTensor(output).to(device) - - -class Network(nn.Module): -# Stolen from https://towardsdatascience.com/building-neural-network-using-pytorch-84f6e75f9a -# Number of input/output adapted to our use case - def __init__(self): - super().__init__() - - # Inputs to hidden layer linear transformation - self.hidden = nn.Linear(202, 150) - # Output layer, 6 outputs for win/lose - normal/gammon/bg - self.output = nn.Linear(150, 6) - - # Define sigmoid activation and softmax output - self.activation = nn.Tanh() - self.softmax = nn.Softmax(dim=1) - - def forward(self, x): - # Pass the input tensor through each of our operations - x = self.hidden(x) - x = self.activation(x) - x = self.output(x) - x = self.softmax(x) - return x - -model = Network().to(device) - -traindata = WildBgDataSet() -trainloader = DataLoader(traindata, batch_size=64, shuffle=True) - -# Define loss function, L1Loss and MSELoss are good choices -criterion = nn.MSELoss() - -# Optimizer based on model, adjust the learning rate -optimizer = torch.optim.SGD(model.parameters(), lr=4.0) - -epochs = 20 - -for epoch in range(epochs): - running_loss = 0.0 - for i, data in enumerate(trainloader, 0): - inputs, labels = data - # set optimizer to zero grad to remove previous epoch gradients - optimizer.zero_grad() - # forward propagation - outputs = model(inputs) - loss = criterion(outputs, labels) - # backward propagation - loss.backward() - # optimize - optimizer.step() - running_loss += loss.item() - print(f'[{epoch + 1}, {i + 1:5d}] loss: {running_loss / 2000:.5f}') - -Path("../neural-nets").mkdir(exist_ok=True) -dummy_input = torch.randn(1, 202, requires_grad=True, device=device) -model_onnx = torch.onnx.export(model, dummy_input, "../neural-nets/wildbg.onnx") From 1d6f25c5b9bd4f601c56c21180b211eff8857821 Mon Sep 17 00:00:00 2001 From: Orad Reshef Date: Thu, 24 Aug 2023 10:31:06 -0400 Subject: [PATCH 2/5] Corrected link to readme --- training/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/training/pyproject.toml b/training/pyproject.toml index 69824dd..0e233e5 100644 --- a/training/pyproject.toml +++ b/training/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "wildbg" description = "Rust-based backgammon engine based on neural networks." -readme = "README.md" +readme = "../README.md" requires-python = ">=3.11" license = {file = "../LICENSES-MIT"} classifiers = [ From a51a14182dc1a61d12bfa152f55e30c37351a919 Mon Sep 17 00:00:00 2001 From: Orad Reshef Date: Sun, 27 Aug 2023 20:36:25 -0400 Subject: [PATCH 3/5] Revert to old version of training file for this PR --- training/src/train-on-rollout-data.py | 105 ++++++++++++-------------- 1 file changed, 48 insertions(+), 57 deletions(-) diff --git a/training/src/train-on-rollout-data.py b/training/src/train-on-rollout-data.py index f1a7c67..42ba5c3 100644 --- a/training/src/train-on-rollout-data.py +++ b/training/src/train-on-rollout-data.py @@ -1,9 +1,9 @@ from pathlib import Path - -import pandas as pd import torch from torch import nn -from torch.utils.data import DataLoader, Dataset +from torch.utils.data import DataLoader +from torch.utils.data import Dataset +import pandas as pd # "mps" takes more time than "cpu" on Macs, so let's ignore it for now. device = ( @@ -15,81 +15,72 @@ ) print(f"Using {device} device") - class WildBgDataSet(Dataset): def __init__(self): - self.data = pd.read_csv("../training-data/rollouts.csv", sep=";") + self.data = pd.read_csv("../training-data/rollouts.csv", sep = ';') def __len__(self): return self.data.shape[0] - def __getitem__(self, idx: int): + def __getitem__(self, idx): # First 6 columns are outputs, last 202 columns are inputs output = self.data.iloc[idx, 0:6] - input_ = self.data.iloc[idx, 6:208] - return torch.FloatTensor(input_).to(device), torch.FloatTensor(output).to(device) + input = self.data.iloc[idx, 6:208] + return torch.FloatTensor(input).to(device), torch.FloatTensor(output).to(device) class Network(nn.Module): - """Stolen from https://towardsdatascience.com/building-neural-network-using-pytorch-84f6e75f9a - Number of input/output adapted to our use case""" - +# Stolen from https://towardsdatascience.com/building-neural-network-using-pytorch-84f6e75f9a +# Number of input/output adapted to our use case def __init__(self): super().__init__() - + # Inputs to hidden layer linear transformation self.hidden = nn.Linear(202, 150) # Output layer, 6 outputs for win/lose - normal/gammon/bg self.output = nn.Linear(150, 6) - - # Define sigmoid activation and softmax output - self.sigmoid = nn.Sigmoid() + + # Define sigmoid activation and softmax output + self.activation = nn.Tanh() self.softmax = nn.Softmax(dim=1) - + def forward(self, x): - """Pass the input tensor through each of our operations.""" + # Pass the input tensor through each of our operations x = self.hidden(x) - x = self.sigmoid(x) + x = self.activation(x) x = self.output(x) x = self.softmax(x) return x - -def main(): - model = Network().to(device) - - traindata = WildBgDataSet() - trainloader = DataLoader(traindata, batch_size=64, shuffle=True) - - # Define loss function, L1Loss and MSELoss are good choices - criterion = nn.MSELoss() - - # Optimizer based on model, adjust the learning rate - optimizer = torch.optim.SGD(model.parameters(), lr=4.0) - - epochs = 20 - - for epoch in range(1, epochs + 1): - running_loss = 0.0 - for i, data in enumerate(trainloader, start=1): - inputs, labels = data - # set optimizer to zero grad to remove previous epoch gradients - optimizer.zero_grad() - # forward propagation - outputs = model(inputs) - loss = criterion(outputs, labels) - # backward propagation - loss.backward() - # optimize - optimizer.step() - running_loss += loss.item() - print(f"[{epoch}, {i:5d}] loss: {running_loss / 2000:.5f}") - - neural_net_path = Path("../neural-nets") - neural_net_path.mkdir(exist_ok=True) - dummy_input = torch.randn(1, 202, requires_grad=True, device=device) - model_onnx = torch.onnx.export(model, dummy_input, neural_net_path / "wildbg.onnx") - - -if __name__ == "__main__": - main() +model = Network().to(device) + +traindata = WildBgDataSet() +trainloader = DataLoader(traindata, batch_size=64, shuffle=True) + +# Define loss function, L1Loss and MSELoss are good choices +criterion = nn.MSELoss() + +# Optimizer based on model, adjust the learning rate +optimizer = torch.optim.SGD(model.parameters(), lr=4.0) + +epochs = 20 + +for epoch in range(epochs): + running_loss = 0.0 + for i, data in enumerate(trainloader, 0): + inputs, labels = data + # set optimizer to zero grad to remove previous epoch gradients + optimizer.zero_grad() + # forward propagation + outputs = model(inputs) + loss = criterion(outputs, labels) + # backward propagation + loss.backward() + # optimize + optimizer.step() + running_loss += loss.item() + print(f'[{epoch + 1}, {i + 1:5d}] loss: {running_loss / 2000:.5f}') + +Path("../neural-nets").mkdir(exist_ok=True) +dummy_input = torch.randn(1, 202, requires_grad=True, device=device) +model_onnx = torch.onnx.export(model, dummy_input, "../neural-nets/wildbg.onnx") From 0414d823bca6628a0f6fe3546b5ff97c7509caa9 Mon Sep 17 00:00:00 2001 From: Orad Reshef Date: Sun, 27 Aug 2023 21:42:49 -0400 Subject: [PATCH 4/5] Removed non mandatory fields --- training/pyproject.toml | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/training/pyproject.toml b/training/pyproject.toml index 0e233e5..6844517 100644 --- a/training/pyproject.toml +++ b/training/pyproject.toml @@ -1,19 +1,6 @@ [project] name = "wildbg" -description = "Rust-based backgammon engine based on neural networks." -readme = "../README.md" requires-python = ">=3.11" -license = {file = "../LICENSES-MIT"} -classifiers = [ - "Programming Language :: Python :: 3", - "Environment :: Console", - "Development Status :: 2 - Pre-Alpha", - "License :: OSI Approved :: MIT License", - "License :: OSI Approved :: Apache Software License", - "Natural Language :: English", - "Topic :: Games/Entertainment :: Board Games", - "Typing :: Typed" -] dynamic = ["dependencies", "optional-dependencies", "version"] [tool.setuptools] From a7d3a244fcf59166e782665703b0f0ba425b7b6d Mon Sep 17 00:00:00 2001 From: Orad Reshef Date: Sun, 27 Aug 2023 21:43:22 -0400 Subject: [PATCH 5/5] mend --- training/pyproject.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/training/pyproject.toml b/training/pyproject.toml index 6844517..5f81b14 100644 --- a/training/pyproject.toml +++ b/training/pyproject.toml @@ -3,9 +3,6 @@ name = "wildbg" requires-python = ">=3.11" dynamic = ["dependencies", "optional-dependencies", "version"] -[tool.setuptools] -license-files = ["../LICENSES*"] - [project.urls] Repository = "https://github.com/carsten-wenderdel/wildbg/"