Neural Network Library 2.0

Sorry for replying so late, finals week.

I see I forgot to make :Save() automatically convert the save into a string. 1 missing boolean right up; you can remove the :JSONEncode() now.
The reason the saved network is doing so bad is that you’re saving the template network you created before training. You’re saving the network that was never trained to begin with.

About your pull request; the point of it is to allow simultaneous testing, right? Well, GeneticAlgorithm already has this ability. When processing the generation, you can either supply a ScoreFunc when creating the GeneticAlgorithm that will score the networks for you, or, you can supply an array of scores whose indexes match up with the network indexes in the population, and whose values match up to the score that the network should have. Sorry for it not being evident, I will update the documentation to reflect this.

4 Likes

Hi, thanks for fixing the bug with saving. I don’t understand what you mean with how the GeneticAlgorithm already has that feature. The function I added can simultaneously test an entire generation with a specified interval between each member of that generation. That way the testing goes alot faster as the network doesn’t have to wait until one car finishes simulating to send the next one. What code can I use to implement this with the GeneticAlgorithm?

4 Likes

All you have to do is create the function that scores the given network (exactly the same as ScoreFunc), but you don’t supply it when creating GeneticAlgorithm. Instead, keep the ScoreFunc field empty, as it is by default. This will let the GeneticAlgorithm know that you are the one supplying it with the scores, and it is not to calculate them.
Then, you test all of the networks using your scoring function manually. Since you have control over what networks are scored and when, you can score them all at once, and then do :ProcessGeneration(scoreArray).

4 Likes

I tried implementing this, but I can’t figure out how I can get the GeneticAlgorithm to use the trained network. The cars seem to only change their behavior when the generation has fiinished.
This is part of my code:

-- Create a new network with 5 inputs, 2 layers with 4 nodes each and 1 output "steerDirection"
local tempNet = FeedforwardNetwork.new({"front", "frontLeft", "frontRight", "left", "right"}, 2, 4, {"steerDirection"}, feedForwardSettings)
--local tempNet = FeedforwardNetwork.newFromSave(game.ServerStorage.NetworkSave.Value)
local populationSize = 15

local geneticAlgo = ParamEvo.new(tempNet, populationSize, geneticSetting)		-- Create ParamEvo with the tempNet template, population size and settings

local scoreTable = {}
local generations = 50	-- Number of generations to train network with
local firstRun = true
for _ = 0, generations do
	for index = 1, populationSize+1 do
		spawn(function()
			local startTime = os.clock()
			local clone = game:GetService("ServerStorage").Car:Clone()
			clone.RemoteControl.MaxSpeed = 200

			-- Parent to workspace and then setup Scripts of car
			clone.Parent = workspace

			local score = 0
			local bool = true
			local checkpointsHit = {}
			for _, v in pairs(clone:GetDescendants()) do
				if v:IsA("BasePart") and v.CanCollide == true then
					v.Touched:Connect(function(hit)
						if hit.Parent.Parent == workspace.Walls then	-- Destroy car on hit of walls
							bool = false
						elseif hit.Parent == workspace.Checkpoints and not checkpointsHit[tonumber(hit.Name)] then	-- Give extra points when car reaches checkpoint
							local numHit = tonumber(hit.Name)
							score += (numHit * 2)
							checkpointsHit[numHit] = hit
						end
					end)
				end
			end
			while bool do
				local distances = getRayDistances(clone)		-- Get Distances of rays
				local output
				if firstRun then
					output = tempNet(distances)				-- Get output of NN with input distances
				else
					local population = geneticAlgo:GetPopulation()
					local net = population[1].Network
					output = net(distances)				-- Get output of NN with input distances
				end

				-- Set steering direction to direction of NN
				clone.RemoteControl.SteerFloat = output.steerDirection
				-- Set speed of car
				--clone.RemoteControl.MaxSpeed = math.abs(output.speed) * 300

				-- Check if this simulation has been running for longer than x seconds
				if os.clock() > startTime + 90 then
					score -= 40	-- Punish algorithm
					break
				end
				wait()
			end
			
			score += (os.clock() - startTime)/2		-- Increment score based on time alive (longer is better)
			print("Exit score: "..math.floor(score*100)/100)

			clone:Destroy()
			scoreTable[index] = score
		end)
		wait(1)
	end
	-- Wait until generation finished
	repeat
		wait(1)
	until #scoreTable >= populationSize
	
	geneticAlgo:ProcessGeneration(scoreTable)
	scoreTable = {}
	firstRun = false
