BackPropagationLearningをSIMDに対応してみた

Accord.NET(AForge.NET)のBackPropagationLearningをSIMDを使用するように修正してみた。
速度的には以前に作ったマルチスレッド対応版のBackPropagationLearningの1.2倍程度の早さになってます。
簡略版なので並列数は4に固定。したがってAVX非対応のCPUだと動きません。
並列数2だと、おそらく速度的に変わらないので作るつもりもありません。
DeepBeliefNetworkなど関連するクラスも合わせて修正すればもっと早くなるのだろうけど、とりあえずBackPropagationLearningのみの対応。流石に全部はボリューム多すぎるし・・・orz

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Numerics;
using System.Diagnostics;

using AForge.Neuro;
using AForge.Neuro.Learning;

namespace LernDetectImage
{
    class SimdBackPropagationLearning : BackPropagationLearning
    {
        // network to teach
        private ActivationNetwork network;

        // learning rate
        private double learningRate = 0.1;

        // momentum
        private double momentum = 0.0;

        // neuron's errors
        private Vector<double>[][] neuronErrors = null;

        // weight's updates
        private Vector<double>[][][] weightsUpdates = null;

        // threshold's updates
        private Vector<double>[][] thresholdsUpdates = null;

        public new double LearningRate
        {
            get { return learningRate; }
            set
            {
                learningRate = Math.Max(0.0, Math.Min(1.0, value));
            }
        }

        public new double Momentum
        {
            get { return momentum; }
            set
            {
                momentum = Math.Max(0.0, Math.Min(1.0, value));
            }
        }

        public SimdBackPropagationLearning(ActivationNetwork network) : base(network)
        {
            this.network = network;

            // create error and deltas arrays
            neuronErrors = new Vector<double>[network.Layers.Length][];
            weightsUpdates = new Vector<double>[network.Layers.Length][][];
            thresholdsUpdates = new Vector<double>[network.Layers.Length][];

            // initialize errors and deltas arrays for each layer
            for (int i = 0; i < network.Layers.Length; i++)
            {
                Layer layer = network.Layers[i];

                neuronErrors[i] = new Vector<double>[layer.Neurons.Length / 4];
                weightsUpdates[i] = new Vector<double>[layer.Neurons.Length][];
                thresholdsUpdates[i] = new Vector<double>[layer.Neurons.Length / 4];

                // for each neuron
                for (int j = 0; j < weightsUpdates[i].Length; j++)
                {
                    weightsUpdates[i][j] = new Vector<double>[layer.InputsCount / 4];
                }
            }
        }

        public new double Run(double[] input, double[] output)
        {
            // compute the network's output
            network.Compute(input);

            // calculate network error
            double error = CalculateError(output);

            // calculate weights updates
            CalculateUpdates(input);

            // update the network
            UpdateNetwork();

            return error;

        }

        public new double RunEpoch(double[][] input, double[][] output)
        {
            double error = 0.0;

            // run learning procedure for all samples
            for (int i = 0; i < input.Length; i++)
            {
                error += Run(input[i], output[i]);
            }

            // return summary error
            return error;
        }

        private double CalculateError(double[] desiredOutput)
        {
            // current and the next layers
            Layer layer, layerNext;
            // current and the next errors arrays
            Vector<double>[] errors, errorsNext;
            // error values
            double error = 0;
            // layers count
            int layersCount = network.Layers.Length;

            // vecrorize output
            Vector<double>[] desiredOutputVector = new Vector<double>[desiredOutput.Length / 4];
            for (int i = 0; i < desiredOutputVector.Length; i++)
            {
                desiredOutputVector[i] = new Vector<double>(desiredOutput, i * 4);
            }

            // assume, that all neurons of the network have the same activation function
            IActivationFunction function = (network.Layers[0].Neurons[0] as ActivationNeuron).ActivationFunction;

            // calculate error values for the last layer first
            layer = network.Layers[layersCount - 1];
            errors = neuronErrors[layersCount - 1];
            int outputLoopCnt = layer.Neurons.Length / 4;
            Vector<double>[] errorWork = new Vector<double>[outputLoopCnt];
            Parallel.For(0, outputLoopCnt, new ParallelOptions { MaxDegreeOfParallelism = 16 }, i =>
            {
                // neuron's output value
                double[] vectorInitTemp = new double[4];
                vectorInitTemp[0] = layer.Neurons[i * 4 + 0].Output;
                vectorInitTemp[1] = layer.Neurons[i * 4 + 1].Output;
                vectorInitTemp[2] = layer.Neurons[i * 4 + 2].Output;
                vectorInitTemp[3] = layer.Neurons[i * 4 + 3].Output;
                Vector<double> output = new Vector<double>(vectorInitTemp);

                // error of the neuron
                Vector<double> e = desiredOutputVector[i] - output;

                // error multiplied with activation function's derivative
                vectorInitTemp[0] = function.Derivative2(output[0]);
                vectorInitTemp[1] = function.Derivative2(output[1]);
                vectorInitTemp[2] = function.Derivative2(output[2]);
                vectorInitTemp[3] = function.Derivative2(output[3]);
                Vector<double> derivative = new Vector<double>(vectorInitTemp);
                errors[i] = e * derivative;

                // squre the error and sum it
                errorWork[i] = (e * e);
            });

            // エラー積算値の算出
            Vector<double> errorTemp = Vector<double>.Zero;
            for (int i = 0;i < outputLoopCnt;i++)
            {
                errorTemp += errorWork[i];
            }
            error = errorTemp[0] + errorTemp[1] + errorTemp[2] + errorTemp[3];

            // calculate error values for other layers
            for (int j = layersCount - 2; j >= 0; j--)
            {
                layer = network.Layers[j];
                layerNext = network.Layers[j + 1];
                errors = neuronErrors[j];
                errorsNext = neuronErrors[j + 1];

                // for all neurons of the layer
                int nextNyuronsLengthTemp = layerNext.Neurons.Length / 4;
                Parallel.For(0, (layer.Neurons.Length / 4), new ParallelOptions { MaxDegreeOfParallelism = 16 }, i =>
                {
                    double[] vectorInitTemp = new double[4];
                    Vector<double> sum = Vector<double>.Zero;

                    // for all neurons of the next layer
                    for (int k = 0; k < nextNyuronsLengthTemp; k++)
                    {
                        for (int l = 0; l < 4; l++)
                        {
                            vectorInitTemp[0] = layerNext.Neurons[k * 4 + l].Weights[i * 4 + 0];
                            vectorInitTemp[1] = layerNext.Neurons[k * 4 + l].Weights[i * 4 + 1];
                            vectorInitTemp[2] = layerNext.Neurons[k * 4 + l].Weights[i * 4 + 2];
                            vectorInitTemp[3] = layerNext.Neurons[k * 4 + l].Weights[i * 4 + 3];
                            Vector<double> weightsTemp = new Vector<double>(vectorInitTemp);
                            sum += errorsNext[k] * weightsTemp;
                        }
                    }

                    vectorInitTemp[0] = function.Derivative2(layer.Neurons[i * 4 + 0].Output);
                    vectorInitTemp[1] = function.Derivative2(layer.Neurons[i * 4 + 1].Output);
                    vectorInitTemp[2] = function.Derivative2(layer.Neurons[i * 4 + 2].Output);
                    vectorInitTemp[3] = function.Derivative2(layer.Neurons[i * 4 + 3].Output);
                    Vector<double> derivative = new Vector<double>(vectorInitTemp);

                    errors[i] = sum * derivative;
                });
            }

            // return squared error of the last layer divided by 2
            return error / 2.0;
        }

