OpenML - Machine Learning

OpenML

An Open Source Machine Learning module. This module supports Neural Networks. I plan to add more to this module.

OpenML is easy to use and understand, it was built for ease of use and beginner knowledge.
All usage cases and examples are explained as much as possible as I could for new programmers.

OpenML has a lot of Type Checking so when using functions or getting objects it should tell you what’s there.


What is Machine Learning
Its a type of Artificial Intelligence that uses algorithms to train models. It allows computer systems to learn and adapt without following explicit instructions. Machine Learning basically imitates the human brain features. Humans have Biological Neural Networks while computers have Artificial Neural Networks which is composed of algorithms.

To sum it up. Machine Learning is computers learning based on algorithms.


Why use this resource?
Create Computers/AI that can learn. It’s very easy to use. One line to create a neural network, 2 more lines to teach that network using Forward/Back Propagation. This module is very versatile.

I only will create algorithms that are actually needed for Roblox.
You want to teach an AI how to parkour then go ahead with, reinforcement learning.

OpenML is Open Source.


Upcoming
Use Parallel Lua, In-depth tutorial on how Machine Learning works. I will teach you every variable behind it. I will not tell you how to use my module I will teach you, so you understand what you can do with it.

To the people who are new to Machine Learning, I will create a introduction to it and teach about Machine Learning. I also will show you how to use OpenML with that knowledge.


Demos/Previews
All Demos are not up to latest OpenML module.

Preview of Network Learning Shapes (Rectangles and Triangles) (OpenML V1.1)

OpenML_NetworkLearnsShapes.rbxl (74.0 KB)

Preview of using Deep Q-Learning for Cars (OpenML V1.1)

OpenML_DQN_Showcase.rbxl (121.0 KB)

Preview of Deep Q-Learning for Sword Fighting AI (OpenML V1.1)

OpenMLSwordAI.rbxl (92.6 KB)


How to Use OpenML (Documentation) Updated to Version 1.2

Some Examples provide demos and videos.
Current Videos are in Deep Q-Learning and CNN

How To Create a Neural Network
local OpenML = require(game:GetService("ServerScriptService").OpenML)

local NeuralNetwork = OpenML.Resources.MLP.new({ 2, 3, 2 }, math.random)
-- Creates a Neural Network with 2 Input Nodes, 3 Hidden Nodes, and 2 Output Nodes
--[[
What the Network Looks like, I = Input, H = Hidden, O = Output
I   H   O
---------
O   O   O
    O
O   O   O

If you did
local NeuralNetwork = OpenML.Resources.MLP.new({ 2, 3, 3, 2 }, math.random)
You would have 2 Input Nodes, 3 Hidden Nodes, 3 Hidden Nodes, and 2 Output Nodes

What it would look like, I = Input, H = Hidden, O = Output

I   H  H   O
------------
O   O  O   O
    O  O
O   O  O   O
]]

Custom Initialization Values
local OpenML = require(game:GetService("ServerScriptService").OpenML)

local NeuralNetwork = OpenML.Resources.MLP.new({ 2, 3, 2 }, math.random)

local NeuralNetworkCustom = OpenML.Resources.MLP.new({ 2, 3, 2 }, function()
	return math.random() * 2 - 1 -- Returns a number between -1 and 1
end)

How to use Forward Propagation/Back Propagation
local OpenML = require(game:GetService("ServerScriptService").OpenML)

-- Get Propagator for forward and back propagation
local Propagator = OpenML.Algorithms.Propagator

-- Get our selected activation function
local ActivationFunction = OpenML.ActivationFunctions.TanH

-- Initialize The Network
local NeuralNetwork = OpenML.Resources.MLP.new({ 2, 3, 2 }, math.random)