end

Also, would this save the right (best) network?
local save = geneticAlgo:GetBestNetwork():Save()

4 Likes

You’re still using tempNet in your code. The template network should only be used as a template for GeneticAlgorithm creation and never again.

Why does one of your loops have “populationSize+1”? If you have 15 networks for your population, go past that will give you nil values.

Yes. Just make sure that you have the updated library and that should give you the string of the best network in the population.

4 Likes

Hi, I think I’m very close to actually succeeding here but there’s still one thing I can’t wrap my head around. I am now trying to use your method of training a generation simultaneously, but there are sometimes errors with the sorting of the scores.
This is the code I’m using:

local PhysicsService = game:GetService("PhysicsService")
local Package = game:GetService("ReplicatedStorage").NNLibrary
local FeedforwardNetwork = require(Package.NeuralNetwork.FeedforwardNetwork)
local ParamEvo = require(Package.GeneticAlgorithm.ParamEvo)

local clock = os.clock()
local collisionGroupName = "CarCollisionsDisabled"

-- Setup CollisionGroup for cars
PhysicsService:CreateCollisionGroup(collisionGroupName)
PhysicsService:CollisionGroupSetCollidable(collisionGroupName, collisionGroupName, false)

-- Function to activate scripts inside of car
local function setupCar(car)
	car.RemoteControl.Torque = 1000
	car.RemoteControl.Throttle = 5
	car.RemoteControl.TurnSpeed = 30
	for _, item in pairs(car:GetDescendants()) do
		if item:IsA("BasePart") or item:IsA("UnionOperation") then
			PhysicsService:SetPartCollisionGroup(item, collisionGroupName)
		end
	end

	-- Create ray visualizers
	local ray = Instance.new("Part")
	ray.CanCollide = false
	ray.Massless = true
	ray.CFrame = car.RaycastPart.CFrame
	ray.Size = Vector3.new(1, 1, 1)
	ray.Color = Color3.fromRGB(255, 0, 0)
	ray.Name = "frontRay"
	ray.Parent = car
	local weld = Instance.new("WeldConstraint")
	weld.Part0 = car.RaycastPart
	weld.Part1 = ray
	weld.Parent = ray
end

-- Setup car
local carSource = game:GetService("ServerStorage").Car
setupCar(carSource)

-- Function that casts rays from the car in five directions and returns the distances in a table
local function getRayDistances(car)
	-- Setup RayCastParams
	local rayCastParams = RaycastParams.new()
	rayCastParams.IgnoreWater = true
	rayCastParams.FilterType = Enum.RaycastFilterType.Whitelist
	rayCastParams.FilterDescendantsInstances = {workspace.Walls}

	local bumper = car.RaycastPart
	local bumperPos = bumper.Position
	local dist = 250

	local FrontRay = workspace:Raycast(bumperPos, bumper.CFrame.RightVector * dist, rayCastParams)														-- Cast ray for front
	local FrontLeftRay = workspace:Raycast(bumperPos, Vector3.new(bumper.CFrame.RightVector.X, 0, bumper.CFrame.LookVector.Z) * dist, rayCastParams)	-- Cast ray to frontLeft
	local FrontRightRay = workspace:Raycast(bumperPos, Vector3.new(bumper.CFrame.RightVector.X, 0, -bumper.CFrame.LookVector.Z) * dist, rayCastParams)	-- Cast ray to frontRight
	local LeftRay = workspace:Raycast(bumperPos, bumper.CFrame.LookVector * dist, rayCastParams)														-- Cast ray to left
	local RightRay = workspace:Raycast(bumperPos, -bumper.CFrame.LookVector * dist, rayCastParams)														-- Cast ray to right

	local pos1 = 1
	local pos2 = 1
	local pos3 = 1
	local pos4 = 1
	local pos5 = 1
	if FrontRay then
		pos1 = ((FrontRay.Position - bumperPos).Magnitude) / dist
	end
	if FrontLeftRay then
		pos2 = ((FrontLeftRay.Position - bumperPos).Magnitude) / dist
	end
	if FrontRightRay then
		pos3 = ((FrontRightRay.Position - bumperPos).Magnitude) / dist
	end
	if LeftRay then
		pos4 = ((LeftRay.Position - bumperPos).Magnitude) / dist
	end
	if RightRay then
		pos5 = ((RightRay.Position - bumperPos).Magnitude) / dist
	end

	--local calc = pos1*dist
	--local ray = car.frontRay
	--ray.Size = Vector3.new(calc, 1, 1)
	--ray.Position = Vector3.new(bumperPos.X + calc/2, ray.Position.Y, ray.Position.Z)
	return {front = pos1, frontLeft = pos2, frontRight = pos3, left = pos4, right = pos5}
