OSCR

Spinal-inspired artificial tactile interneuron with high-order burst spiking for intelligent edge interfaces.

Code ↔ Paper

4 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 4 matches
  1. [1] § Results › AMINs-SNN for multimodal object recognition ↔ SNNmodel/SNN_classification_multi_runs_450.py, lines 80–193 · score 0.60 · cosine annealing, confusion matrix, validation accuracy, schedule, configuration, classification
  2. [2] § Results › AMINs-SNN for multimodal object recognition ↔ SNNmodel/SNN_classification_multi_runs_450_rebuttal.py, lines 80–193 · score 0.60 · cosine annealing, confusion matrix, validation accuracy, schedule, configuration, classification
  3. [3] § Results › AMINs-SNN for multimodal object recognition ↔ SNNmodel/SNN_classification_multi_runs_450.py, lines 195–310 · score 0.59 · training epochs, Confusion matrix, Validation accuracy, Heatmaps, pads, classification
  4. [4] § Results › AMINs-SNN for multimodal object recognition ↔ SNNmodel/SNN_classification_multi_runs_450_rebuttal.py, lines 195–310 · score 0.59 · training epochs, Confusion matrix, Validation accuracy, Heatmaps, pads, classification

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Python · 623 lines · 25 KB · no license · 2 matches

  1. from __future__ import print_function
  2. import os
  3. import argparse
  4. import torch
  5. import torch.nn as nn
  6. import torch.nn.functional as F
  7. import torch.optim as optim
  8. from torchvision import datasets, transforms
  9. from torch.optim.lr_scheduler import StepLR
  10. from sklearn.metrics import confusion_matrix, classification_report
  11. import numpy as np
  12. from merge_batchnorm import *
  13. from units import *
  14. from models import *
  15. from torch.optim.lr_scheduler import MultiStepLR
  16. import matplotlib.pyplot as plt
  17. from torch.utils.data import random_split
  18. from torch.optim.lr_scheduler import CosineAnnealingWarmRestarts
  19. import numpy as np
  20. import torch
  21. import torch.nn as nn
  22. import torch.nn.functional as F
  23. from torch.utils.data import DataLoader, TensorDataset
  24. import json
  25. from datetime import datetime
  26. import seaborn as sns
  27. from collections import defaultdict
  28. import pandas as pd
  29. import csv
  30. def train(model, device, train_loader, optimizer, epoch):
  31. criterion = nn.CrossEntropyLoss(label_smoothing=0.1)
  32. model.train()
  33. total_loss = 0
  34. for batch_idx, (data, target) in enumerate(train_loader):
  35. data, target = data.to(device), target.to(device)
  36. onehot = torch.nn.functional.one_hot(target, 20).float()
  37. optimizer.zero_grad()
  38. output = model(data)
  39. loss = criterion(output, target)
  40. loss.backward()
  41. optimizer.step()
  42. total_loss += loss.item()
  43. # Print less frequently to reduce output
  44. if batch_idx % 20 == 0:
  45. print(f'Train Epoch: {epoch} [{batch_idx * len(data)}/{len(train_loader.dataset)}]\tLoss: {loss.item():.6f}')
  46. return total_loss / len(train_loader)
  47. def validate(model, device, val_loader, testdataset_=False, return_cm=False):
  48. model.eval()
  49. correct = 0
  50. all_preds = []
  51. all_targets = []
  52. with torch.no_grad():
  53. for batch_idx, (data, target) in enumerate(val_loader):
  54. data, target = data.to(device), target.to(device)
  55. output = model(data)
  56. pred = output.argmax(dim=1, keepdim=False)
  57. correct += pred.eq(target.view_as(pred)).sum().item()
  58. all_preds.extend(pred.view(-1).cpu().numpy())
  59. all_targets.extend(target.cpu().numpy())
  60. accuracy = 100. * correct / len(val_loader.dataset)
  61. cm = confusion_matrix(all_targets, all_preds)
  62. print(f'Accuracy: {accuracy:.2f}%')
  63. if return_cm:
  64. return accuracy, cm, all_preds, all_targets
  65. elif testdataset_:
  66. return accuracy
  67. else:
  68. return accuracy
  69. def single_run(run_id, args, device):
  70. """
  71. Perform a single training run and return results
  72. """
  73. print(f"\n{'='*60}")
  74. print(f"RUN {run_id + 1}/5")
  75. print(f"{'='*60}")
  76. # Set different seed for each run
  77. torch.manual_seed(3407+17+run_id)
  78. np.random.seed(3407+17+run_id)
  79. # Load data
  80. train_data = torch.load('train_data_3dimension_force.pt')
  81. val_data = torch.load('val_data_3dimension_force.pt')
  82. test_data = torch.load('test_data_3dimension_force.pt')
  83. # train_data 300
  84. # val
  85. # test_data
  86. train_loader = DataLoader(TensorDataset(train_data['X'], train_data['y']), batch_size=32, shuffle=True)
  87. val_loader = DataLoader(TensorDataset(val_data['X'], val_data['y']), batch_size=2000, shuffle=False)
  88. test_loader = DataLoader(TensorDataset(test_data['X'], test_data['y']), batch_size=2000, shuffle=False)
  89. # Model configuration
  90. input_size = 450
  91. hidden_sizes = [384, 512, 768, 128]
  92. output_size = 20
  93. custom_dropouts = [0.10, 0.10, 0.35, 0.25]
  94. # Create model
  95. model = Net(input_size=input_size, hidden_sizes=hidden_sizes, output_size=output_size,
  96. quantize_level=args.T, dropout_rates=custom_dropouts).to(device)
  97. #optimizer = optim.Adam(model.parameters(), lr=args.lr)
  98. #scheduler = CosineAnnealingWarmRestarts(optimizer, T_0=10, T_mult=2, eta_min=1e-6)
  99. optimizer = optim.AdamW(model.parameters(), lr=0.001, weight_decay=1e-4)
  100. scheduler = optim.lr_scheduler.CosineAnnealingWarmRestarts(optimizer, T_0=10, T_mult=2, eta_min=1e-6)
  101. # Training tracking
  102. best_val_acc = 0.0
  103. best_model_path = f"snn_model_run_{run_id}.pt"
  104. train_losses = []
  105. val_accuracies = []
  106. test_accuracies = []
  107. print(f"Starting training for run {run_id + 1}...")
  108. # Training loop
  109. for epoch in range(1, args.epochs + 1):
  110. if epoch % 10 == 0: # Print every 10 epochs
  111. print(f"Run {run_id + 1} - Epoch {epoch}/{args.epochs}, LR: {scheduler.get_last_lr()[0]:.6f}")
  112. # Train
  113. train_loss = train(model, device, train_loader, optimizer, epoch)
  114. train_losses.append(train_loss)
  115. # Validate
  116. val_acc = validate(model, device, val_loader, testdataset_=True)
  117. test_acc = validate(model, device, test_loader, testdataset_=True)
  118. val_accuracies.append(val_acc)
  119. test_accuracies.append(test_acc)
  120. # Save best model
  121. if val_acc > best_val_acc:
  122. best_val_acc = val_acc
  123. torch.save(model.state_dict(), best_model_path)
  124. #if epoch % 10 == 0:
  125. print(f"Run {run_id + 1} - New best model saved with validation accuracy: {val_acc:.2f}%")
  126. scheduler.step()
  127. # Load best model for final evaluation
  128. model.load_state_dict(torch.load(best_model_path))
  129. print(f"\nRun {run_id + 1} - Final ANN evaluation:")
  130. final_ann_acc, ann_cm, ann_preds, ann_targets = validate(model, device, test_loader, testdataset_=True, return_cm=True)
  131. # Convert to quantized model
  132. print(f"Run {run_id + 1} - Converting to quantized model...")
  133. model_inference = InferenceNet(input_size=input_size, hidden_sizes=hidden_sizes,
  134. output_size=output_size, quantize_level=args.T).to(device)
  135. model_inference = transfer_weights_to_inference_model(model, model_inference)
  136. # Convert to SNN
  137. print(f"Run {run_id + 1} - Converting to SNN...")
  138. snn_quantized = Sparrow_SNN(input_size=input_size, hidden_sizes=hidden_sizes,
  139. output_size=output_size, quantized_index=8, T=args.T,
  140. Hybrid=args.Hybrid).to(device)
  141. snn_quantized.load_state_dict(model_inference.state_dict(), strict=False)
  142. # Final SNN evaluation
  143. print(f"Run {run_id + 1} - Final SNN evaluation:")
  144. final_snn_acc, snn_cm, snn_preds, snn_targets = validate(snn_quantized, device, test_loader, testdataset_=True, return_cm=True)
  145. # Clean up model file
  146. if os.path.exists(best_model_path):
  147. os.remove(best_model_path)
  148. return {
  149. 'run_id': run_id + 1,
  150. 'train_losses': train_losses,
  151. 'val_accuracies': val_accuracies,
  152. 'test_accuracies': test_accuracies,
  153. 'best_val_acc': best_val_acc,
  154. 'final_ann_acc': final_ann_acc,
  155. 'final_snn_acc': final_snn_acc,
  156. 'ann_confusion_matrix': ann_cm,
  157. 'snn_confusion_matrix': snn_cm,
  158. 'ann_predictions': ann_preds,
  159. 'ann_targets': ann_targets,
  160. 'snn_predictions': snn_preds,
  161. 'snn_targets': snn_targets
  162. }
  163. def plot_results(all_results, args):
  164. """
  165. Create simplified visualizations focusing on SNN confusion matrix and validation accuracy evolution
  166. """
  167. print("\nCreating simplified SNN visualizations...")
  168. # Create results directory
  169. results_dir = "results_450d_onlyforce"
  170. os.makedirs(results_dir, exist_ok=True)
  171. print(f"Results will be saved to: {results_dir}/")
  172. # Set publication-quality style
  173. plt.rcParams.update({
  174. 'font.size': 12,
  175. 'font.family': 'serif',
  176. 'font.serif': ['Times New Roman', 'DejaVu Serif'],
  177. 'axes.linewidth': 1.2,
  178. 'axes.spines.top': False,
  179. 'axes.spines.right': False,
  180. 'axes.grid': True,
  181. 'grid.alpha': 0.3,
  182. 'grid.linewidth': 0.8,
  183. 'legend.frameon': True,
  184. 'legend.fancybox': True,
  185. 'legend.shadow': True,
  186. 'legend.fontsize': 10,
  187. 'xtick.direction': 'in',
  188. 'ytick.direction': 'in',
  189. 'xtick.major.size': 4,
  190. 'ytick.major.size': 4,
  191. 'figure.dpi': 300,
  192. 'savefig.dpi': 300,
  193. 'savefig.bbox': 'tight',
  194. 'savefig.pad_inches': 0.1
  195. })
  196. # Create figure with 2 subplots
  197. fig = plt.figure(figsize=(16, 6))
  198. # ============ SUBPLOT 1: Average SNN Confusion Matrix ============
  199. ax1 = plt.subplot(1, 2, 1)
  200. # Calculate average confusion matrix across all runs
  201. avg_snn_cm = np.mean([result['snn_confusion_matrix'] for result in all_results], axis=0)
  202. # Normalize to percentages
  203. avg_snn_cm_norm = avg_snn_cm / avg_snn_cm.sum(axis=1, keepdims=True) * 100
  204. # Create heatmap
  205. im = plt.imshow(avg_snn_cm_norm, cmap='Blues', aspect='auto', vmin=0, vmax=100)
  206. # Add colorbar
  207. cbar = plt.colorbar(im, fraction=0.046, pad=0.04)
  208. cbar.set_label('Classification Accuracy (%)', fontweight='bold')
  209. # Add text annotations for all elements
  210. for i in range(avg_snn_cm_norm.shape[0]):
  211. for j in range(avg_snn_cm_norm.shape[1]):
  212. if avg_snn_cm_norm[i, j] > 50: # White text for dark cells
  213. color = 'white'
  214. else: # Black text for light cells
  215. color = 'black'
  216. text = plt.text(j, i, f'{avg_snn_cm_norm[i, j]:.1f}',
  217. ha="center", va="center", color=color,
  218. fontsize=8, fontweight='bold')
  219. # Set labels and title
  220. plt.xlabel('Predicted Class', fontweight='bold')
  221. plt.ylabel('True Class', fontweight='bold')
  222. #plt.title('Average SNN Confusion Matrix (450D, 5 Runs)', fontweight='bold', pad=15)
  223. # Set tick labels
  224. num_classes = avg_snn_cm_norm.shape[0]
  225. plt.xticks(range(num_classes), range(num_classes))
  226. plt.yticks(range(num_classes), range(num_classes))
  227. # ============ SUBPLOT 2: Validation Accuracy Evolution Over 310 Epochs ============
  228. ax2 = plt.subplot(1, 2, 2)
  229. # Professional color palette for different runs
  230. colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd']
  231. # Plot validation accuracy for each run
  232. for i, result in enumerate(all_results):
  233. epochs = range(1, len(result['val_accuracies']) + 1)
  234. plt.plot(epochs, result['val_accuracies'], color=colors[i % len(colors)],
  235. alpha=0.7, linewidth=1.5, label=f'Run {i+1}')
  236. plt.xlabel('Training Epoch', fontweight='bold')
  237. plt.ylabel('Validation Accuracy (%)', fontweight='bold')
  238. plt.legend(loc='lower right', ncol=2, fontsize=9)
  239. plt.grid(True, alpha=0.3)
  240. # Adjust layout
  241. plt.tight_layout(pad=2.0)
  242. # Save figure to results directory
  243. timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
  244. plot_filename = os.path.join(results_dir, f'snn_450d_results_{timestamp}.pdf')
  245. plt.savefig(plot_filename, format='pdf', dpi=300, bbox_inches='tight')
  246. plt.savefig(plot_filename.replace('.pdf', '.png'), format='png', dpi=300, bbox_inches='tight')
  247. print(f"450D SNN results figure saved as: {plot_filename}")
  248. # Calculate overall SNN accuracy from confusion matrix
  249. overall_accuracy = np.trace(avg_snn_cm_norm) / avg_snn_cm_norm.shape[0]
  250. print(f"\nAverage SNN Classification Accuracy: {overall_accuracy:.2f}%")
  251. # Show plot
  252. plt.show()
  253. # Reset matplotlib parameters
  254. plt.rcdefaults()
  255. return plot_filename
  256. def save_training_data_to_csv(all_results, results_dir, timestamp):
  257. """
  258. Save training process data (validation accuracies, test accuracies, losses) to CSV files
  259. """
  260. print("Saving training process data to CSV files...")
  261. # 1. Save individual run training curves
  262. for i, result in enumerate(all_results):
  263. run_data = {
  264. 'epoch': list(range(1, len(result['val_accuracies']) + 1)),
  265. 'train_loss': result['train_losses'],
  266. 'val_accuracy': result['val_accuracies'],
  267. 'test_accuracy': result['test_accuracies']
  268. }
  269. df = pd.DataFrame(run_data)
  270. csv_file = os.path.join(results_dir, f'training_curves_450d_run_{i+1}_{timestamp}.csv')
  271. df.to_csv(csv_file, index=False)
  272. print(f" Run {i+1} training curves saved: {csv_file}")
  273. # 2. Save combined validation accuracies, test accuracies, and losses across all runs
  274. max_epochs = max(len(result['val_accuracies']) for result in all_results)
  275. combined_data = {'epoch': list(range(1, max_epochs + 1))}
  276. for i, result in enumerate(all_results):
  277. # Pad with NaN if some runs have fewer epochs
  278. val_accs = result['val_accuracies'] + [None] * (max_epochs - len(result['val_accuracies']))
  279. test_accs = result['test_accuracies'] + [None] * (max_epochs - len(result['test_accuracies']))
  280. train_losses = result['train_losses'] + [None] * (max_epochs - len(result['train_losses']))
  281. combined_data[f'val_accuracy_run_{i+1}'] = val_accs
  282. combined_data[f'test_accuracy_run_{i+1}'] = test_accs
  283. combined_data[f'train_loss_run_{i+1}'] = train_losses
  284. combined_df = pd.DataFrame(combined_data)
  285. combined_csv = os.path.join(results_dir, f'all_runs_training_curves_450d_{timestamp}.csv')
  286. combined_df.to_csv(combined_csv, index=False)
  287. print(f" Combined training curves saved: {combined_csv}")
  288. return combined_csv
  289. def save_confusion_matrices_to_csv(all_results, results_dir, timestamp):
  290. """
  291. Save SNN confusion matrices to CSV files
  292. """
  293. print("Saving SNN confusion matrices to CSV files...")
  294. # 1. Save individual SNN confusion matrices
  295. for i, result in enumerate(all_results):
  296. # SNN confusion matrix
  297. snn_cm_df = pd.DataFrame(result['snn_confusion_matrix'],
  298. index=[f'True_{j}' for j in range(20)],
  299. columns=[f'Pred_{j}' for j in range(20)])
  300. snn_csv = os.path.join(results_dir, f'snn_confusion_matrix_450d_run_{i+1}_{timestamp}.csv')
  301. snn_cm_df.to_csv(snn_csv)
  302. print(f" Run {i+1} SNN confusion matrix saved: {snn_csv}")
  303. # 2. Save average SNN confusion matrix
  304. avg_snn_cm = np.mean([result['snn_confusion_matrix'] for result in all_results], axis=0)
  305. # Average SNN confusion matrix
  306. avg_snn_df = pd.DataFrame(avg_snn_cm,
  307. index=[f'True_{j}' for j in range(20)],
  308. columns=[f'Pred_{j}' for j in range(20)])
  309. avg_snn_csv = os.path.join(results_dir, f'avg_snn_confusion_matrix_450d_{timestamp}.csv')
  310. avg_snn_df.to_csv(avg_snn_csv)
  311. print(f" Average SNN confusion matrix saved: {avg_snn_csv}")
  312. # 3. Save normalized SNN confusion matrix (percentages)
  313. avg_snn_cm_norm = avg_snn_cm / avg_snn_cm.sum(axis=1, keepdims=True) * 100
  314. avg_snn_norm_df = pd.DataFrame(avg_snn_cm_norm,
  315. index=[f'True_{j}' for j in range(20)],
  316. columns=[f'Pred_{j}' for j in range(20)])
  317. avg_snn_norm_csv = os.path.join(results_dir, f'avg_snn_confusion_matrix_normalized_450d_{timestamp}.csv')
  318. avg_snn_norm_df.to_csv(avg_snn_norm_csv)
  319. print(f" Average SNN confusion matrix (normalized) saved: {avg_snn_norm_csv}")
  320. return avg_snn_csv, avg_snn_norm_csv
  321. def save_summary_results_to_csv(all_results, results_dir, timestamp):
  322. """
  323. Save summary results for each run to CSV
  324. """
  325. print("Saving summary results to CSV...")
  326. summary_data = {
  327. 'run_id': [],
  328. 'best_val_accuracy': [],
  329. 'final_ann_accuracy': [],
  330. 'final_snn_accuracy': [],
  331. 'accuracy_drop': []
  332. }
  333. for result in all_results:
  334. summary_data['run_id'].append(result['run_id'])
  335. summary_data['best_val_accuracy'].append(result['best_val_acc'])
  336. summary_data['final_ann_accuracy'].append(result['final_ann_acc'])
  337. summary_data['final_snn_accuracy'].append(result['final_snn_acc'])
  338. summary_data['accuracy_drop'].append(result['final_ann_acc'] - result['final_snn_acc'])
  339. # Add statistics
  340. ann_accuracies = summary_data['final_ann_accuracy']
  341. snn_accuracies = summary_data['final_snn_accuracy']
  342. accuracy_drops = summary_data['accuracy_drop']
  343. # Add summary row
  344. summary_data['run_id'].append('MEAN')
  345. summary_data['best_val_accuracy'].append(np.mean([result['best_val_acc'] for result in all_results]))
  346. summary_data['final_ann_accuracy'].append(np.mean(ann_accuracies))
  347. summary_data['final_snn_accuracy'].append(np.mean(snn_accuracies))
  348. summary_data['accuracy_drop'].append(np.mean(accuracy_drops))
  349. summary_data['run_id'].append('STD')
  350. summary_data['best_val_accuracy'].append(np.std([result['best_val_acc'] for result in all_results]))
  351. summary_data['final_ann_accuracy'].append(np.std(ann_accuracies))
  352. summary_data['final_snn_accuracy'].append(np.std(snn_accuracies))
  353. summary_data['accuracy_drop'].append(np.std(accuracy_drops))
  354. summary_df = pd.DataFrame(summary_data)
  355. summary_csv = os.path.join(results_dir, f'summary_results_450d_onlyforce_{timestamp}.csv')
  356. summary_df.to_csv(summary_csv, index=False)
  357. print(f" Summary results saved: {summary_csv}")
  358. return summary_csv
  359. def main():
  360. # Training settings
  361. parser = argparse.ArgumentParser(description='SNN Multi-Run Classification')
  362. parser.add_argument('--batch-size', type=int, default=512, metavar='N',
  363. help='input batch size for training (default: 512)')
  364. parser.add_argument('--test-batch-size', type=int, default=100, metavar='N',
  365. help='input batch size for testing (default: 100)')
  366. parser.add_argument('--epochs', type=int, default=310, metavar='N',
  367. help='number of epochs to train (default: 50)')
  368. parser.add_argument('--lr', type=float, default=2e-2, metavar='LR',
  369. help='learning rate (default: 2e-2)')
  370. parser.add_argument('--T', type=int, default=31, metavar='LR',
  371. help='time window size')
  372. parser.add_argument('--gamma', type=float, default=0.7, metavar='M',
  373. help='Learning rate step gamma (default: 0.7)')
  374. parser.add_argument('--no-cuda', action='store_true', default=False,
  375. help='disables CUDA training')
  376. parser.add_argument('--seed', type=int, default=1, metavar='S',
  377. help='random seed (default: 1)')
  378. parser.add_argument('--Hybrid', type=str, default=False, metavar='RESUME',
  379. help='Resume model from checkpoint')
  380. parser.add_argument('--num-runs', type=int, default=5, metavar='N',
  381. help='number of runs to perform (default: 5)')
  382. args = parser.parse_args()
  383. use_cuda = not args.no_cuda and torch.cuda.is_available()
  384. device = torch.device("cuda" if use_cuda else "cpu")
  385. print(f"Device: {device}")
  386. print(f"Running {args.num_runs} independent training runs...")
  387. print(f"Configuration: {args.epochs} epochs, LR={args.lr}, T={args.T}")
  388. # Store results from all runs
  389. all_results = []
  390. # Run multiple training sessions
  391. for run_id in range(args.num_runs):
  392. try:
  393. result = single_run(run_id, args, device)
  394. all_results.append(result)
  395. print(f"Run {run_id + 1} completed successfully!")
  396. print(f" ANN Accuracy: {result['final_ann_acc']:.2f}%")
  397. print(f" SNN Accuracy: {result['final_snn_acc']:.2f}%")
  398. except Exception as e:
  399. print(f"Run {run_id + 1} failed with error: {e}")
  400. continue
  401. if not all_results:
  402. print("No successful runs completed!")
  403. return
  404. # Calculate statistics
  405. print(f"\n{'='*60}")
  406. print("FINAL RESULTS SUMMARY")
  407. print(f"{'='*60}")
  408. ann_accuracies = [result['final_ann_acc'] for result in all_results]
  409. snn_accuracies = [result['final_snn_acc'] for result in all_results]
  410. print(f"\nANN Test Accuracies: {[f'{acc:.2f}%' for acc in ann_accuracies]}")
  411. print(f"SNN Test Accuracies: {[f'{acc:.2f}%' for acc in snn_accuracies]}")
  412. # ANN Statistics
  413. ann_mean = np.mean(ann_accuracies)
  414. ann_std = np.std(ann_accuracies)
  415. ann_ci = 1.96 * ann_std / np.sqrt(len(ann_accuracies)) # 95% confidence interval
  416. print(f"\nANN Results:")
  417. print(f" Mean ± Std: {ann_mean:.2f}% ± {ann_std:.2f}%")
  418. print(f" 95% CI: [{ann_mean - ann_ci:.2f}%, {ann_mean + ann_ci:.2f}%]")
  419. print(f" Range: [{min(ann_accuracies):.2f}%, {max(ann_accuracies):.2f}%]")
  420. # SNN Statistics
  421. snn_mean = np.mean(snn_accuracies)
  422. snn_std = np.std(snn_accuracies)
  423. snn_ci = 1.96 * snn_std / np.sqrt(len(snn_accuracies))
  424. print(f"\nSNN Results:")
  425. print(f" Mean ± Std: {snn_mean:.2f}% ± {snn_std:.2f}%")
  426. print(f" 95% CI: [{snn_mean - snn_ci:.2f}%, {snn_mean + snn_ci:.2f}%]")
  427. print(f" Range: [{min(snn_accuracies):.2f}%, {max(snn_accuracies):.2f}%]")
  428. # Accuracy drop analysis
  429. accuracy_drops = [ann - snn for ann, snn in zip(ann_accuracies, snn_accuracies)]
  430. drop_mean = np.mean(accuracy_drops)
  431. drop_std = np.std(accuracy_drops)
  432. print(f"\nAccuracy Drop (ANN → SNN):")
  433. print(f" Mean ± Std: {drop_mean:.2f}% ± {drop_std:.2f}%")
  434. print(f" Range: [{min(accuracy_drops):.2f}%, {max(accuracy_drops):.2f}%]")
  435. # Target result
  436. print(f"\n🎯 AVERAGE SNN TEST ACCURACY: {snn_mean:.2f}%")
  437. # Create results directory
  438. results_dir = "results_450d_onlyforce"
  439. os.makedirs(results_dir, exist_ok=True)
  440. # Save detailed results to the results directory
  441. timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
  442. results_file = os.path.join(results_dir, f'snn_450d_results_{timestamp}.json')
  443. # Prepare data for JSON serialization
  444. save_data = {
  445. 'timestamp': timestamp,
  446. 'configuration': {
  447. 'epochs': args.epochs,
  448. 'learning_rate': args.lr,
  449. 'time_window': args.T,
  450. 'num_runs': len(all_results),
  451. 'input_size': 450, # Original 450D input
  452. 'architecture': [384, 512, 768, 128], # Correct hidden layer sizes
  453. 'dropout_rates': [0.10, 0.10, 0.35, 0.25] # Correct dropout rates
  454. },
  455. 'results': {
  456. 'ann_accuracies': ann_accuracies,
  457. 'snn_accuracies': snn_accuracies,
  458. 'accuracy_drops': accuracy_drops
  459. },
  460. 'statistics': {
  461. 'ann': {
  462. 'mean': float(ann_mean),
  463. 'std': float(ann_std),
  464. 'ci_95': [float(ann_mean - ann_ci), float(ann_mean + ann_ci)],
  465. 'range': [float(min(ann_accuracies)), float(max(ann_accuracies))]
  466. },
  467. 'snn': {
  468. 'mean': float(snn_mean),
  469. 'std': float(snn_std),
  470. 'ci_95': [float(snn_mean - snn_ci), float(snn_mean + snn_ci)],
  471. 'range': [float(min(snn_accuracies)), float(max(snn_accuracies))]
  472. },
  473. 'accuracy_drop': {
  474. 'mean': float(drop_mean),
  475. 'std': float(drop_std),
  476. 'range': [float(min(accuracy_drops)), float(max(accuracy_drops))]
  477. }
  478. },
  479. 'all_results': [
  480. {
  481. 'run_id': result['run_id'],
  482. 'final_snn_acc': result['final_snn_acc'],
  483. 'snn_confusion_matrix': result['snn_confusion_matrix'].tolist()
  484. }
  485. for result in all_results
  486. ]
  487. }
  488. with open(results_file, 'w') as f:
  489. json.dump(save_data, f, indent=2)
  490. print(f"\nDetailed results saved to: {results_file}")
  491. # Create comprehensive visualizations
  492. plot_filename = plot_results(all_results, args)
  493. print(f"\n{'='*60}")
  494. print("450D MULTI-RUN ANALYSIS COMPLETE")
  495. print(f"{'='*60}")
  496. print(f"📁 Results directory: {results_dir}/")
  497. print(f"📊 Results file: {results_file}")
  498. print(f"📈 Main figure: {plot_filename}")
  499. print(f"🎯 Average SNN accuracy: {snn_mean:.2f}% ± {snn_std:.2f}%")
  500. print(f"📋 Input dimension: 450D (original)")
  501. print(f"🔄 Number of runs: {len(all_results)}")
  502. print(f"⚡ Training epochs: {args.epochs}")
  503. # List all files in results directory
  504. import glob
  505. result_files = glob.glob(os.path.join(results_dir, "*"))
  506. print(f"\n📂 Files in {results_dir}/:")
  507. for file in sorted(result_files):
  508. file_size = os.path.getsize(file) / 1024 # KB
  509. print(f" {os.path.basename(file)} ({file_size:.1f} KB)")
  510. #Save training data to CSV
  511. save_training_data_to_csv(all_results, results_dir, timestamp)
  512. # Save confusion matrices to CSV
  513. save_confusion_matrices_to_csv(all_results, results_dir, timestamp)
  514. # Save summary results to CSV
  515. save_summary_results_to_csv(all_results, results_dir, timestamp)
  516. if __name__ == '__main__':
  517. main()

