Neural Network Library (Obsolete)

Lua already has Torch, the original library that was ported to Python and is now known as PyTorch. My old and new module are still child’s play compared to what is on there.

1 Like

Is there a way to make neural networks in roblox work with the driveseat .throttle and .steer property? How would this go in to a script?

1 Like

Use the outputs the AI returns and scale and set them, with a output value being a higher value.

1 Like

What power limitations does roblox have with CNNs?

1 Like

They tend to require a ton of computations and roblox lua is not very good at doing a lot of math very quickly.

5 Likes

Why does it have to do with roblox lua, and not with the machines hardware?

1 Like

Because you do not run code directly on the hardware. To use the hardware, you need an interface that lets you use it. This interface is machine code. The problem is that machine code is impossible to write complex programs with due to it’s lack of readability (binary) and lack of features, so we use programming languages. These languages interpret our code and turn it into machine code. Every language is different and has different features, but all of them have the same result. Lua is an old and rather unsupported scripting language. Due to lack of support and frequent updates, Lua does not tend to be very fast at all. Lua 5.1, which we’re using, is a snail compared to newer modified versions like LuaJIT. Anywho, point is, with the way everything works, Lua is the bottleneck here. Due it’s low speed, you cannot do the same things that you could do in Java for example.

2 Likes

Lua is actually compiled to bytecode (not machine code), so it can be safely sandboxed. Roblox also forked from Lua earlier this year with “Luau”, which has a new VM with a lot of great optimizations that can compete with LuaJIT in some specific cases.

If course it’s still quite a bit slower than machine code. If we identify the fundamental bottlenecks we hit with AI implementations in Lua, perhaps new API’s can be introduced to speed up training/runtime calculations.

4 Likes

When giving a raycast as an input, assuming that the ray’s direction relative to the bot is always the same, the distance to the object the ray hit is used as an input, capped at a reasonable max distance.
For example, if the raycast hit something 134 studs away and the maximum is 1000, 134 is the input. If it hit something 1458 studs away, 1000 is the input instead.

1 Like

Dude this is awesome! Recently got into python and started making a NN in that language, I wondered, imagine an AI in roblox. Truly interesting, this really shows how far roblox has come as a game engine.

2 Likes

Im getting this error

[18:52:08.923 - Workspace.KNNLibrary:350: Wrong Arg#1: Network must be a string or StringValue.]
[18:52:08.924 - Script ‘Workspace.KNNLibrary’, Line 350 - function loadNet]
[18:52:08.924 - Script ‘Workspace.KNNLibrary’, Line 495 - function runGenNet]
[18:52:08.925 - Script ‘Workspace.Script’, Line 115]

Heres the script

local module=require(workspace.KNNLibrary) --Activating and getting the library

local nets = module.createGenNet(workspace.Networks,20,9,2,6,5,“LeakyReLU”) --Creating a generation of 20 networks with 2 inputs, 2 hidden layers, 2 nodes per hidden layer,
–1 output node, and with ReLU as the activation function
local vis = module.getVisual(nets[1])
vis.Parent = game.StarterGui

local function visualizeRay(ray, name, E)
if game.Workspace:FindFirstChild(name) then
game.Workspace[name]:Destroy()
end
local hitpart, hitposition = workspace:FindPartOnRay(ray)
local p = Instance.new(“Part”, workspace) – creating a part to visualize the ray)
p.Name = name
p.Position = hitposition
p.Anchored = true
p.CanCollide = false
p.Size = Vector3.new(1,1,1)
p.Shape = “Ball”
p.BrickColor = BrickColor.new(“Really red”)
return (p.Position - E.Position).Magnitude – magnitude from Origin part and the part that visualizes the ray(i could use a better system)
end
function getray(part, food)
local forwardray = Ray.new(part.Position, part.CFrame.LookVector * 500)
local backwardray =Ray.new(part.Position, part.CFrame.LookVector * 500*(-1))
local rightray = Ray.new(part.Position, part.CFrame.RightVector * 500)
local leftray = Ray.new(part.Position, (part.CFrame.RightVector * 500)-1)
local forwardleftray = Ray.new(part.Position, ((part.CFrame.LookVector * 500) -(part.CFrame.RightVector * 500)
-1))
local forwardrightray = Ray.new(part.Position, ((part.CFrame.LookVector * 500) -(part.CFrame.RightVector * 500)1))
local backwardleftray = Ray.new(part.Position, ((part.CFrame.LookVector * 500
(-1)) -(part.CFrame.RightVector500)-1))
local backwardrightray = Ray.new(part.Position, ((part.CFrame.LookVector * 500*(-1)) -(part.CFrame.RightVector*500)*1))
local foodray = Ray.new(part.Position, (food.Position - part.Position).unit)
local f = visualizeRay(forwardray, “forwardray”, part)
local r = visualizeRay(rightray, “rightray”, part)