end

-- Settings for genetic algorithm
local geneticSetting = {
	--[[ The function that, when given the network, will return it's score.
	ScoreFunction = function(net)
		local startTime = os.clock()
		local clone = game:GetService("ServerStorage").Car:Clone()
		clone.RemoteControl.MaxSpeed = 200

		-- Parent to workspace and then setup Scripts of car
		clone.Parent = workspace

		local score = 0
		local bool = true
		local checkpointsHit = {}
		for _, v in pairs(clone:GetDescendants()) do
			if v:IsA("BasePart") and v.CanCollide == true then
				v.Touched:Connect(function(hit)
					if hit.Parent.Parent == workspace.Walls then	-- Destroy car on hit of walls
						bool = false
					elseif hit.Parent == workspace.Checkpoints and not checkpointsHit[tonumber(hit.Name)] then	-- Give extra points when car reaches checkpoint
						local numHit = tonumber(hit.Name)
						score += (numHit * 2)
						checkpointsHit[numHit] = hit
					end
				end)
			end
		end
		while bool do
			local distances = getRayDistances(clone)		-- Get Distances of rays
			local output = net(distances)					-- Get output of NN with input distances

			-- Set steering direction to direction of NN
			clone.RemoteControl.SteerFloat = output.steerDirection
			-- Set speed of car
			--clone.RemoteControl.MaxSpeed = math.abs(output.speed) * 300

			-- Check if this simulation has been running for longer than x seconds
			if os.clock() > startTime + 90 then
				score -= 40	-- Punish algorithm
				break
			end
			wait()
		end
		
		score += (os.clock() - startTime)/2		-- Increment score based on time alive (longer is better)
		print("Exit score: "..math.floor(score*100)/100)

		clone:Destroy()
		return score
	end;]]
	-- The function that runs when a generation is complete. It is given the genetic algorithm as input.
	PostFunction = function(geneticAlgo)
		local info = geneticAlgo:GetInfo()
		print("Generation " .. info.Generation .. ", Best Score: " .. info.BestScore)
	end;

	HigherScoreBetter = true;
	
	PercentageToKill = 0.4;
	PercentageOfKilledToRandomlySpare = 0.1;
	PercentageOfBestParentToCrossover = 0.8;
	PercentageToMutate = 0.8;
	
	MutateBestNetwork = true;
	PercentageOfCrossedToMutate = 0.6;
	--NumberOfNodesToMutate = 3;
	--ParameterMutateRange = 3;
}

local feedForwardSettings = {
	HiddenActivationName = "ReLU";
	OutputActivationName = "Tanh";
	--Bias = 0;
    --LearningRate = 0.1;
    --RandomizeWeights = true;
}

-- Create a new network with 5 inputs, 2 layers with 4 nodes each and 1 output "steerDirection"
local tempNet = FeedforwardNetwork.new({"front", "frontLeft", "frontRight", "left", "right"}, 2, 4, {"steerDirection"}, feedForwardSettings)
--local tempNet = FeedforwardNetwork.newFromSave(game.ServerStorage.NetworkSave.Value)

local populationSize = 20
local geneticAlgo = ParamEvo.new(tempNet, populationSize, geneticSetting)		-- Create ParamEvo with the tempNet template, population size and settings

local function roundDecimals(num, places)
    places = math.pow(10, places or 0)
    num = num * places
    if num >= 0 then 
        num = math.floor(num + 0.5) 
    else 
        num = math.ceil(num - 0.5) 
    end
    return num / places
end

