44sys .path .insert (0 , str (Path (__file__ ).parent .parent / "python_lib" ))
55
66import math
7+ import random
78import numpy as np
89from sklearn .datasets import fetch_openml
910from sklearn .model_selection import train_test_split
@@ -32,14 +33,10 @@ def to_one_hot(y, n_classes=10):
3233 return one_hot
3334
3435
35- def make_batches (x , y , batch_size , shuffle = True ):
36- n = x .shape [0 ]
37- indices = np .arange (n )
38- if shuffle :
39- np .random .shuffle (indices )
40- for start in range (0 , n , batch_size ):
41- batch_idx = indices [start : start + batch_size ]
42- yield x [batch_idx ], y [batch_idx ]
36+ def to_gpu (np_arr ):
37+ t = fromNumpy (np_arr )
38+ t .device = Device .CUDA
39+ return t
4340
4441
4542# ─── network ─────────────────────────────────────────────────────────────────
@@ -56,19 +53,25 @@ def make_net():
5653
5754# ─── training ────────────────────────────────────────────────────────────────
5855
59- def train_epoch (net , loss_fn , optim , x , y , batch_size = 64 ):
56+ def train_epoch (net , loss_fn , optim , x_gpu , y_gpu , batch_size = 64 ):
57+ n = x_gpu .dims [0 ]
58+
59+ # shuffle both tensors identically on the GPU via a random permutation of row indices
60+ indices = list (range (n ))
61+ random .shuffle (indices )
62+ x_shuf = x_gpu .slice (indices )
63+ y_shuf = y_gpu .slice (indices )
64+
6065 total_loss = 0.0
6166 n_batches = 0
62- max_batches = math .ceil (x .shape [0 ] / batch_size )
63- for xb , yb in make_batches (x , y , batch_size ):
64- xTensor = fromNumpy (xb )
65- xTensor .device = Device .CUDA
66-
67- yTensor = fromNumpy (yb )
68- yTensor .device = Device .CUDA
67+ max_batches = math .ceil (n / batch_size )
68+ for start in range (0 , n , batch_size ):
69+ end = min (start + batch_size , n )
70+ xb = x_shuf .slice (start , end )
71+ yb = y_shuf .slice (start , end )
6972
70- pred = net .forward (xTensor )
71- loss = loss_fn (yTensor , pred )
73+ pred = net .forward (xb )
74+ loss = loss_fn (yb , pred )
7275 loss .backward ()
7376
7477 optim .clipGradients (1.0 )
@@ -83,21 +86,17 @@ def train_epoch(net, loss_fn, optim, x, y, batch_size=64):
8386 return total_loss / n_batches
8487
8588
86- def evaluate (net , x , y , batch_size = 256 ):
89+ def evaluate (net , x_gpu , y_np , batch_size = 256 ):
90+ n = x_gpu .dims [0 ]
8791 correct = 0
88- total = 0
89- for xb , yb in make_batches (x , y , batch_size , shuffle = False ):
90- xTensor = fromNumpy (xb )
91- xTensor .device = Device .CUDA
92-
93- pred = net .forward (xTensor )
92+ for start in range (0 , n , batch_size ):
93+ end = min (start + batch_size , n )
94+ pred = net .forward (x_gpu .slice (start , end ))
9495 pred .device = Device .CPU
9596 pred_np = toNumpy (pred )
96-
9797 predicted = np .argmax (pred_np , axis = 1 )
98- correct += np .sum (predicted == np .argmax (yb , axis = 1 ))
99- total += len (yb )
100- return correct / total
98+ correct += np .sum (predicted == np .argmax (y_np [start :end ], axis = 1 ))
99+ return correct / n
101100
102101
103102# ─── main ────────────────────────────────────────────────────────────────────
@@ -112,14 +111,19 @@ def evaluate(net, x, y, batch_size=256):
112111
113112 print (f"Train: { x_train .shape } , Val: { x_val .shape } " )
114113
114+ # upload once; all batching and shuffling happens on the GPU
115+ x_train_gpu = to_gpu (x_train )
116+ y_train_gpu = to_gpu (y_train )
117+ x_val_gpu = to_gpu (x_val )
118+
115119 net = make_net ()
116120 loss_fn = CrossEntropyWithSoftmax ()
117121 optim = RmsProp (net .parameters (), 0.0001 , 0.999 )
118122
119123 n_epochs = 5
120124 for epoch in range (n_epochs ):
121- train_loss = train_epoch (net , loss_fn , optim , x_train , y_train )
122- val_acc = evaluate (net , x_val , y_val )
125+ train_loss = train_epoch (net , loss_fn , optim , x_train_gpu , y_train_gpu )
126+ val_acc = evaluate (net , x_val_gpu , y_val )
123127 print (
124128 f"Epoch { epoch + 1 } /{ n_epochs } "
125129 f"loss={ train_loss :.4f} "
0 commit comments