        private void CalculateUpdates(double[] input)
        {
            // current and previous layers
            Layer layer, layerPrev;

            // layer's weights updates
            Vector<double>[][] layerWeightsUpdates;

            // layer's thresholds updates
            Vector<double>[] layerThresholdUpdates;

            // layer's error
            Vector<double>[] errors;

            // vecrorize input
            Vector<double>[] inputVector = new Vector<double>[input.Length / 4];
            Parallel.For(0, inputVector.Length, new ParallelOptions { MaxDegreeOfParallelism = 16 }, i =>
            {
                inputVector[i] = new Vector<double>(input, i * 4);
            });

            // 1 - calculate updates for the first layer
            layer = network.Layers[0];
            errors = neuronErrors[0];
            layerWeightsUpdates = weightsUpdates[0];
            layerThresholdUpdates = thresholdsUpdates[0];

            // cache for frequently used values
            //double cachedMomentum = learningRate * momentum;
            //double cached1mMomentum = learningRate * (1 - momentum);
            Vector<double> cachedMomentum = Vector.Multiply(Vector<double>.One, learningRate * momentum);
            Vector<double> cached1mMomentum = Vector.Multiply(Vector<double>.One, learningRate * (1 - momentum));

            // for each neuron of the layer
            Parallel.For(0, (layer.Neurons.Length / 4), new ParallelOptions { MaxDegreeOfParallelism = 16 }, i =>
            {
                Vector<double> cachedError = Vector.Multiply(cached1mMomentum, errors[i]);
                Vector<double>[][] neuronWeightUpdates = new Vector<double>[4][];
                neuronWeightUpdates[0] = layerWeightsUpdates[i * 4 + 0];
                neuronWeightUpdates[1] = layerWeightsUpdates[i * 4 + 1];
                neuronWeightUpdates[2] = layerWeightsUpdates[i * 4 + 2];
                neuronWeightUpdates[3] = layerWeightsUpdates[i * 4 + 3];

                // for each weight of the neuron
                int neuronWeightUpdatesTemp = neuronWeightUpdates[0].Length;
                for (int j = 0; j < neuronWeightUpdatesTemp; j++)
                {
                    // calculate weight update
                    for (int k = 0;k < 4;k++)
                    {
                        neuronWeightUpdates[k][j] = Vector.Multiply(cachedMomentum, neuronWeightUpdates[k][j]) + Vector.Multiply(cachedError[k], inputVector[j]);
                    }
                }

                // calculate treshold update
                layerThresholdUpdates[i] = Vector.Multiply(cachedMomentum, layerThresholdUpdates[i]) + cachedError;
            });


            // 2 - for all other layers
            int layersLengthTemp = network.Layers.Length;
            for (int k = 1; k < layersLengthTemp; k++)
            {
                layerPrev = network.Layers[k - 1];
                layer = network.Layers[k];
                errors = neuronErrors[k];
                layerWeightsUpdates = weightsUpdates[k];
                layerThresholdUpdates = thresholdsUpdates[k];

                // for each neuron of the layer
                int neuronWeightUpdatesTemp = layerWeightsUpdates[0].Length;
                Parallel.For(0, (layer.Neurons.Length / 4), new ParallelOptions { MaxDegreeOfParallelism = 16 }, i =>
                {
                    double[] vectorInitTemp = new double[4];
                    Vector<double> cachedError = Vector.Multiply(cached1mMomentum, errors[i]);
                    Vector<double>[][] neuronWeightUpdates = new Vector<double>[4][];
                    neuronWeightUpdates[0] = layerWeightsUpdates[i * 4 + 0];
                    neuronWeightUpdates[1] = layerWeightsUpdates[i * 4 + 1];
                    neuronWeightUpdates[2] = layerWeightsUpdates[i * 4 + 2];
                    neuronWeightUpdates[3] = layerWeightsUpdates[i * 4 + 3];

                    // for each synapse of the neuron
                    for (int j = 0; j < neuronWeightUpdatesTemp; j++)
                    {
                        // calculate weight update
                        vectorInitTemp[0] = layerPrev.Neurons[j * 4 + 0].Output;
                        vectorInitTemp[1] = layerPrev.Neurons[j * 4 + 1].Output;
                        vectorInitTemp[2] = layerPrev.Neurons[j * 4 + 2].Output;
                        vectorInitTemp[3] = layerPrev.Neurons[j * 4 + 3].Output;
                        Vector<double> neuronsOutput = new Vector<double>(vectorInitTemp);
                        for (int l = 0; l < 4; l++)
                        {
                            neuronWeightUpdates[l][j] = Vector.Multiply(cachedMomentum, neuronWeightUpdates[l][j]) + Vector.Multiply(cachedError[l], neuronsOutput);
                        }
                    }

                    // calculate treshold update
                    layerThresholdUpdates[i] = Vector.Multiply(cachedMomentum, layerThresholdUpdates[i]) + cachedError;
                });
            }
        }

