文章目錄
- 兩層gcn,提取最后一層節點輸出特征,10個節點,每個節點8個特征,連接關系隨機生成(無全連接層)
- 如何計算MSE
- 100個樣本,并且使用批量大小為32進行訓練
- 第一個版本定義數據集出錯,添加super()并修改為__len__和__getitem__
- idx的作用
- 使用super()方法
- 再次解釋一遍定義數據集的代碼
- 另外一個值得注意的錯誤:定義數據集部分修改之后還是報obj = super().__new__(cls)TypeError: Can't instantiate abstract class MyDataset with abstract methods get, len錯誤
- from torch.utils.data import Dataset 與 from torch_geometric.data import Data 和 Dataset是不一樣的
- from torch_geometric.data import Data的作用
- 驗證 MyDataset 類生成的樣本和批次數據的形狀
- 完整代碼
兩層gcn,提取最后一層節點輸出特征,10個節點,每個節點8個特征,連接關系隨機生成(無全連接層)
如果沒有全連接層(fc
層),那么輸出將是每個節點的特征。這樣的話,標簽需要對應每個節點的輸出特征。在這種情況下,標簽的維度應該是節點數乘以輸出特征數。
如果我們將輸出特征數設為1,并且沒有全連接層,那么每個節點的輸出將是一個標量。標簽的維度將與節點數相同。例如,假設有10個節點,每個節點的輸出是一個標量,那么標簽的維度應該是 (10,)
。
以下是一個沒有全連接層的雙層GCN實現,其中標簽數和節點數相同:
import torch
import torch.nn.functional as F
from torch_geometric.nn import GCNConv
from torch_geometric.data import Dataclass GCN(torch.nn.Module):def __init__(self, in_channels, hidden_channels, out_channels):super(GCN, self).__init__()self.conv1 = GCNConv(in_channels, hidden_channels)self.conv2 = GCNConv(hidden_channels, out_channels)def forward(self, x, edge_index):print(x.shape)x = self.conv1(x, edge_index)print(x.shape)x = F.relu(x)x = self.conv2(x, edge_index)print(x.shape)return x# 定義圖數據
num_nodes = 10
num_node_features = 8
edge_index = torch.tensor([[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]], dtype=torch.long)
x = torch.randn((num_nodes, num_node_features))data = Data(x=x, edge_index=edge_index)# 定義并訓練模型
model = GCN(in_channels=num_node_features, hidden_channels=16, out_channels=1)
print(model)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
criterion = torch.nn.MSELoss()# 假設我們有一些訓練標簽,每個節點一個標簽
labels = torch.randn((num_nodes, 1))# 簡單的訓練循環
model.train()
for epoch in range(1):optimizer.zero_grad()out = model(data.x, data.edge_index)loss = criterion(out, labels)print(f"loss {loss}")loss.backward()optimizer.step()# 提取最后一層節點輸出特征
model.eval()
with torch.no_grad():out = model(data.x, data.edge_index)print(out)
在這個示例中:
- 模型輸出的形狀是
(num_nodes, 1)
,即每個節點有一個輸出特征。 - 標簽的形狀也是
(num_nodes, 1)
,即每個節點有一個對應的標簽。 - 損失函數使用均方誤差損失(MSELoss),并計算每個節點輸出與對應標簽之間的誤差。
這樣,我們確保輸出與標簽的維度一致,從而正確計算損失并進行訓練。
如何計算MSE
在沒有全連接層的情況下,每個節點都有一個輸出特征,標簽也是每個節點一個標簽。損失函數(如MSELoss)會計算每個節點輸出特征與對應標簽之間的誤差,然后將這些誤差進行平均或求和,得到總體損失。
以下是一個更詳細的說明:
- 模型輸出:形狀為
(num_nodes, 1)
,表示每個節點的輸出特征。 - 標簽:形狀為
(num_nodes, 1)
,表示每個節點的對應標簽。
在訓練過程中,損失函數會計算每個節點的輸出與對應標簽之間的誤差,并將所有節點的誤差進行匯總(通常是求平均),得到一個標量損失值。優化器會根據這個損失值更新模型的參數。
我們使用均方誤差損失(MSELoss),其計算公式為:
MSE = 1 N ∑ i = 1 N ( y i ? y ^ i ) 2 \text{MSE} = \frac{1}{N} \sum_{i=1}^N (y_i - \hat{y}_i)^2 MSE=N1?∑i=1N?(yi??y^?i?)2
其中 N N N是節點數, y i y_i yi? 是第 i i i 個節點的標簽, y ^ i \hat{y}_i y^?i?是第 i i i 個節點的預測值。
在這個示例中:
- 模型輸出:每個節點的輸出特征(形狀為
(num_nodes, 1)
)。 - 標簽:每個節點的對應標簽(形狀為
(num_nodes, 1)
)。 - 損失函數:均方誤差損失(MSELoss),計算每個節點輸出與對應標簽之間的誤差,并將這些誤差進行平均,得到總體損失。
這個過程確保了每個節點的輸出特征與其對應的標簽進行比較,從而計算損失并優化模型。
100個樣本,并且使用批量大小為32進行訓練
第一個版本定義數據集出錯,添加super()并修改為__len__和__getitem__
# 定義數據集類
class MyDataset(Dataset):def __init__(self, num_samples, num_nodes, num_node_features):self.num_samples = num_samplesself.num_nodes = num_nodesself.num_node_features = num_node_featuresdef len(self):return self.num_samplesdef get(self, idx):edge_index = torch.tensor([[i for i in range(self.num_nodes)],[(i + 1) % self.num_nodes for i in range(self.num_nodes)]], dtype=torch.long)x = torch.randn((self.num_nodes, self.num_node_features))y = torch.randn((self.num_nodes, 1)) # 每個節點一個標簽return Data(x=x, edge_index=edge_index, y=y)
當你遇到 AttributeError: can't set attribute
錯誤時,通常是因為你試圖在一個類的實例上設置一個屬性,但該類不允許直接設置屬性。在Python中,某些類,特別是那些繼承自某些基類(比如 torch.utils.data.Dataset
)的類,限制了直接屬性賦值的操作,這是為了安全或一致性考慮。
要解決這個問題,你應該在你的 MyDataset
類的 __init__
方法中正確地初始化屬性。以下是如何修復這個問題的方法:
from torch.utils.data import Dataset
from torch_geometric.data import Data# 定義數據集類
class MyDataset(Dataset):def __init__(self, num_samples, num_nodes, num_node_features):super(MyDataset, self).__init__() # Initialize base class if inheritingself.num_samples = num_samplesself.num_nodes = num_nodesself.num_node_features = num_node_features# 創建固定的邊索引,這里簡單使用環形圖self.edge_index = torch.tensor([[i for i in range(self.num_nodes)],[(i + 1) % self.num_nodes for i in range(self.num_nodes)]], dtype=torch.long)def __len__(self):return self.num_samplesdef __getitem__(self, idx):# 創建隨機特征和標簽,這里僅作示例x = torch.randn((self.num_nodes, self.num_node_features))y = torch.randn((self.num_nodes, 1)) # 每個節點一個標簽# 返回一個包含圖數據的 Data 對象,保持相同的邊索引return Data(x=x, edge_index=self.edge_index, y=y)# 創建數據集和數據加載器
num_samples = 100
num_nodes = 10
num_node_features = 8
batch_size = 32dataset = MyDataset(num_samples, num_nodes, num_node_features)
print(dataset[0].edge_index)
idx的作用
在PyTorch中,特別是在使用 torch.utils.data.Dataset
和 torch.utils.data.DataLoader
構建數據加載和處理管道時,idx
(或者通常命名為 index
)代表著數據集中樣本的索引。具體來說:
-
__getitem__
方法中的idx
:- 在自定義的數據集類中,通常會實現
__getitem__
方法。這個方法接收一個參數idx
,它表示你要獲取的樣本在數據集中的索引。 - 例如,在一個圖像分類任務中,
idx
就是每張圖像在數據集中的位置。通過這個索引,你可以從數據集中加載并返回對應位置的樣本數據。
- 在自定義的數據集類中,通常會實現
-
作用:
idx
的作用是定位和訪問數據集中特定樣本的數據。在訓練過程中,DataLoader
會使用__getitem__
方法迭代數據集,根據給定的idx
獲取每個樣本,然后將它們組織成批量供模型訓練。- 在使用
DataLoader
加載數據時,idx
通常會被DataLoader
內部迭代器管理,你無需手動傳遞它,只需實現好__getitem__
方法即可。
-
示例:
- 假設你有一個自定義的數據集類
MyDataset
,實現了__getitem__
方法來根據idx
加載圖像數據。當你使用DataLoader
加載這個數據集時,DataLoader
會自動處理索引的管理和批量數據的組織,你只需要關注數據集類的實現和模型的訓練過程。
- 假設你有一個自定義的數據集類
總結來說,idx
是用來在數據集中定位和訪問特定樣本的索引參數,它在自定義數據集類中的作用是非常重要的,能夠幫助你有效地管理和處理數據集中的樣本數據。
使用super()方法
如果在使用 super()
調用時出現錯誤,通常是因為類的初始化方法(__init__
)中沒有正確地調用父類的初始化方法。這可能會導致 Python 報告類的屬性無法設置的錯誤。讓我們來看看如何正確使用 super()
并初始化屬性。
在你的 MyDataset
類中,確保按照以下方式使用 super()
和正確初始化屬性:
super()
函數:在 Python 中,super()
函數用于調用父類的方法。在MyDataset
類的__init__
方法中,super(MyDataset, self).__init__()
調用了Dataset
類的初始化方法,確保正確初始化了Dataset
類中的屬性和方法。- 屬性初始化:在
MyDataset
的__init__
方法中,通過self.num_samples
、self.num_nodes
和self.num_node_features
初始化了數據集的屬性。這些屬性用于定義數據集的特征和樣本數量。 - 數據加載:
__getitem__
方法用于按照給定的idx
加載數據集中的樣本,并返回一個包含圖數據的Data
對象。
通過這樣的方式,你可以確保 MyDataset
類正確地繼承了 Dataset
類,并正確初始化了屬性,避免了 AttributeError
錯誤的發生。
再次解釋一遍定義數據集的代碼
在PyTorch中,torch.utils.data.Dataset
是一個抽象基類,要求自定義的數據集類必須實現 __len__
和 __getitem__
方法。這些方法分別用于確定數據集的長度和獲取數據集中的一個樣本。
__init__
方法:在__init__
方法中,創建了一個固定的邊索引self.edge_index
,這里使用簡單的環形圖示例。這個邊索引在數據集初始化時被創建,并在每次調用__getitem__
方法時被重復使用,從而確保每個樣本的圖數據保持相同的連接關系。__len__
方法:這個方法返回數據集的長度,即數據集中樣本的數量。在這里,它返回了num_samples
,表示數據集中有多少個樣本。__getitem__
方法:這個方法根據給定的索引idx
返回數據集中的一個樣本。在這里,它返回一個包含隨機節點特征、固定邊索引和隨機節點標簽的Data
對象,確保了圖連接關系的不變性。
你可以正確地實現并使用 MyDataset
類來創建多個數據集樣本,并確保每個樣本的圖連接關系保持不變。
另外一個值得注意的錯誤:定義數據集部分修改之后還是報obj = super().new(cls)TypeError: Can’t instantiate abstract class MyDataset with abstract methods get, len錯誤
from torch.utils.data import Dataset 與 from torch_geometric.data import Data 和 Dataset是不一樣的
torch.utils.data.Dataset
和 torch_geometric.data.Dataset
是兩個不同的類,分別來自于不同的模塊,功能和用途也略有不同。
-
torch.utils.data.Dataset
:- 這是 PyTorch 提供的一個抽象基類,用于創建自定義數據集。它要求用戶繼承并實現
__len__
和__getitem__
方法,以便能夠使用torch.utils.data.DataLoader
進行數據加載和批處理。 - 主要用途是在通用的機器學習任務中加載和處理數據集,例如圖像分類、文本處理等。
- 這是 PyTorch 提供的一個抽象基類,用于創建自定義數據集。它要求用戶繼承并實現
-
torch_geometric.data.Dataset
:- 這是 PyTorch Geometric 提供的一個特定數據集類,用于處理圖數據。它繼承自
torch.utils.data.Dataset
,并額外提供了一些方法和功能,使得可以更方便地處理圖數據集。 - 主要用途是在圖神經網絡中加載和處理圖數據,包括節點特征、邊索引等。
- 這是 PyTorch Geometric 提供的一個特定數據集類,用于處理圖數據。它繼承自
-
功能特點:
torch.utils.data.Dataset
適用于通用的數據加載和處理,可以處理各種類型的數據集。torch_geometric.data.Dataset
專門用于處理圖數據,提供了額外的功能來處理節點和邊的特征。
-
使用場景:
- 如果你處理的是普通的數據集(如圖像、文本等),可以使用
torch.utils.data.Dataset
來創建自定義的數據加載器。 - 如果你處理的是圖數據(如節點和邊具有特定的連接關系和屬性),建議使用
torch_geometric.data.Dataset
來利用其專門針對圖數據設計的功能。
- 如果你處理的是普通的數據集(如圖像、文本等),可以使用
如果你想要處理圖數據,可以使用 torch_geometric.data.Dataset
的子類,例如 torch_geometric.datasets.Planetoid
,用來加載圖數據集,例如 Planetoid 數據集:
from torch_geometric.datasets import Planetoid
import torch_geometric.transforms as Tdataset = Planetoid(root='/your/data/path', name='Cora', transform=T.NormalizeFeatures())
這里使用了 Planetoid
數據集類,它繼承自 torch_geometric.data.Dataset
,專門用于加載和處理圖數據集,例如 Cora 數據集。
from torch_geometric.data import Data的作用
在 PyTorch Geometric 中,torch_geometric.data.Data
是一個用于表示圖數據的核心數據結構之一。它主要用來存儲圖中的節點特征、邊索引以及可選的圖級別特征,具有以下作用:
-
存儲節點特征和邊索引:
Data
對象可以存儲節點特征矩陣(通常是一個二維張量)和邊索引(通常是一個二維長整型張量)。節點特征矩陣的每一行表示一個節點的特征向量,邊索引描述了節點之間的連接關系。
-
支持圖級別的特征:
- 除了節點特征和邊索引外,
Data
對象還可以存儲圖級別的特征,例如全局圖特征(如圖的標簽或屬性)。
- 除了節點特征和邊索引外,
-
作為輸入輸出的載體:
- 在圖神經網絡中,
Data
對象通常作為輸入數據的載體。例如,在進行圖分類、節點分類或圖生成任務時,模型的輸入通常是Data
對象。
- 在圖神經網絡中,
-
與其他 PyTorch Geometric 函數和類的兼容性:
Data
對象與 PyTorch Geometric 中的其他函數和類高度兼容,例如數據轉換、數據集加載等。它們共同支持創建、處理和轉換圖數據。
-
用于數據集的表示:
- 在自定義的數據集中,你可以使用
Data
對象來表示每個樣本的圖數據。通過組織和存儲節點特征、邊索引和圖級別特征,可以更方便地加載和處理復雜的圖結構數據集。
- 在自定義的數據集中,你可以使用
下面是一個簡單的示例,展示如何使用 Data
對象創建和操作圖數據:
from torch_geometric.data import Data
import torch# 創建節點特征和邊索引
x = torch.tensor([[1, 2], [3, 4], [5, 6]], dtype=torch.float) # 3個節點,每個節點2個特征
edge_index = torch.tensor([[0, 1, 1, 2], [1, 0, 2, 1]], dtype=torch.long) # 邊索引表示節點之間的連接關系# 創建一個 Data 對象
data = Data(x=x, edge_index=edge_index)# 訪問和操作 Data 對象中的屬性
print(data)
print("Number of nodes:", data.num_nodes)
print("Number of edges:", data.num_edges)
print("Node features shape:", data.x.shape)
print("Edge index shape:", data.edge_index.shape)
在這個示例中,我們首先創建了節點特征矩陣 x
和邊索引 edge_index
,然后使用它們來實例化一個 Data
對象 data
。通過訪問 data
對象的屬性,可以獲取節點數、邊數以及節點特征和邊索引的形狀信息。
總之,torch_geometric.data.Data
在 PyTorch Geometric 中扮演著關鍵的角色,用于表示和處理圖數據,是構建圖神經網絡模型的重要基礎之一。
驗證 MyDataset 類生成的樣本和批次數據的形狀
為了實現一個自定義數據集 MyDataset,可以創建一個包含 100 個樣本的數據集,每個樣本包含一個形狀為 (32, 8) 的節點特征矩陣。需要注意的是,MyDataset 類中的 __getitem__
方法應該返回每個樣本的數據,包括節點特征、邊索引等。
我們可以通過打印 MyDataset 中每個樣本的數據形狀來驗證數據的形狀。以下是實現和驗證的示例代碼:
dataset = MyDataset(num_samples, num_nodes, num_node_features)
print(len(dataset))# 查看數據集中的前幾個樣本的形狀
for i in range(3):data = dataset[i]print(f"Sample {i} - Node features shape: {data.x.shape}, Edge index shape: {data.edge_index.shape}, Labels shape: {data.y.shape}")dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True)
# 從 DataLoader 中獲取一個批次的數據
for batch in dataloader:print("Batch node features shape:", batch.x.shape)print("Batch edge index shape:", batch.edge_index.shape)print("Batch labels shape:", batch.y.shape)break # 僅查看第一個批次的形狀
數據格式是展平的 (batch_size * num_nodes, num_features)
使用DenseDataLoader數據格式為(batch_size , num_nodes, num_features)
DenseDataLoader 和 DataLoader 在處理數據的方式上有所不同。DenseDataLoader 是專門用于處理稠密圖數據的,而 DataLoader 通常用于處理稀疏圖數據。在你的案例中,如果所有圖的節點數和邊數是固定的,可以使用 DenseDataLoader 進行更高效的批處理。
完整代碼
import torch
import torch.nn.functional as F
from torch_geometric.nn import GCNConvfrom torch.utils.data import Dataset
from torch_geometric.data import Data
from torch_geometric.loader import DataLoader, DenseDataLoader# 定義數據集類
class MyDataset(Dataset):def __init__(self, num_samples, num_nodes, num_node_features):super(MyDataset, self).__init__() # Initialize base class if inheritingself.num_samples = num_samplesself.num_nodes = num_nodesself.num_node_features = num_node_features# 創建固定的邊索引,這里簡單使用環形圖self.edge_index = torch.tensor([[i for i in range(self.num_nodes)],[(i + 1) % self.num_nodes for i in range(self.num_nodes)]], dtype=torch.long)def __len__(self):return self.num_samplesdef __getitem__(self, idx):# 創建隨機特征和標簽,這里僅作示例x = torch.randn((self.num_nodes, self.num_node_features))y = torch.randn((self.num_nodes, 1)) # 每個節點一個標簽# 返回一個包含圖數據的 Data 對象,保持相同的邊索引return Data(x=x, edge_index=self.edge_index, y=y)# 創建數據集和數據加載器
num_samples = 100
num_nodes = 10
num_node_features = 8
batch_size = 32dataset = MyDataset(num_samples, num_nodes, num_node_features)
# data_list = [dataset[i] for i in range(num_samples)]
dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True)
# 從 DataLoader 中獲取一個批次的數據
for batch in dataloader:print("Batch node features shape:", batch.x.shape)print("Batch edge index shape:", batch.edge_index.shape)print("Batch labels shape:", batch.y.shape)break # 僅查看第一個批次的形狀# 定義GCN模型
class GCN(torch.nn.Module):def __init__(self, in_channels, hidden_channels, out_channels):super(GCN, self).__init__()self.conv1 = GCNConv(in_channels, hidden_channels)self.conv2 = GCNConv(hidden_channels, out_channels)def forward(self, x, edge_index):print(f"first {x.shape}")x = self.conv1(x, edge_index)print(f"conv1 {x.shape}")x = F.relu(x)x = self.conv2(x, edge_index)print(f"conv2 {x.shape}")return xmodel = GCN(in_channels=num_node_features, hidden_channels=16, out_channels=1)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
criterion = torch.nn.MSELoss()# 訓練模型
model.train()
for epoch in range(2):for data in dataloader:optimizer.zero_grad()out = model(data.x, data.edge_index)loss = criterion(out, data.y)loss.backward()optimizer.step()# 評估模型
model.eval()
with torch.no_grad():for batch in dataloader:out = model(batch.x, batch.edge_index)print(out.shape) # 待實現把batch中所有features都拼接起來
寫得比較亂,最后densedataloader和dataloader都可以專門來寫一篇了