local scoreTable = {}
local generations = 30	-- Number of generations to train network with
for _ = 1, generations do
	for index = 1, populationSize do
		local newThread = coroutine.create(function()
			local startTime = os.clock()
			local clone = game:GetService("ServerStorage").Car:Clone()
			clone.RemoteControl.MaxSpeed = 200
			-- Parent to workspace and then setup Scripts of car
			clone.Parent = workspace

			local score = 0
			local bool = true
			local checkpointsHit = {}
			for _, v in pairs(clone:GetDescendants()) do
				if v:IsA("BasePart") and v.CanCollide == true then
					v.Touched:Connect(function(hit)
						if hit.Parent.Parent == workspace.Walls then	-- Destroy car on hit of walls
							bool = false
						elseif hit.Parent == workspace.Checkpoints and not checkpointsHit[tonumber(hit.Name)] then	-- Give extra points when car reaches checkpoint
							local numHit = tonumber(hit.Name)
							if numHit and typeof(numHit) == "number" then
								score += (numHit * 2)
								checkpointsHit[numHit] = hit
							end
						end
					end)
				end
			end

			-- Setup Algorithm
			local net = geneticAlgo:GetPopulation()[index].Network
			while bool do
				local distances = getRayDistances(clone)				-- Get Distances of rays
				local output = net(distances)	-- Get output of NN with input distances

				-- Set steering direction to direction of NN
				clone.RemoteControl.SteerFloat = output.steerDirection
				-- Set speed of car
				--clone.RemoteControl.MaxSpeed = math.abs(output.speed) * 300

				-- Check if this simulation has been running for longer than x seconds
				if os.clock() > startTime + 90 then
					score -= 40	-- Punish algorithm
					break
				end
				wait()
			end
			clone:Destroy()
			
			score = score + (os.clock() - startTime)/2		-- Increment score based on time alive (longer is better)

			--print("Exit score: " .. score)
			scoreTable[index] = roundDecimals(score, 2)
		end)
		coroutine.resume(newThread)
		wait(0.5)
	end
	-- Wait until generation finished
	while #scoreTable < populationSize do
		wait(1)
	end
	print(scoreTable)
	
	geneticAlgo:ProcessGeneration(scoreTable)
	scoreTable = {}
	print(scoreTable)
end

local save = geneticAlgo:GetBestNetwork():Save()
game.ServerStorage.NetworkSave.Value = save
print(save)

Here is the place file: Neural Network Test.rbxl (1.0 MB)

This is the error I’m getting:

ReplicatedStorage.NNLibrary.GeneticAlgorithm:195: attempt to compare nil and number  -  Server  -  GeneticAlgorithm:195

I think there is something wrong with my scoring system, but I’m not sure what. I tried debugging and found out that the ScoreTable sometimes returns “void” values? I don’t know how this is happening. Thanks.

4 Likes

The reason is due to the way array size is determined. Array size is equal to the greatest index in the array. If the 20th car hits a wall before other cars are finished, it will end the generation, leaving unfinished car scores as ‘void’, another word for nil (not sure why Roblox didn’t use ‘nil’ here but whatever).
Instead, you need to keep a counter for how many cars have finished, ‘numFinished’, and replace #scoreTable with it.
Whenever a car submits it’s score, just increment this counter by 1.
With this, 3 tiny lines of code are edited. Nothing else is changed.

local PhysicsService = game:GetService("PhysicsService")
local Package = game:GetService("ReplicatedStorage").NNLibrary
local FeedforwardNetwork = require(Package.NeuralNetwork.FeedforwardNetwork)
local ParamEvo = require(Package.GeneticAlgorithm.ParamEvo)

local clock = os.clock()
local collisionGroupName = "CarCollisionsDisabled"

-- Setup CollisionGroup for cars
PhysicsService:CreateCollisionGroup(collisionGroupName)
PhysicsService:CollisionGroupSetCollidable(collisionGroupName, collisionGroupName, false)

-- Function to activate scripts inside of car
local function setupCar(car)
	car.RemoteControl.Torque = 1000
	car.RemoteControl.Throttle = 5
	car.RemoteControl.TurnSpeed = 30
	for _, item in pairs(car:GetDescendants()) do
		if item:IsA("BasePart") or item:IsA("UnionOperation") then
			PhysicsService:SetPartCollisionGroup(item, collisionGroupName)
		end
	end

	-- Create ray visualizers
	local ray = Instance.new("Part")
	ray.CanCollide = false
	ray.Massless = true
	ray.CFrame = car.RaycastPart.CFrame
	ray.Size = Vector3.new(1, 1, 1)
	ray.Color = Color3.fromRGB(255, 0, 0)
	ray.Name = "frontRay"
	ray.Parent = car
	local weld = Instance.new("WeldConstraint")
	weld.Part0 = car.RaycastPart
	weld.Part1 = ray
	weld.Parent = ray