        private void UpdateNetwork()
        {
            // current layer
            Layer layer;
            // layer's weights updates
            Vector<double>[][] layerWeightsUpdates;
            // layer's thresholds updates
            Vector<double>[] layerThresholdUpdates;

            // for each layer of the network
            int layersLengthTemp = network.Layers.Length;
            for (int i = 0; i < layersLengthTemp; i++)
            {
                layer = network.Layers[i];
                layerWeightsUpdates = weightsUpdates[i];
                layerThresholdUpdates = thresholdsUpdates[i];

                // 誘導変数の使用
                int weightsLengthTemp = layer.Neurons[0].Weights.Length / 4;

                // for each neuron of the layer
                Parallel.For(0, (layer.Neurons.Length / 4), j =>
                {
                    ActivationNeuron[] neuron = new ActivationNeuron[4];
                    neuron[0] = layer.Neurons[j * 4 + 0] as ActivationNeuron;
                    neuron[1] = layer.Neurons[j * 4 + 1] as ActivationNeuron;
                    neuron[2] = layer.Neurons[j * 4 + 2] as ActivationNeuron;
                    neuron[3] = layer.Neurons[j * 4 + 3] as ActivationNeuron;

                    Vector<double>[][] neuronWeightUpdates = new Vector<double>[4][];
                    neuronWeightUpdates[0] = layerWeightsUpdates[j * 4 + 0];
                    neuronWeightUpdates[1] = layerWeightsUpdates[j * 4 + 1];
                    neuronWeightUpdates[2] = layerWeightsUpdates[j * 4 + 2];
                    neuronWeightUpdates[3] = layerWeightsUpdates[j * 4 + 3];

                    // for each weight of the neuron
                    for (int k = 0; k < weightsLengthTemp; k++)
                    {
                        for (int l = 0; l < 4; l++)
                        {
                            // update weight
                            neuron[l].Weights[k * 4 + 0] += neuronWeightUpdates[l][k][0];
                            neuron[l].Weights[k * 4 + 1] += neuronWeightUpdates[l][k][1];
                            neuron[l].Weights[k * 4 + 2] += neuronWeightUpdates[l][k][2];
                            neuron[l].Weights[k * 4 + 3] += neuronWeightUpdates[l][k][3];
                        }
                    }

                    // update treshold
                    neuron[0].Threshold += layerThresholdUpdates[j][0];
                    neuron[1].Threshold += layerThresholdUpdates[j][1];
                    neuron[2].Threshold += layerThresholdUpdates[j][2];
                    neuron[3].Threshold += layerThresholdUpdates[j][3];
                });
            }
        }
    }
}

Download:SimdBackPropagationLearning

ニューラルネットワークを使って回帰分析モデルを作る

地図上の座標に対して大体の緯度経度を求めたかったので、Accord.NETのニューラルネットワークを使った機械学習で回帰分析モデルを作って対処してみた。

本当はきっちり計算式を求めて算出すれば良いのだろうけれど、平面を球面に投射する変換式とか、測地系の変換とか私の数学センスでは手が出ない。そもそも元の地図がどんな図法かもしらないし・・・。と言うわけで、思考を放棄して機械に頼ることにした。

1. 学習元データを作成する

地図上の特徴的な地形の場所の座標と、緯度経度を対にした学習データを作成する。ニューラルネットワークでは学習データのない入力に対して、どのような出力が発生するか予測困難という特徴がある。変換対象内に満遍なく学習サンプルをとるのは当然として、実用上変換したい地図上の座標の上下左右の隅の辺りの学習サンプルも加えるのが重要。
ニューロンからの出力は0~1の範囲になるので、学習元データの出力がこの範囲に収まるように適当に剰余算して丸めておきます。

2.学習アルゴリズムの選択

BackPropagationLearning(誤差逆伝搬法)による学習だと誤差の収束に時間がかかるので、LevenbergMarquardtLearning(レーベンバーグ・マーカート法)を使います。

3.最適な中間層の数を推定する

