-
Notifications
You must be signed in to change notification settings - Fork 53
Add missing examples #59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| // Copyright (c) .NET Foundation and Contributors. All Rights Reserved. See LICENSE in the project root for license information. | ||
| using System; | ||
| using System.IO; | ||
| using System.Collections.Generic; | ||
| using System.Diagnostics; | ||
|
|
||
| using TorchSharp; | ||
| using static TorchSharp.torchvision; | ||
|
|
||
| using TorchSharp.Examples; | ||
| using TorchSharp.Examples.Utils; | ||
|
|
||
| using static TorchSharp.torch; | ||
| using static TorchSharp.torch.nn; | ||
| using static TorchSharp.torch.nn.functional; | ||
|
|
||
| namespace CSharpExamples | ||
| { | ||
| /// <summary> | ||
| /// Forward-Forward MNIST classification | ||
| /// | ||
| /// Based on: https://github.com/pytorch/examples/tree/main/mnist_forward_forward | ||
| /// | ||
| /// Implements the Forward-Forward algorithm (Geoffrey Hinton, 2022). Instead of | ||
| /// backpropagation, each layer is trained independently using a local contrastive loss. | ||
| /// Positive examples have the correct label overlaid, negative examples have wrong labels. | ||
| /// </summary> | ||
| public class ForwardForward | ||
| { | ||
| internal static void Run(int epochs, int timeout, string logdir) | ||
| { | ||
| var device = | ||
| torch.cuda.is_available() ? torch.CUDA : | ||
| torch.mps_is_available() ? torch.MPS : | ||
| torch.CPU; | ||
|
|
||
| Console.WriteLine(); | ||
| Console.WriteLine($"\tRunning Forward-Forward MNIST on {device.type} for {epochs} epochs."); | ||
| Console.WriteLine(); | ||
|
|
||
| torch.random.manual_seed(1); | ||
|
|
||
| var dataset = "mnist"; | ||
| var datasetPath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), "..", "Downloads", dataset); | ||
|
|
||
| var sourceDir = datasetPath; | ||
| var targetDir = Path.Combine(datasetPath, "test_data"); | ||
|
|
||
| if (!Directory.Exists(targetDir)) { | ||
| Directory.CreateDirectory(targetDir); | ||
| Decompress.DecompressGZipFile(Path.Combine(sourceDir, "train-images-idx3-ubyte.gz"), targetDir); | ||
| Decompress.DecompressGZipFile(Path.Combine(sourceDir, "train-labels-idx1-ubyte.gz"), targetDir); | ||
| Decompress.DecompressGZipFile(Path.Combine(sourceDir, "t10k-images-idx3-ubyte.gz"), targetDir); | ||
| Decompress.DecompressGZipFile(Path.Combine(sourceDir, "t10k-labels-idx1-ubyte.gz"), targetDir); | ||
| } | ||
|
|
||
| Console.WriteLine($"\tLoading data..."); | ||
|
|
||
| // Load full training set as a single batch for the Forward-Forward algorithm | ||
| int trainSize = 50000; | ||
| int testSize = 10000; | ||
|
|
||
| using (MNISTReader trainReader = new MNISTReader(targetDir, "train", trainSize, device: device), | ||
| testReader = new MNISTReader(targetDir, "t10k", testSize, device: device)) | ||
| { | ||
| Stopwatch totalTime = new Stopwatch(); | ||
| totalTime.Start(); | ||
|
|
||
| // Get one big batch of training data | ||
| Tensor x = null, y = null, xTe = null, yTe = null; | ||
|
|
||
| foreach (var (data, target) in trainReader) { | ||
| // Flatten the images: (N, 1, 28, 28) -> (N, 784) | ||
| x = data.view(data.shape[0], -1); | ||
| y = target; | ||
| break; // Just the first (and only) batch | ||
| } | ||
|
|
||
| foreach (var (data, target) in testReader) { | ||
| xTe = data.view(data.shape[0], -1); | ||
| yTe = target; | ||
| break; | ||
| } | ||
|
|
||
| Console.WriteLine($"\tCreating Forward-Forward network [784, 500, 500]..."); | ||
|
|
||
| var net = new ForwardForwardNet(new int[] { 784, 500, 500 }, device); | ||
|
|
||
| // Create positive and negative examples | ||
| var xPos = ForwardForwardNet.OverlayLabelOnInput(x, y); | ||
| var yNeg = ForwardForwardNet.GetNegativeLabels(y); | ||
| var xNeg = ForwardForwardNet.OverlayLabelOnInput(x, yNeg); | ||
|
|
||
| Console.WriteLine($"\tTraining..."); | ||
| net.Train(xPos, xNeg, epochs, lr: 0.03, logInterval: 10); | ||
|
|
||
| // Evaluate | ||
| var trainPred = net.Predict(x); | ||
| var trainError = 1.0f - trainPred.eq(y).to_type(ScalarType.Float32).mean().item<float>(); | ||
| Console.WriteLine($"\tTrain error: {trainError:F4}"); | ||
|
|
||
| var testPred = net.Predict(xTe); | ||
| var testError = 1.0f - testPred.eq(yTe).to_type(ScalarType.Float32).mean().item<float>(); | ||
| Console.WriteLine($"\tTest error: {testError:F4}"); | ||
|
|
||
| totalTime.Stop(); | ||
| Console.WriteLine($"Elapsed time: {totalTime.Elapsed.TotalSeconds:F1} s."); | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| // Copyright (c) .NET Foundation and Contributors. All Rights Reserved. See LICENSE in the project root for license information. | ||
| using System; | ||
| using System.Diagnostics; | ||
|
|
||
| using TorchSharp; | ||
| using TorchSharp.Examples; | ||
|
|
||
| using static TorchSharp.torch; | ||
| using static TorchSharp.torch.nn; | ||
| using static TorchSharp.torch.nn.functional; | ||
|
|
||
| namespace CSharpExamples | ||
| { | ||
| /// <summary> | ||
| /// Graph Attention Network (GAT) for node classification | ||
| /// | ||
| /// Based on: https://github.com/pytorch/examples/tree/main/gat | ||
| /// | ||
| /// Implements a 2-layer GAT with multi-head attention for semi-supervised | ||
| /// node classification. Uses synthetic graph data for demonstration. | ||
| /// </summary> | ||
| public class GAT | ||
| { | ||
| internal static void Run(int epochs, int timeout, string logdir) | ||
| { | ||
| var device = | ||
| torch.cuda.is_available() ? torch.CUDA : | ||
| torch.mps_is_available() ? torch.MPS : | ||
| torch.CPU; | ||
|
|
||
| Console.WriteLine(); | ||
| Console.WriteLine($"\tRunning GAT on {device.type} for {epochs} epochs, terminating after {TimeSpan.FromSeconds(timeout)}."); | ||
| Console.WriteLine(); | ||
|
|
||
| torch.random.manual_seed(13); | ||
|
|
||
| // Synthetic graph data (simulating Cora-like structure) | ||
| int numNodes = 2708; | ||
| int numFeatures = 1433; | ||
| int numClasses = 7; | ||
| int hiddenDim = 64; | ||
| int numHeads = 8; | ||
|
|
||
| Console.WriteLine($"\tGenerating synthetic graph data..."); | ||
| Console.WriteLine($"\t Nodes: {numNodes}, Features: {numFeatures}, Classes: {numClasses}"); | ||
| Console.WriteLine($"\t Hidden: {hiddenDim}, Heads: {numHeads}"); | ||
|
|
||
| var features = torch.randn(numNodes, numFeatures, device: device); | ||
| var labels = torch.randint(numClasses, numNodes, device: device); | ||
|
|
||
| // Create adjacency matrix with self-loops | ||
| var adjMat = torch.eye(numNodes, device: device); | ||
| // Add some random edges to simulate graph structure | ||
| var rng = new Random(13); | ||
| int numEdges = 10556; | ||
| for (int e = 0; e < numEdges; e++) { | ||
| int i = rng.Next(numNodes); | ||
| int j = rng.Next(numNodes); | ||
| adjMat[i, j] = 1.0f; | ||
| adjMat[j, i] = 1.0f; | ||
| } | ||
|
|
||
| // Split | ||
| var idx = torch.randperm(numNodes, device: device); | ||
| var idxTrain = idx.slice(0, 1600, numNodes, 1); | ||
| var idxVal = idx.slice(0, 1200, 1600, 1); | ||
| var idxTest = idx.slice(0, 0, 1200, 1); | ||
|
|
||
| Console.WriteLine($"\tCreating GAT model..."); | ||
|
|
||
| var model = new GATModel("gat", numFeatures, hiddenDim, numHeads, numClasses, | ||
| concat: false, dropout: 0.6, leakyReluSlope: 0.2, device: device); | ||
|
|
||
| var optimizer = optim.Adam(model.parameters(), lr: 0.005, weight_decay: 5e-4); | ||
| var criterion = NLLLoss(); | ||
|
|
||
| Console.WriteLine($"\tTraining..."); | ||
|
|
||
| Stopwatch totalTime = new Stopwatch(); | ||
| totalTime.Start(); | ||
|
|
||
| for (int epoch = 1; epoch <= epochs; epoch++) { | ||
| using (var d = torch.NewDisposeScope()) { | ||
| model.train(); | ||
| optimizer.zero_grad(); | ||
|
|
||
| var output = model.forward(features, adjMat); | ||
| var loss = criterion.forward(output.index(idxTrain), labels.index(idxTrain)); | ||
| loss.backward(); | ||
| optimizer.step(); | ||
|
|
||
| if (epoch % 20 == 0 || epoch == 1) { | ||
| model.eval(); | ||
| using (torch.no_grad()) { | ||
| var evalOutput = model.forward(features, adjMat); | ||
|
|
||
| var trainAcc = evalOutput.index(idxTrain).argmax(1) | ||
| .eq(labels.index(idxTrain)).to_type(ScalarType.Float32).mean().item<float>(); | ||
| var valAcc = evalOutput.index(idxVal).argmax(1) | ||
| .eq(labels.index(idxVal)).to_type(ScalarType.Float32).mean().item<float>(); | ||
|
|
||
| Console.WriteLine($"\tEpoch {epoch:D4} | Loss: {loss.item<float>():F4} | Train Acc: {trainAcc:F4} | Val Acc: {valAcc:F4}"); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (totalTime.Elapsed.TotalSeconds > timeout) break; | ||
| } | ||
|
|
||
| // Final test | ||
| model.eval(); | ||
| using (torch.no_grad()) { | ||
| var testOutput = model.forward(features, adjMat); | ||
| var testAcc = testOutput.index(idxTest).argmax(1) | ||
| .eq(labels.index(idxTest)).to_type(ScalarType.Float32).mean().item<float>(); | ||
| Console.WriteLine($"\tTest accuracy: {testAcc:F4}"); | ||
| } | ||
|
|
||
| totalTime.Stop(); | ||
| Console.WriteLine($"Elapsed time: {totalTime.Elapsed.TotalSeconds:F1} s."); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,137 @@ | ||||||||||||||||||||||||||||||||
| // Copyright (c) .NET Foundation and Contributors. All Rights Reserved. See LICENSE in the project root for license information. | ||||||||||||||||||||||||||||||||
| using System; | ||||||||||||||||||||||||||||||||
| using System.Diagnostics; | ||||||||||||||||||||||||||||||||
| using System.Linq; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| using TorchSharp; | ||||||||||||||||||||||||||||||||
| using TorchSharp.Examples; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| using static TorchSharp.torch; | ||||||||||||||||||||||||||||||||
| using static TorchSharp.torch.nn; | ||||||||||||||||||||||||||||||||
| using static TorchSharp.torch.nn.functional; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| namespace CSharpExamples | ||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||
| /// Graph Convolutional Network (GCN) for node classification | ||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||
| /// Based on: https://github.com/pytorch/examples/tree/main/gcn | ||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||
| /// Implements a 2-layer GCN for semi-supervised node classification. | ||||||||||||||||||||||||||||||||
| /// Uses synthetic graph data for demonstration since the Cora dataset | ||||||||||||||||||||||||||||||||
| /// requires external download infrastructure. | ||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||
| public class GCN | ||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||
| internal static void Run(int epochs, int timeout, string logdir) | ||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||
| var device = | ||||||||||||||||||||||||||||||||
| torch.cuda.is_available() ? torch.CUDA : | ||||||||||||||||||||||||||||||||
| torch.mps_is_available() ? torch.MPS : | ||||||||||||||||||||||||||||||||
| torch.CPU; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| Console.WriteLine(); | ||||||||||||||||||||||||||||||||
| Console.WriteLine($"\tRunning GCN on {device.type} for {epochs} epochs, terminating after {TimeSpan.FromSeconds(timeout)}."); | ||||||||||||||||||||||||||||||||
| Console.WriteLine(); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| torch.random.manual_seed(42); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Create synthetic graph data for demonstration | ||||||||||||||||||||||||||||||||
| // In practice, you would load a real graph dataset like Cora | ||||||||||||||||||||||||||||||||
| int numNodes = 2708; | ||||||||||||||||||||||||||||||||
| int numFeatures = 1433; | ||||||||||||||||||||||||||||||||
| int numClasses = 7; | ||||||||||||||||||||||||||||||||
| int hiddenDim = 16; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| Console.WriteLine($"\tGenerating synthetic graph data..."); | ||||||||||||||||||||||||||||||||
| Console.WriteLine($"\t Nodes: {numNodes}, Features: {numFeatures}, Classes: {numClasses}"); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Random features and labels | ||||||||||||||||||||||||||||||||
| var features = torch.randn(numNodes, numFeatures, device: device); | ||||||||||||||||||||||||||||||||
| var labels = torch.randint(numClasses, numNodes, device: device); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Create a random sparse adjacency matrix (simulating graph structure) | ||||||||||||||||||||||||||||||||
| int numEdges = 10556; | ||||||||||||||||||||||||||||||||
| var edgeIdx1 = torch.randint(numNodes, numEdges, device: device); | ||||||||||||||||||||||||||||||||
| var edgeIdx2 = torch.randint(numNodes, numEdges, device: device); | ||||||||||||||||||||||||||||||||
| var adjMat = torch.zeros(numNodes, numNodes, device: device); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Add edges and self-loops | ||||||||||||||||||||||||||||||||
| for (int i = 0; i < numNodes; i++) { | ||||||||||||||||||||||||||||||||
| adjMat[i, i] = 1.0f; // self-loops | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| // Note: In a real implementation, you'd construct the adjacency matrix properly | ||||||||||||||||||||||||||||||||
| // and apply the renormalization trick D^(-1/2) A D^(-1/2) | ||||||||||||||||||||||||||||||||
| // For now, use identity + random edges normalized by degree | ||||||||||||||||||||||||||||||||
| adjMat = adjMat + torch.eye(numNodes, device: device) * 0.1f; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
Comment on lines
+59
to
+67
|
||||||||||||||||||||||||||||||||
| // Add edges and self-loops | |
| for (int i = 0; i < numNodes; i++) { | |
| adjMat[i, i] = 1.0f; // self-loops | |
| } | |
| // Note: In a real implementation, you'd construct the adjacency matrix properly | |
| // and apply the renormalization trick D^(-1/2) A D^(-1/2) | |
| // For now, use identity + random edges normalized by degree | |
| adjMat = adjMat + torch.eye(numNodes, device: device) * 0.1f; | |
| // Add self-loops | |
| for (int i = 0; i < numNodes; i++) { | |
| adjMat[i, i] = 1.0f; // self-loops | |
| } | |
| // Note: In a real implementation, you'd construct the adjacency matrix properly, | |
| // add edges based on the dataset, and apply the renormalization trick D^(-1/2) A D^(-1/2). | |
| // Here we only add self-loops for demonstration; the adjacency is then normalized below. |
Copilot
AI
Feb 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The slice operations for train/val/test splits are incorrect. The indices are specified as (dim, start, end, step), but the order should be (dim, start, end, step) where start and end define the range. Line 77 uses idx.slice(0, 1500, numNodes, 1) which would try to slice from index 1500 to numNodes, but based on the context this should be slicing indices 1500 onwards. The correct call should be idx.slice(0, 1500, numNodes, 1) or more simply idx[TensorIndex.Slice(1500, numNodes)]. Similar issues exist on lines 78-79.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The slice operations for train/val/test splits appear to be incorrect. Line 65 uses
idx.slice(0, 1600, numNodes, 1)which attempts to slice from index 1600 to numNodes with step 1, but based on the context (training should use indices 1600 onwards), this appears backwards. Similarly, lines 66-67 have the same issue. The correct usage should ensure training gets the largest subset, validation gets a medium subset, and test gets the smallest. Verify the slice parameters match the intended data split.