Skip to content

Commit 0823927

Browse files
committed
readme updated + pytorch results are comparalbe
1 parent c80301d commit 0823927

File tree

8 files changed

+129
-140
lines changed

8 files changed

+129
-140
lines changed

GCN/GCN.fsproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
</PropertyGroup>
77
<ItemGroup>
88
<Compile Include="TorchSharp.Fun.fs" />
9-
<Compile Include="Utils.fs" />
109
<Compile Include="GCNModel.fs" />
10+
<Compile Include="Utils.fs" />
1111
<Compile Include="Train.fs" />
1212
<Compile Include="Program.fs" />
1313
</ItemGroup>

GCN/GCNModel.fs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ open type TorchSharp.NN.Modules
55
open TorchSharp.Fun
66
let inline (!>) (x:^a) : ^b = ((^a or ^b) : (static member op_Implicit : ^a -> ^b) x)
77

8+
///single graph convolutional layer
89
let gcnLayer in_features out_features hasBias (adj:TorchTensor) =
910
let weight = Parameter(randName(),Float32Tensor.empty([|in_features; out_features|],requiresGrad=true))
1011
let bias = if hasBias then Parameter(randName(),Float32Tensor.empty([|out_features|],requiresGrad=true)) |> Some else None
@@ -23,7 +24,7 @@ let gcnLayer in_features out_features hasBias (adj:TorchTensor) =
2324
let create nfeat nhid nclass dropout adj =
2425
let gc1 = gcnLayer nfeat nhid true adj
2526
let gc2 = gcnLayer nhid nclass true adj
26-
let drp = if dropout > 0.0 then Dropout(dropout) |> M else Model.nop
27+
let drp = Dropout(dropout)
2728