SNN_classification_multi_runs_450.py at commit 917836f, no license · at the source

Overview

Authors: Fanfan Li1,2, Zhanglu Yan3, Jiayi Mao2, Guolei Liu2,4, Huihui Ren1,2, Bangbang Qin2, Zhongfang Zhang2, Haiyue Zhang2, Yiyang Shen2, Zeqi Zheng5, Weilong Feng2, Dingwei Li2,4, Yingjie Tang2,4, Saisai Wang6, Yaochu Jin5, Tao Luo7, Weng-fai Wong3, Hong Wang8, Bowen Zhu2,6
  1. School of Materials Science and Engineering, Zhejiang University, Hangzhou, China
  2. Zhejiang Key Laboratory of 3D Micro/Nano Fabrication and Characterization, Department of Electronic and Information Engineering, School of Engineering, Westlake University, Hangzhou, China
  3. School of Computing, National University of Singapore, Singapore, Singapore
  4. College of Information Science & Electronic Engineering, Zhejiang University, Hangzhou, China
  5. Department of Artificial Intelligence, School of Engineering, Westlake University, Hangzhou, China
  6. Westlake Institute for Optoelectronics, Westlake University, Hangzhou, China
  7. Institute of High Performance Computing, Agency for Science, Technology and Research, Singapore, Singapore
  8. Key Laboratory of Wide Band Gap Semiconductor Technology, School of Microelectronics, Xidian University, Xi’an, China