end

-- Setup car
local carSource = game:GetService("ServerStorage").Car
setupCar(carSource)

-- Function that casts rays from the car in five directions and returns the distances in a table
local function getRayDistances(car)
	-- Setup RayCastParams
	local rayCastParams = RaycastParams.new()
	rayCastParams.IgnoreWater = true
	rayCastParams.FilterType = Enum.RaycastFilterType.Whitelist
	rayCastParams.FilterDescendantsInstances = {workspace.Walls}

	local bumper = car.RaycastPart
	local bumperPos = bumper.Position
	local dist = 250

	local FrontRay = workspace:Raycast(bumperPos, bumper.CFrame.RightVector * dist, rayCastParams)														-- Cast ray for front
	local FrontLeftRay = workspace:Raycast(bumperPos, Vector3.new(bumper.CFrame.RightVector.X, 0, bumper.CFrame.LookVector.Z) * dist, rayCastParams)	-- Cast ray to frontLeft
	local FrontRightRay = workspace:Raycast(bumperPos, Vector3.new(bumper.CFrame.RightVector.X, 0, -bumper.CFrame.LookVector.Z) * dist, rayCastParams)	-- Cast ray to frontRight
	local LeftRay = workspace:Raycast(bumperPos, bumper.CFrame.LookVector * dist, rayCastParams)														-- Cast ray to left
	local RightRay = workspace:Raycast(bumperPos, -bumper.CFrame.LookVector * dist, rayCastParams)														-- Cast ray to right

	local pos1 = 1
	local pos2 = 1
	local pos3 = 1
	local pos4 = 1
	local pos5 = 1
	if FrontRay then
		pos1 = ((FrontRay.Position - bumperPos).Magnitude) / dist
	end
	if FrontLeftRay then
		pos2 = ((FrontLeftRay.Position - bumperPos).Magnitude) / dist
	end
	if FrontRightRay then
		pos3 = ((FrontRightRay.Position - bumperPos).Magnitude) / dist
	end
	if LeftRay then
		pos4 = ((LeftRay.Position - bumperPos).Magnitude) / dist
	end
	if RightRay then
		pos5 = ((RightRay.Position - bumperPos).Magnitude) / dist
	end

	--local calc = pos1*dist
	--local ray = car.frontRay
	--ray.Size = Vector3.new(calc, 1, 1)
	--ray.Position = Vector3.new(bumperPos.X + calc/2, ray.Position.Y, ray.Position.Z)
	return {front = pos1, frontLeft = pos2, frontRight = pos3, left = pos4, right = pos5}
end

-- Settings for genetic algorithm
local geneticSetting = {
	--[[ The function that, when given the network, will return it's score.
	ScoreFunction = function(net)
		local startTime = os.clock()
		local clone = game:GetService("ServerStorage").Car:Clone()
		clone.RemoteControl.MaxSpeed = 200

		-- Parent to workspace and then setup Scripts of car
		clone.Parent = workspace

		local score = 0
		local bool = true
		local checkpointsHit = {}
		for _, v in pairs(clone:GetDescendants()) do
			if v:IsA("BasePart") and v.CanCollide == true then
				v.Touched:Connect(function(hit)
					if hit.Parent.Parent == workspace.Walls then	-- Destroy car on hit of walls
						bool = false
					elseif hit.Parent == workspace.Checkpoints and not checkpointsHit[tonumber(hit.Name)] then	-- Give extra points when car reaches checkpoint
						local numHit = tonumber(hit.Name)
						score += (numHit * 2)
						checkpointsHit[numHit] = hit
					end
				end)
			end
		end
		while bool do
			local distances = getRayDistances(clone)		-- Get Distances of rays
			local output = net(distances)					-- Get output of NN with input distances

			-- Set steering direction to direction of NN
			clone.RemoteControl.SteerFloat = output.steerDirection
			-- Set speed of car
			--clone.RemoteControl.MaxSpeed = math.abs(output.speed) * 300

			-- Check if this simulation has been running for longer than x seconds
			if os.clock() > startTime + 90 then
				score -= 40	-- Punish algorithm
				break
			end
			wait()
		end
		
		score += (os.clock() - startTime)/2		-- Increment score based on time alive (longer is better)
		print("Exit score: "..math.floor(score*100)/100)

		clone:Destroy()
		return score
	end;]]
	-- The function that runs when a generation is complete. It is given the genetic algorithm as input.
	PostFunction = function(geneticAlgo)
		local info = geneticAlgo:GetInfo()
		print("Generation " .. info.Generation .. ", Best Score: " .. info.BestScore)
	end;

	HigherScoreBetter = true;
	
	PercentageToKill = 0.4;
	PercentageOfKilledToRandomlySpare = 0.1;
	PercentageOfBestParentToCrossover = 0.8;
	PercentageToMutate = 0.8;
	
	MutateBestNetwork = true;
	PercentageOfCrossedToMutate = 0.6;
	--NumberOfNodesToMutate = 3;
	--ParameterMutateRange = 3;
}