local l = visualizeRay(leftray, “leftray”, part)
local fl = visualizeRay(forwardleftray, “forwardleftray”, part)
local fr = visualizeRay(forwardrightray, “forwardrightray”, part)
local bl = visualizeRay(backwardleftray, “backwardleftray”, part)
local br = visualizeRay(backwardrightray, “backwardrightray”,part)
local b = visualizeRay(backwardray, “backwardray”, part)
local fd = visualizeRay(foodray, “foodray”, part)
return {f, r, l, fl, fr, bl, br, b, fd} – table for input
end

for g=1, 1000 do --We will run for 1 million generations, which is basically forever
local i = 0

–print("Generation: "…g) --Print the generation number
local scores = {} --Array storing the scores for every network
local step = 8 --step value used for lowering the resolution of the scoring. Lower step means higher resolution but far slower
local neg = 1
local tim=tick()
for z=1,#nets do --Looping through every network in the generation
local network = module.loadNet(nets[z]) --We load up the StringValue of the network in question
local wins=0
local part = game.ReplicatedStorage.Jaco:Clone()
local food = game.ReplicatedStorage.Food:Clone()
local previousWin = game.ServerScriptService.PreviousGen
part.Parent = workspace
food.Parent = workspace
game.ServerScriptService.StopGeneration.Value = false
game.ServerScriptService.Touching.Value = false
game.ServerScriptService.Food.Value = false
while game.ServerScriptService.StopGeneration.Value ~= true do
local answer=module.forwardNet(network,getray(part, food)) --We run the network and get a response from it
local ra = unpack(answer)
print(g, nets[z])
i = i + 1

  	if ra >= 0.9 then
  		part.CFrame = CFrame.new(part.CFrame.X+1, part.CFrame.Y, part.CFrame.Z)
  	elseif ra > 0.6 and ra < 0.9 then
  		part.CFrame = CFrame.new(part.CFrame.X, part.CFrame.Y, part.CFrame.Z-1)
  	elseif ra < 0.6 and ra > 0.4 then
  		part.CFrame = CFrame.new(part.CFrame.X-1, part.CFrame.Y, part.CFrame.Z)
  	elseif ra < 0.4 then
  		part.CFrame = CFrame.new(part.CFrame.X, part.CFrame.Y, part.CFrame.Z+1)
  	end
  	
  	if game.ServerScriptService.Food.Value == true then
  		wins = wins + 1
  		game.ServerScriptService.Food.Value = false
  		i=i-50
  	end
  	if tick()-tim>=0.1 then	--To avoid timeouts, we add a wait() every tenth of a second the script runs. This is to make sure even potato processors will work
  		tim=tick()			--If you want a little more speed over the pretty effects, switch the 0.1 into a 0.5	
  	end
  	
  	if game.ServerScriptService.StopGeneration.Value == true then
  		i = 0
  		break
  	end
  	if game.ServerScriptService.Touching.Value == true then
  		previousWin.Value = g
  		wins = wins - 1
  		wait()
  		i = 0
  		break
  	end
  	if i == 100 then
  		wait()
  		wins = wins - 1
  		i = 0
  		break
  	end
  	
  	game.StarterGui.bruhmoment.Wins.Text = "Wins: "..wins
  	game.StarterGui.bruhmoment.Moves.Text = "Move Left: ".. 100-i
  	game.StarterGui.bruhmoment.Answer.Text = ra
  	game.StarterGui.bruhmoment.TextLabel.Text = "Generation: "..g.." Part: "..z
  	wait()
  	table.insert(scores,wins)	--With the score calculated, we insert it into the scores array
  end
  local best = module.runGenNet(nets, scores)
  module.updateVisualActive(network,vis, getray(part, food))
  part:Destroy()
  food:Destroy()

