🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
深層学習

深層学習は、多数のレイヤのニューラルネットワークによる機械学習手法。人工知能研究の一つでディープラーニングとも呼ばれています。コンピューター自体がデータの潜在的な特徴を汲み取り、効率的で的確な判断を実現することができます。

PyTorch

PyTorchは、オープンソースのPython向けの機械学習ライブラリ。Facebookの人工知能研究グループが開発を主導しています。強力なGPUサポートを備えたテンソル計算、テープベースの自動微分による柔軟なニューラルネットワークの記述が可能です。

機械学習

機械学習は、データからパターンを自動的に発見し、そこから知能的な判断を下すためのコンピューターアルゴリズムを指します。人工知能における課題のひとつです。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

2回答

4964閲覧

2値分類問題での損失関数の上昇理由と対処法を知りたい。

TakoyakiOishii

総合スコア16

深層学習

深層学習は、多数のレイヤのニューラルネットワークによる機械学習手法。人工知能研究の一つでディープラーニングとも呼ばれています。コンピューター自体がデータの潜在的な特徴を汲み取り、効率的で的確な判断を実現することができます。

PyTorch

PyTorchは、オープンソースのPython向けの機械学習ライブラリ。Facebookの人工知能研究グループが開発を主導しています。強力なGPUサポートを備えたテンソル計算、テープベースの自動微分による柔軟なニューラルネットワークの記述が可能です。

機械学習

機械学習は、データからパターンを自動的に発見し、そこから知能的な判断を下すためのコンピューターアルゴリズムを指します。人工知能における課題のひとつです。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

1グッド

0クリップ

投稿2021/03/20 03:28

編集2021/03/20 05:47

前提・実現したいこと

何故、損失関数の値が上がっていくのかの理由と、その対処法を知りたいです。

使用している損失関数、最適アルゴリズム

損失関数 MSELoss
最適アルゴリズム SGD

発生している問題・エラーメッセージ

現在、ツイッターbotがbotかそうではないか(0or1)の判断をする機械学習プログラムをpytorchで作っています。
そこで、以下のような事態に遭遇しました。

正解データの0と、1の数です。
0 1073
1 197

###BCELoss使用
####SGD lr =0.005 エポック数 500
イメージ説明

####SGD lr =0.005 エポック数 2000
イメージ説明

####SGD lr =0.001 エポック数 500
イメージ説明

####SGD lr =0.001 エポック数 2000
イメージ説明

####Adam デフォルト エポック数 500
イメージ説明

####Adam デフォルト エポック数 2000
イメージ説明

###--------------------------追記(修正依頼参照)--------------------------

BCEWithLogitsLoss使用

####SGD lr =0.005 エポック数 500
イメージ説明
####SGD lr =0.005 エポック数 2000
イメージ説明

####SGD lr =0.001 エポック数 500
イメージ説明

####SGD lr =0.001 エポック数 2000
イメージ説明

####Adam デフォルト エポック数 500
イメージ説明

####Adam デフォルト エポック数 2000
イメージ説明

###Adamの挙動確認 layer =self.l1のみ(Sigmoid不使用)、BCEWithLogitsLoss使用

####Adam デフォルト エポック数 500
イメージ説明

####Adam デフォルト エポック数 2000
イメージ説明

該当のソースコード

python

1import torch 2import torch.nn as nn 3import torch.optim as optimizers 4class MLP(nn.Module): 5 def __init__(self,input_dim,output_dim): 6 super().__init__() 7 self.l1 = nn.Linear(input_dim,1) 8 self.a1 = nn.Sigmoid() 9 self.l2 = nn.Linear(3,1) 10 self.a2 = nn.Sigmoid() 11 12 self.layers = [self.l1,self.a1] 13 14 def forward(self,x): 15 for layer in self.layers: 16 x = layer(x) 17 return x 18 19device = torch.device("cuda" if torch.cuda.is_available() else "cpu") 20 21model = MLP(x_train.shape[1],1).to(device) 22 23criterion = nn.BCELoss() 24 25def compute_loss(t,y): 26 return criterion(y,t) 27 28optimizers = optimizers.SGD(model.parameters(),lr=0.005)

python

1import matplotlib.pyplot as plt 2from sklearn.utils import shuffle 3 4def train_step(x,t): 5 model.train() 6 preds = model(x) 7 loss = compute_loss(t,preds) 8 loss.backward() 9 optimizers.step() 10 return loss 11 12epochs = 1000 13train_loss_list = [] 14test_loss_list = [] 15 16""" 17データの整形 18""" 19 20x_train_row = x_train.values.reshape(-1,x_train.shape[1]) 21x_test_row = x_test.values.reshape(-1,x_test.shape[1]) 22 23for epoch in range(epochs): 24 train_loss = 0 25 test_loss = 0 26 27 x_train_row,y_train_row=shuffle(x_train_row,y_train_row) 28 x_ = torch.tensor(x_train_row).float().to(device) 29 t_ = torch.tensor(y_train_row).float().to(device) 30 loss = train_step(x_,t_).data.cpu().numpy() 31 train_loss = loss.item() 32 33 train_loss_list.append(train_loss) 34 now_epoch = epoch 35 36 37plt.plot(np.arange(0,now_epoch+1,1),train_loss_list) 38plt.xlabel("epoch数") 39plt.ylabel("BCELoss")