local feedForwardSettings = {
	HiddenActivationName = "ReLU";
	OutputActivationName = "Tanh";
	--Bias = 0;
    --LearningRate = 0.1;
    --RandomizeWeights = true;
}

-- Create a new network with 5 inputs, 2 layers with 4 nodes each and 1 output "steerDirection"
local tempNet = FeedforwardNetwork.new({"front", "frontLeft", "frontRight", "left", "right"}, 2, 4, {"steerDirection"}, feedForwardSettings)
--local tempNet = FeedforwardNetwork.newFromSave(game.ServerStorage.NetworkSave.Value)

local populationSize = 20
local geneticAlgo = ParamEvo.new(tempNet, populationSize, geneticSetting)		-- Create ParamEvo with the tempNet template, population size and settings

local scoreTable = {}
local generations = 30	-- Number of generations to train network with
for _ = 1, generations do
	
	local numFinished = 0
	for index = 1, populationSize do
		local newThread = coroutine.create(function()
			local startTime = os.clock()
			local clone = game:GetService("ServerStorage").Car:Clone()
			clone.RemoteControl.MaxSpeed = 200
			-- Parent to workspace and then setup Scripts of car
			clone.Parent = workspace

			local score = 0
			local bool = true
			local checkpointsHit = {}
			for _, v in pairs(clone:GetDescendants()) do
				if v:IsA("BasePart") and v.CanCollide == true then
					v.Touched:Connect(function(hit)
						if hit.Parent.Parent == workspace.Walls then	-- Destroy car on hit of walls
							bool = false
						elseif hit.Parent == workspace.Checkpoints and not checkpointsHit[tonumber(hit.Name)] then	-- Give extra points when car reaches checkpoint
							local numHit = tonumber(hit.Name)
							if numHit and typeof(numHit) == "number" then
								score += (numHit * 2)
								checkpointsHit[numHit] = hit
							end
						end
					end)
				end
			end

			-- Setup Algorithm
			local net = geneticAlgo:GetPopulation()[index].Network
			while bool do
				local distances = getRayDistances(clone)				-- Get Distances of rays
				local output = net(distances)	-- Get output of NN with input distances

				-- Set steering direction to direction of NN
				clone.RemoteControl.SteerFloat = output.steerDirection
				-- Set speed of car
				--clone.RemoteControl.MaxSpeed = math.abs(output.speed) * 300

				-- Check if this simulation has been running for longer than x seconds
				if os.clock() > startTime + 90 then
					score -= 40	-- Punish algorithm
					break
				end
				wait()
			end
			clone:Destroy()
			
			score = score + (os.clock() - startTime)/2		-- Increment score based on time alive (longer is better)

			--print("Exit score: " .. score)
			scoreTable[index] = score
			
			numFinished += 1
		end)
		coroutine.resume(newThread)
		wait(0.5)
	end
	-- Wait until generation finished
	while numFinished < populationSize do
		wait(1)
	end
	print(scoreTable)
	
	geneticAlgo:ProcessGeneration(scoreTable)
	scoreTable = {}
	print(scoreTable)
end

local save = geneticAlgo:GetBestNetwork():Save()
game.ServerStorage.NetworkSave.Value = save
print(save)