Journal: Nature communications, volume 17, issue 1, article 9285
Dates: received 4 December 2025; accepted 22 July 2026; published online 31 July 2026
Type: Research article · Language: English
License: CC BY-NC-ND
Identifiers: DOI 10.1038/s41467-026-76185-0 · PMID 42669703 · PMCID PMC13526877 · OpenAlex W7171982398
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: human (organism), cognitive (subfield)
Keywords: Electrical and electronic engineering, Electronic devices
MeSH: Interneurons*, Touch*, Touch Perception*, Action Potentials, Humans, Models, Neurological (* major topic)
Topic: Advanced Sensor and Energy Harvesting Materials (Biomedical Engineering, Engineering), according to OpenAlex
Funding: Natural Science Foundation of Zhejiang Province (Zhejiang Provincial Natural Science Foundation) (LQKWL26F0401, D25F020001); National Natural Science Foundation of China (National Science Foundation of China) (92464105, 62574169, 62504208)
Citations: not cited yet (Europe PMC); 45 references in the paper

Abstract

The abstract is not reproduced here: the paper's license (CC BY-NC-ND) does not allow it. Read it in the paper, at the publisher or on Europe PMC.

Repository

Its files are read in the Code ↔ Paper reader above, with 4 matches between paragraphs and lines of code.

