diff --git a/Modify.ipynb b/Modify.ipynb new file mode 100644 index 0000000..4f9c612 --- /dev/null +++ b/Modify.ipynb @@ -0,0 +1,696 @@ +{ + "cells": [ + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-24T17:37:28.323528Z", + "start_time": "2025-06-24T17:37:28.321407Z" + } + }, + "cell_type": "code", + "source": "# Modify.py\n", + "id": "98da35e8f6af6b7a", + "outputs": [], + "execution_count": 9 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-24T17:37:28.355899Z", + "start_time": "2025-06-24T17:37:28.352650Z" + } + }, + "cell_type": "code", + "source": [ + "import os\n", + "import random\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "import deepquantum as dq\n", + "import matplotlib.pyplot as plt\n", + "import torch\n", + "import torch.nn as nn\n", + "import torch.optim as optim\n", + "import torchvision.transforms as transforms\n", + "from torchvision.datasets import FashionMNIST\n", + "from tqdm import tqdm\n", + "from torch.utils.data import DataLoader\n", + "from multiprocessing import freeze_support\n" + ], + "id": "fba02718b5ce470f", + "outputs": [], + "execution_count": 10 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-24T17:37:28.376643Z", + "start_time": "2025-06-24T17:37:28.373463Z" + } + }, + "cell_type": "code", + "source": [ + "def seed_torch(seed=1024):\n", + " random.seed(seed)\n", + " os.environ['PYTHONHASHSEED'] = str(seed)\n", + " np.random.seed(seed)\n", + " torch.manual_seed(seed)\n", + " torch.cuda.manual_seed(seed)\n", + " torch.cuda.manual_seed_all(seed)\n", + " torch.backends.cudnn.benchmark = False\n", + " torch.backends.cudnn.deterministic = True\n" + ], + "id": "e21c1ebc100b5079", + "outputs": [], + "execution_count": 11 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-24T17:37:28.406416Z", + "start_time": "2025-06-24T17:37:28.403413Z" + } + }, + "cell_type": "code", + "source": [ + "def calculate_score(y_true, y_preds):\n", + " preds_prob = torch.softmax(y_preds, dim=1)\n", + " preds_class = torch.argmax(preds_prob, dim=1)\n", + " correct = (preds_class == y_true).float()\n", + " return (correct.sum() / len(correct)).cpu().numpy()\n" + ], + "id": "f70cc647d264747", + "outputs": [], + "execution_count": 12 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-24T17:37:28.439020Z", + "start_time": "2025-06-24T17:37:28.433485Z" + } + }, + "cell_type": "code", + "source": [ + "def train_model(model, criterion, optimizer, scheduler, train_loader, valid_loader, num_epochs, device, save_path):\n", + " model.to(device)\n", + " best_acc = 0.0\n", + " metrics = {'epoch': [], 'train_acc': [], 'valid_acc': [], 'train_loss': [], 'valid_loss': []}\n", + "\n", + " for epoch in range(1, num_epochs + 1):\n", + " # --- 训练阶段 ---\n", + " model.train()\n", + " running_loss, running_acc = 0.0, 0.0\n", + " for imgs, labels in train_loader:\n", + " imgs, labels = imgs.to(device), labels.to(device)\n", + " optimizer.zero_grad()\n", + " outputs = model(imgs)\n", + " loss = criterion(outputs, labels)\n", + " loss.backward()\n", + " torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)\n", + " optimizer.step()\n", + " running_loss += loss.item()\n", + " running_acc += calculate_score(labels, outputs)\n", + "\n", + " train_loss = running_loss / len(train_loader)\n", + " train_acc = running_acc / len(train_loader)\n", + " scheduler.step()\n", + "\n", + " # --- 验证阶段 ---\n", + " model.eval()\n", + " val_loss, val_acc = 0.0, 0.0\n", + " with torch.no_grad():\n", + " for imgs, labels in valid_loader:\n", + " imgs, labels = imgs.to(device), labels.to(device)\n", + " outputs = model(imgs)\n", + " loss = criterion(outputs, labels)\n", + " val_loss += loss.item()\n", + " val_acc += calculate_score(labels, outputs)\n", + "\n", + " valid_loss = val_loss / len(valid_loader)\n", + " valid_acc = val_acc / len(valid_loader)\n", + "\n", + " metrics['epoch'].append(epoch)\n", + " metrics['train_loss'].append(train_loss)\n", + " metrics['valid_loss'].append(valid_loss)\n", + " metrics['train_acc'].append(train_acc)\n", + " metrics['valid_acc'].append(valid_acc)\n", + "\n", + " tqdm.write(f\"[{save_path}] Epoch {epoch}/{num_epochs} \"\n", + " f\"Train Acc: {train_acc:.4f} Valid Acc: {valid_acc:.4f}\")\n", + "\n", + " if valid_acc > best_acc:\n", + " best_acc = valid_acc\n", + " torch.save(model.state_dict(), save_path)\n", + "\n", + " return model, metrics\n" + ], + "id": "7cfe89d63ab0e68e", + "outputs": [], + "execution_count": 13 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-24T17:37:28.467170Z", + "start_time": "2025-06-24T17:37:28.463678Z" + } + }, + "cell_type": "code", + "source": [ + "def test_model(model, test_loader, device):\n", + " model.to(device).eval()\n", + " acc = 0.0\n", + " with torch.no_grad():\n", + " for imgs, labels in test_loader:\n", + " imgs, labels = imgs.to(device), labels.to(device)\n", + " outputs = model(imgs)\n", + " acc += calculate_score(labels, outputs)\n", + " acc /= len(test_loader)\n", + " print(f\"Test Accuracy: {acc:.4f}\")\n", + " return acc\n" + ], + "id": "235ac16bd786e65f", + "outputs": [], + "execution_count": 14 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-24T17:37:28.506986Z", + "start_time": "2025-06-24T17:37:28.496003Z" + } + }, + "cell_type": "code", + "source": [ + "singlegate_list = ['rx','ry','rz','s','t','p','u3']\n", + "doublegate_list = ['rxx','ryy','rzz','swap','cnot','cp','ch','cu','ct','cz']\n", + "\n", + "class RandomQuantumConvolutionalLayer(nn.Module):\n", + " def __init__(self, nqubit, num_circuits, seed=1024):\n", + " super().__init__()\n", + " random.seed(seed)\n", + " self.nqubit = nqubit\n", + " self.cirs = nn.ModuleList([self.circuit(nqubit) for _ in range(num_circuits)])\n", + " def circuit(self, nqubit):\n", + " cir = dq.QubitCircuit(nqubit)\n", + " cir.rxlayer(encode=True); cir.barrier()\n", + " for _ in range(3):\n", + " for i in range(nqubit):\n", + " getattr(cir, random.choice(singlegate_list))(i)\n", + " c,t = random.sample(range(nqubit),2)\n", + " gate = random.choice(doublegate_list)\n", + " if gate[0] in ['r','s']:\n", + " getattr(cir, gate)([c,t])\n", + " else:\n", + " getattr(cir, gate)(c,t)\n", + " cir.barrier()\n", + " cir.observable(0)\n", + " return cir\n", + " def forward(self, x):\n", + " k,s = 2,2\n", + " x_unf = x.unfold(2,k,s).unfold(3,k,s)\n", + " w = (x.shape[-1]-k)//s + 1\n", + " x_r = x_unf.reshape(-1, self.nqubit)\n", + " exps = []\n", + " for cir in self.cirs:\n", + " cir(x_r)\n", + " exps.append(cir.expectation())\n", + " exps = torch.stack(exps,1).reshape(x.size(0), len(self.cirs), w, w)\n", + " return exps\n", + "\n", + "class RandomQCCNN(nn.Module):\n", + " def __init__(self):\n", + " super().__init__()\n", + " self.conv = nn.Sequential(\n", + " RandomQuantumConvolutionalLayer(4,3,seed=1024),\n", + " nn.ReLU(), nn.MaxPool2d(2,1),\n", + " nn.Conv2d(3,6,2,1), nn.ReLU(), nn.MaxPool2d(2,1)\n", + " )\n", + " self.fc = nn.Sequential(\n", + " nn.Linear(6*6*6,1024), nn.Dropout(0.4),\n", + " nn.Linear(1024,10)\n", + " )\n", + " def forward(self,x):\n", + " x = self.conv(x)\n", + " x = x.view(x.size(0),-1)\n", + " return self.fc(x)\n", + "\n", + "class ParameterizedQuantumConvolutionalLayer(nn.Module):\n", + " def __init__(self,nqubit,num_circuits):\n", + " super().__init__()\n", + " self.nqubit = nqubit\n", + " self.cirs = nn.ModuleList([self.circuit(nqubit) for _ in range(num_circuits)])\n", + " def circuit(self,nqubit):\n", + " cir = dq.QubitCircuit(nqubit)\n", + " cir.rxlayer(encode=True); cir.barrier()\n", + " for _ in range(4):\n", + " cir.rylayer(); cir.cnot_ring(); cir.barrier()\n", + " cir.observable(0)\n", + " return cir\n", + " def forward(self,x):\n", + " k,s = 2,2\n", + " x_unf = x.unfold(2,k,s).unfold(3,k,s)\n", + " w = (x.shape[-1]-k)//s +1\n", + " x_r = x_unf.reshape(-1,self.nqubit)\n", + " exps = []\n", + " for cir in self.cirs:\n", + " cir(x_r); exps.append(cir.expectation())\n", + " exps = torch.stack(exps,1).reshape(x.size(0),len(self.cirs),w,w)\n", + " return exps\n", + "\n", + "class QCCNN(nn.Module):\n", + " def __init__(self):\n", + " super().__init__()\n", + " self.conv = nn.Sequential(\n", + " ParameterizedQuantumConvolutionalLayer(4,3),\n", + " nn.ReLU(), nn.MaxPool2d(2,1)\n", + " )\n", + " self.fc = nn.Sequential(\n", + " nn.Linear(8*8*3,128), nn.Dropout(0.4), nn.ReLU(),\n", + " nn.Linear(128,10)\n", + " )\n", + " def forward(self,x):\n", + " x = self.conv(x); x = x.view(x.size(0),-1)\n", + " return self.fc(x)\n", + "\n", + "def vgg_block(in_c,out_c,n_convs):\n", + " layers = [nn.Conv2d(in_c,out_c,3,padding=1), nn.ReLU()]\n", + " for _ in range(n_convs-1):\n", + " layers += [nn.Conv2d(out_c,out_c,3,padding=1), nn.ReLU()]\n", + " layers.append(nn.MaxPool2d(2,2))\n", + " return nn.Sequential(*layers)\n", + "\n", + "VGG = nn.Sequential(\n", + " vgg_block(1,10,3),\n", + " vgg_block(10,16,3),\n", + " nn.Flatten(),\n", + " nn.Linear(16*4*4,120), nn.Sigmoid(),\n", + " nn.Linear(120,84), nn.Sigmoid(),\n", + " nn.Linear(84,10), nn.Softmax(dim=-1)\n", + ")\n" + ], + "id": "c996822b3d5f8305", + "outputs": [], + "execution_count": 15 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-24T18:51:44.622880Z", + "start_time": "2025-06-24T17:37:28.529442Z" + } + }, + "cell_type": "code", + "source": [ + "if __name__ == '__main__':\n", + " freeze_support()\n", + "\n", + " # 数据增广与加载\n", + " train_transform = transforms.Compose([\n", + " transforms.Resize((18, 18)),\n", + " transforms.RandomRotation(15),\n", + " transforms.RandomHorizontalFlip(),\n", + " transforms.RandomVerticalFlip(0.3),\n", + " transforms.ToTensor(),\n", + " transforms.Normalize((0.5,), (0.5,))\n", + " ])\n", + " eval_transform = transforms.Compose([\n", + " transforms.Resize((18, 18)),\n", + " transforms.ToTensor(),\n", + " transforms.Normalize((0.5,), (0.5,))\n", + " ])\n", + "\n", + " full_train = FashionMNIST(root='./data/notebook2', train=True, transform=train_transform, download=True)\n", + " test_dataset = FashionMNIST(root='./data/notebook2', train=False, transform=eval_transform, download=True)\n", + " train_size = int(0.8 * len(full_train))\n", + " valid_size = len(full_train) - train_size\n", + " train_ds, valid_ds = torch.utils.data.random_split(full_train, [train_size, valid_size])\n", + " valid_ds.dataset.transform = eval_transform\n", + "\n", + " batch_size = 128\n", + " train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True, drop_last=True, num_workers=4)\n", + " valid_loader = DataLoader(valid_ds, batch_size=batch_size, shuffle=False, drop_last=True, num_workers=4)\n", + " test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False, drop_last=False, num_workers=4)\n", + "\n", + " # 三种模型配置\n", + " device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n", + " models = {\n", + " 'random_qccnn': (RandomQCCNN(), 1e-3, './data/notebook2/random_qccnn_best.pt'),\n", + " 'qccnn': (QCCNN(), 1e-4, './data/notebook2/qccnn_best.pt'),\n", + " 'vgg': (VGG, 1e-4, './data/notebook2/vgg_best.pt')\n", + " }\n", + "\n", + " all_metrics = {}\n", + " for name, (model, lr, save_path) in models.items():\n", + " seed_torch(1024)\n", + " model = model.to(device)\n", + " criterion = nn.CrossEntropyLoss()\n", + " optimizer = optim.AdamW(model.parameters(), lr=lr, weight_decay=1e-4)\n", + " scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=50)\n", + "\n", + " print(f\"\\n=== Training {name} ===\")\n", + " _, metrics = train_model(\n", + " model, criterion, optimizer, scheduler,\n", + " train_loader, valid_loader,\n", + " num_epochs=50, device=device, save_path=save_path\n", + " )\n", + " all_metrics[name] = metrics\n", + " pd.DataFrame(metrics).to_csv(f'./data/notebook2/{name}_metrics.csv', index=False)\n", + "\n", + " # 测试与可视化\n", + " plt.figure(figsize=(12,5))\n", + " for i,(name,metrics) in enumerate(all_metrics.items(),1):\n", + " model, _, save_path = models[name]\n", + " best_model = model.to(device)\n", + " best_model.load_state_dict(torch.load(save_path))\n", + " print(f\"\\n--- Testing {name} ---\")\n", + " test_model(best_model, test_loader, device)\n", + "\n", + " plt.subplot(1,3,i)\n", + " plt.plot(metrics['epoch'], metrics['valid_acc'], label=f'{name} Val Acc')\n", + " plt.xlabel('Epoch'); plt.ylabel('Valid Acc')\n", + " plt.title(name); plt.legend()\n", + "\n", + " plt.tight_layout(); plt.show()\n", + "\n", + " # 参数量统计\n", + " def count_parameters(m):\n", + " return sum(p.numel() for p in m.parameters() if p.requires_grad)\n", + "\n", + " print(\"\\nParameter Counts:\")\n", + " for name,(model,_,_) in models.items():\n", + " print(f\"{name}: {count_parameters(model)}\")\n" + ], + "id": "2d6b93bb78001086", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "=== Training random_qccnn ===\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 1/50 Train Acc: 0.6377 Valid Acc: 0.7426\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 2/50 Train Acc: 0.7589 Valid Acc: 0.7799\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 3/50 Train Acc: 0.7830 Valid Acc: 0.7955\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 4/50 Train Acc: 0.7928 Valid Acc: 0.8010\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 5/50 Train Acc: 0.7997 Valid Acc: 0.8065\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 6/50 Train Acc: 0.8044 Valid Acc: 0.8140\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 7/50 Train Acc: 0.8097 Valid Acc: 0.8157\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 8/50 Train Acc: 0.8155 Valid Acc: 0.8163\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 9/50 Train Acc: 0.8162 Valid Acc: 0.8159\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 10/50 Train Acc: 0.8169 Valid Acc: 0.8160\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 11/50 Train Acc: 0.8210 Valid Acc: 0.8269\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 12/50 Train Acc: 0.8210 Valid Acc: 0.8266\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 13/50 Train Acc: 0.8241 Valid Acc: 0.8212\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 14/50 Train Acc: 0.8240 Valid Acc: 0.8264\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 15/50 Train Acc: 0.8245 Valid Acc: 0.8231\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 16/50 Train Acc: 0.8270 Valid Acc: 0.8291\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 17/50 Train Acc: 0.8274 Valid Acc: 0.8297\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 18/50 Train Acc: 0.8281 Valid Acc: 0.8338\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 19/50 Train Acc: 0.8295 Valid Acc: 0.8291\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 20/50 Train Acc: 0.8306 Valid Acc: 0.8304\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 21/50 Train Acc: 0.8320 Valid Acc: 0.8280\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 22/50 Train Acc: 0.8316 Valid Acc: 0.8293\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 23/50 Train Acc: 0.8315 Valid Acc: 0.8298\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 24/50 Train Acc: 0.8329 Valid Acc: 0.8282\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 25/50 Train Acc: 0.8321 Valid Acc: 0.8313\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 26/50 Train Acc: 0.8350 Valid Acc: 0.8311\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 27/50 Train Acc: 0.8343 Valid Acc: 0.8335\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 28/50 Train Acc: 0.8347 Valid Acc: 0.8320\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 29/50 Train Acc: 0.8354 Valid Acc: 0.8333\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 30/50 Train Acc: 0.8359 Valid Acc: 0.8314\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 31/50 Train Acc: 0.8380 Valid Acc: 0.8348\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 32/50 Train Acc: 0.8369 Valid Acc: 0.8330\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 33/50 Train Acc: 0.8375 Valid Acc: 0.8367\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 34/50 Train Acc: 0.8373 Valid Acc: 0.8315\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 35/50 Train Acc: 0.8381 Valid Acc: 0.8352\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 36/50 Train Acc: 0.8393 Valid Acc: 0.8374\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 37/50 Train Acc: 0.8384 Valid Acc: 0.8348\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 38/50 Train Acc: 0.8398 Valid Acc: 0.8355\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 39/50 Train Acc: 0.8402 Valid Acc: 0.8365\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 40/50 Train Acc: 0.8400 Valid Acc: 0.8346\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 41/50 Train Acc: 0.8411 Valid Acc: 0.8374\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 42/50 Train Acc: 0.8409 Valid Acc: 0.8373\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 43/50 Train Acc: 0.8415 Valid Acc: 0.8364\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 44/50 Train Acc: 0.8414 Valid Acc: 0.8359\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 45/50 Train Acc: 0.8409 Valid Acc: 0.8364\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 46/50 Train Acc: 0.8419 Valid Acc: 0.8371\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 47/50 Train Acc: 0.8422 Valid Acc: 0.8369\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 48/50 Train Acc: 0.8421 Valid Acc: 0.8360\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 49/50 Train Acc: 0.8417 Valid Acc: 0.8369\n", + "[./data/notebook2/random_qccnn_best.pt] Epoch 50/50 Train Acc: 0.8408 Valid Acc: 0.8369\n", + "\n", + "=== Training qccnn ===\n", + "[./data/notebook2/qccnn_best.pt] Epoch 1/50 Train Acc: 0.3113 Valid Acc: 0.5076\n", + "[./data/notebook2/qccnn_best.pt] Epoch 2/50 Train Acc: 0.4621 Valid Acc: 0.5548\n", + "[./data/notebook2/qccnn_best.pt] Epoch 3/50 Train Acc: 0.5201 Valid Acc: 0.5892\n", + "[./data/notebook2/qccnn_best.pt] Epoch 4/50 Train Acc: 0.5642 Valid Acc: 0.6311\n", + "[./data/notebook2/qccnn_best.pt] Epoch 5/50 Train Acc: 0.6056 Valid Acc: 0.6599\n", + "[./data/notebook2/qccnn_best.pt] Epoch 6/50 Train Acc: 0.6317 Valid Acc: 0.6804\n", + "[./data/notebook2/qccnn_best.pt] Epoch 7/50 Train Acc: 0.6514 Valid Acc: 0.6929\n", + "[./data/notebook2/qccnn_best.pt] Epoch 8/50 Train Acc: 0.6678 Valid Acc: 0.7008\n", + "[./data/notebook2/qccnn_best.pt] Epoch 9/50 Train Acc: 0.6809 Valid Acc: 0.7096\n", + "[./data/notebook2/qccnn_best.pt] Epoch 10/50 Train Acc: 0.6877 Valid Acc: 0.7186\n", + "[./data/notebook2/qccnn_best.pt] Epoch 11/50 Train Acc: 0.6976 Valid Acc: 0.7247\n", + "[./data/notebook2/qccnn_best.pt] Epoch 12/50 Train Acc: 0.7031 Valid Acc: 0.7303\n", + "[./data/notebook2/qccnn_best.pt] Epoch 13/50 Train Acc: 0.7119 Valid Acc: 0.7317\n", + "[./data/notebook2/qccnn_best.pt] Epoch 14/50 Train Acc: 0.7164 Valid Acc: 0.7404\n", + "[./data/notebook2/qccnn_best.pt] Epoch 15/50 Train Acc: 0.7211 Valid Acc: 0.7438\n", + "[./data/notebook2/qccnn_best.pt] Epoch 16/50 Train Acc: 0.7249 Valid Acc: 0.7481\n", + "[./data/notebook2/qccnn_best.pt] Epoch 17/50 Train Acc: 0.7294 Valid Acc: 0.7500\n", + "[./data/notebook2/qccnn_best.pt] Epoch 18/50 Train Acc: 0.7345 Valid Acc: 0.7518\n", + "[./data/notebook2/qccnn_best.pt] Epoch 19/50 Train Acc: 0.7350 Valid Acc: 0.7550\n", + "[./data/notebook2/qccnn_best.pt] Epoch 20/50 Train Acc: 0.7391 Valid Acc: 0.7587\n", + "[./data/notebook2/qccnn_best.pt] Epoch 21/50 Train Acc: 0.7434 Valid Acc: 0.7608\n", + "[./data/notebook2/qccnn_best.pt] Epoch 22/50 Train Acc: 0.7443 Valid Acc: 0.7634\n", + "[./data/notebook2/qccnn_best.pt] Epoch 23/50 Train Acc: 0.7481 Valid Acc: 0.7654\n", + "[./data/notebook2/qccnn_best.pt] Epoch 24/50 Train Acc: 0.7498 Valid Acc: 0.7683\n", + "[./data/notebook2/qccnn_best.pt] Epoch 25/50 Train Acc: 0.7529 Valid Acc: 0.7696\n", + "[./data/notebook2/qccnn_best.pt] Epoch 26/50 Train Acc: 0.7547 Valid Acc: 0.7708\n", + "[./data/notebook2/qccnn_best.pt] Epoch 27/50 Train Acc: 0.7547 Valid Acc: 0.7723\n", + "[./data/notebook2/qccnn_best.pt] Epoch 28/50 Train Acc: 0.7580 Valid Acc: 0.7736\n", + "[./data/notebook2/qccnn_best.pt] Epoch 29/50 Train Acc: 0.7571 Valid Acc: 0.7749\n", + "[./data/notebook2/qccnn_best.pt] Epoch 30/50 Train Acc: 0.7602 Valid Acc: 0.7760\n", + "[./data/notebook2/qccnn_best.pt] Epoch 31/50 Train Acc: 0.7610 Valid Acc: 0.7767\n", + "[./data/notebook2/qccnn_best.pt] Epoch 32/50 Train Acc: 0.7618 Valid Acc: 0.7764\n", + "[./data/notebook2/qccnn_best.pt] Epoch 33/50 Train Acc: 0.7630 Valid Acc: 0.7784\n", + "[./data/notebook2/qccnn_best.pt] Epoch 34/50 Train Acc: 0.7632 Valid Acc: 0.7791\n", + "[./data/notebook2/qccnn_best.pt] Epoch 35/50 Train Acc: 0.7627 Valid Acc: 0.7786\n", + "[./data/notebook2/qccnn_best.pt] Epoch 36/50 Train Acc: 0.7653 Valid Acc: 0.7803\n", + "[./data/notebook2/qccnn_best.pt] Epoch 37/50 Train Acc: 0.7640 Valid Acc: 0.7811\n", + "[./data/notebook2/qccnn_best.pt] Epoch 38/50 Train Acc: 0.7674 Valid Acc: 0.7799\n", + "[./data/notebook2/qccnn_best.pt] Epoch 39/50 Train Acc: 0.7649 Valid Acc: 0.7816\n", + "[./data/notebook2/qccnn_best.pt] Epoch 40/50 Train Acc: 0.7661 Valid Acc: 0.7823\n", + "[./data/notebook2/qccnn_best.pt] Epoch 41/50 Train Acc: 0.7668 Valid Acc: 0.7818\n", + "[./data/notebook2/qccnn_best.pt] Epoch 42/50 Train Acc: 0.7662 Valid Acc: 0.7818\n", + "[./data/notebook2/qccnn_best.pt] Epoch 43/50 Train Acc: 0.7668 Valid Acc: 0.7824\n", + "[./data/notebook2/qccnn_best.pt] Epoch 44/50 Train Acc: 0.7678 Valid Acc: 0.7825\n", + "[./data/notebook2/qccnn_best.pt] Epoch 45/50 Train Acc: 0.7677 Valid Acc: 0.7826\n", + "[./data/notebook2/qccnn_best.pt] Epoch 46/50 Train Acc: 0.7666 Valid Acc: 0.7827\n", + "[./data/notebook2/qccnn_best.pt] Epoch 47/50 Train Acc: 0.7689 Valid Acc: 0.7828\n", + "[./data/notebook2/qccnn_best.pt] Epoch 48/50 Train Acc: 0.7675 Valid Acc: 0.7827\n", + "[./data/notebook2/qccnn_best.pt] Epoch 49/50 Train Acc: 0.7678 Valid Acc: 0.7828\n", + "[./data/notebook2/qccnn_best.pt] Epoch 50/50 Train Acc: 0.7677 Valid Acc: 0.7826\n", + "\n", + "=== Training vgg ===\n", + "[./data/notebook2/vgg_best.pt] Epoch 1/50 Train Acc: 0.2536 Valid Acc: 0.4195\n", + "[./data/notebook2/vgg_best.pt] Epoch 2/50 Train Acc: 0.4692 Valid Acc: 0.5073\n", + "[./data/notebook2/vgg_best.pt] Epoch 3/50 Train Acc: 0.5270 Valid Acc: 0.5266\n", + "[./data/notebook2/vgg_best.pt] Epoch 4/50 Train Acc: 0.5354 Valid Acc: 0.5351\n", + "[./data/notebook2/vgg_best.pt] Epoch 5/50 Train Acc: 0.5935 Valid Acc: 0.6175\n", + "[./data/notebook2/vgg_best.pt] Epoch 6/50 Train Acc: 0.6366 Valid Acc: 0.6626\n", + "[./data/notebook2/vgg_best.pt] Epoch 7/50 Train Acc: 0.6866 Valid Acc: 0.7303\n", + "[./data/notebook2/vgg_best.pt] Epoch 8/50 Train Acc: 0.7415 Valid Acc: 0.7512\n", + "[./data/notebook2/vgg_best.pt] Epoch 9/50 Train Acc: 0.7542 Valid Acc: 0.7582\n", + "[./data/notebook2/vgg_best.pt] Epoch 10/50 Train Acc: 0.7608 Valid Acc: 0.7646\n", + "[./data/notebook2/vgg_best.pt] Epoch 11/50 Train Acc: 0.7677 Valid Acc: 0.7719\n", + "[./data/notebook2/vgg_best.pt] Epoch 12/50 Train Acc: 0.7705 Valid Acc: 0.7731\n", + "[./data/notebook2/vgg_best.pt] Epoch 13/50 Train Acc: 0.7716 Valid Acc: 0.7736\n", + "[./data/notebook2/vgg_best.pt] Epoch 14/50 Train Acc: 0.7748 Valid Acc: 0.7749\n", + "[./data/notebook2/vgg_best.pt] Epoch 15/50 Train Acc: 0.7763 Valid Acc: 0.7751\n", + "[./data/notebook2/vgg_best.pt] Epoch 16/50 Train Acc: 0.7790 Valid Acc: 0.7768\n", + "[./data/notebook2/vgg_best.pt] Epoch 17/50 Train Acc: 0.7801 Valid Acc: 0.7802\n", + "[./data/notebook2/vgg_best.pt] Epoch 18/50 Train Acc: 0.7818 Valid Acc: 0.7828\n", + "[./data/notebook2/vgg_best.pt] Epoch 19/50 Train Acc: 0.7827 Valid Acc: 0.7816\n", + "[./data/notebook2/vgg_best.pt] Epoch 20/50 Train Acc: 0.7827 Valid Acc: 0.7840\n", + "[./data/notebook2/vgg_best.pt] Epoch 21/50 Train Acc: 0.7849 Valid Acc: 0.7858\n", + "[./data/notebook2/vgg_best.pt] Epoch 22/50 Train Acc: 0.7877 Valid Acc: 0.7849\n", + "[./data/notebook2/vgg_best.pt] Epoch 23/50 Train Acc: 0.7880 Valid Acc: 0.7849\n", + "[./data/notebook2/vgg_best.pt] Epoch 24/50 Train Acc: 0.7891 Valid Acc: 0.7860\n", + "[./data/notebook2/vgg_best.pt] Epoch 25/50 Train Acc: 0.7903 Valid Acc: 0.7884\n", + "[./data/notebook2/vgg_best.pt] Epoch 26/50 Train Acc: 0.7910 Valid Acc: 0.7900\n", + "[./data/notebook2/vgg_best.pt] Epoch 27/50 Train Acc: 0.7919 Valid Acc: 0.7886\n", + "[./data/notebook2/vgg_best.pt] Epoch 28/50 Train Acc: 0.7937 Valid Acc: 0.7906\n", + "[./data/notebook2/vgg_best.pt] Epoch 29/50 Train Acc: 0.7934 Valid Acc: 0.7876\n", + "[./data/notebook2/vgg_best.pt] Epoch 30/50 Train Acc: 0.7946 Valid Acc: 0.7902\n", + "[./data/notebook2/vgg_best.pt] Epoch 31/50 Train Acc: 0.7959 Valid Acc: 0.7933\n", + "[./data/notebook2/vgg_best.pt] Epoch 32/50 Train Acc: 0.7956 Valid Acc: 0.7933\n", + "[./data/notebook2/vgg_best.pt] Epoch 33/50 Train Acc: 0.7971 Valid Acc: 0.7920\n", + "[./data/notebook2/vgg_best.pt] Epoch 34/50 Train Acc: 0.7974 Valid Acc: 0.7932\n", + "[./data/notebook2/vgg_best.pt] Epoch 35/50 Train Acc: 0.7980 Valid Acc: 0.7948\n", + "[./data/notebook2/vgg_best.pt] Epoch 36/50 Train Acc: 0.7985 Valid Acc: 0.7950\n", + "[./data/notebook2/vgg_best.pt] Epoch 37/50 Train Acc: 0.7990 Valid Acc: 0.7959\n", + "[./data/notebook2/vgg_best.pt] Epoch 38/50 Train Acc: 0.7992 Valid Acc: 0.7949\n", + "[./data/notebook2/vgg_best.pt] Epoch 39/50 Train Acc: 0.8001 Valid Acc: 0.7960\n", + "[./data/notebook2/vgg_best.pt] Epoch 40/50 Train Acc: 0.8006 Valid Acc: 0.7957\n", + "[./data/notebook2/vgg_best.pt] Epoch 41/50 Train Acc: 0.8007 Valid Acc: 0.7963\n", + "[./data/notebook2/vgg_best.pt] Epoch 42/50 Train Acc: 0.8015 Valid Acc: 0.7959\n", + "[./data/notebook2/vgg_best.pt] Epoch 43/50 Train Acc: 0.8014 Valid Acc: 0.7962\n", + "[./data/notebook2/vgg_best.pt] Epoch 44/50 Train Acc: 0.8016 Valid Acc: 0.7965\n", + "[./data/notebook2/vgg_best.pt] Epoch 45/50 Train Acc: 0.8018 Valid Acc: 0.7958\n", + "[./data/notebook2/vgg_best.pt] Epoch 46/50 Train Acc: 0.8021 Valid Acc: 0.7965\n", + "[./data/notebook2/vgg_best.pt] Epoch 47/50 Train Acc: 0.8025 Valid Acc: 0.7966\n", + "[./data/notebook2/vgg_best.pt] Epoch 48/50 Train Acc: 0.8026 Valid Acc: 0.7962\n", + "[./data/notebook2/vgg_best.pt] Epoch 49/50 Train Acc: 0.8025 Valid Acc: 0.7970\n", + "[./data/notebook2/vgg_best.pt] Epoch 50/50 Train Acc: 0.8025 Valid Acc: 0.7970\n", + "\n", + "--- Testing random_qccnn ---\n", + "Test Accuracy: 0.8249\n", + "\n", + "--- Testing qccnn ---\n", + "Test Accuracy: 0.7720\n", + "\n", + "--- Testing vgg ---\n", + "Test Accuracy: 0.7899\n" + ] + }, + { + "data": { + "text/plain": [ + "
" + ], + "image/png": "iVBORw0KGgoAAAANSUhEUgAABKUAAAHqCAYAAADVi/1VAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAzRBJREFUeJzs3XlYlOX6B/DvLMwM67CvsokiICqKK4plGqZmWplkLqnY8rOTmWZlVifNk9npKGnpOZZGntwy2065RLnvirjvomyy7+sMzLy/PwZGJxZBgYHh+7muuU6888w798upeZj7vZ/7EQmCIICIiIiIiIiIiKgFiY0dABERERERERERtT9MShERERERERERUYtjUoqIiIiIiIiIiFock1JERERERERERNTimJQiIiIiIiIiIqIWx6QUERERERERERG1OCaliIiIiIiIiIioxTEpRURERERERERELY5JKSIiIiIiIiIianFMShE1kI+PD6ZOnWrsMIiIiIiIiIhMApNSRERERERERETU4piUojartLTU2CEQERERERER0X1iUorahA8++AAikQinTp3CuHHjYGdnBz8/P5w8eRLPPvssfHx8YG5uDh8fH0yYMAGJiYkGr4+JiYFIJMKePXvwf//3f3B0dISDgwOeeuop3L5922BsRUUF3nzzTbi6usLCwgKDBg3C8ePHa43r/PnzGDNmDOzs7KBQKBASEoJvvvnGYMzevXshEomwceNGvPXWW3Bzc4OVlRVGjx6NjIwMFBUV4cUXX4SjoyMcHR0xbdo0FBcXN/p3FBMTgy5dukAulyMwMBDr16/H1KlT4ePjYzBOpVJh0aJFCAwMhEKhgIODA4YMGYLDhw/rx2i1WqxcuRIhISEwNzeHra0t+vfvj19++UU/xsfHB48//jh27tyJXr16wdzcHAEBAVi3bt19/+6JiEzRb7/9hpCQEMjlcvj6+uLTTz/Vz2vVGvK5CwAbN27EgAEDYGVlBSsrK4SEhGDt2rX65x9++GEEBwfjxIkTCA8Ph4WFBTp27IiPP/4YWq1WP656btq0aRMWLFgAd3d32NjYYNiwYbhy5Urz/1KIiKjBfvrpJ4hEIvz55581nlu9ejVEIhHOnj0LAPjyyy/h7+8PuVyOoKAgbNy4sdbvBCkpKRg3bhysra1ha2uLiRMn4sSJExCJRIiJiWmBqyLS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2n1dhaCUE5WUlZtfkymF2tx444169NFHNWvWLIWGhmrs2LGSpMWLF+uqq65Su3btFBkZqaSkJJ09e9a8zp6yuWXLFvXv31/t27fXT37yE+Xk5JjnlJeXa9asWQoJCVHnzp01Z84cGYbh8P1LS0v1q1/9Sl26dJG/v7+uv/56ffTRR+bxHTt2yGKxaMuWLRo0aJACAgJ08803Ky8vT++995769++voKAg3XfffSoqKqpXn8+dO6fJkyerffv2ioiI0IsvvlhlWltpaanmzJmjyMhI+fn5qW/fvg6/SD/77DPdeuutCgoKUocOHTRy5EgdOXJE0sUpii+88IIiIiLUuXNnTZ8+XefPnzev79mzp373u9/poYceUocOHXT55ZdrxYoVNbb5tttuU5cuXZSamuqw3/6LPjExUSdPntR9992n7t27KzAwUFdddZXeeuutev1MLrVy5Urdf//9uv/++6sdQGrrv1QZKIuOjpafn58iIiL06KOPNqodcD1qSgEA6lLA9D3Aqf7whz+oW7duqqiocNh/xx13aMqUKebrZ555Rl26dFGHDh308MMP68knn9Q111xjHi8rK9OvfvUr83nr17/+taZMmVJjuRQfHx8lJCQoNTW1ynPZqlWrFBsbq4EDB9b5LFhfb7/9tgYMGKDk5GTt3btX3377rcPxpjxzuQJBKSeyFzmXCEq5i2EYKrKVuWW79BdMXVavXi1vb2/t3btXf/jDHyRVrl6wdOlS/fe//9Xq1av1/vvva86cOQ7XFRUV6YUXXtCf//xn7dq1S1lZWZo9e7Z5/MUXX9SqVau0cuVK7dmzR6dOndKGDRsc7jFnzhylpaVp9erV+uSTT9SnTx+NGzdOp06dcjhv/vz5euWVV7Rv3z5lZ2drwoQJSklJ0ZtvvqlNmzYpPT1dL7/8cr36+8QTT2j79u3asGGDtm7dqh07dmj//v0O50yePFnr1q3T0qVL9fnnn+u1115T+/btJUnHjh3TqFGj5O/vr/fff1/79+/XQw89pLKyMvP67du368iRI9q+fbtWr16t1NTUKgGlF198UYMHD1ZGRoaSkpL0yCOP6Isvvqi2zd7e3po8eXKVAeTtt9+WzWbTpEmTVFJSotjYWP3973/Xf//7X02dOlUJCQn617/+Va+fi92RI0f0wQcfaMKECZowYYL27dunr7/+2jxeV/+XL1+u6dOna+rUqTpw4IA2btyoPn36NKgNaD5kSgEAamMYBtP30Kq0huewn//85zpx4oS2b99u7jt9+rS2bNmiSZMmSZLWrl2rZ599Vs8//7z279+vyy+/XMuXL3e4z/PPP6+1a9fq9ddf1969e1VYWKh333231u+dmJior7/+Wjt37jT3nTt3Tn/5y1/MLKn6PAvWh/2D7uDgYN1yyy16/fXXHY439ZnL2SxGQ5+k24DCwkIFBweroKBAQUFB9b4u+1SRRv5+u/y8vXTomfEubCEkqaSkRN9884169eolf39/SZU1vQb8f1vc0p6DC8cp0Ne7XufeeOONKigoUEZGRq3nvf3223rkkUd04sQJSZWZUg8++KC++uor9e7dW5K0bNkyLVy4ULm5uZKkrl27asaMGfr1r38tqTKS36tXL8XGxurdd9/VuXPn1LFjR6WmpuoXv/iFJOn8+fPq2bOnZs6cqSeeeEI7duzQTTfdpG3btmn06NGSpOeee07Jyck6cuSIrrjiCknStGnT9O233+of//hHrf04e/asOnfurDVr1mjixImSpFOnTql79+6aOnWqUlJS9OWXXyoqKkrp6enV1nH6zW9+o3Xr1unQoUPy8an6x9kDDzygHTt26MiRI7JaK4PCEyZMkJeXlzmXumfPnho5cqT+/Oc/S6ocPMPDw7VgwQJNmzat2rZ/8cUX6t+/v95//33ddNNNkqQbbrhB3bp105tvvlntNbfeeqv69++vF154QVLlv/c111yjlJSUGn9Gc+fO1cGDB80A4l133aWYmBg988wz9ep/t27d9OCDD5rn16a6/zt2jf391xY19mdVUWEo6un3dL7c0L4nb1bXkAAXthIAnI+xov4a+7M6V1qm6HmVf9M25G9MoLlc+vdka3kOu/POOxUaGmpmBq1YsULz5s3Td999J6vVqmHDhmnw4MF65ZVXzGuuv/56nT17VpmZmZKk8PBwzZ4920wMKC8v1xVXXKFBgwbVGpwaNmyYoqKizGl8r7/+uqZPn67vv/++2gLm1T0Lzpw5U/n5+TV+j8OHDys6Olrff/+9QkND9e677+pXv/qVvv32W3l5eTX5metSzniuIFPKiUourLwXSD0p1MPgwYOr7Nu+fbvGjh2rbt26qUOHDpo8ebJOnjypc+fOmecEBgaaASlJioiIUF5eniSpoKBAOTk5iouLM497e3s7fK8jR47o/PnzGjFihLnPx8dHQ4YM0eeff+7Qnquvvtr8OiwsTIGBgWZAyr7P/r1rc+TIEdlsNod2derUSVFRUebrzMxMWa1W3XDDDdXeIzMzUyNHjqz1l2N0dLQZkJIcfzbV9clisSg8PLzWPvTr10/Dhw/XqlWrzL7s3r1bDz30kKTKQejZZ5/V1Vdfrc6dO6t9+/baunVrjYUMq1NeXq7Vq1fr/vvvN/fdf//9Wr16tcrLy+vsf15enr7//nszgIiWraD4vM6XV34e1Lk9dUIAAFXZV97ztXoxAwNwokmTJiktLU2lpZVZ62vXrtW9995rPkMcOnRIQ4YMcbjmx68LCgr0ww8/OOyzWq2KjY2t83snJibqr3/9q86cOSOpcure3XffbQak6vMsWJeVK1dq3LhxCg0NlSTdcsstOnfunLZt2ybJOc9czkbI3YmKLwSlGDjcJ8DHqoMLx7ntezdEu3btHF4fPXpUt9xyi6ZNm6bf/va36tSpk/bs2aPExESHukiX/oKwWCwNmjpoP9disVTZf+m+H38vi8VS7fe+dE52bd+zNgEBtWeL1HVcqv5nc2n7GtOHxMREPfroo3r11Vf1+uuvq0ePHmYA6MUXX9SSJUuUkpJizgGfOXNmg4rXb9myRceOHTOzyOzKy8u1detWjR8/vtb+1+dng5bDXk8qJNBHft6MFwCAqgqKKv/2CwrwqfL3GdAStZbnsNtvv10VFRXatGmTrrvuOu3evVuLFy92OKe656RL1eecS9177716/PHHtX79et14443as2ePFi5cKKn+z4K1KS8v15o1a5Sbmytvb2+H/StXrlR8fLxTnrmcjUwpJyq6UFOKlffcx2KxKNDX2y1bU/9g+Pjjj1VWVqYXX3xRw4YN05VXXqnvv/++QfcIDg5WRESEPvzwQ3NfWVmZQ+2mPn36yNfXV3v27DH3nT9/Xh9//LH69+/fpD7UpE+fPvLx8XFo1+nTp/Xll1+ar6+66ipVVFQ4zLP+sauvvlq7d++u9y9lZ5owYYKsVqvefPNNrV69Wg8++KD57717927deeeduv/++zVw4EBdccUVOnz4cIPuv3LlSt17773KzMx02CZNmmSmFtfW/w4dOqhnz5765z//2fTOwuXMelIUOQcA1CC/uPLDLYqco7VoLc9hAQEBuvvuu7V27Vq99dZbuvLKKx2ynKKiovTvf//b4ZqPP/7Y/Do4OFhhYWEO55SXl9dZlkWq/Jv95z//uV5//XWtWrVKV1xxhW688UbzezT1WXDz5s06c+aMMjIyHJ4p3n77bb377rs6efJki3zmIlPKifqFd9Dqh4bIx4tPM9BwvXv3VllZmV5++WXdfvvt2rt3r1577bUG32fGjBl67rnn1LdvX/Xv31+LFy92mHfcrl07PfLII3riiSfUqVMnXX755fr973+voqIipy5F+mPt27dXYmKinnjiCXXu3FlhYWGaO3euvLwuxsV79uypKVOm6KGHHtLSpUs1cOBAHT16VHl5eZowYYIeffRRvfzyy7r33nuVnJys4OBgffjhhxoyZIjDNEBXtX/ixIn6zW9+o4KCAj3wwAPmsT59+igtLU379u1Tx44dtXjxYuXm5tY7wHf8+HH93//9nzZu3KiYmBiHY1OmTNGtt96q48eP19n/+fPna9q0aerSpYvGjx+vM2fOaO/evXrsscec+aOAE8R0DdYbiUNliJKOAIDqDYgI0pqHhsiLLCnA6SZNmqTbb79dn332mUP5DEl67LHH9Mtf/lKDBw/W8OHDtX79en366acOJUwee+wxLVq0SH369FG/fv308ssv6/Tp0/UKjiUmJmrkyJE6ePCgZs+ebV7jjGfBlStX6tZbb9XAgQMd9kdHR2vmzJl64403NGPGjBb3zEWmlBOFBPrqhisv0/A+oe5uClqha665RosXL9bzzz+vmJgYrV27VosWLWrwff7nf/5HkydP1gMPPKC4uDh16NBBP/3pTx3Oee655/Szn/1MCQkJuvbaa/XVV19py5Yt6tixo7O6U8X//u//atSoUbrjjjs0ZswYXX/99VXmXi9fvlz33HOPkpKS1K9fP/3yl78051B37txZ77//vs6ePasbbrhBsbGx+uMf/9hs850TExN1+vRpjRkzRpdffrm5/+mnn9a1116rcePG6cYbb1R4eHiNy8FWZ82aNWrXrl219aBuuukmdejQQX/+85/r7P+UKVOUkpKiZcuWKTo6WrfddluDM7bQPIIDfXR931CN7HuZu5sCAGihQgJ9NerKy3R9X54rAGe7+eab1alTJx06dMhc+Mlu0qRJSk5O1uzZs3Xttdfqm2++0QMPPOBQxPvXv/617rvvPk2ePFlxcXFq3769xo0bV6XQ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" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Parameter Counts:\n", + "random_qccnn: 232581\n", + "qccnn: 26042\n", + "vgg: 49870\n" + ] + } + ], + "execution_count": 16 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "下面是新版本脚本相对于原始代码的主要改进点及对应说明,已结合关键代码片段进行标注:\n", + "\n", + "1. **数据增强(Data Augmentation)**\n", + "\n", + " * **原始**:训练和验证都只做了 `Resize` + `ToTensor`,数据量固定;\n", + " * **改进**:对训练集添加了 `RandomRotation`、`RandomHorizontalFlip`、`RandomVerticalFlip` 等操作,大幅增加了样本多样性,有助于提升模型泛化能力。\n", + "\n", + " ```python\n", + " train_transform = transforms.Compose([\n", + " transforms.Resize((18, 18)),\n", + " transforms.RandomRotation(15), # 随机旋转\n", + " transforms.RandomHorizontalFlip(), # 随机水平翻转\n", + " transforms.RandomVerticalFlip(0.3), # 随机垂直翻转\n", + " transforms.ToTensor(),\n", + " transforms.Normalize((0.5,), (0.5,))\n", + " ])\n", + " ```\n", + "\n", + "2. **优化器与学习率调度**\n", + "\n", + " * **原始**:`SGD(lr=0.01, weight_decay=0.001)` 或 `Adam(lr=1e-5)`,无学习率变化;\n", + " * **改进**:统一使用 `AdamW`(带权重衰减的 Adam),更稳定;添加 `CosineAnnealingLR`,能在训练中动态调整学习率,促进更快收敛、更高精度。\n", + "\n", + " ```python\n", + " optimizer = optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4)\n", + " scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=50)\n", + " ```\n", + "\n", + "3. **批大小与训练轮次(Batch Size & Epochs)**\n", + "\n", + " * **原始**:`batch_size=64`,`num_epochs=300`;\n", + " * **改进**:增大到 `batch_size=128`,减少到 `num_epochs=50`。\n", + "\n", + " * **批量更大**:更好利用 GPU 并行能力;\n", + " * **轮次更少**:缩短训练时间,同时在学习率调度下仍能达到高准确度。\n", + "\n", + "4. **梯度裁剪(Gradient Clipping)**\n", + "\n", + " * 增加了 `torch.nn.utils.clip_grad_norm_(…)`,避免量子网络中可能出现的梯度爆炸,保障训练稳定性。\n", + "\n", + " ```python\n", + " torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)\n", + " ```\n", + "\n", + "5. **统一训练框架与多模型支持**\n", + "\n", + " * 将原来三段几乎重复的训练逻辑,抽象成 `train_model(...)` 函数,并通过一个字典 `models` 循环一次性训练:\n", + "\n", + " * `random_qccnn`、`qccnn`、`vgg` 三种架构统一流程;\n", + " * 每种模型均保存最优权重(`save_path`)与训练曲线(`metrics.csv`)。\n", + "\n", + " ```python\n", + " models = {\n", + " 'random_qccnn': (RandomQCCNN(), 1e-3, './random_qccnn_best.pt'),\n", + " 'qccnn': (QCCNN(), 1e-4, './qccnn_best.pt'),\n", + " 'vgg': (VGG, 1e-4, './vgg_best.pt')\n", + " }\n", + " for name, (model, lr, save_path) in models.items():\n", + " trained, metrics = train_model(\n", + " model, criterion, optimizer, scheduler,\n", + " train_loader, valid_loader,\n", + " num_epochs=50, device=device, save_path=save_path\n", + " )\n", + " pd.DataFrame(metrics).to_csv(f'./{name}_metrics.csv', index=False)\n", + " ```\n", + "\n", + "6. **最佳模型保存和测试分离**\n", + "\n", + " * `train_model` 内部自动监控验证集准确率并保存最佳参数;\n", + " * 训练结束后,再统一加载最佳权重进行测试,确保测试结果与最优状态对应。\n", + "\n", + "7. **可视化对比**\n", + "\n", + " * 最后一个代码块中,通过 `matplotlib` 并排绘制三种模型的验证准确率曲线,直观比较不同模型的收敛速度和最终性能。\n", + "\n", + " ```python\n", + " plt.figure(figsize=(12,5))\n", + " for i,(name,metrics) in enumerate(all_metrics.items(),1):\n", + " plt.subplot(1,3,i)\n", + " plt.plot(metrics['epoch'], metrics['valid_acc'], label=f'{name} Val Acc')\n", + " plt.title(name); plt.legend()\n", + " plt.tight_layout(); plt.show()\n", + " ```\n", + "\n", + "8. **参数量统计**\n", + "\n", + " * 在脚本末尾增加 `count_parameters` 函数,打印三种模型的可训练参数量,帮助评估模型复杂度与性能的权衡。\n", + "\n", + " ```python\n", + " def count_parameters(m):\n", + " return sum(p.numel() for p in m.parameters() if p.requires_grad)\n", + " for name,(model,_,_) in models.items():\n", + " print(f\"{name}: {count_parameters(model)}\")\n", + " ```\n", + "\n", + "---\n", + "\n", + "**总体效果**:\n", + "\n", + "* **训练速度**:批量更大、轮次更少、学习率动态调度,整体训练时间显著缩短。\n", + "* **模型准确率**:数据增强 + 优化器 + 调度器 + 梯度裁剪等多项改进,显著提高了各模型在验证集和测试集上的准确率。\n", + "* **可维护性**:统一框架、函数抽象、循环训练及结果保存,大大简化了代码结构,便于后续扩展和调试。\n" + ], + "id": "6a5e9602f481107e" + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/Modify.py b/Modify.py new file mode 100644 index 0000000..dcebaa7 --- /dev/null +++ b/Modify.py @@ -0,0 +1,291 @@ +# Modify.py + +#%% 导入所有需要的包 +import os +import random + +import numpy as np +import pandas as pd +import deepquantum as dq +import matplotlib.pyplot as plt +import torch +import torch.nn as nn +import torch.optim as optim +import torchvision.transforms as transforms +from torchvision.datasets import FashionMNIST +from tqdm import tqdm +from torch.utils.data import DataLoader +from multiprocessing import freeze_support + +#%% 设置随机种子以保证可复现 +def seed_torch(seed=1024): + random.seed(seed) + os.environ['PYTHONHASHSEED'] = str(seed) + np.random.seed(seed) + torch.manual_seed(seed) + torch.cuda.manual_seed(seed) + torch.cuda.manual_seed_all(seed) + torch.backends.cudnn.benchmark = False + torch.backends.cudnn.deterministic = True + +#%% 准确率计算函数 +def calculate_score(y_true, y_preds): + preds_prob = torch.softmax(y_preds, dim=1) + preds_class = torch.argmax(preds_prob, dim=1) + correct = (preds_class == y_true).float() + return (correct.sum() / len(correct)).cpu().numpy() + +#%% 训练与验证函数 +def train_model(model, criterion, optimizer, scheduler, train_loader, valid_loader, num_epochs, device, save_path): + model.to(device) + best_acc = 0.0 + metrics = {'epoch': [], 'train_acc': [], 'valid_acc': [], 'train_loss': [], 'valid_loss': []} + + for epoch in range(1, num_epochs + 1): + # --- 训练阶段 --- + model.train() + running_loss, running_acc = 0.0, 0.0 + for imgs, labels in train_loader: + imgs, labels = imgs.to(device), labels.to(device) + optimizer.zero_grad() + outputs = model(imgs) + loss = criterion(outputs, labels) + loss.backward() + torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) + optimizer.step() + running_loss += loss.item() + running_acc += calculate_score(labels, outputs) + + train_loss = running_loss / len(train_loader) + train_acc = running_acc / len(train_loader) + scheduler.step() + + # --- 验证阶段 --- + model.eval() + val_loss, val_acc = 0.0, 0.0 + with torch.no_grad(): + for imgs, labels in valid_loader: + imgs, labels = imgs.to(device), labels.to(device) + outputs = model(imgs) + loss = criterion(outputs, labels) + val_loss += loss.item() + val_acc += calculate_score(labels, outputs) + + valid_loss = val_loss / len(valid_loader) + valid_acc = val_acc / len(valid_loader) + + metrics['epoch'].append(epoch) + metrics['train_loss'].append(train_loss) + metrics['valid_loss'].append(valid_loss) + metrics['train_acc'].append(train_acc) + metrics['valid_acc'].append(valid_acc) + + tqdm.write(f"[{save_path}] Epoch {epoch}/{num_epochs} " + f"Train Acc: {train_acc:.4f} Valid Acc: {valid_acc:.4f}") + + if valid_acc > best_acc: + best_acc = valid_acc + torch.save(model.state_dict(), save_path) + + return model, metrics + +#%% 测试函数 +def test_model(model, test_loader, device): + model.to(device).eval() + acc = 0.0 + with torch.no_grad(): + for imgs, labels in test_loader: + imgs, labels = imgs.to(device), labels.to(device) + outputs = model(imgs) + acc += calculate_score(labels, outputs) + acc /= len(test_loader) + print(f"Test Accuracy: {acc:.4f}") + return acc + +#%% 定义量子卷积层与模型 +singlegate_list = ['rx','ry','rz','s','t','p','u3'] +doublegate_list = ['rxx','ryy','rzz','swap','cnot','cp','ch','cu','ct','cz'] + +class RandomQuantumConvolutionalLayer(nn.Module): + def __init__(self, nqubit, num_circuits, seed=1024): + super().__init__() + random.seed(seed) + self.nqubit = nqubit + self.cirs = nn.ModuleList([self.circuit(nqubit) for _ in range(num_circuits)]) + def circuit(self, nqubit): + cir = dq.QubitCircuit(nqubit) + cir.rxlayer(encode=True); cir.barrier() + for _ in range(3): + for i in range(nqubit): + getattr(cir, random.choice(singlegate_list))(i) + c,t = random.sample(range(nqubit),2) + gate = random.choice(doublegate_list) + if gate[0] in ['r','s']: + getattr(cir, gate)([c,t]) + else: + getattr(cir, gate)(c,t) + cir.barrier() + cir.observable(0) + return cir + def forward(self, x): + k,s = 2,2 + x_unf = x.unfold(2,k,s).unfold(3,k,s) + w = (x.shape[-1]-k)//s + 1 + x_r = x_unf.reshape(-1, self.nqubit) + exps = [] + for cir in self.cirs: + cir(x_r) + exps.append(cir.expectation()) + exps = torch.stack(exps,1).reshape(x.size(0), len(self.cirs), w, w) + return exps + +class RandomQCCNN(nn.Module): + def __init__(self): + super().__init__() + self.conv = nn.Sequential( + RandomQuantumConvolutionalLayer(4,3,seed=1024), + nn.ReLU(), nn.MaxPool2d(2,1), + nn.Conv2d(3,6,2,1), nn.ReLU(), nn.MaxPool2d(2,1) + ) + self.fc = nn.Sequential( + nn.Linear(6*6*6,1024), nn.Dropout(0.4), + nn.Linear(1024,10) + ) + def forward(self,x): + x = self.conv(x) + x = x.view(x.size(0),-1) + return self.fc(x) + +class ParameterizedQuantumConvolutionalLayer(nn.Module): + def __init__(self,nqubit,num_circuits): + super().__init__() + self.nqubit = nqubit + self.cirs = nn.ModuleList([self.circuit(nqubit) for _ in range(num_circuits)]) + def circuit(self,nqubit): + cir = dq.QubitCircuit(nqubit) + cir.rxlayer(encode=True); cir.barrier() + for _ in range(4): + cir.rylayer(); cir.cnot_ring(); cir.barrier() + cir.observable(0) + return cir + def forward(self,x): + k,s = 2,2 + x_unf = x.unfold(2,k,s).unfold(3,k,s) + w = (x.shape[-1]-k)//s +1 + x_r = x_unf.reshape(-1,self.nqubit) + exps = [] + for cir in self.cirs: + cir(x_r); exps.append(cir.expectation()) + exps = torch.stack(exps,1).reshape(x.size(0),len(self.cirs),w,w) + return exps + +class QCCNN(nn.Module): + def __init__(self): + super().__init__() + self.conv = nn.Sequential( + ParameterizedQuantumConvolutionalLayer(4,3), + nn.ReLU(), nn.MaxPool2d(2,1) + ) + self.fc = nn.Sequential( + nn.Linear(8*8*3,128), nn.Dropout(0.4), nn.ReLU(), + nn.Linear(128,10) + ) + def forward(self,x): + x = self.conv(x); x = x.view(x.size(0),-1) + return self.fc(x) + +def vgg_block(in_c,out_c,n_convs): + layers = [nn.Conv2d(in_c,out_c,3,padding=1), nn.ReLU()] + for _ in range(n_convs-1): + layers += [nn.Conv2d(out_c,out_c,3,padding=1), nn.ReLU()] + layers.append(nn.MaxPool2d(2,2)) + return nn.Sequential(*layers) + +VGG = nn.Sequential( + vgg_block(1,10,3), + vgg_block(10,16,3), + nn.Flatten(), + nn.Linear(16*4*4,120), nn.Sigmoid(), + nn.Linear(120,84), nn.Sigmoid(), + nn.Linear(84,10), nn.Softmax(dim=-1) +) + +#%% 主入口 +if __name__ == '__main__': + freeze_support() + + # 数据增广与加载 + train_transform = transforms.Compose([ + transforms.Resize((18, 18)), + transforms.RandomRotation(15), + transforms.RandomHorizontalFlip(), + transforms.RandomVerticalFlip(0.3), + transforms.ToTensor(), + transforms.Normalize((0.5,), (0.5,)) + ]) + eval_transform = transforms.Compose([ + transforms.Resize((18, 18)), + transforms.ToTensor(), + transforms.Normalize((0.5,), (0.5,)) + ]) + + full_train = FashionMNIST(root='./data/notebook2', train=True, transform=train_transform, download=True) + test_dataset = FashionMNIST(root='./data/notebook2', train=False, transform=eval_transform, download=True) + train_size = int(0.8 * len(full_train)) + valid_size = len(full_train) - train_size + train_ds, valid_ds = torch.utils.data.random_split(full_train, [train_size, valid_size]) + valid_ds.dataset.transform = eval_transform + + batch_size = 128 + train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True, drop_last=True, num_workers=4) + valid_loader = DataLoader(valid_ds, batch_size=batch_size, shuffle=False, drop_last=True, num_workers=4) + test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False, drop_last=False, num_workers=4) + + # 三种模型配置 + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + models = { + 'random_qccnn': (RandomQCCNN(), 1e-3, './data/notebook2/random_qccnn_best.pt'), + 'qccnn': (QCCNN(), 1e-4, './data/notebook2/qccnn_best.pt'), + 'vgg': (VGG, 1e-4, './data/notebook2/vgg_best.pt') + } + + all_metrics = {} + for name, (model, lr, save_path) in models.items(): + seed_torch(1024) + model = model.to(device) + criterion = nn.CrossEntropyLoss() + optimizer = optim.AdamW(model.parameters(), lr=lr, weight_decay=1e-4) + scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=50) + + print(f"\n=== Training {name} ===") + _, metrics = train_model( + model, criterion, optimizer, scheduler, + train_loader, valid_loader, + num_epochs=50, device=device, save_path=save_path + ) + all_metrics[name] = metrics + pd.DataFrame(metrics).to_csv(f'./data/notebook2/{name}_metrics.csv', index=False) + + # 测试与可视化 + plt.figure(figsize=(12,5)) + for i,(name,metrics) in enumerate(all_metrics.items(),1): + model, _, save_path = models[name] + best_model = model.to(device) + best_model.load_state_dict(torch.load(save_path)) + print(f"\n--- Testing {name} ---") + test_model(best_model, test_loader, device) + + plt.subplot(1,3,i) + plt.plot(metrics['epoch'], metrics['valid_acc'], label=f'{name} Val Acc') + plt.xlabel('Epoch'); plt.ylabel('Valid Acc') + plt.title(name); plt.legend() + + plt.tight_layout(); plt.show() + + # 参数量统计 + def count_parameters(m): + return sum(p.numel() for p in m.parameters() if p.requires_grad) + + print("\nParameter Counts:") + for name,(model,_,_) in models.items(): + print(f"{name}: {count_parameters(model)}") diff --git a/Origin.py b/Origin.py new file mode 100644 index 0000000..dc21e0d --- /dev/null +++ b/Origin.py @@ -0,0 +1,460 @@ +#%% +# 首先我们导入所有需要的包: +import os +import random + +import numpy as np +import pandas as pd +import deepquantum as dq +import matplotlib.pyplot as plt +import torch +import torch.nn as nn +import torch.optim as optim +import torchvision.transforms as transforms +from tqdm import tqdm +from sklearn.metrics import roc_auc_score +from torch.utils.data import DataLoader +# from torchvision.datasets import MNIST, FashionMNIST + +def seed_torch(seed=1024): + """ + Set random seeds for reproducibility. + + Args: + seed (int): Random seed number to use. Default is 1024. + """ + + random.seed(seed) + os.environ['PYTHONHASHSEED'] = str(seed) + np.random.seed(seed) + torch.manual_seed(seed) + torch.cuda.manual_seed(seed) + + # Seed all GPUs with the same seed if using multi-GPU + torch.cuda.manual_seed_all(seed) + torch.backends.cudnn.benchmark = False + torch.backends.cudnn.deterministic = True + +seed_torch(1024) +#%% +def calculate_score(y_true, y_preds): + # 将模型预测结果转为概率分布 + preds_prob = torch.softmax(y_preds, dim=1) + # 获得预测的类别(概率最高的一类) + preds_class = torch.argmax(preds_prob, dim=1) + # 计算准确率 + correct = (preds_class == y_true).float() + accuracy = correct.sum() / len(correct) + return accuracy.cpu().numpy() + + +def train_model(model, criterion, optimizer, train_loader, valid_loader, num_epochs, device): + """ + 训练和验证模型。 + + Args: + model (torch.nn.Module): 要训练的模型。 + criterion (torch.nn.Module): 损失函数。 + optimizer (torch.optim.Optimizer): 优化器。 + train_loader (torch.utils.data.DataLoader): 训练数据加载器。 + valid_loader (torch.utils.data.DataLoader): 验证数据加载器。 + num_epochs (int): 训练的epoch数。 + + Returns: + model (torch.nn.Module): 训练后的模型。 + """ + + model.train() + train_loss_list = [] + valid_loss_list = [] + train_acc_list = [] + valid_acc_list = [] + + with tqdm(total=num_epochs) as pbar: + for epoch in range(num_epochs): + # 训练阶段 + train_loss = 0.0 + train_acc = 0.0 + for images, labels in train_loader: + images = images.to(device) + labels = labels.to(device) + optimizer.zero_grad() + outputs = model(images) + loss = criterion(outputs, labels) + loss.backward() + optimizer.step() + train_loss += loss.item() + train_acc += calculate_score(labels, outputs) + + train_loss /= len(train_loader) + train_acc /= len(train_loader) + + # 验证阶段 + model.eval() + valid_loss = 0.0 + valid_acc = 0.0 + with torch.no_grad(): + for images, labels in valid_loader: + images = images.to(device) + labels = labels.to(device) + outputs = model(images) + loss = criterion(outputs, labels) + valid_loss += loss.item() + valid_acc += calculate_score(labels, outputs) + + valid_loss /= len(valid_loader) + valid_acc /= len(valid_loader) + + pbar.set_description(f"Train loss: {train_loss:.3f} Valid Acc: {valid_acc:.3f}") + pbar.update() + + + train_loss_list.append(train_loss) + valid_loss_list.append(valid_loss) + train_acc_list.append(train_acc) + valid_acc_list.append(valid_acc) + + metrics = {'epoch': list(range(1, num_epochs + 1)), + 'train_acc': train_acc_list, + 'valid_acc': valid_acc_list, + 'train_loss': train_loss_list, + 'valid_loss': valid_loss_list} + + + + return model, metrics + +def test_model(model, test_loader, device): + model.eval() + test_acc = 0.0 + with torch.no_grad(): + for images, labels in test_loader: + images = images.to(device) + labels = labels.to(device) + outputs = model(images) + test_acc += calculate_score(labels, outputs) + + test_acc /= len(test_loader) + print(f'Test Acc: {test_acc:.3f}') + return test_acc +#%% +# 定义图像变换 +trans1 = transforms.Compose([ + transforms.Resize((18, 18)), # 调整大小为18x18 + transforms.ToTensor() # 转换为张量 +]) + +trans2 = transforms.Compose([ + transforms.Resize((16, 16)), # 调整大小为16x16 + transforms.ToTensor() # 转换为张量 +]) +train_dataset = FashionMNIST(root='./data/notebook1', train=False, transform=trans1,download=True) +test_dataset = FashionMNIST(root='./data/notebook1', train=False, transform=trans1,download=True) + +# 定义训练集和测试集的比例 +train_ratio = 0.8 # 训练集比例为80%,验证集比例为20% +valid_ratio = 0.2 +total_samples = len(train_dataset) +train_size = int(train_ratio * total_samples) +valid_size = int(valid_ratio * total_samples) + +# 分割训练集和测试集 +train_dataset, valid_dataset = torch.utils.data.random_split(train_dataset, [train_size, valid_size]) + +# 加载随机抽取的训练数据集 +train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True, drop_last=True) +valid_loader = DataLoader(valid_dataset, batch_size=64, shuffle=False, drop_last=True) +test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False, drop_last=True) +#%% +singlegate_list = ['rx', 'ry', 'rz', 's', 't', 'p', 'u3'] +doublegate_list = ['rxx', 'ryy', 'rzz', 'swap', 'cnot', 'cp', 'ch', 'cu', 'ct', 'cz'] +#%% +# 随机量子卷积层 +class RandomQuantumConvolutionalLayer(nn.Module): + def __init__(self, nqubit, num_circuits, seed:int=1024): + super(RandomQuantumConvolutionalLayer, self).__init__() + random.seed(seed) + self.nqubit = nqubit + self.cirs = nn.ModuleList([self.circuit(nqubit) for _ in range(num_circuits)]) + + def circuit(self, nqubit): + cir = dq.QubitCircuit(nqubit) + cir.rxlayer(encode=True) # 对原论文的量子线路结构并无影响,只是做了一个数据编码的操作 + cir.barrier() + for iter in range(3): + for i in range(nqubit): + singlegate = random.choice(singlegate_list) + getattr(cir, singlegate)(i) + control_bit, target_bit = random.sample(range(0, nqubit - 1), 2) + doublegate = random.choice(doublegate_list) + if doublegate[0] in ['r', 's']: + getattr(cir, doublegate)([control_bit, target_bit]) + else: + getattr(cir, doublegate)(control_bit, target_bit) + cir.barrier() + + cir.observable(0) + return cir + + def forward(self, x): + kernel_size, stride = 2, 2 + # [64, 1, 18, 18] -> [64, 1, 9, 18, 2] -> [64, 1, 9, 9, 2, 2] + x_unflod = x.unfold(2, kernel_size, stride).unfold(3, kernel_size, stride) + w = int((x.shape[-1] - kernel_size) / stride + 1) + x_reshape = x_unflod.reshape(-1, self.nqubit) + + exps = [] + for cir in self.cirs: # out_channels + cir(x_reshape) + exp = cir.expectation() + exps.append(exp) + + exps = torch.stack(exps, dim=1) + exps = exps.reshape(x.shape[0], 3, w, w) + return exps +#%% +net = RandomQuantumConvolutionalLayer(nqubit=4, num_circuits=3, seed=1024) +net.cirs[0].draw() +#%% +# 基于随机量子卷积层的混合模型 +class RandomQCCNN(nn.Module): + def __init__(self): + super(RandomQCCNN, self).__init__() + self.conv = nn.Sequential( + RandomQuantumConvolutionalLayer(nqubit=4, num_circuits=3, seed=1024), # num_circuits=3代表我们在quanv1层只用了3个量子卷积核 + nn.ReLU(), + nn.MaxPool2d(kernel_size=2, stride=1), + nn.Conv2d(3, 6, kernel_size=2, stride=1), + nn.ReLU(), + nn.MaxPool2d(kernel_size=2, stride=1) + ) + self.fc = nn.Sequential( + nn.Linear(6 * 6 * 6, 1024), + nn.Dropout(0.4), + nn.Linear(1024, 10) + ) + + def forward(self, x): + x = self.conv(x) + x = x.reshape(x.size(0), -1) + x = self.fc(x) + return x +#%% +num_epochs = 300 +device = torch.device("cuda" if torch.cuda.is_available() else "cpu") +print(device) +seed_torch(1024) # 重新设置随机种子 +model = RandomQCCNN() +model.to(device) +criterion = nn.CrossEntropyLoss() +optimizer = optim.SGD(model.parameters(), lr=0.01, weight_decay=0.001) # 添加正则化项 +optim_model, metrics = train_model(model, criterion, optimizer, train_loader, valid_loader, num_epochs, device) +torch.save(optim_model.state_dict(), './data/notebook1/random_qccnn_weights.pt') # 保存训练好的模型参数,用于后续的推理或测试 +pd.DataFrame(metrics).to_csv('./data/notebook1/random_qccnn_metrics.csv', index='None') # 保存模型训练过程,用于后续图标展示 +#%% +state_dict = torch.load('./data/notebook1/random_qccnn_weights.pt', map_location=device) +random_qccnn_model = RandomQCCNN() +random_qccnn_model.load_state_dict(state_dict) +random_qccnn_model.to(device) + +test_acc = test_model(random_qccnn_model, test_loader, device) +#%% +data = pd.read_csv('./data/notebook1/random_qccnn_metrics.csv') +epoch = data['epoch'] +train_loss = data['train_loss'] +valid_loss = data['valid_loss'] +train_acc = data['train_acc'] +valid_acc = data['valid_acc'] + +# 创建图和Axes对象 +fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5)) + +# 绘制训练损失曲线 +ax1.plot(epoch, train_loss, label='Train Loss') +ax1.plot(epoch, valid_loss, label='Valid Loss') +ax1.set_title('Training Loss Curve') +ax1.set_xlabel('Epoch') +ax1.set_ylabel('Loss') +ax1.legend() + +# 绘制训练准确率曲线 +ax2.plot(epoch, train_acc, label='Train Accuracy') +ax2.plot(epoch, valid_acc, label='Valid Accuracy') +ax2.set_title('Training Accuracy Curve') +ax2.set_xlabel('Epoch') +ax2.set_ylabel('Accuracy') +ax2.legend() + +plt.show() +#%% +class ParameterizedQuantumConvolutionalLayer(nn.Module): + def __init__(self, nqubit, num_circuits): + super().__init__() + self.nqubit = nqubit + self.cirs = nn.ModuleList([self.circuit(nqubit) for _ in range(num_circuits)]) + + def circuit(self, nqubit): + cir = dq.QubitCircuit(nqubit) + cir.rxlayer(encode=True) #对原论文的量子线路结构并无影响,只是做了一个数据编码的操作 + cir.barrier() + for iter in range(4): #对应原论文中一个量子卷积线路上的深度为4,可控参数一共16个 + cir.rylayer() + cir.cnot_ring() + cir.barrier() + + cir.observable(0) + return cir + + def forward(self, x): + kernel_size, stride = 2, 2 + # [64, 1, 18, 18] -> [64, 1, 9, 18, 2] -> [64, 1, 9, 9, 2, 2] + x_unflod = x.unfold(2, kernel_size, stride).unfold(3, kernel_size, stride) + w = int((x.shape[-1] - kernel_size) / stride + 1) + x_reshape = x_unflod.reshape(-1, self.nqubit) + + exps = [] + for cir in self.cirs: # out_channels + cir(x_reshape) + exp = cir.expectation() + exps.append(exp) + + exps = torch.stack(exps, dim=1) + exps = exps.reshape(x.shape[0], 3, w, w) + return exps +#%% +# 此处我们可视化其中一个量子卷积核的线路结构: +net = ParameterizedQuantumConvolutionalLayer(nqubit=4, num_circuits=3) +net.cirs[0].draw() +#%% +# QCCNN整体网络架构: +class QCCNN(nn.Module): + def __init__(self): + super(QCCNN, self).__init__() + self.conv = nn.Sequential( + ParameterizedQuantumConvolutionalLayer(nqubit=4, num_circuits=3), + nn.ReLU(), + nn.MaxPool2d(kernel_size=2, stride=1) + ) + + self.fc = nn.Sequential( + nn.Linear(8 * 8 * 3, 128), + nn.Dropout(0.4), + nn.ReLU(), + nn.Linear(128, 10) + ) + + def forward(self, x): + x = self.conv(x) + x = x.reshape(x.size(0), -1) + x = self.fc(x) + return x +#%% +num_epochs = 300 +device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + +model = QCCNN() +model.to(device) +criterion = nn.CrossEntropyLoss() +optimizer = optim.Adam(model.parameters(), lr=1e-5) # 添加正则化项 +optim_model, metrics = train_model(model, criterion, optimizer, train_loader, valid_loader, num_epochs, device) +torch.save(optim_model.state_dict(), './data/notebook1/qccnn_weights.pt') # 保存训练好的模型参数,用于后续的推理或测试 +pd.DataFrame(metrics).to_csv('./data/notebook1/qccnn_metrics.csv', index='None') # 保存模型训练过程,用于后续图标展示 +#%% +state_dict = torch.load('./data/notebook1/qccnn_weights.pt', map_location=device) +qccnn_model = QCCNN() +qccnn_model.load_state_dict(state_dict) +qccnn_model.to(device) + +test_acc = test_model(qccnn_model, test_loader, device) +#%% +def vgg_block(in_channel,out_channel,num_convs): + layers = nn.ModuleList() + assert num_convs >= 1 + layers.append(nn.Conv2d(in_channel,out_channel,kernel_size=3,padding=1)) + layers.append(nn.ReLU()) + for _ in range(num_convs-1): + layers.append(nn.Conv2d(out_channel,out_channel,kernel_size=3,padding=1)) + layers.append(nn.ReLU()) + layers.append(nn.MaxPool2d(kernel_size=2,stride=2)) + return nn.Sequential(*layers) + +VGG = nn.Sequential( + vgg_block(1,10,3), # 14,14 + vgg_block(10,16,3), # 4 * 4 + nn.Flatten(), + nn.Linear(16 * 4 * 4, 120), + nn.Sigmoid(), + nn.Linear(120, 84), + nn.Sigmoid(), + nn.Linear(84,10), + nn.Softmax(dim=-1) +) +#%% +num_epochs = 300 +device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + +vgg_model = VGG +vgg_model.to(device) +criterion = nn.CrossEntropyLoss() +optimizer = optim.Adam(vgg_model.parameters(), lr=1e-5) # 添加正则化项 +vgg_model, metrics = train_model(vgg_model, criterion, optimizer, train_loader, valid_loader, num_epochs, device) +torch.save(vgg_model.state_dict(), './data/notebook1/vgg_weights.pt') # 保存训练好的模型参数,用于后续的推理或测试 +pd.DataFrame(metrics).to_csv('./data/notebook1/vgg_metrics.csv', index='None') # 保存模型训练过程,用于后续图标展示 +#%% +state_dict = torch.load('./data/notebook1/vgg_weights.pt', map_location=device) +vgg_model = VGG +vgg_model.load_state_dict(state_dict) +vgg_model.to(device) + +vgg_test_acc = test_model(vgg_model, test_loader, device) +#%% +vgg_data = pd.read_csv('./data/notebook1/vgg_metrics.csv') +qccnn_data = pd.read_csv('./data/notebook1/qccnn_metrics.csv') +vgg_epoch = vgg_data['epoch'] +vgg_train_loss = vgg_data['train_loss'] +vgg_valid_loss = vgg_data['valid_loss'] +vgg_train_acc = vgg_data['train_acc'] +vgg_valid_acc = vgg_data['valid_acc'] + +qccnn_epoch = qccnn_data['epoch'] +qccnn_train_loss = qccnn_data['train_loss'] +qccnn_valid_loss = qccnn_data['valid_loss'] +qccnn_train_acc = qccnn_data['train_acc'] +qccnn_valid_acc = qccnn_data['valid_acc'] + +# 创建图和Axes对象 +fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5)) + +# 绘制训练损失曲线 +ax1.plot(vgg_epoch, vgg_train_loss, label='VGG Train Loss') +ax1.plot(vgg_epoch, vgg_valid_loss, label='VGG Valid Loss') +ax1.plot(qccnn_epoch, qccnn_train_loss, label='QCCNN Valid Loss') +ax1.plot(qccnn_epoch, qccnn_valid_loss, label='QCCNN Valid Loss') +ax1.set_title('Training Loss Curve') +ax1.set_xlabel('Epoch') +ax1.set_ylabel('Loss') +ax1.legend() + +# 绘制训练准确率曲线 +ax2.plot(vgg_epoch, vgg_train_acc, label='VGG Train Accuracy') +ax2.plot(vgg_epoch, vgg_valid_acc, label='VGG Valid Accuracy') +ax2.plot(qccnn_epoch, qccnn_train_acc, label='QCCNN Train Accuracy') +ax2.plot(qccnn_epoch, qccnn_valid_acc, label='QCCNN Valid Accuracy') +ax2.set_title('Training Accuracy Curve') +ax2.set_xlabel('Epoch') +ax2.set_ylabel('Accuracy') +ax2.legend() + +plt.show() +#%% +# 这里我们对比不同模型之间可训练参数量的区别 + +def count_parameters(model): + """ + 计算模型的参数数量 + """ + return sum(p.numel() for p in model.parameters() if p.requires_grad) + +number_params_VGG = count_parameters(VGG) +number_params_QCCNN = count_parameters(QCCNN()) +print(f'VGG 模型可训练参数量:{number_params_VGG}\t QCCNN模型可训练参数量:{number_params_QCCNN}') \ No newline at end of file diff --git a/data/notebook2/FashionMNIST/raw/t10k-images-idx3-ubyte b/data/notebook2/FashionMNIST/raw/t10k-images-idx3-ubyte new file mode 100644 index 0000000..37bac79 Binary files /dev/null and b/data/notebook2/FashionMNIST/raw/t10k-images-idx3-ubyte differ diff --git a/data/notebook2/FashionMNIST/raw/t10k-images-idx3-ubyte.gz b/data/notebook2/FashionMNIST/raw/t10k-images-idx3-ubyte.gz new file mode 100644 index 0000000..667844f Binary files /dev/null and b/data/notebook2/FashionMNIST/raw/t10k-images-idx3-ubyte.gz differ diff --git a/data/notebook2/FashionMNIST/raw/t10k-labels-idx1-ubyte b/data/notebook2/FashionMNIST/raw/t10k-labels-idx1-ubyte new file mode 100644 index 0000000..2195a4d Binary files /dev/null and b/data/notebook2/FashionMNIST/raw/t10k-labels-idx1-ubyte differ diff --git a/data/notebook2/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz b/data/notebook2/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz new file mode 100644 index 0000000..abdddb8 Binary files /dev/null and b/data/notebook2/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz differ diff --git a/data/notebook2/FashionMNIST/raw/train-images-idx3-ubyte b/data/notebook2/FashionMNIST/raw/train-images-idx3-ubyte new file mode 100644 index 0000000..ff2f5a9 Binary files /dev/null and b/data/notebook2/FashionMNIST/raw/train-images-idx3-ubyte differ diff --git a/data/notebook2/FashionMNIST/raw/train-images-idx3-ubyte.gz b/data/notebook2/FashionMNIST/raw/train-images-idx3-ubyte.gz new file mode 100644 index 0000000..e6ee0e3 Binary files /dev/null and b/data/notebook2/FashionMNIST/raw/train-images-idx3-ubyte.gz differ diff --git a/data/notebook2/FashionMNIST/raw/train-labels-idx1-ubyte b/data/notebook2/FashionMNIST/raw/train-labels-idx1-ubyte new file mode 100644 index 0000000..30424ca Binary files /dev/null and b/data/notebook2/FashionMNIST/raw/train-labels-idx1-ubyte differ diff --git a/data/notebook2/FashionMNIST/raw/train-labels-idx1-ubyte.gz b/data/notebook2/FashionMNIST/raw/train-labels-idx1-ubyte.gz new file mode 100644 index 0000000..9c4aae2 Binary files /dev/null and b/data/notebook2/FashionMNIST/raw/train-labels-idx1-ubyte.gz differ diff --git a/data/notebook2/qccnn_best.pt b/data/notebook2/qccnn_best.pt new file mode 100644 index 0000000..9aae44c Binary files /dev/null and b/data/notebook2/qccnn_best.pt differ diff --git a/data/notebook2/qccnn_metrics.csv b/data/notebook2/qccnn_metrics.csv new file mode 100644 index 0000000..4a094fc --- /dev/null +++ b/data/notebook2/qccnn_metrics.csv @@ -0,0 +1,51 @@ +epoch,train_acc,valid_acc,train_loss,valid_loss +1,0.3112916666666667,0.5076444892473119,2.1129255460103353,1.7863053237238238 +2,0.46210416666666665,0.5547715053763441,1.5605127541224162,1.3499009532313193 +3,0.5200833333333333,0.5892137096774194,1.298180630683899,1.1617528520604616 +4,0.5641666666666667,0.6311323924731183,1.161390997727712,1.0419180438082705 +5,0.6055833333333334,0.6598622311827957,1.062113331158956,0.9560088053826363 +6,0.6316666666666667,0.6804435483870968,0.9944041889508565,0.8958990977656457 +7,0.6513958333333333,0.6928763440860215,0.9395476338068645,0.8504296951396491 +8,0.6677708333333333,0.7007728494623656,0.8985106336275737,0.8148919709267155 +9,0.6809375,0.7095934139784946,0.8673048818906148,0.7897684433126962 +10,0.6876666666666666,0.7185819892473119,0.8408271819750468,0.7671139176173877 +11,0.697625,0.7247143817204301,0.8202435002326965,0.7502269674372929 +12,0.7030625,0.7303427419354839,0.8024592914581299,0.7343912464316174 +13,0.7118958333333333,0.7316868279569892,0.7869214735031128,0.7222210591839205 +14,0.7163958333333333,0.7404233870967742,0.7718908907572428,0.7093277560767307 +15,0.7211041666666667,0.7437836021505376,0.7589795832633972,0.6981546148177116 +16,0.724875,0.748067876344086,0.7483940289815267,0.6881407947950465 +17,0.7293958333333334,0.75,0.7373875479698181,0.6788085179944192 +18,0.7345,0.7517641129032258,0.7301613558133443,0.6724013622089099 +19,0.7350416666666667,0.7550403225806451,0.7210039290587107,0.6651292238184201 +20,0.7391458333333333,0.758736559139785,0.711990216255188,0.6584682823509298 +21,0.7433958333333334,0.7608366935483871,0.7047773049672444,0.6513981505106854 +22,0.7442916666666667,0.7634408602150538,0.6997552577654521,0.6454382868864204 +23,0.7481458333333333,0.7653729838709677,0.6937046423753103,0.6401395256160408 +24,0.7498333333333334,0.7683131720430108,0.6873725473880767,0.6349253362865859 +25,0.7529166666666667,0.769573252688172,0.6828897857666015,0.6308066611007977 +26,0.7547291666666667,0.7708333333333334,0.6747500312328338,0.6260690948655528 +27,0.7547291666666667,0.7722614247311828,0.6727884339491527,0.6230032514500362 +28,0.7580208333333334,0.7736055107526881,0.6705719958146413,0.6201616948650729 +29,0.757125,0.7748655913978495,0.6678872625033061,0.6165956912502166 +30,0.76025,0.7759576612903226,0.6641453323364258,0.6141596103227267 +31,0.7609583333333333,0.7767137096774194,0.6619020007451375,0.6114715427480718 +32,0.7618333333333334,0.776377688172043,0.6576849890549977,0.609050533463878 +33,0.7629583333333333,0.7783938172043011,0.6566993356545766,0.6069687149857962 +34,0.7631666666666667,0.7791498655913979,0.6553838003476461,0.6052348261238426 +35,0.7626666666666667,0.7785618279569892,0.6523663277626037,0.6037159392269709 +36,0.7653333333333333,0.780325940860215,0.6526273953914642,0.6023694485105494 +37,0.7639791666666667,0.7810819892473119,0.6524330813884736,0.6012957778669172 +38,0.7673958333333334,0.7799059139784946,0.6500039516290029,0.6002496516191831 +39,0.764875,0.7815860215053764,0.6476710465749105,0.5992967845291219 +40,0.7661041666666667,0.782258064516129,0.6484741485118866,0.5987250285763894 +41,0.7668333333333334,0.7818380376344086,0.64447620677948,0.5979425240588444 +42,0.7662291666666666,0.7817540322580645,0.6458657967249553,0.5976644568545844 +43,0.76675,0.7824260752688172,0.6449048202037811,0.5971484975789183 +44,0.7677916666666667,0.7825100806451613,0.6437487976551056,0.5968063899906733 +45,0.7677083333333333,0.7825940860215054,0.6432626353104909,0.5964810960395361 +46,0.7666041666666666,0.7826780913978495,0.6452286802132925,0.5963560240243071 +47,0.768875,0.7827620967741935,0.6432473388512929,0.5962550809947393 +48,0.7674583333333334,0.7826780913978495,0.6433716455300649,0.5961945524779699 +49,0.7678333333333334,0.7827620967741935,0.6440556212266286,0.596176440036425 +50,0.7677083333333333,0.7825940860215054,0.6438165396849315,0.5961719805835396 diff --git a/data/notebook2/random_qccnn_best.pt b/data/notebook2/random_qccnn_best.pt new file mode 100644 index 0000000..016e9a0 Binary files /dev/null and b/data/notebook2/random_qccnn_best.pt differ diff --git a/data/notebook2/random_qccnn_metrics.csv b/data/notebook2/random_qccnn_metrics.csv new file mode 100644 index 0000000..3050a59 --- /dev/null +++ b/data/notebook2/random_qccnn_metrics.csv @@ -0,0 +1,51 @@ +epoch,train_acc,valid_acc,train_loss,valid_loss +1,0.6377291666666667,0.7426075268817204,1.0222952149709066,0.6658745436899124 +2,0.7588541666666667,0.7799059139784946,0.6436424539883931,0.5876969707909451 +3,0.7829791666666667,0.7955309139784946,0.5888768948713938,0.5508138095178912 +4,0.7927916666666667,0.8009912634408602,0.5641531114578247,0.5473136597423143 +5,0.7997291666666667,0.8065356182795699,0.5444657148520152,0.515406842834206 +6,0.8043541666666667,0.8140120967741935,0.5316140202681223,0.5052012169873843 +7,0.80975,0.8156922043010753,0.521293937365214,0.4983856466508681 +8,0.8155208333333334,0.8162802419354839,0.5079634555180867,0.49958501611986467 +9,0.8161875,0.8159442204301075,0.5036936206022898,0.4907228536503289 +10,0.816875,0.8160282258064516,0.497530157327652,0.4895895427914076 +11,0.8210416666666667,0.8269489247311828,0.4916797243754069,0.47517218673101036 +12,0.8210416666666667,0.8266129032258065,0.4892559293905894,0.468310099135163 +13,0.8241041666666666,0.8211525537634409,0.4825246704419454,0.4856182368852759 +14,0.8239791666666667,0.8264448924731183,0.48390908201535543,0.46895075997998636 +15,0.8245,0.8230846774193549,0.4795244421164195,0.47176380727880746 +16,0.8270416666666667,0.829133064516129,0.47462198424339297,0.4671747069205007 +17,0.8273958333333333,0.8297211021505376,0.4733834793567657,0.4658442559421703 +18,0.828125,0.8337533602150538,0.47195579528808596,0.46035799896845253 +19,0.8294583333333333,0.829133064516129,0.4668528276284536,0.46208705472689804 +20,0.830625,0.8303931451612904,0.46666145197550457,0.4616392642580053 +21,0.8320416666666667,0.8280409946236559,0.46406093080838523,0.4690516353935324 +22,0.8315833333333333,0.8293010752688172,0.4628266294002533,0.4590829178210228 +23,0.8315416666666666,0.8298051075268817,0.46202420779069264,0.4582436110383721 +24,0.8328541666666667,0.8282090053763441,0.4594616918563843,0.46520241454083433 +25,0.8321458333333334,0.8313172043010753,0.45899707794189454,0.4611524093535639 +26,0.835,0.8311491935483871,0.4561348853111267,0.460902286793596 +27,0.8342708333333333,0.8335013440860215,0.45571692689259846,0.4524733103731627 +28,0.83475,0.831989247311828,0.45314634958902994,0.4535403392648184 +29,0.8353958333333333,0.8333333333333334,0.4508692183494568,0.4509869323622796 +30,0.8358958333333333,0.8314012096774194,0.4508490650653839,0.4559444804345408 +31,0.8379791666666667,0.8347614247311828,0.44810916344324747,0.45132114586009775 +32,0.8369375,0.832997311827957,0.4488534228801727,0.4531173186917459 +33,0.8375208333333334,0.8366935483870968,0.4460577855904897,0.4518213977095901 +34,0.8372708333333333,0.8314852150537635,0.4453533016045888,0.4571616124081355 +35,0.8381458333333334,0.8351814516129032,0.4446527805328369,0.4513627043975297 +36,0.8392916666666667,0.8373655913978495,0.4431237142880758,0.44869983228304056 +37,0.838375,0.8347614247311828,0.4424218458334605,0.4486082660895522 +38,0.8397916666666667,0.8355174731182796,0.4412206415732702,0.44703892129723743 +39,0.8401875,0.8365255376344086,0.4396123299598694,0.4470836206149029 +40,0.8399791666666667,0.8345934139784946,0.43971687642733254,0.4466231196157394 +41,0.8410833333333333,0.8373655913978495,0.43810279977321626,0.4461495735312021 +42,0.8408958333333333,0.8372815860215054,0.4371747978528341,0.4456534408113008 +43,0.8414583333333333,0.8363575268817204,0.43774009283383686,0.4454879440287108 +44,0.841375,0.8358534946236559,0.4363077602386475,0.4460145914426414 +45,0.8408958333333333,0.8363575268817204,0.43665687982241314,0.44518788110825325 +46,0.841875,0.8371135752688172,0.43598757874965666,0.44520101848468985 +47,0.8422083333333333,0.836945564516129,0.4349166991710663,0.44520143988311933 +48,0.8421041666666667,0.8360215053763441,0.43520630804697674,0.44510498354511874 +49,0.8416875,0.836861559139785,0.4354708949327469,0.44491737311886204 +50,0.8408125,0.836861559139785,0.43524290529886883,0.4449239748139535 diff --git a/data/notebook2/vgg_best.pt b/data/notebook2/vgg_best.pt new file mode 100644 index 0000000..787171d Binary files /dev/null and b/data/notebook2/vgg_best.pt differ diff --git a/data/notebook2/vgg_metrics.csv b/data/notebook2/vgg_metrics.csv new file mode 100644 index 0000000..a0ee2e0 --- /dev/null +++ b/data/notebook2/vgg_metrics.csv @@ -0,0 +1,51 @@ +epoch,train_acc,valid_acc,train_loss,valid_loss +1,0.2535833333333333,0.41952284946236557,2.274448096593221,2.2001695325297694 +2,0.4691875,0.5073084677419355,2.116311640103658,2.051948335862929 +3,0.5270416666666666,0.5266297043010753,2.009436710357666,1.9777893276624783 +4,0.5353541666666667,0.535114247311828,1.9530798486073813,1.9356736265203005 +5,0.5935416666666666,0.6175235215053764,1.9118746633529664,1.891348486305565 +6,0.636625,0.6625504032258065,1.8629151741663614,1.847881397893352 +7,0.686625,0.7302587365591398,1.8228577140172322,1.8057919330494379 +8,0.7415416666666667,0.7511760752688172,1.7803055089314779,1.7647134245082896 +9,0.7542291666666666,0.7582325268817204,1.7502256110509236,1.7423059594246648 +10,0.7607708333333333,0.764616935483871,1.7317150996526083,1.724781782396378 +11,0.7677291666666667,0.7719254032258065,1.7192926041285197,1.713816902970755 +12,0.7704791666666667,0.7731014784946236,1.71117463239034,1.7070557173862253 +13,0.771625,0.7736055107526881,1.7053865385055542,1.7027398668309695 +14,0.7747708333333333,0.7749495967741935,1.7004834445317587,1.6978983327906618 +15,0.7763333333333333,0.7751176075268817,1.6967166414260864,1.6960316857983988 +16,0.7790416666666666,0.7767977150537635,1.6929608987172444,1.692371742699736 +17,0.780125,0.7801579301075269,1.6910814901987712,1.6917471321680213 +18,0.7817916666666667,0.7827620967741935,1.6882368882497152,1.6861866725388395 +19,0.7827291666666667,0.7815860215053764,1.6857908573150635,1.6868788478195027 +20,0.7827083333333333,0.7840221774193549,1.6843910643259685,1.6850647259784002 +21,0.7848541666666666,0.7857862903225806,1.6823169787724812,1.6813292862266622 +22,0.7876666666666666,0.7848622311827957,1.6801685171127319,1.6815461574062225 +23,0.7879791666666667,0.7848622311827957,1.6788572374979656,1.6818229216401295 +24,0.7890833333333334,0.7859543010752689,1.6776897859573365,1.6799169009731663 +25,0.7902708333333334,0.7883904569892473,1.6763870159784953,1.6777271570697907 +26,0.791,0.789986559139785,1.6751194190979004,1.6777111407249206 +27,0.7918541666666666,0.7886424731182796,1.674490571975708,1.6776156912567795 +28,0.7936666666666666,0.7905745967741935,1.6728278172810873,1.6744540147883917 +29,0.793375,0.7876344086021505,1.671903525352478,1.6776354030896259 +30,0.7945833333333333,0.7901545698924731,1.6710031661987306,1.6751063805754467 +31,0.7959166666666667,0.7932627688172043,1.6700964994430543,1.6719800926023913 +32,0.7955833333333333,0.7933467741935484,1.6697869466145834,1.6723043623790945 +33,0.7971458333333333,0.792002688172043,1.668815224647522,1.6722341519530102 +34,0.7974375,0.7931787634408602,1.6682115157445272,1.6723163063808153 +35,0.7979791666666667,0.7947748655913979,1.6676976483662924,1.670254216399244 +36,0.7984583333333334,0.7950268817204301,1.6669070380528768,1.6704239499184392 +37,0.799,0.795866935483871,1.6664897476832072,1.6691849411174815 +38,0.7991875,0.7948588709677419,1.6660230029424032,1.6700230785595473 +39,0.800125,0.795950940860215,1.6655609397888183,1.6688298884258475 +40,0.8006458333333333,0.7956989247311828,1.6652250595092772,1.6690674840763051 +41,0.8006875,0.7962869623655914,1.664971160888672,1.6688041135828982 +42,0.8015416666666667,0.795866935483871,1.6647087678909303,1.668453808753721 +43,0.8013958333333333,0.7962029569892473,1.6643381754557292,1.6682484457569737 +44,0.8016458333333333,0.7965389784946236,1.6641663211186728,1.668392535178892 +45,0.8017708333333333,0.7957829301075269,1.6640429185231527,1.6681856429705055 +46,0.8021041666666666,0.7965389784946236,1.663849822362264,1.6680005634984663 +47,0.8024583333333334,0.7966229838709677,1.663770879427592,1.6679569816076627 +48,0.8025833333333333,0.7962029569892473,1.6636734215418498,1.6680130330465173 +49,0.8024583333333334,0.7970430107526881,1.6635978577931723,1.6679544077124646 +50,0.8025,0.7970430107526881,1.6635685895284016,1.6679527682642783