DGL 0.5ユーザガイド : 5 章 訓練 : 5.2 エッジ分類/回帰 (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 09/21/2020 (0.5.2)
* 本ページは、DGL の以下のドキュメントを翻訳した上で適宜、補足説明したものです:
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
ユーザガイド : 5 章 訓練 : 5.2 エッジ分類/回帰
グラフのエッジ上の属性、あるいは 2 つの与えられたノード間にエッジが存在するか否かさえ予測することを時に望むかもしれません。そのような場合、エッジ分類/回帰モデルを持ちたいでしょう。
ここでは実演としてエッジ予測のためにランダムなグラフを生成します。
src = np.random.randint(0, 100, 500) dst = np.random.randint(0, 100, 500) # make it symmetric edge_pred_graph = dgl.graph((np.concatenate([src, dst]), np.concatenate([dst, src]))) # synthetic node and edge features, as well as edge labels edge_pred_graph.ndata['feature'] = torch.randn(100, 10) edge_pred_graph.edata['feature'] = torch.randn(1000, 10) edge_pred_graph.edata['label'] = torch.randn(1000) # synthetic train-validation-test splits edge_pred_graph.edata['train_mask'] = torch.zeros(1000, dtype=torch.bool).bernoulli(0.6)
概要
前のセクションで多層 GNN でノード分類をどのように行なうか学びました。任意のノードの隠れ表現を計算するために同じテクニックが適用できます。それからエッジ上の予測はそれらの付随するノードの表現から導出できます。
エッジ上の予測を計算する最も一般的なケースはそれを付随するノードの表現と、そしてオプションでエッジ自身の特徴のパラメータ化された関数として表現することです。
ノード分類とのモデル実装の相違
前のセクションからのモデルでノード表現を計算すると仮定すると、apply_edges() メソッドでエッジ予測を計算するもう一つのコンポーネントを書く必要があるだけです。
例えば、エッジ回帰のために各エッジのためにスコアを計算したい場合、以下のコードは各エッジ上の付随するノード表現の dot 積を計算します。
import dgl.function as fn class DotProductPredictor(nn.Module): def forward(self, graph, h): # h contains the node representations computed from the GNN defined # in the node classification section (Section 5.1). with graph.local_scope(): graph.ndata['h'] = h graph.apply_edges(fn.u_dot_v('h', 'h', 'score')) return graph.edata['score']
MLP で各エッジのためのベクトルを予測する予測関数を書くこともできます。そのようなベクトルは更なるダウンストリームなタスクで利用できます、e.g. カテゴリカル分布のロジットとして。
class MLPPredictor(nn.Module): def __init__(self, in_features, out_classes): super().__init__() self.W = nn.Linear(in_features * 2, out_classes) def apply_edges(self, edges): h_u = edges.src['h'] h_v = edges.dst['h'] score = self.W(torch.cat([h_u, h_v], 1)) return {'score': score} def forward(self, graph, h): # h contains the node representations computed from the GNN defined # in the node classification section (Section 5.1). with graph.local_scope(): graph.ndata['h'] = h graph.apply_edges(self.apply_edges) return graph.edata['score']
訓練ループ
ノード表現計算モデルとエッジ予測器モデルが与えられれば、full-グラフ訓練ループを容易に書くことができます、そこでは総てのエッジ上の予測を計算します。
次のサンプルは前のセクションの SAGE をノード表現計算モデルとしてそして DotPredictor をエッジ予測器モデルとして取ります。
class Model(nn.Module): def __init__(self, in_features, hidden_features, out_features): super().__init__() self.sage = SAGE(in_features, hidden_features, out_features) self.pred = DotProductPredictor() def forward(self, g, x): h = self.sage(g, x) return self.pred(g, h)
このサンプルでは、訓練/検証/テスト・エッジセットがエッジ上の boolean マスクで識別されることも仮定しています。このサンプルはまた early stopping とモデルセービングは含みません。
node_features = edge_pred_graph.ndata['feature'] edge_label = edge_pred_graph.edata['label'] train_mask = edge_pred_graph.edata['train_mask'] model = Model(10, 20, 5) opt = torch.optim.Adam(model.parameters()) for epoch in range(10): pred = model(edge_pred_graph, node_features) loss = ((pred[train_mask] - edge_label[train_mask]) ** 2).mean() opt.zero_grad() loss.backward() opt.step() print(loss.item())
異質グラフ
異質グラフ上のエッジ分類は均質グラフ上のそれと大きくは違いません。一つのエッジ型上でエッジ分類を遂行することを望む場合には、総てのノード型に対してノード表現を計算し、そしてそのエッジ型上で apply_edges により予測する必要があるだけです。
例えば、DotProductPredictor を異質グラフの一つのエッジ型上で動作させるには、apply_edges メソッドでエッジ型を指定する必要があるだけです。
class HeteroDotProductPredictor(nn.Module): def forward(self, graph, h, etype): # h contains the node representations for each edge type computed from # the GNN for heterogeneous graphs defined in the node classification # section (Section 5.1). with graph.local_scope(): graph.ndata['h'] = h # assigns 'h' of all node types in one shot graph.apply_edges(fn.u_dot_v('h', 'h', 'score'), etype=etype) return graph.edges[etype].data['score']
同様に HeteroMLPPredictor を書くことができます。
class MLPPredictor(nn.Module): def __init__(self, in_features, out_classes): super().__init__() self.W = nn.Linear(in_features * 2, out_classes) def apply_edges(self, edges): h_u = edges.src['h'] h_v = edges.dst['h'] score = self.W(torch.cat([h_u, h_v], 1)) return {'score': score} def forward(self, graph, h, etype): # h contains the node representations for each edge type computed from # the GNN for heterogeneous graphs defined in the node classification # section (Section 5.1). with graph.local_scope(): graph.ndata['h'] = h # assigns 'h' of all node types in one shot graph.apply_edges(self.apply_edges, etype=etype) return graph.edges[etype].data['score']
単一エッジ型上の各エッジに対するスコアを予測する end-to-end モデルはこのようなものです :
class Model(nn.Module): def __init__(self, in_features, hidden_features, out_features, rel_names): super().__init__() self.sage = RGCN(in_features, hidden_features, out_features, rel_names) self.pred = HeteroDotProductPredictor() def forward(self, g, x, etype): h = self.sage(g, x) return self.pred(g, h, etype)
モデルの使用はモデルにノード型と特徴の辞書を単純に供給することを伴います。
model = Model(10, 20, 5, hetero_graph.etypes) user_feats = hetero_graph.nodes['user'].data['feature'] item_feats = hetero_graph.nodes['item'].data['feature'] label = hetero_graph.edges['click'].data['label'] train_mask = hetero_graph.edges['click'].data['train_mask'] node_features = {'user': user_feats, 'item': item_feats}
それから訓練ループは均質グラフのそれと殆ど同じように見えます。例えば、エッジ型 click 上でエッジラベルを予測することを望むのであれば、単純に以下を行なうことができます :
opt = torch.optim.Adam(model.parameters()) for epoch in range(10): pred = model(hetero_graph, node_features, 'click') loss = ((pred[train_mask] - label[train_mask]) ** 2).mean() opt.zero_grad() loss.backward() opt.step() print(loss.item())
異質グラフ上で存在するエッジのエッジ型を予測する
時に存在するエッジがどの型に属するか予測することを望むかもしれません。
例えば、異質グラフサンプル が与えられたとき、貴方のタスクはユーザと項目を接続するエッジが与えられたとき、ユーザが項目をクリックするか好きでないか (= dislike) を予測します。
これはレーティング予測の単純化されたバージョンで、これはリコメンデーション文献では一般的です。ノード表現を得るために異質グラフ畳込みネットワークを利用できます。例えば、この目的で 前に定義された RGCN を依然として利用できます。
エッジ型を予測するために、上の HeteroDotProductPredictor を単純に最目的化できます、その結果それは予測される総てのエッジ型を「マージする」一つのエッジ型を持つもう一つのグラフを取り、総てのエッジについて各型のスコアを出力します。
ここのサンプルでは、2 つのノード型 user と item と、そして user と item からの総てのエッジ型, i.e. click と dislike を「マージする」一つの単一エッジ型を持つグラフを必要とします。これは次のシンタックスを使用して便利に作成できます :
dec_graph = hetero_graph['user', :, 'item']
これはノード型 user と item、そして中間にある総てのエッジ型, i.e. click と dislike を結合する単一エッジ型を持つ異質グラフを返します。
上のステートメントはまた元のエッジ型を dgl.ETYPE として名前付けられた特徴として返しますので、それをラベルとして利用できます。
edge_label = dec_graph.edata[dgl.ETYPE]
エッジ型予測器モジュールへの入力として上のグラフが与えられたとき、予測器モジュールを次のように書くことができます。
class HeteroMLPPredictor(nn.Module): def __init__(self, in_dims, n_classes): super().__init__() self.W = nn.Linear(in_dims * 2, n_classes) def apply_edges(self, edges): x = torch.cat([edges.src['h'], edges.dst['h']], 1) y = self.W(x) return {'score': y} def forward(self, graph, h): # h contains the node representations for each edge type computed from # the GNN for heterogeneous graphs defined in the node classification # section (Section 5.1). with graph.local_scope(): graph.ndata['h'] = h # assigns 'h' of all node types in one shot graph.apply_edges(self.apply_edges) return graph.edata['score']
ノード表現モジュールとエッジ型予測器モジュールを結合したモデルは次のようなものです :
class Model(nn.Module): def __init__(self, in_features, hidden_features, out_features, rel_names): super().__init__() self.sage = RGCN(in_features, hidden_features, out_features, rel_names) self.pred = HeteroMLPPredictor(out_features, len(rel_names)) def forward(self, g, x, dec_graph): h = self.sage(g, x) return self.pred(dec_graph, h)
それから訓練ループは単純に次のようなものです :
model = Model(10, 20, 5, hetero_graph.etypes) user_feats = hetero_graph.nodes['user'].data['feature'] item_feats = hetero_graph.nodes['item'].data['feature'] node_features = {'user': user_feats, 'item': item_feats} opt = torch.optim.Adam(model.parameters()) for epoch in range(10): logits = model(hetero_graph, node_features, dec_graph) loss = F.cross_entropy(logits, edge_label) opt.zero_grad() loss.backward() opt.step() print(loss.item())
DGL はレーティング予測のサンプルとして グラフ畳込み行列補完 (= Matrix Completion) を提供します、これは異質グラフ上で存在するエッジの型を予測することで定式化されます。モデル実装ファイル のノード表現モジュールは GCMCLayer と呼称されます。エッジ型予測器モジュールは BiDecoder と呼ばれます。それら両者はここで説明されている設定よりも複雑です。
以上