zhangluyan9/nc-spinal-inspired

License: none: the authors keep all their rights
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 917836f723db5aa475aa22aa3f5dbe08bd013be4, 2 June 2026
Languages: Python (18)
Size: 34 files, 18 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: README
Not found: license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (16 files), pandas (13 files), PyTorch (8 files), Matplotlib (3 files), scikit-learn (3 files), seaborn (3 files)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
19 files

Code availability statement

The paper has a code availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:

Read it in the paper: doi.org/10.1038/s41467-026-76185-0.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 18 scripts, each with its path and the digest of its content;
  • 4 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

No dataset and no data link were found in the paper.

Data availability statement

The paper has a data availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:

  • no repository, dataset or request procedure was recognized in it

Read it in the paper: doi.org/10.1038/s41467-026-76185-0.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 19 authors, 2 keywords, 6 MeSH terms, 2 funders, 38 references.

Cite

This paper

Li, F., Yan, Z., Mao, J., Liu, G., Ren, H., Qin, B., Zhang, Z., Zhang, H., Shen, Y., Zheng, Z., Feng, W., Li, D., Tang, Y., Wang, S., Jin, Y., Luo, T., Wong, W.-f., Wang, H., & Zhu, B. (2026). Spinal-inspired artificial tactile interneuron with high-order burst spiking for intelligent edge interfaces. Nature communications, 17(1), 9285. https://doi.org/10.1038/s41467-026-76185-0

