深層学習フレームワーク-4の実践的な実装は、クロスエントロピー損失関数を使用して分類タスクをサポートします

コードリポジトリ:https : //github.com/brandonlyg/cute-dl

対象

  1. フレームワークが分類タスクモデルをサポートできるように、クロスエントロピー損失関数を増やします。
  2. MLPモデルを作成し、91%の精度でニーニストデータセットに対して分類タスクを実行します。

クロスエントロピー損失関数を実装する

数学の原則

クロスエントロピー損失関数を分解する

        クロスエントロピー損失関数は、モデルの出力値を離散確率変数の分布として扱います。モデルの出力を次のようにします:\(\ hat {Y} = f(X)\)、ここで\(f(X)\)はモデルを表します。\(\ hat {Y} \)は、以下に示すようにm X n行列です。

\ [\ begin {bmatrix} \ hat {y} _ {11}&\ hat {y} _ {12}&...&\ hat {y} _ {1n} \\ \ hat {y} _ {21 }&\ hat {y} _ {22}&...&\ hat {y} _ {2n} \\ ...&...&...&... \\ \ hat {y} _ {m1}&\ hat {y} _ {m2}&...&\ hat {y} _ {mn} \ end {bmatrix} \]

        この行列のi番目の行を\(\ hat {y} _i \)とします。これは\(\\ R ^ {1Xn} \)ベクトルであり、そのj番目の要素は\(\ hat {y } _ {ij} \)
        クロスエントロピー損失関数では、\(\ hat {y} _i \)に次のプロパティが必要です。

\ [\ begin {matrix} 0 <= \ hat {y} _ {ij} <= 1&&(1)\\ \ sum_ {j = 1} ^ {n} \ hat {y} _ {ij} = 1、&n = 2,3、...&(2)\ end {matrix} \]

        特に、n = 1の場合、最初のプロパティのみが満たされる必要があります。最初にn> 1の場合を検討します。この場合、n = 2はn = 1と同等です。エンジニアリングでは、n = 1はn = 2の最適化と見なすことができます。
        時々、モデルは出力値がこれらのプロパティを持っていることを保証しない場合、損失関数は\(\ hat {y} _i \)を分布列に変換する必要があります:\(\ hat {p} _i \)、変換関数の定義次のように:

\ [\ begin {matrix} S_i = \ sum_ {j = 1} ^ {n} e ^ {\ hat {y} _ {ij}} \\ \ hat {p} _ {ij} = \ frac {e ^ {\ hat {y} _ {ij}}} {S_i} \ end {matrix} \]

        ここで\(\ハット{P} _i \)を満たすために必要とされます。関数\(e ^ {\ hat {y} _ {ij}} \)は、任意の2つの異なる\(\ hat {y} _ {ia} <\ hat {y} _ {ib}に対して単調増加関数です\)、すべて:\(e ^ {\ hat {y} _ {ia}} \)\(e ^ {\ hat {y} _ {ib}} \)、これにより次のようになります:\(\ hat { p} _ {ia} <\ hat {p} _ {ib} \)したがって、この関数はモデルの出力値を確率値に変換し、確率のサイズと出力値のサイズの関係は同じです。
        提供されたデータ\(X_I \)カテゴリラベル\(Y_I \)\(R&LT \\ ^ {X N} \) もし\(X_I \)真カテゴリT、\(Y_I \)が満たされます。

\ [\ begin {matrix} y_ {ij} = 1&{if j = t} \\ y_ {ij} = 0&{if j≠t} \ end {matrix} \]

        \(y_i \)は、ワンホットエンコーディングを使用します。クロスエントロピー損失関数の定義は次のとおりです。

\ [J_i = \ frac {1} {m} \ sum_ {j = 1} ^ {n} -y_ {ij} ln(\ hat {p} _ {ij})\]

        いずれかのために\(Y_ {} \のIJ) いずれかの損失関数は、次の特性をもちます。

\ [\ begin {matrix} -y_ {ij} ln(\ hat {p} _ {ij})∈[0、∞)、&if:y_ {ij} = 1 \\ -y_ {ij} ln(\ハット{p} _ {ij})= 0、&if:y_ {ij} = 0 \ end {matrix} \]

        項\(y_ {ij} = 0 \)は損失関数の値に影響を与えないことがわかるので、そのような項は計算中に損失関数から無視できます。\(y_ {ij} = 1 \)の他の項では、\(\ hat {p} _ {ij} = y_ {ij} = 1 \)の場合、損失関数は最小値0に達します。

勾配の導出

        連鎖規則によれば、損失関数の勾配は次のとおりです。