学習サンプルを使ってニューラルネットワークに学習させた後、学習済ネットワークに学習サンプルを与えて計算をさせ出力の差を積分することで、学習誤差を求める。これを中間層の数を増減させながら繰り返し、学習誤差が大きくならない、最小の中間層の数を求めます。
中間層が多いほどより複雑なデータを学習することがでますが、学習サンプル間を補間するときに発振してしまう可能性が高くなります。可能な限り少ない中間層の数で学習させると、なだらかな曲線で補間される可能性が高くなります。

4.最適な学習済ニューロンを作る

最適と思われる中間層の数を確定したら、学習データを元に学習済ニューラルネットワークを複数作ります。ニューラルネットワークは初期値として与える乱数データによって、学習結果がどのような状態に収束するか変わります。したがって良好な学習結果を得るためには初期状態を変えながら複数の学習結果を得て、その結果同士を比較する必要があります。
今回私は学習誤差の小さい物を最適と判断したました。用途によっては単純に誤差の小さい物では無く、判定方法を変える必要があると思います。

5.使う

これで一応は学習モデルが出来たので、実際に使って異常な変換とか発生しないか確かめます。使いたい範囲内で変な発振してないので大丈夫っぽい。

AForge.NETのBackPropagationLearningをマルチスレッドに書き換える

AForge.NETで遊んでいる。Back Propagation Learning事態は20年以上前からあるのだが、当時は少ないメモリをやり繰りしながら一から実装するほかなかった。お手軽にライブラリでできるって素晴らしいよね。ただ残念なことにAForge.NETのBack Propagation LearningはMulti Threadに対応しないので、Parallel.Forを使って修正してみた。
マルチスレッドに対応させるための書き換えも.Net Framework 4.xではとてもお手軽にできる。今時マルチスレッドに動作しないのってすごく罪だよね。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

using AForge.Neuro;
using AForge.Neuro.Learning;

namespace RemoveRule
{
    class MultiThreadBackPropagationLearning : BackPropagationLearning
    {
        // network to teach
        private ActivationNetwork network;

        // learning rate
        private double learningRate = 0.1;

        // momentum
        private double momentum = 0.0;

        // neuron's errors
        private double[][] neuronErrors = null;

        // weight's updates
        private double[][][] weightsUpdates = null;

        // threshold's updates
        private double[][] thresholdsUpdates = null;

        public new double  LearningRate
        {
            get { return learningRate; }
            set
            {
                learningRate = Math.Max(0.0, Math.Min(1.0, value));
            }
        }

        public new double Momentum
        {
            get { return momentum; }
            set
            {
                momentum = Math.Max(0.0, Math.Min(1.0, value));
            }
        }

        public MultiThreadBackPropagationLearning(ActivationNetwork network) : base(network)
        {
            this.network = network;

            // create error and deltas arrays
            neuronErrors = new double[network.Layers.Length][];
            weightsUpdates = new double[network.Layers.Length][][];
            thresholdsUpdates = new double[network.Layers.Length][];

            // initialize errors and deltas arrays for each layer
            for (int i = 0; i < network.Layers.Length; i++)
            {
                Layer layer = network.Layers[i];

                neuronErrors[i] = new double[layer.Neurons.Length];
                weightsUpdates[i] = new double[layer.Neurons.Length][];
                thresholdsUpdates[i] = new double[layer.Neurons.Length];

                // for each neuron
                for (int j = 0; j < weightsUpdates[i].Length; j++)
                {
                    weightsUpdates[i][j] = new double[layer.InputsCount];
                }
            }
        }

        public new double Run(double[] input, double[] output)
        {
            // compute the network's output
            network.Compute(input);

            // calculate network error
            double error = CalculateError(output);

            // calculate weights updates
            CalculateUpdates(input);

            // update the network
            UpdateNetwork();

            return error;

        }

        public new double RunEpoch(double[][] input, double[][] output)
        {
            double error = 0.0;

            // run learning procedure for all samples
            for (int i = 0; i < input.Length; i++)
            {
                error += Run(input[i], output[i]);
            }

            // return summary error
            return error;
        }

        private double CalculateError(double[] desiredOutput)
        {
            // current and the next layers
            Layer layer, layerNext;
            // current and the next errors arrays
            double[] errors, errorsNext;
            // error values
            double error = 0;
            // neuron's output value
            double output;
            // layers count
            int layersCount = network.Layers.Length;

            // assume, that all neurons of the network have the same activation function
            IActivationFunction function = (network.Layers[0].Neurons[0] as ActivationNeuron).ActivationFunction;

            // calculate error values for the last layer first
            layer = network.Layers[layersCount - 1];
            errors = neuronErrors[layersCount - 1];
            Parallel.For(0, layer.Neurons.Length, i =>
            {
                output = layer.Neurons[i].Output;

                // error of the neuron
                double e = desiredOutput[i] - output;

                // error multiplied with activation function's derivative
                double derivative;
                lock (function)
                {
                    derivative = function.Derivative2(output);
                }
                errors[i] = e * derivative;

                // squre the error and sum it
                Monitor.Enter(this);
                error += (e * e);
                Monitor.Exit(this);
            });

            // calculate error values for other layers
            for (int j = layersCount - 2; j >= 0; j--)
            {
                layer = network.Layers[j];
                layerNext = network.Layers[j + 1];
                errors = neuronErrors[j];
                errorsNext = neuronErrors[j + 1];

                // for all neurons of the layer
                Parallel.For(0, layer.Neurons.Length, i =>
                {
                    double sum = 0.0;

                    // for all neurons of the next layer
                    for (int k = 0; k < layerNext.Neurons.Length; k++)
                    {
                        sum += errorsNext[k] * layerNext.Neurons[k].Weights[i];
                    }

                    double derivative;
                    lock (function)
                    {
                        derivative = function.Derivative2(layer.Neurons[i].Output);
                    }
                    errors[i] = sum * derivative;
                });
            }

            // return squared error of the last layer divided by 2
            return error / 2.0;
        }

