/// <summary>
 /// コピーコンストラクタ
 /// <para>メンバーはディープコピーされます。</para>
 /// </summary>
 /// <param name="model">コピー元のモデル</param>
 public Model(Model model)
     : base()
 {
     this.className = model.className;
     this.SequenceMode = model.SequenceMode;
     this.FeatureSupplyMode = model.FeatureSupplyMode;
     this.MemberSize = model.MemberSize;
     foreach (Feature newMenber in model.buff)
     {
         this.buff.Add(new Feature(newMenber));
         if (this.MemberSize != newMenber.Length) throw new SystemException("渡された特徴ベクトルのサイズが不統一です。");
     }
     this.DivisionNum = model.DivisionNum;           // これらはメンバーのコピーの後でないと、代入ができない。詳しくはプロパティを見てほしい。
     this.IndexForDivision = model.IndexForDivision;
     this.myRandom = new Random(this.className.GetHashCode());
 }
        /// <summary>
        /// フォルダを指定してモデルデータを読み込む
        /// <para>既にセットされていたモデルは初期化されます。</para>
        /// <para>読み込んだファイル名の一覧よりモデルリストを作成し、ファイルの中身から入力層に必要なユニット数を確保します。</para>
        /// <para>読み込まれた特徴量がモデルによって異なる場合は、学習の段階で入力ユニット数と特徴ベクトルのサイズの不一致に関するエラーがスローされます。</para>
        /// </summary>
        /// <param name="dirName">特徴データの格納されたフォルダ名</param>
        /// <exception cref="SystemException">ファイルから読み込んだ特徴ベクトルの長さとニューラルネットの入力層ユニット数が一致しない場合にスロー</exception>
        public void Setup(string dirName)
        {
            this._class = new List<Model>(0);
            if (System.IO.Directory.Exists(dirName) == false)
                throw new ArgumentException("TeacherクラスのSetup()メソッドにてエラーがスローされました。指定されたフォルダは存在しません。");

            string[] fnames = System.IO.Directory.GetFiles(dirName, "*.fea");   // フォルダ内の指定拡張子を持つファイル一覧を取得
            if (fnames.Length > 1)
            {
                Hashtable modelID = Teacher.GetModelIDs(fnames);                 // ファイル名の一覧からユニークなモデルIDを取得
                if (modelID.Count >= 2)
                {
                    var models = new Model[modelID.Count];                      // 存在するファイルの数だけモデルを生成する
                    // クラスモデルを構成
                    foreach(var fname in fnames)
                    {
                        string id = Teacher.GetModelIDname(fname);              // ファイル名からモデル名を取得
                        var index = (int)modelID[id];                           // modelID[id]で、番号を得ることができる
                        if (models[index] == null)
                            models[index] = new Model(id);                      // インスタンスが未生成なら作る
                        using (System.IO.StreamReader sr = new System.IO.StreamReader(fname, Encoding.UTF8))
                        {
                            try
                            {
                                while (sr.EndOfStream == false)
                                {
                                    string line = sr.ReadLine();                // 一行読み込み
                                    Feature feature = new Feature(line);        // 読み込んだテキストデータから特徴ベクトルを生成
                                    models[index].Add(feature);                 // 特徴を追加
                                }
                            }
                            catch (Exception e)
                            {
                                string message = "エラーがスローされました。\nエラーメッセージ:" + e.Message + "エラーの発生したファイル名:" + fname;
                                Console.WriteLine(message);
                                throw new Exception(message);                   // さらにスロー
                            }
                        }
                    }
                    this.Setup(models);
                }
                else
                    throw new Exception("TeacherクラスのSetup()メソッドにてエラーがスローされました。モデルが2つ以上見つかりませんでした。");
            }
            else
                throw new Exception("TeacherクラスのSetup()メソッドにてエラーがスローされました。特徴ベクトルを格納したファイル(*.fea)が2つ以上見つかりませんでした。フォルダ若しくは拡張子をチェックして下さい。");
            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;
        }
        /// <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>
 /// クラスモデルを追加する
 /// <para>クラスモデルはディープコピーされます。</para>
 /// </summary>
 /// <param name="newClass">追加するクラスモデル</param>
 /// <exception cref="SystemException">モデルの2重登録があるとスロー</exception>
 public void AddModel(Model newClass)
 {
     this._class.Add(new Model(newClass));
     if (this.ExistOfSameID)
         throw new SystemException("モデルの2重登録が行われました。");
     return;
 }