/// <summary>
 /// ニューラルネットのインスタンスとモデル名のリストを渡して初期化するコンストラクタ
 /// </summary>
 /// <param name="NNet">ニューラルネットワーククラスオブジェクト</param>
 /// <param name="IDnames">モデルIDのリスト<para>リストの要素番号とモデル名は関係があるので適当な順序で渡さない様に。あくまで学習器(NeuralNetTeacher)から取得して下さい。</para></param>
 /// <exception cref="SystemException">ニューラルネットのインスタンスが確保されていない場合と、モデルIDの数と出力層ユニット数が一致しなければスロー</exception>
 public Discriminator(NeuralNetwork NNet, string[] IDnames)
 {
     if (NNet == null) throw new SystemException("ニューラルネットのインスタンスが確保されていません。");
     this._IDnames = new string[IDnames.Length];
     for (int i = 0; i < IDnames.Length; i++) this._IDnames[i] = IDnames[i];
     this._NNet = NNet;
     if (this._NNet.OutputVectorLength != this._IDnames.Length) throw new SystemException("モデルIDの数と出力層ユニット数が一致しません。");
 }
 /// <summary>
 /// sin()関数の学習の例
 /// <para>NeuralNetworkクラスを直接利用して関数近似を行わせるサンプルです。</para>
 /// <para>
 /// 2011/12/25現在、0~1までの出力しか得られない。
 /// 出力関数と学習則を変更するか、関数近似時には値のマッピングが必要である。
 /// 他の人はどうやってんだろう?</para>
 /// </summary>
 public static void sin_learnig()
 {
     var para = new Parameter(1, 13, 1, 3, 0.01);            // ニューラルネットのパラメータ設定
     var NNforSin = new NeuralNetwork();                     // ニューラルネットクラスのインスタンス生成
     NNforSin.Setup(para);
     for (int i = 0; i < 500; i++)                           // 学習開始
     {
         Console.Write("\nnow i: " + i.ToString() + "@ ");
         Console.Write("ans = ");
         for (int k = 0; k < 15; k++)
         {
             double theta = (double)k / 15.0 * 2.0 * Math.PI;
             double ans = 0.0;
             for (int l = 0; l < 100; l++) ans = NNforSin.Learn(theta, Math.Sin(theta));       // 特徴ベクトルと教師ベクトルを渡す
             Console.Write(", " + ans.ToString("0.000"));
         }
     }
     NNforSin.Save("NN.ini");
     return;
 }
 /// <summary>
 /// XORの学習の例
 /// <para>NeuralNetworkクラスを直接操作して論理演算を実行する例です。</para>
 /// </summary>
 public static void XOR_learning()
 {
     var para = new Parameter(2, 5, 1, 1, 0.1);                          // ニューラルネットのパラメータ設定(中間層が多いと、学習係数を小さくとる必要がある)7層が限界・・
     var NNforXOR = new NeuralNetwork();                                 // ニューラルネットクラスのインスタンス生成
     NNforXOR.Setup(para);
     for (int i = 0; i < 100; i++)                                       // 学習開始
     {
         var ans = new double[4];
         Console.Write("now i: " + i.ToString() + "@ ");
         for (int k = 0; k < 100; k++)
         {
             ans[0] = NNforXOR.Learn(new double[2] { 0.0, 0.0 }, 0.0);   // 特徴ベクトルと教師ベクトルを渡す
             ans[1] = NNforXOR.Learn(new double[2] { 0.0, 1.0 }, 1.0);
             ans[2] = NNforXOR.Learn(new double[2] { 1.0, 0.0 }, 1.0);
             ans[3] = NNforXOR.Learn(new double[2] { 1.0, 1.0 }, 0.0);
         }
         Console.WriteLine("ans = " + ans[0].ToString("0.000") + ", " + ans[1].ToString("0.000") + ", " + ans[2].ToString("0.000") + ", " + ans[3].ToString("0.000") +
             ", 最後の学習結果→Error variation," + NNforXOR.VariationOfError.ToString("0.000") + ", Total Error," + NNforXOR.TotalOutputError.ToString("0.000"));
     }
     NNforXOR.Save("NN.ini");
     return;
 }
 /* メソッド **********************************************************************/
 /// <summary>
 /// 識別用ニューラルネットの設定ファイルを読み込む
 /// <para>デフォルトのファイル名は"discriminator.ini"です。</para>
 /// </summary>
 /// <param name="fname">ファイル名</param>
 public void Setup(string fname = "discriminator.ini")
 {
     if (System.IO.File.Exists(fname) == false) throw new SystemException("指定されたファイルは存在しません。");
     try
     {
         // モデルID名をファイルより読み込む
         using (System.IO.StreamReader sr = new System.IO.StreamReader(fname, System.Text.Encoding.UTF8))
         {
             Boolean idFound = false;
             while (sr.EndOfStream == false)
             {
                 string line = sr.ReadLine();                            // 一行読み込み
                 if (idFound)
                 {
                     string[] field = line.Split(',');
                     this._IDnames = new string[field.Length];
                     for (int i = 0; i < this._IDnames.Length; i++) this._IDnames[i] = field[i];
                     idFound = false;
                 }
                 if (line.IndexOf("ID") >= 0 && line.IndexOf("/ID") < 0) idFound = true;
             }
         }
         // ニューラルネット復元
         this._NNet = new NeuralNetwork();
         this._NNet.Setup(fname);
     }
     catch (Exception e)
     {
         throw new SystemException(e.GetType().FullName + "\nDiscriminatorクラスのSetup()にてファイル読み込み時にエラーが発生しました。以下詳細:\n" + e.Message);
     }
     return;
 }
        /// <summary>
        /// 学習を実行させる
        /// <para>学習強度を引数で設定可能です。</para>
        /// </summary>
        /// <param name="learningTimes">学習回数</param>
        /// <param name="learningStrength">学習強度(要素数はモデル数と一致すること)</param>
        /// <param name="discriminator">学習させる識別器</param>
        /// <param name="classForLearning">学習用教師データモデル</param>
        /// <returns>識別器の出力誤差積算値</returns>
        private double Learn(int learningTimes, int[] learningStrength, NeuralNetwork discriminator, List<Model> classForLearning)
        {
            if (this.Ready == false)
                throw new SystemException("学習の準備はまだ完了しておりません。モデルを追加して下さい。");
            if (classForLearning.Count != learningStrength.Length)
                throw new SystemException("学習強度の要素数がモデル数と一致しません。");
            if (discriminator == null)
                throw new Exception("判別器のインスタンスが確保されておらず、学習ができません。");
            // 学習をスタート
            foreach (Model model in classForLearning)
                model.SequenceMode = this.PickOutMode;
            int learningCount = 0;
            double[] errorEachModel = new double[classForLearning.Count];
            Boolean need = CheckLearningStrength(learningStrength);                             // 学習させる必要性を学習強度からチェック
            int backupOfModelIndex = int.MaxValue;
            int sameCount = 0;

            while (learningCount < learningTimes && need)                                       // 学習回数をチェックしながらループ
            {
                int modelIndex;
                if (this.RandomModelChoice)
                    modelIndex = this.myRandom.Next(classForLearning.Count);
                else
                    modelIndex = learningCount % classForLearning.Count;
                if (backupOfModelIndex != modelIndex)                                           // 2回連続で学習させると学習が偏ってしまうので回避。エニグマみたい。
                {
                    Vector teature = this.CreateTeachingVector(modelIndex);
                    double error = 0.0;
                    int count = 0;
                    // 指定された強度分配に基づき、計算した回数分モデルを呼び出す
                    for (int i = 0; i < learningStrength[modelIndex]; i++)
                    {
                        Feature feature = classForLearning[modelIndex].GetFeature();
                        Vector outputForDebug = discriminator.Learn(feature, teature);          // 学習
                        learningCount++;
                        error += discriminator.TotalOutputError;
                        count++;
                    }
                    error /= (double)count;                                                     // 特徴量1件当たりの誤差量を計算
                    errorEachModel[modelIndex] = error;                                         // シーケンス上では何の役にも立っていないが、デバッグ用と思ってください。将来的には誤差の収束条件を入れたいと思っています。
                    sameCount = 0;
                }
                else
                {
                    if (sameCount < 100)                                                        // 同じ数が出るようになったらインスタンスをもう一度作ってみる
                        sameCount++;                                                            // 乱数ジェネレータが壊れるとかあほかと(並列演算が悪いの?)
                    else                                                                        // これで一応うまくいくことを確認した
                        this.myRandom = new Random();
                }
                backupOfModelIndex = modelIndex;
            }
            return discriminator.TotalOutputError;
        }
 /// <summary>
 /// 学習を実行させる
 /// <para>学習強度は1:1に設定されますので、各モデルの保持する特徴ベクトルは等しい回数呼び出されます。</para>
 /// </summary>
 /// <param name="learningTimes">学習回数</param>
 /// <param name="discriminator">学習させる識別器</param>
 /// <param name="classForLearning">学習用教師データモデル</param>
 private void Learn(int learningTimes, NeuralNetwork discriminator, List<Model> classForLearning)
 {
     if (discriminator == null)
         throw new Exception("判別器のインスタンスが確保されておらず、学習ができません。");
     int[] learningStrength = new int[this.Length];
     for (int i = 0; i < learningStrength.Length; i++)
         learningStrength[i] = 1;
     this.Learn(learningTimes, learningStrength, discriminator, classForLearning);
 }
        /// <summary>
        /// 一通りの判別を行い、結果をヒストグラム(表:実態はVector[])にまとめる
        /// <para>閾値による棄却は行っておりません。最大値の出力を持ったクラスを識別器の出した答えだとして集計を行っています。</para>
        /// <para>並列演算による交差確認法の演算のために、引数としてニューラルネットのインスタンスを受け取るようにしています。</para>
        /// </summary>
        /// <param name="discriminator">識別器</param>
        /// <param name="classForTest">識別対象の教師データモデル</param>
        /// <returns>判別結果を表にまとめたもの(実態はVector[])</returns>
        /// <exception cref="SystemException">クラスモデルがセットされた状態でなければ例外をスロー</exception>
        private Vector[] CreateDecisionChart(NeuralNetwork discriminator, List<Model> classForTest)
        {
            if (this.Ready == false)
                throw new SystemException("実験の準備はまだ完了しておりません。モデルを追加して下さい。");
            if (discriminator == null)
                throw new Exception("判別器のインスタンスが確保されておらず、判別表を作ることができません。");

            Vector[] decisionChart = new Vector[classForTest.Count];
            for (int i = 0; i < decisionChart.Length; i++)
                decisionChart[i] = new Vector(this.Length);
            int modelIndex = 0;
            foreach (Model model in classForTest)                               // 保持しているモデルの数だけループ
            {

                Model.ReadSequenceMode backup = model.SequenceMode;
                model.SequenceMode = Model.ReadSequenceMode.InRotation;         // 重複を避けるため、特徴ベクトルの選出方法はローテーションとする
                // 特徴モデルを順番に呼び出す
                for (int count = 0; count < model.LengthForGetFeature; count++)
                {
                    Feature feature = model.GetFeature();                       // 特徴ベクトルをモデルから取得
                    Vector outputForDebug = discriminator.Recognize(feature);   // 識別
                    // 最大値となるインデックスを検索
                    double max = double.MinValue;
                    int maxIndex = 0;
                    for (int j = 0; j < outputForDebug.Length; j++)
                    {
                        if (max < outputForDebug[j])
                        {
                            max = outputForDebug[j];
                            maxIndex = j;
                        }
                    }
                    // 最大値となったインデックスについて、加算
                    decisionChart[modelIndex][maxIndex] += 1;
                }
                model.SequenceMode = backup;
                modelIndex++;
            }
            return decisionChart;
        }
        /// <summary>
        /// モデルオブジェクトを用いてセットアップします
        /// <para>既にセットされていたモデルは初期化されます。</para>
        /// <para>なお、モデルオブジェクトはディープコピーされます。</para>
        /// </summary>
        /// <param name="models">セットするモデル</param>
        public void Setup(Model[] models)
        {
            this._class = new List<Model>(0);
            if (models == null)
                throw new Exception("TeacherクラスのSetup()メソッドにてエラーがスローされました。モデルのインスタンスが確保されていません。");

            int featureSize = 0;
            foreach (var model in models)
            {
                if (models == null)
                    throw new Exception("TeacherクラスのSetup()メソッドにてエラーがスローされました。モデルのインスタンスが確保されていません。");
                if (featureSize == 0)
                    featureSize = model.MemberSize;
                else if (featureSize != model.MemberSize)
                    throw new Exception("TeacherクラスのSetup()メソッドにてエラーがスローされました。特徴ベクトルの長さに統一性がありません。モデルに格納されている特徴ベクトルのサイズをチェックして下さい。");
            }
            // モデルをセット
            for(int i = 0; i < models.Length; i++)
            {
                this._class.Add(new Model(models[i]));
            }
            // ニューラルネットを構成
            this._NNet = new NeuralNetwork();                           // ニューラルネットクラスのインスタンスを生成
            this._NNet.Setup(this.parameter, featureSize, models.Length);
            return;
        }
        /// <summary>
        /// 交差確認法(Cross Validation)を用いた特徴ベクトルの評価を行います
        /// <para>確認結果をファイルとして出力します。ファイル名は引数で指定可能です。</para>
        /// <para>モデル選択はランダム、モデルからの特徴データの取得もランダムとします。</para>
        /// <para>本メソッドによる学習成果はModelクラスの保持する識別器へは反映されません。</para>
        /// <para>交差検定で使用する分割数は教師データ数に合わせて自動的に決定されます。</para>
        /// <para>コード可読性が低いのでいつか変えると思う。。。</para>
        /// </summary>
        /// <param name="learningTimes">一回の試行当たりの学習回数</param>
        /// <param name="checkNum">試行回数</param>
        /// <param name="fname">保存ファイル名</param>
        /// <returns>交差検定で使用した分割数<para>最大でも10となります。</para></returns>
        public int CV(int learningTimes, int checkNum, string fname = "decision chart.csv")
        {
            if (learningTimes == 0 || checkNum == 0)
                throw new Exception("Teacherクラスにおいてエラーがスローされました。CV()の引数の値が不正です。");
            var backupOfPom = this.PickOutMode;                                 // 各設定のバックアップを取る
            this.PickOutMode = Model.ReadSequenceMode.AtRandom;                 // Lean()での学習をランダムにする
            var backupOfMode = this.RandomModelChoice;
            this.RandomModelChoice = true;

            int divideLimit = int.MaxValue;
            foreach (Model model in this._class)
            {
                model.Shuffle();                                                // 学習に使用する教師データをシャッフル
                if (divideLimit > model.Length)
                    divideLimit = model.Length;                                 // モデル数に合わせて分割数を調整するために、最小の教師データ数を計る
                model.FeatureSupplyMode = Model.SupplyMode.NeglectParticularGroup;
            }
            if (divideLimit > 10)
                divideLimit = 10;                                               // 最大の分割数を10とする
            foreach (Model model in this._class)
                model.DivisionNum = divideLimit;
            var sequence = new Vector[this.Length, checkNum * divideLimit];     // 時系列の識別率を格納する
            Parallel.For(0, checkNum * divideLimit, i =>
            //for(int i = 0; i < checkNum * divideLimit; i++)
            {
                NeuralNetwork nn = new NeuralNetwork();                         // ニューラルネットのインスタンスを確保
                nn.Setup(this._NNet.GetParameter());
                var localModel = new List<Model>(0);
                int maskIndex = i % divideLimit;
                foreach (Model model in this._class)
                {
                    var cpyModel = new Model(model);
                    cpyModel.IndexForDivision = maskIndex;
                    cpyModel.FeatureSupplyMode = Model.SupplyMode.NeglectParticularGroup;       // 特定のグループを無視するモードに切り替える
                    localModel.Add(cpyModel);
                }
                this.Learn(learningTimes, nn, localModel);                                      // 学習させる
                foreach (Model model in localModel)
                {
                    model.FeatureSupplyMode = Model.SupplyMode.TakeParticularGroup;             // 特定のグループのみをテストするモードに切り替える
                }
                var tempResult = this.CreateDecisionChart(nn, localModel);                      // 学習に使わなかった教師データを用いた識別を行う
                for (int modelIndex = 0; modelIndex < this.Length; modelIndex++) sequence[modelIndex, i] = tempResult[modelIndex]; // 識別の結果を保存しておく
            });
            // 平均の識別率を計算し、ファイルへ保存する
            var averageDecisionChart = new Vector[this.Length];
            for (int modelIndex = 0; modelIndex < this.Length; modelIndex++)
                averageDecisionChart[modelIndex] = new Vector(this.Length);
            for (int i = 0; i < sequence.GetLength(1); i++)                                     // 平均の識別率を求めるために積算
                for (int modelIndex = 0; modelIndex < this.Length; modelIndex++)
                    averageDecisionChart[modelIndex] += sequence[modelIndex, i];
            for (int modelIndex = 0; modelIndex < this.Length; modelIndex++)                    // 平均の識別率を計算
                averageDecisionChart[modelIndex] /= ((double)checkNum * divideLimit);
            this.SaveDecisionChartBody(averageDecisionChart, this._class, "f1", fname);         // ファイルに保存
            // 識別率の標準偏差を計算し、ファイルへ保存する
            string fnameForDev = System.IO.Path.GetFileNameWithoutExtension(fname) + ".dev";    // 標準偏差を計算する準備
            var deviation = new Vector[this.Length];
            for (int modelIndex = 0; modelIndex < this.Length; modelIndex++)
                deviation[modelIndex] = new Vector(this.Length);
            for (int i = 0; i < sequence.GetLength(1); i++)                                     // 分散*(n-1)を計算
                for (int modelIndex = 0; modelIndex < this.Length; modelIndex++)
                    deviation[modelIndex] += (sequence[modelIndex, i] - averageDecisionChart[modelIndex]).Pow(2.0);
            for (int modelIndex = 0; modelIndex < this.Length; modelIndex++)                    // 標準偏差を求める
                deviation[modelIndex] = (deviation[modelIndex] / (double)(checkNum * divideLimit - 1)).Pow(0.5);
            using (System.IO.StreamWriter sw = new System.IO.StreamWriter(fnameForDev, false, Encoding.UTF8))
            {
                for (int j = 0; j < this.Length; j++) sw.Write("," + this.ModelIDs[j].ToString() + "を出力"); // ラベル
                sw.Write("\n");
                for (int i = 0; i < deviation.Length; i++)
                {
                    sw.Write(this.ModelIDs[i].ToString() + "を入力");
                    sw.WriteLine("," + deviation[i].ToString("f2"));
                }
                sw.Write("\n");
            }
            // 後始末
            this.PickOutMode = backupOfPom;
            this.RandomModelChoice = backupOfMode;
            for (int modelIndex = 0; modelIndex < this.Length; modelIndex++)
            {
                this._class[modelIndex].FeatureSupplyMode = Model.SupplyMode.NonDivide;     // 分割しないように戻しておく
            }
            return divideLimit;
        }