ニューラルネットワークを使えるようにする。
Luaで、引いてはNScripterで人工知能の一種であるニューラルネットワークを使えるようにする。
http://www.forums.evilmana.com/psp-lua-codebase/lua-neural-networks/をコピペ。
NeuralNetwork.lua
ACTIVATION_RESPONSE = 1 NeuralNetwork = { transfer = function( x) return 1 / (1 + math.exp(-x / ACTIVATION_RESPONSE)) end --This is the Transfer function (in this case a sigmoid) } function NeuralNetwork.create( _numInputs, _numOutputs, _numHiddenLayers, _neuronsPerLayer, _learningRate) _numInputs = _numInputs or 1 _numOutputs = _numOutputs or 1 _numHiddenLayers = _numHiddenLayers or math.ceil(_numInputs/2) _neuronsPerLayer = _neuronsPerLayer or math.ceil(_numInputs*.66666+_numOutputs) _learningRate = _learningRate or .5 --order goes network[layer][neuron][wieght] local network = setmetatable({ learningRate = _learningRate },{ __index = NeuralNetwork}); network[1] = {} --Input Layer for i = 1,_numInputs do network[1][i] = {} end for i = 2,_numHiddenLayers+2 do --plus 2 represents the output layer (also need to skip input layer) network[i] = {} local neuronsInLayer = _neuronsPerLayer if i == _numHiddenLayers+2 then neuronsInLayer = _numOutputs end for j = 1,neuronsInLayer do network[i][j] = {bias = math.random()*2-1} local numNeuronInputs = table.getn(network[i-1]) for k = 1,numNeuronInputs do network[i][j][k] = math.random()*2-1 --return random number between -1 and 1 end end end return network end function NeuralNetwork:forewardPropagate(...) if table.getn(arg) ~= table.getn(self[1]) and type(arg[1]) ~= "table" then error("Neural Network received "..table.getn(arg).." input[s] (expected "..table.getn(self[1]).." input[s])",2) elseif type(arg[1]) == "table" and table.getn(arg[1]) ~= table.getn(self[1]) then error("Neural Network received "..table.getn(arg[1]).." input[s] (expected "..table.getn(self[1]).." input[s])",2) end local outputs = {} for i = 1,table.getn(self) do for j = 1,table.getn(self[i]) do if i == 1 then if type(arg[1]) == "table" then self[i][j].result = arg[1][j] else self[i][j].result = arg[j] end else self[i][j].result = self[i][j].bias for k = 1,table.getn(self[i][j]) do self[i][j].result = self[i][j].result + (self[i][j][k]*self[i-1][k].result) end self[i][j].result = NeuralNetwork.transfer(self[i][j].result) if i == table.getn(self) then table.insert(outputs,self[i][j].result) end end end end return outputs end function NeuralNetwork:backwardPropagate(inputs,desiredOutputs) if table.getn(inputs) ~= table.getn(self[1]) then error("Neural Network received "..table.getn(inputs).." input[s] (expected "..table.getn(self[1]).." input[s])",2) elseif table.getn(desiredOutputs) ~= table.getn(self[table.getn(self)]) then error("Neural Network received "..table.getn(desiredOutputs).." desired output[s] (expected "..table.getn(self[table.getn(self)]).." desired output[s])",2) end self:forewardPropagate(inputs) --update the internal inputs and outputs for i = table.getn(self),2,-1 do --iterate backwards (nothing to calculate for input layer) local tempResults = {} for j = 1,table.getn(self[i]) do if i == table.getn(self) then --special calculations for output layer self[i][j].delta = (desiredOutputs[j] - self[i][j].result) * self[i][j].result * (1 - self[i][j].result) else local weightDelta = 0 for k = 1,table.getn(self[i+1]) do weightDelta = weightDelta + self[i+1][k][j]*self[i+1][k].delta end self[i][j].delta = self[i][j].result * (1 - self[i][j].result) * weightDelta end end end for i = 2,table.getn(self) do for j = 1,table.getn(self[i]) do self[i][j].bias = self[i][j].delta * self.learningRate for k = 1,table.getn(self[i][j]) do self[i][j][k] = self[i][j][k] + self[i][j].delta * self.learningRate * self[i-1][k].result end end end end function NeuralNetwork:save() --[[ File specs: |INFO| - should be FF BP NN |I| - number of inputs |O| - number of outputs |HL| - number of hidden layers |NHL| - number of neurons per hidden layer |LR| - learning rate |BW| - bias and weight values ]]-- local data = "|INFO|FF BP NN|I|"..tostring(table.getn(self[1])).."|O|"..tostring(table.getn(self[table.getn(self)])).."|HL|"..tostring(table.getn(self)-2).."|NHL|"..tostring(table.getn(self[2])).."|LR|"..tostring(self.learningRate).."|BW|" for i = 2,table.getn(self) do -- nothing to save for input layer for j = 1,table.getn(self[i]) do local neuronData = tostring(self[i][j].bias).."{" for k = 1,table.getn(self[i][j]) do neuronData = neuronData..tostring(self[i][j][k]) neuronData = neuronData.."," end data = data..neuronData.."}" end end data = data.."|END|" return data end function NeuralNetwork.load( data) local dataPos = string.find(data,"|")+1 local currentChunk = string.sub( data, dataPos, string.find(data,"|",dataPos)-1) local dataPos = string.find(data,"|",dataPos)+1 local _inputs, _outputs, _hiddenLayers, _neuronsPerLayer, _learningRate local biasWeights = {} local errorExit = false while currentChunk ~= "END" and not errorExit do if currentChuck == "INFO" then currentChunk = string.sub( data, dataPos, string.find(data,"|",dataPos)-1) dataPos = string.find(data,"|",dataPos)+1 if currentChunk ~= "FF BP NN" then errorExit = true end elseif currentChunk == "I" then currentChunk = string.sub( data, dataPos, string.find(data,"|",dataPos)-1) dataPos = string.find(data,"|",dataPos)+1 _inputs = tonumber(currentChunk) elseif currentChunk == "O" then currentChunk = string.sub( data, dataPos, string.find(data,"|",dataPos)-1) dataPos = string.find(data,"|",dataPos)+1 _outputs = tonumber(currentChunk) elseif currentChunk == "HL" then currentChunk = string.sub( data, dataPos, string.find(data,"|",dataPos)-1) dataPos = string.find(data,"|",dataPos)+1 _hiddenLayers = tonumber(currentChunk) elseif currentChunk == "NHL" then currentChunk = string.sub( data, dataPos, string.find(data,"|",dataPos)-1) dataPos = string.find(data,"|",dataPos)+1 _neuronsPerLayer = tonumber(currentChunk) elseif currentChunk == "LR" then currentChunk = string.sub( data, dataPos, string.find(data,"|",dataPos)-1) dataPos = string.find(data,"|",dataPos)+1 _learningRate = tonumber(currentChunk) elseif currentChunk == "BW" then currentChunk = string.sub( data, dataPos, string.find(data,"|",dataPos)-1) dataPos = string.find(data,"|",dataPos)+1 local subPos = 1 local subChunk for i = 1,_hiddenLayers+1 do biasWeights[i] = {} local neuronsInLayer = _neuronsPerLayer if i == _hiddenLayers+1 then neuronsInLayer = _outputs end for j = 1,neuronsInLayer do biasWeights[i][j] = {} biasWeights[i][j].bias = tonumber(string.sub(currentChunk,subPos,string.find(currentChunk,"{",subPos)-1)) subPos = string.find(currentChunk,"{",subPos)+1 subChunk = string.sub( currentChunk, subPos, string.find(currentChunk,",",subPos)-1) local maxPos = string.find(currentChunk,"}",subPos) while subPos < maxPos do table.insert(biasWeights[i][j],tonumber(subChunk)) subPos = string.find(currentChunk,",",subPos)+1 if string.find(currentChunk,",",subPos) ~= nil then subChunk = string.sub( currentChunk, subPos, string.find(currentChunk,",",subPos)-1) end end subPos = maxPos+1 end end end currentChunk = string.sub( data, dataPos, string.find(data,"|",dataPos)-1) dataPos = string.find(data,"|",dataPos)+1 end if errorExit then error("Failed to load Neural Network:"..currentChunk,2) end local network = setmetatable({ learningRate = _learningRate },{ __index = NeuralNetwork}); network[1] = {} --Input Layer for i = 1,_inputs do network[1][i] = {} end for i = 2,_hiddenLayers+2 do --plus 2 represents the output layer (also need to skip input layer) network[i] = {} local neuronsInLayer = _neuronsPerLayer if i == _hiddenLayers+2 then neuronsInLayer = _outputs end for j = 1,neuronsInLayer do network[i][j] = {bias = biasWeights[i-1][j].bias} local numNeuronInputs = table.getn(network[i-1]) for k = 1,numNeuronInputs do network[i][j][k] = biasWeights[i-1][j][k] end end end return network end
使い方
NL_dofile("NeuralNetwork.lua") -- まずは読み込む。 -- ネットワークを作る。サンプルとして、xorを実現する人工知能を作る。 input = 2 -- 入力用ニューロンの数。 output = 1 -- 出力用ニューロンの数。 -- xor は二つの入力から一つの出力を発生させる。なので、2と1になる。 hiddenLayers = 1 -- 隠れレイヤーの数 neuronsPerLayer = 4 -- 隠れレイヤー一つあたりのニューロンの数。 -- 隠れレイヤーは1以上の整数ならなんでもいいが普通は1でよい。増やしても極端に精度がよくなる訳でもない。レイヤーあたりの数は、適当。 learningRate = 0.5 -- 学習精度。0.5くらいで始めて。増やすと大雑把に、減らすと細かくなる。減らすほど学習に時間がかかることも忘れずに。 network = NeuralNetwork.create(input,output,hiddenLayers,neuronsPerLayer,learningRate) -- networkに作成した人工知能が入る。以降はこれに対して操作をする。 -- 計算をさせる。引数は、入力用ニューロンに最初に与える値。 result = network:forewardPropagate(in1,in2) -- 結果はテーブルの形を取る。result[1]のようにしてアクセスする。 -- 学習をさせる。引数は、二つのテーブル。最初のテーブルは入力用ニューロンに与える値、第二引数は教師信号 network:backwardPropagate({0,0},{0}) -- ただし、一度の学習ではちょっとしか学習しないので、10,000回はぶん回すのが基本。 for i = 1, 10000 do network:backwardPropagate({0,0},{0}) end -- これならいい加減覚えるだろう。きっと多分。 -- 人工知能を保存する。 str = network:save() -- str にダンプした人工知能の文字列が入る。これをNScripterの文字列変数に保存してもよい。 -- 人工知能を再生する。 new_network = NeuralNetwork.load(str) -- ちなみに実際やってみると、同じ人工知能でも保存・再生で微妙に差異が出る模様。コピーすると劣化するとか、生物っぽいね!
これはどういうもので、何ができるか。
ニューラルネットワークは、一種の人工知能である。
入力には、0〜1までの数値*1を入力用ニューロンの数と同じだけ与えると、内部で謎の計算をして、出力用ニューロンの数だけやはり0〜1の範囲の数値を返してくる。
作られたばかりの初期値では内部がランダムに設定されているので、出力される数値もデタラメである。
result = network:fowardPropagate(i1,i2)
そこで、教師信号を与えて学習をさせる。つまり、ある入力の時は、教師信号と同じ数値(実際には近似値)を返すように繰り返し躾けるのである。
for i=1,10000 do network:backwardPropagate({i1,i2},{o1}) end
こうして学習したニューラルネットワークは、その特定の入力を与えれば特定の出力を返すし、逆にそれ以外の入力を与えても「それっぽい」出力を返すようになる。
この「それっぽい」が肝心なところで、どんな難問にも回答不能に陥ることなく、なんらかの回答を返す堅牢性がニューラルネットワークの特徴である。(その回答が正しいかどうかはまた別の問題)
*1:実はこの範囲から逸脱してもよい。