この記事では、トレーニングと検証の考えられる状況をまとめ、これらのチャートがどのような情報を提供できるかを紹介します。 簡単なコードから始めましょう。次のコードは、トレーニング プロセスの基本的なフレームワークを設定します。 sklearn.model_selection から train_test_split をインポートします sklearn.datasets から make_classification をインポートします 輸入トーチ torch.utils.data から Dataset、DataLoader をインポートします torch.optim を torch_optim としてインポートします。 torch.nnをnnとしてインポートする torch.nn.function を F としてインポートします。 numpyをnpとしてインポートする matplotlib.pyplot を pltclass MyCustomDataset(Dataset) としてインポートします。 def __init__(self, X, Y, スケール=False): 自己.X = torch.from_numpy(X.astype(np.float32)) 自己.y = torch.from_numpy(Y.astype(np.int64))
__len__(自分)を定義します: len(self.y) を返す
__getitem__(self, idx)を定義します。 self.X[idx]、self.y[idx]を返します。def get_optimizer(model、lr=0.001、wd=0.0): パラメータ = フィルター(lambda p: p.requires_grad, model.parameters()) optim = torch_optim.Adam(パラメータ、lr=lr、weight_decay=wd) optimdef train_model(model, optim, train_dl, loss_func) を返します: # モデルがトレーニングモードになっていることを確認する モデル.train() 合計 = 0 合計損失 = 0 train_dlのx、yについて: バッチ = y.shape[0] # このバッチ分のデータに対してモデルをトレーニングする ロジット = モデル(x) # 損失関数を実行します。これはトレーニングループを呼び出すときに決定します。 損失 = loss_func(logits, y) # 次の3行はPyTorchのバックプロパゲーションの優れた機能をすべて実行します 最適化ゼロ勾配() 損失.後方() 最適化ステップ() # このエポックのサンプルの総数を継続的にチェックする 合計 += バッチ # そして損失の合計を記録しておく sum_loss += バッチ*(loss.item()) sum_loss/totalを返す def train_loop(モデル、train_dl、valid_dl、エポック、loss_func、lr=0.1、wd=0): optim = get_optimizer(モデル、lr=lr、wd=wd) トレーニング損失リスト = [] val_loss_list = [] acc_list = [] i が範囲(エポック)内である場合: 損失 = train_model(モデル、最適化、train_dl、損失関数) # このエポックをトレーニングした後、進捗状況のリストを保持します # 各時代の損失 train_loss_list.append(損失) val、acc = val_loss(モデル、valid_dl、loss_func) # 検証損失と精度についても同様 val_loss_list.append(val) acc_list.append(acc) print("トレーニング損失: %.5f 有効な損失: %.5f 精度: %.5f" % (loss, val, acc))
train_loss_list、val_loss_list、acc_list を返す val_loss(モデル、valid_dl、loss_func): # モデルをトレーニングモードではなく評価モードにする モデル評価() 合計 = 0 合計損失 = 0 正解 = 0 バッチカウント = 0 valid_dl の x、y について: バッチカウント += 1 現在のバッチサイズ = y.shape[0] ロジット = モデル(x) 損失 = loss_func(logits, y) 合計損失 += 現在のバッチサイズ*(損失.item()) 合計 += 現在のバッチサイズ # 上記のコードはすべて本質的には同じであり、 # トレーニングなのでコメントを見てください # 返された予測のうちどれが最も大きな声で聞こえるか調べる # すべて、そしてそれが私たちの予測です preds = logits.sigmoid().argmax(1) # 私たちの予測が正しいかどうか見てみましょう 正解 += (予測値 == y).float().mean().item() sum_loss/total、correct/batch_count を返す def view_results(train_loss_list、val_loss_list、acc_list): plt.rcParams["figure.figsize"] = (15, 5) plt.figure() エポック = np.arange(0, len(train_loss_list)) plt.subplot(1, 2, 1) plt.plot(エポック-0.5、train_loss_list) plt.plot(エポック、val_loss_list) plt.title('モデル損失') plt.ylabel('損失') plt.xlabel('エポック') plt.legend(['train', 'val', 'acc'], loc = '左上')
plt.サブプロット(1, 2, 2) plt.plot(acc_list) plt.title('精度') plt.ylabel('精度') plt.xlabel('エポック') plt.legend(['train', 'val', 'acc'], loc = '左上') plt.show()
def get_data_train_and_show(モデル、batch_size=128、n_samples=10000、n_classes=2、n_features=30、val_size=0.2、epochs=20、lr=0.1、wd=0、break_it=False): # 関連するすべてのデータが揃っていると仮定して、架空のデータセットを作成します。 # EDA / 機能エンジニアリングが完了し、これが私たちの # 結果データ X、y = make_classification(n_samples=n_samples、n_classes=n_classes、n_features=n_features、n_informative=n_features、n_redundant=0、random_state=1972)
if break_it: # 具体的にデータを壊す X = np.random.rand(n_samples,n_features) X_train、X_val、y_train、y_val = train_test_split(X、y、test_size=val_size、random_state=1972) train_ds = MyCustomDataset(X_train、y_train) valid_ds = MyCustomDataset(X_val, y_val) train_dl = DataLoader(train_ds、batch_size=batch_size、shuffle=True) valid_dl = DataLoader(valid_ds、batch_size=batch_size、shuffle=True) train_loss_list、val_loss_list、acc_list = train_loop(model、train_dl、valid_dl、epochs=epochs、loss_func=F.cross_entropy、lr=lr、wd=wd) 結果を表示(train_loss_list、val_loss_list、acc_list) 上記のコードは非常にシンプルです。データの取得、トレーニング、検証という基本的なプロセスです。それでは本題に入りましょう。 シナリオ 1 - モデルは学習しているように見えるが、検証や精度のパフォーマンスが低いハイパーパラメータに関係なく、モデルのトレーニング損失はゆっくりと減少しますが、値損失は減少せず、精度は何かを学習していることを示しません。 たとえば、この場合、バイナリ分類の精度は約 50% になります。 クラス Scenario_1_Model_1(nn.Module): def __init__(self, in_features=30, out_features=2): スーパー().__init__() self.lin1 = nn.Linear(in_features, out_features) def forward(self, x): x = 自己.lin1(x) xを返す get_data_train_and_show(シナリオ1モデル1()、lr=0.001、break_it=True) データには「学習」を可能にするのに十分な情報がありません。トレーニング データには、モデルが「学習」するのに十分な情報が含まれていない可能性があります。 この場合(コード内のトレーニング データはランダムです)、実質的なことは何も学習できないことを意味します。 データには、そこから学習するのに十分な情報が含まれている必要があります。 EDA と機能エンジニアリングが鍵です! モデルは、存在しないものを作り出すのではなく、学習可能なものを学習します。 シナリオ2 - トレーニング、検証、精度曲線はすべて非常に不安定ですたとえば、次のコード: lr=0.1, bs=128 クラス Scenario_2_Model_1(nn.Module): def __init__(self、in_features=30、out_features=2): スーパー().__init__() self.lin1 = nn.Linear(in_features, out_features) def forward(self, x): x = 自己.lin1(x) xを返す get_data_train_and_show(シナリオ2モデル1()、lr=0.1) 「学習率が高すぎる」または「バッチ サイズが小さすぎる」学習率を 0.1 から 0.001 に下げてみてください。これにより、「跳ね返る」ことはなく、スムーズに減少します。 get_data_train_and_show(シナリオ1モデル1()、lr=0.001) 学習率を下げるだけでなく、バッチ サイズを増やすと、よりスムーズになります。 get_data_train_and_show(シナリオ1_モデル1()、lr=0.001、バッチサイズ=256) シナリオ3 - トレーニング損失はゼロに近く、精度は良好だが、検証は低下していないが、増加しているクラス Scenario_3_Model_1(nn.Module): def __init__(self、in_features=30、out_features=2): スーパー().__init__() self.lin1 = nn.Linear(in_features, 50) 自己.lin2 = nn.Linear(50, 150) 自己.lin3 = nn.Linear(150, 50) self.lin4 = nn.Linear(50, out_features) def forward(self, x): x = F.relu(self.lin1(x)) x = F.relu(self.lin2(x)) x = F.relu(self.lin3(x)) x = 自己.lin4(x) xを返す get_data_train_and_show(シナリオ3_モデル1()、lr=0.001) これは明らかに過剰適合です。トレーニング損失は低く、精度は高いですが、検証損失とトレーニング損失はどんどん大きくなっており、これは典型的な過剰適合の指標です。 基本的に、モデルの学習があまりにもうまくいっています。 トレーニング データを非常によく記憶しているため、新しいデータに一般化することもできません。 最初に試せるのは、モデルの複雑さを軽減することです。 クラス Scenario_3_Model_2(nn.Module): def __init__(self, in_features=30, out_features=2): スーパー().__init__() self.lin1 = nn.Linear(in_features, 50) self.lin2 = nn.Linear(50, out_features) def forward(self, x): x = F.relu(self.lin1(x)) x = 自己.lin2(x) xを返す get_data_train_and_show(シナリオ3_モデル2()、lr=0.001) これにより、モデルはより良くなりますが、L2 重み減衰正規化を導入して、さらにモデルを良くすることもできます (より浅いモデルの場合)。 get_data_train_and_show(シナリオ3_モデル2()、lr=0.001、wd=0.02) モデルの深さとサイズを維持したい場合は、ドロップアウト (より深いモデルに適しています) の使用を試すことができます。 クラス Scenario_3_Model_3(nn.Module): def __init__(self, in_features=30, out_features=2): スーパー().__init__() self.lin1 = nn.Linear(in_features, 50) 自己.lin2 = nn.Linear(50, 150) 自己.lin3 = nn.Linear(150, 50) self.lin4 = nn.Linear(50, out_features) 自己.ドロップ = nn.ドロップアウト(0.4) def forward(self, x): x = F.relu(self.lin1(x)) x = 自己ドロップ(x) x = F.relu(self.lin2(x)) x = 自己ドロップ(x) x = F.relu(self.lin3(x)) x = 自己ドロップ(x) x = 自己.lin4(x) xを返す get_data_train_and_show(シナリオ3_モデル3()、lr=0.001) シナリオ4 - トレーニングと検証はうまくいったが、精度は向上しなかったlr = 0.001、bs = 128(デフォルト、分類カテゴリ = 5 クラス Scenario_4_Model_1(nn.Module): def __init__(self, in_features=30, out_features=2): スーパー().__init__() self.lin1 = nn.Linear(in_features, 2) self.lin2 = nn.Linear(2, out_features) def forward(self, x): x = F.relu(self.lin1(x)) x = 自己.lin2(x) xを返す get_data_train_and_show(シナリオ4モデル1(出力機能=5)、lr=0.001、n_classes=5) 学習能力が不十分です: モデル内のレイヤーの 1 つに、モデルの可能な出力のクラス数よりも少ないパラメータがあります。 この場合、出力クラスは 5 つありますが、中央のパラメーターは 2 つだけです。 これは、モデルを埋めるために情報をより小さなレイヤーに通す必要があるため情報が失われ、レイヤーのパラメータが再び拡大されるとこの情報を回復することが困難になることを意味します。 したがって、記録層のパラメータはモデルの出力サイズよりも小さくなってはなりません。 クラス Scenario_4_Model_2(nn.Module): def __init__(self、in_features=30、out_features=2): スーパー().__init__() self.lin1 = nn.Linear(in_features, 50) self.lin2 = nn.Linear(50, out_features) def forward(self, x): x = F.relu(self.lin1(x)) x = 自己.lin2(x) xを返す get_data_train_and_show(シナリオ4モデル2(出力機能=5)、lr=0.001、n_classes=5) 要約する上記は、トレーニングおよび検証中の曲線の一般的な例です。同じ状況に遭遇したときに、すぐに見つけて改善できることを願っています。
|