        private void CalculateUpdates(double[] input)
        {
            // current neuron
            Neuron neuron;

            // current and previous layers
            Layer layer, layerPrev;

            // layer's weights updates
            double[][] layerWeightsUpdates;

            // layer's thresholds updates
            double[] layerThresholdUpdates;

            // layer's error
            double[] errors;

            // error value
            // double           error;

            // 1 - calculate updates for the first layer
            layer = network.Layers[0];
            errors = neuronErrors[0];
            layerWeightsUpdates = weightsUpdates[0];
            layerThresholdUpdates = thresholdsUpdates[0];

            // cache for frequently used values
            double cachedMomentum = learningRate * momentum;
            double cached1mMomentum = learningRate * (1 - momentum);

            // for each neuron of the layer
            Parallel.For(0, layer.Neurons.Length, i =>
            {
                neuron = layer.Neurons[i];
                double cachedError = errors[i] * cached1mMomentum;
                double[] neuronWeightUpdates = layerWeightsUpdates[i];

                // for each weight of the neuron
                for (int j = 0; j < neuronWeightUpdates.Length; j++)
                {
                    // calculate weight update
                    neuronWeightUpdates[j] = cachedMomentum * neuronWeightUpdates[j] + cachedError * input[j];
                }

                // calculate treshold update
                layerThresholdUpdates[i] = cachedMomentum * layerThresholdUpdates[i] + cachedError;
            });


            // 2 - for all other layers
            for (int k = 1; k < network.Layers.Length; k++)
            {
                layerPrev = network.Layers[k - 1];
                layer = network.Layers[k];
                errors = neuronErrors[k];
                layerWeightsUpdates = weightsUpdates[k];
                layerThresholdUpdates = thresholdsUpdates[k];

                // for each neuron of the layer
                Parallel.For(0, layer.Neurons.Length, i =>
                {
                    neuron = layer.Neurons[i];
                    double cachedError = errors[i] * cached1mMomentum;
                    double[] neuronWeightUpdates = layerWeightsUpdates[i];

                    // for each synapse of the neuron
                    for (int j = 0; j < neuronWeightUpdates.Length; j++)
                    {
                        // calculate weight update
                        neuronWeightUpdates[j] = cachedMomentum * neuronWeightUpdates[j] + cachedError * layerPrev.Neurons[j].Output;
                    }

                    // calculate treshold update
                    layerThresholdUpdates[i] = cachedMomentum * layerThresholdUpdates[i] + cachedError;
                });
            }
        }

        private void UpdateNetwork()
        {
            // current layer
            Layer layer;
            // layer's weights updates
            double[][] layerWeightsUpdates;
            // layer's thresholds updates
            double[] layerThresholdUpdates;

            // for each layer of the network
            for (int i = 0; i < network.Layers.Length; i++)
            {
                layer = network.Layers[i];
                layerWeightsUpdates = weightsUpdates[i];
                layerThresholdUpdates = thresholdsUpdates[i];

                // for each neuron of the layer
                Parallel.For(0, layer.Neurons.Length, j =>
                {
                    ActivationNeuron neuron = layer.Neurons[j] as ActivationNeuron;
                    double[]  neuronWeightUpdates = layerWeightsUpdates[j];

                    // for each weight of the neuron
                    for (int k = 0; k < neuron.Weights.Length; k++)
                    {
                        // update weight
                        neuron.Weights[k] += neuronWeightUpdates[k];
                    }

                    // update treshold
                    neuron.Threshold += layerThresholdUpdates[j];
                });
            }
        }
    }
}

元々のBackPropagationLearningクラスでメンバ変数がprivate宣言されていたのがかなり残念。派生したクラスで挙動をオーバーライト出来ないので、丸ごと同じクラスを実装する他に無いのだ。こうなると、そもそもBackPropagationLearningを継承する必要すらなかったかもしれない。

Download MultiThreadBackPropagationLearning.zip

暗号ファイルを使った正しい秘密情報の守り方

年金機構の情報漏えい事件に関して、暗号化して保存するルールになってたなんて発言もあり、暗号ファイルの正しい秘密情報の守り方を確認しておきたいと思う。

1.暗号鍵の管理

大事なのは暗号鍵の管理となる。鍵が漏えいすれば、暗号にして保存しておいても何も意味がない。これが意外に難しい。
たとえばファイルサーバーに暗号化したファイルを置いておいたとして、その鍵をチーム内で共有していたとする。鍵がチーム外に漏れては意味がないので、チームメンバーの入れ替えが発生するたびに既存の暗号ファイルの鍵を全部変更する作業が発生する。
また暗号ファイルをメールなどで遠隔地に送る場合、鍵はメールとは異なる安全な媒体で送る必要がある。よくメールで送った添付ファイルの鍵を、別のメールで送ってくるが、これは殆ど意味がない。最低でも封書などで送る必要がある。

2.暗号鍵の長さ

鍵の長さも大事な要素となる。技術の進歩とコストの低下の恩恵を受けているのはクラッカーも同じで、ランダムな英数字記号による8文字程度の鍵なら、総当たりで試行錯誤を繰り返せば解くことができるようになってきた。AES暗号を使ったZIPファイルでも、ハイエンドパソコンなら毎秒40万通りの試行錯誤を実施できるので、英数大小文字に数字と記号までを使ったパスワードでも36年で全ての組み合わせを試せる。本気で暗号を破ろうとするなら、この1/10,000時間で解析することも出来る。最低でも英数字記号による12文字程度の鍵を設定しないと安心は出来ない。。

