PyTorchの損失関数
紹介
損失関数は機械学習モデルのトレーニングにおいて基本的なものであり、ほとんどの機械学習プロジェクトでは損失関数なしにモデルが正しい予測を行うことはできません。一般的に言えば、損失関数とは、モデルが特定のデータセットでどれだけうまく機能しているかを測るために使用される数学的な関数または式です。特定のデータセットに対してモデルがどれだけうまく機能しているかを知ることは、開発者がトレーニング中に新しい、より強力なモデルを使用したり、損失関数自体を別のタイプに変更したりするなど、多くの決定を下すための洞察を提供します。損失関数のタイプについて言えば、これまでに開発された損失関数はいくつかあり、それぞれ特定のトレーニングタスクに適したものです。
前提条件
この記事では神経ネットワークを理解することが必要です。高レベルでは、神経ネットワークは層に組織された相互接続されたノード(「ニューロン」)から構成されています。彼らは「トレーニング」と呼ばれるプロセスを通じて学習し、予測を行います。このプロセスではニューロン間の接続の重みやバイアスが調整されます。神経ネットワークの理解には、異なる層(入力層、隠れ層、出力層)、活性化関数、最適化アルゴリズム(勾配降下法の変種)、損失関数などに関する知識が含まれます。
さらに、本記事で提示されているコードスニペットを理解するためには、Pythonの構文やPyTorchライブラリに対する理解が不可欠です。
この記事では、PyTorchのnnモジュールに含まれるさまざまな損失関数について探ります。さらに、カスタム損失関数を構築することで、PyTorchがこれらの損失関数をどのようにnnモジュールAPIの一部としてユーザーに提供しているかを深堀りします。
損失関数が何であるかについての高レベルの理解を得たので、損失関数がどのように機能するかについてのより技術的な詳細を探ってみましょう。
損失関数とは何ですか?
前述のように、損失関数は、モデルが特定のデータセットでどれだけうまく機能しているかを示します。技術的には、予測値が実際の値にどれだけ近いかを測定することでこれを行います。モデルがトレーニングデータセットとテストデータセットの両方で実際の値に非常に近い予測を行っている場合、それはかなり堅牢なモデルがあることを意味します。
損失関数はモデルの性能に関する重要な情報を提供しますが、それが損失関数の主な機能ではありません。評価手法として精度やFスコアのようなより堅牢な手法があるからです。損失関数の重要性は主にトレーニング中に実感されます。この際、私たちはモデルの重みを損失を最小化する方向に調整します。そうすることで、モデルが正しい予測を行う可能性を高めます。これはおそらく損失関数がなければ実現できなかったでしょう。
異なる損失関数は異なる問題に適しており、それぞれが研究者によって注意深く設計されて、訓練中に安定した勾配の流れを確保しています。
時には、損失関数の数学的表現が少し daunting(困難)になることがあり、そのため一部の開発者はそれらをブラックボックスとして扱っています。後で PyTorch で最もよく使用される損失関数のいくつかを明らかにしますが、その前に、PyTorch の世界で損失関数をどのように使用するかを見てみましょう。
PyTorchにおける損失関数
PyTorchは、開発者がトレーニング中に異なる損失関数を迅速に反復できるようにする、シンプルなデザインパターンの多くの標準損失関数を、そのまま使用できる形で提供しています。すべてのPyTorchの損失関数は、すべてのニューラルネットワークのためのPyTorchの基本クラスであるnnモジュールにパッケージされています。これにより、プロジェクトに損失関数を追加するのが、たった1行のコードを追加するだけで簡単になります。PyTorchで平均二乗誤差損失関数を追加する方法を見てみましょう。
import torch.nn as nn
MSE_loss_fn = nn.MSELoss()
上記のコードから返される関数は、以下の形式を使用して予測値が実際の値からどれほど離れているかを計算するために使用できます。
#predicted_value is the prediction from our neural network
#target is the actual value in our dataset
#loss_value is the loss between the predicted value and the actual value
Loss_value = MSE_loss_fn(predicted_value, target)
PyTorchで損失関数をどのように使用するかのアイデアを得たので、PyTorchが提供するいくつかの損失関数の裏側に深く掘り下げてみましょう。
PyTorchで利用可能な損失関数はどれですか?
PyTorchに付属するこれらの損失関数の多くは、回帰損失、分類損失、ランキング損失の3つのグループに大きく分類されます。
回帰損失は主に、2つの限界の間の任意の値を取ることができる連続値に関係しています。この一例として、コミュニティの住宅価格の予測が挙げられます。
分類ロス関数は、物体を箱、ペン、またはボトルとして分類するタスクのように、離散値を扱います。
ランキング損失は、値の相対的な距離を予測します。この例として、顔の検証があります。ここでは、特定の顔に属する顔画像がどれかを知りたい場合に、対象となる顔のスキャンに対する相対的な近似度によって、元の顔の持ち主に属する顔と属さない顔をランキングすることによってそれを行うことができます。
L1損失関数/平均絶対誤差
L1損失関数は、予測されたテンソルの各値とターゲットの各値との間の平均絶対誤差を計算します。最初に、予測されたテンソルの各値とターゲットの各値との絶対差を計算し、各絶対差計算から得られるすべての値の合計を求めます。最後に、この合計値の平均を計算して平均絶対誤差(MAE)を得ます。L1損失関数は、ノイズを扱うのに非常に堅牢です。
import torch.nn as nn
#size_average and reduce are deprecated
#reduction specifies the method of reduction to apply to output. Possible values are 'mean' (default) where we compute the average of the output, 'sum' where the output is summed and 'none' which applies no reduction to output
Loss_fn = nn.L1Loss(size_average=None, reduce=None, reduction='mean')
input = torch.randn(3, 5, requires_grad=True)
target = torch.randn(3, 5)
output = loss_fn(input, target)
print(output) #tensor(0.7772, grad_fn=<L1LossBackward>)
返される単一の値は、次元が3×5の2つのテンソル間の計算された損失です。
平均二乗誤差
平均二乗誤差(MSE)は、平均絶対誤差(MAE)といくつかの顕著な類似点を共有しています。平均絶対誤差のように、予測テンソルとターゲットの間の絶対差を計算するのではなく、予測テンソルの値とターゲットテンソルの値との間の二乗差を計算します。これにより、比較的大きな差はより厳しく罰せられ、比較的小さな差はあまり厳しく罰せられません。ただし、MSEはMAEよりも外れ値やノイズを処理する際にロバスト性が低いと考えられています。
import torch.nn as nn
loss = nn.MSELoss(size_average=None, reduce=None, reduction='mean')
#L1 loss function parameters explanation applies here.
input = torch.randn(3, 5, requires_grad=True)
target = torch.randn(3, 5)
output = loss(input, target)
print(output) #tensor(0.9823, grad_fn=<MseLossBackward>)
クロスエントロピー損失
クロスエントロピー損失は、いくつかの離散的なクラスを含む分類問題で使用されます。これは、特定のランダム変数のセットに対する二つの確率分布の違いを測定します。通常、クロスエントロピー損失を使用する際、ネットワークの出力はソフトマックス層であり、これにより神経ネットワークの出力が確率値(0から1の間の値)になることが保証されます。
ソフトマックス層は2つの部分から構成されています - 特定のクラスの予測の指数です。
yiは特定のクラスに対するニューラルネットワークの出力です。この関数の出力は、yiが大きく負の場合はゼロに近いがゼロにはならず、yiが正で非常に大きい場合は1に近くなります。
import numpy as np
np.exp(34) #583461742527454.9
np.exp(-34) #1.713908431542013e-15
第二の部分は正規化値であり、ソフトマックス層の出力が常に確率値になるようにするために使用されます。
これは各クラス値のすべての指数を合計することによって得られます。ソフトマックスの最終方程式はこのようになります:
]
PyTorchのnnモジュールでは、クロスエントロピー損失はlog-softmaxと負の対数尤度(NLL)損失を単一の損失関数に統合しています。
印刷された出力の勾配関数がNLL損失であることに注目してください。これは実際に、クロスエントロピー損失が内部でNLL損失とログソフトマックス層を組み合わせていることを明らかにしています。
負の対数尤度 (NLL) 損失
NLL損失関数は、クロスエントロピー損失関数と非常に似たように機能します。クロスエントロピー損失は、ログソフトマックス層とNLL損失を組み合わせてクロスエントロピー損失の値を取得します。これは、NLL損失を使用して、ニューラルネットワークの最後の層を通常のソフトマックス層の代わりにログソフトマックス層にすることで、クロスエントロピー損失の値を取得できることを意味します。
m = nn.LogSoftmax(dim=1)
loss = nn.NLLLoss()
input is of size N x C = 3 x 5
input = torch.randn(3, 5, requires_grad=True)
each element in target has to have 0 <= value < C
target = torch.tensor([1, 0, 4])
output = loss(m(input), target)
output.backward()
2D loss example (used, for example, with image inputs)
N, C = 5, 4
loss = nn.NLLLoss()
input is of size N x C x height x width
data = torch.randn(N, 16, 10, 10)
conv = nn.Conv2d(16, C, (3, 3))
m = nn.LogSoftmax(dim=1)
each element in target has to have 0 <= value < C
target = torch.empty(N, 8, 8, dtype=torch.long).random_(0, C)
output = loss(m(conv(data)), target)
print(output) #tensor(1.4892, grad_fn=<NllLoss2DBackward>)
#credit NLLLoss — PyTorch 1.9.0 documentation
バイナリー交差エントロピー損失
バイナリクロスエントロピー損失は、データポイントを2つのクラスのみに分類する特別な問題のために使用されるクロスエントロピー損失の特別なクラスです。このタイプの問題に対するラベルは通常バイナリであり、したがって私たちの目標は、ゼロラベルに対してはゼロに近い数値を、1ラベルに対しては1に近い数値を予測するようにモデルを促すことです。通常、バイナリ分類のためにBCE損失を使用する際、ニューラルネットワークの出力はシグモイド層であり、出力がゼロに近い値または1に近い値のいずれかになることを保証します。
import torch.nn as nn
m = nn.Sigmoid()
loss = nn.BCELoss()
input = torch.randn(3, requires_grad=True)
target = torch.empty(3).random_(2)
output = loss(m(input), target)
print(output) #tensor(0.4198, grad_fn=<BinaryCrossEntropyBackward>)
ロジットを用いたバイナリ交差エントロピー損失
前のセクションで、バイナリ交差エントロピー損失は通常、出力が0と1の間になるようにシグモイドレイヤーとして出力されると述べました。ロジットを使用したバイナリ交差エントロピー損失は、これら2つのレイヤーを1つのレイヤーに統合します。PyTorchのドキュメンテーションによると、これはログ和指数のトリックを利用するため、数値的により安定したバージョンです。
import torch
import torch.nn as nn
target = torch.ones([10, 64], dtype=torch.float32) # 64 classes, batch size = 10
output = torch.full([10, 64], 1.5) # A prediction (logit)
pos_weight = torch.ones([64]) # All weights are equal to 1
criterion = torch.nn.BCEWithLogitsLoss(pos_weight=pos_weight)
loss = criterion(output, target) # -log(sigmoid(1.5))
print(loss) #tensor(0.2014)
スムーズL1損失
import torch.nn as nn
loss = nn.SmoothL1Loss()
input = torch.randn(3, 5, requires_grad=True)
target = torch.randn(3, 5)
output = loss(input, target)
print(output) #tensor(0.7838, grad_fn=<SmoothL1LossBackward>)
ヒンジ埋め込み損失
ヒンジ埋め込み損失は、主に半教師あり学習タスクにおいて、2つの入力間の類似性を測定するために使用されます。これは、入力テンソルと1または-1の値を含むラベルテンソルがあるときに使用されます。主に非線形埋め込みと半教師あり学習に関わる問題で使用されます。
import torch
import torch.nn as nn
input = torch.randn(3, 5, requires_grad=True)
target = torch.randn(3, 5)
hinge_loss = nn.HingeEmbeddingLoss()
output = hinge_loss(input, target)
output.backward()
print('input: ', input)
print('target: ', target)
print('output: ', output)
#input: tensor([[ 1.4668e+00, 2.9302e-01, -3.5806e-01, 1.8045e-01, #1.1793e+00],
[-6.9471e-05, 9.4336e-01, 8.8339e-01, -1.1010e+00, #1.5904e+00],
[-4.7971e-02, -2.7016e-01, 1.5292e+00, -6.0295e-01, #2.3883e+00]],
requires_grad=True)
#target: tensor([[-0.2386, -1.2860, -0.7707, 1.2827, -0.8612],
[ 0.6747, 0.1610, 0.5223, -0.8986, 0.8069],
[ 1.0354, 0.0253, 1.0896, -1.0791, -0.0834]])
#output: tensor(1.2103, grad_fn=<MeanBackward0>)
マージンランキング損失
マージンランキングロスは、他の損失関数とは異なり、データセット内の一連の入力の相対的な距離を測定することを主な目的とするランキング損失に属します。マージンランキングロス関数は、2つの入力と1または-1のラベルを含むラベルを受け取ります。ラベルが1の場合、最初の入力が2番目の入力よりも高いランキングを持つべきであると仮定され、ラベルが-1の場合、2番目の入力が最初の入力よりも高いランキングを持つべきであると仮定されます。この関係は、以下の方程式とコードで示されています。
import torch.nn as nn
loss = nn.MarginRankingLoss()
input1 = torch.randn(3, requires_grad=True)
input2 = torch.randn(3, requires_grad=True)
target = torch.randn(3).sign()
output = loss(input1, input2, target)
print('input1: ', input1)
print('input2: ', input2)
print('output: ', output)
#input1: tensor([-1.1109, 0.1187, 0.9441], requires_grad=True)
#input2: tensor([ 0.9284, -0.3707, -0.7504], requires_grad=True)
#output: tensor(0.5648, grad_fn=<MeanBackward0>)
トリプレットマージン損失
この基準は、トレーニングデータサンプルのトリプレットを使用してデータポイント間の類似性を測定します。関与するトリプレットは、アンカーサンプル、ポジティブサンプル、ネガティブサンプルです。目的は、1) ポジティブサンプルとアンカーとの距離をできるだけ最小にし、2) アンカーとネガティブサンプルとの距離がマージン値にポジティブサンプルとアンカーとの距離を加えた値よりも大きくなるようにすることです。通常、ポジティブサンプルはアンカーと同じクラスに属しますが、ネガティブサンプルはそうではありません。したがって、この損失関数を使用することで、トリプレットマージンロスを用いて、アンカーとポジティブサンプルの間で高い類似性値を予測し、アンカーとネガティブサンプルの間で低い類似性値を予測することを目指します。
import torch.nn as nn
triplet_loss = nn.TripletMarginLoss(margin=1.0, p=2)
anchor = torch.randn(100, 128, requires_grad=True)
positive = torch.randn(100, 128, requires_grad=True)
negative = torch.randn(100, 128, requires_grad=True)
output = triplet_loss(anchor, positive, negative)
print(output) #tensor(1.1151, grad_fn=<MeanBackward0>)
コサイン埋め込み損失
コサイン埋め込み損失は、入力x1、x2と、1または-1の値を含むラベルテンソルyを考慮して損失を測定します。これは、2つの入力がどれほど類似しているか、または異なるかを測定するために使用されます。
この基準は、空間内の2点間のコサイン距離を計算することによって類似性を測定します。コサイン距離は2点間の角度に関連しており、角度が小さいほど入力が近くなるため、より類似していることを意味します。
import torch.nn as nn
loss = nn.CosineEmbeddingLoss()
input1 = torch.randn(3, 6, requires_grad=True)
input2 = torch.randn(3, 6, requires_grad=True)
target = torch.randn(3).sign()
output = loss(input1, input2, target)
print('input1: ', input1)
print('input2: ', input2)
print('output: ', output)
#input1: tensor([[ 1.2969e-01, 1.9397e+00, -1.7762e+00, -1.2793e-01, #-4.7004e-01,
-1.1736e+00],
[-3.7807e-02, 4.6385e-03, -9.5373e-01, 8.4614e-01, -1.1113e+00,
4.0305e-01],
[-1.7561e-01, 8.8705e-01, -5.9533e-02, 1.3153e-03, -6.0306e-01,
7.9162e-01]], requires_grad=True)
#input2: tensor([[-0.6177, -0.0625, -0.7188, 0.0824, 0.3192, 1.0410],
[-0.5767, 0.0298, -0.0826, 0.5866, 1.1008, 1.6463],
[-0.9608, -0.6449, 1.4022, 1.2211, 0.8248, -1.9933]],
requires_grad=True)
#output: tensor(0.0033, grad_fn=<MeanBackward0>)
カルバック・ライブラー発散損失
二つの分布、PとQが与えられたとき、クレバック・ライブラー(KL)ダイバージェンス損失は、P(真の分布であると仮定されている)がQに置き換えられたときに失われる情報の量を測定します。Qを使用してPを近似する際に失われる情報の量を測定することで、PとQの類似性を得ることができ、したがってアルゴリズムを駆動して真の分布Pに非常に近い分布を生成することができます。Qを使用してPを近似する際の情報損失は、Pを使用してQを近似する際の情報損失とは異なるため、KLダイバージェンスは対称的ではありません。
import torch.nn as nn
loss = nn.KLDivLoss(size_average=None, reduce=None, reduction='mean', log_target=False)
input1 = torch.randn(3, 6, requires_grad=True)
input2 = torch.randn(3, 6, requires_grad=True)
output = loss(input1, input2)
print('output: ', output) #tensor(-0.0284, grad_fn=<KlDivBackward>)
カスタム損失関数の構築
PyTorchは、私たちの問題に適した独自の損失関数を構築するための2つの一般的な方法を提供しています。それはクラス実装を使用する方法と関数実装を使用する方法です。まずは、関数実装から始めて、両方の方法をどのように実装できるか見ていきましょう。
これは独自のカスタム損失関数を書く最も簡単な方法です。関数を作成し、必要な入力と他のパラメータを渡し、PyTorchのコアAPIまたはFunctional APIを使用して何らかの操作を行い、値を返すのと同じくらい簡単です。カスタムの平均二乗誤差を使ったデモを見てみましょう。
def custom_mean_square_error(y_predictions, target):
square_difference = torch.square(y_predictions - target)
loss_value = torch.mean(square_difference)
return loss_value
上記のコードでは、予測テンソルとターゲットテンソルを用いて平均二乗誤差を計算するためのカスタム損失関数を定義しています。
y_predictions = torch.randn(3, 5, requires_grad=True);
target = torch.randn(3, 5)
pytorch_loss = nn.MSELoss();
p_loss = pytorch_loss(y_predictions, target)
loss = custom_mean_square_error(y_predictions, target)
print('custom loss: ', loss)
print('pytorch loss: ', p_loss)
#custom loss: tensor(2.3134, grad_fn=<MeanBackward0>)
#pytorch loss: tensor(2.3134, grad_fn=<MseLossBackward>)
私たちはカスタム損失関数とPyTorchのMSE損失関数を使用して損失を計算でき、同じ結果を得たことを確認できます。
Pythonクラスを使用したカスタムロス
このアプローチは、おそらくPyTorchでカスタム損失を定義するための標準かつ推奨される方法です。損失関数は、nnモジュールをサブクラス化することで神経ネットワークグラフのノードとして作成されます。これは、私たちのカスタム損失関数が、畳み込み層と全く同じようにPyTorchレイヤーであることを意味します。では、カスタムMSE損失を使用してこれがどのように機能するかを示すデモを見てみましょう。
class Custom_MSE(nn.Module):
def __init__(self):
super(Custom_MSE, self).__init__();
def forward(self, predictions, target):
square_difference = torch.square(predictions - target)
loss_value = torch.mean(square_difference)
return loss_value
def __call__(self, predictions, target):
square_difference = torch.square(y_predictions - target)
loss_value = torch.mean(square_difference)
return loss_value
最後の考え
私たちはPyTorchで利用可能な損失関数について多くの議論をし、これらの損失関数の内部動作についても深く掘り下げてきました。特定の問題に対して適切な損失関数を選ぶことは、圧倒的な作業になる可能性があります。幸いにも、このチュートリアルと公式のPyTorchドキュメントが、どの損失関数があなたの問題に適しているのかを理解する際のガイドラインとして役立つことを願っています。
