{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"language_info":{"name":"python","version":"3.10.14","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"colab":{"provenance":[],"gpuType":"T4"},"accelerator":"GPU","kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":3004,"databundleVersionId":861823,"sourceType":"competition"}],"dockerImageVersionId":30805,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"Загрузка MNIST","metadata":{"id":"xKGbmNAZMJDC","jupyter":{"source_hidden":true}}},{"cell_type":"code","source":"!pip install pyopencl\n\nimport numpy as np\nimport pandas as pd\nimport pyopencl as cl\nimport pyopencl.array as cl_array\nimport matplotlib.pyplot as plt\nimport time","metadata":{"colab":{"base_uri":"https://localhost:8080/","height":1000},"id":"AYs4hcCggkTh","outputId":"c0caefd4-834a-4a88-ae98-45517782e14e","trusted":true,"execution":{"iopub.status.busy":"2024-12-06T08:01:07.09938Z","iopub.execute_input":"2024-12-06T08:01:07.099682Z","iopub.status.idle":"2024-12-06T08:01:17.682149Z","shell.execute_reply.started":"2024-12-06T08:01:07.099656Z","shell.execute_reply":"2024-12-06T08:01:17.68131Z"}},"outputs":[{"name":"stdout","text":"Collecting pyopencl\n  Downloading pyopencl-2024.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (4.8 kB)\nRequirement already satisfied: numpy in /opt/conda/lib/python3.10/site-packages (from pyopencl) (1.26.4)\nRequirement already satisfied: platformdirs>=2.2 in /opt/conda/lib/python3.10/site-packages (from pyopencl) (3.11.0)\nRequirement already satisfied: pytools>=2024.1.5 in /opt/conda/lib/python3.10/site-packages (from pyopencl) (2024.1.16)\nRequirement already satisfied: typing-extensions>=4 in /opt/conda/lib/python3.10/site-packages (from pytools>=2024.1.5->pyopencl) (4.12.2)\nDownloading pyopencl-2024.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (645 kB)\n\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m645.4/645.4 kB\u001b[0m \u001b[31m8.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m:00:01\u001b[0m00:01\u001b[0m\n\u001b[?25hInstalling collected packages: pyopencl\nSuccessfully installed pyopencl-2024.3\n","output_type":"stream"},{"name":"stderr","text":"/opt/conda/lib/python3.10/site-packages/pytools/persistent_dict.py:52: RecommendedHashNotFoundWarning: Unable to import recommended hash 'siphash24.siphash13', falling back to 'hashlib.sha256'. Run 'python3 -m pip install siphash24' to install the recommended hash.\n  warn(\"Unable to import recommended hash 'siphash24.siphash13', \"\n","output_type":"stream"}],"execution_count":1},{"cell_type":"code","source":"# Загрузка данных\nloaded_data = pd.read_csv('/kaggle/input/digit-recognizer/train.csv')\nloaded_data = np.array(loaded_data)\nm, n = loaded_data.shape\nnp.random.shuffle(loaded_data)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-06T08:01:17.684117Z","iopub.execute_input":"2024-12-06T08:01:17.68452Z","iopub.status.idle":"2024-12-06T08:01:20.920935Z","shell.execute_reply.started":"2024-12-06T08:01:17.684491Z","shell.execute_reply":"2024-12-06T08:01:20.919985Z"}},"outputs":[],"execution_count":2},{"cell_type":"code","source":"test_data = loaded_data[0:1000].T\ny_test = test_data[0].T\nX_test = test_data[1:n].T\nX_test = X_test / 255\n\ntrain_data = loaded_data[1000:m].T\ny_train = train_data[0].T\nX_train = train_data[1:n].T\nX_train = X_train / 255\nX_train = X_train.astype('float32')\nprint (X_train.dtype)\nprint(train_data.shape)\nprint(X_train.shape)\nprint(y_train.shape)\n\n# One-hot кодирование y\ndef one_hot_encode(labels, num_classes):\n    encoded = np.zeros((labels.size, num_classes))\n    encoded[np.arange(labels.size), labels] = 1\n    return encoded\n\ny_train_encoded = one_hot_encode(y_train, 10)\ny_test_encoded = one_hot_encode(y_test, 10)\ny_train_encoded = y_train_encoded.astype('float32')\nprint(y_train_encoded[:10,])\n\nm = X_train.shape[0]\nprint(m)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-06T08:01:20.921932Z","iopub.execute_input":"2024-12-06T08:01:20.922186Z","iopub.status.idle":"2024-12-06T08:01:21.074247Z","shell.execute_reply.started":"2024-12-06T08:01:20.92216Z","shell.execute_reply":"2024-12-06T08:01:21.073401Z"}},"outputs":[{"name":"stdout","text":"float32\n(785, 41000)\n(41000, 784)\n(41000,)\n[[0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]\n [0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]\n [0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]\n [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]\n [0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]\n [0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]\n [0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]\n [0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]\n [0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]\n [0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]]\n41000\n","output_type":"stream"}],"execution_count":3},{"cell_type":"code","source":"# Гиперпараметры\ninput_size = X_train.shape[1]\nprint(input_size)\nhidden_size = 128\noutput_size = 10\nlearning_rate = 0.01\nbatch_size = 64","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-06T08:01:21.075409Z","iopub.execute_input":"2024-12-06T08:01:21.075721Z","iopub.status.idle":"2024-12-06T08:01:21.080497Z","shell.execute_reply.started":"2024-12-06T08:01:21.075693Z","shell.execute_reply":"2024-12-06T08:01:21.079581Z"}},"outputs":[{"name":"stdout","text":"784\n","output_type":"stream"}],"execution_count":4},{"cell_type":"code","source":"# Инициализация весов\nm = 1000\nX_train.flatten()\nnp.random.seed(42)\nW1 = (np.random.randn(input_size, hidden_size) * np.sqrt(1 / input_size)).astype('float32')\nW2 = (np.random.randn(hidden_size, output_size) * np.sqrt(1 / input_size)).astype('float32')\nb1 = np.zeros((1, hidden_size), dtype=np.float32)\nb2 = np.zeros((1, output_size), dtype=np.float32)\n\nW1_out = np.zeros((input_size, hidden_size), dtype=np.float32)\nW2_out = np.zeros((hidden_size, output_size), dtype=np.float32)\nb1_out = np.zeros((1, hidden_size), dtype=np.float32)\nb2_out = np.zeros((1, output_size), dtype=np.float32)\n\nZ1 = np.zeros((m, hidden_size), dtype=np.float32)\nA1 = np.zeros((m, hidden_size), dtype=np.float32)\nZ2 = np.zeros((m, output_size), dtype=np.float32)\nA2 = np.zeros((m, output_size), dtype=np.float32)\ndZ2 = np.zeros((m, output_size), dtype=np.float32)\ndW2 = np.zeros((hidden_size, output_size), dtype=np.float32)\ndb2 = np.zeros((1, output_size), dtype=np.float32)\ndA1 = np.zeros((m, hidden_size), dtype=np.float32)\ndZ1 = np.zeros((m, hidden_size), dtype=np.float32)\ndW1 = np.zeros((input_size, hidden_size), dtype=np.float32)\ndb1 = np.zeros((1, hidden_size), dtype=np.float32)\n\nZ1_biased = np.zeros((m, hidden_size), dtype=np.float32)\nZ1_relu_deriv = np.zeros((m, hidden_size), dtype=np.float32)\nZ2_biased = np.zeros((m, output_size), dtype=np.float32)\ndW2_mult = np.zeros((hidden_size, output_size), dtype=np.float32)\ndb2_mult = np.zeros((1, output_size), dtype=np.float32)\ndW1_mult = np.zeros((input_size, hidden_size), dtype=np.float32)\ndb1_mult = np.zeros((1, hidden_size), dtype=np.float32)\n\nrow_max = np.zeros((m, 1), dtype=np.float32)\nrow_sum_exp = np.zeros((m, 1), dtype=np.float32)\n\nA1_t = A1.T\nW2_t = W2.T\nX_train_t = X_train.T","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-06T08:36:52.015623Z","iopub.execute_input":"2024-12-06T08:36:52.015969Z","iopub.status.idle":"2024-12-06T08:36:52.096366Z","shell.execute_reply.started":"2024-12-06T08:36:52.01594Z","shell.execute_reply":"2024-12-06T08:36:52.095377Z"}},"outputs":[],"execution_count":72},{"cell_type":"code","source":"context = cl.create_some_context()\nqueue = cl.CommandQueue(context)\n\n# OpenCL-программа для умножения матриц\nprogram_src = \"\"\"\n__kernel void matrix_multiply(\n    const int M,\n    const int N,\n    const int P,\n    __global const float* A,\n    __global const float* B,\n    __global float* C)\n{\n    // Мы убрали индексы строк и столбцов. Вместо этого создаем циклы для умножения\n    for (int i = 0; i < M; i++) {\n        for (int j = 0; j < N; j++) {\n            float value = 0.0f;\n            // Перемножаем строки A и столбцы B\n            for (int k = 0; k < P; k++) {\n                value += A[i * P + k] * B[k * N + j];\n            }\n            // Записываем результат в матрицу C\n            C[i * N + j] = value;\n        }\n    }\n}\n\n__kernel void matrix_multiply_parallel(\n    const int M,                // Количество строк матрицы A и C\n    const int N,                // Количество столбцов матрицы B и C\n    const int P,                // Количество столбцов матрицы A и строк матрицы B\n    __global const float* A,    // Матрица A (размер M x P)\n    __global const float* B,    // Матрица B (размер P x N)\n    __global float* C           // Матрица C (результат, размер M x N)\n) {\n    // Размер блока\n    const int block_size = 32;\n\n    // Локальные индексы\n    int local_i = get_local_id(0);\n    int local_j = get_local_id(1);\n\n    // Глобальные индексы\n    int global_i = get_group_id(0) * block_size + local_i;\n    int global_j = get_group_id(1) * block_size + local_j;\n\n    // Локальные буферы для блока A и B\n    __local float local_A[block_size][block_size];\n    __local float local_B[block_size][block_size];\n\n    // Частичное произведение для текущего элемента C\n    float value = 0.0f;\n\n    // Итерация по блокам\n    for (int k = 0; k < (P + block_size - 1) / block_size; k++) {\n        // Загрузка блока A в локальную память\n        if (global_i < M && (k * block_size + local_j) < P) {\n            local_A[local_i][local_j] = A[global_i * P + k * block_size + local_j];\n        } else {\n            local_A[local_i][local_j] = 0.0f;\n        }\n\n        // Загрузка блока B в локальную память\n        if (global_j < N && (k * block_size + local_i) < P) {\n            local_B[local_i][local_j] = B[(k * block_size + local_i) * N + global_j];\n        } else {\n            local_B[local_i][local_j] = 0.0f;\n        }\n\n        // Барьер для синхронизации потоков в локальной группе\n        barrier(CLK_LOCAL_MEM_FENCE);\n\n        // Вычисляем произведение для текущего блока\n        for (int t = 0; t < block_size; t++) {\n            value += local_A[local_i][t] * local_B[t][local_j];\n        }\n\n        // Барьер для синхронизации перед следующим блоком\n        barrier(CLK_LOCAL_MEM_FENCE);\n    }\n\n    // Сохраняем результат в глобальную память\n    if (global_i < M && global_j < N) {\n        C[global_i * N + global_j] = value;\n    }\n}\n\n__kernel void elementwise_multiply(\n    const int rows,\n    const int cols,\n    __global const float* A,\n    __global const float* B,\n    __global float* C)\n{\n    // Циклы по строкам и столбцам\n    for (int i = 0; i < rows; i++) {\n        for (int j = 0; j < cols; j++) {\n            int idx = i * cols + j; // Индекс элемента\n            C[idx] = A[idx] * B[idx]; // Поэлементное умножение\n        }\n    }\n}\n\n__kernel void elementwise_multiply_parallel(\n    const int rows,\n    const int cols,\n    __global const float* A,\n    __global const float* B,\n    __global float* C)\n{\n    // Индекс элемента для распараллеливания\n    int i = get_global_id(0);\n    int j = get_global_id(1);\n    \n    // Проверяем, что индексы не выходят за пределы\n    if (i < rows && j < cols) {\n        int idx = i * cols + j; // Индекс элемента\n        C[idx] = A[idx] * B[idx]; // Поэлементное умножение\n    }\n}\n\n__kernel void add_bias(\n    const int rows,          // Количество строк в матрице\n    const int cols,          // Количество столбцов в матрице\n    __global const float* mat,  // Входная матрица\n    __global const float* bias, // Смещение (вектор)\n    __global float* result      // Результирующая матрица\n) {\n    // Вложенные циклы для обработки всех элементов матрицы\n    for (int i = 0; i < rows; i++) {\n        for (int j = 0; j < cols; j++) {\n            // Индекс текущего элемента\n            int index = i * cols + j;\n            // Добавляем смещение к элементу матрицы\n            result[index] = mat[index] + bias[j];\n        }\n    }\n}\n\n__kernel void add_bias_parallel(\n    const int rows,          // Количество строк в матрице\n    const int cols,          // Количество столбцов в матрице\n    __global const float* mat,  // Входная матрица\n    __global const float* bias, // Смещение (вектор)\n    __global float* result      // Результирующая матрица\n) {\n    // Индекс строки и столбца для распараллеливания\n    int i = get_global_id(0);\n    int j = get_global_id(1);\n\n    // Проверяем, что индексы не выходят за пределы\n    if (i < rows && j < cols) {\n        int index = i * cols + j;\n        result[index] = mat[index] + bias[j]; // Добавляем смещение\n    }\n}\n\n__kernel void relu(\n    const int rows,             // Количество строк матрицы\n    const int cols,             // Количество столбцов матрицы\n    __global const float* mat,  // Входная матрица\n    __global float* result      // Результирующая матрица\n) {\n    // Итерируемся по всем элементам матрицы\n    for (int i = 0; i < rows; i++) {\n        for (int j = 0; j < cols; j++) {\n            // Индекс текущего элемента\n            int idx = i * cols + j;\n            // Применяем функцию ReLU: max(0, mat[idx])\n            result[idx] = fmax(0.0f, mat[idx]);\n        }\n    }\n}\n\n__kernel void relu_parallel(\n    const int rows,             // Количество строк матрицы\n    const int cols,             // Количество столбцов матрицы\n    __global const float* mat,  // Входная матрица\n    __global float* result      // Результирующая матрица\n) {\n    // Индекс строки и столбца для распараллеливания\n    int i = get_global_id(0);\n    int j = get_global_id(1);\n\n    // Проверяем, что индексы не выходят за пределы\n    if (i < rows && j < cols) {\n        int idx = i * cols + j;\n        result[idx] = fmax(0.0f, mat[idx]); // Применяем ReLU\n    }\n}\n\n__kernel void relu_derivative(\n    const int rows,             // Количество строк матрицы\n    const int cols,             // Количество столбцов матрицы\n    __global const float* mat,  // Входная матрица\n    __global float* result      // Результирующая матрица (производные)\n) {\n    // Итерируемся по всем элементам матрицы\n    for (int i = 0; i < rows; i++) {\n        for (int j = 0; j < cols; j++) {\n            // Индекс текущего элемента\n            int idx = i * cols + j;\n            // Вычисляем производную ReLU\n            result[idx] = mat[idx] > 0.0f ? 1.0f : 0.0f;\n        }\n    }\n}\n\n__kernel void relu_derivative_parallel(\n    const int rows,             // Количество строк матрицы\n    const int cols,             // Количество столбцов матрицы\n    __global const float* mat,  // Входная матрица\n    __global float* result      // Результирующая матрица (производные)\n) {\n    // Индекс строки и столбца для распараллеливания\n    int i = get_global_id(0);\n    int j = get_global_id(1);\n\n    // Проверяем, что индексы не выходят за пределы\n    if (i < rows && j < cols) {\n        int idx = i * cols + j;\n        result[idx] = mat[idx] > 0.0f ? 1.0f : 0.0f; // Производная ReLU\n    }\n}\n\n__kernel void find_row_max(\n    const int rows,              // Количество строк в матрице\n    const int cols,              // Количество столбцов в матрице\n    __global const float* mat,   // Входная матрица\n    __global float* row_max     // Массив для хранения максимума каждой строки\n) {\n    for (int i = 0; i < rows; i++) {  // Итерируем по строкам\n        float max_val = -FLT_MAX; // Инициализируем максимум\n        for (int j = 0; j < cols; j++) {\n            int idx = i * cols + j;\n            if (mat[idx] > max_val) {\n                max_val = mat[idx];  // Обновляем максимум для строки\n            }\n        }\n        row_max[i] = max_val;  // Сохраняем максимум для строки\n    }\n}\n\n__kernel void find_row_max_parallel(\n    const int rows,              // Количество строк в матрице\n    const int cols,              // Количество столбцов в матрице\n    __global const float* mat,   // Входная матрица\n    __global float* row_max     // Массив для хранения максимума каждой строки\n) {\n    int i = get_global_id(0);  // Индекс текущей строки\n    if (i < rows) {  // Проверяем, что индекс в пределах допустимых строк\n        float max_val = -FLT_MAX; // Инициализируем максимум\n        for (int j = 0; j < cols; j++) {\n            int idx = i * cols + j;\n            if (mat[idx] > max_val) {\n                max_val = mat[idx];  // Обновляем максимум для строки\n            }\n        }\n        row_max[i] = max_val;  // Сохраняем максимум для строки\n    }\n}\n\n__kernel void compute_sum_exp(\n    const int rows,              // Количество строк в матрице\n    const int cols,              // Количество столбцов в матрице\n    __global const float* mat,   // Входная матрица\n    __global const float* row_max, // Массив с максимумами для строк\n    __global float* sum_exp     // Массив для хранения суммы экспонент\n) {\n    for (int i = 0; i < rows; i++) {  // Итерируем по строкам\n        float sum = 0.0f;  // Сумма экспонент для строки\n        for (int j = 0; j < cols; j++) {\n            int idx = i * cols + j;\n            sum += exp(mat[idx] - row_max[i]);  // Вычисляем экспоненту с учетом сдвига\n        }\n        sum_exp[i] = sum;  // Сохраняем сумму для строки\n    }\n}\n\n__kernel void compute_sum_exp_parallel(\n    const int rows,              // Количество строк в матрице\n    const int cols,              // Количество столбцов в матрице\n    __global const float* mat,   // Входная матрица\n    __global const float* row_max, // Массив с максимумами для строк\n    __global float* sum_exp     // Массив для хранения суммы экспонент\n) {\n    int i = get_global_id(0);  // Индекс текущей строки\n    if (i < rows) {  // Проверяем, что индекс в пределах допустимых строк\n        float sum = 0.0f;  // Сумма экспонент для строки\n        for (int j = 0; j < cols; j++) {\n            int idx = i * cols + j;\n            sum += exp(mat[idx] - row_max[i]);  // Вычисляем экспоненту с учетом сдвига\n        }\n        sum_exp[i] = sum;  // Сохраняем сумму для строки\n    }\n}\n\n__kernel void compute_softmax(\n    const int rows,              // Количество строк в матрице\n    const int cols,              // Количество столбцов в матрице\n    __global const float* mat,   // Входная матрица\n    __global const float* row_max,  // Массив с максимумами для строк\n    __global const float* sum_exp,  // Массив с суммами экспонент для строк\n    __global float* result       // Результирующая матрица\n) {\n    for (int i = 0; i < rows; i++) {  // Итерируем по строкам\n        for (int j = 0; j < cols; j++) {  // Итерируем по столбцам\n            int idx = i * cols + j;\n            float exp_value = exp(mat[idx] - row_max[i]);  // Вычисляем экспоненту с учетом сдвига\n            result[idx] = exp_value / sum_exp[i];  // Применяем softmax для элемента\n        }\n    }\n}\n\n__kernel void compute_softmax_parallel(\n    const int rows,              // Количество строк в матрице\n    const int cols,              // Количество столбцов в матрице\n    __global const float* mat,   // Входная матрица\n    __global const float* row_max,  // Массив с максимумами для строк\n    __global const float* sum_exp,  // Массив с суммами экспонент для строк\n    __global float* result       // Результирующая матрица\n) {\n    int i = get_global_id(0);  // Индекс текущей строки\n    int j = get_global_id(1);  // Индекс текущего столбца\n\n    if (i < rows && j < cols) {  // Проверяем, что индексы в пределах допустимых значений\n        int idx = i * cols + j;\n        float exp_value = exp(mat[idx] - row_max[i]);  // Вычисляем экспоненту с учетом сдвига\n        result[idx] = exp_value / sum_exp[i];  // Применяем softmax для элемента\n    }\n}\n\n__kernel void matrix_difference(\n    const int rows,               // Количество строк в матрицах\n    const int cols,               // Количество столбцов в матрицах\n    __global const float* mat1,   // Первая матрица\n    __global const float* mat2,   // Вторая матрица\n    __global float* result        // Результирующая матрица\n) {\n    for (int i = 0; i < rows; i++) {\n        for (int j = 0; j < cols; j++) {\n            int idx = i * cols + j;\n            result[idx] = mat1[idx] - mat2[idx]; // Разность элементов\n        }\n    }\n}\n\n__kernel void matrix_difference_parallel(\n    const int rows,               // Количество строк в матрицах\n    const int cols,               // Количество столбцов в матрицах\n    __global const float* mat1,   // Первая матрица\n    __global const float* mat2,   // Вторая матрица\n    __global float* result        // Результирующая матрица\n) {\n    int i = get_global_id(0);\n    int j = get_global_id(1);\n\n    if (i < rows && j < cols) {\n        int idx = i * cols + j;\n        result[idx] = mat1[idx] - mat2[idx]; // Разность элементов\n    }\n}\n\n__kernel void cross_entropy(\n    const int rows,               // Количество строк (образцы)\n    const int cols,               // Количество столбцов (классы)\n    __global const float* preds,  // Предсказания (вероятности, после softmax)\n    __global const float* labels, // Истинные метки (one-hot encoding)\n    __global float* result        // Результат (поэлементная кросс-энтропия)\n) {\n    for (int i = 0; i < rows; i++) {\n        float loss = 0.0f;\n        for (int j = 0; j < cols; j++) {\n            int idx = i * cols + j;\n            float p = fmax(preds[idx], 1e-7f); // Предотвращение log(0)\n            loss -= labels[idx] * log(p);     // Сумма для кросс-энтропии\n        }\n        result[i] = loss; // Сохраняем кросс-энтропию для строки\n    }\n}\n\n__kernel void cross_entropy_parallel(\n    const int rows,               // Количество строк (образцы)\n    const int cols,               // Количество столбцов (классы)\n    __global const float* preds,  // Предсказания (вероятности, после softmax)\n    __global const float* labels, // Истинные метки (one-hot encoding)\n    __global float* result        // Результат (поэлементная кросс-энтропия)\n) {\n    int i = get_global_id(0);  // Индекс текущей строки\n\n    if (i < rows) {  // Проверяем, что индекс в пределах допустимых значений\n        float loss = 0.0f;\n        for (int j = 0; j < cols; j++) {  // Итерируем по всем классам\n            int idx = i * cols + j;\n            float p = fmax(preds[idx], 1e-7f);  // Предотвращение log(0)\n            loss -= labels[idx] * log(p);       // Сумма для кросс-энтропии\n        }\n        result[i] = loss;  // Сохраняем кросс-энтропию для строки\n    }\n}\n\n__kernel void transpose(\n    const int rows,               // Количество строк в матрице\n    const int cols,               // Количество столбцов в матрице\n    __global const float* mat,    // Входная матрица\n    __global float* result        // Результирующая матрица\n) {\n    for (int i = 0; i < rows; i++) {\n        for (int j = 0; j < cols; j++) {\n            int idx_in = i * cols + j;\n            int idx_out = j * rows + i;\n            result[idx_out] = mat[idx_in]; // Транспонируем элементы\n        }\n    }\n}\n\n__kernel void transpose_parallel(\n    const int rows,               // Количество строк в матрице\n    const int cols,               // Количество столбцов в матрице\n    __global const float* mat,    // Входная матрица\n    __global float* result        // Результирующая матрица\n) {\n    int i = get_global_id(0);  // Индекс строки\n    int j = get_global_id(1);  // Индекс столбца\n\n    if (i < rows && j < cols) {  // Проверяем, что индексы в пределах матрицы\n        int idx_in = i * cols + j;\n        int idx_out = j * rows + i;\n        result[idx_out] = mat[idx_in]; // Транспонируем элементы\n    }\n}\n\n__kernel void column_sum(\n    const int rows,               // Количество строк в матрице\n    const int cols,               // Количество столбцов в матрице\n    __global const float* mat,    // Входная матрица\n    __global float* result        // Результат (сумма столбцов)\n) {\n    for (int j = 0; j < cols; j++) {\n        float sum = 0.0f;\n        for (int i = 0; i < rows; i++) {\n            int idx = i * cols + j;\n            sum += mat[idx]; // Суммируем элементы в столбце\n        }\n        result[j] = sum; // Записываем сумму столбца\n    }\n}\n\n__kernel void column_sum_parallel(\n    const int rows,               // Количество строк в матрице\n    const int cols,               // Количество столбцов в матрице\n    __global const float* mat,    // Входная матрица\n    __global float* result        // Результат (сумма столбцов)\n) {\n    int j = get_global_id(0);  // Индекс столбца\n\n    if (j < cols) {  // Проверяем, что индекс в пределах столбца\n        float sum = 0.0f;\n        for (int i = 0; i < rows; i++) {\n            int idx = i * cols + j;\n            sum += mat[idx]; // Суммируем элементы в столбце\n        }\n        result[j] = sum; // Записываем сумму столбца\n    }\n}\n\n__kernel void mult_by_scalar(\n    const int rows,               // Количество строк в матрице\n    const int cols,               // Количество столбцов в матрице\n    __global const float* mat,    // Входная матрица\n    const float scalar,           // Число, на которое будем делить\n    __global float* result        // Результат (матрица после деления)\n) {\n    for (int i = 0; i < rows; i++) {\n        for (int j = 0; j < cols; j++) {\n            int idx = i * cols + j;\n            result[idx] = mat[idx] * scalar; // Умножаем элементы на число\n        }\n    }\n}\n\n__kernel void mult_by_scalar_parallel(\n    const int rows,               // Количество строк в матрице\n    const int cols,               // Количество столбцов в матрице\n    __global const float* mat,    // Входная матрица\n    const float scalar,           // Число, на которое будем умножать\n    __global float* result        // Результат (матрица после умножения)\n) {\n    int i = get_global_id(0);  // Индекс строки\n    int j = get_global_id(1);  // Индекс столбца\n\n    if (i < rows && j < cols) {  // Проверяем, что индексы в пределах матрицы\n        int idx = i * cols + j;\n        result[idx] = mat[idx] * scalar; // Умножаем элементы на число\n    }\n}\n\n__kernel void divide_by_scalar(\n    const int rows,               // Количество строк в матрице\n    const int cols,               // Количество столбцов в матрице\n    __global const float* mat,    // Входная матрица\n    const float scalar,           // Число, на которое будем делить\n    __global float* result        // Результат (матрица после деления)\n) {\n    for (int i = 0; i < rows; i++) {\n        for (int j = 0; j < cols; j++) {\n            int idx = i * cols + j;\n            result[idx] = mat[idx] / scalar; // Делим элементы на число\n        }\n    }\n}\n\n__kernel void divide_by_scalar_parallel(\n    const int rows,               // Количество строк в матрице\n    const int cols,               // Количество столбцов в матрице\n    __global const float* mat,    // Входная матрица\n    const float scalar,           // Число, на которое будем делить\n    __global float* result        // Результат (матрица после деления)\n) {\n    int i = get_global_id(0);  // Индекс строки\n    int j = get_global_id(1);  // Индекс столбца\n\n    if (i < rows && j < cols) {  // Проверяем, что индексы в пределах матрицы\n        int idx = i * cols + j;\n        result[idx] = mat[idx] / scalar; // Делим элементы на число\n    }\n}\n\n__kernel void train_model(\n    __global const float *X_train, __global float *W1, __global float *W2,\n    __global float *b1, __global float *b2,\n    __global float *W1_out, __global float *W2_out,\n    __global float *b1_out, __global float *b2_out,\n    __global float *Z1, __global float *A1, __global float *Z2, __global float *A2,\n    __global float *dZ2, __global float *dW2, __global float *db2, __global float *dA1,\n    __global float *dZ1, __global float *dW1, __global float *db1,\n    __global float *A1_t, __global float *W2_t, __global float *X_train_t,\n    __global float *Z1_biased, __global float *Z1_relu_deriv, __global float *Z2_biased,\n    __global float *dW2_mult, __global float *db2_mult, \n    __global float *dW1_mult, __global float *db1_mult,\n    __global float *row_max, __global float *row_sum_exp,\n    __global const float *y_train_encoded,\n    __global float *losses, const unsigned int m,\n    const unsigned int input_size, const unsigned int hidden_size, const unsigned int output_size,\n    const float learning_rate)\n{\n    // Прямой проход\n    matrix_multiply_parallel(m, hidden_size, input_size, X_train, W1, Z1);  // Z1 = X_train * W1\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    add_bias_parallel(m, hidden_size, Z1, b1, Z1_biased);  \n    barrier(CLK_GLOBAL_MEM_FENCE);\n    relu_parallel(m, hidden_size, Z1_biased, A1);  // A1 = ReLU(Z1)\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    \n    matrix_multiply_parallel(m, output_size, hidden_size, A1, W2, Z2);  // Z2 = A1 * W2\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    add_bias_parallel(m, output_size, Z2, b2, Z2_biased);\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    find_row_max_parallel(m, output_size, Z2_biased, row_max);\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    compute_sum_exp_parallel(m, output_size, Z2_biased, row_max, row_sum_exp);\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    compute_softmax_parallel(m, output_size, Z2_biased, row_max, row_sum_exp, A2);\n    barrier(CLK_GLOBAL_MEM_FENCE);\n\n    // Вычисление потерь (кросс-энтропия)\n    cross_entropy_parallel(m, output_size, A2, y_train_encoded, losses);\n    barrier(CLK_GLOBAL_MEM_FENCE);\n\n    // Обратное распространение\n    matrix_difference_parallel(m, output_size, A2, y_train_encoded, dZ2);  // dZ2 = A2 - y_train_encoded\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    \n    transpose_parallel(m, hidden_size, A1, A1_t);  // Транспонированная матрица A1_t\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    matrix_multiply_parallel(hidden_size, output_size, m, A1_t, dZ2, dW2);  // dW2 = A1^T * dZ2\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    divide_by_scalar_parallel(hidden_size, output_size, dW2, m, dW2);  // dW2 = dW2 / m\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    \n    column_sum_parallel(m, output_size, dZ2, db2);  // db2 = sum(dZ2, axis=0)\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    divide_by_scalar_parallel(1, output_size, db2, m, db2_mult);  // db2 = db2 / m\n    barrier(CLK_GLOBAL_MEM_FENCE);\n\n    transpose_parallel(hidden_size, output_size, W2, W2_t);  // Транспонированная матрица W2_t\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    matrix_multiply_parallel(m, hidden_size, output_size, dZ2, W2_t, dA1);  // dA1 = dZ2 * W2^T\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    \n    relu_derivative_parallel(m, hidden_size, Z1_biased, Z1_relu_deriv);  // Z1 = ReLU'(Z1)\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    elementwise_multiply_parallel(m, hidden_size, dA1, Z1_relu_deriv, dZ1);  // dZ1 = dA1 * Z1\n    barrier(CLK_GLOBAL_MEM_FENCE);\n\n    transpose_parallel(m, input_size, X_train, X_train_t);  // Транспонированная матрица X_train_t\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    matrix_multiply_parallel(input_size, hidden_size, m, X_train_t, dZ1, dW1);  // dW1 = X_train^T * dZ1\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    divide_by_scalar_parallel(input_size, hidden_size, dW1, m, dW1);  // dW1 = dW1 / m\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    \n    column_sum_parallel(m, hidden_size, dZ1, db1);  // db1 = sum(dZ1, axis=0)\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    divide_by_scalar_parallel(1, hidden_size, db1, m, db1_mult);  // db1 = db1 / m\n    barrier(CLK_GLOBAL_MEM_FENCE);\n\n    // Обновление весов\n    mult_by_scalar_parallel(input_size, hidden_size, dW1, learning_rate, dW1_mult);  // W1 -= learning_rate * dW1\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    matrix_difference_parallel(input_size, hidden_size, W1, dW1_mult, W1_out);  // W1_out = W1 - learning_rate * dW1\n    barrier(CLK_GLOBAL_MEM_FENCE);\n\n    mult_by_scalar_parallel(hidden_size, output_size, dW2, learning_rate, dW2_mult);  // W2 -= learning_rate * dW2\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    matrix_difference_parallel(m, output_size, W2, dW2_mult, W2_out);  // W2_out = W2 - learning_rate * dW2\n    barrier(CLK_GLOBAL_MEM_FENCE);\n\n    mult_by_scalar_parallel(1, output_size, db2_mult, learning_rate, db2);  // b2 -= learning_rate * db2\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    matrix_difference_parallel(1, output_size, b2, db2, b2_out);  // b2_out = b2 - learning_rate * db2\n    barrier(CLK_GLOBAL_MEM_FENCE);\n\n    mult_by_scalar_parallel(1, hidden_size, db1_mult, learning_rate, db1);  // b1 -= learning_rate * db1\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    matrix_difference_parallel(1, hidden_size, b1, db1_mult, b1_out);  // b1_out = b1 - learning_rate * db1\n    barrier(CLK_GLOBAL_MEM_FENCE);\n}\n\"\"\"\n\n# Компиляция программы OpenCL\nprogram = cl.Program(context, program_src).build()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-06T08:36:53.912831Z","iopub.execute_input":"2024-12-06T08:36:53.913157Z","iopub.status.idle":"2024-12-06T08:36:54.005954Z","shell.execute_reply.started":"2024-12-06T08:36:53.913127Z","shell.execute_reply":"2024-12-06T08:36:54.004993Z"}},"outputs":[],"execution_count":73},{"cell_type":"code","source":"import time\n\n# Функции активации\ndef relu(z):\n    return np.maximum(0, z)\n\ndef relu_derivative(z):\n    return (z > 0).astype(float)\n\ndef softmax(z):\n    exp_z = np.exp(z - np.max(z, axis=1, keepdims=True))\n    return exp_z / np.sum(exp_z, axis=1, keepdims=True)\n\n# Функция потерь (Cross-Entropy)\ndef cross_entropy_loss(y_true, y_pred):\n    return -np.sum(y_true * np.log(y_pred + 1e-9)) / m\n\nall_losses = []\n\nprev = W2\nepochs = 500\nfor epoch in range(epochs):\n    total_loss = 0.0\n    start_time = time.time()\n\n    X_train_test = X_train[:m,:]\n    # Объявление буферов для данных и переменных\n    X_train_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=X_train_test.flatten())\n    y_train_encoded_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=y_train_encoded)\n    W1_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=W1)\n    W2_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=W2)\n    b1_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=b1)\n    b2_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=b2)\n\n    W1_out_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=W1_out)\n    W2_out_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=W2_out)\n    b1_out_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=b1_out)\n    b2_out_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=b2_out)\n\n    Z1_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=Z1)\n    A1_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=A1)\n    Z2_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=Z2)\n    A2_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=A2)\n    dZ2_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=dZ2)\n    dW2_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=dW2)\n    db2_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=db2)\n    dA1_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=dA1)\n    dZ1_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=dZ1)\n    dW1_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=dW1)\n    db1_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=db1)\n    \n    A1_t_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=A1_t)\n    W2_t_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=W2_t)\n    X_train_t_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=X_train_t)\n    \n    Z1_biased_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=Z1_biased)\n    Z1_relu_deriv_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=Z1_relu_deriv)\n    Z2_biased_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=Z2_biased)\n    dW2_mult_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=dW2_mult)\n    db2_mult_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=db2_mult)\n    dW1_mult_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=dW1_mult)\n    db1_mult_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=db1_mult)\n\n    row_max_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=row_max)\n    row_sum_exp_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=row_sum_exp)\n    \n    # Буфер для лоссов\n    losses = np.zeros(train_data.shape[0], dtype=np.float32)\n    losses_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=losses)\n    \n    # Запуск ядра OpenCL\n    program.train_model(\n        queue, (4096, 4096), (32, 32),\n        X_train_cl, W1_cl, W2_cl, b1_cl, b2_cl, \n        W1_out_cl, W2_out_cl, b1_out_cl, b2_out_cl, \n        Z1_cl, A1_cl, Z2_cl, A2_cl, \n        dZ2_cl, dW2_cl, db2_cl, dA1_cl, \n        dZ1_cl, dW1_cl, db1_cl,\n        A1_t_cl, W2_t_cl, X_train_t_cl, \n        Z1_biased_cl, Z1_relu_deriv_cl, Z2_biased_cl,\n        dW2_mult_cl, db2_mult_cl,\n        dW1_mult_cl, db1_mult_cl,\n        row_max_cl, row_sum_exp_cl,\n        y_train_encoded_cl, losses_cl,\n        np.uint32(m), np.uint32(input_size), np.uint32(hidden_size), np.uint32(output_size),\n        np.float32(learning_rate)\n    )\n    # print(X_train_test)\n    # y_train_encoded_test = y_train_encoded[:m,:]\n    # Z1u_test = np.dot(X_train_test, W1)\n    # Z1_test = np.dot(X_train_test, W1) + b1\n    # A1_test = relu(Z1_test)\n    # Z2u_test = np.dot(A1_test, W2)\n    # Z2_test = np.dot(A1_test, W2) + b2\n    # A2_test = softmax(Z2_test)\n\n    # # Вычисление потерь\n    # loss_test = cross_entropy_loss(y_train_encoded_test, A2_test)\n\n    # # Обратное распространение\n    # dZ2_test = A2_test - y_train_encoded_test\n    # dW2_test = np.dot(A1_test.T, dZ2_test) / m\n    # db2_test = np.sum(dZ2_test, axis=0, keepdims=True) / m\n    # dA1_test = np.dot(dZ2_test, W2.T)\n    # Z1_relu_deriv_test = relu_derivative(Z1_test)\n    # dZ1_test = dA1_test * Z1_relu_deriv_test\n    # dW1_test = np.dot(X_train_test.T, dZ1_test) / m\n    # db1_test = np.sum(dZ1_test, axis=0, keepdims=True) / m\n\n    # # Обновление весов\n    # W1_test = W1 - learning_rate * dW1_test\n    # b1_test = b1 - learning_rate * db1_test\n    # W2_test = W2 - learning_rate * dW2_test\n    # b2_test = b2 - learning_rate * db2_test\n    \n\n    # print('Z1')\n    # print(Z1.shape)\n    # print(Z1)\n    # Считывание обновленных весов\n    cl.enqueue_copy(queue, Z1, Z1_cl).wait()\n    cl.enqueue_copy(queue, Z1_biased, Z1_biased_cl).wait()\n    cl.enqueue_copy(queue, Z1_relu_deriv, Z1_relu_deriv_cl).wait()\n    cl.enqueue_copy(queue, A1, A1_cl).wait()\n    cl.enqueue_copy(queue, Z2, Z2_cl).wait()\n    cl.enqueue_copy(queue, Z2_biased, Z2_biased_cl).wait()\n    cl.enqueue_copy(queue, A2, A2_cl).wait()\n    cl.enqueue_copy(queue, losses, losses_cl).wait()\n    cl.enqueue_copy(queue, dZ2, dZ2_cl).wait()\n    cl.enqueue_copy(queue, dW2, dW2_cl).wait()\n    cl.enqueue_copy(queue, db2_mult, db2_mult_cl).wait()\n    cl.enqueue_copy(queue, dA1, dA1_cl).wait()\n    cl.enqueue_copy(queue, dZ1, dZ1_cl).wait()\n    cl.enqueue_copy(queue, dW1, dW1_cl).wait()\n    cl.enqueue_copy(queue, db1, db1_cl).wait()\n    cl.enqueue_copy(queue, W1_out, W1_out_cl).wait()\n    cl.enqueue_copy(queue, W2_out, W2_out_cl).wait()\n    cl.enqueue_copy(queue, b1_out, b1_out_cl).wait()\n    cl.enqueue_copy(queue, b2_out, b2_out_cl).wait()\n\n    # if np.max(np.abs(Z1 - Z1u_test)) < 1e-6:\n    #     print('Z1 ok')\n    # else:\n    #     print('Z1 not ok')\n    #     # print('diff')\n    #     # print(Z1 - Z1u_test)\n    #     print('max diff')\n    #     print(np.max(np.abs(Z1 - Z1u_test)))\n    #     print(Z1)\n    #     # print(Z1u_test)\n    \n    # if np.max(np.abs(Z1_biased - Z1_test)) < 1e-6:\n    #     print('Z1_biased ok')\n    # else:\n    #     print('Z1_biased not ok')\n    #     print('max diff')\n    #     print(np.max(np.abs(Z1_biased - Z1_test)))\n    #     # print('diff')\n    #     # print(Z1_biased - Z1_test)\n    #     print(Z1_biased)\n    #     print(Z1_test)\n\n    # if np.max(np.abs(Z1_biased - Z1_test)) < 1e-6:\n    #     print('A1 ok')\n    # else:\n    #     print('A1 not ok')\n    #     print(np.max(np.abs(A1 - A1_test)))\n    #     print(A1)\n    #     print(A1_test)\n\n    # if np.max(np.abs(Z2 - Z2u_test)) < 1e-6:\n    #     print('Z2 ok')\n    # else:\n    #     print('Z2 not ok')\n    #     print('max diff')\n    #     print(np.max(np.abs(Z2 - Z2u_test)))\n    #     print('diff')\n    #     print(Z2 - Z2u_test)\n    #     # print(Z2)\n    #     # print(Z2u_test)\n\n    # if np.max(np.abs(Z2_biased - Z2_test)) < 1e-6:\n    #     print('Z2_biased ok')\n    # else:\n    #     print('Z2_biased not ok')\n    #     print(np.max(np.abs(Z2_biased - Z2_test)))\n    #     print(Z2_biased - Z2_test)\n    #     print(Z2_test)\n\n    # if np.max(np.abs(A2 - A2_test)) < 1e-6:\n    #     print('A2 ok')\n    # else:\n    #     print('A2 not ok')\n    #     print(np.max(np.abs(A2 - A2_test)))\n    #     print(A2)\n    #     print(A2_test)\n\n    # if np.max(np.abs(dZ2 - dZ2_test)) < 1e-6:\n    #     print('dZ2 ok')\n    # else:\n    #     print('dZ2 not ok')\n    #     print(np.max(np.abs(dZ2 - dZ2_test)))\n    #     print(dZ2)\n    #     print(dZ2_test)\n\n    # if np.max(np.abs(dW2 - dW2_test)) < 1e-6:\n    #     print('dW2 ok')\n    # else:\n    #     print('dW2 not ok')\n    #     print(np.max(np.abs(dW2 - dW2_test)))\n    #     print(dW2)\n    #     print(dW2_test)\n\n    # if np.max(np.abs(db2_mult - db2_test)) < 1e-6:\n    #     print('db2 ok')\n    # else:\n    #     print('db2 not ok')\n    #     print(np.max(np.abs(db2_mult - db2_test)))\n    #     print(db2_mult)\n    #     print(db2_test)\n        \n    # if np.max(np.abs(dA1 - dA1_test)) < 1e-6:\n    #     print('dA1 ok')\n    # else:\n    #     print('dA1 not ok')\n    #     print(np.max(np.abs(dA1 - dA1_test)))\n    #     print(dA1.shape)\n    #     print(dA1_test.shape)\n\n    # if np.max(np.abs(Z1_relu_deriv - Z1_relu_deriv_test)) < 1e-6:\n    #     print('Z1_relu_deriv ok')\n    # else:\n    #     print('Z1_relu_deriv not ok')\n    #     print(np.max(np.abs(Z1_relu_deriv - Z1_relu_deriv_test)))\n    #     print(Z1_relu_deriv)\n    #     print(Z1_relu_deriv_test)\n        \n    # if np.max(np.abs(dZ1 - dZ1_test)) < 1e-6:\n    #     print('dZ1 ok')\n    # else:\n    #     print('dZ1 not ok')\n    #     print(np.max(np.abs(dZ1 - dZ1_test)))\n    #     print(dA1.shape)\n    #     print(Z1_relu_deriv.shape)\n    #     print(dZ1)\n    #     print(dZ1_test)\n        \n    # if np.max(np.abs(dW1 - dW1_test)) < 1e-6:\n    #     print('dW1 ok')\n    # else:\n    #     print('dW1 not ok')\n    #     print(np.max(np.abs(dW1 - dW1_test)))\n    #     print(dW1)\n    #     print(dW1_test)\n        \n    # if np.max(np.abs(db1 - db1_test)) < 1e-6:\n    #     print('db1 ok')\n    # else:\n    #     print('db1 not ok')\n    #     print(np.max(np.abs(db1 - db1_test)))\n    #     print(db1)\n    #     print(db1_test)\n\n    W1 = W1_out\n    W2 = W2_out\n    b1 = b1_out\n    b2 = b2_out\n\n\n    # Считывание лоссов из буфера\n    \n    # Сбор статистики\n    epoch_loss = np.mean(losses)\n    all_losses.append(epoch_loss)\n\n    if (epoch + 1) % 100 == 0:\n        print(f\"Epoch {epoch+1}/{epochs}, Loss: {epoch_loss:.4f}\")\n\n        end_time = time.time()\n        print(f\"Время выполнения: {end_time - start_time:.6f} секунд\")\n\n# График потерь\nplt.plot(all_losses)\nplt.xlabel(\"Epochs\")\nplt.ylabel(\"Loss\")\nplt.title(\"Training Loss Over Epochs\")\nplt.show()\n    ","metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"vAI2cCrMThQE","outputId":"edf3d578-90a8-4a37-d942-c6c829e09aa9","trusted":true,"execution":{"iopub.status.busy":"2024-12-06T08:54:22.671956Z","iopub.execute_input":"2024-12-06T08:54:22.672297Z","iopub.status.idle":"2024-12-06T08:58:03.449429Z","shell.execute_reply.started":"2024-12-06T08:54:22.672248Z","shell.execute_reply":"2024-12-06T08:58:03.448281Z"}},"outputs":[{"name":"stdout","text":"Epoch 100/500, Loss: 2.0069\nВремя выполнения: 0.440246 секунд\nEpoch 200/500, Loss: 1.9898\nВремя выполнения: 0.439055 секунд\nEpoch 300/500, Loss: 1.9728\nВремя выполнения: 0.440037 секунд\nEpoch 400/500, Loss: 1.9555\nВремя выполнения: 0.438310 секунд\nEpoch 500/500, Loss: 1.9379\nВремя выполнения: 0.440644 секунд\n","output_type":"stream"},{"output_type":"display_data","data":{"text/plain":"<Figure size 640x480 with 1 Axes>","image/png":"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"},"metadata":{}}],"execution_count":109},{"cell_type":"code","source":"import numpy as np\n\n# Функции активации\ndef relu(z):\n    return np.maximum(0, z)\n\ndef relu_derivative(z):\n    return (z > 0).astype(float)\n\ndef softmax(z):\n    exp_z = np.exp(z - np.max(z, axis=1, keepdims=True))\n    return exp_z / np.sum(exp_z, axis=1, keepdims=True)\n\n# Функция потерь (Cross-Entropy)\ndef cross_entropy_loss(y_true, y_pred):\n    m = y_true.shape[0]\n    return -np.sum(y_true * np.log(y_pred + 1e-9)) / m\n\n# Функция для предсказания\ndef predict(X, W1, W2, b1, b2):\n    Z1 = np.dot(X, W1) + b1\n    A1 = relu(Z1)\n    Z2 = np.dot(A1, W2) + b2\n    A2 = softmax(Z2)\n    return np.argmax(A2, axis=1)  # Возвращаем индекс класса с наибольшей вероятностью\n\n# Оценка точности\ndef accuracy(y_true, y_pred):\n    correct = np.sum(y_true == y_pred)  # Считаем количество правильных предсказаний\n    total = len(y_true)  # Общее количество примеров\n    return correct / total  # Точность = правильные предсказания / общее количество\n\n# Получаем предсказания для тестовых данных\ny_pred = predict(X_test, W1, W2, b1, b2)\n\n# Выбираем случайные 100 индексов из тестовых данных\nrandom_indices = np.random.choice(X_test.shape[0], 100, replace=False)\n\n# Получаем подвыборку данных для случайных тестов\nX_random = X_test[random_indices]\ny_random_true = y_test[random_indices]\n\n# Получаем предсказания для случайной подвыборки\ny_random_pred = predict(X_random, W1, W2, b1, b2)\n\n# Расчет точности на случайной подвыборке\nacc_random = accuracy(y_random_true, y_random_pred)\n\n# Выводим точность на случайной подвыборке\nprint(f'Accuracy on random sample: {acc_random * 100:.2f}%')\n\n# Оценка точности на всех данных\nacc_all = accuracy(y_test, y_pred)\nprint(f'Accuracy on full test data: {acc_all * 100:.2f}%')","metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"5nKrbwWoTqFf","outputId":"3d614c47-4385-45d3-dac6-714aa4214257","trusted":true,"execution":{"iopub.status.busy":"2024-12-06T09:00:15.837103Z","iopub.execute_input":"2024-12-06T09:00:15.837814Z","iopub.status.idle":"2024-12-06T09:00:15.85767Z","shell.execute_reply.started":"2024-12-06T09:00:15.837778Z","shell.execute_reply":"2024-12-06T09:00:15.856331Z"}},"outputs":[{"name":"stdout","text":"Accuracy on random sample: 44.00%\nAccuracy on full test data: 41.80%\n","output_type":"stream"}],"execution_count":115},{"cell_type":"code","source":"# Получаем предсказания для тестовых данных\ny_pred = predict(X_test, W1, W2, b1, b2)\n\n# Выбираем 20 случайных индексов для изображений\nrandom_indices = np.random.choice(X_test.shape[0], 20, replace=False)\n\n# Визуализация 20 изображений с предсказаниями и правильными метками\nplt.figure(figsize=(10, 10))\n\n# Отображаем 20 случайных изображений\nfor i, idx in enumerate(random_indices):\n    plt.subplot(4, 5, i+1)\n    image = X_test[idx].reshape(28, 28)  # Преобразуем в 2D изображение (28x28)\n    plt.imshow(image, cmap='gray')\n    plt.title(f\"Pred: {y_pred[idx]} / True: {y_test[idx]}\")\n    plt.axis('off')\n\nplt.tight_layout()\nplt.show()","metadata":{"colab":{"base_uri":"https://localhost:8080/","height":1000},"id":"bqWPjyWuTrpW","outputId":"ec8d0b83-a949-44fc-e5b1-9551204f9873","trusted":true,"execution":{"iopub.status.busy":"2024-12-06T08:59:53.10842Z","iopub.execute_input":"2024-12-06T08:59:53.109227Z","iopub.status.idle":"2024-12-06T08:59:54.47015Z","shell.execute_reply.started":"2024-12-06T08:59:53.10918Z","shell.execute_reply":"2024-12-06T08:59:54.469205Z"}},"outputs":[{"output_type":"display_data","data":{"text/plain":"<Figure size 1000x1000 with 20 Axes>","image/png":"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ALCIxhsAAAAAAItovAEAAAAAsIjGGwAAAAAAi2i8AQAAAACwiMYbAAAAAACLgmI7sbx582rjL730kjFn1KhR2vi2bdu8MKP/MW074ew8pUqVcisu4nxbF8AVpq1YLl++7OOZZJ02bdpo4yVKlPDJ+a9eveqT8yBwmLYNa9SokTHH2XZib775pjbevn17Y06fPn208bZt2xpzBgwYoI1//fXXxhye//7B2ZZdpq0Vq1evbsz59ddftfFu3boZc0zbiRUsWNCYY+JsOzGTXLlyGceef/55bfyFF14w5hQoUEAbb9iwoVvzgv9xtq3qihUrtHFn27T6irPt/DyxceNGrx4vWPGJNwAAAAAAFtF4AwAAAABgEY03AAAAAAAW0XgDAAAAAGARjTcAAAAAABaFKdNSxjc+0Mur33lTYmKiNu5stUjTquLOVoo9c+aMNj59+nRjjsnTTz9tHNu3b5827mxu3l6NPVS4+PTX8lVNnDx5Uhs3rebvKdMqm1u2bPHqeXzFtAtAnTp1jDlTp07VxmNiYrwxpXSff/65Nt6rVy9jjq9Wlw+EmghU8+fP18adrQ4+duxYbXzEiBFemVOaqlWrGsfuuusubfw///mPMce0KvSUKVOMOZMnT9bGTStc+5KndRGINXHfffcZx5YsWaKNJyUlGXNatGihjZ84ccK9ifm548ePG8dMq5o724HAn/E6kTmvvPKKceyZZ57RxsPDzRtR7dixQxsvVqyYMSdfvnzGMU92ualbt642Hkr9iSt1wSfeAAAAAABYROMNAAAAAIBFNN4AAAAAAFhE4w0AAAAAgEU03gAAAAAAWETjDQAAAACARUGxndjWrVu18TvvvNMn5zdtMyYi0qNHD228W7duxhzTvKtVq+bRHGAWCFtipKamauOZmbtO2bJltfH//ve/Xj2PN40cOdI41rJlS228evXqlmbj6P333zeOffXVV9q4aaseXwqEmghUpi1knnvuOWPOr7/+qo07217SV9s0Va5c2Tg2ZMgQbfzBBx805pw+fVobb9q0qTFn+/btxjFvCsbtxCpUqKCNr1mzxphj2harSJEixpxg2zbMxNl1M2215GyLKH/G64Q9pq1QnT1XTNuGTZs2zZhjes8nYv7+Ots2sFatWsaxUMF2YgAAAAAAZDEabwAAAAAALKLxBgAAAADAIhpvAAAAAAAsovEG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oSLQa6Jfv34qPDxc7dy5Mz128eJFVaJECVW9enWXjxPMNeGtaxQqAr0mZs6cqUREff311w7xESNGKBFRSUlJLh0nmGvCW9coVAR6TSil1L59+5RSSiUnJysRUS+99JLbx6Cf8C8B86uxf/3rXyIism/fPhER6d69u8TExMjevXslISFBcufOLY888oiI/H1bxcSJE6Vy5coSGRkphQsXlj59+sjp06cdjqmUkjFjxkjx4sUlV65c0qhRI9mxY4f2/Hv37pW9e/fedJ41atSQmJgYh1iBAgWkfv368ttvv7n89S5ZskT72yl3lCpVSlq1aiUrVqyQmjVrSlRUlLz33nvpt5bMmDEjQ47uVpY//vhDevbsKYULF5acOXNK5cqV5aOPPsqQe/DgQdm5c+dN51W5cmUpWLCgQyxnzpySkJAghw8flvPnz7v1dYaqQKmJb775RrZv3y4vvfSSREVFyaVLl+T69etufa2pqamyfPnyTNdEWFiYDBgwQGbOnCmVK1eWnDlzyvLly9NvQVy9erXD4021snPnTunYsaPkz59fIiMjpWbNmrJ48eIM5/PlNULg1MS6deukWrVqUr58+fRYrly5pE2bNpKUlCS7d+++6TGCvSa8cY0QODVx7tw5EREpXLiwQ7xo0aIiIhIVFXXTYwR7TXjjGiFwakLk7/fxmUU/4V/89lbzG6U9SQsUKJAeu3btmjRv3lzq1asnr7/+evotI3369JEZM2ZIjx495Mknn5R9+/bJO++8I1u3bpUNGzZIRESEiIiMGDFCxowZIwkJCZKQkCBJSUnSrFkzuXLlSobzN27cWETE479x+PPPPzM8OZw9duvWrTJ69GiPzvVPv//+uzz00EPSp08f6d27t8ObGFccO3ZM7r777vQXori4OFm2bJn06tVLzp07JwMHDkx/bNeuXWXNmjWilPJorn/++afkypXL72/98ReBUhNpf2uTM2dOqVmzpmzZskVy5Mgh7du3lylTpkj+/Plv+rVu2rRJkpOTJSEh4eYX5ia+++47mTNnjgwYMEAKFiwopUqVkjNnzricv2PHDqlbt64UK1ZMhg4dKtHR0TJnzhxp166dzJs3T9q3b5/+WF9eIwROTfz111+SL1++DPG0uW3ZskXKli3r9BjBXhPeuEYInJq49957JVu2bPLUU0/JG2+8IcWLF5dffvlFxo4dK+3atZMKFSrc9GsN9prwxjVC4NSEN9BP+GE/kYWftmul3RqyatUqlZycrA4dOqQ+//xzVaBAARUVFaUOHz6slFKqW7duSkTU0KFDHfLXrVunRETNnDnTIb58+XKH+PHjx1WOHDlUy5YtVWpqavrjXnjhBSUiGW4NiY+Pd+tWjX9au3atCgsLU8OHD3fp8R9++KGKiopy6zYO3a0h8fHxSkTU8uXLHeL79u1TIqKmT5+e4Thyw60svXr1UkWLFlUnTpxweFznzp1VbGyswxwbNGigPH1K7d69W0VGRqouXbp4lB/MAr0m2rRpo0REFShQQD3yyCPqyy+/VMOHD1fh4eHqnnvucTiXibu3Simlv4VQRFS2bNnUjh07HOKJiYlKRFRiYqJDXFcrjRs3Vrfffru6fPlyeiw1NVXdc889qmzZsg75vrxGoSTQa6J169Yqb9686ty5cw7xOnXqKBFRr7/++k2PEew14Y1rFEoCvSaUUuqDDz5QefPmVSKS/q9bt27q6tWrLuUHe00olflrFEqCoSbSeHqrOf2E//HbW82bNGkicXFxUqJECencubPExMTIggULpFixYg6P69u3r8P/586dK7GxsdK0aVM5ceJE+r+0W8DTVkRdtWqVXLlyRZ544gmHFSz/+RuXf9q/f79Hv506fvy4PPzww1K6dGkZMmSISzlLly6VRo0aeeW2odKlS0vz5s09ylVKybx586R169ailHK4ns2bN5ezZ89KUlJS+uNXr17t0W+nLl26JA888IBERUXJ+PHjPZprKAjUmrhw4YKIiNSqVUs+++wz6dChg4wePVpefvll2bhxo3z77bc3PcbSpUszfatUmgYNGkilSpU8yj116pR899130qlTJzl//nz6tTx58qQ0b95cdu/eLX/88Uf64315jUJRoNZE37595cyZM/Lggw/K1q1bZdeuXTJw4MD03S9SUlJueoxgrwlvXKNQFKg1ISJSrFgxqV27tkycOFEWLFgggwYNkpkzZ8rQoUNdyg/2mhDJ/DUKRYFcE5lFP+F//PZW88mTJ0u5cuUkPDxcChcuLOXLl8+wWmN4eLgUL17cIbZ79245e/asFCpUSHvc48ePi4jIgQMHREQy3KoWFxenvb3NExcvXpRWrVrJ+fPnZf369Rn+9lvn6tWrsnLlShk3bpxX5lC6dGmPc5OTk+XMmTMy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r5PlrrvuulBRURG23377gvnl/BullLXXXju0b99+tWuiRYsWYf/99w/33HNPmDVr1irH+f7/tdg2MR9//HH0b/yuvfbaEEJY7W/rKU19r4mhQ4eGysrKlV5fWZaFq6++Omy44YZh1113TeY2l+tE7H3j/fffD9dff33YZpttVrRQojzqe024Tqz+OtGzZ8+QZVm48847V4rfdtttIYQQtttuu6L+HxSnvtfE0KFDQ+vWrcMNN9yw4o5xCN99LhgwYEAytzlfJ0Io/n2jPjS5O9577rlnGDFiRBg7dmx46aWXwl577RUqKyvDG2+8Ee66665w+eWXh+rq6tChQ4cwevToMHbs2DBo0KAwcODA8OKLL4apU6eG9u3br3LcUre2nzZtWli4cGFJG4Yt/6pU3gtlnXXWCQcccECYMGFCqKioCJtuuml44IEHon9zdMUVV4Tdd9899O7dOwwfPjz06NEjfPjhh+Hpp58O7733Xnj55ZdXPLYuniNKV99r4vzzzw8zZ84Me++9d+jWrVv45JNPwj333BOee+65cNxxxxX8etFbb70VXnvttXDVVVfV6jkoxrHHHhsuvPDCcOyxx4Ydd9wxzJgxI8yePXuVx1144YVh+vTpYeeddw7Dhw8PW221Vfjkk0/CCy+8EB599NHwySefrHhssW1iJk+eHK6++uqw3377hR49eoQvv/wyTJs2LTzyyCNh8ODBK/qOUh71vSY22mijMGrUqDBu3LiwZMmS0KdPn/DnP/85/O1vfwu33npraNGiRTK3uVwnTj755DBnzpzQr1+/0KVLl/D222+HiRMnhq+//jpcfvnlZf2/Uv9rwnVi9deJI488MlxyySVhxIgR4cUXXwy9evUKL7zwQrj22mtDr169VmrHRO3V95ro1KlTOP3008NZZ50V9t5777DffvuFl19+OVxzzTXh4IMPDn369EnmNpfrRG3eN+pNHe6gXpTl2/8/99xzBR93xBFHZG3btk2OT5o0Kdthhx2yNm3aZO3atct69+6dnXzyydm8efNWPGbp0qXZmDFjss6dO2dt2rTJqqqqslmzZmXdu3evdUuMgw46KKusrMwWLlxYdM7o0aOzrbbaqujH/6/U9v8jR46MPn7BggXZ/vvvn6255prZeuutl40YMWJFW6/vt8qYM2dOdvjhh2edOnXKKisrsw033DAbNGhQ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"},"metadata":{}}],"execution_count":114},{"cell_type":"code","source":"# Пример: Тест для matrix_multiply\nM, N, P = 2, 3, 4  # Размеры матриц\nA = np.random.rand(M, P).astype(np.float32)\nB = np.random.rand(P, N).astype(np.float32)\nC = np.zeros((M, N), dtype=np.float32)\n\n# Создание OpenCL буферов\nA_buf = cl.Buffer(context, cl.mem_flags.READ_ONLY | cl.mem_flags.COPY_HOST_PTR, hostbuf=A)\nB_buf = cl.Buffer(context, cl.mem_flags.READ_ONLY | cl.mem_flags.COPY_HOST_PTR, hostbuf=B)\nC_buf = cl.Buffer(context, cl.mem_flags.WRITE_ONLY, C.nbytes)\n\n# Запуск OpenCL метода\nprogram.matrix_multiply_parallel(queue, (4, 4, 4), None, np.int32(M), np.int32(N), np.int32(P), A_buf, B_buf, C_buf)\n\n# Чтение результата\ncl.enqueue_copy(queue, C, C_buf).wait()\n\n# Проверка с numpy\nC_np = np.dot(A, B)\nif (C_np==C).all():\n    print (C_np)\n    print(C)\n    print(\"Matrix multiplication test passed\")\nelse:\n    print(\"Matrix multiplication test not passed\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-05T19:10:15.187491Z","iopub.execute_input":"2024-12-05T19:10:15.187841Z","iopub.status.idle":"2024-12-05T19:10:15.197879Z","shell.execute_reply.started":"2024-12-05T19:10:15.187812Z","shell.execute_reply":"2024-12-05T19:10:15.197064Z"}},"outputs":[{"name":"stdout","text":"[[0.7433785  1.1592435  1.4248863 ]\n [0.8499696  1.2782222  0.97176117]]\n[[0.7433785  1.1592435  1.4248863 ]\n [0.8499696  1.2782222  0.97176117]]\nMatrix multiplication test passed\n","output_type":"stream"}],"execution_count":112},{"cell_type":"code","source":"# Пример: Тест для add_bias\nrows, cols = 2, 3\nmat = np.random.rand(rows, cols).astype(np.float32)\nbias = np.random.rand(cols).astype(np.float32)\nresult = np.zeros_like(mat)\n\n# Создание OpenCL буферов\nmat_buf = cl.Buffer(context, cl.mem_flags.READ_ONLY | cl.mem_flags.COPY_HOST_PTR, hostbuf=mat)\nbias_buf = cl.Buffer(context, cl.mem_flags.READ_ONLY | cl.mem_flags.COPY_HOST_PTR, hostbuf=bias)\nresult_buf = cl.Buffer(context, cl.mem_flags.WRITE_ONLY, result.nbytes)\n\n# Запуск OpenCL метода\nprogram.add_bias(queue, (rows, cols), None, np.int32(rows), np.int32(cols), mat_buf, bias_buf, result_buf)\n\n# Чтение результата\ncl.enqueue_copy(queue, result, result_buf).wait()\n\n# Проверка с numpy\nresult_np = mat + bias\nassert np.allclose(result, result_np), f\"Add bias test failed: {result} != {result_np}\"\n\nprint(\"Add bias test passed\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-05T12:24:18.806738Z","iopub.execute_input":"2024-12-05T12:24:18.80733Z","iopub.status.idle":"2024-12-05T12:24:18.81631Z","shell.execute_reply.started":"2024-12-05T12:24:18.807295Z","shell.execute_reply":"2024-12-05T12:24:18.815424Z"}},"outputs":[{"name":"stdout","text":"Add bias test passed\n","output_type":"stream"}],"execution_count":26},{"cell_type":"code","source":"# Пример: Тест для relu\nmat = np.random.randn(rows, cols).astype(np.float32)\nresult = np.zeros_like(mat)\n\n# Создание OpenCL буферов\nmat_buf = cl.Buffer(context, cl.mem_flags.READ_ONLY | cl.mem_flags.COPY_HOST_PTR, hostbuf=mat)\nresult_buf = cl.Buffer(context, cl.mem_flags.WRITE_ONLY, result.nbytes)\n\n# Запуск OpenCL метода\nprogram.relu(queue, (rows, cols), None, np.int32(rows), np.int32(cols), mat_buf, result_buf)\n\n# Чтение результата\ncl.enqueue_copy(queue, result, result_buf).wait()\n\n# Проверка с numpy\nresult_np = np.maximum(0, mat)\nassert np.allclose(result, result_np), f\"ReLU test failed: {result} != {result_np}\"\n\nprint(\"ReLU test passed\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-05T10:51:45.281277Z","iopub.execute_input":"2024-12-05T10:51:45.282231Z","iopub.status.idle":"2024-12-05T10:51:45.290526Z","shell.execute_reply.started":"2024-12-05T10:51:45.282195Z","shell.execute_reply":"2024-12-05T10:51:45.28951Z"}},"outputs":[{"name":"stdout","text":"ReLU test passed\n","output_type":"stream"}],"execution_count":17},{"cell_type":"code","source":"# Пример: Тест для softmax\nrows, cols = 2, 3\nmat = np.random.randn(rows, cols).astype(np.float32)\nresult = np.zeros_like(mat)\n\n# Создание OpenCL буферов\nmat_buf = cl.Buffer(context, cl.mem_flags.READ_ONLY | cl.mem_flags.COPY_HOST_PTR, hostbuf=mat)\nresult_buf = cl.Buffer(context, cl.mem_flags.WRITE_ONLY, result.nbytes)\n\n# Запуск OpenCL метода\nprogram.softmax(queue, (rows, cols), None, np.int32(rows), np.int32(cols), mat_buf, result_buf)\n\n# Чтение результата\ncl.enqueue_copy(queue, result, result_buf).wait()\n\n# Проверка с numpy\nresult_np = np.exp(mat - np.max(mat, axis=1, keepdims=True))\nresult_np /= np.sum(result_np, axis=1, keepdims=True)\nassert np.allclose(result, result_np), f\"Softmax test failed: {result} != {result_np}\"\n\nprint(\"Softmax test passed\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-05T10:51:56.225821Z","iopub.execute_input":"2024-12-05T10:51:56.226504Z","iopub.status.idle":"2024-12-05T10:51:56.235618Z","shell.execute_reply.started":"2024-12-05T10:51:56.22644Z","shell.execute_reply":"2024-12-05T10:51:56.234551Z"}},"outputs":[{"name":"stdout","text":"Softmax test passed\n","output_type":"stream"}],"execution_count":18},{"cell_type":"code","source":"# Пример: Тест для transpose\nmat = np.random.randn(rows, cols).astype(np.float32)\nresult = np.zeros((cols, rows), dtype=np.float32)\n\n# Создание OpenCL буферов\nmat_buf = cl.Buffer(context, cl.mem_flags.READ_ONLY | cl.mem_flags.COPY_HOST_PTR, hostbuf=mat)\nresult_buf = cl.Buffer(context, cl.mem_flags.WRITE_ONLY, result.nbytes)\n\n# Запуск OpenCL метода\nprogram.transpose(queue, (rows, cols), None, np.int32(rows), np.int32(cols), mat_buf, result_buf)\n\n# Чтение результата\ncl.enqueue_copy(queue, result, result_buf).wait()\n\n# Проверка с numpy\nresult_np = mat.T\nassert np.allclose(result, result_np), f\"Transpose test failed: {result} != {result_np}\"\n\nprint(\"Transpose test passed\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-06T07:43:35.327687Z","iopub.execute_input":"2024-12-06T07:43:35.328056Z","iopub.status.idle":"2024-12-06T07:43:35.351627Z","shell.execute_reply.started":"2024-12-06T07:43:35.328024Z","shell.execute_reply":"2024-12-06T07:43:35.350372Z"}},"outputs":[{"traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mNameError\u001b[0m                                 Traceback (most recent call last)","Cell \u001b[0;32mIn[77], line 2\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[38;5;66;03m# Пример: Тест для transpose\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m mat \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39mrandom\u001b[38;5;241m.\u001b[39mrandn(\u001b[43mrows\u001b[49m, cols)\u001b[38;5;241m.\u001b[39mastype(np\u001b[38;5;241m.\u001b[39mfloat32)\n\u001b[1;32m      3\u001b[0m result \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39mzeros((cols, rows), dtype\u001b[38;5;241m=\u001b[39mnp\u001b[38;5;241m.\u001b[39mfloat32)\n\u001b[1;32m      5\u001b[0m \u001b[38;5;66;03m# Создание OpenCL буферов\u001b[39;00m\n","\u001b[0;31mNameError\u001b[0m: name 'rows' is not defined"],"ename":"NameError","evalue":"name 'rows' is not defined","output_type":"error"}],"execution_count":77},{"cell_type":"code","source":"# Пример: Тест для matrix_difference\nmat1 = np.random.rand(rows, cols).astype(np.float32)\nmat2 = np.random.rand(rows, cols).astype(np.float32)\nresult = np.zeros_like(mat1)\n\n# Создание OpenCL буферов\nmat1_buf = cl.Buffer(context, cl.mem_flags.READ_ONLY | cl.mem_flags.COPY_HOST_PTR, hostbuf=mat1)\nmat2_buf = cl.Buffer(context, cl.mem_flags.READ_ONLY | cl.mem_flags.COPY_HOST_PTR, hostbuf=mat2)\nresult_buf = cl.Buffer(context, cl.mem_flags.WRITE_ONLY, result.nbytes)\n\n# Запуск OpenCL метода\nprogram.matrix_difference(queue, (1,), None, np.int32(rows), np.int32(cols), mat1_buf, mat2_buf, result_buf)\n\n# Чтение результата\ncl.enqueue_copy(queue, result, result_buf).wait()\n\n# Проверка с numpy\nresult_np = mat1 - mat2\nassert np.allclose(result, result_np), f\"Matrix difference test failed: {result} != {result_np}\"\n\nprint(\"Matrix difference test passed\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-05T12:24:48.55443Z","iopub.execute_input":"2024-12-05T12:24:48.554739Z","iopub.status.idle":"2024-12-05T12:24:48.562985Z","shell.execute_reply.started":"2024-12-05T12:24:48.554713Z","shell.execute_reply":"2024-12-05T12:24:48.562061Z"}},"outputs":[{"name":"stdout","text":"Matrix difference test passed\n","output_type":"stream"}],"execution_count":28},{"cell_type":"code","source":"import sys\nnp.set_printoptions(threshold=sys.maxsize)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-06T07:13:25.614661Z","iopub.execute_input":"2024-12-06T07:13:25.615015Z","iopub.status.idle":"2024-12-06T07:13:25.619502Z","shell.execute_reply.started":"2024-12-06T07:13:25.614981Z","shell.execute_reply":"2024-12-06T07:13:25.618555Z"}},"outputs":[],"execution_count":11},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}