module NeuralNetwork open MathNet.Numerics.Distributions open MathNet.Numerics open System open MathNet.Numerics.LinearAlgebra open LinearAlgebra.Matrix // Byte value must be normalized to range from 0.01 to 0.99. let normalizeFloat0 = 0.01 let normalizeFloat1 v : float = v * 0.98 + normalizeFloat0 let denormalizeFloat1 v : float = (v - normalizeFloat0) / 0.98 let randomMatrix outputRowsCount inputColsumnsCount = // Scientifically proved optimal standard deviation. let stdDev = 1.0 / (sqrt (float inputColsumnsCount)) DenseMatrix.init outputRowsCount inputColsumnsCount (fun _ _ -> Normal.Sample(0.0, stdDev) |> float) // Matrixes of weightes of neuron connections beween neural network layers. let randomMatrixList (layersSizeList : int list) = layersSizeList |> Seq.pairwise |> Seq.map (fun (leftCount, rightCount) -> randomMatrix rightCount leftCount) |> List.ofSeq /// Normalize neural network layer output by sigmoid function. let logisticVector = Vector.map SpecialFunctions.Logistic /// Converts neural network layer output to sigmiod-inverse input. let logitVector = Vector.map SpecialFunctions.Logit type Model = Matrix list type DataVector = Vector // Query trained neural network let query (layers : Model) (inputs : DataVector) = List.fold (fun vector matrix -> matrix * vector |> logisticVector) inputs layers let queryBack (layers : Model) (outputs : DataVector) = let scaleVectorToNormaziedFloat1 v = let removeLow = v - (Vector.min v) let scaleTop = removeLow / (Vector.max removeLow) scaleTop |> Vector.map normalizeFloat1 let queryBackIter rightOutput matrix = let rightInput = rightOutput |> logitVector; let leftOutput = (transpose matrix) * rightInput |> scaleVectorToNormaziedFloat1 leftOutput layers |> List.rev |> List.fold queryBackIter outputs // Trains neural network by data sample let trainSample (learningRate: float) (allLayerWeigthes: Model) (neuralNetworkInputs: DataVector) (neuralNetworkTartets: DataVector) = if List.isEmpty allLayerWeigthes then raise (ArgumentException("At least one layer required.")) if allLayerWeigthes.Head.ColumnCount <> neuralNetworkInputs.Count then raise (ArgumentException("Input must be multiplied correctly.")) if (List.last allLayerWeigthes).RowCount <> neuralNetworkTartets.Count then raise (ArgumentException("Target must be multiplied correctly.")) let rec iter previousOutputs weightMatrixList = match weightMatrixList with | weightMatrix :: sublayers -> let nextInputs = weightMatrix * previousOutputs let nextOutputs = nextInputs |> logisticVector let (nextErrors : DataVector), sublayersUpdated = iter nextOutputs sublayers let weightMatrixDelta = DenseMatrix.ofColumns [nextErrors.PointwiseMultiply(nextOutputs).PointwiseMultiply(1.0 - nextOutputs)] * DenseMatrix.ofRows [previousOutputs] * learningRate let weightMatrixUpdated = weightMatrix + weightMatrixDelta let previousErrors = (transpose weightMatrix) * nextErrors previousErrors, weightMatrixUpdated :: sublayersUpdated | [] -> let outputErrors = neuralNetworkTartets - previousOutputs outputErrors, [] iter neuralNetworkInputs allLayerWeigthes |> snd