【PyTorch】「Expected object of scalar type Long but got Float」から学ぶ損失関数とデータ型の深淵

Python

PyTorchでDeep Learningモデルのネットワーク構造を一生懸命に書き上げ、データローダーも完璧!いざ学習開始!と意気揚々と実行した途端に叩きつけられる恐怖の型(Type)エラーメッセージ。
その代表格がこちらです。

「RuntimeError: Expected object of scalar type Long but got scalar type Float for argument」

このエラー、直訳すると「引数としてLong型(整数)のオブジェクトを期待していたのに、Float型(小数)のオブジェクトが渡されたぞ!」と怒られているだけなのですが、初心者は「どこで!?」と長時間スタックしがちです。

本記事では、このエラーを紐解きながら、分類タスクにおける損失関数(CrossEntropyLoss等)の背後にある数学的要件と、PyTorchならではの厳密な「テンソルのデータ型(dtype)管理」について、非常に分かりやすく深掘りします。

1. クロスエントロピー誤差(CrossEntropyLoss)のトラップ

AIに「犬」「猫」「鳥」の3種類の画像を分類させたいとしましょう(多クラス分類タスク)。このとき、PyTorchでは損失関数としてほぼ必ず nn.CrossEntropyLoss() を使用します。
この関数は非常に賢く、内部的に「各クラスに属する確率をはじき出すSoftmax計算」と「Negative Log Likelihood Loss(NLLLoss)」を合体させて一度に計算してくれる超便利ツールです。

しかし、便利な反面、引数として受け取るデータの型(dtype)の仕様が超厳密なのです。

  • 第1引数:モデルの出力(outputs
    各クラスのもっともらしさ(スコア/ロジット)を持った配列。小数点を含む確率的な数値なので、これは Float型 (torch.float32) テンソルでなければなりません。
  • 第2引数:正解ラベル(labels
    ここが罠です!「この写真は犬(0番目)ですよ」「これは鳥(2番目)ですよ」という正解のインデックス(番号)を示すものなので、小数を含むことのない Long型 (torch.int64) テンソルでなければならないのです。

多くの初学者は、画像を float32 に変換するついでに、正解データ(0や1)まで何も考えずに float32 としてテンソル化してしまいます。そのため、関数が「おい!正解ラベルはインデックス番号(整数)しか受け付けないんじゃボケ!」と怒って先ほどのエラーを吐くのです。

import torch
import torch.nn as nn
# モデルの推論出力(バッチサイズ2、クラス数3:犬、猫、鳥)
# これは実数のスコアなので Float32 型で正解
outputs = torch.tensor([
    [2.5, -1.2, 0.3], 
    [0.1,  3.4, 0.2]
], dtype=torch.float32)
# 【バグの原因】
# 正解ラベル(最初は犬=0、次は猫=1)を、うっかりfloat型で定義してしまっている!
labels_wrong = torch.tensor([0, 1], dtype=torch.float32)
criterion = nn.CrossEntropyLoss()
try:
    loss = criterion(outputs, labels_wrong)
except RuntimeError as e:
    print(f"【大爆発】エラー発生:{e}")
    # RuntimeError: Expected object of scalar type Long but got scalar type Float
    
# ========================
# 【正しい対処法】
# ========================
# Datasetの段階で正しく型指定するか、.long() メソッドで強制キャストする
labels_correct = labels_wrong.long() # あるいは torch.tensor([0, 1], dtype=torch.long)
loss = criterion(outputs, labels_correct)
print(f"正しいロス: {loss.item():.4f}")

2. ワンホットエンコーディング (One-Hot) との混同

もう一つのよくある原因は、TensorFlow (Keras) の経験者がPyTorchに移動してきた際に起こる「文化のギャップ」です。

Keras界隈では、正解ラベルを [1.0, 0.0, 0.0] のようなワンホットベクトルとして与えるのが一般的でした。しかし、PyTorchの CrossEntropyLoss は、メモリ効率を究極まで高めるため、デフォルトで「ワンホットではなく、クラスのインデックスそのもの([0][2])」を整数で要求するという思想で作られています。

※補足事項:PyTorch 1.10以降のバージョンでは、仕様が拡張されワンホットや確率分布(Float型)のラベルもオプションとして受け付けるようになりました。しかし、メモリ効率や計算速度の観点から考えると、通常のハードラベル(単一正解)を扱う際は現在でも「Long型のインデックス」を渡すのがベストプラクティスとされています。

まとめ:PyTorchの「厳格さ」と正しく付き合う

Pythonという言語自体は動的型付けで、小数に整数を足してもエラーにならないルーズで優しい言語です。
しかしPyTorchはその裏側で、C++やCUDAを用いて強固なメモリ配置とテンソル演算の最適化を行っています。計算速度を限界までブーストするため、「この実数は32ビットなのか」「このラベルは64ビット整数なのか」をプログラマに強烈に意識させるのです。

型エラーが出たときは「面倒くさい」と思うのではなく、「PyTorchがメモリと計算効率のために最適な型を要求してくれているんだな」と考えましょう。自作のDatasetクラスやDataLoaderの中で、常に image = image.float()label = label.long() と正しくキャスト(変換)する習慣をつけることが、PyTorchマスターへの第一歩です。

コメント

タイトルとURLをコピーしました