716 lines
57 KiB
Plaintext
716 lines
57 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "code",
|
||
"id": "initial_id",
|
||
"metadata": {
|
||
"collapsed": true,
|
||
"ExecuteTime": {
|
||
"end_time": "2025-06-24T05:07:43.448551Z",
|
||
"start_time": "2025-06-24T05:07:43.431592Z"
|
||
}
|
||
},
|
||
"source": [
|
||
"# 首先我们导入所有需要的包:\n",
|
||
"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 tqdm import tqdm\n",
|
||
"from sklearn.metrics import roc_auc_score\n",
|
||
"from torch.utils.data import DataLoader\n",
|
||
"from torchvision.datasets import MNIST, FashionMNIST\n",
|
||
"\n",
|
||
"def seed_torch(seed=1024):\n",
|
||
" \"\"\"\n",
|
||
" Set random seeds for reproducibility.\n",
|
||
"\n",
|
||
" Args:\n",
|
||
" seed (int): Random seed number to use. Default is 1024.\n",
|
||
" \"\"\"\n",
|
||
"\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",
|
||
"\n",
|
||
" # Seed all GPUs with the same seed if using multi-GPU\n",
|
||
" torch.cuda.manual_seed_all(seed)\n",
|
||
" torch.backends.cudnn.benchmark = False\n",
|
||
" torch.backends.cudnn.deterministic = True\n",
|
||
"\n",
|
||
"seed_torch(1024)"
|
||
],
|
||
"outputs": [],
|
||
"execution_count": 4
|
||
},
|
||
{
|
||
"metadata": {
|
||
"ExecuteTime": {
|
||
"end_time": "2025-06-24T05:07:43.472777Z",
|
||
"start_time": "2025-06-24T05:07:43.463779Z"
|
||
}
|
||
},
|
||
"cell_type": "code",
|
||
"source": [
|
||
"def calculate_score(y_true, y_preds):\n",
|
||
" # 将模型预测结果转为概率分布\n",
|
||
" preds_prob = torch.softmax(y_preds, dim=1)\n",
|
||
" # 获得预测的类别(概率最高的一类)\n",
|
||
" preds_class = torch.argmax(preds_prob, dim=1)\n",
|
||
" # 计算准确率\n",
|
||
" correct = (preds_class == y_true).float()\n",
|
||
" accuracy = correct.sum() / len(correct)\n",
|
||
" return accuracy.cpu().numpy()\n",
|
||
"\n",
|
||
"\n",
|
||
"def train_model(model, criterion, optimizer, train_loader, valid_loader, num_epochs, device):\n",
|
||
" \"\"\"\n",
|
||
" 训练和验证模型。\n",
|
||
"\n",
|
||
" Args:\n",
|
||
" model (torch.nn.Module): 要训练的模型。\n",
|
||
" criterion (torch.nn.Module): 损失函数。\n",
|
||
" optimizer (torch.optim.Optimizer): 优化器。\n",
|
||
" train_loader (torch.utils.data.DataLoader): 训练数据加载器。\n",
|
||
" valid_loader (torch.utils.data.DataLoader): 验证数据加载器。\n",
|
||
" num_epochs (int): 训练的epoch数。\n",
|
||
"\n",
|
||
" Returns:\n",
|
||
" model (torch.nn.Module): 训练后的模型。\n",
|
||
" \"\"\"\n",
|
||
"\n",
|
||
" model.train()\n",
|
||
" train_loss_list = []\n",
|
||
" valid_loss_list = []\n",
|
||
" train_acc_list = []\n",
|
||
" valid_acc_list = []\n",
|
||
"\n",
|
||
" with tqdm(total=num_epochs) as pbar:\n",
|
||
" for epoch in range(num_epochs):\n",
|
||
" # 训练阶段\n",
|
||
" train_loss = 0.0\n",
|
||
" train_acc = 0.0\n",
|
||
" for images, labels in train_loader:\n",
|
||
" images = images.to(device)\n",
|
||
" labels = labels.to(device)\n",
|
||
" optimizer.zero_grad()\n",
|
||
" outputs = model(images)\n",
|
||
" loss = criterion(outputs, labels)\n",
|
||
" loss.backward()\n",
|
||
" optimizer.step()\n",
|
||
" train_loss += loss.item()\n",
|
||
" train_acc += calculate_score(labels, outputs)\n",
|
||
"\n",
|
||
" train_loss /= len(train_loader)\n",
|
||
" train_acc /= len(train_loader)\n",
|
||
"\n",
|
||
" # 验证阶段\n",
|
||
" model.eval()\n",
|
||
" valid_loss = 0.0\n",
|
||
" valid_acc = 0.0\n",
|
||
" with torch.no_grad():\n",
|
||
" for images, labels in valid_loader:\n",
|
||
" images = images.to(device)\n",
|
||
" labels = labels.to(device)\n",
|
||
" outputs = model(images)\n",
|
||
" loss = criterion(outputs, labels)\n",
|
||
" valid_loss += loss.item()\n",
|
||
" valid_acc += calculate_score(labels, outputs)\n",
|
||
"\n",
|
||
" valid_loss /= len(valid_loader)\n",
|
||
" valid_acc /= len(valid_loader)\n",
|
||
"\n",
|
||
" pbar.set_description(f\"Train loss: {train_loss:.3f} Valid Acc: {valid_acc:.3f}\")\n",
|
||
" pbar.update()\n",
|
||
"\n",
|
||
"\n",
|
||
" train_loss_list.append(train_loss)\n",
|
||
" valid_loss_list.append(valid_loss)\n",
|
||
" train_acc_list.append(train_acc)\n",
|
||
" valid_acc_list.append(valid_acc)\n",
|
||
"\n",
|
||
" metrics = {'epoch': list(range(1, num_epochs + 1)),\n",
|
||
" 'train_acc': train_acc_list,\n",
|
||
" 'valid_acc': valid_acc_list,\n",
|
||
" 'train_loss': train_loss_list,\n",
|
||
" 'valid_loss': valid_loss_list}\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
" return model, metrics\n",
|
||
"\n",
|
||
"def test_model(model, test_loader, device):\n",
|
||
" model.eval()\n",
|
||
" test_acc = 0.0\n",
|
||
" with torch.no_grad():\n",
|
||
" for images, labels in test_loader:\n",
|
||
" images = images.to(device)\n",
|
||
" labels = labels.to(device)\n",
|
||
" outputs = model(images)\n",
|
||
" test_acc += calculate_score(labels, outputs)\n",
|
||
"\n",
|
||
" test_acc /= len(test_loader)\n",
|
||
" print(f'Test Acc: {test_acc:.3f}')\n",
|
||
" return test_acc"
|
||
],
|
||
"id": "cc4c2323375a0d64",
|
||
"outputs": [],
|
||
"execution_count": 5
|
||
},
|
||
{
|
||
"metadata": {
|
||
"ExecuteTime": {
|
||
"end_time": "2025-06-24T05:07:44.895616Z",
|
||
"start_time": "2025-06-24T05:07:43.501957Z"
|
||
}
|
||
},
|
||
"cell_type": "code",
|
||
"source": [
|
||
"# 定义图像变换\n",
|
||
"trans1 = transforms.Compose([\n",
|
||
" transforms.Resize((18, 18)), # 调整大小为18x18\n",
|
||
" transforms.ToTensor() # 转换为张量\n",
|
||
"])\n",
|
||
"\n",
|
||
"trans2 = transforms.Compose([\n",
|
||
" transforms.Resize((16, 16)), # 调整大小为16x16\n",
|
||
" transforms.ToTensor() # 转换为张量\n",
|
||
"])\n",
|
||
"train_dataset = FashionMNIST(root='./data/notebook1', train=False, transform=trans1,download=True)\n",
|
||
"test_dataset = FashionMNIST(root='./data/notebook1', train=False, transform=trans1,download=True)\n",
|
||
"\n",
|
||
"# 定义训练集和测试集的比例\n",
|
||
"train_ratio = 0.8 # 训练集比例为80%,验证集比例为20%\n",
|
||
"valid_ratio = 0.2\n",
|
||
"total_samples = len(train_dataset)\n",
|
||
"train_size = int(train_ratio * total_samples)\n",
|
||
"valid_size = int(valid_ratio * total_samples)\n",
|
||
"\n",
|
||
"# 分割训练集和测试集\n",
|
||
"train_dataset, valid_dataset = torch.utils.data.random_split(train_dataset, [train_size, valid_size])\n",
|
||
"\n",
|
||
"# 加载随机抽取的训练数据集\n",
|
||
"train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True, drop_last=True)\n",
|
||
"valid_loader = DataLoader(valid_dataset, batch_size=64, shuffle=False, drop_last=True)\n",
|
||
"test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False, drop_last=True)"
|
||
],
|
||
"id": "4b641527c641afc1",
|
||
"outputs": [
|
||
{
|
||
"name": "stderr",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"100%|██████████| 5.15k/5.15k [00:00<?, ?B/s]\n"
|
||
]
|
||
}
|
||
],
|
||
"execution_count": 6
|
||
},
|
||
{
|
||
"metadata": {
|
||
"ExecuteTime": {
|
||
"end_time": "2025-06-24T05:07:44.960786Z",
|
||
"start_time": "2025-06-24T05:07:44.954222Z"
|
||
}
|
||
},
|
||
"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']"
|
||
],
|
||
"id": "1c3e55f43e47a4f1",
|
||
"outputs": [],
|
||
"execution_count": 7
|
||
},
|
||
{
|
||
"metadata": {
|
||
"ExecuteTime": {
|
||
"end_time": "2025-06-24T05:07:44.989143Z",
|
||
"start_time": "2025-06-24T05:07:44.981160Z"
|
||
}
|
||
},
|
||
"cell_type": "code",
|
||
"source": [
|
||
"# 随机量子卷积层\n",
|
||
"class RandomQuantumConvolutionalLayer(nn.Module):\n",
|
||
" def __init__(self, nqubit, num_circuits, seed:int=1024):\n",
|
||
" super(RandomQuantumConvolutionalLayer, self).__init__()\n",
|
||
" random.seed(seed)\n",
|
||
" self.nqubit = nqubit\n",
|
||
" self.cirs = nn.ModuleList([self.circuit(nqubit) for _ in range(num_circuits)])\n",
|
||
"\n",
|
||
" def circuit(self, nqubit):\n",
|
||
" cir = dq.QubitCircuit(nqubit)\n",
|
||
" cir.rxlayer(encode=True) # 对原论文的量子线路结构并无影响,只是做了一个数据编码的操作\n",
|
||
" cir.barrier()\n",
|
||
" for iter in range(3):\n",
|
||
" for i in range(nqubit):\n",
|
||
" singlegate = random.choice(singlegate_list)\n",
|
||
" getattr(cir, singlegate)(i)\n",
|
||
" control_bit, target_bit = random.sample(range(0, nqubit - 1), 2)\n",
|
||
" doublegate = random.choice(doublegate_list)\n",
|
||
" if doublegate[0] in ['r', 's']:\n",
|
||
" getattr(cir, doublegate)([control_bit, target_bit])\n",
|
||
" else:\n",
|
||
" getattr(cir, doublegate)(control_bit, target_bit)\n",
|
||
" cir.barrier()\n",
|
||
"\n",
|
||
" cir.observable(0)\n",
|
||
" return cir\n",
|
||
"\n",
|
||
" def forward(self, x):\n",
|
||
" kernel_size, stride = 2, 2\n",
|
||
" # [64, 1, 18, 18] -> [64, 1, 9, 18, 2] -> [64, 1, 9, 9, 2, 2]\n",
|
||
" x_unflod = x.unfold(2, kernel_size, stride).unfold(3, kernel_size, stride)\n",
|
||
" w = int((x.shape[-1] - kernel_size) / stride + 1)\n",
|
||
" x_reshape = x_unflod.reshape(-1, self.nqubit)\n",
|
||
"\n",
|
||
" exps = []\n",
|
||
" for cir in self.cirs: # out_channels\n",
|
||
" cir(x_reshape)\n",
|
||
" exp = cir.expectation()\n",
|
||
" exps.append(exp)\n",
|
||
"\n",
|
||
" exps = torch.stack(exps, dim=1)\n",
|
||
" exps = exps.reshape(x.shape[0], 3, w, w)\n",
|
||
" return exps"
|
||
],
|
||
"id": "f03fcd820876a62",
|
||
"outputs": [],
|
||
"execution_count": 8
|
||
},
|
||
{
|
||
"metadata": {
|
||
"ExecuteTime": {
|
||
"end_time": "2025-06-24T05:07:45.475988Z",
|
||
"start_time": "2025-06-24T05:07:45.019221Z"
|
||
}
|
||
},
|
||
"cell_type": "code",
|
||
"source": [
|
||
"net = RandomQuantumConvolutionalLayer(nqubit=4, num_circuits=3, seed=1024)\n",
|
||
"net.cirs[0].draw()"
|
||
],
|
||
"id": "fcea5aa513a0bd68",
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"<Figure size 1207.22x367.889 with 1 Axes>"
|
||
],
|
||
"image/png": "iVBORw0KGgoAAAANSUhEUgAAA7UAAAEvCAYAAACaO+Y5AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAPYQAAD2EBqD+naQAAX8hJREFUeJzt3QlYVFX/B/DvsK+CLCqKCCKIa5qKuedWLmm5tJrma/urab2mZsvfbDFTe01ts7LMFrPUci3NNMMd9w0R2QQBUUAWZWf+zzm8TCKogDJ37r3fz/PMc5k7M3CGued35nfPcg1Go9EIIiIiIiIiIhWyUroARERERERERDXFpJaIiIiIiIhUi0ktERERERERqRaTWiIiIiIiIlItJrVERERERESkWkxqiYiIiIiISLWY1BIREREREZFqMaklIiIiIiIi1WJSS0RERERERKrFpJaIiIiIiIhUi0ktERERERERqRaTWiIiIiIiIlItJrVERERERESkWkxqiYiIiIiISLWY1BIREREREZFqMaklIiIiIiIi1WJSS0RERERERKrFpJaIiIiIiIhUi0ktERERERERqRaTWiIiIiIiIlItJrVERERERESkWkxqiYiIiIiISLWY1BIREREREZFqMaklIiIiIiIi1WJSS0RERERERKrFpJaIiIiIiIhUi0ktERERERERqRaTWiIiIiIiIlItG6ULQHQrwsPDq/X8ixcvYvXq1Rg+fDi8vLyq9JpOnTrVsHREZGlxoCYxQGAcIEvGtpCIwnXeFrKnlnRFVOIvv/xSbolIfxgDiFgPiPTuogZjAJNaIiIiIiIiUi0mtURERERERKRaTGqJiIiIiIhItZjUkq64urpiwIABcktE+sMYQMR6QKR3rhqMAQaj0WhUuhBE5lrxsSYseaU3ImIcIGIdIKJwnccB9tSSruTn5yMhIUFuiUh/GAOIWA+I9C5fgzGASS3pSmxsLEaMGCG3RKQ/jAFErAdEeherwRhgo3QBqHJiVHhRrnrOntg42sNgMChdDFVS22dN1cO6QXqKDTzeSa/HPt16LNDq5864aB5Mai2UqNTfBz4OtRgV/R1snRyULoYqqe2zpuph3SA9xQYe76TXY59uPRZo9XNnXDQPDj8mIiIiIiIi1WJSS0RERERERKrF4cekKyEhIdi3b5/SxSAihTAGELEeEOldiAZj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P7V72vQ6MRd295TP5P7rCXqsD878tE3O6712JW/XAB/Zi1pSWH5u7vX+vl0dJzk/t4zfgE7y/YnbtRzruZvmN9k4O8C3XwekHS891rPPnodPt9ayfgi+/TviUmTCdd8DkRrYuteDU+CdSPvrO3n/0q5VsPP0rXTocXFuDqJmDkCdOwfA56HXFSgtkboV5VxCQdo/ScKlPb/CxtUT1q4elT4/bcuScr20Qp0OA1GQloiCtHPyftaBjXDwbWEankxEleOcWg0QQyR9+3aQX9j7L39d9kat7voCus57Ts4TlHMFu7dGiycHyXmEIrkVQyuPzl9p6qk9unA1Bq17F8YSo+yx3TWlfMJDlsvJxwOhb45FVlwKBqx8U+4Tl8TZMHi6/LndlIeRez5DJpkB93eD/+C75GV6hv4xVz5+PjwSe1/98qZ/x+uOpohYcuO5QZW5+u8Ltq5O8BvUGWt6l+8FqtepuZwPfCkqEfdteE/uy05IxbZxc2/4923rOOHuzyfLxc/E8Svm6P45pvT1wtX1oMngu9D8iXvlommiHsSv240zP26Vzzv19e9wD/LF0D/noaSwWF5qSAzJJ1KL+E+eReb+DXI+bNSb98rLibRefAZNnl+MuIVjkbJyFqwd68B/4tem18QtekouUOPeeShS1y3A5ah9KMm/jEt7Si8tUrfrg/B56DUF3xWReoiFoWLmPIiSglwYDFawqeMt56WLuexX1zUhLzESV2IOo9kb5ds1awdnNHn+M5x5e7AcNSQuwyVWJSeiGzMYxVgksjiFV/LwfWDl8wIt0ajo72Dr5GD2vxseXvWFhIRTp05hzJgxWLZsGUJCQqr0mk6dOkHvx4W9Zx30+ngSNj/yttn/tiX8fUs53unW40BNYoBScSC3COhR/fNIigobBDjydLnZ6a0tNBfWQVKTcI22hVXF4cekK/b29mjevLncUtWJlbCVTCiV/vukHYwBRKwHRHpnr8EYwHM5pCsBAQH49ttvlS4GESmEMYCI9YBI7wI0GAPYU0tERERERESqxaSWdCUyMhLdunWTWyLSH8YAItYDIr2L1GAMYFJLuiLWRSssLJRbItIfxgAi1gMivTNqMAZwTq2FsnG0lyusqqm8VPvUdlxoFY93MgcH69KVTNVWZiKtYB0kUg8mtRZKXNOMlwyha/G4INIPg4GX5iBSEusgkXpw+DERERERERGpFs8/ka74+/tj+fLlaNSokdJFISIFMAYQsR4Q6Z2/BmMAk1rSFQcHBwQGBipdDCJSCGMAEesBkd45aDAGcPgx6UpycjLeeecduSUi/WEMIGI9INK7ZA3GACa1pCuZmZlYu3at3BKR/jAGELEeEOldpgZjAJNaIiIiIiIiUi0mtURERERERKRaTGqJiIiIiIhItZjUkq54eHjgiSeekFsi0h/GACLWAyK989BgDDAYjUaj0oUgIiIiIiIiqgn21BIREREREZFqMaklIiIiIiIi1WJSS0RERERERKrFpJaIiIiIiIhUi0ktERERERERqRaTWiIiIiIiIlItJrVERERERESkWkxqiYiIiIiISLWY1BIREREREZFqMaklIiIiIiIi1WJSS0RERERERKrFpJaIiIiIiIhUi0ktERERERERqRaTWiIiIiIiIlItJrVEREREREQEtfp/0FH3zftkLs4AAAAASUVORK5CYII="
|
||
},
|
||
"execution_count": 9,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"execution_count": 9
|
||
},
|
||
{
|
||
"metadata": {
|
||
"ExecuteTime": {
|
||
"end_time": "2025-06-24T05:07:45.523034Z",
|
||
"start_time": "2025-06-24T05:07:45.502484Z"
|
||
}
|
||
},
|
||
"cell_type": "code",
|
||
"source": [
|
||
"# 基于随机量子卷积层的混合模型\n",
|
||
"class RandomQCCNN(nn.Module):\n",
|
||
" def __init__(self):\n",
|
||
" super(RandomQCCNN, self).__init__()\n",
|
||
" self.conv = nn.Sequential(\n",
|
||
" RandomQuantumConvolutionalLayer(nqubit=4, num_circuits=3, seed=1024), # num_circuits=3代表我们在quanv1层只用了3个量子卷积核\n",
|
||
" nn.ReLU(),\n",
|
||
" nn.MaxPool2d(kernel_size=2, stride=1),\n",
|
||
" nn.Conv2d(3, 6, kernel_size=2, stride=1),\n",
|
||
" nn.ReLU(),\n",
|
||
" nn.MaxPool2d(kernel_size=2, stride=1)\n",
|
||
" )\n",
|
||
" self.fc = nn.Sequential(\n",
|
||
" nn.Linear(6 * 6 * 6, 1024),\n",
|
||
" nn.Dropout(0.4),\n",
|
||
" nn.Linear(1024, 10)\n",
|
||
" )\n",
|
||
"\n",
|
||
" def forward(self, x):\n",
|
||
" x = self.conv(x)\n",
|
||
" x = x.reshape(x.size(0), -1)\n",
|
||
" x = self.fc(x)\n",
|
||
" return x"
|
||
],
|
||
"id": "64082ff8ea82fe8",
|
||
"outputs": [],
|
||
"execution_count": 10
|
||
},
|
||
{
|
||
"metadata": {
|
||
"jupyter": {
|
||
"is_executing": true
|
||
},
|
||
"ExecuteTime": {
|
||
"start_time": "2025-06-24T05:07:45.545363Z"
|
||
}
|
||
},
|
||
"cell_type": "code",
|
||
"source": [
|
||
"num_epochs = 300\n",
|
||
"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
|
||
"seed_torch(1024) # 重新设置随机种子\n",
|
||
"model = RandomQCCNN()\n",
|
||
"model.to(device)\n",
|
||
"criterion = nn.CrossEntropyLoss()\n",
|
||
"optimizer = optim.SGD(model.parameters(), lr=0.01, weight_decay=0.001) # 添加正则化项\n",
|
||
"optim_model, metrics = train_model(model, criterion, optimizer, train_loader, valid_loader, num_epochs, device)\n",
|
||
"torch.save(optim_model.state_dict(), './data/notebook1/random_qccnn_weights.pt') # 保存训练好的模型参数,用于后续的推理或测试\n",
|
||
"pd.DataFrame(metrics).to_csv('./data/notebook1/random_qccnn_metrics.csv', index='None') # 保存模型训练过程,用于后续图标展示"
|
||
],
|
||
"id": "19b3021c114a9129",
|
||
"outputs": [
|
||
{
|
||
"name": "stderr",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Train loss: 1.012 Valid Acc: 0.621: 5%|▌ | 16/300 [08:18<2:36:59, 33.17s/it]"
|
||
]
|
||
}
|
||
],
|
||
"execution_count": null
|
||
},
|
||
{
|
||
"metadata": {},
|
||
"cell_type": "code",
|
||
"outputs": [],
|
||
"execution_count": null,
|
||
"source": [
|
||
"state_dict = torch.load('./data/notebook1/random_qccnn_weights.pt', map_location=device)\n",
|
||
"random_qccnn_model = RandomQCCNN()\n",
|
||
"random_qccnn_model.load_state_dict(state_dict)\n",
|
||
"random_qccnn_model.to(device)\n",
|
||
"\n",
|
||
"test_acc = test_model(random_qccnn_model, test_loader, device)"
|
||
],
|
||
"id": "49ceb326295cd4a9"
|
||
},
|
||
{
|
||
"metadata": {},
|
||
"cell_type": "code",
|
||
"outputs": [],
|
||
"execution_count": null,
|
||
"source": [
|
||
"data = pd.read_csv('./data/notebook1/random_qccnn_metrics.csv')\n",
|
||
"epoch = data['epoch']\n",
|
||
"train_loss = data['train_loss']\n",
|
||
"valid_loss = data['valid_loss']\n",
|
||
"train_acc = data['train_acc']\n",
|
||
"valid_acc = data['valid_acc']\n",
|
||
"\n",
|
||
"# 创建图和Axes对象\n",
|
||
"fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))\n",
|
||
"\n",
|
||
"# 绘制训练损失曲线\n",
|
||
"ax1.plot(epoch, train_loss, label='Train Loss')\n",
|
||
"ax1.plot(epoch, valid_loss, label='Valid Loss')\n",
|
||
"ax1.set_title('Training Loss Curve')\n",
|
||
"ax1.set_xlabel('Epoch')\n",
|
||
"ax1.set_ylabel('Loss')\n",
|
||
"ax1.legend()\n",
|
||
"\n",
|
||
"# 绘制训练准确率曲线\n",
|
||
"ax2.plot(epoch, train_acc, label='Train Accuracy')\n",
|
||
"ax2.plot(epoch, valid_acc, label='Valid Accuracy')\n",
|
||
"ax2.set_title('Training Accuracy Curve')\n",
|
||
"ax2.set_xlabel('Epoch')\n",
|
||
"ax2.set_ylabel('Accuracy')\n",
|
||
"ax2.legend()\n",
|
||
"\n",
|
||
"plt.show()"
|
||
],
|
||
"id": "45287356d5a9a0ad"
|
||
},
|
||
{
|
||
"metadata": {},
|
||
"cell_type": "code",
|
||
"outputs": [],
|
||
"execution_count": null,
|
||
"source": [
|
||
"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",
|
||
"\n",
|
||
" def circuit(self, nqubit):\n",
|
||
" cir = dq.QubitCircuit(nqubit)\n",
|
||
" cir.rxlayer(encode=True) #对原论文的量子线路结构并无影响,只是做了一个数据编码的操作\n",
|
||
" cir.barrier()\n",
|
||
" for iter in range(4): #对应原论文中一个量子卷积线路上的深度为4,可控参数一共16个\n",
|
||
" cir.rylayer()\n",
|
||
" cir.cnot_ring()\n",
|
||
" cir.barrier()\n",
|
||
"\n",
|
||
" cir.observable(0)\n",
|
||
" return cir\n",
|
||
"\n",
|
||
" def forward(self, x):\n",
|
||
" kernel_size, stride = 2, 2\n",
|
||
" # [64, 1, 18, 18] -> [64, 1, 9, 18, 2] -> [64, 1, 9, 9, 2, 2]\n",
|
||
" x_unflod = x.unfold(2, kernel_size, stride).unfold(3, kernel_size, stride)\n",
|
||
" w = int((x.shape[-1] - kernel_size) / stride + 1)\n",
|
||
" x_reshape = x_unflod.reshape(-1, self.nqubit)\n",
|
||
"\n",
|
||
" exps = []\n",
|
||
" for cir in self.cirs: # out_channels\n",
|
||
" cir(x_reshape)\n",
|
||
" exp = cir.expectation()\n",
|
||
" exps.append(exp)\n",
|
||
"\n",
|
||
" exps = torch.stack(exps, dim=1)\n",
|
||
" exps = exps.reshape(x.shape[0], 3, w, w)\n",
|
||
" return exps"
|
||
],
|
||
"id": "736fe987b84d5891"
|
||
},
|
||
{
|
||
"metadata": {},
|
||
"cell_type": "code",
|
||
"outputs": [],
|
||
"execution_count": null,
|
||
"source": [
|
||
"# 此处我们可视化其中一个量子卷积核的线路结构:\n",
|
||
"net = ParameterizedQuantumConvolutionalLayer(nqubit=4, num_circuits=3)\n",
|
||
"net.cirs[0].draw()"
|
||
],
|
||
"id": "e8058c7fde0a012b"
|
||
},
|
||
{
|
||
"metadata": {},
|
||
"cell_type": "code",
|
||
"outputs": [],
|
||
"execution_count": null,
|
||
"source": [
|
||
"# QCCNN整体网络架构:\n",
|
||
"class QCCNN(nn.Module):\n",
|
||
" def __init__(self):\n",
|
||
" super(QCCNN, self).__init__()\n",
|
||
" self.conv = nn.Sequential(\n",
|
||
" ParameterizedQuantumConvolutionalLayer(nqubit=4, num_circuits=3),\n",
|
||
" nn.ReLU(),\n",
|
||
" nn.MaxPool2d(kernel_size=2, stride=1)\n",
|
||
" )\n",
|
||
"\n",
|
||
" self.fc = nn.Sequential(\n",
|
||
" nn.Linear(8 * 8 * 3, 128),\n",
|
||
" nn.Dropout(0.4),\n",
|
||
" nn.ReLU(),\n",
|
||
" nn.Linear(128, 10)\n",
|
||
" )\n",
|
||
"\n",
|
||
" def forward(self, x):\n",
|
||
" x = self.conv(x)\n",
|
||
" x = x.reshape(x.size(0), -1)\n",
|
||
" x = self.fc(x)\n",
|
||
" return x"
|
||
],
|
||
"id": "e3c6160fff06bed2"
|
||
},
|
||
{
|
||
"metadata": {},
|
||
"cell_type": "code",
|
||
"outputs": [],
|
||
"execution_count": null,
|
||
"source": [
|
||
"num_epochs = 300\n",
|
||
"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
|
||
"\n",
|
||
"model = QCCNN()\n",
|
||
"model.to(device)\n",
|
||
"criterion = nn.CrossEntropyLoss()\n",
|
||
"optimizer = optim.Adam(model.parameters(), lr=1e-5) # 添加正则化项\n",
|
||
"optim_model, metrics = train_model(model, criterion, optimizer, train_loader, valid_loader, num_epochs, device)\n",
|
||
"torch.save(optim_model.state_dict(), './data/notebook1/qccnn_weights.pt') # 保存训练好的模型参数,用于后续的推理或测试\n",
|
||
"pd.DataFrame(metrics).to_csv('./data/notebook1/qccnn_metrics.csv', index='None') # 保存模型训练过程,用于后续图标展示"
|
||
],
|
||
"id": "34202fca380ee084"
|
||
},
|
||
{
|
||
"metadata": {},
|
||
"cell_type": "code",
|
||
"outputs": [],
|
||
"execution_count": null,
|
||
"source": [
|
||
"state_dict = torch.load('./data/notebook1/qccnn_weights.pt', map_location=device)\n",
|
||
"qccnn_model = QCCNN()\n",
|
||
"qccnn_model.load_state_dict(state_dict)\n",
|
||
"qccnn_model.to(device)\n",
|
||
"\n",
|
||
"test_acc = test_model(qccnn_model, test_loader, device)"
|
||
],
|
||
"id": "f613b1c9a9ea0cd6"
|
||
},
|
||
{
|
||
"metadata": {},
|
||
"cell_type": "code",
|
||
"outputs": [],
|
||
"execution_count": null,
|
||
"source": [
|
||
"def vgg_block(in_channel,out_channel,num_convs):\n",
|
||
" layers = nn.ModuleList()\n",
|
||
" assert num_convs >= 1\n",
|
||
" layers.append(nn.Conv2d(in_channel,out_channel,kernel_size=3,padding=1))\n",
|
||
" layers.append(nn.ReLU())\n",
|
||
" for _ in range(num_convs-1):\n",
|
||
" layers.append(nn.Conv2d(out_channel,out_channel,kernel_size=3,padding=1))\n",
|
||
" layers.append(nn.ReLU())\n",
|
||
" layers.append(nn.MaxPool2d(kernel_size=2,stride=2))\n",
|
||
" return nn.Sequential(*layers)\n",
|
||
"\n",
|
||
"VGG = nn.Sequential(\n",
|
||
" vgg_block(1,10,3), # 14,14\n",
|
||
" vgg_block(10,16,3), # 4 * 4\n",
|
||
" nn.Flatten(),\n",
|
||
" nn.Linear(16 * 4 * 4, 120),\n",
|
||
" nn.Sigmoid(),\n",
|
||
" nn.Linear(120, 84),\n",
|
||
" nn.Sigmoid(),\n",
|
||
" nn.Linear(84,10),\n",
|
||
" nn.Softmax(dim=-1)\n",
|
||
")"
|
||
],
|
||
"id": "37cc9edc6c4b035d"
|
||
},
|
||
{
|
||
"metadata": {},
|
||
"cell_type": "code",
|
||
"outputs": [],
|
||
"execution_count": null,
|
||
"source": [
|
||
"num_epochs = 300\n",
|
||
"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
|
||
"\n",
|
||
"vgg_model = VGG\n",
|
||
"vgg_model.to(device)\n",
|
||
"criterion = nn.CrossEntropyLoss()\n",
|
||
"optimizer = optim.Adam(vgg_model.parameters(), lr=1e-5) # 添加正则化项\n",
|
||
"vgg_model, metrics = train_model(vgg_model, criterion, optimizer, train_loader, valid_loader, num_epochs, device)\n",
|
||
"torch.save(vgg_model.state_dict(), './data/notebook1/vgg_weights.pt') # 保存训练好的模型参数,用于后续的推理或测试\n",
|
||
"pd.DataFrame(metrics).to_csv('./data/notebook1/vgg_metrics.csv', index='None') # 保存模型训练过程,用于后续图标展示"
|
||
],
|
||
"id": "643da0fb0433f438"
|
||
},
|
||
{
|
||
"metadata": {},
|
||
"cell_type": "code",
|
||
"outputs": [],
|
||
"execution_count": null,
|
||
"source": [
|
||
"state_dict = torch.load('./data/notebook1/vgg_weights.pt', map_location=device)\n",
|
||
"vgg_model = VGG\n",
|
||
"vgg_model.load_state_dict(state_dict)\n",
|
||
"vgg_model.to(device)\n",
|
||
"\n",
|
||
"vgg_test_acc = test_model(vgg_model, test_loader, device)"
|
||
],
|
||
"id": "cc56710965ab7c82"
|
||
},
|
||
{
|
||
"metadata": {},
|
||
"cell_type": "code",
|
||
"outputs": [],
|
||
"execution_count": null,
|
||
"source": [
|
||
"vgg_data = pd.read_csv('./data/notebook1/vgg_metrics.csv')\n",
|
||
"qccnn_data = pd.read_csv('./data/notebook1/qccnn_metrics.csv')\n",
|
||
"vgg_epoch = vgg_data['epoch']\n",
|
||
"vgg_train_loss = vgg_data['train_loss']\n",
|
||
"vgg_valid_loss = vgg_data['valid_loss']\n",
|
||
"vgg_train_acc = vgg_data['train_acc']\n",
|
||
"vgg_valid_acc = vgg_data['valid_acc']\n",
|
||
"\n",
|
||
"qccnn_epoch = qccnn_data['epoch']\n",
|
||
"qccnn_train_loss = qccnn_data['train_loss']\n",
|
||
"qccnn_valid_loss = qccnn_data['valid_loss']\n",
|
||
"qccnn_train_acc = qccnn_data['train_acc']\n",
|
||
"qccnn_valid_acc = qccnn_data['valid_acc']\n",
|
||
"\n",
|
||
"# 创建图和Axes对象\n",
|
||
"fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))\n",
|
||
"\n",
|
||
"# 绘制训练损失曲线\n",
|
||
"ax1.plot(vgg_epoch, vgg_train_loss, label='VGG Train Loss')\n",
|
||
"ax1.plot(vgg_epoch, vgg_valid_loss, label='VGG Valid Loss')\n",
|
||
"ax1.plot(qccnn_epoch, qccnn_train_loss, label='QCCNN Valid Loss')\n",
|
||
"ax1.plot(qccnn_epoch, qccnn_valid_loss, label='QCCNN Valid Loss')\n",
|
||
"ax1.set_title('Training Loss Curve')\n",
|
||
"ax1.set_xlabel('Epoch')\n",
|
||
"ax1.set_ylabel('Loss')\n",
|
||
"ax1.legend()\n",
|
||
"\n",
|
||
"# 绘制训练准确率曲线\n",
|
||
"ax2.plot(vgg_epoch, vgg_train_acc, label='VGG Train Accuracy')\n",
|
||
"ax2.plot(vgg_epoch, vgg_valid_acc, label='VGG Valid Accuracy')\n",
|
||
"ax2.plot(qccnn_epoch, qccnn_train_acc, label='QCCNN Train Accuracy')\n",
|
||
"ax2.plot(qccnn_epoch, qccnn_valid_acc, label='QCCNN Valid Accuracy')\n",
|
||
"ax2.set_title('Training Accuracy Curve')\n",
|
||
"ax2.set_xlabel('Epoch')\n",
|
||
"ax2.set_ylabel('Accuracy')\n",
|
||
"ax2.legend()\n",
|
||
"\n",
|
||
"plt.show()"
|
||
],
|
||
"id": "8e450f8cfb2812d2"
|
||
},
|
||
{
|
||
"metadata": {},
|
||
"cell_type": "code",
|
||
"outputs": [],
|
||
"execution_count": null,
|
||
"source": [
|
||
"# 这里我们对比不同模型之间可训练参数量的区别\n",
|
||
"\n",
|
||
"def count_parameters(model):\n",
|
||
" \"\"\"\n",
|
||
" 计算模型的参数数量\n",
|
||
" \"\"\"\n",
|
||
" return sum(p.numel() for p in model.parameters() if p.requires_grad)\n",
|
||
"\n",
|
||
"number_params_VGG = count_parameters(VGG)\n",
|
||
"number_params_QCCNN = count_parameters(QCCNN())\n",
|
||
"print(f'VGG 模型可训练参数量:{number_params_VGG}\\t QCCNN模型可训练参数量:{number_params_QCCNN}')"
|
||
],
|
||
"id": "9675ba847f4a998d"
|
||
}
|
||
],
|
||
"metadata": {
|
||
"kernelspec": {
|
||
"display_name": "Python 3",
|
||
"language": "python",
|
||
"name": "python3"
|
||
},
|
||
"language_info": {
|
||
"codemirror_mode": {
|
||
"name": "ipython",
|
||
"version": 2
|
||
},
|
||
"file_extension": ".py",
|
||
"mimetype": "text/x-python",
|
||
"name": "python",
|
||
"nbconvert_exporter": "python",
|
||
"pygments_lexer": "ipython2",
|
||
"version": "2.7.6"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 5
|
||
}
|