\ [\ frac {\ partial J_i} {\ partial \ hat {y} _ {ij}} = \ frac {\ partial J_i} {\ partial \ hat {p} _ {ij}} \ frac {\ partial \ hat {p} _ {ij}} {\ partial \ hat {y} _ {ij}}、\ quad(1)\]

        それらの中で:

\ [\ frac {\ partial J_i} {\ partial \ hat {p} _ {ij}} = \ frac {1} {m} \ frac {-y_ {ij}} {\ hat {p} _ {ij} } \ quad(2)\]

\ [\ frac {\ partial \ hat {p} _ {ij}} {\ partial \ hat {y} _ {ij}} = \ frac {e ^ {\ hat {y} _ {ij}} S_i-e ^ {2 \ hat {y} _ {ij}}} {S_i ^ 2} = \ frac {\ hat {y} _ {ij}} {S_i}-[\ frac {e ^ {\ hat {y} _ {ij}}} {S_i}] ^ 2 = \ hat {p} _ {ij}-(\ hat {p} _ {ij})^ 2 = \ hat {p} _ {ij}(1- \ hat {p} _ {ij})\ quad(3)\]

        (2)、(3)を(1)に代入すると、次のようになります。

\ [\ frac {\ partial J_i} {\ partial \ hat {y} _ {ij}} = \ frac {1} {m} \ frac {-y_ {ij}} {\ hat {p} _ {ij} } \ hat {p} _ {ij}(1- \ hat {p} _ {ij})= \ frac {1} {m}(y_ {ij} \ hat {p} _ {ij} -y_ {ij })\]

        場合ため\(Y_ {IJ} = 0 \) 、勾配値は0で、これは、得られた勾配を無視することができます。

\ [\ frac {\ partial J_i} {\ partial \ hat {y} _ {ij}} = \ frac {1} {m}(\ hat {p} _ {ij} -y_ {ij})\]

        モデルの出力値は、確率変数の分布列である場合、機能の損失がに省略することができる\(\ハット{Y} _ {IJ} \) に変換される(\ \ハット{P} _ {IJ} \) ステップ、今回は\(\ hat {y} _ {ij} = \ hat {p} _ {ij} \)、最終的な勾配は次のようになります。

\ [\ frac {\ partial J_i} {\ partial \ hat {y} _ {ij}} = \ frac {\ partial J_i} {\ partial \ hat {p} _ {ij}} =-\ frac {y_ { ij}} {m \ hat {y} _ {ij}} \]


クロスエントロピー損失関数の特殊なケース:2つのカテゴリのみ

        ここで、n = 1の場合について説明します。現時点では、\(\ hat {y} _i \)\(\\ R ^ {1 X 1} \)はスカラーとして扱うことができます。
        モデルの出力が分布列でない場合、損失関数は次のように分解できます。

\ [\ begin {matrix} \ hat {p} _ {i} = \ frac {1} {1 + e ^ {-\ hat {y} _ {i}}} \\ \\ J_i = \ frac {1 } {m} [-y_iln(\ hat {p} _ {i})-(1-y_i)ln(1- \ hat {p} _ {i})] \ end {matrix} \]

        出力値に関する損失関数の勾配は次のとおりです。

\ [\ frac {\ partial J_i} {\ partial \ hat {p} _i} = \ frac {1} {m}(-\ frac {y_i} {\ hat {p} _i} + \ frac {1-y_i } {1-\ hat {p} _i})= \ frac {\ hat {p} _i-y_i} {m \ hat {p} _i(1- \ hat {p} _i)}、\ quad(1) \]

\ [\ frac {\ partial \ hat {p} _i} {\ partial \ hat {y} _i} = \ frac {e ^ {-\ hat {y} _ {i}}} {(1 + e ^ { -\ hat {y} _ {i}})^ 2} = \ frac {1} {1 + e ^ {-\ hat {y} _ {i}}} \ frac {e ^ {-\ hat {y } _ {i}}} {1 + e ^ {-\ hat {y} _ {i}}} = \ hat {p} _ {i}(1- \ hat {p} _ {i})、\クワッド(2)\]

\ [\ frac {\ partial J_i} {\ partial \ hat {y} _i} = \ frac {\ partial J_i} {\ partial \ hat {p} _i} \ frac {\ partial \ hat {p} _i} { \ partial \ hat {y} _i}、\ quad(3)\]

        (1)、(2)を(3)に代入すると、次のようになります。

\ [\ frac {\ partial J_i} {\ partial \ hat {y} _i} = \ frac {\ hat {p} _i-y_i} {m \ hat {p} _i(1- \ hat {p} _i) } \ hat {p} _ {i}(1- \ hat {p} _ {i})= \ frac {1} {m}(\ hat {p} _i-y_i)\]

        モデルの出力値が確率変数の分布列の場合、次のようになります。