BibTeX

@article{li2026spinal,
author = {Li, Fanfan and Yan, Zhanglu and Mao, Jiayi and Liu, Guolei and Ren, Huihui and Qin, Bangbang and Zhang, Zhongfang and Zhang, Haiyue and Shen, Yiyang and Zheng, Zeqi and Feng, Weilong and Li, Dingwei and Tang, Yingjie and Wang, Saisai and Jin, Yaochu and Luo, Tao and Wong, Weng-fai and Wang, Hong and Zhu, Bowen},
title = {{Spinal-inspired artificial tactile interneuron with high-order burst spiking for intelligent edge interfaces}},
journal = {Nature communications},
year = {2026},
month = jul,
volume = {17},
number = {1},
pages = {9285},
publisher = {Nature Publishing Group},
issn = {2041-1723},
doi = {10.1038/s41467-026-76185-0},
url = {https://doi.org/10.1038/s41467-026-76185-0},
pmid = {42669703},
pmcid = {PMC13526877}
}

RIS

TY - JOUR
AU - Li, Fanfan
AU - Yan, Zhanglu
AU - Mao, Jiayi
AU - Liu, Guolei
AU - Ren, Huihui
AU - Qin, Bangbang
AU - Zhang, Zhongfang
AU - Zhang, Haiyue
AU - Shen, Yiyang
AU - Zheng, Zeqi
AU - Feng, Weilong
AU - Li, Dingwei
AU - Tang, Yingjie
AU - Wang, Saisai
AU - Jin, Yaochu
AU - Luo, Tao
AU - Wong, Weng-fai
AU - Wang, Hong
AU - Zhu, Bowen
TI - Spinal-inspired artificial tactile interneuron with high-order burst spiking for intelligent edge interfaces
T2 - Nature communications
J2 - Nat Commun
PY - 2026
DA - 2026/07/31
VL - 17
IS - 1
SP - 9285
SN - 2041-1723
PB - Nature Publishing Group
DO - 10.1038/s41467-026-76185-0
UR - https://doi.org/10.1038/s41467-026-76185-0
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41467-026-76185-0",
"type": "article-journal",
"title": "Spinal-inspired artificial tactile interneuron with high-order burst spiking for intelligent edge interfaces",
"container-title": "Nature communications",
"author": [
{
"family": "Li",
"given": "Fanfan"
},
{
"family": "Yan",
"given": "Zhanglu"
},
{
"family": "Mao",
"given": "Jiayi"
},
{
"family": "Liu",
"given": "Guolei"
},
{
"family": "Ren",
"given": "Huihui"
},
{
"family": "Qin",
"given": "Bangbang"
},
{
"family": "Zhang",
"given": "Zhongfang"
},
{
"family": "Zhang",
"given": "Haiyue"
},
{
"family": "Shen",
"given": "Yiyang"
},
{
"family": "Zheng",
"given": "Zeqi"
},
{
"family": "Feng",
"given": "Weilong"
},
{
"family": "Li",
"given": "Dingwei"
},
{
"family": "Tang",
"given": "Yingjie"
},
{
"family": "Wang",
"given": "Saisai"
},
{
"family": "Jin",
"given": "Yaochu"
},
{
"family": "Luo",
"given": "Tao"
},
{
"family": "Wong",
"given": "Weng-fai"
},
{
"family": "Wang",
"given": "Hong"
},
{
"family": "Zhu",
"given": "Bowen"
}
],
"container-title-short": "Nat Commun",
"volume": "17",
"issue": "1",
"page": "9285",
"DOI": "10.1038/s41467-026-76185-0",
"PMID": "42669703",
"PMCID": "PMC13526877",
"ISSN": "2041-1723",
"publisher": "Nature Publishing Group",
"URL": "https://doi.org/10.1038/s41467-026-76185-0",
"language": "en",
"issued": {
"date-parts": [
[
2026,
7,
31
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1038/s41467-026-75979-6 [code]
Artificial neural manifolds.
Journal: Nature communications
In common: scikit-learn, Matplotlib, NumPy, 4 references, 2 authors
[2] doi:10.1038/s41467-026-72428-2 [code]
Artificial plateau neurons with in-situ spike-malleability for rhythmic quadrupedal locomotion.
Journal: Nature communications
In common: PyTorch, pandas, NumPy, 3 references
[3] doi:10.1002/advs.202519479 [code]
Diminished Signal-to-Noise Ratio Disrupts Somatosensory Population Encoding and Drives Tactile Hyposensitivity in the Fmr1<sup>-/y</sup> Autism Model.
Journal: Advanced science (Weinheim, Baden-Wurttemberg, Germany)
In common: seaborn, scikit-learn, pandas, 2 other tools, cognitive, 1 reference
[4] doi:10.1038/s41597-026-07955-0 [code]
Human neuron activity during an 83-minute movie from 2,286 neurons and 29 patients.
Journal: Scientific data
In common: PyTorch, seaborn, pandas, 2 other tools, 1 reference
[5] doi:10.1038/s41593-026-02257-5 [code]
Neural sequences underlying directed turning in Caenorhabditis elegans.
Journal: Nature neuroscience
In common: PyTorch, seaborn, pandas, 2 other tools, 1 reference
[6] doi:10.1016/j.celrep.2026.117419 [code]
Conserved role of primary motor cortex in the control of prehension in mice and macaques.
Journal: Cell reports
In common: seaborn, scikit-learn, pandas, 2 other tools, 1 reference
[7] doi:10.3389/fnins.2026.1874302 [code]
Architecture-data matching for EEG-EMG decoding: compact deep models match classical spectral decoders on the WAY-EEG-GAL grasp-and-lift dataset.
Journal: Frontiers in neuroscience
In common: PyTorch, scikit-learn, pandas, 2 other tools, 1 reference
[8] doi:10.1038/s41467-026-73669-x [code]
Homeostatic dendritic neuron based on co-integrated volatile and non-volatile memristors for neuromorphic processing.
Journal: Nature communications
In common: PyTorch, scikit-learn, Matplotlib, 1 other tool, 1 reference
[9] doi:10.7554/elife.106554 [code]
Enhanced tactile coding in rat neocortex under darkness.
Journal: eLife
In common: PyTorch, scikit-learn, pandas, 1 other tool, 1 reference
[10] doi:10.1016/j.isci.2026.115375 [code]
Bursts of regional cortical inhibition during smartphone use.
Journal: iScience
In common: scikit-learn, pandas, Matplotlib, 1 other tool, 1 reference

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.