local CalculateLoss = OpenML.Calculate.MSE
-- 20 iterations of training
for i = 1, 20 do
	local Inputs = { 1, 1 }
	local Activations = Propagator.ForwardPropagation(NeuralNetwork, Inputs, ActivationFunction)
	-- Returns all Activation of each layer
	
	local ExpectedOutput = { 0, 0 }
	
	-- print the Loss (Just tells us how much of difference we are from what we got and what we expected)
	local Loss = CalculateLoss(Activations[#Activations], ExpectedOutput)
	print("\n Loss:", Loss, "\n Output Activation:", unpack(Activations[#Activations]))
	
	-- Train the network, where when the inputs are { 1, 1 } it should output { 0, 0 }
	Propagator.BackPropagation(NeuralNetwork, Activations, ExpectedOutput, {
		ActivationFunction = ActivationFunction,

		LearningRate = 0.1
	})
end


You can see it back propagating and the loss getting decreased.


Custom Activation Functions
local OpenML = require(game:GetService("ServerScriptService").OpenML)

local Propagator = OpenML.Algorithms.Propagator
local NeuralNetwork = OpenML.Resources.MLP.new({ 2, 3, 2 }, math.random)

local ReLU = OpenML.ActivationFunctions.ReLU
local TanH = OpenML.ActivationFunctions.TanH

local Activations = Propagator.ForwardPropagation(NeuralNetwork, { 1, 1 }, function(layer: number)
	if layer == 3 then -- Use TanH only on layer 3 which in our case is the last layer
		return TanH
	else -- Other wise if its not just use ReLU
		return ReLU
	end
end)

print(Activations)

How to use Genetic Algorithm
local OpenML = require(game:GetService("ServerScriptService").OpenML)

local Genetic = OpenML.Algorithms.Genetic

local NetworkA = OpenML.Resources.MLP.new({ 2, 3, 2 }, math.random)
local NetworkB = OpenML.Resources.MLP.new({ 2, 3, 2 }, math.random)

-- Mutate is by chance
local MutatedNetwork = Genetic:Mutate(NetworkA, NetworkB, 0.3)

-- Blend is a mixture of the networks by percentage, so 0.5 would mean, half of its values are between NetworkA and NetworkB
local BlendedNetwork = Genetic:Blend(NetworkA, NetworkB, 0.3)

How to use CNN's
local OpenML = require(game:GetService("ServerScriptService").OpenML)

-- Get the CNN Resource
local CNN = OpenML.Resources.CNN

-- Kernel Presets
local Kernels = CNN.Kernels

-- Create the Convolutional Network, along with its kernels
local ConvolutionalNetwork = CNN.new({
	{ Kernels.Size3x3.SobelX, { -- SobelX to apply a Convolution to it
		{ "MaxPooling", {2, 2}, { -- Reduce the resolution by 2
			{ Kernels.Size3x3.HorizontalLine, { -- Apply HorizontalLine convolution to it
				{ "MaxPooling", {2, 2} } -- Reduce resolution by 2
			} }
		} } 
	} }
})

local Image = {}

-- We will create a black circle on the center of the image, and surrounding it will be white
local center = Vector2.new(12, 12)
for y = 1, 24 do
	Image[y] = {}
	
	for x = 1, 24 do
		Image[y][x] = (Vector2.new(x, y) - center).Magnitude/12
	end
end

-- Send the Image through our Convolutional Network
local ConvolutedOutput = CNN.ForwardPropagation(ConvolutionalNetwork, Image)
print(ConvolutedOutput[1]) -- We only made one Node so we get the first one.

--[[
What it should do is follow how we made out kernels:
Apply SobelX
Reduce Resolution by 2 24/2 = 12
Apply HorizonalLine
Reduce Resolution by 2 12/2 = 6

It should leave us with a 6x6 Image.
For Computer vision or image recognition you can send each pixel value of the 6x6 image into a Neural Network
]]

How to use Optimizers
local OpenML = require(game:GetService("ServerScriptService").OpenML)

local Propagator = OpenML.Algorithms.Propagator
local ActivationFunction = OpenML.ActivationFunctions.TanH

-- Select our Optimizer
local AdamOptimizer = OpenML.Optimizers.Adam.new()

local NeuralNetwork = OpenML.Resources.MLP.new({ 2, 3, 2 }, math.random)

for i = 1, 20 do
	local Activations = Propagator.ForwardPropagation(NeuralNetwork, { 1, 1 }, ActivationFunction)
	print(unpack(Activations[#Activations]))

	Propagator.BackPropagation(NeuralNetwork, Activations, { 0, 0 }, {
		ActivationFunction = ActivationFunction,
		Optimizer = AdamOptimizer, -- Apply the Optimizer here, It should speed up the Process of Forward and Back Propagation

		LearningRate = 0.05
	})
end

Deep Q-Learning
local OpenML = require(game:GetService("ServerScriptService").OpenML)

-- Choose our Activation Function
local ActivationFunction = OpenML.ActivationFunctions.TanH

-- We need this to forward propagate and back propagate the network when learning
local Propagator = OpenML.Algorithms.Propagator

-- we will create our network to use, keep in mind its 2 inputs, 3 hidden nodes, and 2 outputs
local NeuralNetwork = OpenML.Resources.MLP.new({ 2, 3, 2 }, math.random)
setmetatable(NeuralNetwork, { __index = Propagator })

-- we get our learning resource which in this case is Deep Q-Learning (DQN)
local DQL = OpenML.Algorithms.DQL.new() -- Link the DQN to the network

-- These are required and you have to put them. It also makes it easy for manipulation in between
DQL.OnForwardPropagation = function(states)
	return NeuralNetwork:ForwardPropagation(states, ActivationFunction)
end

-- these too, it'll just update the network to its target (expected)
DQL.OnBackPropagation = function(activations, target)
	return NeuralNetwork:BackPropagation(activations, target, { ActivationFunction = ActivationFunction, LearningRate = 0.25 }) -- if you want to use optimizer go ahead
end


local ReplayBuffer = OpenML.Resources.ReplayBuffer.new(16) -- this isn't required, its optional, number inside is the size of your buffer
-- if you want to train your network while retraining past training experiences. use this, it'll help training process.

for i = 1, 5 do
	local state = { 1, 0 } --[[
	Our state is basically our input. It tells us things about the environment
	for example if you were making a car drive.
	It would most likely be distance to something, or things like velocity
	]]
	
	local activations = NeuralNetwork:ForwardPropagation(state, ActivationFunction) --[[
	we need to run the state and see waht our result is, just run it like a regular neural network
	]]
	
	local actions = activations[#activations] --[[
	these are our Q Values, our single action is the Q Value thats the highest.
	If action[1] is 0.2 and action[2] is 0.6, our action is actions[2] because it is the highest
	]]
	
	local action = actions[1] > actions[2] and 1 or 2 -- you can do this another way like using max
	-- we need the index numbers for our actions because uh thats how we do it!
	
	local reward = -1 -- we can set our reward, it changes the network on how good. Positive mean keep doing it, negative mean nono bad do something else.
	-- i set it 1 so i can test that it keeps doing what it should do
	
	-- this is totally optional, if you have a next state you want to put in. you can do this
	--[[
	local nextState = { 0, 1 } -- just change it however you want. it's basically ur environment changing.
	-- what it looks like is going on here state is { 1, 0 } it looks like we moved right os our next state is { 0, 1 }
	]]
	
	print(unpack(actions)) -- lets see how the network is doing before learning
	
	-- we send our values through to change the network parameters
	DQL:Learn{
		State = state,
		Action = action,
		Reward = reward,
		
		ReplayBuffer = ReplayBuffer -- this is optional if you want your network to train with previous training data then this is it.
	}
	
	--[[
	let me explain what goes on here.
	what it basically does lets say your action is 1, action[1] and lets say action[1] is 0.75
	if action[1] gets a negative reward. then it will change only that output action[1], to go into the opposite direction
	so instead of action[1] being 0.75 it now proabbly has 0.3 because it got a negative reward and it'll vary depending on learning rate
	
	thats basically it. it only changes the action you send it.
	]]
	
	print(unpack(actions)) -- lets see how the network is doing after learning
	
	-- what you should see is the numbers going down.
end

Compression

Compression Defaults to ALPBase64

There are 4 types of Compression/Encoding

ALP
ALPBase64
IEEE754
JSON

local OpenML = require(script.Parent.OpenML)

local MLP = OpenML.Resources.MLP
local NeuralNetwork = MLP.new({2, 3, 2}, function()
	return math.random() * 3 - 1.5
end)

local ALPCompression = MLP.Compress(NeuralNetwork, "ALP") -- ALP Compression
local ALPBase64Compression = MLP.Compress(NeuralNetwork, "ALPBase64") -- ALPBase64 Compression
local IEEE754Compression = MLP.Compress(NeuralNetwork, "IEEE754") -- IEEE754 Compression
local JSONCompression = MLP.Compress(NeuralNetwork, "JSON") -- JSON Encoding

print(ALPCompression) -- ALP:?��G�2��.?G��>��w�,�.��t/7=>6%�k���g�;?����N��h.=0[?K��;?��l�p�;�kJN���A
print(ALPBase64Compression) -- ALPBase64:P5XtR78yo7I=.P0f6nT63j3e9LKYV.vxkQGb50Lzc==Pgo2Jb9r0ea/Z8QI;P6zr9r8GEk69Fdho.PTALWz9L/Ik=;P4GHbL5wC8U=;vmtKTr+3nEE=
print(IEEE754Compression) -- IEEE754:3F95ED47BF32A3B2.3F47FA9D3EB78F77BD2CA615.BF191019BE742F37=3E0A3625BF6BD1E6BF67C408;3FACEBF6BF06124EBD15D868.3D300B5B3F4BFC89;3F81876CBE700BC5;BE6B4A4EBFB79C41
print(JSONCompression) -- {"Nodes":[[1.1713036732564346,-0.6978103370441914],[0.7811678588612416,0.35851643279008918,-0.04215057641266262],[-0.5979019220644414,-0.23846136544807096]],"Weights":[[[0.13497218171855208,-0.9211716011775545,-0.9053349815348153],[1.3509510532232856,-0.5237168113041347,-0.03658333767135313]],[[0.042979580702473988,0.7968221325624154],[1.0119453055476955,-0.23441989869542269],[-0.229775640498594,-1.4344560463058896]]],"Format":"MLP"}

-- ALPBase64 is the default compression because datastores support it.

local ALPNetwork = MLP.Decompress(ALPCompression, "ALP")
local ALPBase64Network = MLP.Decompress(ALPBase64Compression, "ALPBase64")
local IEEE754Network = MLP.Decompress(IEEE754Compression, "IEEE754")
local JSONNetwork = MLP.Decompress(JSONCompression, "JSON")

print(ALPNetwork, ALPBase64Network, IEEE754Network, JSONNetwork) -- They all return the same network.

You can use this to store Networks in datastores, the best reliable compression ALPBase64.


ALP Compression will compress your network 500% smaller
ALP Base64 Compression will compress your network 350% smaller
IEEE754 Compression will compress your network 250% smaller
JSON is not compression its encoding.


How to Save a Network
local DataStoreService = game:GetService("DataStoreService")
local ServerScriptService = game:GetService("ServerScriptService")

-- Create/Get our datastore called "Networks"
local NetworkDataStore = DataStoreService:GetDataStore("Networks")

local OpenML = require(ServerScriptService.OpenML) -- Get OpenML

local MLP = OpenML.Resources.MLP -- Multi-layer Perceptron
local NeuralNetwork = MLP.new({ 2, 3, 2 }, math.random) -- Create a new Network

local CompressedNetwork = MLP.Compress(NeuralNetwork, "ALPBase64") -- Compress the network using ALP Base64
NetworkDataStore:SetAsync("MyNetwork", CompressedNetwork) -- Save Compressed Network to the DataStore

print(CompressedNetwork) -- see our compressed network
local DataStoreService = game:GetService("DataStoreService")
local ServerScriptService = game:GetService("ServerScriptService")

-- Create/Get our datastore called "Networks"
local NetworkDataStore = DataStoreService:GetDataStore("Networks")

-- How to get the network after its compressed?
local MyNetwork = NetworkDataStore:GetAsync("MyNetwork") -- Get the saved compressed Network from the DataStore
local NewNeuralNetwork = MLP.Decompress(MyNetwork, "ALPBase64")
print(NewNeuralNetwork) -- see our decompressed network that we can use

Directly Save the Network

-- Or just save the network directly
local DataStoreService = game:GetService("DataStoreService")
local ServerScriptService = game:GetService("ServerScriptService")

-- Create/Get our datastore called "Networks"
local NetworkDataStore = DataStoreService:GetDataStore("Networks")

local OpenML = require(ServerScriptService.OpenML) -- Get OpenML

local MLP = OpenML.Resources.MLP -- Multi-layer Perceptron
local NeuralNetwork = MLP.new({ 2, 3, 2 }, math.random) -- Create a new Network
NetworkDataStore:SetAsync("MyNetwork", NeuralNetwork) -- Save the network directly

Download Versions:
V1: OpenML.rbxm (8.0 KB)
V1.1: OpenML.rbxm (9.4 KB)
V1.2: OpenML.rbxm (12.3 KB)
V1.2.1: OpenML.rbxm (14.0 KB)
V1.2.2 (LATEST): OpenML.rbxm (17.5 KB)


Latest Update Log
OpenML Version 1.2.2

  • Deep Q-Learning is now able to update multiple rewards instead of one.

  • Optimized Deep Q-Learning

  • Moved Resources/DiscountRewards to Calculate/DiscountRewards

  • Moved Resources/GetHighestActionValue to Calculate/GetHighestActionValue

  • Added BinaryStep Activation Function

  • Added BentIdentity Activation Function

  • Added Gaussian Activation Function

  • Added LogSigmoid Activation Function

  • Added ISRU Activation Function

  • Added GELU Activation Function

  • Added HardTanH Activation Function

  • Added SoftShrink Activation Function

  • Added ThresholdReLU Activation Function

  • Added Logit Activation Function

  • Added Linear Activation Function

  • Renamed Private Resource “Compression” to “IEEE754”

  • Moved Private Resource “IEEE754” from Resources/IEEE754 to Resources/Compression/IEEE754

  • Removed Reference to Private Resources (To get them you have to require them yourself)

  • Renamed Calculate/Loss to Calculate/MSE

  • Added ALP Compression

  • Added ALP Base64 Compression

  • Renamed “ASCII” Compression to “IEEE754” (you have to specify which compression you want)

  • Compression now defaults to ALP Base64

50 Likes

DAMN, Never thought I’d live to see the day Machine Learning would be added to Roblox, as a plugin, and accessible to everyone. I commend you good sir

5 Likes

It only contains neural networks. So it should be deep learning library. Don’t mislead users.

1 Like

Yes it contains Neural Networks. but my module wont be mainly based on neural networks. It focuses mainly on Machine Learning and Neural Networks happen to be part of Machine Learning.

1 Like

I added Deep Q-Learning and cool car thing!

I can’t figure out how to use it, I don’t understand everything :frowning:

1 Like

I’ll create a full tutorial in the future on Machine Learning and how to use OpenML as well. I’ll give a run down on the basics then go into how it works. I’ll also set up a documentation in the future as well explaining every part of the module.

Made more preview/demos like Sword Fighting and Shape recognition.

Could you show us how to save and load the trained model so we dont have to retrain it every time we start the studio?

1 Like

I released version 1.2 and it shows how to save and “load” a model. There is no “loading” a model in OpenML because its just arrays of numbers
(its like that to be very compact, optimized and easy to use with other scripts)

Heres the code for saving a Network w/ Compression

local DataStoreService = game:GetService("DataStoreService")
local ServerScriptService = game:GetService("ServerScriptService")

-- Create/Get our datastore called "Networks"
local NetworkDataStore = DataStoreService:GetDataStore("Networks")

local OpenML = require(ServerScriptService.OpenML) -- Get OpenML

local MLP = OpenML.Resources.MLP -- Multi-layer Perceptron
local NeuralNetwork = MLP.new({ 2, 3, 2 }, math.random) -- Create a new Network

local CompressedASCII = MLP.Compress(NeuralNetwork, "ASCII") -- Compress the network into a string. (You can do "ASCII" or "JSON" either works)
NetworkDataStore:SetAsync("MyNetwork", CompressedASCII) -- Save Compressed Network to the DataStore

print(CompressedASCII) -- see our compressed network

-- How to get the network after its compressed?
local MyNetwork = NetworkDataStore:GetAsync("MyNetwork") -- Get the saved compressed Network from the DataStore
local NewNeuralNetwork = MLP.Decompress(MyNetwork, "ASCII")
print(NewNeuralNetwork) -- see our decompressed network that we can use

Absolutely mindblowing. But how do you handle optimization and training?

1 Like

I tried to apply it to the sword fighting bot and it got Attempt to find nil value

local OpenML = require(script.OpenML)
local DataStoreService = game:GetService("DataStoreService")
local ServerScriptService = game:GetService("ServerScriptService")

-- Create/Get our datastore called "Networks"
local NetworkDataStore = DataStoreService:GetDataStore("Networks")

local OpenML = require(script.OpenML) -- Get OpenML

local MLP = OpenML.Resources.MLP -- Multi-layer Perceptron



local States = { "Distance", "Health", "Angle", "DirX", "DirZ", "Random", }
local Actions = { "Idle", "Attack", "TurnLeft", "TurnRight", "MoveX", "MoveZ", "Jump" }

local turnSpeed = 20

local ActivationFunction = OpenML.ActivationFunctions.TanH

game:GetService("Players").PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function(character)
		character:AddTag("Character")
		for _, v in next, character:GetDescendants() do
			if v:IsA("BasePart") then
				v.CollisionGroup = "Fighter"
			end
		end
		character.Humanoid.BreakJointsOnDeath = false
		character.Humanoid.Died:Once(function()
			character:RemoveTag("Character")
			character.Humanoid.AutoRotate = false
			character.HumanoidRootPart:SetNetworkOwner(player)
			for _, v in next, character:GetDescendants() do
				if v:IsA("BasePart") then
					v.CollisionGroup = "Ragdoll"
				end
			end
		end)
	end)
end)

local tpCooldown = {}
for _, v in next, workspace.Box:GetChildren() do
	if v:IsA("BasePart") then
		v.Touched:Connect(function(otherPart)
			if otherPart.Parent:HasTag("Character") then
				if tpCooldown[otherPart.Parent.Name] then return end
				otherPart.Parent:PivotTo(script.Rig:GetPivot() * CFrame.new((math.random() * 2 - 1) * 35, 0, (math.random() * 2 - 1) * 35))
				tpCooldown[otherPart.Parent.Name] = true
				task.wait(1)
				tpCooldown[otherPart.Parent.Name] = false
			end
		end)
	end
end

for i = 1, 1 do
	task.spawn(function()
		local NeuralNetwork = setmetatable(OpenML.Resources.MLP.new({ #States, 10, 10, 10, #Actions }, function()
			return (math.random() * 2 - 1) * 1.5
		end), { __index = OpenML.Algorithms.Propagator })

		local DQL = OpenML.Resources.DQL.new()

		local startingLearningRate = 0.00015
		local maxLearningRate = 0.0015

		local learningRateMultiplier = 1.00015

		local learningRate = startingLearningRate

		DQL.OnForwardPropagation = function(state) return NeuralNetwork:ForwardPropagation(state, ActivationFunction) end
		DQL.OnBackPropagation = function(activations, target) return NeuralNetwork:BackPropagation(activations, target, { ActivationFunction = ActivationFunction, LearningRate = learningRate }) end
		while task.wait() do

			local rig = script.Rig:Clone()
			rig.Name = "AI-"..i
			rig:AddTag("Character")
			rig:PivotTo(rig:GetPivot() * CFrame.new((math.random() * 2 - 1) * 35, 0, (math.random() * 2 - 1) * 35))
			rig.Parent = workspace

			local humanoid = rig.Humanoid
			local humanoidRootPart = rig.HumanoidRootPart


			humanoid.Died:Once(function()
				rig:RemoveTag("Character")
				rig.HumanoidRootPart.AngularVelocity.Enabled = false
				rig.ClassicSword.SwordScript.Enabled = false
				for _, v in next, rig:GetDescendants() do
					if v:IsA("BasePart") then
						v.CollisionGroup = "Ragdoll"
					end
				end
				task.wait(3)
				rig:Destroy()
			end)

			while true and rig.Parent and humanoid.Health > 0 do
				learningRate = math.max(math.min(learningRate * learningRateMultiplier, maxLearningRate)%maxLearningRate, startingLearningRate)

				local deltaTime = task.wait()
				local characters = game:GetService("CollectionService"):GetTagged("Character")
				local character, closestDistance = nil, math.huge

				for _, v in next, characters do
					if v == rig or not v:FindFirstChild("HumanoidRootPart") then continue end

					local distance = (v.HumanoidRootPart.Position - humanoidRootPart.Position).Magnitude
					if distance < closestDistance then
						character, closestDistance = v, distance
					end
				end

				if character then
					local positionDifference = (character.HumanoidRootPart.Position - humanoidRootPart.Position)
					local distance = positionDifference.Magnitude / 50

					local angleDot = positionDifference.Unit:Dot(humanoidRootPart.CFrame.LookVector)

					local unitPositionDifferenceXZ = (positionDifference * Vector3.new(1, 0, 1)).Unit
					local environment = { distance, 1 - humanoid.Health/humanoid.MaxHealth, angleDot, unitPositionDifferenceXZ.X, unitPositionDifferenceXZ.Z, math.random() }

					local activations = NeuralNetwork:ForwardPropagation(environment, ActivationFunction)
					local lastActivationLayer = activations[#activations]

					if not rig:FindFirstChild("ClassicSword") then break end

					if lastActivationLayer[2] > 0.5 then
						rig.ClassicSword.ToolActivate:Fire()
					end

					--[[humanoidRootPart:PivotTo(
						humanoidRootPart:GetPivot() 
							* CFrame.fromEulerAnglesXYZ(0, deltaTime * turnSpeed * lastActivationLayer[3], 0)
							* CFrame.fromEulerAnglesXYZ(0, -deltaTime * turnSpeed * lastActivationLayer[4], 0)
					)]]
					
					humanoidRootPart.AngularVelocity.AngularVelocity = 
						Vector3.yAxis * (turnSpeed * lastActivationLayer[3] - turnSpeed * lastActivationLayer[4])


					if lastActivationLayer[1] < 0.5 then
						humanoid:Move(Vector3.new(lastActivationLayer[5] - 0.5, 0, lastActivationLayer[6] - 0.5).Unit)
					end

					if lastActivationLayer[7] > 0.5 then
						humanoid.Jump = true
					end

					DQL:Learn{
						State = environment,
						Action = 1,
						Reward = (distance > 3 and lastActivationLayer[1] > 0.5 and 1 or -1)
					}

					DQL:Learn{
						State = environment,
						Action = 2,
						Reward = (distance < 7 and lastActivationLayer[2] > 0.5 and 1 or -1)
					}

					local rightAngleDot = positionDifference.Unit:Dot(humanoidRootPart.CFrame.RightVector)

					DQL:Learn{
						State = environment,
						Action = 3,
						Reward = ((-rightAngleDot) + 0.05)
					}; DQL:Learn{
						State = environment,
						Action = 4,
						Reward = ((rightAngleDot) + 0.05)
					};

					DQL:Learn{
						State = environment,
						Action = 5,
						Reward = (positionDifference.Unit.X - humanoid.MoveDirection.X) - 0.5,
					}; DQL:Learn{
						State = environment,
						Action = 6,
						Reward = (positionDifference.Unit.Z - humanoid.MoveDirection.Z) - 0.5,
					};

					DQL:Learn{
						State = environment,
						Action = 7,
						Reward = lastActivationLayer[7] > 0.5 and humanoid.Health/humanoid.MaxHealth < 0.2 and 0.5 or -0.5,
					};
				end
			end
		
			task.wait(3)
			local CompressedASCII = MLP.Compress(OpenML.Resources.MLP.new({ #States, 10, 10, 10, #Actions }, function()
				return (math.random() * 2 - 1) * 1.5
			end), "ASCII") -- Compress the network into a string. (You can do "ASCII" or "JSON" either works)
			NetworkDataStore:SetAsync("MyNetwork", CompressedASCII) -- Save Compressed Network to the DataStore
			print(CompressedASCII) -- see our compressed network

			-- How to get the network after its compressed?
			local MyNetwork = NetworkDataStore:GetAsync("MyNetwork") -- Get the saved compressed Network from the DataStore
			local NewNeuralNetwork = MLP.Decompress(MyNetwork, "ASCII")
			print(NewNeuralNetwork) -- see our decompressed network that we can use
		end
	end)
end```

this is how heirarchy looks like:
![Capture|238x261](upload://idjjp2bv28xZktm0BsMUqXFWo3.png)
1 Like

In this snippet, you are creating a new MLP (Neural Network) and Compressing it. You should be compressing the current AI network. Not creating a new one.

If you want to save each variant of the network you would have to use a different key.

here is the page about GlobalDataStores so you can research about them.

Here is the corrected version of your code.

local OpenML = require(script.OpenML)
local DataStoreService = game:GetService("DataStoreService")
local ServerScriptService = game:GetService("ServerScriptService")

-- Create/Get our datastore called "Networks"
local NetworkDataStore = DataStoreService:GetDataStore("Networks", "DataVersion")

local OpenML = require(script.OpenML) -- Get OpenML

local MLP = OpenML.Resources.MLP -- Multi-layer Perceptron

local States = { "Distance", "Health", "Angle", "DirX", "DirZ", "Random", }
local Actions = { "Idle", "Attack", "TurnLeft", "TurnRight", "MoveX", "MoveZ", "Jump" }

local turnSpeed = 25

local ActivationFunction = OpenML.ActivationFunctions.TanH

game:GetService("Players").PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function(character)
		character:AddTag("Character")
		for _, v in next, character:GetDescendants() do
			if v:IsA("BasePart") then
				v.CollisionGroup = "Fighter"
			end
		end
		character.Humanoid.BreakJointsOnDeath = false
		character.Humanoid.Died:Once(function()
			character:RemoveTag("Character")
			character.Humanoid.AutoRotate = false
			character.HumanoidRootPart:SetNetworkOwner(player)
			for _, v in next, character:GetDescendants() do
				if v:IsA("BasePart") then
					v.CollisionGroup = "Ragdoll"
				end
			end
		end)
	end)
end)

local tpCooldown = {}
for _, v in next, workspace.Box:GetChildren() do
	if v:IsA("BasePart") then
		v.Touched:Connect(function(otherPart)
			if otherPart.Parent:HasTag("Character") then
				if tpCooldown[otherPart.Parent.Name] then return end
				otherPart.Parent:PivotTo(script.Rig:GetPivot() * CFrame.new((math.random() * 2 - 1) * 35, 0, (math.random() * 2 - 1) * 35))
				tpCooldown[otherPart.Parent.Name] = true
				task.wait(1)
				tpCooldown[otherPart.Parent.Name] = false
			end
		end)
	end
end

local StoredNetworks = {}
game:BindToClose(function()
	for i = 1, #StoredNetworks do
		local success, response = pcall(function()
			NetworkDataStore:UpdateAsync("Network-"..i, function() -- save datastore
				return StoredNetworks[i]
			end)
		end)
		
		if not success then
			warn("ERROR OCCURRED", response)
		end
	end
end)

for i = 1, 3 do
	task.spawn(function()
		local NeuralNetwork = setmetatable(NetworkDataStore:UpdateAsync("Network"..i, function(data) -- get version of the datastore NOW.
			return data or OpenML.Resources.MLP.new({ #States, 10, 10, 10, #Actions }, function() -- set it to their data or if they dont have one then create a new one
				return (math.random() * 2 - 1) * 1.5
			end)
		end), { __index = OpenML.Algorithms.Propagator })
		
		StoredNetworks[i] = NeuralNetwork

		local DQL = OpenML.Resources.DQL.new()

		local startingLearningRate = 0.00003
		local maxLearningRate = 0.0003

		local learningRateMultiplier = 1.00003

		local learningRate = startingLearningRate

		DQL.OnForwardPropagation = function(state) return NeuralNetwork:ForwardPropagation(state, ActivationFunction) end
		DQL.OnBackPropagation = function(activations, target) return NeuralNetwork:BackPropagation(activations, target, { ActivationFunction = ActivationFunction, LearningRate = learningRate }) end
		while task.wait() do

			local rig = script.Rig:Clone()
			rig.Name = "AI-"..i
			rig:AddTag("Character")
			rig:PivotTo(rig:GetPivot() * CFrame.new((math.random() * 2 - 1) * 35, 0, (math.random() * 2 - 1) * 35))
			rig.Parent = workspace

			local humanoid = rig.Humanoid
			local humanoidRootPart = rig.HumanoidRootPart


			humanoid.Died:Once(function()
				rig:RemoveTag("Character")
				rig.HumanoidRootPart.AngularVelocity.Enabled = false
				rig.ClassicSword.SwordScript.Enabled = false
				for _, v in next, rig:GetDescendants() do
					if v:IsA("BasePart") then
						v.CollisionGroup = "Ragdoll"
					end
				end
				task.wait(3)
				rig:Destroy()
			end)

			while true and rig.Parent and humanoid.Health > 0 do
				learningRate = math.max(math.min(learningRate * learningRateMultiplier, maxLearningRate)%maxLearningRate, startingLearningRate)

				local deltaTime = task.wait()
				local characters = game:GetService("CollectionService"):GetTagged("Character")
				local character, closestDistance = nil, math.huge

				for _, v in next, characters do
					if v == rig or not v:FindFirstChild("HumanoidRootPart") then continue end

					local distance = (v.HumanoidRootPart.Position - humanoidRootPart.Position).Magnitude
					if distance < closestDistance then
						character, closestDistance = v, distance
					end
				end

				if character then
					local positionDifference = (character.HumanoidRootPart.Position - humanoidRootPart.Position)
					local distance = positionDifference.Magnitude / 50

					local angleDot = positionDifference.Unit:Dot(humanoidRootPart.CFrame.LookVector)

					local unitPositionDifferenceXZ = (positionDifference * Vector3.new(1, 0, 1)).Unit
					local environment = { distance, 1 - humanoid.Health/humanoid.MaxHealth, angleDot, unitPositionDifferenceXZ.X, unitPositionDifferenceXZ.Z, math.random() }

					local activations = NeuralNetwork:ForwardPropagation(environment, ActivationFunction)
					local lastActivationLayer = activations[#activations]

					if not rig:FindFirstChild("ClassicSword") then break end

					if lastActivationLayer[2] > 0.5 then
						rig.ClassicSword.ToolActivate:Fire()
					end

					--[[humanoidRootPart:PivotTo(
						humanoidRootPart:GetPivot() 
							* CFrame.fromEulerAnglesXYZ(0, deltaTime * turnSpeed * lastActivationLayer[3], 0)
							* CFrame.fromEulerAnglesXYZ(0, -deltaTime * turnSpeed * lastActivationLayer[4], 0)
					)]]

					humanoidRootPart.AngularVelocity.AngularVelocity = 
						Vector3.yAxis * (turnSpeed * lastActivationLayer[3] - turnSpeed * lastActivationLayer[4])


					if lastActivationLayer[1] < 0.5 then
						humanoid:Move(Vector3.new(lastActivationLayer[5] - 0.5, 0, lastActivationLayer[6] - 0.5).Unit)
					end

					if lastActivationLayer[7] > 0.5 then
						humanoid.Jump = true
					end

					DQL:Learn{
						State = environment,
						Action = 1,
						Reward = (distance > 3 and lastActivationLayer[1] > 0.5 and 1 or -1)
					}

					DQL:Learn{
						State = environment,
						Action = 2,
						Reward = (distance < 7 and lastActivationLayer[2] > 0.5 and 1 or -1)
					}

					local rightAngleDot = positionDifference.Unit:Dot(humanoidRootPart.CFrame.RightVector)

					DQL:Learn{
						State = environment,
						Action = 3,
						Reward = ((-rightAngleDot) + 0.05)
					}; DQL:Learn{
						State = environment,
						Action = 4,
						Reward = ((rightAngleDot) + 0.05)
					};

					DQL:Learn{
						State = environment,
						Action = 5,
						Reward = (positionDifference.Unit.X - humanoid.MoveDirection.X) - 0.5,
					}; DQL:Learn{
						State = environment,
						Action = 6,
						Reward = (positionDifference.Unit.Z - humanoid.MoveDirection.Z) - 0.5,
					};

					DQL:Learn{
						State = environment,
						Action = 7,
						Reward = lastActivationLayer[7] > 0.5 and humanoid.Health/humanoid.MaxHealth < 0.2 and 0.5 or -0.5,
					};
				end
			end
		end
	end)
end

Keep in mind that if you train it repetitively for too long it’ll suffer from “Model Collapse”
To achieve the best results you can use the Genetic Algorithm.

Another thing to note, it could learn best if the inputs were relative, like the direction to the enemy. If that was relative it would achieve greater results.

1 Like

Awesome thank you. (Sdohweoqheqw)

1 Like

If you’re wondering how Optimization in OpenML works, I eliminated all generic loops and replaced them with Numerical loops. I work with games that deal with optimization and need to run fast. I try to keep the logic simple as possible so it can run fast and efficiently.

About the training, you train the network on what you want it to learn.

For the Sword Fighting AI, the network is rewarded when It is moving towards the enemy, facing the enemy and attacking near the enemy. To reward it when it it looks at the enemy I got the dot product of the direction of the enemy (enemyPosition - myPosition).Unit this gets its unit vector (direction) then you do a dot product of the AI’s rightvector. Why? it gives us negative numbers easier without doing complicated calculations. It also starts at zero so that helps us. To make the enemy move towards the target, we get the unit vector (direction) of the enemy again and compare it to the AI’s direction using dot product again.

For the Deep Learning Cars, the cars shoot 7 raycasts forward and some of the raycasts are averaged and lets say the max raycast distance was 10. The math would be reward = (raycastDistance/10) - 0.3 the farther it is the more reward it gets, the closer it gets the more lower and negative it gets

For the Network learning shapes, it using a Convolutional Neural Network (CNN). First it takes the image on the screen, then modifies the image and downscales it from 24x24 to 6x6 image, it extracts the important details like horizontal lines and vertical lines using convolutional layers with kernels. It does this 2 times, one for the horizontal lines and another for the vertical lines. so your left with 2 6x6 images. After that you send both of those grayscale image pixels through a Neural Network. The pixel number would either be 0 or 1. The input layer would have 72 nodes because 6*6 = 36 and 36 * 2 = 72. We multiplied it by 2 because we have 2 images. We send those pixels through a big network, pretty sure its 72 inputs, 50 hidden, 50 hidden, 50 hidden, 2 output. After that we get its prediction and use back propagation to update its values to the correct answer.

Released version 1.2.1 to maximize optimization of the module, and added new optimization functions.

I watched on videos on machine learning, it says each activation hold a number between 0 to 1, but when I tested this out, initially the output gives me 2, 3, etc. I am a little confused.

Depends on the activation function. Activation functions like LeakyReLU go above 1, that’s why usually on the end they apply TanH and you can do that with OpenML on Custom Activation Functions tab.

TanH caps numbers at -1 and 1 same as Sigmoid, but ReLU makes it cap at 0 and inf,
LeakyReLU does -0.01x to x which is inf

Heres an Image of LeakyReLU, its just goes to infinity
ReLUimage

And here TanH, which caps it at -1 and 1
tanh

I made a moving lava part that go back & forth, and have a script inside a rig using deep Q learning to tell the AI when to jump in order to avoid the lava, but both idle and jump actions resulted with a value of 0 after beginlearning.

local OpenML = require(game:GetService("ServerScriptService").OpenML)

local States = {"Distance"}
local Actions = {"Idle", "Jump"}

-- Get Propagator for forward and back propagation
local Propagator = OpenML.Algorithms.Propagator

local NeuralNetwork = OpenML.Resources.MLP.new({ #States, 10, 10, #Actions }, math.random)
setmetatable(NeuralNetwork, { __index = Propagator })

-- Get our selected activation function
local ActivationFunction = OpenML.ActivationFunctions.ReLU

-- we get our learning resource which in this case is Deep Q-Learning (DQN)
local DQL = OpenML.Algorithms.DQL.new() -- Link the DQN to the network

-- These are required and you have to put them. It also makes it easy for manipulation in between
DQL.OnForwardPropagation = function(states)
	return NeuralNetwork:ForwardPropagation(states, ActivationFunction)
end

-- these too, it'll just update the network to its target (expected)
DQL.OnBackPropagation = function(activations, target)
	return NeuralNetwork:BackPropagation(activations, target, { ActivationFunction = ActivationFunction, LearningRate = 0.25 }) -- if you want to use optimizer go ahead
end

local ReplayBuffer = OpenML.Resources.ReplayBuffer.new(16) -- this isn't required, its optional, number inside is the size of your buffer
-- if you want to train your network while retraining past training experiences. use this, it'll help training process.

function Run()
	local distance = (script.Parent:GetPivot().Position - workspace.Part.Position).Magnitude

	local state = {distance} -- basically the input

	local activations = NeuralNetwork:ForwardPropagation(state, ActivationFunction) --[[
	we need to run the state and see waht our result is, just run it like a regular neural network
	]]

	local actions = activations[#activations] --[[
	these are our Q Values, our single action is the Q Value thats the highest.
	If action[1] is 0.2 and action[2] is 0.6, our action is actions[2] because it is the highest
	]]

	if actions[2] > actions[1] then
		script.Parent.Humanoid.Jump = true
	end
	print(distance)
	print("Idle: "..actions[1])
	print("Jump: "..actions[2]) -- lets see how the network is doing before learning
end

function Train()
	local distance = (script.Parent:GetPivot().Position - workspace.Part.Position).Magnitude

	local state = {distance}-- basically the input

	local activations = NeuralNetwork:ForwardPropagation(state, ActivationFunction) --[[
	we need to run the state and see waht our result is, just run it like a regular neural network
	]]

	local actions = activations[#activations] --[[
	these are our Q Values, our single action is the Q Value thats the highest.
	If action[1] is 0.2 and action[2] is 0.6, our action is actions[2] because it is the highest
	]]
	
	if actions[2] > actions[1] then
		script.Parent.Humanoid.Jump = true
	end
	print(distance)
	print("Idle: "..actions[1])
	print("Jump: "..actions[2])

	-- we send our values through to change the network parameters
	DQL:Learn{
		State = state,
		Action = 1, -- Idle
		Reward = distance > 5 and actions[1] > actions[2] and 1 or -1, -- basically, if distance > 5 and AI decide to idle then return 1, else return -1

		ReplayBuffer = ReplayBuffer -- this is optional if you want your network to train with previous training data then this is it.
	}
	print(distance > 5 and actions[1] > actions[2] and 1 or -1)
	
	DQL:Learn{
		State = state,
		Action = 2, -- Jump
		Reward = (distance < 5 and actions[2] > actions[1] and 1 or -1), -- basically, if distance < 5 and AI decide to jump then return 1, else return -1
		
		ReplayBuffer = ReplayBuffer
	}
	print(distance < 5 and actions[2] > actions[1] and 1 or -1)
end

while true do
	task.wait(0.1)
	Train()
end

script.Parent is the rig, workspace.Part is the lava

1 Like

Here is the Improvised version of your code.
I added comments to what was changed.

The model learns very quickly so when you press run it already can jump over the lava part.

local OpenML = require(game:GetService("ServerScriptService").OpenML)

local States = {"Distance"}
local Actions = {"Jump"} -- Removed Idle State to keep it simple. Either jump or dont jump.

local Propagator = OpenML.Algorithms.Propagator
local NeuralNetwork = OpenML.Resources.MLP.new({ #States, 10, #Actions }, function() -- reduced hidden layer size to 1 instead of 2, doesnt need to be big only for simple actions
	return math.random() * 3 - 1.5 --made it initialize with negative valeus (produces better output)
end)

setmetatable(NeuralNetwork, { __index = Propagator })

local ActivationFunction = OpenML.ActivationFunctions.TanH -- Changed Activation function from ReLU to TanH
-- ReLU suffers from Dying ReLU so we changed it to TanH

local DQL = OpenML.Algorithms.DQL.new()

DQL.OnForwardPropagation = function(states) return NeuralNetwork:ForwardPropagation(states, ActivationFunction) end
DQL.OnBackPropagation = function(activations, target) return NeuralNetwork:BackPropagation(activations, target, { ActivationFunction = ActivationFunction, LearningRate = 0.01 }) end -- changed the learning rate to 0.01

function Run()
	local distance = (script.Parent:GetPivot().Position - workspace.Part.Position).Magnitude
	local state = { distance }

	local activations = NeuralNetwork:ForwardPropagation(state, ActivationFunction)
	local actions = activations[#activations]

	if actions[1] > 0.5 then -- If the first action is greater than the threshold 0.5 then it'll jump anything under then it doesnt jump
		script.Parent.Humanoid.Jump = true
	end
	
	return state
end

function Train()
	local state = Run()
	local distance = state[1]

	DQL:Learn{
		State = state,
		Action = 1, -- Jump
		Reward = distance < 7 and 1 or -1, -- changed distance from 5 to 7 so it jump faster
	}
end

while task.wait() do -- changed time from 0.1 to just the server framerate
	Train()
end