- cross_entropy計算誤差方式,輸入向量z為[1,2,3],預測y為[1],選擇數為2,計算出一大坨e的式子為3.405,再用-2+3.405計算得到1.405
- MSE計算誤差方式,輸入z為[1,2,3],預測向量應該是[1,0,0],和輸入向量維度相同
將cross_entropy直接替換成mse_loss報錯RuntimeError: The size of tensor a (7) must match the size of tensor b (140) at non-singleton dimension 1
將 cross_entropy
換成 mse_loss
會報錯的原因是,這兩個損失函數的輸入和輸出形狀要求不同。cross_entropy
是一個分類損失函數,它期望輸入是未歸一化的logits(形狀為 [batch_size, num_classes]
),而標簽是整數類別(形狀為 [batch_size]
)。mse_loss
是一個回歸損失函數,它期望輸入和標簽的形狀相同。
如果你想使用 mse_loss
來替代 cross_entropy
,你需要對標簽進行one-hot編碼,使它們與模型的輸出形狀匹配。下面是如何修改代碼以使用 mse_loss
的示例:
修改代碼以使用 mse_loss
-
加載必要的庫:
你需要一個工具來將標簽轉換為one-hot編碼。這里我們使用torch.nn.functional.one_hot
。 -
修改訓練函數:
在訓練函數中,將標簽轉換為one-hot編碼,然后計算mse_loss
。
核心測試代碼講解
out=model(data)模型輸出形狀為torch.Size([140, 7])
data.y中測試數據輸出形狀為torch.Size([140]),打印第一個數據為3,7個類別中的第4個類別
將3轉化為7位置獨熱碼計算MSE,對應train_labels_one_hot第一個數據[0., 0., 0., 1., 0., 0., 0.]為4
out形狀為torch.Size([140, 7]),train_labels_one_hot的形狀為[140, 7]
torch.Size([140, 7]) torch.Size([140])
tensor([-0.0166, 0.0191, -0.0036, -0.0053, -0.0160, 0.0071, -0.0042],device='cuda:0', grad_fn=<SelectBackward0>) tensor(3, device='cuda:0')
tensor([[0., 0., 0., 1., 0., 0., 0.],...[0., 1., 0., 0., 0., 0., 0.]], device='cuda:0')
train_labels_one_hot shape torch.Size([140, 7])
test out torch.Size([2708, 7])
train_labels_one_hot = F.one_hot(data.y[data.train_mask], num_classes=dataset.num_classes).float()
print(out[data.train_mask].shape, data.y[data.train_mask].shape)
print(out[data.train_mask][0], data.y[data.train_mask][0])
print(train_labels_one_hot)
print(f"train_labels_one_hot shape {train_labels_one_hot.shape}")
loss = F.mse_loss(out[data.train_mask], train_labels_one_hot)
解釋
- 加載庫:我們使用
torch.nn.functional.one_hot
將標簽轉換為one-hot編碼。 - 修改訓練函數:
- 將標簽
train_labels
轉換為one-hot編碼,train_labels_one_hot = F.one_hot(train_labels, num_classes=dataset.num_classes).float()
。 - 使用
mse_loss
計算均方誤差損失loss = F.mse_loss(train_out, train_labels_one_hot)
。
- 將標簽
- 保持評估函數不變:評估函數仍然使用
argmax
提取預測類別,并計算準確性。
魔改完整代碼
import torch
import torch.nn.functional as F
from torch_geometric.nn import GCNConv
from torch_geometric.datasets import Planetoid
from torch_geometric.transforms import NormalizeFeatures# 加載Cora數據集
dataset = Planetoid(root='/tmp/Cora', name='Cora', transform=NormalizeFeatures())
data = dataset[0]# 定義GCN模型
class GCN(torch.nn.Module):def __init__(self):super(GCN, self).__init__()self.conv1 = GCNConv(dataset.num_node_features, 16)self.conv2 = GCNConv(16, dataset.num_classes)def forward(self, data):x, edge_index = data.x, data.edge_indexx = self.conv1(x, edge_index)x = F.relu(x)x = F.dropout(x, training=self.training)x = self.conv2(x, edge_index)return x# return F.log_softmax(x, dim=1)# 初始化模型和優化器
model = GCN()
print(model)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4)
data = data.to('cuda')
model = model.to('cuda')# 打印歸一化后的特征
print(data.x[0])print(f"data.train_mask{data.train_mask}")# 訓練模型
def train():model.train()optimizer.zero_grad()out = model(data)# print(f"out[data.train_mask] {data.train_mask.shape} {out[data.train_mask].shape} {out[data.train_mask]}")# loss = F.nll_loss(out[data.train_mask], data.y[data.train_mask])train_labels_one_hot = F.one_hot(data.y[data.train_mask], num_classes=dataset.num_classes).float()print(out[data.train_mask].shape, data.y[data.train_mask].shape)print(out[data.train_mask][0], data.y[data.train_mask][0])print(train_labels_one_hot)print(f"train_labels_one_hot shape {train_labels_one_hot.shape}")loss = F.mse_loss(out[data.train_mask], train_labels_one_hot)loss.backward()optimizer.step()return loss.item()# 評估模型
def test():model.eval()out = model(data)print(f"test out {out.shape}")print(f"test out[0] {out[0].shape} {out[0]}")print(f"test out[0:1,:] {out[0:1,:].shape} {out[0:1,:]}")print(f"test out[0:1,:].argmax(dim=1) {out[0:1,:].argmax(dim=1)}")pred = out.argmax(dim=1)print(f"test pred {pred[data.test_mask].shape} {pred[data.test_mask]}")print(f"data {data.y[data.test_mask].shape} {data.y[data.test_mask]}")correct = (pred[data.test_mask] == data.y[data.test_mask]).sum()acc = int(correct) / int(data.test_mask.sum())return accfor epoch in range(1):loss = train()acc = test()print(f'Epoch {epoch+1}, Loss: {loss:.4f}, Accuracy: {acc:.4f}')
原始代碼
import torch
import torch.nn.functional as F
from torch_geometric.nn import GCNConv
from torch_geometric.datasets import Planetoid
from torch_geometric.transforms import NormalizeFeatures# 加載Cora數據集,并應用NormalizeFeatures變換
dataset = Planetoid(root='/tmp/Cora', name='Cora', transform=NormalizeFeatures())
data = dataset[0]# 計算訓練、驗證和測試集的大小
num_train = data.train_mask.sum().item()
num_val = data.val_mask.sum().item()
num_test = data.test_mask.sum().item()print(f'Number of training nodes: {num_train}')
print(f'Number of validation nodes: {num_val}')
print(f'Number of test nodes: {num_test}')# 定義GCN模型
class GCN(torch.nn.Module):def __init__(self):super(GCN, self).__init__()self.conv1 = GCNConv(dataset.num_node_features, 16)self.conv2 = GCNConv(16, dataset.num_classes)def forward(self, data):x, edge_index = data.x, data.edge_indexx = self.conv1(x, edge_index)x = F.relu(x)x = F.dropout(x, training=self.training)x = self.conv2(x, edge_index)return x # 返回未歸一化的logits# 初始化模型和優化器
model = GCN()
optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4)
data = data.to('cuda')
model = model.to('cuda')# 訓練模型
def train():model.train()optimizer.zero_grad()out = model(data) # out 的形狀是 [num_nodes, num_classes]train_out = out[data.train_mask] # 選擇訓練集節點的輸出train_labels = data.y[data.train_mask] # 選擇訓練集節點的標簽# 將標簽轉換為one-hot編碼train_labels_one_hot = F.one_hot(train_labels, num_classes=dataset.num_classes).float()# 計算均方誤差損失loss = F.mse_loss(train_out, train_labels_one_hot)loss.backward()optimizer.step()return loss.item()# 評估模型
def test():model.eval()out = model(data)pred = out.argmax(dim=1) # 提取預測類別correct = (pred[data.test_mask] == data.y[data.test_mask]).sum()acc = int(correct) / int(data.test_mask.sum())return accfor epoch in range(200):loss = train()acc = test()print(f'Epoch {epoch+1}, Loss: {loss:.4f}, Accuracy: {acc:.4f}')
通過這些修改,你可以將交叉熵損失函數替換為均方誤差損失函數,并確保輸入和標簽的形狀匹配,從而避免報錯。
- 簡單版本的的答案
Cross Entropy vs. MSE Loss
-
Cross Entropy Loss:
- 輸入:模型的logits,形狀為 ([N, C]),其中 (N) 是批次大小,(C) 是類別數量。
- 目標:目標類別的索引,形狀為 ([N])。
-
MSE Loss:
- 輸入:模型的預測值,形狀為 ([N, C])。
- 目標:實際值,形狀為 ([N, C])(通常是 one-hot 編碼)。
要將 cross_entropy
換成 mse_loss
,需要確保輸入和目標的形狀匹配。具體來說,你需要將目標類別索引轉換為 one-hot 編碼。
示例代碼
假設你有一個分類任務,其中模型輸出的是 logits,目標是類別索引。我們將這個設置轉換為使用 MSE Loss。
import torch
import torch.nn.functional as F# 假設有一個批次的模型輸出和目標標簽
logits = torch.tensor([[1.0, 2.0, 3.0], [1.0, 2.0, 3.0]], requires_grad=True) # 模型輸出
target = torch.tensor([0, 2]) # 目標類別# 使用 cross_entropy
cross_entropy_loss = F.cross_entropy(logits, target)
print("Cross-Entropy Loss:")
print(cross_entropy_loss)# 轉換目標類別為 one-hot 編碼
target_one_hot = F.one_hot(target, num_classes=logits.size(1)).float()
print("One-Hot Encoded Targets:")
print(target_one_hot)# 計算 MSE Loss
mse_loss = F.mse_loss(F.softmax(logits, dim=1), target_one_hot)
print("MSE Loss:")
print(mse_loss)
輸出
Cross-Entropy Loss:
tensor(1.4076, grad_fn=<NllLossBackward>)
One-Hot Encoded Targets:
tensor([[1., 0., 0.],[0., 0., 1.]])
MSE Loss:
tensor(0.2181, grad_fn=<MseLossBackward>)
解釋
logits
: 模型的原始輸出,形狀為 ([N, C])。target
: 原始目標類別索引,形狀為 ([N])。target_one_hot
: 將目標類別索引轉換為 one-hot 編碼,形狀為 ([N, C])。F.mse_loss
: 使用F.softmax(logits, dim=1)
計算模型的概率分布,然后與target_one_hot
計算 MSE 損失。
通過將目標類別轉換為 one-hot 編碼并確保輸入和目標的形狀匹配,可以成功地將 cross_entropy
換成 mse_loss
。