3.暗号方式の選択

暗号方式には設計レベルでの脆弱性が見つかっているものが多数ある。例えば圧縮ファイルのZIPで使われているPKZIP暗号とか、古いWordやExcelのファイルには、容易に鍵を類推出来るという脆弱性があります。設計レベルでの脆弱性なので、使わない以外の対策はありません。
したがってZIP形式の場合にはAES暗号を使うようにする必要があります。ただしWindowsの標準機能では複合出来なくなる。ExcelやWordではxlsxやdocx形式を選択すると良い。

4.内容を読めない

暗号ファイルにしてるので内容を読めません。何故これが問題なのかというと、例えばウィルス対策ソフトもファイルの内容をチェック出来ないので、ウィルスが含まれていても検出出来ません。また本来は持ち出しの許されていない情報が含まれてないか調べることも出来ません。これを防ぐためにはファイルに使用されている暗号鍵を全て提出させて日々更新し、しかるべき立場の者以外が閲覧できないように厳重に保管しておく必要が生まれます。
これを実現するには、暗号鍵を24桁にして前半12桁と後半12桁とを全く別々の担当者通知して管理させるといったことになる。

ファイルの暗号化によって秘密を守ろうとするとここまでの事が必要になりますが、到底実施不可能だと感じたのでは無いかと思います。それは当然のことです。そもそも共通鍵暗号でファイルを暗号化して秘密を守ろうというのは20年前の発想で、ネットワークに接続された機器が増え、殆ど全ての情報が電子化されている時代に通用する物では無いのですから。

チーム内で安全にファイルを共有するためには、IRM【Information Rights Management】という仕組みを使います。これ自体は単体のシステムとして販売されているのではなく、グループウェアや文書管理システムの一機能として提供されている事が多いです。実は、MicrosoftのOffice製品は10年以上も前から標準でIRMに対応していて、サーバーを1台用意すればIRMを使ったファイルの暗号化をできます。

秘密情報をファイルサーバーに保存する事に関して、まともなSEに少しでも相談していたなら、複雑な運用ルールにあれこれ悩む必要など無かったのに・・・と思わずには居られません。

社内連絡にメールを使うのを止めよう

標的型攻撃で送られてくるメールへの対策はどうあるべきか、様々な話がされてます。その一つとして検討すべき事が、社内の連絡にはインターネットメールを使わないってことです。

情報漏洩の原因のひとつが、リスクの高い外部との通信と、リスクの低い内部との通信を同じ仕組みの中で取扱っていることです。インターネットメールを外部との通信以外で使わないようにすれば、特定の拡張子のファイルを全て削除するといった対策も取りやすくなりますし、部内者を装った標的型攻撃のメールに騙される可能性もなくなります。

他にも、インターネットメールを社内連絡に使う事には問題があります。インターネットメールは基本的にインターネット上のサーバーを介して送信します。常時インターネットとの通信を行っているために、自社のメールサーバーのメンテナンスに不手際があった場合、クラッカーに侵入されるリスクが高いのです。万が一侵入されればメールサーバーを経由した全ての通信内容が漏洩する事につながります。

そこでインターネットメールに変わって、グループウェアや社内SNSを使うようにします。インターネットメールにはセキュリティー上の問題以外にも、同報性や共有性が悪い、既読確認の仕組が使いにくい、大きな容量の写真などを送れない、過去データの保存性や一覧性が悪い…などなど様々な使い難い点があります。グループウェアを使えば、これらの問題も一挙に解決します。導入に躊躇していたり、活用できずにいるなら、セキュリティーの為にも活用を進めてください。

個人情報を扱うならインターネットから切断するのが常識・・・とはならないで欲しい

市町村にネット遮断要請へ 厚労省、年金情報流出で

個人情報を扱うなら、インターネットから切断するのが常識・・・とされそうで危惧してたんですが、案の定その方向に動いていて頭を抱えています。インターネットから切断するのは、案外難しく、実効性を持たせるのは大変なのです。

1.業務ができるのかということ。
端末入力業務程度しかしないのであれば、確かにネットから切断しても問題ないのだけれど、ほとんどの場合は情報を加工したり、あるいは別の形で利用したりしなければ意味のある業務になりません。業務をよほど詳細に分析し、場合によっては根本から見直さないと、結局のところ情報を外に持ち出すことになります。USBメモリで、CDで、あるいは印刷した紙媒体で隔離されたネットワークの外に持ち出して仕事をするわけです。実務担当者にとって、与えられた仕事を遂行することが優先しますから、例外として持ち出すことを認めれば当然持ち出します。最初は仕方なく持ち出すのだと緊張していても、それが常態化すれば感覚は麻痺していきます。

2.持ち出された情報は追跡できない。
持ち出された情報がどうなったのか、追跡するのは非常に困難です。紙であろうと、USBメモリであろうと、容易に複製出来ます。他の媒体に持ち出された時点から、誰によって閲覧され、複製が何処にあるのか追跡することが著しく困難になります。
全てオンラインシステム内で扱っていれば、何時誰が作成し、閲覧あるいは複製したか追跡することも可能です。あるいはファイルサーバー内のファイルを精査して、個人情報が含まれてないか調べることも出来ます。インターネットから切断したことで、頻繁に個人情報をシステム外に持ち出して業務をするのでは、むしろ全体的なリスクが増える事になるのです。