--[[ * Code for running network
for i = 1, 20 do
	local clone = game:GetService("ServerStorage").Car:Clone()
	clone.RemoteControl.MaxSpeed = 200
	clone.Parent = workspace

	local bool = true
	for _, v in pairs(clone:GetDescendants()) do
		if v:IsA("BasePart") and v.CanCollide == true then
			v.Touched:Connect(function(hit)
				if hit.Parent.Parent == workspace.Walls then	-- Destroy car on hit of walls
					bool = false
				end
			end)
		end
	end
	while bool do
		local distances = getRayDistances(clone)	-- Get Distances of rays
		local output = tempNet(distances)			-- Get output of NN with input distances

		-- Set steering direction to direction of NN
		clone.RemoteControl.SteerFloat = output.steerDirection
		wait()
	end
	clone:Destroy()
end]]
7 Likes

Sorry to ask, but the sigmoid function shouldn‘t return a value between -1 and 1? And that ReLU is better because it returns a value between 0 and 1 or infinity.

Still, I just saw that it has his own website too, wich is amazing. If you want to do a short tutorial, consider to make one about “How to setup a own website with Mk Materials“ (No, really, I can‘t get it :smile:) But, thanks for put in much effort just to make our developpement easier, it‘s great tho have members in the community like you

P.S.: Why shouldn‘t we use TensorFlow? Isn‘t it compatible with Lua or is here one reason to use your module instead of TensorFlow‘s?

4 Likes

this basically generates the path for the AI like PathfindingService ?, does it detect any nearby objects and checks if it’s a player If not then that’s fine but if he knows then he is big :brain: and it just makes the NPC or AI Smart?

2 Likes

It shouldn’t. I didn’t write that it should anywhere but if you encounter a situation where I did write that it does or the library produces incorrect outputs, let me know.

LeakyReLU is better because it discourages negative activations while not completely cutting them off. It is necessary for some network designs in order to reduce the risk of dead nodes.

I didn’t write that you shouldn’t use TensorFlow; in fact, you should, if possible. It’s just that TensorFlow is for Python, not Lua. If you want a ML library for Lua, you have to use Torch. But, because Torch isn’t made for Luau and Roblox in general, a custom library like this is necessary.

3 Likes

I’m not really sure what you’re talking about. This ML library has nothing to do with with generating paths like the PathfindingService, unless you’re talking about self-driving vehicles. In which case, sort of.
Self-driving vehicles that use simple neural networks don’t generate a path; they just navigate through their immediate environment in the best possible way they were taught. This doesn’t replace pathfinding, but it can complement it.

2 Likes

Just thought I’d point out something in the example code that I think was a typo

--2 layers with 2 nodes each, and 1 output 'out'.
local net = FeedforwardNetwork.new({"x","y"},2,3,{"out"},setting)

this code creates two layers with 3 nodes but it says there are only two hidden layers with two nodes. I got stumped on this and had to look at the code to figure it out but others may find it confusing so I thought I’d mention it.

side note:
A feature request, unlimited amount of layers with a selected amount of nodes would be nice. Ex:

-- 5 layers, 1st being the input x, y; 2-4 is
-- hidden nodes; and 5 is the output.
local net = ({"x", "y"}, {6, 4, 3}, {"out"}, settings)

I don’t think it would make much of a difference but if it’s possible, it would be nice.

2 Likes

I just have another question to make my understanding better: In your example code its basically just for loops and when the minimum number reaches the generations number it ends but how would I make it infinite and train the AI forever?

2 Likes

Hey i don’t have much NN experience but I want to ask whats the purpose of this function?

function isAboveFunction(x,y)
    if x^3 + 2*x^2 < y then
        return 0
    end
    return 1
end

And are there any resources I can learn about Nueral Networks?

2 Likes

He is summing the delta weights i think. I usually just do weights * previousLayer but I saw this method on 3blue1brown when working with biases

3 Likes

but whats it purpose? Is it necessory?

2 Likes

Yes, in machine learning at least we are simply tweaking these weights to get a desired result. It is not alive but rather finding the minima of a data model. This is why I cringe when I hear elon talking about ai btw :wink:

4 Likes

just forward propogate and backword propogate in a infinite loop, updating the weights each loop. I dont really understand what u mean maybe i am giving you a extremly oversimplified explanation

3 Likes

Like @Kironte what a neural network actually is is it receives inputs and gives out outputs by messing with activation functions. The data that is recorded or trained in a sense is weights, the synapses in a sense that connect the neurons/nodes. In a sense we are finding the minima to achieve our target goal form our data model.

3 Likes

what kind of weight did you mean this??

local weights = 100

for _ = 1, weights, 1 do
   -- ...
end
2 Likes