/// <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>
 /// 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>
        /// 交差確認法(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;
        }