3.メンテナンスも困難になる。
インターネットから切断したからと言って、メンテナンスをしなくてよいというわけではありません。業務用に作成したプログラムの更新やセキュリティ更新プログラムのインストールなどは常に発生します。
インターネットに繋がっているのであれば、セキュリティ更新プログラムのインストールなどは半自動で実行されるのですが、インターネットから切断されるとそうはいきません。セキュリティ更新プログラムをダウンロードし、それが確かにベンダーが提供しているものなのかを確認し、隔離したシステムに導入する必要があります。隔離しているのだから更新プログラムを当てなくても良いのではと思うかもしれませんが、そうはいきません。業務用に作成したプログラムの更新など、何らかの形で外部からファイルやデータを持ち込む可能性があるなら脆弱性を放置するわけにはいかないのです。

このあたりを無視して切断した結果、個人情報を隔離したシステムから持ち出すのが常態化して、情報漏えいにつながったのがまさしく今回の年金情報流出事件でしょう。さらに別の組織にまでインターネットからの切断を求めるなんて、今回の事件からいったい何を学んだんでしょう?

ではどうすれば良いのかというと、セキュリティーの基本となる、アクセス権の管理と、ログによる監視をしっかりと行うことです。基本ができていないのに、インターネット接続だけ遮断しても、絶対にうまくはいきません。

ML入門2:機械学習のアルゴリズム色々

機械学習を理解することよりも、使うことを優先して話を進めていきたいと思う。
機械学習のアルゴリズムは用途によって大きく5つに分類できる。
それぞれの特徴を理解した上で、これらの組み合わせで機械学習を実現することになる。

1.回帰分析(Regression)

学習データとの差違が最小となる多項式を求めて、新たに与えられたデータから値を算出する方式。最も単純なのは線形回帰分析(Linear Regression)で「機械学習とは何をしているのか」で示した例がまさにこれに当る。相関関係を単純な数式で表せることが前提条件となる。
非線形回帰分析を行う改良型もあるが、学習データに異常値が含まれていると学習結果の精度が大きく落ちる。どちらが良い悪いという物では無い。

2.教師データありの分類(Classification)

対象がどの分類に含まれるかを判断する。本質的には回帰分析と大きく変わらない。学習データを元に回帰分析によって境界線となる多項式を求め、その多項式で分断される空間のどちら側になるかを算出することで分類する。

3.教師データなしの分類(Clustering)

対象を類似度の高い、いくつかのグループ(クラスタ)に分類する。学習のための教師データを用意する必要が無い。k-meansアルゴリズムがよく使用される。クラスタの所属する円の中心を繰り返し計算で求めていく。いくつのグループに分割するのかは指示する必要があるがグループ数が小さいと計算が収束しなくなる可能性が高い。かといって大きすぎても一つのクラスタが複数に分断される事になる。またクラスタに属せずに孤立するデータが含まれていると計算結果が不正確になりやすく、与えるデータの品質が求められる点は変わらない。

