PyTorchの損失関数

導入

損失関数は、機械学習モデルのトレーニングにおいて基本的な概念であり、ほとんどの機械学習プロジェクトでは、損失関数を使用しないと、モデルを正しい予測に導くことはできません。一般の言葉で言えば、損失関数は、模型的なパフォーマンスを計測する数学的な関数や式である。特定のデータセット上でのモデルのパフォーマンスを知ることで、開発者は、新しい、より強力なモデルを使用したり、loss function自体を別の型に変更したりすることが多くのトレーニング中の決定を下すことができます。損失関数のタイプについて語ると、年月が経ってきている間に開発されたいくつかの損失関数があり、それぞれ特定のトレーニングタスクに適して使用できます。

前提知識

この記事では、ニューラルネットワークの理解が必要です。高层次的に言えば、ニューラルネットワークは、層として組織されたインターコネクトード(“ニューロン”)からなる相互に接続されたノードで構成されています。ニューラルネットワークは、重みや偏置を調整する過程である”トレーニング”を通じて学び、予測を行います。ニューラルネットワークについての理解には、異なる層(入力層、隠れ層、出力層)、激活関数、最適化アルゴリズム(勾配下降のバリエーション)、損失関数などの知識が含まれます。

この記事では、PythonのスyntaxとPyTorchライブラリの熟悉が重要であることを前置きします。

この記事では、PyTorch nnモジュールに含まれる異なる損失関数を探索します。さらに、PyTorchがnnモジュールAPIの一部としてこれらの損失関数をユーザーに露出している仕組みについて深入了解し、独自のものを構築します。

損失関数とは何かを高层次に理解した後、私たちは損失関数の仕組みについてもっと技術的な詳細を探索しましょう。

損失関数とはどんなものでしょうか?

先程お話ししましたように、損失関数はあるデータセットでモデルの性能を教えてくれます。技術的には、予測された値が実際の値にどれくらい近いかを測定しています。私のモデルが学習とテストデータセットで実際の値に非常に近い予測をしている場合、それは私が相当な robustなモデルを持っていることを意味します。

損失関数は私のモデルの性能について重要な情報を提供しますが、それは損失関数の主な機能ではありません。モデルの性能を評価するには、精度とFスコアなどより robustな技術があります。損失関数の重要性は、主に学習時に認識されます。それは、モデルの重みを最小限の損失の方向に動かしていくことであり、これにより、私のモデルが正しい予測をした可能性を高めます。これは、損失関数なしでは可能ではないでしょう。

各异な損失関数は各异の問題に適しており、研究者たちはそれぞれ精巧に作られて、学習中に安定した勾配流を保証するために用いられます。

時に、損失関数の数学的な表記は少しは畏敬感をもたらすかもしれませんが、それには開発者の中にはそれらを黒盒子として取り扱うことがあります。後でPyTorchで最も多く使用されている損失関数を説明しますが、その前に、私たちがPyTorchの中で損失関数をどのように使用するかを見ていきましょう。

PyTorchの損失関数

PyTorchは、開発者が学習中に簡単に様々な損失関数を迭代することができる、シンプリックな設計パターンを持つ公式の損失関数を多く提供しています。PyTorchの損失関数はすべてnnモジュールに含まれています。これは、すべての neural networks に基づくPyTorchの基本クラスです。これにより、loss functionをプロジェクトに追加するのはたった一文字のコードを追加するだけの簡単なことになります。次に、PyTorchでMean Squared Error(MSE)損失関数を追加する方法を見ていきましょう。

import torch.nn as nn
MSE_loss_fn = nn.MSELoss()

上のコードから返される関数は、以下の形式で予測値と実際の値の間の差を計算することができます。

#予測値は私たちのニューラルネットワークからの予測です
#目標は私たちのデータセットの実際の値です
#損失値は予測値と実際の値の間の損失です
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とreduceは非推奨です

#reductionは、出力に適用する缩約方法を指定します。可能な値は'mean'(デフォルト)であり、これは出力の平均を計算します、'sum'は出力を合計し、'none'は出力に缩約を適用しません

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は、予測テンソルと目標テンソルの値の絶対的な差を計算する代わりに、予測テンソルと目標テンソルの値の2乗差を計算します。このため、比較的大きな差により重いペナルティがかかり、比較的小さな差により軽いペナルティがかかります。MSEはMAEよりもOutlierとノイズに対してより脆いと考えられています。

import torch.nn as nn

loss = nn.MSELoss(size_average=None, reduce=None, reduction='mean')
#L1損失関数のパラメーターの説明はここに適用されます。

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>)

クロスエントロピー損失

クロスエントロピー損失は、複数の離散のクラスに関連する分類問題に使用されます。これは、与えられた乱数変数の2つの確率分布の差を測定します。通常、クロスエントロピー損失を使用する場合、ネットワークの出力はソフトマックス層であり、これは、ニューラルネットワークの出力が確率値(0から1の間)であることを保証します。

ソフトマックス層は2つの部分から構成されていますが、予測の指数とすることができます。

yiは、特定のクラスに対するニューラルネットワークの出力です。この関数の出力は、yiが大きく负の数である場合には接近して0になり、yiが非常に大きく正の数である場合には1に近づく数値になります。

import numpy as np

np.exp(34) #583461742527454.9
np.exp(-34) #1.713908431542013e-15

第二部分は標準化値であり、ソフトマクス層の出力が常に確率値であることを保証するために使用されます。

これは各クラス値のすべての指数の和を計算することで取得されます。ソフトマクスの最終の方程式は以下のようになります:

]

PyTorchのnnモジュールで、交叉熵損失は、对数Softmaxと非対数 log-可能性(NLL)損失を单一の損失関数に結合します。