end
–With all of the networks scored, we run the next generation
–For every new generation, we show how the best network looks
–for a split second
table.sort(scores) --Purely for demo purposes, I sort the scores to find the best one
–We calculate the best possible score using our step value and print out the success rate %
print(“Best network success rate: “…scores[#scores]/(800/step)^2*(100)…”%”)
end

1 Like

Same, i dont know what causes it

1 Like

i would just use tostring the table i guess

1 Like

For whatever reason, the network provided isn’t a string or a StringValue. My guess is that somewhere the entry is replaced with a table version of a network. Its a bit difficult to check the code with the format its in so could you post the entire script in a code bubble?

With all the problems caused by my odd design choices and poor coding practices when I made this abomination of a library, I am really glad that I am working on a new one that will deprecate this one. If you would like to keep your sanity, please, don’t look into the code behind this thing.

I tried using tostring but its not working

sry for the bad scripting format
here’s the script

local module=require(workspace.KNNLibrary) --Activating and getting the library

local nets = module.createGenNet(workspace.Networks,20,9,2,6,5,"LeakyReLU") 	--Creating a generation of 20 networks with 2 inputs, 2 hidden layers, 2 nodes per hidden layer, 
																				--1 output node, and with ReLU as the activation function
local vis = module.getVisual(nets[1])
vis.Parent = game.StarterGui

local function visualizeRay(ray, name, E)
	--Visualize Ray
	if game.Workspace:FindFirstChild(name) then
		game.Workspace[name]:Destroy()
	end
	local hitpart, hitposition = workspace:FindPartOnRay(ray)
	local p = Instance.new("Part", workspace) -- creating a part to visualize the ray)
	p.Name = name
	p.Position = hitposition
	p.Anchored = true
	p.CanCollide = false
	p.Size = Vector3.new(1,1,1)
	p.Shape = "Ball"
	p.BrickColor = BrickColor.new("Really red")
	return (p.Position - E.Position).Magnitude -- magnitude from Origin part and  the part that visualizes the ray(i could use a better system)
end	

function getray(part, food)
	--Raycasting for inputs
	local forwardray = Ray.new(part.Position, part.CFrame.LookVector * 500)
	local backwardray =Ray.new(part.Position, part.CFrame.LookVector * 500*(-1))
	local rightray = Ray.new(part.Position, part.CFrame.RightVector * 500)
	local leftray = Ray.new(part.Position, (part.CFrame.RightVector * 500)*-1)
	local forwardleftray = Ray.new(part.Position, ((part.CFrame.LookVector * 500) -(part.CFrame.RightVector * 500)*-1))
	local forwardrightray = Ray.new(part.Position, ((part.CFrame.LookVector * 500) -(part.CFrame.RightVector * 500)*1))
	local backwardleftray = Ray.new(part.Position, ((part.CFrame.LookVector * 500*(-1)) -(part.CFrame.RightVector*500)*-1))
	local backwardrightray = Ray.new(part.Position, ((part.CFrame.LookVector * 500*(-1)) -(part.CFrame.RightVector*500)*1))
	local foodray = Ray.new(part.Position, (food.Position - part.Position).unit)
	local f	= visualizeRay(forwardray, "forwardray", part)
	local r =	visualizeRay(rightray, "rightray", part)

	local l = 	visualizeRay(leftray, "leftray", part)
	local fl =	visualizeRay(forwardleftray, "forwardleftray", part)
	local fr =	visualizeRay(forwardrightray, "forwardrightray", part)
	local bl = visualizeRay(backwardleftray, "backwardleftray", part)
	local br = visualizeRay(backwardrightray, "backwardrightray",part)
	local b = visualizeRay(backwardray, "backwardray", part)
	local fd = visualizeRay(foodray, "foodray", part)
	return {f, r, l, fl, fr, bl, br, b, fd} -- table for input
end
																
for g=1, 1000 do 			--We will run for 1 million generations, which is basically forever 
	local i = 0
	
	--print("Generation: "..g)	--Print the generation number
	local scores = {}			--Array storing the scores for every network
	local step = 8		--step value used for lowering the resolution of the scoring. Lower step means higher resolution but far slower
	local neg = 1
	local tim=tick()			
	for z=1,#nets do	--Looping through every network in the generation
		local network = module.loadNet(nets[z])	--We load up the StringValue of the network in question
		local wins=0
		local part = game.ReplicatedStorage.Jaco:Clone()
		local food = game.ReplicatedStorage.Food:Clone()
		local previousWin = game.ServerScriptService.PreviousGen
		part.Parent = workspace
		food.Parent = workspace
		game.ServerScriptService.StopGeneration.Value = false
		game.ServerScriptService.Touching.Value = false
		game.ServerScriptService.Food.Value = false
		while game.ServerScriptService.StopGeneration.Value ~= true do
			local answer=module.forwardNet(network,getray(part, food))
			local ra = unpack(answer)
			print(g, nets[z])
			i = i + 1
			
			if ra >= 0.9 then
				part.CFrame = CFrame.new(part.CFrame.X+1, part.CFrame.Y, part.CFrame.Z)
			elseif ra > 0.6 and ra < 0.9 then
				part.CFrame = CFrame.new(part.CFrame.X, part.CFrame.Y, part.CFrame.Z-1)
			elseif ra < 0.6 and ra > 0.4 then
				part.CFrame = CFrame.new(part.CFrame.X-1, part.CFrame.Y, part.CFrame.Z)
			elseif ra < 0.4 then
				part.CFrame = CFrame.new(part.CFrame.X, part.CFrame.Y, part.CFrame.Z+1)
			end
			
			if game.ServerScriptService.Food.Value == true then
				wins = wins + 1
				game.ServerScriptService.Food.Value = false
				i=i-50
			end
			if tick()-tim>=0.1 then	--To avoid timeouts, we add a wait() every tenth of a second the script runs. This is to make sure even potato processors will work
				tim=tick()			--If you want a little more speed over the pretty effects, switch the 0.1 into a 0.5	
			end
			
			if game.ServerScriptService.StopGeneration.Value == true then
				i = 0
				break
			end
			if game.ServerScriptService.Touching.Value == true then
				previousWin.Value = g
				wins = wins - 1
				wait()
				i = 0
				break
			end
			if i == 100 then
				wait()
				wins = wins - 1
				i = 0
				break
			end
			
			game.StarterGui.bruhmoment.Wins.Text = "Wins: "..wins
			game.StarterGui.bruhmoment.Moves.Text = "Move Left: ".. 100-i
			game.StarterGui.bruhmoment.Answer.Text = ra
			game.StarterGui.bruhmoment.TextLabel.Text = "Generation: "..g.." Part: "..z
			wait()
			table.insert(scores,wins)	--With the score calculated, we insert it into the scores array
		end
		local best = module.runGenNet(nets, scores)
		module.updateVisualActive(network,vis, getray(part, food))
		part:Destroy()
		food:Destroy()
	end
	 			--With all of the networks scored, we run the next generation
				--For every new generation, we show how the best network looks 
														--for a split second
	table.sort(scores)									--Purely for demo purposes, I sort the scores to find the best one
	--We calculate the best possible score using our step value and print out the success rate %
	print("Best network success rate: "..scores[#scores]/(800/step)^2*(100).."%")
end

btw my things are named VERY badly. i could reformat and make it easier to understand

so the network not being a tostring i went into the module, and the line in the module the error was is 495, and then i did

print(tostring(nets[best[1]]))

and it printed nil, so somewhere i mustve messed it up where it would return nil

SOLVED
Here is a script I had a problem with, but not anymore. its supposed to make a part, “Goobee,” play tag with you; this is basically the finished product for those who want to see it or use it. (If you see something wrong, feel free to tell me!)

-- CONFIGURATIONS --------------------------------------------------------------------------------

local totalGenerations, step, netsPerGen = 10000, 16, 60 --Generation Amount, Generation length, Generation Size
local inputs, layers, nodes, outputs =  3,2,6,3 --Nerual network configurations
local yourName = "qwert347" --Player name for tagging
-- VARIABLES --------------------------------------------------------------------------------

local module=require(workspace.KNNLibrary) --Activating and getting the library
local nets = module.createGenNet(workspace.Networks,netsPerGen, inputs, layers, nodes, outputs,"LeakyReLU") 															
local vis = module.getVisual(nets[1]); vis.Parent = game.StarterGui
local player = game.Players.PlayerAdded:Wait()
local character = player.CharacterAdded:Wait()
local rootPart = character.HumanoidRootPart

--- CODE -------------------------------------------------------------------------------------
--Generations
for g=1, totalGenerations do 
	
	--Setup	Gen
	print("Generation: "..g)	
	local scores = {}			
	local updater = Instance.new("NumberValue",script)
	local botFolder = Instance.new("Folder",workspace)
	botFolder.Name = "BotFolder"
	updater.Name = "Updater"
	
	--Networks
	for z=1,#nets do
		print("Network: "..z)
		--Setup Net	 
		local network = module.loadNet(nets[z])	
		local bot = game:GetService("ReplicatedStorage").Goobee:Clone()
		bot.Parent = botFolder
		
		--Scoring player hits
		local wins = 10000
		bot.Touched:Connect(function(hit) 
			if hit.Parent.Name == yourName then wins=wins+100	end	
		end)
		
		--Updater setup
		script.Updater.Changed:Connect(function()
			if script.Updater.Value ~= 0 then
				--Normal update
				local cac  = math.tanh(rootPart.CFrame:ToObjectSpace(bot.CFrame).X/50) 
				local cac2 = math.tanh(rootPart.CFrame:ToObjectSpace(bot.CFrame).Z/50) 	
				local cac3 = bot.Velocity.Magnitude/50
				local answer=module.forwardNet(network,{cac,cac2,cac3})
				bot.Speed.Force = Vector3.new(0,0,(answer[1]-.5)*10000)
				bot.Steering.AngularVelocity = Vector3.new(0,answer[2]-answer[3],0)

			else --Final update
				wins= wins - math.floor((rootPart.Position-bot.Position).Magnitude + .5) --scoring distance from player
				table.insert(scores,wins) -- logging score
			end
		end)
	end
	
	
	--Updater Running
	for d=0,399,step do	updater.Value=d; wait(.2) end
	
	--Ending Gen
	botFolder:Destroy()
	updater.Value = 0
	updater:Destroy()

	local best = module.runGenNet(nets,scores) 
	module.updateVisualState(nets[best],vis)			
	print("Best network success: " ,module.hardCode(nets[best]))
end
2 Likes