4.ベイジアンフィルタ(Bayesian Filter

Classificationの一つだが、回帰分析ではなく統計的な手法(ベイズ理論)により確率を求めることで分類を行う。全ての特徴に対する特定の特徴の発生頻度を元にして、個々の特徴が存在する時にその分類となる確率を計算する。実データのもつ個々の特徴を元にこの確率を積分することで、どの分類になるかを判断する。
全体の特徴の種類が増えると計算結果となる確率の差が小さくて誤判定が増えたり、全体的に出現頻度の低い特徴に判断結果が引っ張られたりと、学習データの質に大きく左右される。

5.ニューラルネットワーク(neural network)

Classificationの一つだが、神経細胞を単純化して模したニューラルネットワークで計算を表現する。実質的にはひとつのニューロンがひとつの線形回帰分析機となる。これを複数組み合わせてニューラルネットワークを構成することで、複数の線形回帰分析による複雑なルール学習が可能になる。欠点は学習のための計算量が大幅に増えてしまうこと。

番外1.遺伝的アルゴリズム(genetic algorithm)

番外として遺伝的アルゴリズムを加えておく。学習結果となるパラメータ(遺伝子パターン)をランダムに生成し、それぞれを評価関数の元で競わせる。より良い結果を出した遺伝子パターンを残してそれらをランダムにシャッフルする事を繰り返して、最も適した遺伝子パターンを求める物。遺伝子パターンや評価関数の設計が難しく、適切な方法をプログラマが提示する必要があり、また計算量も膨大になってしまう。
現在の機械学習において遺伝的アルゴリズムが話題にあがる事は少ないので番外としておく。

番外2.ディープラーニング(Deep Lerning)

現状ではバズワードだと思っているので、番外扱い。
複数の機械学習(一般的にはニューラルネットワークを使っているが、ニューあっルネットワークに限らない)を階層的に、あるいは並列的に組み合わせて使用することで、より高度で複雑な事象の学習を可能にしようとする試み。コンピューターの高性能化、低価格化によって可能になってきた。

ML入門1:機械学習とは何をしているのか?

機械学習とMachine Lerningについて情報収集中なので、せっかくだから整理しつつ残していきたいと思う。

機械学習とは何をしているのか?

機械学習が何をしているのかというと実は非常にシンプルなことをしています。下のようなグラフがあったとき直線(回帰直線)を引いたりしませんでしたか?あるいは大学で最小二乗法による回帰直線を求める方法を学びませんでしたか?機械学習がやっていることは、まさしくその回帰直線を求めて、与えられたXからYを求める方程式を作成し、新たなXが与えられたときにYの値を予想する事です。

regresion1
例えば上の散布図のように与えられたサンプルデータに対する破線を求めるのが機械学習です。そして学習結果であるy=0.7482x+14.612の計算式を用いて、新たに与えられた値に対してyを求める事で結果を予想するわけです。勿論これは極端に単純かした場合の話で、実際の機械学習では二次元はなく十数次元~数千次元にもなります。図示することなど到底不可能なレベルです。でもどれだけデータの次元数が増えて複雑に見えても、内部的に行っていることはこの延長線上にあります。

この事を踏まえると機械学習に出来ること、出来ないこと、なぜ与えるデータが重要なのか理解も容易になります。

機械学習に出来ること、出来ないこと

機械学習では学習させるデータの質が非常に重要になります。これを提供できるか否かが、機械学習で出来るか否かに直結しています。

異常値が含まれていたり、学習サンプルデータが偏っていたり、相関関係の薄いデータが紛れていたりすれば、学習結果となる数式は容易に異なった物になります。十分な量の、質の高いサンプルデータを必要とします。

regresion1

上の図のように異常値を一つ加えただけで、学習結果が変わってきます。x値が小さいときには10近く大きな数字が出るようになってしまいました。このような異常値は学習させる前に取り除く必要があります。これは二次元のグラフだから異常値と言うことがわかりやすいのですが一般的に機械学習に使うデータは数十次元にも及ぶので異常値を取り除くだけでも高い数学的知識を求められます。

Azure Machine Lerningの基本的な使い方1

概要

Microsoft Azure Mchine Lerningが追加されました。Google CloudやAmazon Web Service(AWS)でもMachine Lerning APIを提供していますが、MicrosoftのAzure Mchine LerningはGUI上で学習アルゴリズムをテストし、そのままWEBサービスとしてリリースすることが出来ます。そのためコーディングなど、学習のための敷居が低いのが特徴です。

それでいて、Rスクリプトを実行することが出来たり、Vowpal Wabbitの機械学習アルゴリズムを使うことも出来ます。これにより拡張性も担保されています。

チュートリアルの補足

Create a simple experimentのチュートリアルを元に基本的な使い方を確認します。このチュートリアルは自動車のメーカーや車種、サイズ、排気量、馬力等の情報から、販売価格を予測する物です。ごくシンプルなチュートリアルですが、不動産の立地条件から家賃を予測したり、様々な条件から売上を予測したりと応用範囲の広いものです。

基本操作でいくつかわかりにくいところがあります。

Azure Mchine Lerningでは作成した機械学習モデルをExperiment(実験)と呼びます。ExperimentのCanvas上にModuleを配置して、Module同士をデータの流れを表す線で接続することで危害学習モデルを作成していきます。

scoremodel1Moduleの四角い枠同士を接続している矢印の起点、終点となる○の部分をクリックするとメニューが表示されます。チュートリアルではダブルクリックしてvisualization windowを表示すると記載されていますが、この起点、終点の部分をクリックして表示されるメニューからVisualize選択して表示します。

出来上がったExperimentをWEBサービスとしてリリースする場合には終点となる部分をクリックして表示されるメニューからSet as Publish Inputを選択します。また終点となる部分をクリックしてSet as Publish Outputを選択します。これが、それぞれWEBサービスの入力パラメータと出力パラメータになります。これらを設定した後、Publish Web ServiceをクリックするとWEBサービスが作成されます。

感染した未知のコンピュータウィルスを削除するには・・・実践編2/2

続いてレジストリの確認を取ります。実践編1/2でウィルスが起動しないようにしたあと、for VirusCheck OSを起動して、レジストリエディタから全レジストリの内容をエクスポートします。エクスポートしたレジストリはテキストファイルイン保存されますので、for Refrence OSのウィルス感染前のレジストリをエクスポートしたものと比較して変更箇所を検証していきます。
registry
実際に細かく追っていくとかなり根気のいる作業です。今回のウィルスでは以下の場所に改変がくわえられ、ログオン時にウィルスが起動されるようになっていました。

[HKEY_LOCAL_MACHINE&yenSOFTWARE&yenMicrosoft&yenWindows NT&yenCurrentVersion&yenWinlogon]
“Userinit”=”C:&yen&yenWINDOWS&yen&yensystem32&yen&yenuserinit.exe,C:&yen&yenProgram Files&yen&yenCommon Files&yen&yensvchost.exe -s”

今回のウィルスは比較的シンプルな作りになっていたので、ファイル1つ、レジストリ1箇所だけで済んでいます。etcやDNSサーバの情報を改ざんしたり、電子証明書を改ざんしたり、ドライバを組み込んだり、ブラウザやエクスプローラーにプラグインを組み込んだりと、複雑な動作をするウィルスもいます。気を抜かずに根気よく一つ一つ確認していく事が重要です。
ウィルスを駆除する為に削除するファイルやレジストリが明確になったら、実際に感染した環境でそれらのファイルを削除したり、あるいはレジストリを修復していく事になります。ウィルスが常駐している状態では削除などの操作はウィルスによってトラップされており、おこなえないのが常です。OSのモジュールの一部が置き換えられている場合には、たとえセーフモードで起動したとしてもウィルスは常駐してしまいます。KNOPPIXなどのCDからブートするLinux環境から、NTFSのファイルシステムをマウントして、ファイルの削除などをおこなうのが一番確実でしょう。
今回のウィルスは幸いな事に、セーフモードのコマンドプロンプトで起動すれば、ウィルスが常駐する事を防げます。したがってセーフモードのコマンドプロンプトで起動した後、C:¥Program Files¥Common Files¥svchost.exeのシステム属性と非表示属性を解除し、ファイルを削除することで除去できました。