2829
fwd3 gc1 gc2 drp (fun t g1 g2 drp ->
2930
use t = gc1.forward(t)

GCN/Program.fs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,6 @@ module Defs =
5757
[<EntryPoint>]
5858
let main args =
5959
let runParms = Defs.parse args
60-
try
61-
Train.run runParms
62-
with ex ->
63-
printfn "%s" ex.Message
60+
Train.run runParms
6461
System.Console.ReadLine() |> ignore
6562
0

GCN/Train.fs

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,34 +22,38 @@ let run (datafolder,no_cuda,fastmode,epochs,dropout,lr,hidden,seed,weight_decay)
2222
let loss = NN.Functions.nll_loss()
2323

2424
if cuda then
25-
model.Module.cuda() |> ignore
25+
model.Module.cuda() |> ignore
2626

2727
let optimizer = NN.Optimizer.Adam(model.Module.parameters(), learningRate = lr, weight_decay=weight_decay)
2828

2929
let train epoch =
3030
let t = DateTime.Now
31-
model.Module.Train()
32-
let parms = model.Module.parameters()
31+
model.Module.Train()
3332
optimizer.zero_grad()
3433
let output = model.forward(features)
3534
let loss_train = loss.Invoke(output.[ idx_train], labels.[idx_train])
36-
let ls = float loss_train
3735
let acc_train = Utils.accuracy(output.[idx_train], labels.[idx_train])
38-
printfn $"training - loss: {ls}, acc: {acc_train}"
3936
loss_train.backward()
4037
optimizer.step()
4138

42-
if fastmode then
43-
model.Module.Eval()
44-
let y' = model.forward(features)
45-
let loss_val = loss.Invoke(y'.[idx_val], labels.[idx_val])
46-
let acc_val = Utils.accuracy(y'.[idx_val], labels.[idx_val])
47-
printfn
48-
$"""
49-
Epoch: {epoch}, loss_train: {float loss_train},
50-
acc_train: {acc_train}, loss_val: {float loss_val},
51-
acc_val: {acc_val}, time: {t}
52-
"""
39+
let parms = model.Module.parameters()
40+
let data = parms |> Array.map TorchSharp.Fun.Tensor.getData<float32>
41+
let i = 1
42+
43+
let loss_val,acc_val =
44+
if not fastmode then
45+
model.Module.Eval()
46+
let y' = model.forward(features)
47+
let loss_val = loss.Invoke(y'.[idx_val], labels.[idx_val])
48+
let acc_val = Utils.accuracy(y'.[idx_val], labels.[idx_val])
49+
loss_val,acc_val
50+
else
51+
let loss_val = loss.Invoke(output.[idx_val], labels.[idx_val])
52+
let acc_val = Utils.accuracy(output.[idx_val], labels.[idx_val])
53+
loss_val,acc_val
54+
55+
printf $"Epoch: {epoch}, loss_train: %0.4f{float loss_train}, acc_train: %0.4f{acc_train}, "
56+
printfn $"loss_val: %0.4f{float loss_val}, acc_val: %0.4f{acc_val}"
5357

5458
let test() =
5559
model.Module.Eval()
@@ -62,10 +66,9 @@ let run (datafolder,no_cuda,fastmode,epochs,dropout,lr,hidden,seed,weight_decay)
6266

6367
let t_total = DateTime.Now
6468
for i in 1 .. epochs-1 do
65-
printfn $"epoch {i}"
6669
train i
6770
printfn "Optimization done"
68-
printfn $"Time elapsed: {(DateTime.Now - t_total).TotalMinutes} minutes"
71+
printfn $"Time elapsed: %0.2f{(DateTime.Now - t_total).TotalSeconds} seconds"
6972

7073
test()
7174

GCN/Utils.fs

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@ let normalize (m:Matrix<float32>) =
3030

3131
let sparse_mx_to_torch_sparse_tensor (m:Matrix<float32>) =
3232
let coo = m.EnumerateIndexed(Zeros.AllowSkip)
33-
let rows = coo |> Seq.map (fun (r,c,v) -> int64 r)
33+
let rows = coo |> Seq.map (fun (r,c,v) -> int64 r)
3434
let cols = coo |> Seq.map (fun (r,c,v) -> int64 c)
35+
let vals = coo |> Seq.map (fun (r,c,v) -> v)
3536
let idxs = Seq.append rows cols |> Seq.toArray
36-
let idx1 = idxs |> Int64Tensor.from |> fun x -> x.view(2L,-1L)
37-
let vals = coo |> Seq.map(fun (r,c,v) -> v) |> Seq.toArray |> Float32Tensor.from
38-
let t = Float32Tensor.sparse(idx1,vals,[|int64 m.RowCount; int64 m.ColumnCount|])
39-
let dt = TorchSharp.Fun.Tensor.getData<float32>(t.to_dense())
37+
let idxT = idxs |> Int64Tensor.from |> fun x -> x.view(2L, idxs.Length / 2 |> int64)
38+
let valsT = vals |> Seq.toArray |> Float32Tensor.from
39+
let t = Float32Tensor.sparse(idxT,valsT,[|int64 m.RowCount; int64 m.ColumnCount|])
4040
t
4141

4242
let accuracy(output:TorchTensor, labels:TorchTensor) =
@@ -64,27 +64,26 @@ let loadData (dataFolder:string) dataset =
6464
Label = xs.[xs.Length-1]
6565
|})
6666

67-
let edges =
67+
let idx_map = dataFeatures |> Seq.mapi (fun i x-> x.Id,i) |> Map.ofSeq
68+
69+
let edges_unordered =
6870
edgesFile
6971
|> File.ReadLines
7072
|> Seq.map (fun x->x.Split('\t'))
7173
|> Seq.map (fun xs -> xs.[0],xs.[1])
7274
|> Seq.toArray
7375

74-
let edgeIdx =
75-
edges
76-
|> Seq.collect (fun (a,b)->[a;b])
77-
|> Seq.distinct
78-
|> Seq.mapi (fun i x->x,i)
79-
|> dict
76+
let edges =
77+
edges_unordered
78+
|> Array.map (fun (a,b) -> idx_map.[a],idx_map.[b])
8079

81-
let ftrs = Matrix.Build.DenseOfRows(dataFeatures |> Seq.map (fun x->Array.toSeq x.Features))
80+
let ftrs = Matrix.Build.SparseOfRowArrays(dataFeatures |> Seq.map (fun x-> x.Features) |> Seq.toArray)
8281

8382
let graph = Matrix.Build.SparseFromCoordinateFormat
8483
(
85-
edgeIdx.Count, edgeIdx.Count, edges.Length, //rows,cols,num vals
86-
edges |> Array.map (fun x -> edgeIdx.[fst x]), //hot row idx
87-
edges |> Array.map (fun x -> edgeIdx.[snd x]), //hot col idx
84+
idx_map.Count, idx_map.Count, edges.Length, //rows,cols,num vals
85+
edges |> Array.map fst, //hot row idx
86+
edges |> Array.map snd, //hot col idx
8887
edges |> Array.map (fun _ -> 1.0f) //values
8988
)
9089

GCN/scripts/gcn.fsx

Lines changed: 13 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,24 @@
11
#load "packages.fsx"
2-
open System
3-
open System.IO
4-
open MathNet.Numerics.LinearAlgebra
2+
#load "../Utils.fs"
3+
open TorchSharp.Fun
54

6-
let dataFolder = @"C:\Users\fwaris\Downloads\pygcn-master\data\cora"
7-
let contentFile = $"{dataFolder}/cora.content"
8-
let citesFile = $"{dataFolder}/cora.cites"
9-
let yourself x = x
5+
let datafolder = @"C:\s\Repos\gcn\data\cora"
6+
let adj, features, labels, idx_train, idx_val, idx_test = Utils.loadData datafolder None
107

11-
let dataCntnt =
12-
contentFile
13-
|> File.ReadLines
14-
|> Seq.map(fun x -> x.Split('\t'))
15-
|> Seq.map(fun xs ->
16-
{|
17-
Id = xs.[0]
18-
Features = xs.[1 .. xs.Length-2] |> Array.map float32
19-
Label = xs.[xs.Length-1]
20-
|})
8+
let v1 = adj.[0L,50L] |> float
219

22-
let dataCites =
23-
citesFile
24-
|> File.ReadLines
25-
|> Seq.map (fun x->x.Split('\t'))
26-
|> Seq.map (fun xs -> xs.[0],xs.[1])
27-
|> Seq.toArray
10+
let idx = adj.SparseIndices |> Tensor.getData<int64>
11+
let rc = idx |> Array.chunkBySize (idx.Length/2)
12+
let vals = adj.SparseValues |> Tensor.getData<float32>
2813

29-
let citationIdx = dataCites |> Seq.collect (fun (a,b)->[a;b]) |> Seq.distinct |> Seq.mapi (fun i x->x,i) |> dict
14+
let i = 500
15+
let r,c = rc.[0].[i],rc.[1].[i]
16+
let vx = adj.[r,c] |> float
3017

31-
let ftrs = Matrix.Build.DenseOfRows(dataCntnt |> Seq.map (fun x->Array.toSeq x.Features))
18+
let df = features |> Tensor.getData<float32> |> Array.chunkBySize (int features.shape.[1])
3219

33-
let graph = Matrix.Build.SparseFromCoordinateFormat
34-
(
35-
dataCites.Length, dataCites.Length, dataCites.Length,
36-
dataCites |> Array.map (fun x -> citationIdx.[fst x]),
37-
dataCites |> Array.map (fun x -> citationIdx.[snd x]),
38-
dataCites |> Array.map (fun _ -> 1.0f)
39-
)
20+
let f1 = features.[1L,12L] |> float
4021

41-
let normalize (m:Matrix<float32>) =
42-
let rowsum = m.RowSums()
43-
let r_inv = rowsum.PointwisePower(-1.0f)
44-
let r_inv = r_inv.Map(fun x-> if Single.IsInfinity x then 0.0f else x)
45-
let r_mat_inv = Matrix.Build.SparseOfDiagonalVector(r_inv)
46-
let mx = r_mat_inv.Multiply(m)
47-
mx
48-
49-
let graph_n = Matrix.Build.SparseIdentity(graph.RowCount) + graph |> normalize
50-
let ftrs_n = normalize ftrs
51-
52-
open TorchSharp.Tensor
53-
let sparse_mx_to_torch_sparse_tensor (m:Matrix<float32>) =
54-
let coo = m.EnumerateIndexed(Zeros.AllowSkip)
55-
let rows = coo |> Seq.map (fun (r,c,v) -> int64 r)
56-
let cols = coo |> Seq.map (fun (r,c,v) -> int64 c)
57-
let idxs = Seq.append rows cols |> Seq.toArray
58-
let idx1 = idxs |> Int64Tensor.from |> fun x -> x.view(2L,-1L)
59-
let vals = coo |> Seq.map(fun (r,c,v) -> v) |> Seq.toArray |> Float32Tensor.from
60-
Float32Tensor.sparse(idx1,vals,[|int64 m.RowCount; int64 m.ColumnCount|])
61-
62-
let adj = sparse_mx_to_torch_sparse_tensor(graph_n)
63-
64-
module GCNModel =
65-
open TorchSharp.Tensor
66-
open TorchSharp.NN
67-
open type TorchSharp.NN.Modules
68-
open TorchSharp.Fun
69-
let inline (!>) (x:^a) : ^b = ((^a or ^b) : (static member op_Implicit : ^a -> ^b) x)
70-
71-
let gcnLayer in_features out_features hasBias (adj:TorchTensor) =
72-
let weight = Parameter(randName(),Float32Tensor.empty([|in_features; out_features|]))
73-
let bias = if hasBias then Parameter(randName(),Float32Tensor.empty([|out_features|])) |> Some else None
74-
let parms = [| yield weight; if hasBias then yield bias.Value|]
75-
Init.kaiming_uniform(weight.Tensor) |> ignore
76-
77-
Model.create(parms,fun wts t ->
78-
let support = t.mm(wts.[0])
79-
let output = adj.mm(support)
80-
if hasBias then
81-
output.add(wts.[1])
82-
else
83-
output)
84-
85-
let create nfeat nhid nclass dropout adj =
86-
let gc1 = gcnLayer nfeat nhid true adj
87-
let gc2 = gcnLayer nhid nclass true adj
88-
let relu = ReLU()
89-
let logm = LogSoftmax(1L)
90-
let drp = if dropout then Dropout() |> M else Model.nop
91-
fwd3 gc1 gc2 drp (fun t g1 g2 drp ->
92-
use t = gc1.forward(t)
93-
use t = relu.forward(t)
94-
use t = drp.forward(t)
95-
use t = gc2.forward(t)
96-
let t = logm.forward(t)
97-
t)
9822

9923

10024

GCN/scripts/packages.fsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@
1515
//#r "nuget: libtorch-cuda-11.1-win-x64, 1.8.0.7"
1616
System.Runtime.InteropServices.NativeLibrary.Load(@"D:\s\libtorch\lib\torch_cuda.dll")
1717

18-
#load @"..\MLUtils.fs"
19-
#load @"..\MathUtils.fs"
18+
2019
#load @"..\TorchSharp.Fun.fs"
2120
#I @"C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App\5.0.4"
2221
#r "System.Windows.Forms"

README.md

Lines changed: 75 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,78 @@
1-
### Design thoughts
1+
# Graph Convolutional Networks in TorchSharp.Fun
22

3-
#### What is avaialble
3+
TorchSharp.Fun is thin functional wrapper in F# over TorchSharp (a .Net binding of PyTorch).
44

5-
- V1 clusters
6-
- V2 clusters
5+
## TorchSharp.Fun Example
76

8-
#### Model
9-
- each micro cluster is a graph
10-
- extract the micrograph
11-
- note time to wait for root alarm
12-
-
7+
Below is a simple sequential model. It is a composition over standard TorchSharp 'modules'. The compostion is performed with the '->>' operator.
8+
9+
```F#
10+
let model =
11+
Linear(10L,5L)
12+
->> Dropout(0.5)
13+
->> Linear(5L,1L)
14+
->> RelU()
15+
```
16+
17+
## GCN Model
18+
19+
The Graph Convolutional Network (GCN) model presented in this repo is based on the work of Thomas Kipf, [Graph Convolutional Networks](http://tkipf.github.io/graph-convolutional-networks/) (2016).
20+
21+
It is a port of the [Pytorch GCN model](http://github.com/tkipf/pygcn).
22+
23+
## TorchSharp.Fun
24+
25+
The code for TorchSharp.Fun is included in the repo. At this stage it is expected to undergo considerable churn and therefore is not released as an independent package.
26+
27+
## Training the model
28+
29+
The data for the model included is however two changes to source are required to train the model. Both are in Program.fs file. These are:
30+
31+
- Path to libtorch native library - [download link](https://pytorch.org/)
32+
- Path to the data folder
33+
34+
It is recommend to use Visual Studio code with F# / Ionide plug-in - just start the project after making the above changes.
35+
36+
## Why TorchSharp.Fun?
37+
38+
A function-compostional approach to deep learning models arose when I could not easily create a deep ResNet model with 'standard' TorchSharp.
39+
40+
An alternative F# library was also tried. The library supports an elegant API; it was easy to create a deep ResNet model. Unfortunately at its current stage of development, the training performance for deep models is not on par with that of basic TorchSharp.
41+
42+
TorchSharp.Fun is a very thin wrapper over TorchSharp does not suffer any noticable performance hits when compared with TorchSharp (or PyTorch for that matter).
43+
44+
Below is an example of a 30 layer ResNet regression model:
45+
46+
```F#
47+
module Resnet =
48+
let RESNET_DIM = 50L
49+
let RESNET_DEPTH = 30
50+
let FTR_DIM = 340L
51+
52+
let act() = SELU()
53+
54+
let resnetCell (input: Model) =
55+
let cell =
56+
act()
57+
->> Linear(RESNET_DIM, RESNET_DIM)
58+
->> act()
59+
->> Linear(RESNET_DIM, RESNET_DIM)
60+
61+
let join =
62+
fwd2 input cell (fun ``input tensor`` inputModel cellModel ->
63+
use t1 = inputModel.forward (``input tensor``)
64+
use t2 = cellModel.forward (t1)
65+
t1 + t2)
66+
67+
join ->> act()
68+
69+
let create() =
70+
let emb = Linear(FTR_DIM, RESNET_DIM) |> M
71+
let rsLayers =
72+
(emb, [ 1 .. RESNET_DEPTH ])
73+
||> List.fold (fun emb _ -> resnetCell emb)
74+
rsLayers
75+
->> Linear(RESNET_DIM,10L)
76+
->> Linear(10L, 1L)
77+
78+
```

0 commit comments

Comments
 (0)