印刷された出力の勾配関数がNLL損失であることに注意してください。これは、交叉熵損失が実際には、log-Softmax層の下でNLL損失を結合していることを揭露します。

非対数Log-可能性(NLL)損失

NLL損失関数は、交叉熵損失関数と非常に似ています。交叉熵損失は、log-Softmax層とNLL損失を結合して交叉熵損失の値を得ます。これは、Neural Networkの最後の層が通常のSoftmax層ではなくlog-Softmax層であることで、NLL損失を用いて交叉熵損失の値を得ることができることを意味します。

m = nn.LogSoftmax(dim=1)
loss = nn.NLLLoss()
入力の大きさはN x C = 3 x 5です
input = torch.randn(3, 5, requires_grad=True)
目標の各要素には0 <= 値 < Cでなければならない
target = torch.tensor([1, 0, 4])
output = loss(m(input), target)
output.backward()
2D損失の例(画像入力などに使用される)
N, C = 5, 4
loss = nn.NLLLoss()
入力の大きさはN x C x 高さ x 幅です
data = torch.randn(N, 16, 10, 10)
conv = nn.Conv2d(16, C, (3, 3))
m = nn.LogSoftmax(dim=1)
目標の各要素には0 <= 値 < Cでなければならない
target = torch.empty(N, 8, 8, dtype=torch.long).random_(0, C)
output = loss(m(conv(data)), target)
print(output) 張量(1.4892, 勾配関数=<NllLoss2DBackward>)

NLLLoss — PyTorch 1.9.0 ドキュメント

二値クロスエントロピー損失

二値クロスエントロピー損失は、データ点を2つのクラスにだけ分类する特殊問題に使用される特殊なクロスエントロピー損失のクラスです。この問題のラベルは通常二値であり、私たちの目標はモデルが0のラベルに近い数を予測し、1のラベルに近い数を予測するようにすることです。二値分类にBCE損失を使用する場合、ニューラルネットワークの出力にはシグモイド層を使用することで、出力が0か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) 張量(0.4198, 勾配関数=<BinaryCrossEntropyBackward>)

二値交叉熵損失(ロジットあり)

先前的章節で述べたように、二値交叉熵損失は通常、0から1の間になるようにsigmoid層で出力されます。logitを用いた二値交叉熵損失は、これらの2つの層を1つの層に結合します。PyTorchのドキュメントによれば、これはlog-sum exp手法を利用して、数値的により安定した版であることになります。

import torch
import torch.nn as nn

target = torch.ones([10, 64], dtype=torch.float32)  # 64のクラス、バッチサイズ = 10
output = torch.full([10, 64], 1.5)  # 予測(ロジット)
pos_weight = torch.ones([64])  # 全ての重みは1に等しい
criterion = torch.nn.BCEWithLogitsLoss(pos_weight=pos_weight)
loss = criterion(output, target)  # -log(sigmoid(1.5))
print(loss) #tensor(0.2014)

スムースL1損失

平滑L1損失関数は、ヒューリスティック値betaを通じてMSE損失とMAE損失の利点を合わせています。この基準は、Fast R-CNN論文で紹介されました。ground truth値と予測値の絶対的な差がbeta未満である場合、この基準はMSE損失と同様に二乗差を使用します。MSE損失のグラフは連続的な曲線であり、各損失値の微分は変化し、各所で導出できます。また、損失値が小さくなるにつれて微分は減少し、梯度下降の際に便利です。しかし、非常に大きな損失値になると微分が爆発するため、MAEに切り替わる基準が必要です。MAEでは、損失値の絶対的な差がbetaを超えると几乎常数である微分を持つため、潜在的な微分の爆発が除去されます。

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>)

ハング Embedding 損失

ハング嵌入損失は、主要に半监督学習タスクに使用されています。これは、入力テンソルと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: テンソル([[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: テンソル([[-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: テンソル(1.2103, grad_fn=)

境界排名损失

境界排名损失は、主要な目標が他の損失関数と異なる順序損失の一员であり、データセット内の入力集合の対応する相対的な距離を測ることである。境界排名損失関数は2つの入力と、1または-1のみを含むラベルを受け取る。ラベルが1の場合、最初の入力が第2の入力より上位に排他的になるべきであると考えられ、ラベルが-1の場合、第2の入力が第1の入力より上位に排他的になるべきであると考えられる。この関係は以下の方程式とコードで示されます。

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)

#入力1:  tensor([-1.1109,  0.1187,  0.9441], requires_grad=True)
#入力2:  tensor([ 0.9284, -0.3707, -0.7504], requires_grad=True)
#出力:  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>)

コサイン埋め込みロス

cosine embedding lossは、入力x1、x2と1または-1の値を持つラベルテンソルyを与えると、それらの入力の似性または非似性の度量を行うために使用される損失関数です。

この基準は、スペース内の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>)

クルバック-リベラルER损失

2つの分布PとQを与えられたとき、クルバック-リベラル(KL)ER損失は、P(真の分布として仮定される)をQに置き換えたときに失われる情報量を測定します。Qを使ってPに近似するときに失われる情報量を測定することで、PとQの似性を得ることができ、そうすることで、アルゴリズムを駆使して真の分布Pと非常に近い分布を生成することができます。Qを使ってPに近似するときの情報丧失は、Pを使ってQに近似するときと同じではなく、したがってKLER損失は対称的ではありません。

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ドキュメントと一緒に、あなたの問題に适した損失関数を理解するためのガイドラインとして機能しています。

Source:
https://www.digitalocean.com/community/tutorials/pytorch-loss-functions