試したこと

考えてみました。
SGDは確率的に、-(y-t)なので、yが小さくなるにつれてy-tは、0になっていくはずです。なので、y=0、t=1となれば確かに上昇はします。が、グラフの上昇はそれが約4000エポック起こり続けていると言うことになるかと思います。yは、0以下の数字を取ることはないので、何が起きてるのか結局理解できませんでした。

その他

初心者なので考え方自体が間違っているかもしれませんが、その際はご指摘頂けますと幸いでございます。
よろしくお願いいたします。

toast-uz👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

toast-uz

2021/03/20 03:52

ソースコードはSGDではなくAdamになっています。
TakoyakiOishii

2021/03/20 03:55 編集

ありがとうございます。 SGDに修正させていただきました。
toast-uz

2021/03/20 04:07

最適化アルゴリズムの普通の選択はAdamですが、それをわざわざSGDにして、かつ学習率もデフォルトから大きくしていますね。Adamの結果や、学習率がデフォルトの場合の結果も記載していただくとよいと思います。
TakoyakiOishii

2021/03/20 04:15

詳しくご説明いただき、ありがとうございます。 不勉強で申し訳ないのですが、学習率のデフォルトって、0.01でしょうか?(kerasの公式リファレンスにのってました) pytorchのoptimizerだとデフォルトが何なのかよく分からなく、念のため質問致しました。
toast-uz

2021/03/20 04:21

すみません。SGDにはデフォルト値はありませんでした。Pytorchでは、SGDの学習率はデフォルト未定義(設定必要)です。Adamは0.001です。
TakoyakiOishii

2021/03/20 04:26

ご説明頂きありがとうございます。Adamの学習率も教えていただき、助かりました。 恐縮なのですが、 SGDのlrを未定義ですと、以下のようになってしまいます。 『parameter group didn't specify a value of required optimization parameter lr パラメータグループが必要な最適化パラメータlrの値を指定していません』 ですので、adamと同じ学習率をデフォルトと仮定して、修正させていただきます。 ご丁寧な修正依頼、大変感謝しております。
toast-uz

2021/03/20 04:31

Adamの学習率はデフォルト値があるので設定不要、SGDの学習率はデフォルト値が無いので設定必要です。だからSGDで学習率を設定しないとエラーが出るのは正しい動きです。 Adamで結果が同様なのかを教えていただけますか?
TakoyakiOishii

2021/03/20 04:40 編集

ありがとうございます。勘違いしておりました。 修正致しました。結果は、Adamのデフォルトの場合は損失関数の上昇も見られませんでした。質問本文にも記載しております。
toast-uz

2021/03/20 05:11

Adamはまだ収束していないようですので、epoch数を増やして、lossが収束するのか、それともSGDのように発散するのか、を確認いただけますでしょうか? train_lossがこのような単調に発散して再収束しないのは、あまり見たことがなく、何か不具合がありそうですね。モデルがあまりにもシンプル(1次元の線形層+Sigmoidが1層だけ)なのは気になりますが。
TakoyakiOishii

2021/03/20 05:27

ありがとうございます。 エポック数が2000の場合を全て追記致しました。 もしかしてなのですが、正解データの0の割合が多い事が関係してますでしょうか? 正解データに0が多いため、初めは、全体としてy=0になるように一気に学習が進むが、ほぼ0に達した瞬間にw-μΣ(y-t) = w-μΣ(0-1) で、大部分が正の方向に発散する。加えて...toast-uzさんのおっしゃるようにモデルがシンプル故に、一つ一つの重みwの重要度が高いため、fourteenlengthさんの回答のように、あらぬ方向にそのまま学習が進みと言う事かと理解しかかっていたのですが、如何でしょうか?
toast-uz

2021/03/20 05:37 編集

修正ありがとうございます。Adamは挙動はかなり安定するものの、それでもやや不安定に見えますね。 正解データの件も、fourteenlength様の回答も、一度下がったtrain_lossが再度増加して戻ってこない(特に瞬間的なものではなく100エポック以上も同傾向で増加している)理由には弱いと思います。(fourteenlength様の回答そのものは間違っていないですが、この挙動を説明しきれていない) さて、PyTorchのドキュメントでは、Sigmoid + BCELossは不安定なので、BCEWithLogitsLossを使え、という記述があります。 https://pytorch.org/docs/stable/generated/torch.nn.BCEWithLogitsLoss.html 質問者様のコードでいうと、以下を書き換えてみて、挙動が変わるか試せますでしょうか? self.layers = [self.l1,self.a1] → self.layers = [self.l1] criterion = nn.BCELoss() → criterion = nn.BCEWithLogitsLoss()
TakoyakiOishii

