{"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"}},{"cell_type":"code","source":"!pip install pyopencl\n\nimport numpy as np\nfrom scipy.ndimage import shift, zoom\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-09T06:10:25.797026Z","iopub.execute_input":"2024-12-09T06:10:25.797321Z","iopub.status.idle":"2024-12-09T06:10:35.880056Z","shell.execute_reply.started":"2024-12-09T06:10:25.797293Z","shell.execute_reply":"2024-12-09T06:10:35.879176Z"}},"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[31m22.1 MB/s\u001b[0m eta \u001b[36m0:00:00\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)\nma, n = loaded_data.shape\nnp.random.shuffle(loaded_data)\n\ntest_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:ma].T\ny_train = train_data[0].T\nX_train = train_data[1:n].T\nX_train = X_train / 255\n# Функция для изменения размера\ndef resize_to_11x11_with_offset(flat_image):\n    # Преобразуем из 1D (784,) в 2D (28, 28)\n    image_28x28 = flat_image.reshape(28, 28)\n    \n    # Масштабируем до 11x11\n    zoom_factors = (11 / 28, 11 / 28)\n    image_11x11 = zoom(image_28x28, zoom_factors, order=3)  # Cubic interpolation\n\n    # Бинаризация: пиксели < 0.2 становятся черными (0), >= 0.2 становятся белыми (1)\n    image_11x11 = np.where(image_11x11 < 0.2, 0, 1)\n\n    # Случайное масштабирование от 0.8x до 1.5x\n    random_scale = np.random.uniform(1, 1.5)\n    scaled_image = zoom(image_11x11, random_scale, order=3)\n\n    # Приведение к размеру 11x11\n    output_image = np.zeros((11, 11))  # Черный фон\n    crop_y, crop_x = scaled_image.shape\n\n    # Если изображение больше 11x11, обрезаем\n    start_y = max((crop_y - 11) // 2, 0)\n    start_x = max((crop_x - 11) // 2, 0)\n    end_y = start_y + 11\n    end_x = start_x + 11\n\n    cropped_image = scaled_image[start_y:end_y, start_x:end_x]\n\n    # Если изображение меньше 11x11, центрируем\n    paste_y = max((11 - crop_y) // 2, 0)\n    paste_x = max((11 - crop_x) // 2, 0)\n    output_image[paste_y:paste_y + cropped_image.shape[0], paste_x:paste_x + cropped_image.shape[1]] = cropped_image\n\n    # Добавляем случайный оффсет (смещение) для каждой оси\n    offset_x = np.random.randint(-1, 1)  # Смещение по x (0-3 пикселя)\n    offset_y = np.random.randint(-1, 1)  # Смещение по y (0-3 пикселя)\n    image_11x11 = shift(output_image, shift=(offset_y, offset_x), mode='constant', cval=0)\n    \n    # Преобразуем обратно в 1D (121,)\n    return image_11x11.flatten()\n\n# Преобразуем весь X_train\nX_train = np.array([resize_to_11x11_with_offset(image) for image in X_train])\nX_test = np.array([resize_to_11x11_with_offset(image) for image in X_test])\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,])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-09T06:26:03.105895Z","iopub.execute_input":"2024-12-09T06:26:03.106707Z","iopub.status.idle":"2024-12-09T06:26:14.46104Z","shell.execute_reply.started":"2024-12-09T06:26:03.106674Z","shell.execute_reply":"2024-12-09T06:26:14.460177Z"}},"outputs":[{"name":"stdout","text":"float32\n(785, 41000)\n(41000, 121)\n(41000,)\n[[0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]\n [0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]\n [0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]\n [0. 0. 1. 0. 0. 0. 0. 0. 0. 0.]\n [0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]\n [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]\n [0. 0. 1. 0. 0. 0. 0. 0. 0. 0.]\n [0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]\n [0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]\n [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]\n","output_type":"stream"}],"execution_count":28},{"cell_type":"code","source":"# Количество примеров для отображения\nnum_examples = 20\n\n# Случайные индексы из тренировочного набора\nrandom_indices = np.random.choice(X_train.shape[0], num_examples, replace=False)\n\n# Настройка фигуры\nplt.figure(figsize=(15, 10))\n\nfor i, idx in enumerate(random_indices):\n    # Восстановление изображения размера 11x11\n    image = X_train[idx].reshape(11, 11)\n    label = y_train[idx]\n\n    # Отображение изображения\n    plt.subplot(4, 5, i + 1)  # 4 строки, 5 столбцов\n    plt.imshow(image, cmap='gray', interpolation='none')\n    plt.title(f\"Label: {label}\")\n    plt.axis('off')\n\n# Показываем все изображения\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-09T06:26:14.462465Z","iopub.execute_input":"2024-12-09T06:26:14.462739Z","iopub.status.idle":"2024-12-09T06:26:15.821972Z","shell.execute_reply.started":"2024-12-09T06:26:14.462712Z","shell.execute_reply":"2024-12-09T06:26:15.821091Z"}},"outputs":[{"output_type":"display_data","data":{"text/plain":"<Figure size 1500x1000 with 20 Axes>","image/png":"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zpzo379/nHjiiQe9DyCHap5F+7uvO+64IzZv3hwXX3zxQW8P5FCtc6hHjx5RW1sb8+fPj127djVu3759eyxYsCD69eu33//lHcipWmfRh7n++utj9+7dMW7cuI90e6DlVfMcevXVV+Nzn/tctGvXLn7zm984oa+ZOOO2zO6555547LHHmmwfO3ZsXHbZZTFv3ry4/PLL49JLL43169fHnXfeGf3792/8D8De75RTTonzzjsvvv71r8fOnTtj6tSp0a1bt5gwYULjdW6//fY477zzYsCAAfG1r30t+vTpE5s2bYqlS5fGxo0b45lnnvnQtS5fvjwuvPDCmDhx4kG/ePqaa66JGTNmxJgxY2LNmjXRq1evuP/+++Oll16KBQsWHPoOAlpEa51FvXv3jpEjR8aAAQOiU6dO8cQTT8QDDzwQZ5xxRlxzzTWHvoOAZtca51D79u1j/Pjx8d3vfjfOOeecGDVqVOzZsyfuvvvu2LhxY8yePfvwdhLQ7FrjLIqI+NGPfhSrVq2KwYMHR01NTTz88MPxv/7X/4qbbropzj777EPfQUCza61z6OKLL44XX3wxJkyYEE888UQ88cQTjZcdf/zx8dnPfvYQ9g4HVVAW9957bxERH/rrP/7jP4qGhobiBz/4QdG7d++iY8eOxZlnnln8+te/Lq666qqid+/ejfe1fv36IiKKW265pZgyZUrxiU98oujYsWNx/vnnF88880yTx163bl0xatSo4oQTTig6dOhQnHTSScVll11WzJ07t/E6ixcvLiKiWLx4cZNtEydOPKTnuGnTpuKqq64qjj322KJjx47F4MGDi8cee+yj7jKgGbT2WfTVr3616N+/f9G1a9eiQ4cOxSmnnFJ861vfKt58880j2W1AGbX2OVQURTFnzpxi0KBBRW1tbdG5c+di8ODB+zwGUHmtfRb9+te/LgYNGlR07dq1OOqoo4pzzjmneOihh45klwFl1trn0IGe2wUXXHAEe473KxXF+87HBgAAAACg4nzHLQAAAABAMsItAAAAAEAywi0AAAAAQDLCLQAAAABAMsItAAAAAEAywi0AAAAAQDI1h3rFUqnUnOuAVq8oikovoeqZQ3BkzKHyMIvgyJhFR84cqpysr1+vicOT9c+x2njdwZE5lFnkjFsAAAAAgGSEWwAAAACAZIRbAAAAAIBkhFsAAAAAgGSE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hw+Pc889NyZNmhSlUikeeuihGDVqVNTX18e4ceMqvUSgDdi+fXtceOGFsW3btvjOd74THTp0iNtuuy0uuOCCePrpp6Nbt26VXiLQBkyaNCkmT54cdXV1MXr06HjnnXdi1apV8corr1R6aa2GcMshmTt3bgwdOnSfbWeddVZcddVVMWfOnPjqV79amYUBbdKOHTviuuuui29961tx4403Vno5QBsyffr06NmzZyxatCg6duwYERHXXHNN9OvXL+677z7hFmgRP/vZz2Lt2rWxfPnyOPvssyMi4pJLLolPfepTMWXKlPjBD35Q4RUCrd2TTz4ZkydPjilTpnj/04x8VUIL2rVrV9x4441x1llnxTHHHBNdunSJ888/PxYvXvyht7ntttuid+/e0blz57jgggti1apVTa6zevXqqKuri2OPPTY6deoUAwcOjEceeeSg63n77bdj9erVUV9ff9DrfjDaRkRcfvnlERHx3HPPHfT2QB7VPIv2+vGPfxwNDQ0xfvz4Q74NkEc1z6E333wzPv7xjzdG24iImpqaOO644/wLJKgy1TyL5s6dG2effXZjtI2I6NevX1x00UXx0EMPHfT2QA7VPIemTp0aJ5xwQowdOzaKoojt27cf9DYcPuG2Bb355psxY8aMGDp0aNx8880xadKk2Lx5cwwbNiyefvrpJtefNWtWTJs2LcaMGRPXX399rFq1Kj796U/Hpk2bGq/z7LPPxjnnnBPPPfdcfPvb344pU6ZEly5dYvjw4TF//vwDrmf58uVx2mmnxfTp0z/S83n11VcjIuK44477SLcHKqPaZ9HLL78cP/rRj+Lmm28WSaBKVfMcGjp0aDz77LNxww03xAsvvBDr1q2L73//+7Fy5cqYMGHCYe8LoHKqdRY1NDTEv/3bv8XAgQObXDZo0KBYt25dvPXWW4e2E4CKqtY5FBHx29/+Ns4+++yYNm1adO/ePbp27Ro9e/b8yI2JD1FQFvfee28REcWKFSs+9Dq7d+8udu7cuc+2119/vTj++OOLr3zlK43b1q9fX0RE0blz52Ljxo2N25ctW1ZERDFu3LjGbRdddFExYMCAYseOHY3bGhoaiiFDhhR9+/Zt3LZ48eIiIorFixc32TZx4sSP8pSLq6++umjfvn2xZs2aj3R7oPzawiyqq6srhgwZ0vj7iCjGjBlzSLcFml9rn0Pbt28vRowYUZRKpSIiiogojjrqqOLhhx8+6G2BltOaZ9HmzZuLiCgmT57c5LLbb7+9iIhi9erVB7wPoPm15jm0devWIiKKbt26FUcffXRxyy23FA8++GBx8cUXFxFR3HnnnQe8PYfOGbctqH379vGxj30sIt79KenWrVtj9+7dMXDgwHjqqaeaXH/48OFx0kknNf5+0KBBMXjw4Hj00UcjImLr1q2xaNGiGDFiRLz11ltRX18f9fX1sWXLlhg2bFisXbv2gF8IPXTo0CiKIiZNmnTYz+UXv/hF3H333XHddddF3759D/v2QOVU8yxavHhx/OpXv4qpU6ce3pMGUqnmOdSxY8c49dRTo66uLn75y1/G7NmzY+DAgXHllVfGk08+eZh7Aqikap1Ff/zjHyMi9vnKlr06deq0z3WA3Kp1Du39WoQtW7bEjBkzYvz48TFixIhYuHBh9O/fP2666abD3RV8COG2hc2cOTNOP/306NSpU3Tr1i26d+8eCxcujG3btjW57v6C6KmnnhobNmyIiIgXXnghiqKIG264Ibp3777Pr4kTJ0ZExGuvvVb25/C73/0urr766hg2bFj84z/+Y9nvH2h+1TiLdu/eHf/wD/8Qf/d3f7fP97kB1aka51BExLXXXhsLFiyIBx54IK644or48pe/HI8//nj07Nkzxo4dW5bHAFpONc6ivV8VtXPnziaX7dixY5/rAPlV8xzq0KFD1NXVNW5v165djBw5MjZu3Bgvv/zyET8OETWVXkBbMnv27Bg9enQMHz48vvnNb0aPHj2iffv28cMf/jDWrVt32PfX0NAQERHjx4+PYcOG7fc6p5xyyhGt+YOeeeaZ+MIXvhCf+tSnYu7cuVFT4yUE1aZaZ9GsWbPi+eefj7vuuqvxjcleb731VmzYsCF69OgRRx111BE/FtC8qnUO7dq1K+6+++6YMGFCtGv33vkPHTp0iEsuuSSmT58eu3btajxzBsitWmfRscceGx07dow//OEPTS7bu+3EE0884scBml81z6FOnTpFbW1ttG/ffp/LevToERERr7/+evTq1euIH6utU91a0Ny5c6NPnz4xb968KJVKjdv3/tTjg9auXdtk25o1a+Lkk0+OiIg+ffpExLsfFj7zmc+Uf8EfsG7durj44oujR48e8eijj8bRRx/d7I8JlF+1zqKXX3453nnnnfjL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"},"metadata":{}}],"execution_count":29},{"cell_type":"code","source":"# Гиперпараметры\ninput_size = X_train.shape[1]\nprint(input_size)\nhidden_size = 64\nhidden_size2 = 32  # Размер второго скрытого слоя\noutput_size = 10\nlearning_rate = 0.01\nbatch_size = 64","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-09T06:26:47.968777Z","iopub.execute_input":"2024-12-09T06:26:47.969117Z","iopub.status.idle":"2024-12-09T06:26:47.974673Z","shell.execute_reply.started":"2024-12-09T06:26:47.969087Z","shell.execute_reply":"2024-12-09T06:26:47.973654Z"}},"outputs":[{"name":"stdout","text":"121\n","output_type":"stream"}],"execution_count":30},{"cell_type":"code","source":"# Инициализация весов\nm = 500\nX_train.flatten()\nnp.random.seed(42)\nprint(input_size)\nW1 = (np.random.randn(input_size, hidden_size) * np.sqrt(1 / input_size)).astype('float32')\nW2 = (np.random.randn(hidden_size, hidden_size2) * np.sqrt(1 / hidden_size)).astype('float32')\nW3 = (np.random.randn(hidden_size2, output_size) * np.sqrt(1 / hidden_size2)).astype('float32')\nW1_out = np.zeros((input_size, hidden_size), dtype=np.float32)\nW2_out = np.zeros((hidden_size, hidden_size2), dtype=np.float32)\nW3_out = np.zeros((hidden_size2, output_size), dtype=np.float32)\n\n# Инициализация смещений\nb1 = np.zeros((1, hidden_size), dtype=np.float32)\nb2 = np.zeros((1, hidden_size2), dtype=np.float32)\nb3 = np.zeros((1, output_size), dtype=np.float32)\nb1_out = np.zeros((1, hidden_size), dtype=np.float32)\nb2_out = np.zeros((1, hidden_size2), dtype=np.float32)\nb3_out = np.zeros((1, output_size), dtype=np.float32)\n\n# Инициализация промежуточных матриц для прямого распространения\nZ1 = np.zeros((m, hidden_size), dtype=np.float32)  # Взвешенные суммы для 1-го слоя\nA1 = np.zeros((m, hidden_size), dtype=np.float32)  # Выходы 1-го слоя (ReLU)\n\nZ2 = np.zeros((m, hidden_size2), dtype=np.float32)  # Взвешенные суммы для 2-го слоя\nA2 = np.zeros((m, hidden_size2), dtype=np.float32)  # Выходы 2-го слоя (ReLU)\n\nZ3 = np.zeros((m, output_size), dtype=np.float32)  # Взвешенные суммы для выходного слоя\nA3 = np.zeros((m, output_size), dtype=np.float32)  # Выходы выходного слоя (softmax)\n\n# Инициализация матриц для обратного распространения\ndZ3 = np.zeros((m, output_size), dtype=np.float32)  # Ошибка на выходе\ndW3 = np.zeros((hidden_size2, output_size), dtype=np.float32)  # Градиент весов 3-го слоя\ndb3 = np.zeros((1, output_size), dtype=np.float32)  # Градиент смещений 3-го слоя\n\ndA2 = np.zeros((m, hidden_size2), dtype=np.float32)  # Ошибка на 2-м слое\ndZ2 = np.zeros((m, hidden_size2), dtype=np.float32)  # Ошибка активации 2-го слоя\ndW2 = np.zeros((hidden_size, hidden_size2), dtype=np.float32)  # Градиент весов 2-го слоя\ndb2 = np.zeros((1, hidden_size2), dtype=np.float32)  # Градиент смещений 2-го слоя\n\ndA1 = np.zeros((m, hidden_size), dtype=np.float32)  # Ошибка на 1-м слое\ndZ1 = np.zeros((m, hidden_size), dtype=np.float32)  # Ошибка активации 1-го слоя\ndW1 = np.zeros((input_size, hidden_size), dtype=np.float32)  # Градиент весов 1-го слоя\ndb1 = np.zeros((1, hidden_size), dtype=np.float32)  # Градиент смещений 1-го слоя\n\n# Промежуточные матрицы для ускорения вычислений\nZ1_biased = Z1 + b1  # Для удобства (например, при добавлении смещений)\nZ1_relu_deriv = np.zeros((m, hidden_size), dtype=np.float32)  # Производная ReLU\n\nZ2_biased = Z2 + b2  # Для удобства\nZ2_relu_deriv = np.zeros((m, hidden_size2), dtype=np.float32)  # Производная ReLU 2-го слоя\n\nZ3_biased = Z3 + b3  # Для удобства\n\ndW1_mult = np.zeros((input_size, hidden_size), dtype=np.float32)\ndW2_mult = np.zeros((hidden_size, hidden_size2), dtype=np.float32)\ndW3_mult = np.zeros((hidden_size2, output_size), dtype=np.float32)\ndb1_mult = np.zeros((1, hidden_size), dtype=np.float32)\ndb2_mult = np.zeros((1, hidden_size2), dtype=np.float32)\ndb3_mult = np.zeros((1, output_size), dtype=np.float32)\n\n# Для softmax\nrow_max = np.zeros((m, 1), dtype=np.float32)  # Максимум по строкам для стабильности softmax\nrow_sum_exp = np.zeros((m, 1), dtype=np.float32)  # Сумма экспонент для нормализации\n\n# Транспонированные версии матриц (для ускорения матричных операций)\nA1_t = A1.T\nW2_t = W2.T\nA2_t = A2.T\nW3_t = W3.T\nX_train_t = X_train.T","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-09T06:26:51.041982Z","iopub.execute_input":"2024-12-09T06:26:51.042306Z","iopub.status.idle":"2024-12-09T06:26:51.063321Z","shell.execute_reply.started":"2024-12-09T06:26:51.042276Z","shell.execute_reply":"2024-12-09T06:26:51.06241Z"}},"outputs":[{"name":"stdout","text":"121\n","output_type":"stream"}],"execution_count":31},{"cell_type":"code","source":"# Указываем пути к файлам\npath_W1 = '/kaggle/working/W1.csv'\npath_W2 = '/kaggle/working/W2.csv'\npath_b1 = '/kaggle/working/b1.csv'\npath_b2 = '/kaggle/working/b2.csv'\n\n# Загружаем матрицы из файлов\nW1 = np.loadtxt(path_W1, delimiter=',', dtype=np.float32)\nW2 = np.loadtxt(path_W2, delimiter=',', dtype=np.float32)\nb1 = np.loadtxt(path_b1, delimiter=',', dtype=np.float32)\nb2 = np.loadtxt(path_b2, delimiter=',', dtype=np.float32)\n\n# Проверяем размеры загруженных матриц (опционально)\nprint(\"W1 shape:\", W1.shape)\nprint(\"W2 shape:\", W2.shape)\nprint(\"b1 shape:\", b1.shape)\nprint(\"b2 shape:\", b2.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-08T17:17:05.570379Z","iopub.execute_input":"2024-12-08T17:17:05.57063Z","iopub.status.idle":"2024-12-08T17:17:05.972302Z","shell.execute_reply.started":"2024-12-08T17:17:05.570605Z","shell.execute_reply":"2024-12-08T17:17:05.971069Z"}},"outputs":[{"traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mFileNotFoundError\u001b[0m                         Traceback (most recent call last)","Cell \u001b[0;32mIn[6], line 8\u001b[0m\n\u001b[1;32m      5\u001b[0m path_b2 \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m'\u001b[39m\u001b[38;5;124m/kaggle/working/b2.csv\u001b[39m\u001b[38;5;124m'\u001b[39m\n\u001b[1;32m      7\u001b[0m \u001b[38;5;66;03m# Загружаем матрицы из файлов\u001b[39;00m\n\u001b[0;32m----> 8\u001b[0m W1 \u001b[38;5;241m=\u001b[39m \u001b[43mnp\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mloadtxt\u001b[49m\u001b[43m(\u001b[49m\u001b[43mpath_W1\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdelimiter\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43m,\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdtype\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mnp\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfloat32\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m      9\u001b[0m W2 \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39mloadtxt(path_W2, delimiter\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m,\u001b[39m\u001b[38;5;124m'\u001b[39m, dtype\u001b[38;5;241m=\u001b[39mnp\u001b[38;5;241m.\u001b[39mfloat32)\n\u001b[1;32m     10\u001b[0m b1 \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39mloadtxt(path_b1, delimiter\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m,\u001b[39m\u001b[38;5;124m'\u001b[39m, dtype\u001b[38;5;241m=\u001b[39mnp\u001b[38;5;241m.\u001b[39mfloat32)\n","File \u001b[0;32m/opt/conda/lib/python3.10/site-packages/numpy/lib/npyio.py:1373\u001b[0m, in \u001b[0;36mloadtxt\u001b[0;34m(fname, dtype, comments, delimiter, converters, skiprows, usecols, unpack, ndmin, encoding, max_rows, quotechar, like)\u001b[0m\n\u001b[1;32m   1370\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(delimiter, \u001b[38;5;28mbytes\u001b[39m):\n\u001b[1;32m   1371\u001b[0m     delimiter \u001b[38;5;241m=\u001b[39m delimiter\u001b[38;5;241m.\u001b[39mdecode(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mlatin1\u001b[39m\u001b[38;5;124m'\u001b[39m)\n\u001b[0;32m-> 1373\u001b[0m arr \u001b[38;5;241m=\u001b[39m \u001b[43m_read\u001b[49m\u001b[43m(\u001b[49m\u001b[43mfname\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdtype\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mdtype\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcomment\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mcomment\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdelimiter\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mdelimiter\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m   1374\u001b[0m \u001b[43m            \u001b[49m\u001b[43mconverters\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mconverters\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mskiplines\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mskiprows\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43musecols\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43musecols\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m   1375\u001b[0m \u001b[43m            \u001b[49m\u001b[43munpack\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43munpack\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mndmin\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mndmin\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mencoding\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mencoding\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m   1376\u001b[0m \u001b[43m            \u001b[49m\u001b[43mmax_rows\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mmax_rows\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mquote\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mquotechar\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m   1378\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m arr\n","File \u001b[0;32m/opt/conda/lib/python3.10/site-packages/numpy/lib/npyio.py:992\u001b[0m, in \u001b[0;36m_read\u001b[0;34m(fname, delimiter, comment, quote, imaginary_unit, usecols, skiplines, max_rows, converters, ndmin, unpack, dtype, encoding)\u001b[0m\n\u001b[1;32m    990\u001b[0m     fname \u001b[38;5;241m=\u001b[39m os\u001b[38;5;241m.\u001b[39mfspath(fname)\n\u001b[1;32m    991\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(fname, \u001b[38;5;28mstr\u001b[39m):\n\u001b[0;32m--> 992\u001b[0m     fh \u001b[38;5;241m=\u001b[39m \u001b[43mnp\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mlib\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_datasource\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mopen\u001b[49m\u001b[43m(\u001b[49m\u001b[43mfname\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43mrt\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mencoding\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mencoding\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m    993\u001b[0m     \u001b[38;5;28;01mif\u001b[39;00m encoding \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[1;32m    994\u001b[0m         encoding \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mgetattr\u001b[39m(fh, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mencoding\u001b[39m\u001b[38;5;124m'\u001b[39m, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mlatin1\u001b[39m\u001b[38;5;124m'\u001b[39m)\n","File \u001b[0;32m/opt/conda/lib/python3.10/site-packages/numpy/lib/_datasource.py:193\u001b[0m, in \u001b[0;36mopen\u001b[0;34m(path, mode, destpath, encoding, newline)\u001b[0m\n\u001b[1;32m    156\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m    157\u001b[0m \u001b[38;5;124;03mOpen `path` with `mode` and return the file object.\u001b[39;00m\n\u001b[1;32m    158\u001b[0m \n\u001b[0;32m   (...)\u001b[0m\n\u001b[1;32m    189\u001b[0m \n\u001b[1;32m    190\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m    192\u001b[0m ds \u001b[38;5;241m=\u001b[39m DataSource(destpath)\n\u001b[0;32m--> 193\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mds\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mopen\u001b[49m\u001b[43m(\u001b[49m\u001b[43mpath\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmode\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mencoding\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mencoding\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnewline\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mnewline\u001b[49m\u001b[43m)\u001b[49m\n","File \u001b[0;32m/opt/conda/lib/python3.10/site-packages/numpy/lib/_datasource.py:533\u001b[0m, in \u001b[0;36mDataSource.open\u001b[0;34m(self, path, mode, encoding, newline)\u001b[0m\n\u001b[1;32m    530\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m _file_openers[ext](found, mode\u001b[38;5;241m=\u001b[39mmode,\n\u001b[1;32m    531\u001b[0m                               encoding\u001b[38;5;241m=\u001b[39mencoding, newline\u001b[38;5;241m=\u001b[39mnewline)\n\u001b[1;32m    532\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m--> 533\u001b[0m     \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mFileNotFoundError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mpath\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m not found.\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n","\u001b[0;31mFileNotFoundError\u001b[0m: /kaggle/working/W1.csv not found."],"ename":"FileNotFoundError","evalue":"/kaggle/working/W1.csv not found.","output_type":"error"}],"execution_count":6},{"cell_type":"code","source":"context = cl.create_some_context()\nqueue = cl.CommandQueue(context)\n\n# OpenCL-программа для умножения матриц\nprogram_src = \"\"\"\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_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_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    const int BLOCK_SIZE = 32;\n\n    // Индексы внутри рабочей группы\n    int local_row = get_local_id(0);\n    int local_col = get_local_id(1);\n\n    // Глобальные индексы\n    int global_row = get_group_id(0) * BLOCK_SIZE + local_row;\n    int global_col = get_group_id(1) * BLOCK_SIZE + local_col;\n\n    // Проверка выхода за границы\n    if (global_row < rows && global_col < cols) {\n        int index = global_row * cols + global_col;\n        result[index] = mat[index] + bias[global_col];\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    const int BLOCK_SIZE = 32;\n\n    // Индексы внутри рабочей группы\n    int local_row = get_local_id(0);\n    int local_col = get_local_id(1);\n\n    // Глобальные индексы\n    int global_row = get_group_id(0) * BLOCK_SIZE + local_row;\n    int global_col = get_group_id(1) * BLOCK_SIZE + local_col;\n\n    // Проверка выхода за границы\n    if (global_row < rows && global_col < cols) {\n        int idx = global_row * cols + global_col;\n        result[idx] = fmax(0.0f, mat[idx]); // Применяем ReLU\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_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_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_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_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_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_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_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_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 copy_matrix_parallel(\n    const int rows,\n    const int cols,\n    __global const float* src,\n    __global float* dst\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        dst[idx] = src[idx]; // Копирование элемента\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 set_zero(\n    const int rows,\n    const int cols,\n    __global float* mat\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        mat[idx] = 0.0f; // Устанавливаем 0\n    }\n}\n\n__kernel void train_model(\n    __global const float *X_train, __global const float *y_train_encoded,\n    __global float *W1, __global float *W2, __global float *W3, \n    __global float *b1, __global float *b2, __global float *b3,\n    __global float *W1_out, __global float *W2_out, __global float *W3_out, \n    __global float *b1_out, __global float *b2_out, __global float *b3_out,\n    __global float *Z1, __global float *A1, \n    __global float *Z2, __global float *A2,\n    __global float *Z3, __global float *A3,\n    __global float *dZ3, __global float *dW3, __global float *db3, \n    __global float *dA2, __global float *dZ2, __global float *dW2, __global float *db2,  \n    __global float *dA1, __global float *dZ1, __global float *dW1, __global float *db1, \n    __global float *Z1_biased, __global float *Z2_biased, __global float *Z3_biased,\n    __global float *Z1_relu_deriv, __global float *Z2_relu_deriv,\n    __global float *A1_t, __global float *A2_t,\n    __global float *W2_t, __global float *W3_t,\n    __global float *X_train_t,\n    __global float *dW3_mult, __global float *db3_mult,\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 float *losses, \n    const unsigned int m,\n    const unsigned int input_size, const unsigned int hidden_size, const unsigned int hidden_size2,\n    const unsigned int output_size, 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, hidden_size2, hidden_size, A1, W2, Z2);  // Z2 = A1 * W2\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    add_bias_parallel(m, hidden_size2, Z2, b2, Z2_biased);  \n    barrier(CLK_GLOBAL_MEM_FENCE);\n    relu_parallel(m, hidden_size2, Z2_biased, A2);  // A2 = ReLU(Z2)\n    barrier(CLK_GLOBAL_MEM_FENCE);\n\n    matrix_multiply_parallel(m, output_size, hidden_size2, A2, W3, Z3);  // Z3 = A2 * W3\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    add_bias_parallel(m, output_size, Z3, b3, Z3_biased);  \n    barrier(CLK_GLOBAL_MEM_FENCE);\n    find_row_max_parallel(m, output_size, Z3_biased, row_max);  // Для softmax\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    compute_sum_exp_parallel(m, output_size, Z3_biased, row_max, row_sum_exp);  \n    barrier(CLK_GLOBAL_MEM_FENCE);\n    compute_softmax_parallel(m, output_size, Z3_biased, row_max, row_sum_exp, A3);  // A3 = Softmax(Z3)\n    barrier(CLK_GLOBAL_MEM_FENCE);\n\n    // Вычисление потерь (кросс-энтропия)\n    cross_entropy_parallel(m, output_size, A3, y_train_encoded, losses);\n    barrier(CLK_GLOBAL_MEM_FENCE);\n\n    // Обратное распространение\n    matrix_difference_parallel(m, output_size, A3, y_train_encoded, dZ3);  // dZ3 = A3 - y_train_encoded\n    barrier(CLK_GLOBAL_MEM_FENCE);\n\n    transpose_parallel(m, hidden_size2, A2, A2_t);  // Транспонированная матрица A2_t\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    matrix_multiply_parallel(hidden_size2, output_size, m, A2_t, dZ3, dW3);  // dW3 = A2^T * dZ3\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    divide_by_scalar_parallel(hidden_size2, output_size, dW3, m, dW3_mult);  // dW3 = dW3 / m\n    barrier(CLK_GLOBAL_MEM_FENCE);\n\n    column_sum_parallel(m, output_size, dZ3, db3);  // db3 = sum(dZ3, axis=0)\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    divide_by_scalar_parallel(m, output_size, db3, m, db3_mult);  // db3 = db3 / m\n    barrier(CLK_GLOBAL_MEM_FENCE);\n\n    transpose_parallel(hidden_size2, output_size, W3, W3_t);  // Транспонированная матрица W3_t\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    matrix_multiply_parallel(m, hidden_size2, output_size, dZ3, W3_t, dA2);  // dA2 = dZ3 * W3^T\n    barrier(CLK_GLOBAL_MEM_FENCE);\n\n    relu_derivative_parallel(m, hidden_size2, Z2_biased, Z2_relu_deriv);  // Z2 = ReLU'(Z2)\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    elementwise_multiply_parallel(m, hidden_size2, dA2, Z2_relu_deriv, dZ2);  // dZ2 = dA2 * Z2'\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, hidden_size2, m, A1_t, dZ2, dW2);  // dW2 = A1^T * dZ2\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    divide_by_scalar_parallel(hidden_size, hidden_size2, dW2, m, dW2_mult);  // dW2 = dW2 / m\n    barrier(CLK_GLOBAL_MEM_FENCE);\n\n    column_sum_parallel(m, hidden_size2, dZ2, db2);  // db2 = sum(dZ2, axis=0)\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    divide_by_scalar_parallel(m, hidden_size2, db2, m, db2_mult);  // db2 = db2 / m\n    barrier(CLK_GLOBAL_MEM_FENCE);\n\n    transpose_parallel(hidden_size, hidden_size2, W2, W2_t);  // Транспонированная матрица W2_t\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    matrix_multiply_parallel(m, hidden_size, hidden_size2, 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_mult);  // 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(m, 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_mult, learning_rate, dW1);  // W1 -= learning_rate * dW1\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    matrix_difference_parallel(input_size, hidden_size, W1, dW1, W1_out);  // W1_out = W1 - learning_rate * dW1\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    copy_matrix_parallel(input_size, hidden_size, W1_out, W1);\n    barrier(CLK_GLOBAL_MEM_FENCE);\n\n    mult_by_scalar_parallel(hidden_size, hidden_size2, dW2_mult, learning_rate, dW2);  // W2 -= learning_rate * dW2\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    matrix_difference_parallel(hidden_size, hidden_size2, W2, dW2, W2_out);  // W2_out = W2 - learning_rate * dW2\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    copy_matrix_parallel(hidden_size, hidden_size2, W2_out, W2);\n    barrier(CLK_GLOBAL_MEM_FENCE);\n\n    mult_by_scalar_parallel(hidden_size2, output_size, dW3_mult, learning_rate, dW3);  // W3 -= learning_rate * dW3\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    matrix_difference_parallel(hidden_size2, output_size, W3, dW3, W3_out);  // W3_out = W3 - learning_rate * dW3\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    copy_matrix_parallel(hidden_size2, output_size, W3_out, W3);\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, b1_out);  // b1_out = b1 - learning_rate * db1\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    copy_matrix_parallel(1, hidden_size, b1_out, b1);\n    barrier(CLK_GLOBAL_MEM_FENCE);\n\n    mult_by_scalar_parallel(1, hidden_size2, db2_mult, learning_rate, db2);  // b2 -= learning_rate * db2\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    matrix_difference_parallel(1, hidden_size2, b2, db2, b2_out);  // b2_out = b2 - learning_rate * db2\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    copy_matrix_parallel(1, hidden_size2, b2_out, b2);\n    barrier(CLK_GLOBAL_MEM_FENCE);\n\n    mult_by_scalar_parallel(1, output_size, db3_mult, learning_rate, db3);  // b3 -= learning_rate * db3\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    matrix_difference_parallel(1, output_size, b3, db3, b3_out);  // b3_out = b3 - learning_rate * db3\n    barrier(CLK_GLOBAL_MEM_FENCE);\n    copy_matrix_parallel(1, output_size, b3_out, b3);\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-09T06:26:55.182524Z","iopub.execute_input":"2024-12-09T06:26:55.182856Z","iopub.status.idle":"2024-12-09T06:26:56.403285Z","shell.execute_reply.started":"2024-12-09T06:26:55.182827Z","shell.execute_reply":"2024-12-09T06:26:56.402476Z"}},"outputs":[{"name":"stderr","text":"/opt/conda/lib/python3.10/site-packages/pyopencl/__init__.py:519: CompilerWarning: Non-empty compiler output encountered. Set the environment variable PYOPENCL_COMPILER_OUTPUT=1 to see more.\n  lambda: self._prg.build(options_bytes, devices),\n","output_type":"stream"}],"execution_count":32},{"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)) / y_true.shape[0]\n\n# Функция для предсказания\ndef predict(X, W1, W2, W3, b1, b2, b3):\n    Z1 = np.dot(X, W1) + b1\n    A1 = relu(Z1)\n    Z2 = np.dot(A1, W2) + b2\n    A2 = relu(Z2)\n    Z3 = np.dot(A2, W3) + b3\n    A3 = softmax(Z3)\n    return np.argmax(A3, 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\nall_losses = []\n\nX_train_test = X_train[(0*m):(1*m),:]\nprint(X_train_test.shape)\ny_train_encoded_test = y_train_encoded[(0*m):(1*m),]\nprint(y_train_encoded_test.shape)\n\nepochs = 6000\nfor epoch in range(epochs):\n    total_loss = 0.0\n    start_time = time.time()\n\n    idx = epoch // 100\n    X_train_test = X_train[(idx*m):((idx+1)*m),:]\n    y_train_encoded_test = y_train_encoded[(idx*m):((idx+1)*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_test.flatten())\n    \n    # Буферы для весов и смещений\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    W3_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=W3)\n    \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    b3_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=b3)\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    W3_out_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=W3_out)\n    \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    b3_out_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=b3_out)\n    \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    Z3_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=Z3)\n    A3_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=A3)\n    \n    # Градиенты\n    dZ3_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=dZ3)\n    dW3_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=dW3)\n    db3_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=db3)\n    \n    dA2_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=dA2)\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    \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    # Буферы для вспомогательных вычислений\n    Z1_biased_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=Z1_biased)\n    Z2_biased_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=Z2_biased)\n    Z3_biased_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=Z3_biased)\n    \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_relu_deriv_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=Z2_relu_deriv)\n    \n    A1_t_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=A1_t)\n    A2_t_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=A2_t)\n    W2_t_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=W2_t)\n    W3_t_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=W3_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    dW3_mult_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=dW3_mult)\n    db3_mult_cl = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.COPY_HOST_PTR, hostbuf=db3_mult)\n    \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    \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, (512, 512), (32, 32),\n        X_train_cl, y_train_encoded_cl,\n        W1_cl, W2_cl, W3_cl, \n        b1_cl, b2_cl, b3_cl,\n        W1_out_cl, W2_out_cl, W3_out_cl, \n        b1_out_cl, b2_out_cl, b3_out_cl,\n        Z1_cl, A1_cl, \n        Z2_cl, A2_cl,\n        Z3_cl, A3_cl,\n        dZ3_cl, dW3_cl, db3_cl, \n        dA2_cl, dZ2_cl, dW2_cl, db2_cl,  \n        dA1_cl, dZ1_cl, dW1_cl, db1_cl, \n        Z1_biased_cl, Z2_biased_cl, Z3_biased_cl,\n        Z1_relu_deriv_cl, Z2_relu_deriv_cl,\n        A1_t_cl, A2_t_cl,\n        W2_t_cl, W3_t_cl,\n        X_train_t_cl,\n        dW3_mult_cl, db3_mult_cl,\n        dW2_mult_cl, db2_mult_cl,\n        dW1_mult_cl, db1_mult_cl,\n        row_max_cl, row_sum_exp_cl,\n        losses_cl, \n        np.uint32(m),\n        np.uint32(input_size), np.uint32(hidden_size), np.uint32(hidden_size2),\n        np.uint32(output_size), np.float32(learning_rate)\n    )\n    # print(X_train_test)\n    # y_train_encoded_test = y_train_encoded[:m,:]\n    # Прямой проход\n    # Z1_test = np.dot(X_train_test, W1) + b1\n    # A1_test = relu(Z1_test)\n    # Z2_test = np.dot(A1_test, W2) + b2\n    # A2_test = relu(Z2_test)  # Активация второго слоя (ReLU)\n    # Z3_test = np.dot(A2_test, W3) + b3  # Добавляем третий слой\n    # A3_test = softmax(Z3_test)  # Выходной слой с softmax\n    \n    # # Вычисление потерь\n    # loss_test = cross_entropy_loss(y_train_encoded_test, A3_test)\n    \n    # # Обратное распространение\n    # dZ3_test = A3_test - y_train_encoded_test  # Ошибка для выходного слоя\n    # dW3_test = np.dot(A2_test.T, dZ3_test) / m  # Градиент для W3\n    # db3_test = np.sum(dZ3_test, axis=0, keepdims=True) / m  # Градиент для b3\n    \n    # # Ошибка для второго слоя\n    # dA2_test = np.dot(dZ3_test, W3.T)\n    # Z2_relu_deriv_test = relu_derivative(Z2_test)\n    # dZ2_test = dA2_test * Z2_relu_deriv_test\n    \n    # # Градиенты для второго слоя\n    # dW2_test = np.dot(A1_test.T, dZ2_test) / m\n    # db2_test = np.sum(dZ2_test, axis=0, keepdims=True) / m\n    \n    # # Ошибка для первого слоя\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    \n    # # Градиенты для первого слоя\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    # W3_test = W3 - learning_rate * dW3_test  # Обновление весов для третьего слоя\n    # b3_test = b3 - learning_rate * db3_test  # Обновление смещений для третьего слоя\n    \n    # # Копирование данных из буферов OpenCL обратно в хост\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    \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    \n    # cl.enqueue_copy(queue, Z3, Z3_cl).wait()\n    # cl.enqueue_copy(queue, Z3_biased, Z3_biased_cl).wait()\n    # cl.enqueue_copy(queue, A3, A3_cl).wait()\n    \n    # cl.enqueue_copy(queue, dZ3, dZ3_cl).wait()\n    # cl.enqueue_copy(queue, dW3, dW3_cl).wait()\n    # cl.enqueue_copy(queue, db3_mult, db3_mult_cl).wait()\n    \n    # cl.enqueue_copy(queue, dA2, dA2_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    \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    \n    cl.enqueue_copy(queue, losses, losses_cl).wait()\n    \n    # Копирование обновлённых весов и смещений\n    cl.enqueue_copy(queue, W1, W1_out_cl).wait()\n    cl.enqueue_copy(queue, W2, W2_out_cl).wait()\n    cl.enqueue_copy(queue, W3, W3_out_cl).wait()\n    \n    cl.enqueue_copy(queue, b1, b1_out_cl).wait()\n    cl.enqueue_copy(queue, b2, b2_out_cl).wait()\n    cl.enqueue_copy(queue, b3, b3_out_cl).wait()\n    \n    # if np.max(np.abs(Z1_biased - Z1_test)) < 1e-2:\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    # # Проверка A1\n    # if np.max(np.abs(A1 - A1_test)) < 1e-2:\n    #     print('A1 ok')\n    # else:\n    #     print('A1 not ok')\n    #     print('max diff:', np.max(np.abs(A1 - A1_test)))\n    #     print(A1)\n    #     print(A1_test)\n    \n    # # Проверка Z2\n    # if np.max(np.abs(Z2_biased - Z2_test)) < 1e-2:\n    #     print('Z2_biased ok')\n    # else:\n    #     print('Z2_biased not ok')\n    #     print('max diff:', np.max(np.abs(Z2_biased - Z2_test)))\n    #     print(Z2_biased-Z2_test)\n    #     # print(Z2_test)\n    \n    # # Проверка A2\n    # if np.max(np.abs(A2 - A2_test)) < 1e-2:\n    #     print('A2 ok')\n    # else:\n    #     print('A2 not ok')\n    #     print('max diff:', np.max(np.abs(A2 - A2_test)))\n    #     print(A2)\n    #     print(A2_test)\n    \n    # # Проверка Z3\n    # if np.max(np.abs(Z3_biased - Z3_test)) < 1e-2:\n    #     print('Z3_biased ok')\n    # else:\n    #     print('Z3_biased not ok')\n    #     print('max diff:', np.max(np.abs(Z3_biased - Z3_test)))\n    #     print(Z3_biased)\n    #     print(Z3_test)\n    \n    # # Проверка A3\n    # if np.max(np.abs(A3 - A3_test)) < 1e-2:\n    #     print('A3 ok')\n    # else:\n    #     print('A3 not ok')\n    #     print('max diff:', np.max(np.abs(A3 - A3_test)))\n    #     print(A3)\n    #     print(A3_test)\n    \n    # # Проверка dZ3\n    # if np.max(np.abs(dZ3 - dZ3_test)) < 1e-2:\n    #     print('dZ3 ok')\n    # else:\n    #     print('dZ3 not ok')\n    #     print('max diff:', np.max(np.abs(dZ3 - dZ3_test)))\n    #     print(dZ3)\n    #     print(dZ3_test)\n    \n    # # Проверка dW3\n    # if np.max(np.abs(dW3 - dW3_test)) < 1e-2:\n    #     print('dW3 ok')\n    # else:\n    #     print('dW3 not ok')\n    #     print('max diff:', np.max(np.abs(dW3 - dW3_test)))\n    #     print(dW3)\n    #     print(dW3_test)\n    \n    # # Проверка db3\n    # if np.max(np.abs(db3_mult - db3_test)) < 1e-2:\n    #     print('db3 ok')\n    # else:\n    #     print('db3 not ok')\n    #     print('max diff:', np.max(np.abs(db3_mult - db3_test)))\n    #     print(db3_mult)\n    #     print(db3_test)\n    \n    # # Проверка dA2\n    # if np.max(np.abs(dA2 - dA2_test)) < 1e-2:\n    #     print('dA2 ok')\n    # else:\n    #     print('dA2 not ok')\n    #     print('max diff:', np.max(np.abs(dA2 - dA2_test)))\n    #     print(dA2)\n    #     print(dA2_test)\n    \n    # # Проверка dW2\n    # if np.max(np.abs(dW2 - dW2_test)) < 1e-2:\n    #     print('dW2 ok')\n    # else:\n    #     print('dW2 not ok')\n    #     print('max diff:', np.max(np.abs(dW2 - dW2_test)))\n    #     print(dW2)\n    #     print(dW2_test)\n    \n    # # Проверка db2\n    # if np.max(np.abs(db2_mult - db2_test)) < 1e-2:\n    #     print('db2 ok')\n    # else:\n    #     print('db2 not ok')\n    #     print('max diff:', np.max(np.abs(db2_mult - db2_test)))\n    #     print(db2_mult)\n    #     print(db2_test)\n    \n    # # Проверка dA1\n    # if np.max(np.abs(dA1 - dA1_test)) < 1e-2:\n    #     print('dA1 ok')\n    # else:\n    #     print('dA1 not ok')\n    #     print('max diff:', np.max(np.abs(dA1 - dA1_test)))\n    #     print(dA1)\n    #     print(dA1_test)\n    \n    # # Проверка dW1\n    # if np.max(np.abs(dW1 - dW1_test)) < 1e-2:\n    #     print('dW1 ok')\n    # else:\n    #     print('dW1 not ok')\n    #     print('max diff:', np.max(np.abs(dW1 - dW1_test)))\n    #     print(dW1)\n    #     print(dW1_test)\n    \n    # # Проверка db1\n    # if np.max(np.abs(db1 - db1_test)) < 1e-2:\n    #     print('db1 ok')\n    # else:\n    #     print('db1 not ok')\n    #     print('max diff:', np.max(np.abs(db1 - db1_test)))\n    #     print(db1)\n    #     print(db1_test)\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\"Время выполнения: {100 * (end_time - start_time):.6f} секунд\")\n\n        # Получаем предсказания для тестовых данных\n        y_pred = predict(X_test, W1, W2, W3, b1, b2, b3)\n        \n        # Выбираем случайные 100 индексов из тестовых данных\n        random_indices = np.random.choice(X_test.shape[0], 100, replace=False)\n        \n        # Получаем подвыборку данных для случайных тестов\n        X_random = X_test[random_indices]\n        y_random_true = y_test[random_indices]\n        \n        # Получаем предсказания для случайной подвыборки\n        y_random_pred = predict(X_random, W1, W2, W3, b1, b2, b3)\n        \n        # Расчет точности на случайной подвыборке\n        acc_random = accuracy(y_random_true, y_random_pred)\n        \n        # Выводим точность на случайной подвыборке\n        print(f'Accuracy on random sample: {acc_random * 100:.2f}%')\n        \n        # Оценка точности на всех данных\n        acc_all = accuracy(y_test, y_pred)\n        print(f'Accuracy on full test data: {acc_all * 100:.2f}%')\n\n# График потерь\nplt.plot(all_losses)\nplt.xlabel(\"Epochs\")\nplt.ylabel(\"Loss\")\nplt.title(\"Training Loss Over Epochs\")\nplt.show()\n    \n\n","metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"vAI2cCrMThQE","outputId":"edf3d578-90a8-4a37-d942-c6c829e09aa9","trusted":true,"execution":{"iopub.status.busy":"2024-12-09T06:36:22.788073Z","iopub.status.idle":"2024-12-09T06:36:22.788512Z","shell.execute_reply.started":"2024-12-09T06:36:22.788288Z","shell.execute_reply":"2024-12-09T06:36:22.78831Z"}},"outputs":[],"execution_count":null},{"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    return -np.sum(y_true * np.log(y_pred + 1e-9)) / y_true.shape[0]\n\n# Функция для предсказания\ndef predict(X, W1, W2, W3, b1, b2, b3):\n    Z1 = np.dot(X, W1) + b1\n    A1 = relu(Z1)\n    Z2 = np.dot(A1, W2) + b2\n    A2 = relu(Z2)\n    Z3 = np.dot(A2, W3) + b3\n    A3 = softmax(Z3)\n    return np.argmax(A3, 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, W3, b1, b2, b3)\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# print(X_random[0:1,:])\n\n# Получаем предсказания для случайной подвыборки\ny_random_pred = predict(X_random, W1, W2, W3, b1, b2, b3)\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":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-09T06:33:34.642301Z","iopub.execute_input":"2024-12-09T06:33:34.642712Z","iopub.status.idle":"2024-12-09T06:33:34.664331Z","shell.execute_reply.started":"2024-12-09T06:33:34.642675Z","shell.execute_reply":"2024-12-09T06:33:34.662676Z"}},"outputs":[{"name":"stdout","text":"Accuracy on random sample: 78.00%\nAccuracy on full test data: 78.60%\n","output_type":"stream"}],"execution_count":36},{"cell_type":"code","source":"# Получаем предсказания для тестовых данных\ny_pred = predict(X_test, W1, W2, W3, b1, b2, b3)\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(11, 11)  # Преобразуем в 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-09T06:33:36.76552Z","iopub.execute_input":"2024-12-09T06:33:36.766157Z","iopub.status.idle":"2024-12-09T06:33:38.114166Z","shell.execute_reply.started":"2024-12-09T06:33:36.766127Z","shell.execute_reply":"2024-12-09T06:33:38.113271Z"}},"outputs":[{"output_type":"display_data","data":{"text/plain":"<Figure size 1000x1000 with 20 Axes>","image/png":"iVBORw0KGgoAAAANSUhEUgAAA94AAAO2CAYAAAAeyk8cAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuNSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/xnp5ZAAAACXBIWXMAAA9hAAAPYQGoP6dpAABfQUlEQVR4nO3daZhU5Z03/l9pIzRLiFG2CLa4jhAdE9GIS5Cg8ARwiShuo2jUYVScmDxKXIYkLonGZYTLqNFR8ZoEk9FkTDIuMEExapL/xAQTLxhNkAEjJgLuihqQvv8vfLrHohvopW6quurzuS5ecPqcU3fdfb7V59t1+lQhpZQCAAAAyGKrcg8AAAAAqpniDQAAABkp3gAAAJCR4g0AAAAZKd4AAACQkeINAAAAGSneAAAAkJHiDQAAABkp3gAAAJCR4t2KnXbaKU477bRyDwMqhkxAMZmAYjIBxWSCDVVc8b7rrruiUCg0/+vRo0fsvvvuMW3atFi5cmW5h7dZy5cvLxr/h//94Ac/aPN+Xnvttairq4t77rmnxdceffTRjT7Ghv8qUanmqFbIxAdkgiZdPRMbmjNnThQKhejdu3e7tqvmTGyoo3NUK6ohE88991wce+yxse2220bPnj3j4IMPjgULFrRrH9WeiVLMUa2ohkx84xvfiCOPPDIGDBgQhUIhvv71r7d7H42NjdGvX7+45pprWnxtU+ceG/5bvnx5559QBrfcckscd9xxseOOO0ahUKj4X3TUlXsAG3P55ZfH0KFD47333osnnngibrnllnjwwQdj0aJF0bNnz3IPb7NOPPHEGD9+fNGykSNHtnn7efPmRaFQiLFjx7b42p577hnf/e53i5ZdfPHF0bt377j00ks7NuAy6Owc1RqZkAmKdfVMRES8/fbbMX369OjVq1e7t62FTER0bo5qTVfNxAsvvBAjR46MrbfeOi688MLo1atXzJ49O8aOHRsPP/xwfOYzn2nTfqo5E6Wao1rTVTMREfFP//RPMXDgwPjkJz8Z8+bN69A+fv3rX8fLL78cEyZMaPG1fv36tcjE9ddfHytWrIgbbrihxbqV6Fvf+la89dZbsf/++8df/vKXcg9n81KFmT17doqI9OSTTxYt//KXv5wiIt19990b3fbtt98uyRgaGhrSlClTOrTtsmXLUkSka6+9tlNjOOWUU9KoUaPavP7w4cM3u/769evTu+++26lxlUKp5qhWyMQHZIImXT0TH/aVr3wl7bHHHunkk09OvXr1ate21ZyJD+vMHNWKrp6Jc845J9XV1aVnn322edmaNWvSkCFD0qc+9ak276eaM1GqOaoVXT0TKX1wbpBSSqtXr04Rkb72ta+1ex8zZsxIDQ0NbV5/woQJm12/sbExvfPOO+0eSw7Lly9PjY2NKaWUevXqVZKfyzlV3KXmG/PZz342IiKWLVsWERGnnXZa9O7dO5YuXRrjx4+PPn36xMknnxwRH1xWMXPmzBg+fHj06NEjBgwYEFOnTo3XXnutaJ8ppbjyyitj8ODB0bNnzxg9enQsXry41cdfunRpLF26tF1jXrNmTaxdu7a9TzUaGxtj7ty5rf52qj0KhUJMmzYt5syZE8OHD4/u3bvH3Llzmy+3evTRR4vWb7rk5K677ipa/uyzz8axxx4bH/vYx6JHjx4xYsSI+OlPf9ri8bbkHCETHSET1a2rZWLJkiVxww03xD//8z9HXV37LkCrlUx0Zo7oOpl4/PHH45Of/GTssccezct69uwZRx55ZCxcuDCWLFmy2X1UeyZKMUd0nUxEfPA34p31wAMPdDoTO+20U0ycODHmzZsXI0aMiPr6+rj11ls3euxHRKuXxr/44ovxhS98IQYMGBDdu3eP4cOHx5133tli2z/96U/x7LPPtmlsDQ0NFf3nIRvqMsW76SDdbrvtmpe9//77MW7cuOjfv39cd911MWnSpIiImDp1alx44YVx0EEHxaxZs+L000+POXPmxLhx42LdunXN23/1q1+NGTNmxN/+7d/GtddeGzvvvHOMHTs21qxZ0+Lxx4wZE2PGjGnzeC+77LLo3bt39OjRI/bbb7/4z//8zzZv++STT8bq1atbXHLaEY888kh86UtfiuOPPz5mzZrV7hAvXrw4DjjggHjmmWfioosuiuuvvz569eoVRx99dNx3331F627JOUImOkomqldXy8T5558fo0eP7tBxXSuZ6Mwc0XUy8de//jXq6+tbLG+6FPi3v/3tZvdR7ZkoxRzRdTJRCi+99FI89dRTJcnEH/7whzjxxBPj8MMPj1mzZsU+++zTru1XrlwZBxxwQMyfPz+mTZsWs2bNil133TXOOOOMmDlzZtG6p556auy5556dHnNFKuv77a1oujRk/vz5afXq1emFF15IP/jBD9J2222X6uvr04oVK1JKKU2ZMiVFRLrooouKtn/88cdTRKQ5c+YULZ87d27R8lWrVqVtttkmTZgwofkShZRSuuSSS1JEtLhUoaGhoU2Xajz//PNp7Nix6ZZbbkk//elP08yZM9OOO+6Yttpqq3T//fe3aQ7ae1lISq1fLhURaauttkqLFy8uWr5gwYIUEWnBggVFy5sud509e3bzsjFjxqS99torvffee83LGhsb04EHHph22223ou235BzVEpmQCYp19UyklNL999+f6urqmo/FKVOmtOsy6mrPREqdn6Na0tUzccQRR6SPfvSj6c033yxaPnLkyBQR6brrrtvsPqo9E6WYo1rS1TPxYR291PyOO+5I9fX17bosvLVLzRsaGlJEpLlz5xYtb+3Yb7LheM8444w0aNCg9PLLLxetd8IJJ6S+ffsWjXHUqFGpIxW1K1xqXrHFe8N/DQ0NRd/wpqA8//zzRdv/4z/+Y+rbt29atWpVWr16ddG/3r17pzPPPDOllNLdd9/d6kG0atWqVoPSGa+88koaMGBA2mOPPdq0/r777pvOOeecdj3Gxn54jB49usW6bf3h8corr6RCoZCuuOKKFnN52WWXpYhofuHqrPbOUS2RCZmgWFfPxF//+te02267pWnTphWNtT2lstozUYo5qiVdPRMPPvhgioj0uc99Li1cuDD94Q9/SF/84hdTt27dUkSkK664YrP7qPZMlGKOaklXz8SHdbR4T5o0KY0fP75d22yseA8dOrTFum0t3o2NjemjH/1o+vu///sWc9n0fXriiSfaNc7WdIXiXbF/MHXTTTfF7rvvHnV1dTFgwIDYY489Yqutiq+Mr6uri8GDBxctW7JkSbzxxhvRv3//Vve7atWqiIh4/vnnIyJit912K/p6v379Ytttty3V04iIiI997GNx+umnx9VXXx0rVqxoMeYPe+mll2LhwoVx+eWXl+Sxhw4d2uFtn3vuuUgpxYwZM2LGjBmtrrNq1arYYYcdOvwYTdozR7VKJmSCYl01EzfccEO8/PLLcdlll3Vo+1rIRGfnqFZ11Ux87nOfixtvvDEuuuii+NSnPhUREbvuumt84xvfiOnTp2/2Y+RqIROdnaNa1VUz0Vnr1q2Ln/3sZ3HVVVeVZH+dycTq1avj9ddfj9tuuy1uu+22Vtdpms9qV7HFe//9948R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PbSK1NUzEfHB8XzNNdfE0KFDo0ePHrH33nvH97///Xbt47XXXou6urq45557Wnzt0UcfbXMmKlUp5qhWVEMmnnvuuTj22GNj2223jZ49e8bBBx8cCxYsaNc+qjkTX//61zc55l/84hflHmJFqYZM/OUvf4m///u/j6FDh0Z9fX3ssssu8eUvfzleeeWVNu+jmjMRUZo5qhXVkAnnTpvX1c6d6so9gI25/PLLY+jQofHee+/FE088Ebfccks8+OCDsWjRoujZs2e5h7dRt99+e9xxxx0xadKkOOecc+KNN96IW2+9NQ444ICYO3duHHbYYW3az69//et4+eWXY8KECS2+1q9fv/jud79btOz666+PFStWxA033NBi3Urzk5/8JL71rW/F0UcfHVOmTIn3338//vVf/zUOP/zwuPPOO+P0008v9xArUlfNRETEpZdeGldffXWcddZZsd9++8VPfvKTOOmkk6JQKMQJJ5zQpn3MmzcvCoVCjB07tsXX9txzzxaZuPjii6N3795x6aWXluQ55FaKOao1XTUTL7zwQowcOTK23nrruPDCC6NXr14xe/bsGDt2bDz88MPxmc98pk37qeZMHHPMMbHrrru2WH7JJZfE22+/Hfvtt18ZRlX5umom3n777Rg5cmSsWbMmzjnnnBgyZEj8/ve/j29/+9uxYMGC+O1vfxtbbbX594qqOROlmqNa01UzEeHcqS263LlTqjCzZ89OEZGefPLJouVf/vKXU0Sku+++e6Pbvv322yUZQ0NDQ5oyZUqHtv3Nb36T3nrrraJlL7/8curXr1866KCD2ryfGTNmpIaGhjavP2HChM2u39jYmN5555027zOXRYsWpdWrVxcte++999Lf/M3fpMGDB5dpVJWrq2dixYoVqVu3buncc89tXtbY2JgOOeSQNHjw4PT++++3aT+nnHJKGjVqVJsfd/jw4Ztdf/369endd99t8z5zKdUc1Yqunolzzjkn1dXVpWeffbZ52Zo1a9KQIUPSpz71qTbvp5oz0Zo//elPqVAopLPOOqvcQ6k4XT0Tc+bMSRGR7r///qLlX/3qV1NEpIULF7ZpP9WciVLNUa3o6plw7rR5XfHcqcv8auyzn/1sREQsW7YsIiJOO+206N27dyxdujTGjx8fffr0iZNPPjkiPrjsYObMmTF8+PDo0aNHDBgwIKZOnRqvvfZa0T5TSnHllVfG4MGDo2fPnjF69OhYvHhxq4+/dOnSWLp06WbHue+++0bv3r2Llm233XZxyCGHxDPPPNPm5/vAAw+0+m53e+y0004xceLEmDdvXowYMSLq6+vj1ltvbb5U/a677mqxTaFQiK9//etFy1588cX4whe+EAMGDIju3bvH8OHD484772yx7Z/+9Kd49tlnNzuu4cOHx/bbb1+0rHv37jF+/PhYsWJFvPXWW+16nrWqq2TiJz/5Saxbty7OOeec5mWFQiHOPvvsWLFiRfzqV7/a7D4aGxtj7ty5nc5EoVCIadOmxZw5c2L48OHRvXv3mDt3bvPlVo8++mjR+hvLyrPPPhvHHntsfOxjH4sePXrEiBEj4qc//WmLx9uSc0TXycTjjz8en/zkJ2OPPfZoXtazZ8848sgjY+HChbFkyZLN7qPaM9Ga73//+5FSav4esnldJRNvvvlmREQMGDCgaPmgQYMiIqK+vn6z+6j2TJRijug6mXDuVJ3nThV7qfmGmr4B2223XfOy999/P8aNGxcHH3xwXHfddc2XjEydOjXuuuuuOP300+Mf//EfY9myZfHtb387nnrqqfjFL34R3bp1i4iIr371q3HllVfG+PHjY/z48bFw4cIYO3ZsrF27tsXjjxkzJiKiw38z/dJLL7Uom5ta96mnnorLL7+8Q4/1YX/4wx/ixBNPjKlTp8ZZZ51VdKLXFitXrowDDjigOXT9+vWLhx56KM4444x488034/zzz29e99RTT42f//znkVLq0Fhfeuml6NmzZ8Vf+lMpukomnnrqqejVq1fsueeeRcv333//5q8ffPDBm9zHk08+GatXr47x48dvcr22eOSRR+Kee+6JadOmxfbbbx877bRTvP76623efvHixXHQQQfFDjvsEBdddFH06tUr7rnnnjj66KPjRz/6UXz+859vXndLzhFdJxN//etfY9ttt22xvGlsv/3tb2O33Xbb5D6qPROtmTNnTgwZMqTNl+LTdTLxmc98Jrbaaqv44he/GNdff30MHjw4nn766fjGN74RRx99dPzN3/zNZp9rtWeiFHNE18mEc6cqPXcq47vtrWq6NGT+/Plp9erV6YUXXkg/+MEP0nbbbZfq6+vTihUrUkopTZkyJUVEuuiii4q2f/zxx1NEpDlz5hQtnzt3btHyVatWpW222SZNmDAhNTY2Nq93ySWXpIhocWlIQ0NDuy79/rDHHnssFQqFNGPGjDatf8cdd6T6+vp2XRbe2qXmDQ0NKSLS3Llzi5YvW7YsRUSaPXt2i/1ERPra177W/P8zzjgjDRo0KL388stF651wwgmpb9++RWMcNWpU6ughtWTJktSjR490yimndGj7atbVMzFhwoS08847t1i+Zs2aVsfbmvb+6UVKrV8uFRFpq622SosXLy5avmDBghQRacGCBUXLW8vKmDFj0l577ZXee++95mWNjY3pwAMPTLvttlvR9ltyjmpJV8/EEUcckT760Y+mN998s2j5yJEjU0Sk6667brP7qPZMbGjRokUpItL06dPbvW0t6OqZSCml22+/PX30ox9NEdH8b8qUKWndunVt2r4WMtHZOaolXT0Tzp02P+6ueO5UsZeaH3bYYdGvX78YMmRInHDCCdG7d++47777Yocddiha7+yzzy76/7333ht9+/aNww8/PF5++eXmf02XgDfdNXb+/Pmxdu3aOO+884ru1vfhd3A/bPny5R36Df2qVavipJNOiqFDh8b06dPbtM2DDz4Yo0ePLsllQ0OHDo1x48Z1aNuUUvzoRz+KI444IlJKRfM5bty4eOONN2LhwoXN6z/66KMderf7nXfeieOOOy7q6+vj6quv7tBYa0FXzcS7774b3bt3b7G8R48ezV/fnAcffLDTl0o1GTVqVAwbNqxD27766qvxyCOPxOTJk+Ott95qnstXXnklxo0bF0uWLIkXX3yxef0tOUe1qKtm4uyzz47XX389jj/++Hjqqafij3/8Y5x//vnNn34hEy3NmTMnIsJl5pvRVTMREbHDDjvE/vvvHzNnzoz77rsvvvzlL8ecOXPioosuatP2tZCJzs5RLeqqmXDutHyz++2K504Ve6n5TTfdFLvvvnvU1dXFgAEDYo899mhxt8a6uroYPHhw0bIlS5bEG2+8Ef379291v6tWrYqIiOeffz4iosXlfP369Wv1EsCOWLNmTUycODHeeuuteOKJJ1r87Xdr1q1bFz/72c/iqquuKskYhg4d2uFtV69eHa+//nrcdtttcdttt7W6TtN8dtT69evjhBNOiP/+7/+Ohx56KD7+8Y93an/VrKtmor6+Pv7617+2WP7ee+81f31TXnrppVi4cGFJ/vQionOZeO655yKlFDNmzIgZM2a0us6qVata/EDfnM7OUa3qqpn43Oc+FzfeeGNcdNFF8alPfSoiInbdddf4xje+EdOnT9/sz4payMSHpZTi7rvvjk984hOx9957d3g/taCrZuIXv/hFTJw4Mf6//+//ixEjRkRExNFHHx0f+chH4rLLLosvfOELmzzpr4VMdHaOalVXzYRzp83riudOFVu8999//+YXlo3p3r17i/A0NjZG//79m387vqEt9fFaa9eujWOOOSaefvrpmDdvXnziE59o03ZPPPFEvPnmmyX5e4yI1g+6jX0e3/r164v+39jYGBERf/d3fxdTpkxpdZvOngSdddZZcf/998ecOXOab3hB67pqJgYNGhQLFiyIlFLRsfeXv/wlImKzv2x56KGHokePHjF69OiSjKcUmbjgggs2eiVJax+BtDmdnaNa1VUzERExbdq0OP300+Ppp5+ObbbZJvbZZ5+44447IiJi99133+S2tZCJD/vFL34Rzz//fMl+IV3Numombr311hgwYECLsR955JHx9a9/PX75y19uslTWQiY6O0e1qqtmwrnT5nXFc6eKLd4dtcsuu8T8+fPjoIMO2uRvOhoaGiLig99o7bzzzs3LV69e3eJuhe3V2NgYp556ajz88MNxzz33xKhRo9q87QMPPBDDhg2LnXbaqVNj2JSm38BteFOEpt/aNenXr1/06dMn1q9f3+bPH2+PCy+8MGbPnh0zZ86ME088seT75wPlzsQ+++wTt99+ezzzzDNFJwX/9V//1fz1TXnggQdK9qcXG9PWTDTNS7du3Uqaic7OEe1T7kw06dWrV4wcObL5//Pnz4/6+vo46KCDNrldLWTiw+bMmROFQiFOOumkLPun/JlYuXJli5P1iA+uAoz44OZXm1ILmejsHNE+5c6Ec6fN64rnThX7N94dNXny5Fi/fn1cccUVLb72/vvvNx8chx12WHTr1i1uvPHGor9LnjlzZqv7bc9HoJx33nnxb//2b3HzzTfHMccc067xl/LvMTbmIx/5SGy//fbx2GOPFS2/+eabi/6/9dZbx6RJk+JHP/pRLFq0qMV+Vq9eXfT/tn6cWETEtddeG9ddd11ccskl8cUvfrGdz4D2KHcmjjrqqOjWrVvR8ZVSiu985zuxww47xIEHHrjRbZv+9CJ3JhoaGmLrrbfebCb69+8fhx56aNx6663Nv1H9sA0zsSXmiPYrdyZa88tf/jL+/d//Pc4444zo27fvRterlUw0WbduXdx7771x8MEHx4477tiOZ0B7lDsTu+++e6xcubLFxxJ9//vfj4iIT37ykxvdtlYy0Zk5ov3KnQnnTtV57lR173iPGjUqpk6dGldddVX87ne/i7Fjx0a3bt1iyZIlce+998asWbPi2GOPjX79+sUFF1wQV111VUycODHGjx8fTz31VDz00EOtfuxXW29tP3PmzLj55ptj5MiR0bNnz/je975X9PXPf/7z0atXr1a3XbZsWTzzzDNxyy23dOzJt8OZZ54ZV199dZx55pkxYsSIeOyxx+KPf/xji/WuvvrqWLBgQXz605+Os846K4YNGxavvvpqLFy4MObPnx+vvvpq87pt/Tix++67L6ZPnx677bZb7Lnnni3m6PDDD2/xOZV0XLkzMXjw4Dj//PPj2muvjXXr1sV+++0XP/7xj+Pxxx+POXPmxNZbb73RbZv+9CL3D4++ffvGcccdFzfeeGMUCoXYZZdd4v7772/1HgY33XRTHHzwwbHXXnvFWWedFTvvvHOsXLkyfvWrX8WKFSvi97//ffO6W2KOaL9yZ+L555+PyZMnx5FHHhkDBw6MxYsXx3e+853Ye++945vf/OYmt62VTDSZN29evPLKK26qllm5MzFt2rSYPXt2HHHEEXHeeedFQ0ND/PznP4/vf//7cfjhh8enP/3pjW5bK5nozBzRfuXOhHOnKj132pK3UG+Lptv/P/nkk5tcb8qUKalXr14b/fptt92W9t1331RfX5/69OmT9tprrzR9+vT05z//uXmd9evXp8suuywNGjQo1dfXp0MPPTQtWrQoNTQ0dPj2/00fS7Cxf8uWLdvott/+9rdT3759O/SxEBv7OLEJEya0uv4777yTzjjjjNS3b9/Up0+fNHny5LRq1aoWHyeWUkorV65M5557bhoyZEjq1q1bGjhwYBozZky67bbbitZr68eJfe1rX9vkHG34sQS1rqtnomm/3/zmN1NDQ0PaZptt0vDhw9P3vve9zW53wQUXpGHDhrXpMTa0sY/EOPfcc1tdf/Xq1WnSpEmpZ8+eadttt01Tp05t/gijDT96b+nSpenUU09NAwcOTN26dUs77LBDmjhxYvrhD39YtN6WmKNa1NUz8eqrr6ajjjoqDRw4MG2zzTZp6NCh6Stf+UqLjxdrTS1l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AAAAkJHiDQAAABkp3gAAAJCR4g0AAAAZKd4AAACQkeINAAAAGRVSSqncgwAAAIBq5R1vAAAAyEjxBgAAgIwUbwAAAMhI8QYAAICMFG8AAADISPEGAACAjBRvAAAAyEjxBgAAgIwUbwAAAMhI8QYAAICMFG8AAADISPEGAACAjBRvAAAAyEjxBgAAgIwUbwAAAMhI8QYAAICMFG8AAADISPEGAACAjBRvAAAAyEjxBgAAgIwUbwAAAMhI8QYAAICMFG8AAADISPEGAACAjBRvAAAAyEjxBgAAgIwUbwAAAMhI8QYAAICMFG8AAADISPEGAACAjBRvAAAAyEjxBgAAgIwUbwAAAMhI8QYAAICMFG8AAADISPEGAACAjBRvAAAAyEjxBgAAgIwU71bstNNOcdppp5V7GFAxZAKKyQQUkwkoJhNsqOKK91133RWFQqH5X48ePWL33XePadOmxcqVK8s9vM169tlnY/r06bHPPvtEnz59YtCgQTFhwoT4zW9+0679NDY2Rr9+/eKaa65p8bXly5cXzdGm/i1fvrxEz6x0Xnjhhbjsssti//33j2233Ta23377OPTQQ2P+/PnlHlpF6uqZiIj4xje+EUceeWQMGDAgCoVCfP3rX2/3Pqo5ExERt9xySxx33HGx4447RqFQ8MN6E6ohE42NjXHNNdfE0KFDo0ePHrH33nvH97///Xbt47XXXou6urq45557Wnzt0UcfbXMmKlUp5qhWdPVMbOo1/Ac/+EGb91PNmSjVHNWKrp6JCOdObdHVzp3qyj2Ajbn88stj6NCh8d5778UTTzwRt9xySzz44IOxaNGi6NmzZ7mHt1G333573HHHHTFp0qQ455xz4o033ohbb701DjjggJg7d24cdthhbdrPr3/963j55ZdjwoQJLb7Wr1+/+O53v1u07Prrr48VK1bEDTfc0GLdSvOTn/wkvvWtb8XRRx8dU6ZMiffffz/+9V//NQ4//PC488474/TTTy/3ECtSV81ERMQ//dM/xcCBA+OTn/xkzJs3r0P7qOZMRER861vfirfeeiv233//+Mtf/lLu4XQJXTkTl156aVx99dVx1llnxX777Rc/+clP4qSTTopCoRAnnHBCm/Yxb968KBQKMXbs2BZf23PPPVtk4uKLL47evXvHpZdeWpLnkFsp5qjWdOVMRESceOKJMX78+KJlI0eObPP21Z6JiM7PUa3pyplw7rR5Xe7cKVWY2bNnp4hITz75ZNHyL3/5yyki0t13373Rbd9+++2SjKGhoSFNmTKlQ9v+5je/SW+99VbRspdffjn169cvHXTQQW3ez4wZM1JDQ0Ob158wYcJm129sbEzvvPNOm/eZy6JFi9Lq1auLlr333nvpb/7mb9LgwYPLNKrK1dUzkVJKy5YtSymltHr16hQR6Wtf+1q791HNmUgppeXLl6fGxsaUUkq9evXq1HxXu66eiRUrVqRu3bqlc889t3lZY2NjOuSQQ9LgwYPT+++/36b9nHLKKWnUqFFtftzhw4dvdv3169end999t837zKVUc1Qrunomli1bliIiXXvttZ0aQzVnolRzVCu6eiZScu7UFl3t3KniLjXfmM9+9rMREbFs2bKIiDjttNOid+/esXTp0hg/fnz06dMnTj755Ij44LKKmTNnxvDhw6NHjx4xYMCAmDp1arz22mtF+0wpxZVXXhmDBw+Onj17xujRo2Px4sWtPv7SpUtj6dKlmx3nvvvuG7179y5att1228UhhxwSzzzzTJuf7wMPPNDqb6faY6eddoqJEyfGvHnzYsSIEVFfXx+33npr86Uld911V4ttWruU5cUXX4wvfOELMWDAgOjevXsMHz487rzzzhbb/ulPf4pnn312s+MaPnx4bL/99kXLunfvHuPHj48VK1bEW2+91a7nWau6SiYiPjgWO6uaMxER0dDQULGXOHYVXSUTP/nJT2LdunVxzjnnNC8rFApx9tlnx4oVK+JXv/rVZvfR2NgYc+fO7XQmCoVCTJs2LebMmRPDhw+P7t27x9y5c5svy3300UeL1t9YVp599tk49thj42Mf+1j06NEjRowYET/96U9bPN6WnCO6TiY+bM2aNbF27dr2PtWqz8SHdXSO6FqZcO60eV3t3KnLFO+mg3S77bZrXvb+++/HuHHjon///nHdddfFpEmTIiJi6tSpceGFF8ZBBx0Us2bNitNPPz3mzJkT48aNi3Xr1jVv/9WvfjVmzJgRf/u3fxvXXntt7LzzzjF27NhYs2ZNi8cfM2ZMjBkzpsPjf+mll1qUzU2t+9RTT7W4lKgj/vCHP8SJJ54Yhx9+eMyaNSv22Wefdm2/cuXKOOCAA2L+/Pkxbdq0mDVrVuy6665xxhlnxMyZM4vWPfXUU2PPPffs8Fhfeuml6NmzZ8Vf+lMpunom2qNWM0H7dJVMPPXUU9GrV68Wx8b+++/f/PXNefLJJ2P16tUlycQjjzwSX/rSl+L444+PWbNmtftkb/HixXHAAQfEM888ExdddFFcf/310atXrzj66KPjvvvuK1p3S84RXScTTS677LLo3bt39OjRI/bbb7/4z//8zzZvW+2ZaNKZOaLrZaIznDtVoLK+396KpktD5s+fn1avXp1eeOGF9IMf/CBtt912qb6+Pq1YsSKllNKUKVNSRKSLLrqoaPvHH388RUSaM2dO0fK5c+cWLV+1alXaZptt0oQJE5ovUUgppUsuuSRFRItLFRoaGtp1qcaHPfbYY6lQKKQZM2a0af077rgj1dfXt+syjtYuDWloaEgRkebOnVu0vOlypdmzZ7fYT2xwKcsZZ5yRBg0alF5++eWi9U444YTUt2/fojGOGjUqdfSQWrJkSerRo0c65ZRTOrR9NaumTHT0cqlay0RXuFyqnLp6JiZMmJB23nnnFsvXrFnT6nhb097LB1Nq/bLaiEhbbbVVWrx4cdHyBQsWpIhICxYsKFreWlbGjBmT9tprr/Tee+81L2tsbEwHHnhg2m233Yq235JzVEu6eiaef/75NHbs2HTLLbekn/70p2nmzJlpxx13TFtttVW6//772zQH1Z6JUsxRLenqmfgw505t0xXOnSr2He/DDjss+vXrF0OGDIkTTjghevfuHffdd1/ssMMOReudffbZRf+/9957o2/fvnH44YfHyy+/3Pyv6RLwBQsWRETE/PnzY+3atXHeeecVXaJw/vnntzqe5cuXd+iOfqtWrYqTTjophg4dGtOnT2/TNg8++GCMHj066uvr2/14Gxo6dGiMGzeuQ9umlOJHP/pRHHHEEZFSKprPcePGxRtvvBELFy5sXv/RRx+NlFK7H+edd96J4447Lurr6+Pqq6/u0FhrQbVkoiNqLRO0TVfNxLvvvhvdu3dvsbxHjx7NX9+cBx98sNOXDzYZNWpUDBs2rEPbvvrqq/HII4/E5MmT46233mqey1deeSXGjRsXS5YsiRdffLF5/S05R7Woq2Zixx13jHnz5sU//MM/xBFHHBFf/OIX46mnnop+/frF//2//7dNz73aM1GKOapFXTUTpeDcqfJU7F3Nb7rppth9992jrq4uBgwYEHvssUdstVXx7wnq6upi8ODBRcuWLFkSb7zxRvTv37/V/a5atSoiIp5//vmIiNhtt92Kvt6vX7/YdtttS/Ic1qxZExMnToy33nornnjiiRZ/+92adevWxc9+9rO46qqrSjKGoUOHdnjb1atXx+uvvx633XZb3Hbbba2u0zSfHbV+/fo44YQT4r//+7/joYceio9//OOd2l81q4ZMdEStZYK266qZqK+vj7/+9a8tlr/33nvNX9+Ul156KRYuXBiXX355h8fwYZ3JxHPPPRcppZgxY0bMmDGj1XVWrVrV4iR3czo7R7Wqq2aiNR/72Mfi9NNPj6uvvjpWrFjRYswfVguZaE175qhWVVMm2sO5U2Wq2OK9//77x4gRIza5Tvfu3VuEp7GxMfr37x9z5sxpdZstdTv8tWvXxjHHHBNPP/10zJs3Lz7xiU+0absnnngi3nzzzZL8PUZE6ycnG7sJwfr164v+39jYGBERf/d3fxdTpkxpdZu99967U+M766yz4v777485c+Y03/CC1nX1THRUrWWCtuuqmRg0aFAsWLAgUkpFx17TR6Fs7heQDz30UPTo0SNGjx5dkvGUIhMXXHDBRt8N2XXXXds9ps7OUa3qqpnYmCFDhkTEB+8ib6pU1kImNqatc1Srqi0TbeXcqTJVbPHuqF122SXmz58fBx100CZ/I97Q0BARH/xGa+edd25evnr16hZ3K2yvxsbGOPXUU+Phhx+Oe+65J0aNGtXmbR944IEYNmxYSe5kuDFNv4F7/fXXi5Y3/dauSb9+/aJPnz6xfv36Nn/+eHtceOGFMXv27Jg5c2aceOKJJd8/H6iETHRGLWWCLaPcmdhnn33i9ttvj2eeeaboctb/+q//av76pjzwwAMlu3xwY9qaiaZ56datW0kz0dk5on3KnYmN+Z//+Z+I2HzJqYVMbExb54j2qdRMtJVzp8pUsX/j3VGTJ0+O9evXxxVXXNHia++//37zwXHYYYdFt27d4sYbbyz6O4IN76zXpD23/z/vvPPi3/7t3+Lmm2+OY445pl3jL+XfKG3MRz7ykdh+++3jscceK1p+8803F/1/6623jkmTJsWPfvSjWLRoUYv9rF69uuj/7bn9/7XXXhvXXXddXHLJJfHFL36xnc+A9qiETHRGrWSCLafcmTjqqKOiW7duRcdXSim+853vxA477BAHHnjgRrdtunwwdyYaGhpi66233mwm+vfvH4ceemjceuutze9Gf9iGmdgSc0T7lTsTGx4nER989NCdd94Ze++9dwwaNGij29ZKJjozR7RfuTPRWc6dKlPVveM9atSomDp1alx11VXxu9/9LsaOHRvdunWLJUuWxL333huzZs2KY489Nvr16xcXXHBBXHXVVTFx4sQYP358PPXUU/HQQw+1+rFfTbf+39wNEWbOnBk333xzjBw5Mnr27Bnf+973ir7++c9/Pnr16tXqtsuWLYtnnnkmbrnllo49+XY488wz4+qrr44zzzwzRowYEY899lj88Y9/bLHe1VdfHQsWLIhPf/rTcdZZZ8WwYcPi1VdfjYULF8b8+fPj1VdfbV731FNPjZ///OebvSHCfffdF9OnT4/ddtst9txzzxZzdPjhh8eAAQNK80QpeyYiIr773e/G888/H++8805ERDz22GNx5ZVXRkTEKaec0vwb4w3VSiYiIv7jP/4jfv/730fEByeSTz/9dPMcHXnkkTVzGdaWUO5MDB48OM4///y49tprY926dbHffvvFj3/843j88cdjzpw5sfXWW29026bLB3OfUPXt2zeOO+64uPHGG6NQKMQuu+wS999/f6t/h3fTTTfFwQcfHHvttVecddZZsfPOO8fKlSvjV7/6VaxYsaL5uI7YMnNE+5U7E9OnT4+lS5fGmDFj4uMf/3gsX748br311lizZk3MmjVrk9vWSiY6M0e0X7kzEeHcqSrPnbbgHdTbpOn2/08++eQm15syZUrq1avXRr9+2223pX333TfV19enPn36pL322itNnz49/fnPf25eZ/369emyyy5LgwYNSvX19enQQw9NixYtSg0NDR2+/X/TxxJs7N+yZcs2uu23v/3t1Ldv37Ru3brNPs6GNnb7/wkTJrS6/jvvvJPOOOOM1Ldv39SnT580efLktGrVqlY/rmDlypXp3HPPTUOGDEndunVLAwcOTGPGjEm33XZb0Xptvf3/1772tU3O0YYf1VHrunomUvrfY6O93+9ayURKm37taO2jOmpZNWRi/fr16Zvf/GZqaGhI22yzTRo+fHj63ve+t9ntLrjggjRs2LA2PcaGNvbRSeeee26r669evTpNmjQp9ezZM2277bZp6tSpadGiRa0ek0uXLk2nnnpqGjhwYOrWrVvaYYcd0sSJE9MPf/jDovW2xBzVoq6eibvvvjt9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0hg/fnz06dMnTj755IiIaGxsjJkzZ8bw4cOjR48eMWDAgJg6dWq89tprRftMKcWVV14ZgwcPjp49e8bo0aNj8eLFrT7+0qVLY+nSpW0e75IlS+KGG26If/7nf466uvZdWNDY2Bhz586NCRMmtGu7DRUKhZg2bVrMmTMnhg8fHt27d4+5c+c2X1ry6KOPFq2/fPnyKBQKcddddxUtf/bZZ+PYY4+Nj33sY9GjR48YMWJE/PSnP23xeG2do3333bfFJbTbbbddHHLIIfHMM8+0+3nWKplov0rNRJPOzBFdJxP/+Z//GYsWLYqvfe1rUV9fH++8806sX7++Xc+12jNRijlCJjpCJqpbV8lEa77//e9HSql5fJtT7Zl4/PHH45Of/GTssccezct69uwZRx55ZCxcuDCWLFnSoeebU5cp3k3fgO2226552fvvvx/jxo2L/v37x3XXXReTJk2KiIipU6fGhRdeGAcddFDMmjUrTj/99JgzZ06MGzcu1q1b17z9V7/61ZgxY0b87d/+bVx77bWx8847x9ixY2PNmjUtHn/MmDExZsyYNo/3/PPPj9GjR8f48ePb/VyffPLJWL16dYe23dAjjzwSX/rSl+L444+PWbNmxU477dSu7RcvXhwHHHBAPPPMM3HRRRfF9ddfH7169Yqjjz467rvvvqJ12ztHG3rppZdi++237/D2tUYmOqaSM9GZOaLrZGL+/PkREdG9e/cYMWJE9OrVK3r27BknnHBCvPrqq216rtWeiVLMETLRUTJRvbpKJlozZ86cGDJkSJsvoa72TPz1r3+N+vr6Fsub/oTgt7/9bbvGuEWU8+321jRdGjJ//vy0evXq9MILL6Qf/OAHabvttkv19fVpxYoVKaWUpkyZkiIiXXTRRUXbP/744yki0pw5c4qWz507t2j5qlWr0jbbbJMmTJiQGhsbm9e75JJLUkS0uDSkoaEhNTQ0tOk53H///amuri4tXry4eaztuWR0xowZbX6sJq1dGhIRaauttmoeR5MFCxakiEgLFiwoWr5s2bIUEWn27NnNy8aMGZP22muv9N577zUva2xsTAceeGDabbfdirZvzxxt6LHHHkuFQiHNmDGjQ9tXM5mojUx0do5qSVfPxJFHHpkiIm233Xbp5JNPTj/84Q/TjBkzUl1dXTrwwAOLHmtjqj0TpZijWiITMiETxbp6Jja0aNGiFBFp+vTpbd6m2jNxxBFHpI9+9KPpzTffLFo+cuTIFBHpuuuu2+w+trSKfcf7sMMOi379+sWQIUPihBNOiN69e8d9990XO+ywQ9F6Z599dtH/77333ujbt28cfvjh8fLLLzf/a7q8uelOd/Pnz4+1a9fGeeedV3S3vvPPP7/V8SxfvjyWL1++2XGvXbs2vvSlL8U//MM/xLBhw9r3pP+fBx98sNOXhTQZNWpUh8fx6quvxiOPPBKTJ0+Ot956q3kuX3nllRg3blwsWbIkXnzxxeb12zpHG1q1alWcdNJJMXTo0Jg+fXqHxloLZKJ6M1GKOapFXTUTb7/9dkRE7LfffvG9730vJk2aFJdffnlcccUV8ctf/jIefvjhze6j2jNRijmqRTIhExTrqpnY0Jw5cyIi2nyZeUT1Z+Lss8+O119/PY4//vh46qmn4o9//GOcf/75zZ+S9O6773ZovDlV7B8R3nTTTbH77rtHXV1dDBgwIPbYY4/Yaqvi3xPU1dXF4MGDi5YtWbIk3njjjejfv3+r+121alVERDz//PMREbHbbrsVfb1fv36x7bbbdnjcN9xwQ7z88stx2WWXdWj7l156KRYuXBiXX355h8fwYUOHDu3wts8991yklGLGjBkxY8aMVtdZtWpVixev9lizZk1MnDgx3nrrrXjiiSd8fNImyET1ZqKzc1Srumommi6NO/HEE4uWn3TSSXHxxRfHL3/5y01+ukMtZKKzc1SrZEImKNZVM/FhKaW4++674xOf+ETsvffebdqmFjLxuc99Lm688ca46KKL4lOf+lREROy6667xjW98I6ZPn16RnaJii/f+++8fI0aM2OQ63bt3bxGexsbG6N+/f/NvhjbUr1+/ko1xQ2+88UZceeWVcc4558Sbb74Zb775ZkR88FvKlFIsX748evbsudEQR0Q89NBD0aNHjxg9enRJxtTa3z5s7PP4NrxJR2NjY0REXHDBBTFu3LhWt2ntow7aau3atXHMMcfE008/HfPmzYtPfOITHd5XLZCJ6sxEKeaoVnXFTEREfPzjH4+IiAEDBhQtb/oeb3jjng1VeyYiOj9HtUomZIJiXTUTH/aLX/winn/++bjqqqvavE0tZCIiYtq0aXH66afH008/Hdtss03ss88+cccdd0RExO67796hfeZUscW7o3bZZZeYP39+HHTQQa0eJE0aGhoi4oPfaO28887Ny1evXt3hF6/XXnst3n777bjmmmvimmuuafH1oUOHxlFHHRU//vGPN7qPBx54IEaPHr3JsXdW02/gXn/99aLlTb+1a9I0L926dSv5b1EbGxvj1FNPjYcffjjuueeeGDVqVEn3z/+Sic0rZyZKMUe0TzkzEfHBJzv8y7/8S9GldRERf/7znyNi8yd01Z6JiM7PEe0jE5snE7Wl3Jn4sDlz5kShUIiTTjqpzdvUQiaa9OrVK0aOHNn8//nz50d9fX0cdNBBJX+szqrYv/HuqMmTJ8f69evjiiuuaPG1999/v/ngOOyww6Jbt25x4403RkqpeZ2ZM2e2ut+23Nq+f//+cd9997X4N3r06OjRo0fcd999cfHFF290+3Xr1sXPfvazkv09xsY0NDTE1ltvHY899ljR8ptvvrno//37949DDz00br311vjLX/7SYj+rV68u+n97PiLhvPPOi3/7t3+Lm2++OY455ph2PgPaQyY2r5yZ6Owc0X7lzERExFFHHRXdu3eP2bNnN78TEBFx++23R0TE4YcfvtFtayETEZ2bI9pPJjZPJmpLuTPRZN26dXHvvffGwQcfHDvuuGObt6mFTLTml7/8Zfz7v/97nHHGGdG3b98O7SOnqnvHe9SoUTF16tS46qqr4ne/+12MHTs2unXrFkuWLIl77703Zs2aFccee2z069cvLrjggrjqqqti4sSJMX78+HjqqafioYceavUjrZpua7+pP/bv2bNnHH300S2W//jHP45f//rXrX7tw5544ol48803swelb9++cdxxx8WNN94YhUIhdtlll7j//vub/17lw2666aY4+OCDY6+99oqzzjordt5551i5cmX86le/ihUrVsTvf//75nXbMkcRH7wY3XzzzTFy5Mjo2bNnfO973yv6+uc///no1atX558oESETbVHOTHR2jmi/cmYiImLgwIFx6aWXxle/+tX4P//n/8TRRx8dv//97+Nf/uVf4sQTT4z99ttvo9vWQiYiOjdHtJ9MbJ5M1JZyZ6LJvHnz4pVXXmnXTdVqJRPPP/98TJ48OY488sgYOHBgLF68OL7zne/E3nvvHd/85jdL+lxLphy3Ut+Uptv/P/nkk5tcb3MftXPbbbelfffdN9XX16c+ffqkvfbaK02fPj39+c9/bl5n/fr16bLLLkuDBg1K9fX16dBDD02LFi1KDQ0NJbv9f1vG2uSCCy5Iw4YN69BjbOz2/+eee26r669evTpNmjQp9ezZM2277bZp6tSpzR9V8OHb/6eU0tKlS9Opp56aBg4cmLp165Z22GGHNHHixPTDH/6waL22zlHTRzds7N+yZcva8cyrn0xUfyZa4+PENq4aMtHY2JhuvPHGtPvuu6du3bqlIUOGpH/6p39Ka9eu3eR2tZSJjs5RLZIJmaBYNWQipZROOOGE1K1bt/TKK6+0eZtaycSrr76ajjrqqDRw4MC0zTbbpKFDh6avfOUrLT5erJIUUvrQdRGU1bBhw2LixImt/p0n1CKZgGIyAcVkAorJROWqukvNu6q1a9fG8ccfH5MnTy73UKAiyAQUkwkoJhNQTCYqm3e8AQAAIKOqu6s5AAAAVBLFGwAAADJSvAEAACAjxRsAAAAyavNdzQuFQs5x0A6luB+e7+cHOjOX5pAPq4T7VJbimJSJylAtx1O16Oj3wxxSjfycgJbakgvveAMAAEBGijcAAABkpHgDAABARoo3AAAAZKR4AwAAQEaKNwAAAGSkeAMAAEBGijcAAABkpHgDAABARoo3AAAAZKR4AwAAQEaKNwAAAGSkeAMAAEBGijcAAABkpHgDAABARnXlHgAAVJuUUrmHEIVCodxDAD6kFK8Lcg1dl+INNU5BAACAvFxqDgAAABkp3gAAAJCR4g0AAAAZKd4AAACQkeINAAAAGSneAAAAkJHiDQAAABkp3gAAAJCR4g0AAAAZKd4AAACQkeINAAAAGSneAAAAkJHiDQAAABkp3gAAAJBRXbkHAAAUKxQK5R4CVJWUUrmHUBKdfR5eW6pHJRzTpTieSvE8uspxrXhDF1YtL7qVwMkMAAC5uNQcAAAAMlK8AQAAICPFGwAAADJSvAEAACAjxRsAAAAyUrwBAAAgI8UbAAAAMlK8AQAAICPFGwAAADJSvAEAACAjxRsAAAAyUrwBAAAgI8UbAAAAMlK8AQAAICPFGwAAADKqK/cAAKCSpJTKPQRgA9WQy0KhUO4hUCKVcDyW4njq7PMoxTzUUi4U7zKohLB2dgy1FBIqXyVkCgAANsal5gAAAJCR4g0AAAAZKd4AAACQkeINAAAAGSneAAAAkJHiDQAAABkp3gAAAJCR4g0AAAAZKd4AAACQkeINAAAAGSneAAAAkJHiDQAAABkp3gAAAJCR4g0AAAAZKd4AAACQUV25B7ClpZTKPYSqUAnzWCgUyj2ETqmEOSyFankeVAfHI1SeasllVz/voLpUS65qiXe8AQAAICPFGwAAADJSvAEAACAjxRsAAAAyUrwBAAAgI8UbAAAAMlK8AQAAICPFGwAAADJSvAEAACAjxRsAAAAyUrwBAAAgI8UbAAAAMlK8AQAAICPFGwAAADJSvAEAACCjunIPAACqTaFQ6NT2KaUSjaS8OjsPUEkqIZcyVRmq5fvQ2WO6WuZhS+lSxbsSXvCqhaCUXym+B5WQCc8DAAA2zaXmAAAAkJHiDQAAABkp3gAAAJCR4g0AAAAZKd4AAACQkeINAAAAGSneAAAAkJHiDQAAABkp3gAAAJCR4g0AAAAZKd4AAACQkeINAAAAGSneAAAAkJHiDQAAABkp3gAAAJBRXbkHQPsVCoVyD4EK4Vj4gHmoHimlcg+hJDr7PEpxTFfLXFJ+jqUPdDaXpZjHSnhtoTKU4nhyPGxZ3vEGAACAjBRvAAAAyEjxBgAAgIwUbwAAAMhI8QYAAICMFG8AAADISPEGAACAjBRvAAAAyEjxBgAAgIwUbwAAAMhI8QYAAICMFG8AAADISPEGAACAjBRvAAAAyEjxBgAAgIzqyj0AAKBYSqnT+ygUCmUfA+Xn+/iBzuYBSk02a0+XKt5eNKHyVEJBAACASuZScwAAAMhI8QYAAICMFG8AAADISPEGAACAjBRvAAAAyEjxBgAAgIwUbwAAAMhI8QYAAICMFG8AAADISPEGAACAjBRvAAAAyEjxBgAAgIwUbwAAAMhI8QYAAICMFG8AAADIqK7cAwDKK6VU7iEAGXQ224VCoUQjoZxK8X2shJ8TnX0elfAcoJS8Rnc93vEGAACAjBRvAAAAyEjxBgAAgIwUbwAAAMhI8QYAAICMFG8AAADISPEGAACAjBRvAAAAyEjxBgAAgIwUbwAAAMhI8QYAAICMFG8AAADISPEGAACAjBRvAAAAyEjxBgAAgIwUbwAAAMiortwDADoupVTuIUShUCj3EKginT2eKiETpSBXUKwSsi2XlJLjqfZ4xxsAAAAyUrwBAAAgI8UbAAAAMlK8AQAAICPFGwAAADJSvAEAACAjxRsAAAAyUrwBAAAgI8UbAAAAMlK8AQAAICPFGwAAADJSvAEAACAjxRsAAAAyUrwBAAAgI8UbAAAAMqor9wCAjisUCuUeAlQUmYDSkyuAzvOONwAAAGSkeAMAAEBGijcAAABkpHgDAABARoo3AAAAZKR4AwAAQEaKNwAAAGSkeAMAAEBGijcAAABkpHgDAABARoo3AAAAZKR4AwAAQEaKNwAAAGSkeAMAAEBGijcAAABkpHgDAABARoWUUir3IAAAAKBaeccbAAAAMlK8AQAAICPFGwAAADJSvAEAACAjxRsAAAAyUrwBAAAgI8UbAAAAMlK8AQAAICPFGwAAADL6/wGxpRM0V/OMrwAAAABJRU5ErkJggg=="},"metadata":{}}],"execution_count":37},{"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},"outputs":[],"execution_count":null},{"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},"outputs":[],"execution_count":null},{"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},"outputs":[],"execution_count":null},{"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},"outputs":[],"execution_count":null},{"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},"outputs":[],"execution_count":null},{"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},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import sys\nnp.set_printoptions(threshold=sys.maxsize)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-08T20:19:29.744216Z","iopub.execute_input":"2024-12-08T20:19:29.74493Z","iopub.status.idle":"2024-12-08T20:19:29.748757Z","shell.execute_reply.started":"2024-12-08T20:19:29.744898Z","shell.execute_reply":"2024-12-08T20:19:29.747817Z"}},"outputs":[],"execution_count":48},{"cell_type":"code","source":"np.savetxt('/kaggle/working/W1.csv', W1, delimiter=',')\nnp.savetxt('/kaggle/working/W2.csv', W2, delimiter=',')\nnp.savetxt('/kaggle/working/W3.csv', W3, delimiter=',')\nnp.savetxt('/kaggle/working/b1.csv', b1, delimiter=',')\nnp.savetxt('/kaggle/working/b2.csv', b2, delimiter=',')\nnp.savetxt('/kaggle/working/b3.csv', b3, delimiter=',')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-08T22:00:28.759788Z","iopub.execute_input":"2024-12-08T22:00:28.760709Z","iopub.status.idle":"2024-12-08T22:00:28.776672Z","shell.execute_reply.started":"2024-12-08T22:00:28.76067Z","shell.execute_reply":"2024-12-08T22:00:28.775901Z"}},"outputs":[],"execution_count":162},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}