PyTorch(および一般的なディープラーニング)を理解するために、いくつかの基本的な分類の例から始めました。そのような例の1つは、sklearnを使用して作成された非線形データセットを分類することです(ノートブックとして利用可能な完全なコード here )。
n_pts = 500
X, y = datasets.make_circles(n_samples=n_pts, random_state=123, noise=0.1, factor=0.2)
x_data = torch.FloatTensor(X)
y_data = torch.FloatTensor(y.reshape(500, 1))
これは、かなり基本的なニューラルネットを使用して正確に分類されます。
class Model(nn.Module):
def __init__(self, input_size, H1, output_size):
super().__init__()
self.linear = nn.Linear(input_size, H1)
self.linear2 = nn.Linear(H1, output_size)
def forward(self, x):
x = torch.sigmoid(self.linear(x))
x = torch.sigmoid(self.linear2(x))
return x
def predict(self, x):
pred = self.forward(x)
if pred >= 0.5:
return 1
else:
return 0
健康データに関心があるので、同じネットワーク構造を使用して基本的な実際のデータセットを分類することにしました。 here から1人の患者の心拍数データを取得し、91を超えるすべての値が異常としてラベル付けされるように変更しました(例:1
および<= 91のすべてに0
)。これは完全に恣意的ですが、分類がどのように機能するかを確認したかっただけです。この例の完全なノートブックは here です。
私にとって直感的でないのは、最初の例が1,000エポック後に0.0016の損失に達するのに対して、2番目の例は10,000エポック後に0.4296の損失にしか達しない理由です。
おそらく、心拍数の例を分類するほうがはるかに簡単だと私は考えているのは初心者です。これが私が見ているものではない理由を理解するのに役立つ洞察は素晴らしいでしょう!
入力データは正規化されていません。
x_data = (x_data - x_data.mean()) / x_data.std()
を使用しますoptimizer = torch.optim.Adam(model.parameters(), lr=0.01)
たった1000回の反復で収束。
2つの例の主な違いは、最初の例のデータx
は(0、0)を中心とし、分散が非常に小さいことです。
一方、2番目の例のデータは92を中心にしており、分散が比較的大きくなっています。
データのこの初期バイアスは、ランダムに 重みを初期化する の場合は考慮されません。これは、入力がおおよそ正規分布しているという仮定に基づいて行われますzero。
最適化プロセスがこの大きな偏差を補償することはほとんど不可能です-したがって、モデルは次善のソリューションで動かなくなります。
入力を正規化したら、平均を減算してstdで割ると、最適化プロセスが再び安定し、すぐに適切なソリューションに収束します。
入力の正規化と重みの初期化の詳細については、セクション2.2のHe et alDelving Deep into Rectifiers:Surpassing Human-Level Performance on ImageNet Classification (ICCV 2015)。
何らかの理由で事前に平均データと標準データを計算できない場合でも、トレーニングプロセスの一部として _nn.BatchNorm1d
_ を使用してデータを推定および正規化できます。例えば
_class Model(nn.Module):
def __init__(self, input_size, H1, output_size):
super().__init__()
self.bn = nn.BatchNorm1d(input_size) # adding batchnorm
self.linear = nn.Linear(input_size, H1)
self.linear2 = nn.Linear(H1, output_size)
def forward(self, x):
x = torch.sigmoid(self.linear(self.bn(x))) # batchnorm the input x
x = torch.sigmoid(self.linear2(x))
return x
_
この変更なし入力データに変更を加えると、わずか1000エポック後に同様の収束が得られます。
数値を安定させるには、 _nn.BCEWithLogitsLoss
_ の代わりに _nn.BCELoss
_ を使用することをお勧めします。このためには、forward()
出力から_torch.sigmoid
_を削除する必要があります。sigmoid
は損失内で計算されます。
たとえば、バイナリ予測の関連するシグモイド+クロスエントロピー損失については this thread を参照してください。