View difference between Paste ID: EhkqZiV2 and gKh8ViK8
SHOW: | | - or go back to the newest paste.
1
--@name NeuroEvolution Experiments
2
--@author Szymekk
3
--@shared
4
5
if CLIENT then return end
6
7
--------------------------------------
8
-- Neuron class
9
--------------------------------------
10
11
local neuron = { }
12
neuron.__index = neuron
13
14
function neuron:init()
15
    self.inputs = { }
16
    self.weights = { }
17
    
18
    self.value = nil
19
end
20
21
function neuron:activationFunction(x)
22
    return 1 / (1 + math.exp(-x)) * 2 - 1
23
    --return x
24
end
25
26
function neuron:addInput(n)
27
    self.inputs[#self.inputs + 1] = n
28
    self.weights[#self.inputs] = math.random() * 2 - 1
29
end
30
31
function neuron:getOutput()
32
    if not self.value and #self.inputs > 0 then
33
        local sum = 0
34
        
35
        for k, v in pairs(self.inputs) do
36
            sum = sum + v:getOutput() * self.weights[k]
37
        end
38
        
39
        self.value = neuron:activationFunction(sum)
40
    end
41
42
    return self.value
43
end
44
45
function neuron.new()
46
    local tbl = { }
47
    setmetatable(tbl, neuron)
48
    tbl:init()
49
    
50
    return tbl
51
end
52
53
--------------------------------------
54
-- Neural network class
55
--------------------------------------
56
57
local network = { }
58
network.__index = network
59
60
function network:init()
61
    self.inputs = { }
62
    self.outputs = { }
63
    self.hiddenLayers = { }
64
    self.allNeurons = { }
65
end
66
67
function network:addInputs(count)
68
    for i = 1, count do
69
        local n = neuron.new()
70
        self.inputs[i] = n
71
        self.allNeurons[#self.allNeurons + 1] = n
72
    end
73
end
74
75
function network:addHiddenLayer(count)
76
    local layer = { }
77
78
    for i = 1, count do
79
        local n = neuron.new()
80
        layer[i] = n
81
        self.allNeurons[#self.allNeurons + 1] = n
82
    end
83
    
84
    self.hiddenLayers[#self.hiddenLayers + 1] = layer
85
    
86
    return layer
87
end
88
89
function network:addOutputs(count)
90
    for i = 1, count do
91
        local n = neuron.new()
92
        self.outputs[i] = n
93
        self.allNeurons[#self.allNeurons + 1] = n
94
    end
95
end
96
97
function network:setInputs(tbl)
98
    for k, v in pairs(self.inputs) do
99
        v.value = tbl[k]
100
    end
101
end
102
103
function network:resetValues()
104
    for k, v in pairs(self.allNeurons) do
105
        v.value = nil
106
    end
107
end
108
109
function network:connectNeurons()
110
    local allLayers = { self.inputs }
111
    for k, v in pairs(self.hiddenLayers) do
112
        allLayers[#allLayers + 1] = v
113
    end
114
    allLayers[#allLayers + 1] = self.outputs
115
116
    for i = 2, #allLayers do
117
        for k1, v1 in pairs(allLayers[i - 1]) do
118
            for k2, v2 in pairs(allLayers[i]) do
119
                v2:addInput(v1)
120
            end
121
        end
122
    end
123
end
124
125
function network:run(tbl)
126
    self:resetValues()
127
    self:setInputs(tbl)
128
129
    local outputValues = { }
130
131
    for k, v in pairs(self.outputs) do
132
        outputValues[k] = v:getOutput()
133
    end
134
    
135
    return outputValues
136
end
137
138
function network:dumpWeights()
139
    local weights = { }
140
    
141
    for k, v in pairs(self.allNeurons) do
142
        weights[k] = { }
143
        
144
        for k2, v2 in pairs(v.weights) do
145
            weights[k][k2] = v2
146
        end
147
    end
148
    
149
    return weights
150
end
151
152
function network:loadWeights(weights)
153
    for k, v in pairs(weights) do
154
        for k2, v2 in pairs(v) do
155
            self.allNeurons[k].weights[k2] = v2
156
        end
157
    end
158
end
159
160
function network.new()
161
    local tbl = { }
162
    setmetatable(tbl, network)
163
    tbl:init()
164
    
165
    return tbl
166
end
167
168
--------------------------------------
169
-- Evolution class
170
--------------------------------------
171
172
local evolution = { }
173
evolution.__index = evolution
174
175
function evolution:init(net)
176
    self.net = net
177
    
178
    self.netsPerGeneration = 100
179
    self.maxGenerations = 40
180
    
181
    self.mutationNeurons = 5
182
    self.mutationRate = 0.01
183
    self.maxWeight = 5
184
end
185
186
function evolution:start()
187
    return coroutine.create(function()
188
        self:yieldedEvolve()
189
    end)
190
end
191
192
function evolution:yieldedEvolve()
193
    self:onStart()
194
195
    local generation = 1
196
    
197
    local lastBest
198
    local mutationRateFactor = 1
199
    
200
    while true do
201
        print("Generation: " .. generation)
202
        
203
        self:onGenerationStart(generation)
204
        
205
        local nets = { }
206
        local weights = self.net:dumpWeights()
207
        
208
        for i = 1, self.netsPerGeneration do
209
            local updatedWeights = self:updateWeights(weights, mutationRateFactor)
210
            self.net:loadWeights(updatedWeights)
211
        
212
            local fitness = self:onUpdate(self.net)
213
            
214
            nets[i] = { fitness, updatedWeights }
215
            
216
            coroutine.yield()
217
        end
218
        
219
        coroutine.yield()
220
        
221
        local best = nets[1]
222
        for k, v in pairs(nets) do
223
            if v[1] < best[1] then
224
                best = v
225
            end
226
        end
227
        
228
        print("Best fitness: ", best[1])
229
        
230
        if lastBest and lastBest[1] * 1 > best[1] then
231
            print("Not better than before")
232
            self.net:loadWeights(lastBest[2])
233
            mutationRateFactor = mutationRateFactor + 0.5
234
            continue
235
        end
236
        
237
        self.net:loadWeights(best[2])
238
        
239
        lastBest = best
240
        
241
        mutationRateFactor = 1
242
        
243
        self:onGenerationEnd(generation)
244
        
245
        if generation >= self.maxGenerations then
246
            break
247
        end
248
        
249
        generation = generation + 1
250
    end
251
    
252
    self:onEnd()
253
end
254
255
-- Evolution begins, all preparation is done here
256
function evolution:onStart() end
257
258
-- Update function of current step, should return current fitness
259
function evolution:onUpdate(net) end
260
261
-- Before training individuals
262
function evolution:onGenerationStart(n) end
263
264
-- After best individual has been chosen
265
function evolution:onGenerationEnd(n) end
266
267
-- The network has evolved
268
function evolution:onEnd() end
269
270
function evolution:updateWeights(weights, factor)
271
    local updated = { }
272
273
    for k, v in pairs(weights) do
274
        updated[k] = { }
275
    
276
        for k2, v2 in pairs(v) do
277
            updated[k][k2] = v2
278
        end
279
    end
280
281
    for i = 1, self.mutationNeurons do
282
        local k = math.random(1, #updated)
283
        
284
        if #updated[k] == 0 then
285
            i = i - 1
286
            continue
287
        end
288
        
289
        local l = math.random(1, #updated[k])
290
        updated[k][l] = math.clamp(updated[k][l] + (math.random() * 2 - 1) * self.mutationRate * factor, -self.maxWeight, self.maxWeight)
291
    end
292
    
293
    return updated
294
end
295
296
function evolution.new(net)
297
    local tbl = { }
298
    setmetatable(tbl, evolution)
299
    tbl:init(net)
300
    
301
    return tbl
302
end
303
304
--------------------------------------
305
-- Action
306
--------------------------------------
307
308
chip():setMaterial("models/shiny")
309
310
local net = network.new()
311
net:addInputs(3)
312
net:addHiddenLayer(3)
313
--net:addHiddenLayer(3)
314
net:addOutputs(1)
315
net:connectNeurons()
316
317
local evo = evolution.new(net)
318
319
evo.netsPerGeneration = 10
320
evo.maxGenerations = 5000
321
322
evo.mutationNeurons = 8
323
evo.mutationRate = 0.2
324
evo.maxWeight = 5
325
326
local evoCoroutine = evo:start()
327
328
local allPower = true
329
330
function evo:onStart()
331
    print("Starting evolution")
332
end
333
334
function scaleInput(a)
335
    return a * 0.1
336
end
337
338
function scaleOutput(a)
339
    return a * 10
340
end
341
342
343
function evo:onGenerationStart()
344
    a, b = math.random(1, 10), math.random(1, 10)
345
end
346
347
startPos = chip():getPos() + Vector(0, 0, 15)
348
349
color = Color(math.random(0, 255), math.random(0, 255), math.random(0, 255))
350
351
baseHolo = holograms.create(startPos, Angle(0, 0, 0), "models/sprops/cuboids/height12/size_2/cube_24x18x12.mdl", Vector(1, 1, 1))
352
baseHolo:setColor(color)
353
traceHolo1 = holograms.create(startPos, Angle(0, 0, 0), "models/sprops/geometry/sphere_6.mdl", Vector(1, 1, 1))
354
traceHolo2 = holograms.create(startPos, Angle(0, 0, 0), "models/sprops/geometry/sphere_6.mdl", Vector(1, 1, 1))
355
traceHolo3 = holograms.create(startPos, Angle(0, 0, 0), "models/sprops/geometry/sphere_6.mdl", Vector(1, 1, 1))
356
357
local bestFitness = 0
358
359
function evo:onUpdate(net)
360
    allPower = false
361
    
362
    local position = startPos
363
    local angle = math.pi / 2
364
    
365
    while true do
366
        position = position + Vector(math.cos(angle), math.sin(angle)) * 2
367
        
368
        baseHolo:setPos(position)
369
        baseHolo:setAngles(Angle(0, angle / math.pi * 180 + 90, 0))
370
        
371
        local tr1 = trace.trace(position, position + Vector(math.cos(angle), math.sin(angle)) * 100)
372
        local tr2 = trace.trace(position, position + Vector(math.cos(angle + math.pi / 4), math.sin(angle + math.pi / 4)) * 100)
373
        local tr3 = trace.trace(position, position + Vector(math.cos(angle - math.pi / 4), math.sin(angle - math.pi / 4)) * 100)
374
        
375
        traceHolo1:setPos(tr1.HitPos)
376
        traceHolo2:setPos(tr2.HitPos)
377
        traceHolo3:setPos(tr3.HitPos)
378
        
379
        if tr1.Fraction < 0.01 or tr2.Fraction < 0.01 or tr3.Fraction < 0.01 then
380
            break
381
        end
382
        
383
        local rawOutput = net:run({ scaleInput(tr1.Fraction), scaleInput(tr2.Fraction), scaleInput(tr3.Fraction) })[1]
384
        local scaledOutput = scaleOutput(rawOutput)
385
        
386
        --[[
387
        if scaledOutput < -0.1 then
388
            angle = angle + 0.02
389
        elseif scaledOutput > 0.1 then
390
            angle = angle - 0.02
391
        end
392
        ]]
393
        
394
        angle = angle + scaledOutput * 0.1
395
        
396
        coroutine.yield()
397
    end
398
399
    allPower = true
400
    
401
    local fitness = (position - startPos).y - (position - startPos).x / 2
402
    
403
    if fitness > bestFitness then
404
        baseHolo:emitSound("resource/warning.wav")
405
        bestFitness = fitness
406
    end
407
    
408
    return fitness
409
end
410
411
function evo:onGenerationEnd()
412
    print("Generation ended")
413
end
414
415
function evo:onEnd()
416
    
417
end
418
419
hook.add("think", "", function()
420
    if allPower then
421
        while coroutine.status(evoCoroutine) ~= "dead" and quotaUsed() / quotaMax() < 0.1 do
422
            coroutine.resume(evoCoroutine)
423
        end
424
    else
425
        if coroutine.status(evoCoroutine) ~= "dead" and quotaUsed() / quotaMax() < 0.1 then
426
            coroutine.resume(evoCoroutine)
427
        end
428
    end
429
end)
430
431
--[[
432
chip():setMaterial("models/shiny")
433
434
local net = network.new()
435
net:addInputs(6)
436
net:addHiddenLayer(3)
437
net:addOutputs(3)
438
net:connectNeurons()
439
440
local evo = evolution.new(net)
441
442
evo.netsPerGeneration = 15
443
evo.maxGenerations = 5000
444
445
evo.mutationNeurons = 8
446
evo.mutationRate = 0.05
447
evo.maxWeight = 5
448
449
local evoCoroutine = evo:start()
450
451
local allPower = true
452
453
function evo:onStart()
454
    print("Starting evolution")
455
end
456
457
function scaleInput(a)
458
    return a * 0.001
459
end
460
461
function scaleOutput(a)
462
    return a * 1000
463
end
464
465
local a, b
466
467
function evo:onGenerationStart()
468
    a, b = math.random(1, 10), math.random(1, 10)
469
end
470
471
startPos = chip():getPos() + Vector(0, 0, 2)
472
desiredPos = startPos + Vector(50, 30, 60)
473
474
holo = holograms.create(desiredPos, Angle(0, 0, 0), "models/sprops/geometry/sphere_9.mdl", Vector(1, 1, 1))
475
476
function evo:onUpdate(net)
477
    allPower = false
478
    
479
    local fitness = 0
480
    local nextEndTime = timer.realtime() + 2
481
    
482
    chip():setPos(startPos)
483
    chip():setFrozen(false)
484
    chip():setVelocity(Vector(0, 0, 0))
485
    chip():enableGravity(false)
486
    
487
    while timer.realtime() < nextEndTime do
488
        fitness = fitness + ((desiredPos - chip():getPos()):getLength() ^ 2) * timer.frametime()
489
        
490
        local relPos = chip():getPos() - desiredPos
491
        local vel = chip():getVelocity()
492
        local rawOutput = net:run({ scaleInput(relPos.x), scaleInput(relPos.y), scaleInput(relPos.z), scaleInput(vel.x), scaleInput(vel.y), scaleInput(vel.z) })
493
        local scaledOutput = Vector(scaleOutput(rawOutput[1]), scaleOutput(rawOutput[2]), scaleOutput(rawOutput[3]))
494
        
495
        chip():applyForceCenter(scaledOutput)
496
        
497
        coroutine.yield()
498
    end
499
500
    allPower = true
501
    
502
    return fitness
503
end
504
505
function evo:onGenerationEnd()
506
    print("Generation ended")
507
end
508
509
function evo:onEnd()
510
    
511
end
512
513
hook.add("think", "", function()
514
    if allPower then
515
        while coroutine.status(evoCoroutine) ~= "dead" and quotaUsed() / quotaMax() < 0.6 do
516
            coroutine.resume(evoCoroutine)
517
        end
518
    else
519
        if coroutine.status(evoCoroutine) ~= "dead" and quotaUsed() / quotaMax() < 0.6 then
520
            coroutine.resume(evoCoroutine)
521
        end
522
    end
523
end)
524
]]