2021/03/20 05:50

詳しく教えていただきありがとうございます。修正致しました。 こうやってみると、まだ詳しく調べられていませんが損失関数側に問題があった。と言う可能性も高いような気もしてきました。
toast-uz

2021/03/20 06:18

だいぶ改善されましたね。モデルと損失関数の組み合わせが不安定、かつ、最適化アルゴリズムもSGDは少し最適化が弱いですので、その組み合わせでしょう。回答書きます。
guest

回答2

0

ベストアンサー

質問者様にいろいろ実験していただいて、2つの要因があることがわかりました。特に今回の現象は2)のウエイトが高かったようです。

  1. 最適化アルゴリズムに比較的収束しにくいSGDを利用していたこと。Adamに変えたところ、収束性がよくなりました。

  2. モデル最終層の活性化関数Sigmoidと、損失関数BCElossを組み合わせていたこと。この組み合わせは不安定であるようです。Sigmoidを外して、損失関数をBCEWithLogitsLossに変えたところ、収束性がよくなりました。

なお、2)について、最終層にSigmoidを入れて出力を0〜1にするのは、一般的には良い打ち手です(最終層以外の活性化関数はReLUが推奨されます)。ただし、BCElossとの組み合わせは不安定になりやすいとのことで、さらにSGDと組み合わせることで顕著に不安定さが出たものと考えます。BCEWithLogitsLossはSigmoid+BCEloss相当でありながら安定性を高めた損失関数です。そのため、BCEWithLogitsLossを使う場合は、モデル側の最終層のSigmoidは不要です。

参考: PyTorchドキュメントのBCEWithLogitsLossの記述
This version is more numerically stable than using a plain Sigmoid followed by a BCELoss as, by combining the operations into one layer, we take advantage of the log-sum-exp trick for numerical stability.

投稿2021/03/20 06:36

toast-uz

総合スコア3266

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

TakoyakiOishii

2021/03/20 07:09 編集

今回は、toast-uz様にご知力頂きながら実験できた事、その上でアンサーを頂けたのでこちらの回答をベストアンサーとさせていただきました。 ご回答いただいた、fourteenlength様にもこの場で感謝させていただきます。なお、両者様の回答に高評価をつけさせていただきました。toast-uz様、fourteenlength様ありがとうございました。
toast-uz

2021/03/20 06:54

最初はfourteenlength様の回答でほぼOKかと思いましたが、実験してみたらいろいろ興味深い結果になり、私も知らなかった話が学べました。 なお、モデルからSigmoidを省いていますので、このモデルを使ってテストデータで推論する際は、モデルの出力後にSigmoidを作用させてあげる必要があります。ご注意ください。
guest

0

(コードは抜きに)一般的な答えを書きます。

よくあるのは勾配爆発で、よくあるその原因は学習率が高すぎるです。
凸凹していて、ところどころグーンとへこんでいる平面を想像してください。

SGDは割とゆっくりと進んでアタリを探していきますが、Adamは筋がよさそうだと踏むと加速するようアタリに向かっていきます。

(掲載はSGDとありますがコードはAdamとあります。勾配爆発の起こりやすさがAdamの方が起こりやすいだけで恐らくSGDでも同じようにお消える問題です)

このアタリに向かっていく過程で、「こっちかな?あっちかな?」とパラメータを調整していくのですが、その時にやりすぎてしまうことがあります。ここでいうやりすぎ=学習率が高すぎで、美味しいアタリに近かったのにはるかに外れた場所に行ってしまい、戻るのにまた「そんなに遠いならグーンと移動してしまえばいいや」というような状態に陥ります。

ですので、対策は

  1. 学習率を少し下げる(お手軽)
  2. 学習するにしたがって徐々に学習率を下げるようなコード(スケジューラ)を挟む

気になる部分をまとめます

・ 層がかなり浅い(class MLP)ので、学習率よりもネットワーク構造そのものの方が影響しそうな気がします。
・ 層が深いほど活性化関数としてのシグモイドはあまり良くないと言われています。これをReLUにするだけでも結果が変わるかもしれません。

投稿2021/03/20 03:53

退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

toast-uz

2021/03/20 04:04

Sigmoidの件は一般論としてそのとおりですが、最終層に使うSigmoidは、結果を0〜1に収めるために、そのままである必要があります。今回特に、1層(self.layers = [self.l1,self.a1])しか使っていないですので、self.a1をReLUに変えない方が良いです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問