\ [\ frac {\ partial J_i} {\ partial \ hat {y} _i} = \ frac {\ partial J_i} {\ partial \ hat {p} _i} = \ frac {\ hat {y} _i-y_i} {m \ hat {y} _i(1- \ hat {y} _i)} \]


実装コード

        これらの2つのクロスエントロピー損失関数の実装コードは、cutedl / losss.pyにあります。一般的なクロスエントロピー損失関数のクラス名はCategoricalCrossentropyで、その主な実装コードは次のとおりです。

  '''
  输入形状为(m, n)
  '''
  def __call__(self, y_true, y_pred):
      m = y_true.shape[0]
      #pdb.set_trace()
      if not self.__form_logists:
          #计算误差
          loss = (-y_true*np.log(y_pred)).sum(axis=0)/m
          #计算梯度
          self.__grad = -y_true/(m*y_pred)
          return loss.sum()

      m = y_true.shape[0]
      #转换成概率分布
      y_prob = dlmath.prob_distribution(y_pred)
      #pdb.set_trace()
      #计算误差
      loss = (-y_true*np.log(y_prob)).sum(axis=0)/m
      #计算梯度
      self.__grad  = (y_prob - y_true)/m

      return loss.sum()

        prob_distribution関数はモデル出力を分布列に変換し、実装方法は次のとおりです。

def prob_distribution(x):
    expval = np.exp(x)
    sum = expval.sum(axis=1).reshape(-1,1) + 1e-8

    prob_d = expval/sum

    return prob_d

        バイナリ分類クロスエントロピー損失関数のクラス名はBinary Crosssentropyで、その主な実装コードは次のとおりです。

'''
输入形状为(m, 1)
'''
def __call__(self, y_true, y_pred):
    #pdb.set_trace()
    m = y_true.shape[0]

    if not self.__form_logists:
        #计算误差
        loss = (-y_true*np.log(y_pred)-(1-y_true)*np.log(1-y_pred))/m
        #计算梯度
        self.__grad = (y_pred - y_true)/(m*y_pred*(1-y_pred))
        return loss.sum()

    #转换成概率
    y_prob = dlmath.sigmoid(y_pred)
    #计算误差
    loss = (-y_true*np.log(y_prob) - (1-y_true)*np.log(1-y_prob))/m
    #计算梯度
    self.__grad = (y_prob - y_true)/m

    return loss.sum()

MNISTデータセットで確認する

        次に、MNIST分類タスクを使用して、クロスエントロピー損失関数を検証します。コードは、examples / mlp / mnist-recognize.pyファイルにあります。このコードを実行する前に、元のMNISTデータセットをexamples / datasets /にダウンロードして解凍します。データセットのダウンロードリンクは、https://pan.baidu.comです。/ s / 1CmYYLyLJ87M8wH2iQWrrFA、パスワード: 1rgr

        モデルをトレーニングするためのコードは次のとおりです。

'''
训练模型
'''
def fit():
    inshape = ds_train.data.shape[1]
    model = Model([
                nn.Dense(10, inshape=inshape, activation='relu')
            ])
    model.assemble()

    sess = Session(model,
            loss=losses.CategoricalCrossentropy(),
            optimizer=optimizers.Fixed(0.001)
            )

    stop_fit = session.condition_callback(lambda :sess.stop_fit(), 'val_loss', 10)

    #pdb.set_trace()
    history = sess.fit(ds_train, 20000, val_epochs=5, val_data=ds_test,
                        listeners=[
                            stop_fit,
                            session.FitListener('val_end', callback=accuracy)
                        ]
                    )

    fit_report(history, report_path+"0.png")

        フィッティングレポート:

        1時間(3699秒)後に約600万ステップのトレーニングを行うと、モデルの正解率が92%に達したことがわかります。同じモデルは、テンソルフロー(CPUバージョン)で10分間トレーニングした後、91%に達することがあります。これは、cute-dlフレームワークはタスクのパフォーマンスの点では問題がないことを示していますが、トレーニングモデルの速度は良くありません。

まとめ

        この段階で、フレームワークは分類タスクのサポートを実装し、モデルのパフォーマンスがMNISTデータセットの期待値を満たしていることを確認します。モデルトレーニングの速度は十分ではありません。
        次の段階では、モデルに学習率オプティマイザを追加して、汎化機能を失うことなくモデルのトレーニングを高速化します。

おすすめ

転載: www.cnblogs.com/brandonli/p/12745859.html