Када сам први пут наишао на идеју да користим логаритамски бројевни систем (ЛНС) у дубоком учењу, био сам заинтригиран, али и скептичан. Као и већина нас, одувек сам радио са аритметиком са плутајућим зарезом (ФП) — стандардом за нумеричко рачунање у дубоком учењу. ФП пружа добар баланс између прецизности и домета, али долази са компромисима: већом употребом меморије, повећаном сложеношћу рачунара и већом потрошњом енергије. Дакле, одлучио сам да експериментишем и уверим се – како се ЛНС пореди са ФП-ом када тренирате једноставан потпуно повезан вишеслојни перцептрон (МЛП) на МНИСТ-у? Зашто узети у обзир ЛНС? ЛНС представља бројеве у логаритамској скали, претварајући множења у сабирања, што може бити рачунарски јефтиније у одређеним хардверским архитектурама. Ова ефикасност долази по цену прецизности, посебно у операцијама сабирања и одузимања, које су сложеније у ЛНС-у. Међутим, потенцијалне предности — смањени меморијски отисак, бржи прорачуни и мања потрошња енергије — учинили су ме довољно радозналим да га испробам. Позадина: Флоатинг-Поинт вс. Логаритамски бројни систем Представљање у покретном зарезу (ФП). Аритметика са плутајућим зарезом је стандардна нумеричка репрезентација у већини оквира за дубоко учење, као што су ПиТорцх и ТенсорФлов. ФП бројеви имају: (одређивање позитивне или негативне вредности) Бит знака (фактор скалирања) Експонент (прецизност броја) Мантиса (значај) ФП32 (сингле прецисион) се обично користи у дубоком учењу, нудећи равнотежу између нумеричке прецизности и рачунарске ефикасности. Ефикаснији формати као што су ФП16 и БФ16 постају све популарнији како би убрзали обуку. Логаритамски систем бројева (ЛНС) ЛНС је алтернативни нумерички приказ где се бројеви чувају као логаритми: [ к = \лог_б (и) ] где је ( б ) база логаритма. ЛНС има неколико предности: : ( к_1 * к_2 = б^{(\лог_б к_1 + \лог_б к_2)}) Множење је поједностављено сабирањем : ( к_1 / к_2 = б^{(\лог_б к_1 - \лог_б к_2)}) Дељење је поједностављено на одузимање Функције експоненцијалног раста постају линеарне Међутим, сабирање и одузимање у ЛНС захтевају апроксимације, што доводи до смањене прецизности. ЛНС аритметичке операције Да бих даље истражио ЛНС, имплементирао сам основне аритметичке операције као што су сабирање, одузимање, множење и дељење користећи ЛНС интерне репрезентације. import torch import numpy as np import xlns as xl # Assuming xlns module is installed and provides xlnsnp # Function to convert floating-point numbers to xlns internal representation def float_to_internal(arr): xlns_data = xl.xlnsnp(arr) return xlns_data.nd # Function to convert xlns internal representation back to floating-point numbers def internal_to_float(internal_data): original_numbers = [] for value in internal_data: x = value // 2 s = value % 2 # Use x and s to create xlns object xlns_value = xl.xlns(0) xlns_value.x = x xlns_value.s = s original_numbers.append(float(xlns_value)) return original_numbers # Function to perform LNS addition using internal representation def lns_add_internal(x, y): max_part = torch.maximum(x, y) diff = torch.abs(x - y) adjust_term = torch.log1p(torch.exp(-diff)) return max_part + adjust_term # Function to perform LNS subtraction using internal representation def lns_sub_internal(x, y): return lns_add_internal(x, -y) # Function to perform LNS multiplication using internal representation def lns_mul_internal(x, y): return x + y # Function to perform LNS division using internal representation def lns_div_internal(x, y): return x - y # Input floating-point arrays x_float = [2.0, 3.0] y_float = [-1.0, 0.0] # Convert floating-point arrays to xlns internal representation x_internal = float_to_internal(x_float) y_internal = float_to_internal(y_float) # Create tensors from the internal representation tensor_x_nd = torch.tensor(x_internal, dtype=torch.int64) tensor_y_nd = torch.tensor(y_internal, dtype=torch.int64) # Perform the toy LNS addition on the internal representation result_add_internal = lns_add_internal(tensor_x_nd, tensor_y_nd) # Perform the toy LNS subtraction on the internal representation result_sub_internal = lns_sub_internal(tensor_x_nd, tensor_y_nd) # Perform the toy LNS multiplication on the internal representation result_mul_internal = lns_mul_internal(tensor_x_nd, tensor_y_nd) # Perform the toy LNS division on the internal representation result_div_internal = lns_div_internal(tensor_x_nd, tensor_y_nd) # Convert the internal results back to original floating-point values result_add_float = internal_to_float(result_add_internal.numpy()) result_sub_float = internal_to_float(result_sub_internal.numpy()) result_mul_float = internal_to_float(result_mul_internal.numpy()) result_div_float = internal_to_float(result_div_internal.numpy()) # Convert the results back to PyTorch tensors result_add_tensor = torch.tensor(result_add_float, dtype=torch.float32) result_sub_tensor = torch.tensor(result_sub_float, dtype=torch.float32) result_mul_tensor = torch.tensor(result_mul_float, dtype=torch.float32) result_div_tensor = torch.tensor(result_div_float, dtype=torch.float32) # Print results print("Input x:", x_float) print("Input y:", y_float) print("Addition Result:", result_add_float) print("Addition Result Tensor:", result_add_tensor) print("Subtraction Result:", result_sub_float) print("Subtraction Result Tensor:", result_sub_tensor) print("Multiplication Result:", result_mul_float) print("Multiplication Result Tensor:", result_mul_tensor) print("Division Result:", result_div_float) print("Division Result Tensor:", result_div_tensor) Ево рашчлањавања моје експерименталне имплементације логаритамског бројевног система (ЛНС). 1. Основни ЛНС концепт и изазови у ПиТорцх-у У ЛНС-у бројеви су представљени као логаритми, што претвара множење и дељење у сабирање и одузимање. Међутим, имплементација овога са ПиТорцх-ом представља специфичне изазове јер ПиТорцх тензори интерно користе репрезентације с помичним зарезом. Ово ствара неколико захтева: Одржавајте логаритамску репрезентацију током рачунања. Обезбедите нумеричку стабилност. Пажљиво поступајте са конверзијама. Управљајте интерним представљањем користећи две компоненте: : логаритамска вредност. к : бит знака (0 или 1). с 2. Интерно заступање и конверзија Први корак је претварање бројева са покретним зарезом у њихову интерну ЛНС репрезентацију. import torch import numpy as np import xl # Hypothetical external LNS library def float_to_internal(arr): xlns_data = xl.xlnsnp(arr) return xlns_data.nd # Convert floating-point arrays to xlns internal representation x_float = np.array([2.0, 3.0]) y_float = np.array([-1.0, 0.0]) x_internal = float_to_internal(x_float) y_internal = float_to_internal(y_float) # Create tensors from the internal representation tensor_x_nd = torch.tensor(x_internal, dtype=torch.int64) tensor_y_nd = torch.tensor(y_internal, dtype=torch.int64) Употреба је кључна јер: dtype=torch.int64 Чува тачну ЛНС интерну репрезентацију без грешака заокруживања у покретном зарезу. Пакује и логаритамску вредност и бит знака у један цео број. Спречава ненамерне операције са покретним зарезом од оштећења ЛНС репрезентације. 3. Основне аритметичке операције а) Множење def lns_mul_internal(x, y): return x + y Множење у ЛНС постаје сабирање: Ако је и , онда . a = log(x) b = log(y) log(x×y) = log(x) + log(y) б) Дивизија def lns_div_internal(x, y): return x - y Дељење постаје одузимање: . log(x/y) = log(x) - log(y) в) Сабирање def lns_add_internal(x, y): max_part = torch.maximum(x, y) diff = torch.abs(x - y) adjust_term = torch.log1p(torch.exp(-diff)) return max_part + adjust_term Сабирање је сложеније и нумерички осетљивије јер: Укључује експоненцијалне и логаритамске операције. Директна имплементација са помичним зарезом може довести до преливања/недовољног тока. Користи једначину: . log(x + y) = log(max(x,y)) + log(1 + exp(log(min(x,y)) - log(max(x,y)))) Користи уместо директног за бољу нумеричку стабилност. log1p log(1 + x) 4. Откуцајте Управљање безбедношћу и конверзијом def internal_to_float(internal_data): for value in internal_data: x = value // 2 # Integer division s = value % 2 # Integer modulo Цевовод конверзије одржава јасно раздвајање: Претвори из флоат → ЛНС интерна репрезентација (цели бројеви). Извршите ЛНС операције користећи целобројну аритметику. Претворите назад у плутајући само када је потребно. # Convert results back to float and tensor result_add_float = internal_to_float(result_add_internal.numpy()) result_add_tensor = torch.tensor(result_add_float, dtype=torch.float32) 5. Кључне предности и ограничења Предности на сабирање и одузимање. Множење и дељење су поједностављени са аритметиком фиксне тачке. Широк динамички опсег за одређене апликације. Потенцијално ефикасније Ограничења операције. Сабирање и одузимање су сложеније између помичног зареза и ЛНС-а. Прекомерни трошкови конверзије нултим и негативним бројевима. Захтева посебно руковање захтева пажљиво управљање типовима. Компатибилност ПиТорцх тензора 6. Могућности оптимизације Да бисте побољшали перформансе, могло би се: Имплементирајте за ЛНС операције. прилагођену ПиТорцх аутоград функцију Креирајте који изворно подржава ЛНС. прилагођени тип тензора Користите за ефикасне ЛНС операције на ГПУ-у. ЦУДА језгра Тренутна имплементација доноси практичне компромисе: Даје приоритет у односу на максималне перформансе. јасноћи и одржавању Користи ПиТорцх-а уз очување ЛНС тачности. постојећу инфраструктуру тензора Одржава кроз пажљиво управљање типовима. нумеричку стабилност Минимизира . конверзије између репрезентација 7. Пример тока података Следећи кораци показују комплетан цевовод користећи пример вредности и : [2.0, 3.0] [-1.0, 0.0] Конвертујте улазне вредности у ЛНС интерну репрезентацију. Креирајте тензоре целих бројева за чување ЛНС вредности. Извршити аритметичке операције у ЛНС домену. Конвертујте резултате назад у покретни зарез. Креирајте коначне ПиТорцх тензоре за даљу обраду. Ова имплементација успешно премошћује јаз између ПиТорцх-овог тензорског система са плутајућим зарезом и ЛНС аритметике уз одржавање нумеричке стабилности и тачности. Обука потпуно повезаног МЛП-а на скупу података МНИСТ Дигит са ФП и ЛНС Подешавање експеримента Обучио сам потпуно повезан МЛП на МНИСТ скупу података користећи и ФП и ЛНС репрезентације. Архитектура модела је била једноставна: 784 неурона (спљоштене слике 28к28) Улазни слој: Два слоја са 256 и 128 неурона, РеЛУ активације Скривени слојеви: 10 неурона (по један за сваку цифру, користећи софтмак) Излазни слој: Унакрсна ентропија Функција губитка: Адам Оптимизатор: За имплементацију ЛНС-а, морао сам да изађем из свог уобичајеног ПиТорцх тока посла. За разлику од ФП-а, који ПиТорцх изворно подржава, ПиТорцх не обезбеђује уграђене ЛНС операције. Пронашао сам ГитХуб пројекат под називом , који имплементира логаритамске репрезентације бројева и аритметику, што омогућава коришћење ЛНС-а у неуронским мрежама. xlns МЛП са плутајућим зарезом у ПиТорцх-у Почињемо са имплементацијом стандардног ФП-базираног потпуно повезаног МЛП-а користећи ПиТорцх: import torch import torch.nn as nn import torch.optim as optim import torchvision import torchvision.transforms as transforms import matplotlib.pyplot as plt import numpy as np import time # For calculating elapsed time # Define the multi-layer perceptron (MLP) model with one hidden layer class MLP(nn.Module): def __init__(self): super(MLP, self).__init__() # Input: 28*28 features; Hidden layer: 100 neurons; Output layer: 10 neurons self.fc1 = nn.Linear(28 * 28, 100) self.relu = nn.ReLU() self.fc2 = nn.Linear(100, 10) self.logsoftmax = nn.LogSoftmax(dim=1) # For stable outputs with NLLLoss def forward(self, x): # Flatten image: (batch_size, 1, 28, 28) -> (batch_size, 784) x = x.view(x.size(0), -1) x = self.fc1(x) x = self.relu(x) x = self.fc2(x) return self.logsoftmax(x) def train_and_validate(num_epochs=5, batch_size=64, learning_rate=0.01, split_ratio=0.8): # Set the device to GPU if available device = torch.device("cuda" if torch.cuda.is_available() else "cpu") print(f"Training on device: {device}") # Transformation for MNIST: convert to tensor and normalize transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,)) ]) # Load the MNIST training dataset train_dataset_full = torchvision.datasets.MNIST( root='./data', train=True, transform=transform, download=True ) # Split the dataset into training and validation sets n_total = len(train_dataset_full) n_train = int(split_ratio * n_total) n_val = n_total - n_train train_dataset, val_dataset = torch.utils.data.random_split(train_dataset_full, [n_train, n_val]) # Create DataLoaders for training and validation datasets train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True) val_loader = torch.utils.data.DataLoader(val_dataset, batch_size=batch_size, shuffle=False) # Initialize the model, loss function, and optimizer; move model to device model = MLP().to(device) criterion = nn.NLLLoss() optimizer = optim.SGD(model.parameters(), lr=learning_rate) # Lists to store training and validation accuracies for each epoch train_accuracies = [] val_accuracies = [] # Record the start time for measuring elapsed time start_time = time.time() # Training loop for epoch in range(num_epochs): model.train() running_loss = 0.0 correct_train = 0 total_train = 0 for inputs, labels in train_loader: # Move inputs and labels to device inputs, labels = inputs.to(device), labels.to(device) optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() # Compute running loss and training accuracy running_loss += loss.item() * inputs.size(0) _, predicted = torch.max(outputs.data, 1) total_train += labels.size(0) correct_train += (predicted == labels).sum().item() train_accuracy = 100.0 * correct_train / total_train train_accuracies.append(train_accuracy) # Evaluate on validation set model.eval() correct_val = 0 total_val = 0 with torch.no_grad(): for inputs, labels in val_loader: inputs, labels = inputs.to(device), labels.to(device) outputs = model(inputs) _, predicted = torch.max(outputs.data, 1) total_val += labels.size(0) correct_val += (predicted == labels).sum().item() val_accuracy = 100.0 * correct_val / total_val val_accuracies.append(val_accuracy) print(f"Epoch [{epoch+1}/{num_epochs}], Loss: {running_loss/total_train:.4f}, " f"Train Acc: {train_accuracy:.2f}%, Val Acc: {val_accuracy:.2f}%") # Calculate elapsed time elapsed_time = time.time() - start_time print(f"Training completed in {elapsed_time:.2f} seconds.") # Show sample predictions from the validation set show_predictions(model, val_loader, device) # Optional: plot training and validation accuracies epochs_arr = np.arange(1, num_epochs + 1) plt.figure(figsize=(10, 6)) plt.plot(epochs_arr, train_accuracies, label='Training Accuracy', marker='o') plt.plot(epochs_arr, val_accuracies, label='Validation Accuracy', marker='x') plt.xlabel('Epoch') plt.ylabel('Accuracy (%)') plt.title('Training and Validation Accuracies') plt.legend() plt.grid(True) plt.savefig('pytorch_accuracy.png') plt.show() def show_predictions(model, data_loader, device, num_images=6): """ Displays a few sample images from the data_loader along with the model's predictions. """ model.eval() images_shown = 0 plt.figure(figsize=(12, 8)) # Get one batch of images from the validation dataset for inputs, labels in data_loader: inputs, labels = inputs.to(device), labels.to(device) with torch.no_grad(): outputs = model(inputs) _, predicted = torch.max(outputs, 1) # Loop through the batch and plot images for i in range(inputs.size(0)): if images_shown >= num_images: break # Move the image to cpu and convert to numpy for plotting img = inputs[i].cpu().squeeze() plt.subplot(2, num_images // 2, images_shown + 1) plt.imshow(img, cmap='gray') plt.title(f"Pred: {predicted[i].item()}") plt.axis('off') images_shown += 1 if images_shown >= num_images: break plt.suptitle("Sample Predictions from the Validation Set") plt.tight_layout() plt.show() if __name__ == '__main__': train_and_validate(num_epochs=5, batch_size=64, learning_rate=0.01, split_ratio=0.8) Ова имплементација прати конвенционални цевовод дубоког учења где се множењем и сабирањем рукује помоћу ФП аритметике. Овде је детаљно објашњење ове ПиТорцх имплементације вишеслојног перцептрона (МЛП) за МНИСТ скуп података. Архитектура модела (МЛП класа): class MLP(nn.Module): def __init__(self): super(MLP, self).__init__() self.fc1 = nn.Linear(28 * 28, 100) # First fully connected layer self.relu = nn.ReLU() # Activation function self.fc2 = nn.Linear(100, 10) # Output layer self.logsoftmax = nn.LogSoftmax(dim=1) Пролаз унапред: def forward(self, x): x = x.view(x.size(0), -1) # Flatten: (batch_size, 1, 28, 28) -> (batch_size, 784) x = self.fc1(x) # First layer x = self.relu(x) # Activation x = self.fc2(x) # Output layer return self.logsoftmax(x) # Final activation Подешавање обуке: def train_and_validate(num_epochs=5, batch_size=64, learning_rate=0.01, split_ratio=0.8): device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # Data preprocessing transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,)) # Normalize to [-1, 1] ]) Кључне компоненте: Подршка за ГПУ кроз избор уређаја Нормализација података за бољу обуку Конфигурабилни хиперпараметри Управљање скупом података: train_dataset_full = torchvision.datasets.MNIST( root='./data', train=True, transform=transform, download=True ) # Split into train/validation n_train = int(split_ratio * n_total) train_dataset, val_dataset = torch.utils.data.random_split(train_dataset_full, [n_train, n_val]) Преузима МНИСТ скуп података ако није присутан Дели податке у скупове за обуку (80%) и валидацију (20%) Петља за обуку: for epoch in range(num_epochs): model.train() for inputs, labels in train_loader: inputs, labels = inputs.to(device), labels.to(device) optimizer.zero_grad() # Clear gradients outputs = model(inputs) # Forward pass loss = criterion(outputs, labels)# Calculate loss loss.backward() # Backward pass optimizer.step() # Update weights Класична процедура тренинга: Нулти градијенти Пас напред Прорачун губитака Пас уназад Ажурирања тежине Валидација: model.eval() with torch.no_grad(): for inputs, labels in val_loader: outputs = model(inputs) _, predicted = torch.max(outputs.data, 1) total_val += labels.size(0) correct_val += (predicted == labels).sum().item() Кључне карактеристике: Модел је подешен на режим евалуације Није потребно израчунавање градијента Прорачун тачности Визуелизација: def show_predictions(model, data_loader, device, num_images=6): model.eval() plt.figure(figsize=(12, 8)) # Display predictions vs actual labels Приказује узорке предвиђања из скупа за валидацију Корисно за квалитативну процену Праћење учинка: # Training metrics train_accuracies.append(train_accuracy) val_accuracies.append(val_accuracy) # Plot learning curves plt.plot(epochs_arr, train_accuracies, label='Training Accuracy') plt.plot(epochs_arr, val_accuracies, label='Validation Accuracy') Прати обуку и тачност валидације Исцртава криве учења Мери време тренинга Ово пружа солидну основу за поређење са имплементацијама заснованим на ЛНС-у, јер имплементира све стандардне компоненте цевовода дубоког учења користећи традиционалну аритметику са покретним зарезом. Логаритамски систем бројева (ЛНС) МЛП За ЛНС, морамо да користимо библиотеку. За разлику од ФП, ЛНС замењује операције тешке множења сабирањем у логаритамском домену. Међутим, ПиТорцх изворно то не подржава, тако да морамо ручно да применимо ЛНС операције где је то могуће. xlns import numpy as np import matplotlib.pyplot as plt import os import time import argparse import xlns as xl from tensorflow.keras.datasets import mnist # Use Keras's MNIST loader # If you are using fractional normalized LNS, make sure the following are uncommented import xlnsconf.xlnsudFracnorm xlnsconf.xlnsudFracnorm.ilog2 = xlnsconf.xlnsudFracnorm.ipallog2 xlnsconf.xlnsudFracnorm.ipow2 = xlnsconf.xlnsudFracnorm.ipalpow2 # Set global parameter in xlns xl.xlnssetF(10) def softmax(inp): max_vals = inp.max(axis=1) max_vals = xl.reshape(max_vals, (xl.size(max_vals), 1)) u = xl.exp(inp - max_vals) v = u.sum(axis=1) v = v.reshape((xl.size(v), 1)) u = u / v return u def main(main_params): print("arbitrary base np LNS. Also xl.hstack, xl routines in softmax") print("testing new softmax and * instead of @ for delta") print("works with type " + main_params['type']) is_training = bool(main_params['is_training']) leaking_coeff = float(main_params['leaking_coeff']) batchsize = int(main_params['minibatch_size']) lr = float(main_params['learning_rate']) num_epoch = int(main_params['num_epoch']) _lambda = float(main_params['lambda']) ones = np.array(list(np.ones((batchsize, 1)))) if is_training: # Load the MNIST dataset from Keras (x_train, y_train), (x_test, y_test) = mnist.load_data() # Normalize images to [0, 1] x_train = x_train.astype(np.float64) / 255.0 x_test = x_test.astype(np.float64) / 255.0 # One-hot encode the labels (assume 10 classes for MNIST digits 0-9) num_classes = 10 y_train = np.eye(num_classes)[y_train] y_test = np.eye(num_classes)[y_test] # Flatten the images from (28, 28) to (784,) x_train = x_train.reshape(x_train.shape[0], -1) x_test = x_test.reshape(x_test.shape[0], -1) # Use a portion of the training data for validation (the 'split' index) split = int(main_params['split']) x_val = x_train[split:] y_val = y_train[split:] x_train = x_train[:split] y_train = y_train[:split] # If available, load pretrained weights; otherwise, initialize new random weights. if os.path.isfile("./weightin.npz"): print("using ./weightin.npz") randfile = np.load("./weightin.npz", "r") W1 = randfile["W1"] W2 = randfile["W2"] randfile.close() else: print("using new random weights") # Note: The input layer now has 785 neurons (784 features + 1 bias). W1 = np.array(list(np.random.normal(0, 0.1, (785, 100)))) # The first hidden layer has 100 neurons; add bias so 101 W2 = np.array(list(np.random.normal(0, 0.1, (101, 10)))) np.savez_compressed("./weightout.npz", W1=W1, W2=W2) delta_W1 = np.array(list(np.zeros(W1.shape))) delta_W2 = np.array(list(np.zeros(W2.shape))) # Convert weights to desired type (xlns variants or float) if main_params['type'] == 'xlnsnp': lnsW1 = xl.xlnsnp(np.array(xl.xlnscopy(list(W1)))) lnsW2 = xl.xlnsnp(np.array(xl.xlnscopy(list(W2)))) lnsones = xl.xlnsnp(np.array(xl.xlnscopy(list(np.ones((batchsize, 1)))))) lnsdelta_W1 = xl.xlnsnp(np.array(xl.xlnscopy(list(np.zeros(W1.shape))))) lnsdelta_W2 = xl.xlnsnp(np.array(xl.xlnscopy(list(np.zeros(W2.shape))))) elif main_params['type'] == 'xlnsnpv': lnsW1 = xl.xlnsnpv(np.array(xl.xlnscopy(list(W1))), 6) lnsW2 = xl.xlnsnpv(np.array(xl.xlnscopy(list(W2))), 6) lnsones = xl.xlnsnpv(np.array(xl.xlnscopy(list(np.ones((batchsize, 1)))))) lnsdelta_W1 = xl.xlnsnpv(np.array(xl.xlnscopy(list(np.zeros(W1.shape))))) lnsdelta_W2 = xl.xlnsnpv(np.array(xl.xlnscopy(list(np.zeros(W2.shape))))) elif main_params['type'] == 'xlnsnpb': lnsW1 = xl.xlnsnpb(np.array(xl.xlnscopy(list(W1))), 2**2**-6) lnsW2 = xl.xlnsnpb(np.array(xl.xlnscopy(list(W2))), 2**2**-6) lnsones = xl.xlnsnpb(np.array(xl.xlnscopy(list(np.ones((batchsize, 1))))), 2**2**-xl.xlnsF) lnsdelta_W1 = xl.xlnsnpb(np.array(xl.xlnscopy(list(np.zeros(W1.shape)))), 2**2**-xl.xlnsF) lnsdelta_W2 = xl.xlnsnpb(np.array(xl.xlnscopy(list(np.zeros(W2.shape)))), 2**2**-xl.xlnsF) elif main_params['type'] == 'xlns': lnsW1 = np.array(xl.xlnscopy(list(W1))) lnsW2 = np.array(xl.xlnscopy(list(W2))) lnsones = np.array(xl.xlnscopy(list(np.ones((batchsize, 1))))) lnsdelta_W1 = np.array(xl.xlnscopy(list(np.zeros(W1.shape)))) lnsdelta_W2 = np.array(xl.xlnscopy(list(np.zeros(W2.shape)))) elif main_params['type'] == 'xlnsud': lnsW1 = np.array(xl.xlnscopy(list(W1), xl.xlnsud)) lnsW2 = np.array(xl.xlnscopy(list(W2), xl.xlnsud)) lnsones = np.array(xl.xlnscopy(list(np.ones((batchsize, 1))), xl.xlnsud)) lnsdelta_W1 = np.array(xl.xlnscopy(list(np.zeros(W1.shape)), xl.xlnsud)) lnsdelta_W2 = np.array(xl.xlnscopy(list(np.zeros(W2.shape)), xl.xlnsud)) elif main_params['type'] == 'xlnsv': lnsW1 = np.array(xl.xlnscopy(list(W1), xl.xlnsv, 6)) lnsW2 = np.array(xl.xlnscopy(list(W2), xl.xlnsv, 6)) lnsones = np.array(xl.xlnscopy(list(np.ones((batchsize, 1))), xl.xlnsv)) lnsdelta_W1 = np.array(xl.xlnscopy(list(np.zeros(W1.shape)), xl.xlnsv)) lnsdelta_W2 = np.array(xl.xlnscopy(list(np.zeros(W2.shape)), xl.xlnsv)) elif main_params['type'] == 'xlnsb': lnsW1 = np.array(xl.xlnscopy(list(W1), xl.xlnsb, 2**2**-6)) lnsW2 = np.array(xl.xlnscopy(list(W2), xl.xlnsb, 2**2**-6)) lnsones = np.array(xl.xlnscopy(list(np.ones((batchsize, 1))), xl.xlnsb, 2**2**-xl.xlnsF)) lnsdelta_W1 = np.array(xl.xlnscopy(list(np.zeros(W1.shape)), xl.xlnsb, 2**2**-xl.xlnsF)) lnsdelta_W2 = np.array(xl.xlnscopy(list(np.zeros(W2.shape)), xl.xlnsb, 2**2**-xl.xlnsF)) elif main_params['type'] == 'float': lnsW1 = np.array(list(W1)) lnsW2 = np.array(list(W2)) lnsones = np.array(list(np.ones((batchsize, 1)))) lnsdelta_W1 = np.array(list(np.zeros(W1.shape))) lnsdelta_W2 = np.array(list(np.zeros(W2.shape))) performance = {} performance['lnsacc_train'] = np.zeros(num_epoch) performance['lnsacc_val'] = np.zeros(num_epoch) start_time = time.process_time() # Training loop for epoch in range(num_epoch): print('At Epoch %d:' % (1 + epoch)) # Loop through training batches for mbatch in range(int(split / batchsize)): start = mbatch * batchsize x = np.array(x_train[start:(start + batchsize)]) y = np.array(y_train[start:(start + batchsize)]) # At this point, each x is already flattened (batchsize x 784) # Conversion based on type if main_params['type'] == 'xlnsnp': lnsx = xl.xlnsnp(np.array(xl.xlnscopy(np.array(x, dtype=np.float64)))) lnsy = xl.xlnsnp(np.array(xl.xlnscopy(np.array(y, dtype=np.float64)))) elif main_params['type'] == 'xlnsnpv': lnsx = xl.xlnsnpv(np.array(xl.xlnscopy(np.array(x, dtype=np.float64)))) lnsy = xl.xlnsnpv(np.array(xl.xlnscopy(np.array(y, dtype=np.float64)))) elif main_params['type'] == 'xlnsnpb': lnsx = xl.xlnsnpb(np.array(xl.xlnscopy(np.array(x, dtype=np.float64))), 2**2**-xl.xlnsF) lnsy = xl.xlnsnpb(np.array(xl.xlnscopy(np.array(y, dtype=np.float64))), 2**2**-xl.xlnsF) elif main_params['type'] == 'xlns': lnsx = np.array(xl.xlnscopy(np.array(x, dtype=np.float64))) lnsy = np.array(xl.xlnscopy(np.array(y, dtype=np.float64))) elif main_params['type'] == 'xlnsud': lnsx = np.array(xl.xlnscopy(np.array(x, dtype=np.float64), xl.xlnsud)) lnsy = np.array(xl.xlnscopy(np.array(y, dtype=np.float64), xl.xlnsud)) elif main_params['type'] == 'xlnsv': lnsx = np.array(xl.xlnscopy(np.array(x, dtype=np.float64), xl.xlnsv)) lnsy = np.array(xl.xlnscopy(np.array(y, dtype=np.float64), xl.xlnsv)) elif main_params['type'] == 'xlnsb': lnsx = np.array(xl.xlnscopy(np.array(x, dtype=np.float64), xl.xlnsv, 2**2**-xl.xlnsF)) lnsy = np.array(xl.xlnscopy(np.array(y, dtype=np.float64), xl.xlnsv, 2**2**-xl.xlnsF)) elif main_params['type'] == 'float': lnsx = np.array(x, dtype=np.float64) lnsy = np.array(y, dtype=np.float64) # Concatenate the bias "ones" with input features for the first layer lnss1 = xl.hstack((lnsones, lnsx)) @ lnsW1 lnsmask = (lnss1 > 0) + (leaking_coeff * (lnss1 < 0)) lnsa1 = lnss1 * lnsmask lnss2 = xl.hstack((lnsones, lnsa1)) @ lnsW2 lnsa2 = softmax(lnss2) lnsgrad_s2 = (lnsa2 - lnsy) / batchsize lnsgrad_a1 = lnsgrad_s2 @ xl.transpose(lnsW2[1:]) lnsdelta_W2 = xl.transpose(xl.hstack((lnsones, lnsa1))) * lnsgrad_s2 lnsgrad_s1 = lnsmask * lnsgrad_a1 lnsdelta_W1 = xl.transpose(xl.hstack((lnsones, lnsx))) * lnsgrad_s1 lnsW2 -= (lr * (lnsdelta_W2 + (_lambda * lnsW2))) lnsW1 -= (lr * (lnsdelta_W1 + (_lambda * lnsW1))) print('#= ', split, ' batch=', batchsize, ' lr=', lr) lnscorrect_count = 0 # Evaluate accuracy on training set for mbatch in range(int(split / batchsize)): start = mbatch * batchsize x = x_train[start:(start + batchsize)] y = y_train[start:(start + batchsize)] if main_params['type'] == 'xlnsnp': lnsx = xl.xlnsnp(np.array(xl.xlnscopy(np.array(x, dtype=np.float64)))) elif main_params['type'] == 'xlnsnpv': lnsx = xl.xlnsnpv(np.array(xl.xlnscopy(np.array(x, dtype=np.float64)))) elif main_params['type'] == 'xlnsnpb': lnsx = xl.xlnsnpb(np.array(xl.xlnscopy(np.array(x, dtype=np.float64))), 2**2**-xl.xlnsF) elif main_params['type'] == 'xlns': lnsx = np.array(xl.xlnscopy(np.array(x, dtype=np.float64))) elif main_params['type'] == 'xlnsud': lnsx = np.array(xl.xlnscopy(np.array(x, dtype=np.float64), xl.xlnsud)) elif main_params['type'] == 'xlnsv': lnsx = np.array(xl.xlnscopy(np.array(x, dtype=np.float64), xl.xlnsv)) elif main_params['type'] == 'xlnsb': lnsx = np.array(xl.xlnscopy(np.array(x, dtype=np.float64), xl.xlnsv, 2**2**-xl.xlnsF)) elif main_params['type'] == 'float': lnsx = np.array(x, dtype=np.float64) lnss1 = xl.hstack((lnsones, lnsx)) @ lnsW1 lnsmask = (lnss1 > 0) + (leaking_coeff * (lnss1 < 0)) lnsa1 = lnss1 * lnsmask lnss2 = xl.hstack((lnsones, lnsa1)) @ lnsW2 lnscorrect_count += np.sum(np.argmax(y, axis=1) == xl.argmax(lnss2, axis=1)) lnsaccuracy = lnscorrect_count / split print("train-set accuracy at epoch %d: %f" % (1 + epoch, lnsaccuracy)) performance['lnsacc_train'][epoch] = 100 * lnsaccuracy lnscorrect_count = 0 # Evaluate on the validation set for mbatch in range(int(split / batchsize)): start = mbatch * batchsize x = x_val[start:(start + batchsize)] y = y_val[start:(start + batchsize)] if main_params['type'] == 'xlnsnp': lnsx = xl.xlnsnp(np.array(xl.xlnscopy(np.array(x, dtype=np.float64)))) elif main_params['type'] == 'xlnsnpv': lnsx = xl.xlnsnpv(np.array(xl.xlnscopy(np.array(x, dtype=np.float64)))) elif main_params['type'] == 'xlnsnpb': lnsx = xl.xlnsnpb(np.array(xl.xlnscopy(np.array(x, dtype=np.float64))), 2**2**-xl.xlnsF) elif main_params['type'] == 'xlns': lnsx = np.array(xl.xlnscopy(np.array(x, dtype=np.float64))) elif main_params['type'] == 'xlnsud': lnsx = np.array(xl.xlnscopy(np.array(x, dtype=np.float64), xl.xlnsud)) elif main_params['type'] == 'xlnsv': lnsx = np.array(xl.xlnscopy(np.array(x, dtype=np.float64), xl.xlnsv)) elif main_params['type'] == 'xlnsb': lnsx = np.array(xl.xlnscopy(np.array(x, dtype=np.float64), xl.xlnsv, 2**2**-xl.xlnsF)) elif main_params['type'] == 'float': lnsx = np.array(x, dtype=np.float64) lnss1 = xl.hstack((lnsones, lnsx)) @ lnsW1 lnsmask = (lnss1 > 0) + (leaking_coeff * (lnss1 < 0)) lnsa1 = lnss1 * lnsmask lnss2 = xl.hstack((lnsones, lnsa1)) @ lnsW2 lnscorrect_count += np.sum(np.argmax(y, axis=1) == xl.argmax(lnss2, axis=1)) lnsaccuracy = lnscorrect_count / split print("Val-set accuracy at epoch %d: %f" % (1 + epoch, lnsaccuracy)) performance['lnsacc_val'][epoch] = 100 * lnsaccuracy print("elapsed time=" + str(time.process_time() - start_time)) fig = plt.figure(figsize=(16, 9)) ax = fig.add_subplot(111) x_axis = range(1, 1 + performance['lnsacc_train'].size) ax.plot(x_axis, performance['lnsacc_train'], 'y') ax.plot(x_axis, performance['lnsacc_val'], 'm') ax.set_xlabel('Number of Epochs') ax.set_ylabel('Accuracy') plt.suptitle(main_params['type'] + ' ' + str(split) + ' Validation and Training MNIST Accuracies F=' + str(xl.xlnsF), fontsize=14) ax.legend(['train', 'validation']) plt.grid(which='both', axis='both', linestyle='-.') plt.savefig('genericaccuracy.png') plt.show() # Now, show predictions on a few test images num_examples = 5 # Number of test images to display selected_indices = np.arange(num_examples) # choose the first few images for demo x_sample = x_test[selected_indices] y_sample = y_test[selected_indices] # For prediction, create a bias vector matching the sample size ones_sample = np.ones((x_sample.shape[0], 1)) z1_sample = np.hstack((ones_sample, x_sample)) @ lnsW1 mask_sample = (z1_sample > 0) + (leaking_coeff * (z1_sample < 0)) a1_sample = z1_sample * mask_sample z2_sample = np.hstack((ones_sample, a1_sample)) @ lnsW2 pred_probs = softmax(z2_sample) predictions = np.argmax(pred_probs, axis=1) true_labels = np.argmax(y_sample, axis=1) # Plot each test image along with its prediction and true label plt.figure(figsize=(10, 2)) for i in range(num_examples): plt.subplot(1, num_examples, i + 1) # Reshape the flattened image back to 28x28 for display plt.imshow(x_sample[i].reshape(28, 28), cmap='gray') plt.title(f"Pred: {predictions[i]}\nTrue: {true_labels[i]}") plt.axis('off') plt.tight_layout() plt.show() if __name__ == '__main__': # In a Kaggle notebook, set parameters manually using a dictionary. main_params = { 'is_training': True, 'split': 50, 'learning_rate': 0.01, 'lambda': 0.000, 'minibatch_size': 1, 'num_epoch': 5, 'leaking_coeff': 0.0078125, 'type': 'float' } main(main_params) Провешћу вас кроз овај код који имплементира вишеслојни перцептрон (МЛП) система логаритамских бројева (ЛНС) за МНИСТ класификацију цифара. Дозволите ми да га поделим на кључне одељке: Подешавање и увоз: Код користи библиотеку за операције логаритамског бројевног система xlns Нуди више ЛНС варијанти (клнснп, клнснпв, клнсуд, итд.) за различите уступке прецизности и перформанси МНИСТ скуп података се учитава преко Кераса Основне функције: def softmax(inp): max_vals = inp.max(axis=1) max_vals = xl.reshape(max_vals, (xl.size(max_vals), 1)) u = xl.exp(inp - max_vals) v = u.sum(axis=1) v = v.reshape((xl.size(v), 1)) u = u / v return u Ово је нумерички стабилна софтмак имплементација прилагођена за ЛНС операције. Архитектура мреже: Улазни слој: 784 неурона (28к28 спљоштених МНИСТ слика) + 1 пристрасност = 785 Скривени слој: 100 неурона + 1 пристрасност = 101 Излазни слој: 10 неурона (један по цифри) Иницијализација тежине: Тежине се или учитавају из датотеке („веигхтин.нпз“) или се насумично иницијализирају Случајне тежине користе нормалну дистрибуцију са средња вредност=0, стд=0,1 Различите ЛНС варијанте захтевају различите методе иницијализације (клнснп, клнснпв, итд.) Петља за обуку: for epoch in range(num_epoch): for mbatch in range(int(split / batchsize)): # Forward pass lnss1 = xl.hstack((lnsones, lnsx)) @ lnsW1 lnsmask = (lnss1 > 0) + (leaking_coeff * (lnss1 < 0)) lnsa1 = lnss1 * lnsmask lnss2 = xl.hstack((lnsones, lnsa1)) @ lnsW2 lnsa2 = softmax(lnss2) # Backward pass lnsgrad_s2 = (lnsa2 - lnsy) / batchsize lnsgrad_a1 = lnsgrad_s2 @ xl.transpose(lnsW2[1:]) lnsdelta_W2 = xl.transpose(xl.hstack((lnsones, lnsa1))) * lnsgrad_s2 lnsgrad_s1 = lnsmask * lnsgrad_a1 lnsdelta_W1 = xl.transpose(xl.hstack((lnsones, lnsx))) * lnsgrad_s1 Кључни аспекти обуке: Користи непропусну РеЛУ активацију (контролисано леакинг_цоефф) Имплементира стандардно ширење уназад, али са ЛНС операцијама Укључује Л2 регуларизацију (ламбда параметар) Ажурира тежине користећи градијентно спуштање са стопом учења 'лр' Евалуација: Прати и обуку и тачност валидације Исцртава криве учења које показују тачност током епоха Приказује узорке предвиђања на тестним сликама Хиперпараметри: main_params = { 'is_training': True, 'split': 50, 'learning_rate': 0.01, 'lambda': 0.000, 'minibatch_size': 1, 'num_epoch': 5, 'leaking_coeff': 0.0078125, 'type': 'float' } Користи спуштање низа у низу (подразумевана величина групе = 1) Имплементира рано заустављање кроз подјелу скупа валидације Коефицијент цурења РеЛУ је постављен на 0,0078125 Визуелизација: Прави графиконе који показују тачност обуке и валидације Приказује узорке тестних слика са предвиђањима и тачним ознакама Чува графикон тачности као 'генерицаццураци.пнг' Кључна иновација овде је употреба ЛНС аритметике која замењује множење са сабирањем у домену дневника, потенцијално нудећи бољу рачунарску ефикасност за одређене хардверске имплементације. Код подржава више ЛНС варијанти које омогућавају различите компромисе између прецизности и перформанси. Основно поређење перформанси Перформансе модела са помичним зарезом Training on device: cuda Epoch [1/5], Loss: 0.8540, Train Acc: 79.60%, Val Acc: 88.22% Epoch [2/5], Loss: 0.3917, Train Acc: 88.97%, Val Acc: 89.92% Epoch [3/5], Loss: 0.3380, Train Acc: 90.29%, Val Acc: 90.60% Epoch [4/5], Loss: 0.3104, Train Acc: 90.96%, Val Acc: 91.12% Epoch [5/5], Loss: 0.2901, Train Acc: 91.60%, Val Acc: 91.62% Training completed in 57.76 seconds. Перформансе модела логаритамског система бројева At Epoch 1: train-set accuracy at epoch 1: 52.00% Val-set accuracy at epoch 1: 24.00% At Epoch 2: train-set accuracy at epoch 2: 74.00% Val-set accuracy at epoch 2: 40.00% At Epoch 3: train-set accuracy at epoch 3: 86.00% Val-set accuracy at epoch 3: 58.00% At Epoch 4: train-set accuracy at epoch 4: 94.00% Val-set accuracy at epoch 4: 70.00% At Epoch 5: train-set accuracy at epoch 5: 96.00% Val-set accuracy at epoch 5: 68.00% elapsed time = 0.35 seconds. ФП вс. ЛНС: Кључна поређења Аспецт Покретни зарез (ФП) Логаритамски систем бројева (ЛНС) Време обуке 57.76с 0.35с Траин Аццураци 91,60% 96,00% Вал Аццураци 91,62% 68,00% Прецизност Високо Ниже (грешке апроксимације) Ефикасност меморије Већа употреба Мањи меморијски отисак Руковање множењем Нативно множење Поједностављења заснована на сабирању Закључак Компромис између и представља занимљиву студију случаја у ко-дизајну хардвера и софтвера за неуронске мреже. Док ЛНС нуди значајне предности у одређеним областима: логаритамског система бројева (ЛНС) аритметике са плутајућим зарезом (ФП) Брзина тренинга Замењује множење са сабирањем у домену евиденције Своди сложене операције на једноставнију аритметику Посебно ефикасан за множење матрица у неуронским мрежама Може постићи у неким имплементацијама 2–3 пута убрзање Предности меморије Обично је потребно за представљање бројева мање битова Може ефикасније компресовати тежине и активације Смањује захтеве за пропусни опсег меморије Мања за приступ меморији потрошња енергије Међутим, су значајни: изазови у вези са прецизношћу током акумулације малих вредности Губитак прецизности веома близу нуле Потешкоће у представљању бројева у прорачунима градијента Потенцијална нестабилност Може захтевати пажљиво подешавање хиперпараметара Футуре Дирецтионс Неколико обећавајућих приступа могло би побољшати применљивост ЛНС-а: 1. Аритметика специфична за слој Користите (попут коначне класификације) ФП за осетљиве слојеве Примените ЛНС у скривеним слојевима са тешким рачунима на основу нумеричких захтева Динамички пребацивање 2. Прецизно прилагодљиво рачунарство за стабилност Почните да тренирате са ФП како се тежине приближавају Постепено пређите на ЛНС у већој прецизности Одржавајте критичне путање 3. Ко-дизајн хардвера са ФП и ЛНС јединицама Прилагођени акцелератори између аритметичких типова Паметно распоређивање за сваки формат Специјализоване меморијске хијерархије 4. Алгоритамске иновације оптимизоване за ЛНС Нове функције активације који одржавају стабилност Модификовани алгоритми оптимизације Хибридне репрезентације бројева Потенцијална подршка за ПиТорцх Да би се ЛНС интегрисао у оквире дубоког учења, могло би се истражити следеће: 1. Прилагођене функције Аутоград Имплементирајте ЛНС операције као прилагођене аутоград функције Одржавајте рачунање градијента у домену дневника Обезбедите за убрзање ефикасне ЦУДА кернеле 2. Екстензије типа броја Додајте изворне типове ЛНС тензора Имплементирајте (*+, -, ) у домену евиденције основне операције , / Обезбедите у/из помичног зареза услужне програме за конверзију 3. Модификације слоја Креирајте (Линеар, Цонв2д) ЛНС верзије уобичајених слојева Оптимизујте за ЛНС рачунање пролазе уназад Подржите мешовиту прецизну обуку Заједница дубоког учења могла би имати велике користи од интеграције ових могућности у главне оквире, омогућавајући . ефикасније неуронске мреже мале снаге и велике брзине Шта мислите о равнотежи између нумеричке прецизности и рачунарске ефикасности? Да ли сте наишли на специфичне случајеве употребе у којима би ЛНС могао бити посебно користан? Јавите ми своје мишљење о овоме. Референце [1] Г. Алсухли, ет ал., „Системи бројева за архитектуре дубоких неуронских мрежа: Преглед“, , 2023. арКсив:2307.05035 [2] М. Арнолд, Е. Цхестер, ет ал., „Тренинг неуронских мрежа користећи само приближни ЛНС АЛУ без табеле.“ , 2020, стр. 69–72. 31. међународна конференција о системима, архитектурама и процесорима специфичним за апликације, ИЕЕЕ ДОИ [3] О. Косхелева, ет ал., „Логаритамски бројевни систем је оптималан за АИ прорачуне: теоријско објашњење емпиријског успеха,” Рад [4] Д. Мииасхита, ет ал., „Конволуционе неуронске мреже које користе логаритамску репрезентацију података“, , мар 2016. арКсив:1603.01025 [5] Ј. Зхао ет ал., “ЛНС-Мадам: Лов-Прецисион Траининг ин Логаритхмиц Нумбер Систем Усинг Мултиплицативе Веигхт Упдате,” , вол. 71, бр. 12, стр. 3179–3190, децембар 2022. ИЕЕЕ Трансацтионс он Цомпутерс ДОИ