{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":3649,"databundleVersionId":46718,"sourceType":"competition"}],"dockerImageVersionId":31041,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# cifar-10图像分类竞赛\n**付柳琴**","metadata":{}},{"cell_type":"markdown","source":"CIFAR-10是由加拿大计算机科学研究所（CIFAR）发起的经典图像分类竞赛，也是计算机视觉领域的入门级基准任务。数据集大小共有6万张32x32彩色图像，其中5万张训练集、1万张测试集，包含10个类别：['frog', 'truck', 'deer', 'automobile', 'bird', 'horse', 'ship', 'cat', 'dog', 'airplane']。该比赛的任务是基于训练集构建模型并对测试集图像进行准确分类，以分类准确率为核心评估指标。难点在于图像分辨率低、类别内差异大，需结合特征提取与模型优化提升性能。","metadata":{}},{"cell_type":"markdown","source":"## 1 数据获取与准备","metadata":{}},{"cell_type":"markdown","source":"安装所有必要的库。具体如下：\n- py7zr，用于解压7z格式的文件，因为我是在输入的地方导入的比赛，所以数据格式就是7z。\n![image.png](attachment:a6f50d79-20c0-4849-8649-66a9818576b7.png)\n- torch:张量，用于提供张量计算，自动微分，神经网络层等核心工作。\n- torchvision：常用数据集、图像预处理工具如缩放、裁剪、归一化、加载预训练模型\n- pandas:用于读取、清洗、转换数据等操作\n- matplotlib用于绘图\n- seaborn：同样用于绘图\n- scikit-learn:用于提供数据预处理、模型评估、特征工程等。","metadata":{},"attachments":{"a6f50d79-20c0-4849-8649-66a9818576b7.png":{"image/png":"iVBORw0KGgoAAAANSUhEUgAAAh8AAAELCAYAAAB58uTBAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAFuuSURBVHhe7Z0JgE3VH8d/s5rVOvZ938m+h5QIEQrJFiJbliyJlMo/iSJEFCVR2lNI9n3fxr7vDGMWzAyz+J/vmXvHm/Fmf+/R+H7qmvfOve/ec8+97/6+53d+v/Oc7imEEEIIIcROJJQazsZfQgghhBCbAtFhzcdB8UEIIYQQm5PYwArKKT4IIYQQYlMSEx4mjPkghBBCiE1ISlJYrqPngxBCCCEOheKDEEIIIekmMa8HyhOuo/gghBBCSJqxJi6Sg+KDEEIIITYnKVFC8UEIIYSQNJFaj4cJxQchhBBCUk1iwiMpj4cJxQchhBBCUkVaPR4mFB+EEEIISTHpFR6Ak4wRQgghJEUkJhlSKyXSJT5+++NPOXT4iJQrW0baPN/KKI1l4+bNsn7DJqvrUsLuPXvkkylTxc3NVT795GPJnj27sYYQQgghjsZSLqRDOmjSJT7e+2CC/LtqtTzd5CkZN2a0URrLNwsWytyv51ldZxIRESEf/G+iHDly1Ci5T2RkpAQHB+vXWbNmVSLETb+2ZPiwIVKrZg3jHSGEEELsQUKpkF7x4fCYjzt37hivRGJiYiQoKEiuXb8u0dHRRmksEBs5c+bUS0LhERIaKlcDAiTCYl+EEEIIsS8QHekVHsCh4sP/4CHp3us1/deSHDlyyEcT3pdPJ8cOr9y6dUuGDXlDvpgxTS8lS5bQYqN7ty76ffVqVY1PEkIIIcSemILDFqLDxGHDLkePHZO3x76rRUSBAvllyscTJU+e3BISEqo9H5MmT5HAGzckKChYoqOi4g21wNOBIZosWbKIR6ZMUqN6NXm1RzfJ7OsrmdR7QgghhNgeUyLYUnhgXy7vKoz3qWbd+g1y6vRpCY8Il+MnTsqGTZv14uTspEXE7j17pVixopI9WzZ56+2xcj0wULy9vWXIoAFSvnw5cXJyEg8PD8mSObPkz59fSpcsqfZ3Rg/NdHjpRWnSuJEWGleuBkjAtWvS+vmW8lyzZ6VevbpSIH8+cXV1NWpCCCGEEFsCkWBL0QHM/dlk2OXy5SuybPmKuOXkydPGGpHdu/fIiLfelqDgYClUsKDM/HyqNHyygRYeJoj3OHzkiOxS22LIJUq9P3jokGzbsVMvgTcC9XYnlMDB+0uXL+n3hBBCCLEP9hIewCbDLhXKl5f2bdsYpSJFixaVDRs36WEX4KyERosWzWVgv9fF09NTl4EbN27I2++8K9euXdfvlcZK0bCLSacOL0k7i+MSQgghJP1AGthSfCTcl008H4jdaPJU47ilWNEixhrRcRnvvzdOhg8dEk94JATej+vXA3WKrZe3d7wMFwzLYHgmJCTEamYMIYQQQmyDrYWHNeye7VKzZg15skH9eMMsJshsmTHtMxkxfKj2aiD1tmiRInpSMTPTxVw+UAIGsSPYBtv2fa2XPNf8WWNPhBBCCHkUsSZkHJpqi6GTpX8v0x4MsGbtOmnRuq0MGz5KTp46pctOnzkjvfv2k7Yvdoy3vDnyLbkRFKS3wbYY8pny2bR484YQQgghJO3Y2uuR2L4cKj7gtUBAaqcu3XXqbf369aRxoyfl+ZYtZPDAAXqblzt2kF+WLJb5X30pgwb0kzeHDpYfvl8g3y+YL2XLlNHZMuPHjZX+r/eRAf36MtWWEEII+Y9hM/EBb8bPv/4mLythgWBTa9wOC5OAgGvi4uIsbm7u4ubqKiOGDdXTpPvl9NPbeHl5SU4/P9m7d79Mn/GF7Ny1W3LlzCk5smdXn4lNrUWMSceXXtTDL4QQQgh59EjKg5Jm8YGdxhiBnxg+ef6F9vLZtOly/sIFnS5rgm3MCkCghIeHi5enlw4itQRZLLlz5VLiw1PPHzLn63ni6+urRQeGXcaN/0ALF29vL3F1ffB3XgghhBCSNmCnzcUWJLefNKfaYor0UaPH6DRYgHRapNi+1L6tNG7UUNauWy8TJk7S6xLSqOGTetZTFxcXPcMpBMmRo0dl9969smnzVrl2DSLDW3tEMFSz9K9lcsDfX2fCODs7S4nixaXdC631cZLKoCGEEEJI8thSeAC7iQ94OAYNHqaFQ7NmTfUwSJ7cuY21olNix457Tw4eOmyUxJIta1b9Oy7lypbVHpLho95WQuagXgcBk0vto1WL5kpctNECxCQyKkp27tolPy75Wc+cClHSs0c36dblFauZNIQQQghJHkcLD6xP1yRjmJW0WNGiSQZ93rx5M+7XZ12cnfXkYfBemBw6fFgPp8Brkid3rhQFkGL4Ztv2HVK3Tm3x8fExSgkhhBCSWtCZtyVJyQpzXbrEByGEEEL+29hSfKREeACHptoSQggh5NEAYuBh+R8oPgghhJDHDFN4OEp8JDwOxQchhBBC0k1iQsZaOcUHIYQQ8phgejsSEwqOAMem+CCEEEKIXUhM5FB8EEIIISRdpNSTYm5H8UEIIYQ8BpiGP6VCIb0kdRyKD0IIISSD4yjBkRSWdaD4IIQQQjIw9hYe1vaf3DEpPgghhBDiUCg+CCGEkAxKQg+Evb0gKYXigxBCCCE2IyXDMBQfhBBCCHEoFB+EEEJIBgOeBkcMuaR1nxQfhBBCCLEJKRUjFB+EEEIIsRvWBAnFByGEEJKBsMfwSkpIzXEpPgghhJAMzsMSJInhpCr0aNXIZsSelnl2MWqJr7Tuqf+c1H/mlqoxsNU9tZUTtsbiqha1lZMqw0bYmBBCCHmEsWbW7WXqLfeb2DGslWdgz0e0OmMIiFis6YbYshj1N1pri3tKeNxzilbv1F8ID91eMXa7aIQQQkhGILV2MkOKDzSCFhIWpxdffMDrEQNZoV6rNWabaY+Hk7ioP066DP/Eej3wihqE2JuYGNuK3Vu3bktQULDeb1qIiIhItD6RkZESFRVlvLM94eHhKa476hgWFm7TtiPkv0as7ftvfAdc3lUYr//7qDaHrNAv7zmJk1IQ9+K8HyiPFR16SEWtNyVJjFrlpNbgXYwSGzHqc9gqWokR7CdGrXB2Uv/E/k8eE2D0goNDJCw8TBvCtCx3Iu5Ipkzu6l588M65fOWKvPfBR9qIlypZQj80Fi76Ub5ZsEiqVKkk3t7expZpZ9aXX8tvfy6VmjWqiZenp1GaMqKjo2Xq9Fny51/L1eerqvPIZKyJ5Zff/pQ5X83XdfXx8TFKbcdvf/wlC75fnKK6Hzl6XMartrxw8ZJUfaKyODunrl+Fa73vgL9kyZxF3Nww3Jo+IMqCQ3DvpP6+gOD6aNIUOXXqtFStUtkoJSTt2FuQpGX/GS7m4/7pxMiJk4ckMOSKuLm7yb3Ie+Li4qYWd8mTK59cuXJV/HL6SHhwkFy7fVMyuWYSn7wl5ETILRF3V3HO5KOHXyA8Kvh6SyFvTy08KD4eHwJv3JD3J3ysBUhayZo1i4wdPUJyZM9ulNznj6XLZOnfy2XwwNelXNkycvjIMZn6+Uz1maxSsGB+YyuFuqVfbN9GcuXMaRSknGlKPJw6cybROiQFjOeEjyZLhfJlpesrnYzSWO7evSuffPq5fv3mkIHi7u6uX9uSJT//Jpu2bE227idOnpLpM7+UkNBQJdi8xNXlvnhwVUKi/QutpVbN6lYNvcnBQ4dl5qy56pngJ2/07yvZs2cz1ohs37FLduzabbyzTvFiRaVZ06eNdyLHjp+QKZ9Nl4g7d4yS+CR1X5j3XbEiRWTQgL5GKSHJk5g5t6eZx76T27+19RlKfOhG0K8Q7yHy9rsDZd2mFZIti6eEBUeIn5/6omfykoGvvSFz586WiiX9JLtTjFy4fEWCozJJjRcHydqAEIlUvab9US4ivlklxt1JRpYrK+0L5RNVYhPxcTssTJYtX6kfrDBsqLeHh4dULF/OqpE5dfqMzJ47T0qVKC49e3Q1SkW+mvetHPA/ZLyzDoxGwt7Txk1b5Kdffle9VW8Z0O81yZM7t7EmPrv37JNvv1tkvLuPs4uzFClcSJ5p8pSUKV0yyYd6UkAAzvn6GwkMvCGFChWQfn166XYwQbts3LxVfv39T7lxI0gfJ1/ePNLhxbZSsUL5NB83pZhGoFqVJ+S55k2N0liuXbuu6ieSK5efUfIgfy/7R3bt2WvVyNy+fVv+N+lT8ciUSYYPHSRBwcEyaco0fU5FixSO3Ujt//iJk7q9Rwx9Q/LksX6dkiI94uOA/0GZNWee9O/bS4sjS86eOy8fT54qjZ6sLy+2a2OUWgei6vr168a7+Hh5eWlxk9CrAlIiPkzh4ZvZV5o0biguFh6Pvfv9Ze/effo70FDVMzmOHjsun8+cLd6qTkPe6B/3vUA9lq1YKT7e3g94VPDEuX07TCqp+9FSKJjio33b1lIlwfcv4X2B78GfSoQ2bthAShQvRvFB0kxi5tyeZl7b3WT2b219xhp2UQ+C2PwVLPfkx0XfSkToRfG5d0f8d58X50xREhEVJW2bt5fAS1ckt3uUuEbckSK580idJ0rInRzF5fsjJ2Xg042let7sUtTVWT1EjkjdksWlbObMOoIkveZOP7Q/mSp79+3XrllfXx/xVAY3Kjpazp0/L+s2bJRsqudbqFBB4xPK0KkH99q1GyRrliy6B2cCEXHm3DlxdXVJ1M1cuVJFyasMtglc6XiYnr9wQUJv3tTrihUtYqyNz5mz5/TDX1lEdYz7vcnwiAj1+Yuyecs2/eAtV7Z0qtzcuBG3bd8pU2fMkoBr13TvEEaoXp1a4ubmFrfNz7/+IT/+9It2icP4ZcuWVR93m+qJomda2KKN7AHc4+s3bNLtU71aFe36x3JP1eereQvkzJmz2qjBKJnrLBcYILRzwwb19HtLNqhrt3XbDunwUlttgGZ8MUe3A4TIU42elJrVq6l7w1ed6w5lwNpI+XLxjb/JHfUZiEQI1HPq3kq4HDx8RG6q64w6wshZ2wbGFu1vAlEL0blF1Q/733/goKxYuUqW//OvEls59T2zfsNmVe6vxRGGRxIukZFRcXVetHiJ/L38H9mzd/8DCwROeSXuLT0NJodU3RNrP9wf69S1+VKJ8hx+OWTwgNelbJnS+p7Agmv317Ll+v5/oXWrFN2ffjlyaA8GBG9o6E2pXLmSHm5FPXCfvjNmpLRt00qaPft03ALx5X/wkP4OW343ISC2bN0uNdR1xJBaUvfFJdX5+UXd6yVV56Jggfxx9x2eA5b7JCQjkcE8H/B4xD5k7km0DBzaVc6d3CoBBy9Lziw+4lY8k9x1yirjhk6Q7etWSanCWWT3xm0SHuks+QsVl5xPd5DhG9bJyxVyiWfUbfHIUkR+C4iWftVrSHvVG02v+ICIgPDAg+nppxrph6KnZ2xPHwZ2s3pYfbdwse7pDurfV8qULqXXmb0oGGDLnhB6tXgwDh08QD/gUsJpZTAnTZkqxYsWlasB1ySz6jHC4FnreeLhCY9Li+bPPtC7PXnqtMyYNUc/pF9/radUq/qEsSZ5vv9hiaz8d40WE3CJ//jzr/pBO2LYG6o9Yo0MDN70L77Uxm7ooP5xxunipcsyZep0bRSHDR5430tgB8weaL06tePOPzw8QrcJxCOMtimWTODmb6uua10lpBLruZtj+hAXr3bvoo092hNeH3hCTBBrEhNzTw8lxIpqkUwemaRPrx5xgtGsY3qGhrC/OrVrGu9i7ysIz+LFixolIpF3I+XQkaPSo2tnqVSxvPbaIBg1Yftbbme5T2uY7TPyzcFWh23gIdixa4/6LvTR94qvj4/eDkJg/rffy2F1nLQ8vhATkpRHAaI8kzqO+Z1IygMDoQAPEO5fa56Pbl1efqAdEu4v4bb0fJC0kNR3wZ5mHvtObv/W1qe8u/qfQJ2OfkbHZrKUaZBP8jfyk/yNfcWrSqRkqhApvlVdxMnjpojnRTl4eovc9LglPgWjxClLsNy7c11y3bogJ89ckA3nb0nAjQBxC7osLhEYt0Xjpf0CovF/Vz1CCBCMDXfq0D5OeAD0zOrXrS2dX+4gd9UD/O/lK7WXwtZgmAbBbo1V77pEiWKq13VZ97xSC3qI6JGjjnAhpwYMs1RRvcr33x0jpUuXNErvg7aCByg6Kkpat3ouXq84f7688lyzptrjAs+LI4FRmvzZ59roQZB1fKmdtHvheb2gnhAGqHuxYtY9SQDrEecB4VdEGe4ff/pVAiAClRApU6pk3P6ebdpEe5twT0CgoeyJyhUlRIkMeMwSgvrMn/vFAwsMLeILJn/84QPrRo8cFk/sWIJhtf59e8ctXV7pKF5escJw+87d2ouC4S/LbbC8pMoQSJkcaIeLFy9po41hmWEj3n5gWbVmnRK3ofLB/ybp97t2x95nuH9v3rolVas8ocRzZhnz1nB9fuYy8cP39P0Jz8ukj96PK/9w/Dvas5AcuBbWxDghxHZkMPEBlEDQKbMiWX2zSa48+aR0zcpSsnpNKV+qmlQqWk71anyUYXNRvUpvyZkrn2TyzSkuXtkll0u0tFFipIl7sHTOESkVQy5Ku5hrUkqC0t1QMLhwgef085OnmzRKNF6h6hOVpITqcd4ICtI9IFuCXtqevfuUMc+uDF8hqVC+nO7Jp9WI++XIrnuI168H6gDElNLyuWY61gSeA2sgcPD8+YuSO09uZZBjvT+WwL2OeBWM9+OcHMG+/f7yzrsfSkhIqHbrb966TXLmzCkN6teVykoU7Ny9RxtFeIESi6EBEH8YcgFou9dfe1XeHfuW5FBtmT9/Pr0/LDgGAifhQTHLSitx8rBBEOryFSu1OEGcQ0JuKVEA70dC4NmD4DBBCnCAEuI4bwgsS/FgLojhsBQXpnetYMEC8v64t6W6eu/s7KS9IvAgYMmeLZtuX4ijNs+31N+3++uyxhs+NPlr2T8yeNiouAWeNXh1bAG8ZN17vR5v+WvZCmMtIY8vNhcfeMAEKcOJ3lxyS2BgoE1797Hhpsqo33PVJ/ZEzurSomALaVqqo1TK002KuLWTom4tVO+5nrRs8z9p3XWqvNDxY2nWdpw0eGaolCvXUNq+NFqebNJPatTuKNUadJJnmnaVQgWtj7enhnPnL0ioMlwlSxbXD8jEQHrlqOFD5YN3x6QpuyEpMOSCYQt4PNDjRAAresWHjx7TAZCpBUMCaHHEE6Qm2wGu+qTG4HFvoMebJ1cuHROTkGyqzhifR5AmUhkdQe5cOaVOnZry7jtv6eGhWjWq6+EruMdHjh4noTdvqes2RAcMJsaVq1flm+++13EEphFED9v0KEAYYigLy9fffCdR0VE6zsIs+3f1Wr3dwwTiC8NDz7d8Tl9Da16YhOC8R48dL98vXhK3PcQ19oXhI9w7pkCwXHAcS3Fh7R7D3B4Lvlsc10bIwEGbYUjrn39Xx5VjmTvvW+15TAoMdd1U19JSKKUFBAcj0LVn9y5Wl5c7vKi/N4Q8rthcfKBHj15+x06dpVbtuokuTZ9trnqTB8TFBTkktgKzdcQ+NOBYyOnjKsXzZhckze7eek6uXfSQvQcClaFwkXMBMRJ8002uhWSS85dEzly4JVdCImXVscuy8shlWX38sqw9cUHWHD8pV9VDMr2EhYVJtOr9weg/LBDgh6EMZL/gOqHXibiSy0qQHD12wtgqZcCIrFqzVsdeYPjABJ4IeGwSLqmZ6Ar7RFvlzp3LKIkP4kLQjhHhEfpecwQwJh3at9XeGgRTYigIogGxGpino7DqjTslE9R48eJl7SHq0rmj9tyY4DuAjA94PuA1QADjLWUA4emw/H7gmNWqVdHDAvbmbuRdK9fvnvbIjBszSovCocNHy6IffnrAUCMOxvL8IKJxz2EY5duFi/W9A68V7kWINXwe7XhAnXdqyJnTTypXqqBT6VE/DMsgYwUZWPFSlQ0gluA1K1UqfnxUi+ZN5bPJH+mlQrlyRml8cO44hmWbYLmhyqwJMFwj02uF+xjB0/hrerEwdwmHdoitSE4s2zszMDmsHd/m4gOUUD3qL2d/IUWL3g9YsyRLliwyZcon8lTjRkaJLcFFuCcx91xkw+5o+WGlu4TcKSotn2suTZ9pIMUqVJc792IkKOCcRAQelvDr+yXkzE6JCb4ggaEhsuPkZdl57prsOR0gOy9dl+2XAuTarbtqj2i8tF/Ay1eu6r8FlIGxJciQmDBx8gOuXSwIbDOBuxxDBxhywXg4wA1hpuFiHoPEbmCMzW/YuDluQZru2++Mlx07d2u3dp3aNYwtRZb+veKBsXss7334kfZUpAS45GHQU5KhkBwwDOYQVsIl5UNF97TIQY96tDrv/m8M08YEgaiffzpJer/aTXuVRr09TgYNHanTh7XQS+DVg8AY+eYQKVMm/lASevTtXmit4zbQM79z567uuSOmxjKeAqIF6ZiJiTJb4n/wcLzrh7gLeKMArgu8V21at5T16n5AdogJrh1EpqVhxfYI2EWsDjK04NWBUMiuxG/ePHn0fbdq9VqZp8oxXJJSIFzQRhBoCJBFnXIogXbhwiVpop4tlm2HNO66tWvpbCmIx+Qe1gmxjD2xXPA9wD4twb7h1THvM2QUIUMMwdlpu/8IyXjYLdUWRq5evbqyceMmCbYwOvYWHmaqLR4tJy56iP/xIMmWRT3Ms3rKzTtOcujERSmd30NCr52UbJ7h4hx9U7yc7yoVdkecc+SWtUfOS2jwLXFWRsvV3Vkio+5I5cKFpLBfjnTJD7iekZqYP18+3ftKDXhQIfMEPT3L1Dukq2KIAt4LPHgRjGm5lCtbRvemAXqVGzZskooVy+sxdlOJIpMChgDzaFSvXiUu2wRcUA9VrLtyNUAbU3NBdD5iRSqUKysD+/fRQyAmevhG7TphXQoXLKjn5kjoOjfTCpGqWE/VC71mzKGxU4mhYkokwWBbA+cOQ43sAHhwEgM97A8mTNKpnv+sXB1vyZc3b5IBiGbdYCBhZDdt3iKFChbQhhQCG+mpyMZBiiTiE1AXtKq//0HdjmhiGCYzpRJDLcguMveLfZVWvXDEAs1VguX3P5aq65VfTzqGQOA1a9dLjWpVdJusXbdBzza6Y8cuKVu2dJwHzdwXhjCPqF4/BKHlcvr0WT2vzOVLV3RgsOU6ZBRdu3ZNB25atgPaFm06cvgQefaZJnpB6i/mzcBcNOa2qP9J1b4QrvBAYJZTiADcIwmvC+433PcQg6vVeeEa165ZQ6cwY10+dY9A2IYokVzliVjPXGKptrhXIYAxhLX0r+U6dqZXj246DRaf3bZjp2zYtFmLbD/1vYVwQHYM5ospWKCA6og8m+iQB84d8R7mvQjgxSletIgO3k1swb2dUx0L4POTP5suPyz5Rd9n+9X9ACG6fecuq/ef+f1GQDHem9cU15iptiSjYtd5PhIKEPsKD4AfiFPCwzCs+w+clyJFvOWee4xcOnlaDq1aIzu3bpTSpXPLqWNb1MPRRzx8Vc/LzVVc3L0kxiO7rPA/IUHhtyU0LEScMrlIZPQdqVq0sBTLlStd4gNBmQhKxAM5NWmpICnxgQfxq91fkebPPqMNhOViCg/0xOCRQNxJkNp+pWoH9OKxrF23UW7euql7rAiUtEydNMUHDCsCRGGEMAeF3k9QkLRs0eyBbBUcM2E9sODBam3M3pr4QCYDZpWEwbH28MXDHQYMQwOoU2KBqwAelJtKEOXJnesBQVSpYgU9lJEYZt2KFC6ssz1at2qhXel+Ofy0kUWgLozgv6vX6eybMkpIwPg906SxFn6HDh+1ajwtxUfhwgVl0eKfdFxDv7699PnA6K5Zt16Cg4LlasB1+XPp3/pYT6nrMLBfH30fmJj7uqbur0uXLj+woMcNwwcBmXAd7kncG9WrVnlAfMCbAC8L6o0Fw5kQBxCc5rbwaBRWwhzHx/4xGRv2u9/fX3uFErYtBAVSwjE3CI4N0Yx2QjnSaHEv4zuCMhjexMQH7pHde/dJabWvPr176OyxzL4+elhn1pyv9H2MWCYIRtyr879dqOsHD9VL7V9IMtbCmvjA/Drm/CGJLabwABCZ+j54urG+ntFR0fqeHj70DXm+ZXMpVaKEnsrdFBsUH+S/DL6/qfUkArsMu1hiDsFUqlTJzsIDOIsT5kPXv8wicuvmZdm9cYNs+3OpXNq+XG5cOynBIeclc7ZcEnTTRY6duiY7d+6TDet3yvrNB+RWcKhkdomSPNm8tDBxiror9+7cSbPgsARj0Oj1wmghoC0xEO+w5OdftQsawwW2AF4XPMjxUMQ8FAnxyOShbyBrQwUAgX9mECAe7HigIr4Bsz6arnhbgoBcTy9P/VBGQGFCIJTQNgjORd2TArEaMDqWLnhzSSo4NCFISYWxRc8dk7phnpZ33h4ps6Z/Kp1eaqdjDQKV8UwtiA3APBd9e7+qh1vg2sdw2YDX+0hT1c6YhAtBmZmzZNaiwzI92xJbp9qmFIi4Z5Xxj4i4o+Oa8NsqGHKBmLIGUowxjAcQEGo5ZANhhyGbnbv2GCXWwX2MoE3MkwPRB68Rhrv+XbVWXevuOiC4Tq2aepgDadFt2zyvvSOY4C+tQFRh8jScnwnuT3jUEgax4ruURV0vfF9wL2PIDn/RVijTv+mitmPMB3mcsbv4ABAgv/z8o52FBySH+ko7x0ZnAE/XQHF3DhQPXxfxLlZKitRQvZHmjaVQibJSp/3rkq9eByn6ZCep8FxPqfpcJ8mVPauUjgqVZtlcpFOJ3NKuaB6p6hotfvfuqn3GxpKkFQTdwT1/+fIV3SNLDAR+rlQPUWSlJNWjTw340S08iJ+sX1emTp4YF1xnLv/7YJx2eyPoz/LhmhhwsZdVvVbMD7Jh033jYStguOCSRpzMmbNnjdL7HDl6TGcOId3TWjaMvUCP9H8fT5FXX+sv/QcN05kuMEioLyZqs/QapRSM+8OrgdgB7BtDExPef0f1ul318AEMWb26dbR3YrUx74Ul6B2Pe3uUHkpILRA1E9S1T60nLiHNn31a/74L0mIhHiDSUO+EoHe0TBlrxCm90uklbaAxFGLGecA4Y/gE3oHkWLt+o/QbNFTGjZ+gBQ08QhMnvKeDSSFGFi5eovdvenYaNawv69RnIBJTAs4DQyOYORbs3L1XCw3L9ofgWr7iXz28kljWHuKc8F3Gd9/0/EE8AzMoFwIUgcSWHi1CUgLu75R6Hax9Jx1JwuM7RHwA22a1WMcZv+mCgRd1LZyUFIm+dkpWb/pBNl5YKafOb5Flv8+UI3tXyx03N7l4V+RKjLsEuGeVq26ZJcTdUxu7b2dMlW0LpsuOLydI9PkdUiinu0RGpT/nH+eP3iliLH7/c6merhoPOEtg/OcvWKjL4WK3Rc8ID0X0JGNdwZWM0vjAg1C5YgUdr7Fvv79RmjjYV/Nmz2i3NGIRbOWhMUFb1alVQ2dDILDTMg0YXpxf/1gqLqoOiF1xJIjjwKyU0z/7RMe6QChgplOkd/bpP1jeHDXG6nVNCgwLofePmIEpkyZIqxbN9K/IYtZMTDqGewBDXI0aNtAes9///DvewwaGHuLHMlYnpeA6okdubTgsNViKjatXAySLEiEYRksIznPr9p1Ss3pVafJUIy00wm6H6V/yhUcJ9UAAdErue1z7Nwa8LjOmTpa3R70phQrml28WfC9D3hylPRxvjRiqJ2cLDwvX9ye8dfCC4Vd+EQeUFPDgTJk6QxYv+VlnHkFwbNuungUFC8QFawOIJWSuIHbm4KEjRml88H3C5HSW0+MnFCqmdy41njhC/us4THw4gthHsjolPcW6k7j53JMatUpJjuxu4pnFSWo+20DuuUSLp3O0OF08LHJsh+TKek7uZdkut9z9xcUtRlqVqyClAqOkyF1vcQ1XPaCoaIkIi4gVNCkTmImCnnGXlzuKs5OzTlEc/OZbeiprzEHw1pj3tMsd02Q/pww7jK8twJj36TNndIaEOSW3NfRvl3h5CuaaQA8/OZAGimm24XJetXqdUWo7aqvzr6p65BBkY9/7UBndv+SHn36RjyZ9qmMDmj79VKoDd20FeqyIV8CPhn3w3lj5cuZU+VD9RQAlPBEwxikFwg/DBNjX3r375a2x7+kJzJCd8UKbVsqox25nDm9A7EGQpUbgpAX/Q4fiTbz13vsfae9ZUmCIDEMRaIOEYgjeqkU/LFHfxWw6swdiBSmxECH169XRQig1YHsY60OHD8uHH32iv0vHjp+UAa+/pq8JvGKWRh7t3LtnNyVaXeSTKdN0zFDCNsT2d+7e1Z4UePVe69ldicHm2ruHMvyOS0Jh9GSDejpeZeW/qx9IuUVqLiZkK1qkUDzRgk6Oh6dHvIBcQv7LpMWrkqHEh+ozqyVaaY/Yh0qw6ime3nFEPE+HSdDxYLl18pqUyOorp/btlu3/LpN9OzfK77u+k8mrJ8uiDX9JZGiQlIu5KwVvxciFPUdl/a9/y/dffyc3riY9MVFqQADZ+Hff1lkDmA0SgXPIPECPHr/YikwHjGXbykWGGTVhFDBMggdwYuDYGOqAixhpo8lhenJgiBGIiM/ZEhiXXj266gc+jN6vvy/VvwSMLBt4B9o838JmbZQclpN/WVu++PIr7Y25GhCgMz/McnwuOWCsFyxcrIznKJ29gXTQ/70/Tg9tIT4Is6aa7vlnn3lK6ilDDe/KJotYCXuQV90L5lTvWJDhg99rwfCBScI05i3btsdNYmcJhv3gcQA9ur0SFw+C64f9JhZUmZgIxs8PoH37Dhii2v5rvR94PzBchSBivIe3AQIDKcvIeAGIV8JEcEjxRZvDi2QpQPDbShiWwRDkuLdHagEMEbLin3+V0CkqNapXNba8Dyafg7fm6PETOgvMBMdHHRFoiplWTdGCNsNQDjKl3N3S53Ei5L+G5TM7Y/2wXIzhntD/R8nmJRPl4vGtEhhyWw6eDNa9nnJlCkv9l0fJ1JlfyqHd/uKdPYtcv31dvD2yy4CXOsm6aRNFQiIkzMtdrkWKnHdyljHjx6uH5Ev6GLY0eHgQ4SGFS2D+aBZ5ELOdkEaNcfzUeBbSAwwq4jowtp+WwEzENnh4ZHrgB8nM/SIjBF6uL7+aL0gBxVAWRN38bxbqDKQTJ0/reWEQT2F6EnCvYGgDnqyU3Itp+Ul9fAYk96NmEAcYHrIUq5j6fLgR/wHgpcI2t27f0nEZSU0Rj3NDWyDzwwTZMTj/hN8N7Pf4yZNSqUKFuCBcDIHNmDVXD2UBd3c36dq5k/asWIKgbojyihXKxYkCHBuZXXvUPl7p1EHvE8Ncn30+Uy5fuSJDBvWPJ7wsMX+ssVrVKjoQFunVU6d/oYdrzONDlM6a/ZWePA9guAbbmmAo6Mu583UMEGa2RSAt5kUxf9CQEGuk1nyndvvUkFJvrFmHjCU+9D9oAPVQvhcle/+ZJa7h5yRa7qjiTBIQGCHZcuQWl2KNZLrqrRYomE9Wr1grly6cFw93Lxk2bKjMnvqJhN++KUWKFhQXz7wScPWWDB39urR/oT32jj2TxwQEBv786+96eAczUqYWpAsj2wLDDKb3AiS3X/TA4TXB/ClIZ05P4DHSqSFWEtbBVuBc7tzFDy+KHk60Jg6RKYIsq5T8XAC8JJg5F2CYEhkwSHVNCRAViLHAX3jOEGdhiqC0gmBYBI0mNcSH4RoIDsSE4NzxSEXaL4aFMPwDIJ79leDBtmgjpBNbDjWhzn8s/VsCrl0XF2cXXXfOgkqSI7Xm257m/vEWH5anokRI1N1bOggVP68fO+26eig6uco9p0zqIaAaSikJ5ODDjYuPwhUaHR2pGjFKr3Nzc1fr7+nASncjpZPigxBCyKNAas23Pc099p3S/WO7DCU+TMxTuncvNuDMyQxtgUNEr4KEwAsoNctemiktVMPE21Zt5WyuI4QQQh4+qTXf9jT32HdK94/tMqT4AOZp4d9Y2YBXRtk9Jy0u4tagIZIZP7dlrAchhBCSXlJrvu1t7lOThZdxxYdaIBcMuaH/fVA+JCzBdtaaA9tRfBBCCHl0eNTEB/af0mNkWPFBCCGEZGRSa77tbe6x/5QewzE5i4QQQgjJ8KQ0RIHigxBCCCHpJjWxkRQfhBBCyGOAoxInUnIcig9CCCGEOBSKD0IIIYTYBNPrkZz3g+KDEEIIIQ6F4oMQQgh5THBU3AdI6lgUH4QQQgixGZaiIzEBQvFBCCGEEIdC8UEIIYQ8Rjhy6AVYOx7FByGEEEJsSkLBkfA9xQchhBBC7I6lAKH4IIQQQh4zHDH0Yu0YZhnFByGEEELsQmIih+KDEEIIIQ4DgoTigxBCCHkMccTQC7A6/HJPYbwmqQRNt2bNWpk4cZJE3IkwStOOi4urvPxyR+nerau4uroapYQQQsiD2MJ8O1ICWB6L4iMdHDt2TF7u3FUCAwONkvTj4uIin075RFq1ammUEJI0MTExcubsOfHLkV0yZ85slNqH0NBQuR54Q4oULiTOznScEvIwsZX5fhgCxOVdhX5FUs3evfvkhx9+lFIlS8qYMaOl9fOtpGChgrJz5654ZW7u7nLkyFGpXbuWjH5rlLRo8ZzVssAbN+T8+fNSuXJlqV69mnEU8teyf2TmrDmSK1dOyZs3j1GaenDTH/A/KDPUvhYuXiK//PanLP17hezctUdyKMON/afHDWmreqaWtes3yuczZsuu3XulRo2q4uHhYayxLbdv35aJk6fKn38tk0yZMknJEsWNNYQQkjrYdbEBfn5+0rTpM9K8eTOpXi1WNFiWlSldWpflz59fv0+sDK/Jg4SFhUlwSIjcuXPHKEk9EB4///qHfDptply6fEXy5M4l1apWkRzZs8mFi5d0Odanpwdgi3qmhbx5counp4dkzZpFMilRay8gOHAsd3c3yZY1q1FKCPmv46jYD2Aei+KDPBYcPnJU/vl3teRWouODd8fIB++NlYH9XpOJE8bL++p9diVCsB7b/dcoU7qUzJw2RUaPHKZEiKdRansQh9S/b2/5cuY0qVO7plFKCMkIOFqAUHxkYMLDw/VQDpa7d+8apQ+C3n5ISKje7kZQkERFRRlr7oO4gqCgYL0dtsf+jhw9JqfPnI23PV7Dk7B77z69bUIS7sd8j2OjvmnBPM/E6g4OHjqi69yy+bOSR/XeLcmfL68816ypXo/tTG7dup3oPs1jJtWu5jY4P5xnQrBf7B/HAdgew0KXLl2Otz2OgXbeu/+A1TZKuJ+EmPVIrr6W90FidU6qTUBKjpVwH3iP7c17Ii2k9BwB1pvbJmxPsy2TqotZ3+SOQwhJHAacpoNVq1ZLr959pG6dOjJnzizx8vKyWjZ79hz5aOLH0q5dW/lk0kT9WWtlbw4fKT///IuMGjlC+vTprcvSwo0bQTJ33re6F29eXvRan36qkbRv2zoukwbr9u47IAu+X6w/YwK3+rPPNJHWrVrEbYuH7fsTPtbuduxn0Y8/xRm73LlyypA3+ktwcKh8+dW8uH0hIPHJ+nXllZc7WN1PhxfbylfzFsi169f1OmxfoXw56fVqV8ns66vLwJKff5O/lq2QPr16xOtx4zjzvvlODh4+EmcocZz6dWtLpw7t9TCBybffLZLVa9dLrx5dpX69OkZp0kybPksOqX0PHTxASpUsYZTGYq1OZllPdYyLSoDBk2LWy8fHW17p1EFq1awuZg/j2PETMuWz6VK2bGkpX7aM/Pjzr8qgRep1iKcY1L+vEkOH5RtVd9NI4tq0avGctHzu2Qf2U07tY9CAvroMpPQ+APv2+6vjfB/vPkDwao+unaXKE5WMksTbJPTmTZn79bfif/BQ3DnjetauVUO6vNxRDwuZmPt4rXcPWbtugxJch+LqV7BgAXXefSSnn59+nxypOW54eIS+17du2xFv26pVKkvP7l31tohr+d+kT+Wm2u+o4UMkb574sTsYVpvw0WS5c/eO1fWEOBJ7mG977NMa9HxkMGCkEFAJg1RHPYDHjRklw4cOkmJFi8iKlavUstrYUmSLeghj21u3bslTjZ7Uhqtzp5e0MEAg5roNm4wt73P9eqD8+sdSeaZJY719jWpVlXgI1AZgztfzpXjRotK396va+CMGAfvYsGmL8en7BAUHK6EyXwd6dlcGrkvnjlK0SGHd85/6+RcP9EgTAiOBukN4VK5UIW4fEEIIwITRteydw8C7KEPz94qVcuXKVaPUPqxes07Wb9ykBEIzZdS6SLOmT0t0dIyuE9o8IadPn5XVygi/2O4Feb1PTz2McuLkKSXMvpWFi3/U17Ffn17yfMvndDo2Aj6TGx5KzX1w/MRJmT33a4mMjJSX2r8gE8a/I926vCzOStx8Nf9b7XVJClwLXDNcO4hHy+u5Zet2+eTTaQ9cz2hl/Bf9ECtgIU6xPTxQ589f0OedkriZ1Bw3Ojpa5i9YqMuxLdoCbQJhhUDdhYt+0A9db29vKavaH56PPXv3689agrZCdhuEXp7c8T1ohDgadEAcOVxiSyg+Mhhnz13Qwx41qleV3j276wdx+XJlZcDrvVUP7wnV23XRD2L0/CLv3tUBhEPfGCBdX+kkVZ+orEVF39d66l7gtu07H3At31Hvu3bupL0i2L5f3166Nw9jiZ4w3qPXCc8JvAxubm6yb9+BB9R0cHCIFC9WVN4cMlAaPVlfmjRuKG+NGKp7oadOn5Gt6thJ8ffylXo7DJfAQ2DuY8xbw3U90JOH4TWpUrmSVFdtcvnyFRn9zni9LP/nX+1itzVXAwJ0ndq2aSUN6teVji+10+9dXVxUvf/RRtMSXAvEUqDta9WoLkMG9dNtg2GWZ5o8pa9NzRrV9P4gDiAS4C1IipTeB2D3nn1yJ+KOPg7aM58SAY0bNpBer3aTEsWLS4RalxS4FidPnZbmzz6j6255PatXq2L1euIcChUsoONUsC0WvC5cuJCq+3k5f+GisWXiJHVc3EcQTbvUuQFcZwwTol37q3sUbYE2eV3d6/CE+fr6xt3rdevUEi8vz9h2sRBBuIdRBiC6/6sPfUIeBSg+Mii3bt6KMy4ALnQEWEIUYC4RuJsbqoc1gi3R07Ykc2ZfcXd31wbCch/Azy+Hfmib4AGcPVs2/bpShfLxHsgI7sRDPCQ0VBmw+JOwoRyGztL1j9eoHzI2kP6a8NgmN9W57dm3X6ezYnvLYyLg8pmnG0t0VJTehwn2DY8MjBQEF0TI4h9/lqHDR8vQEaO1h8Z0xaeXypUqSulSJY13sZQpXVLKly8rV68GaGNsCYYakH1jguEiDDt5qL/4nCXwDqB9sJ+UkNx9YBKjDGto6E3jXSwVVH0HD3xdypaJf39YYl6LLFkyK6FVJ961QJvjGkPIwuNgKWThhapXp3a86w+vQ7kypQXDI9euxQ7FJUZyx8Vw2Iypn+ghOEvgCbEUFNgW3ikMAZrDdGjjAvnzy6XLl3VmlElg4A0tYPKq9aVLxR+GIySjYPldsicUHxmMwoUKqAdnPjlw8JCMGP2OjnXAAzOp4Di4vtHL/vOv5dpV/8mUz7Xb2RowfOg1p5esWbOKX44cxrv7YI4MGEgMy4SFWR96uXnrlh6Th1dlrzJAGzZujrecOXNOnJVhhXfFEnypKlWsIB+Of0emfzZJG6iKSjDB6CJ25H8fT3nAK5EWShQv9sAXGO/R64agC0hgWNGmlkLAFqTmPoCXAAJh4aIf5b0PPtLemYRBr4lhXovcuXJJjuzZjdL7oBzxG9cDA+WWRdvi2iEOJiEQxSkhueNCPFtm/kAgQ2RfVOcFr9ecr7/RXgxrw3v4LOJ4IIIsBSzaEvdUzerVtFAiJKPiCAFC8ZHBwAMXwYDo8SGWA0GWH036VPoOGCIzvpijA/RMEPsw7v3/ycAhw+UzPc/F79qd76LEBYyDPUlMxKDc29tLz5lxN9K6YMJ5Rd6NlLNnz8lX8xc8sCDoE0Y+KWA8YGCGqbaaMmmClC9XRo/nr1qz3tgi7Xglku5qzo1hGdRpL1JzHyC4dcSwwdoDgyGPH3/6VRvo/m+8KctWrExShJjXwke1J4x2QvAMg6DAPmwZyJbccRMCcfdqt1ekVYtmui6bNm+VaTNmSb9Bw2TCxMkPxAEhjgjieN8Bfy1I4T1CDAhEWsUK5YytCHk0cJS3wpZQfGRAvL289Hj97BlTZfLED41AzFyyY9duPQMn3M7o8c2eO08H+CGeAEGGX385Q2ZN/1QPTWBYxJ7cvh2m40cSEh4RoYdpkCXk7mbdqPj4+Iibu5s8UamizJszU+bP/cLqYpn5kRQY4kAGCdzup06dTreRhNfGGpcNA4c5RRxBSu4DE0yX/t7Yt+TLmVN1ICaCZAGEyLIV/+rX1jCvBbwa1rwqaEoYewgQWz4gkzuuNXB9273QWs+JMnXyRO35QgAuROen02bo9GITCMUSJYpJQMA1LcgQP4P4EsTAIFaFkIyOvQUNxUcGBMYTD3zcPMgmQRCemRYIA4heL1z/WMqWKS2v9eyugwxNl7ete6nWwG+E4MGeEPRAMQyEh39iAshXGR4ECMKFnpihtwTpkRhOGPvehzrVN6UgZgUeFDOl2JKkjgsjlbD90HPG76/Ao5QrZ8rSSNNLSu4DE/OaIwYCMT0Iku3Tq7t+f/z4iUTvB/NaIMjWWtuiHKnU8CLAS2ErkjsuvEu4lyzrbbYHQKwIPF8ITsVwTHBIqNrX/fsRbdbASMnG0AtSnuGNQ3aMrYfICHlUsacAofjIYPz2x1LtWkfsgzXw4MQCI+js7KR7v5YpqXhAw72cMPjQ1kSo4yLl0/LYeL1s+Upt8JElkdhD3tfXR2evII5g9Zr1DxgY9Nb7DRoqG40UX9NQXbhwUVatXhdngEwwto/MF7RFsWJF475wCK5FSuimLVvj1fPc+Qvin0S2ycGDh+XI0ePGu1gweRliLiBo0Nu2Nym9D+AB+2jSFBk+amyiQayubq6JPoTMa4EYoQ0bt8S7FmgzxI+gfWHoUzI8Yg19X6xYqVO2zWuX3HExD81bY9/TadcAmVt9+g+Wn375Pd62JvguJBwGLFyooI5BOn7ylE7HhYDCcAwhjxP2EiD8Ybl0cPr0afnjz6U6NuH06TOyZu062bhxk45FsCzbsmWLerBf1Q/6Y8eOKwO4xmrZ9u07JFj1qOvXr5fmH5aDa3nbjp06EBNuZIgIzAmBYMLLV65ItSpP6FRCuOSPHjuhDeLxE6ckyuiZ/7H0b/Wg3qVjL+B5qFe3thYqqOf6DZvE08MjrswEE0bBdV29ahUpWOD+79NY+4xZliVzZj30gnkvcGykRSLo88Sp0zowEyml5jGs7b9I4YLawG/fsUvHqcDgQBSg/hjPh9jA+D7c8/DoFFCfw4Rq+/0PaiMGbwjmLFm5ao2eeAqfxXE7d3wxzkjCwO3de0B7MvbtP6C9NWvWbpBff18q2bJl1e8t62TWs1y5MlpYhYSG6ADFdcoA/vTrb8pw3lPn1VYfB6DHjiyQnDn9dLqyJTCW8BjAaMNrYWLtM9bKUnofoI1D1Lpde/bqWWljomO0xwD7+/m3P7XH5oXWrSSf8UN51uqFa3FU3cM7du6Ouxa4nt8vXqI9Bkh9frFt67jrmdi5AWvXGp6HbxZ8r1O2CxUsGPejfYkdF/OHQOyVK1s6bjI13O+7du+R/QcO6gngIDSRHo5tcTxcE2QAYVsT3Afwem1X7Yj7pVaNajqNnJDHDXsIEIqPdIDgs3Xr1+u4iUPqIevv76+FB0CQmlkGkQEgLPA+sTK8xi+S9uzZQwoVKqTXpxYMV+TPl08JmhP6wQ8vhr/qicOb0KJ5U+nwYjvd44VBRjoo5lOAAIGRgnHG+Pmr3brocW5gL/GBOIse3V7R8zCgd75fB/aFSbWqT8iA11+Ll6lgbf8wDPCOBAUF6fODOED9MZSD2UIx3wjiG0wgdqpUqaQNK875+PGTentcO4gCGJU+vXvEy2KAwYKX4vDhozrlEu0EQfFSuzY6UyNhncx6YjKwvHlzy8p/V2vDCYPo7e2jh7dqVq+qtwX2FB8pvQ9AsaJInb6nxMkxHWCJbWGYsY/er3aLN8OptXpZuxa4nshIqau2e61Xj3jXM7XiI0zdMzuVcMC1af7s01pQgkSPezP2uL16dIv7hV98VzG8giGkI0qwQGhZbttbfees/RowRCzEB7wlmGcFbUzIo4g9h0iArffP6dXTydlz52TRosU2GabAJFQtW7aQGjXuT8GdHtBrwzTQzk7OeozbjOlISEq3swUwlOb06iOGvaEf+Gi7qOgoPTxieh1SA3q8iF/ArZySfUBgwTCClJ4zRBOmPsccKCm9NuZxXF1cU/U5W5PS64shDQxjxNyLkUzumbTASi3mOeNcITAtPQnpAaIJ+0xsf6k5Lq4lBI2t60jIw8YR5txWx6D4IA4lofiw7BETQghJO44y57Y4DgNOCSGEEJJibOHFpfgghBBCMgAQBbYQBo6Awy7EoZhxBYg7eJhxEIQQklH5Lwy/UHwQQgghGQhHmvW0HovDLoQQQghxKBQfhBBCCEkTaR06p/gghBBCMhCOjqVLS6ArxQchhBBC0k1qBAjFByGEEEJsQkoFCMUHIYQQksFIy1CIrUjJcSk+CCGEEGJTkhMgFB+EEEIIsTlJeV8oPgghhJAMysMaerHEWh0oPgghhJAMzKMoQCg+CCGEEGJ3IEBMEULxQQghhGRwHgXvhwnqQvFBCCGEPAY8SgKE4oMQQgghDoXigxBCCHlMsIy7eJhQfBBCCCHEoTjdUxivSRpA861Zs1beHjNWrly5apSmHm9vb+nXr6+81ruXuLq6GqWEEEKI7XnYpp/iI52cOnVaOr/SVQmPK0ZJ2nFzc5N5X8+VevXqGiWEEEKIfXkYMoDDLunkdthtCQ8PlwIF8stfS/+QbVs3x1sGDOint2vR4rkH1pnL2jX/SvVq1SQyMlL8/Q/q7QkhhJCMCj0f6eSAv7906dJdsmbJIj/+uFhy5cpprIll9uw58tHEj6Vdu7byyaSJRml8wsLCpHfvvrJ5yxYZNXKE9OnT21iTMTl1+ozMnjtP7kTcMUqs0/WVTlK1SmXjHSGEEHviSDlAz4edadLkKZk543OpXLmSjH57rMyZM1fu3Ena6KaHiIgImTJ1uowbP0GCgoKNUvvz1bxvZeTb47SwSC9h4WESHBJi13YihBDy8KD4sDMlShSX5s2bSb68eWXRosWydu16iY6ONtbaHijXmzdvSUhoqMTcizFK7c/t22ESEhwiUVFRRkniFCtaRCZ++J58NvmjB5YPx4+VXLlyiY+Pt+TJk9v4BCGEkIwExYedOXHipCxbtlwuXb4snTp1lEaNnhQXFxdjrW1B7MmNoGAtAGJi7mnPR+CNG3L37l1ji/tgW6y7ERSUrGCIiYmJ21dISGg81xz2rY8ReVdQGhp6U7/H/tPC9p275eLFS/JEpYpSpHAhXYb6oZ7Yr7UlJedACCEkaRw5BwhjPtLJoxTzseTn3+SvZSuMd/fp06uH1KldU7++cSNI5n3znRw8fESLCoDU3vp1a0unDu0lU6ZMugzg1li/YZMsXvJLPDGRO3cu6denlxQuVFC2bN2u4zcS0qL5s/JiuzbGu5Rx+/Zt+d+kTyU4OFiGDR4oRYsU1uXHjp+QKZ9Nl4hEhmE8VJ2HDh4gpUqWMEoIIYSkFUfIAno+7IyPj48UUkba08NDAgKuSZDqpdvrwtasUU26dO4ofn45xMvLUzq82FZ6du8iJUsU1+th3GfMmqOFR+VKFaR71856+9xKMK1dv1Hmzvs2ngdhy7YdsuD7HySzr4+82u0VmTD+HWnb5nk9vDJT7QfeEOwbxyhcuJBOFYbowHvUJbVY83qAzL6+Uq1aFalRvWq8pUzpkuLs7Cy+an22bFmNrQkhhKQHR3hAKD7sTOfOnWTd2tXSqFFDqVW7rgwY8EaahySSA56IurVriq8SPO7u7loANKhfV4sR8PfylTog9LlmTWVQ/77S6Mn60qRxQxnz1nDtNdi33197GQAE0vbtO9V+3KTvaz3lyQb1JF++vPJ8y+bSudNLkj9/PgmPiND7xjFyZMsmLkoIQNTgPeqSGkJDQ+XfVWvE29tLmjzVKN6Nj9iP3q92k/59e8ct3bu8LJGRUeKuBM+rSuzk9PMztiaEEPKoQ/HxmIAg1D379kvevHnk2WeaxDPunp6e8szTjSU6Kkp27tpjlMYSFRWtP2sJxAXESz61L1uxQx330uUrD3g9rAHvzPwF32sh9XSTxtoDQgghxLaYHhB7eEEoPuwMYj6KFispy5avkNOnjsvChd+Kl5eXsdZx3Lx1S4mIm3poZK8SIRs2bo63nDlzTpxdXCQ4OERvj5utZs3qOi5k2oxZMmnKVFm1Zp0EBt6w+bARvB6r1b6teT2sgeGgPXv3S/VqVaTN8y3s8sUghBBiPyg+HhNuKfEReTdSzp49J1/NX/DAgkBVzLBqSZ1aNfQQR47s2eTQ4aOyYOFiGTbybRky/C3ZvmOXzURIarweR44ek0U/LJFsWbPKS+1f4O/gEEKIA7C1B4Tiw8506dJZT6HeuFFDadjoKXnjjSESFmafmI+kQOCrm7ubNvDz5syU+XO/sLoMGtDX+ETszVbliUoyccJ4mT3jMxk1fIjUrVNLZ+d8+dV82b1nn7Fl2jG9HohTad6saZI395WrV2XO19/o1z17MM6DEEL+q1B82BkMsSD91sPDQ86dOy/XrweqUsdnN8O4Iyvk4qXLEhSc8plPzQnREMBapnQpea1nd2nfto2Ouzhx8pRelx5MrweyV5KKIcHxfv71D50q3O6F1rouhBBCHIutPCAUHw6ifPnyepr1gQP7x5tLw9bgpkD6KYZEzHk8gK+vj1SpXEmuBwbK6jXr4w2Z4PWPP/0q/QYNlY2btugyiKS3xrwn4yd8rFN0rYH4ERNnF3VM9ReTmyXkwMFDeg6S22FhRkksSNX9Z+UqLYyeatww0Rsa9fvtj790MCziPBo2qGesIYQQ8l/E5V2F8ZqkgYCAAPn119+0Z+PFF9uLt7e3sSY+GPYoWbKEFChQQIsDSxBr8eefS+X8hQtSv349qV499XNkmCAG4sKFi3L4yDG5cjVAT7OOSbgyZ84sRQoXlCNHj+t4jQP+h7Q34dz5C/LH0r9l0+at2jPSqkUzXVdkwJw5e1Ztd1D27T+g9405OFYosfDv6jV6Peb8QOwFgLDAMMylS5d1Ci7SYHP65dCzj06bPkv2H/DXwazVqj6htwer166XHbt26wnO6qklMfGBAFOIF6xFHVGnHTt3x1uyZ8sm2bNni/0AIYSQRxrOcJpOzBlOYfTfHDZEsmTJYqxJOfgBtS++mC3Hjh+XMWNGS89Xexhr0gY8FZh1FAIDl9dytlGIhIXf/yBbt++M84xADJUvW0a6vNJRcuW8P0NreHiELFwUu605+RgEQoH8+eS1Xj2kYIH8ugxg/Xdqv+s3btb7hcgY8Ppr+ofuPvn0czl95qx06/JynNcCXo+PJk3Rxxg5fIjkz5dXl1sjsZlbLbGcxZUQQohjSKuEoPhIJ1euXJFu3XvKsWPHjJK0A4/DN/O/lqpVqxgl6QO/u4IffMuSJfMD3haIhdCbN/WNY05KlhiW23p5emqvR2JgW8wLgmEeMxMFYuRuZKT2wBBCCMk4UHw8RE6fPi2ffTZN9u5Le/ZHsWLF5PW+faRGjeo2CeYhhBBCHEVqpQTFByGEEELSBcUHIYQQQh4aKZEVTLUlhBBCiEOh+CCEEEKIzUDcYnKxixQfhBBCCHEoFB+EEEIIsTlJeUAoPgghhBBiN6yJEIoPQgghhDgUig9CCCGE2B1LDwjFByGEEEIcBgQIxQchhBBCHArFByGEEEIcCsUHIYQQQhwKxQchhBBCHArFByGEEEIcCsUHIYQQQhwKxQchhBBCHArFByGEEEIcCsUHIYQQQhwKxQchhBBCHIrTPYXxmqQBNN+aNWvl7TFj5cqVq0Zp6vH29pZ+/frKa717iaurq1FKCCGEZDwoPtLJqVOnpfMrXZXwuGKUpB03NzeZ9/VcqVevrlHy6BITEyNnzp4TvxzZJXPmzEYpIYQQkjwUH+nkgL+/dOnSXXx9fWT2rC/Ez8/PWBPLgu++k+nTZ0qLFs/JO2PHGKXxCQ8PkzffHCk7d+2SUSNHSJ8+vY01jy5r1m2Qb79bJDnV+Y4eNUyyZslirEkfp06fkdlz58mdiDtGiXW6vtJJqlapbLwjhBDyX4IxHzbCxdlFC49cuXLGW3y8ffR6Dw+PB9aZS86cOcXd3V1vl14iIiJkytTpMm78BAkKCjZKbU/ePLnF09NDsmbNIplsVPeUEKaEWnBIiNy5k7Q4IYQQ8uhCz0c6MT0f6Pn/+ONiLSYsOXHipBw/flyuBwbK4cNHpGiRwtK1axfJlCmTsYUyqGFh0rt3X9m8ZUu6PR/h4eHy8eSpEhQcLGNHj5Ac2bMba/773L59W/436VMJVuc2bPBA3ZaEEEL+e9DzYWdKlCguzZs3k3x588qiRYtl7dr1Eh0dbay1LRAeN4KCJSoqSmJi7mnPR+CNG3L37t249eZ7aM6QkFC1fZDe3hLEc5ifxV+8Twg+g8/eunXbKIm/f8v3ie0jtWzfuVsuXrwkT1SqKEUKF9JlZj1wHGuLtfMjhBDycKHnI508Sp6PJT//Jn8tW2G8u0+fXj2kTu2acetfebmDbNu+U46runmoegwdPEBKlSyhBcK69RvlR7UdhIOJp6endHyxrTzZoJ44OTnpsmPHT8iUz6ZLubJlZNCAvrrM3H/PHl3l5MlTsn7j5jjRkT17NunXp5eUKF5Mv08tiXk9zHpEJDIMY3l+hBBCHg3o+bAzq1atln79B8q+fftlwofvK5HRK57wsCU1a1STLp07ip9fDvHy8pQOSjD07N5FSpYobmwRy9/L/5Fr169L82bPSGclRHLkiB2a+e2PpfLtwsX69fMtn5PBg/rJC61biquriyxcvEQJrUN6XXIsU/s/fPSYvNT+BenetbOUKV1Kez/mfDVf/00L1rweILOvr1SrVkVqVK8abylTuqQ4OzuLr1qfLVtWY2tCCCGPAhQfdsbHx0cKFSoonh4eEhBwTRnfID3kYQ8Kq+PUrV1TfNUxEcAKMdKgfl0tRiyBQf7wvbHSoX1baVCvjo4LuXs3UhtrGPZxb4+Utm1aaUPfulULnVkSHRUlW7fvMPaQNG7q2O+MHiHNmj4tjZ6sL28OGagzUwKuXdfeltQSGhoq/65aI97eXtLkqUZx3heQJ09u6f1qN+nft3fc0r3LyxIZGSXubm7yqhJfyMghhBDy6EDxYWc6d+4k69aulkaNGkqt2nVlwIA34g1pPAyeVIIEk5pZ4u7uJm2ebynjxozSBt2SLJkz6zlIIsIjjJKkqaVEj+X+MWlalcqVtOg6e+68UZpyduzaI5cuX3nA62ENxHfMX/C9Ttl9uklj7QEhhBDyaEHx8Rji5elpvHoQCAQMjezavUd++uV3+XzmlzJrzteJxlRYI1vWB4c5XFxcjFepA16P1WvWWfV6WGPLth2yZ+9+qV6tihJTLZLdnhBCiOOh+LAzs2fPkaLFSsqy5Svk9KnjsnDht+Ll5WWsfbQ4cvS4jHjrHRky/C0tOpb+vVyOHDkq3qq+Ls4P51ZJjdfjyNFjsuiHJVr8IN6E09QTQsijCcUH0VwNuKYDQkNCQ6TpM0/J5Ikfyrw5M2XGtMk6iBXDLo7G9HoghqV5s6ZJejGuXL0qc77+Rr/u2YNxHoQQ8ihD8WFnunTpLNu2bpbGjRpKw0ZPyRtvDJGwsIcb82ENZJIgjbV+3TrycocXdQaMaewxZ8jDyMc2vR7IXsmXN49R+iCI8/j51z/kxo0gafdCa51dQwgh5NGF4sPOYIgFc39gevVz587L9euBqtR+phyCAVkriN1IzcRebm6u4uziIuEREfGycbCPbTt22m068wMHD+n5QW6HhRklsSDu5J+Vq7TX46nGDRP1eqCuv/3xl+xUQgVxHg0b1DPWEEIIeVSh+HAQ5cuXl5kzPpeBA/vbbZ4PAJFTrGgRCQ29KQsW/iDL//lXLly4aKxNHHwmd+5cevKxmbPmyoaNm2XVmnXy4UefyP4D/nqyLluD2UfnffOdnphs4aIfjdJYNm/drlNzk/N6IMAU5+isxAlmW8WP0s2YNSfecuLkKWNrQgghjwIUHw4C6auYZr127VppzvxIKcjyqFihnBzwPyiLf/xZG+jkQGosZkLFD8bt2LVbvpq/QImXxXI3MlJ6v9pdPDw9jC1tB+Y+QXAoPDWlS91PiYXXY/2Gjcl6PQCGizDsEh0TI4ePHJUdO3c/sFxTIoYQQsijA6dXTyfm9OrIrHhz2BDJkoaflseQxhdfzJZjx4/LmDGjpeerPYw16QO/sXL7dpiqU2Zt4FMCbgd4TaKio8TVxVUyZ/ZN0vinFwzrQODYw7NCCCHk0YTiI51cuXJFunXvKceOHTNK0g5mQ/1m/tdStWoVo4QQQgjJeFB82IDTp0/LZ59Nk7379hklqadYsWLyet8+UqNGdbt6GgghhJCHDcUHIYQQQhwKA04JIYQQ4lAoPgghhBDiUCg+CCGEEOJQKD4IIYQQ4lAoPgghhBDiUCg+CCGEEOJQKD4IIYQQ4lAoPgghhBDiUCg+CCGEEOJQKD4IIYQQ4lAoPgghhBDiUCg+CCGEEOJQKD4IIYQQ4lAoPgghhBDiUCg+CCGEEOJQKD4IIYQQ4lAoPgghhBDiUCg+CCGEEOJQKD4IIYQQ4lCc7imM1ySVoOnWrFkrEydOkog7EUZp2nFxcZWXX+4o3bt1FVdXV6OUEEIIyVhQfKSDY8eOycudu0pgYKBRkn5cXFzk0ymfSKtWLY2SR5OYmBg5c/ac+OXILpkzZzZKCSGEkOThsEs6OH/+ghYepUqW1IJh5ozPpW/f1/Q6y7I2bVrrstq1a+n3iZXhdXR0tFy6dFmve5RZt2GTvD/hY/ngf59IcEiIUZo6IiIiZMrU6TJu/AQJCgo2Sm3DX8v+kcHDRsnuPfuMEtuC+qLeqD/OgxBCSMqh+LABfn5+0rTpM9K8eTOpXq3aA2VlSpfWZfnz59fvEyvD6/RiT4NuSd48ucXT00OyZs0imdzdjdLUAafbzZu3JCQ0VGLuxRiltiEsLEyLojt37hgltgX1Rb1RfzoPCSEkdVB8ZDDsadAtKVO6lMycNkVGjxymRIinUUoIIYQkD8VHBiI8PFxuBAVLVFSUxMTc056PwBs35O7du3HrzfcQKSEhoWr7IL29JYjnMD+Lv3ifEHwGn71167ZREn//lu8T20daSUn9EpKauphtg+3xNy2eDbQBPo8Fx04LqdmHZZskrLO5LqlzN9snrXUlhJDUwIDTdLBq1Wrp1buP1K1TR+bMmSVeXl5Wy2bPniMfTfxY2rVrK59Mmqg/a63szeEj5eeff5FRI0dInz69dVlqWPLzb/LXshXGu/v06dVD6tSuGbf+lZc7yLbtO+X4iZPikSmTDB08QEqVLKEN07r1G+VHtZ2lEYJno+OLbeXJBvXEyclJlx07fkKmfDZdypUtI4MG9NVl5v579ugqJ0+ekvUbN8cZu+zZs0m/Pr2kRPFi+j3AMT6ePFWCgoNl7OgRkiN7dmONdVJTP8u6XLx4Sf75d3VcXXx8vOWVTh2kVs3qcdub7NvvL999/4Ncu37dKLG+PQw1Yl6yZc0qI4a9Eef9CQ+PkAXfL5at23bEHc/Z2VmqVqksPbt31UNVyYGhokU//CQbN2+NE4Y4btkypaWXOh+0pQm+vus3bJLFS36J1ya5c+fS7V24UEEdRzR1+iw5cvSYDB74ur5mlkDkfPLp5zqA2Np6QgixNfR8ZCBq1qgmXTp3FD+/HEr0eEoHZZB7du8iJUsUN7aI5e/l/2jj2rzZM9JZCZEcOWKN/m9/LJVvFy7Wr59v+ZwMHtRPXmjdUlxdXWTh4iVywP+QXpccy9T+DytD91L7F6R71856iAa97jlfzdd/00pa6rd6zTolgjZJy+ea6bZo1vRpZYxjZO68b2WLEgiWwDjPnvu13Lp9W2+H7XEcc3uIgaSAkZ+/YKFs2bpdKpQvJ8OHDpJxY0ZJlScqya7de2Xhoh+S9aJAbOBYa5XIKlSwgL6eaMMnKlfU9fvfx1PiCSOcwwIlljL7+sir3V6RCePfkbZtnpeQ4BCZOWuObm9kUKEOkZGRVs/h4qXLcuHiRX284sWKGqWEEGI/KD4yEOjl1q1dU3x9fMTd3V2LkQb162oxYomvr698+N5Y6dC+rTSoV0d7HO7ejdQ99CKFC8m4t0cqA9ZKnqhUUVq3aiFdX+kk0coobt0e31gnhps69jujR2gD3ujJ+vLmkIG65x9w7br2tqSFtNbvakCADOrfV2+Ptuj4Ujv93lUZZIiw20poAHgNfljyi379xoC+ejtsj88NGzxAPDwyyYqVq+K2twaGoSAQYMD79+0l5cuVlaJFCsvrr/WU+qqd0e7mkFRiwGOyWwmValWf0PE0TRo31G2IOqM9ITzWrtugt4WQ2b59p7rWbtJXHQOen3z58irB1Fw6d3pJ8ufPJ+FGJk7lShXEL0cOOX78pK6nJRBtYWHh+hplypTJKCWEEPtB8fEY8qQyqt7e3sa7WGDA2jzfUvfU8+TJbZTGkiVzZnFzc5OI8JSllNZSosdy/5gwrUrlStpYnj133ihNHWmtX2UlUEqXKmm8i6VM6ZJSvnxZuXo1QE6dPqPLTpw8pXr/l6R2zRraU2MJxETFCuXjbZ8UEDKWWTY4f3hR4IlKyrjDcwJPhovaHqLDcqI5DLtACM2Y+om82O4FozSWqKhoHWRsCYQTBEu+vHn0++zZsknJksXlxo0bcvLUaV0GUNc9e/dJliyZtXeEEEIcAcXHY4hXEtkpEAhw1e/avUd++uV3+XzmlzJrztcSkYqUVcRBJASuf1uQ2vohxiRhXAfeQ1BgGALeGIC/eI84jQ0bN8dbNm7aIuFh4Xq9ZYBtQmDgIVwwjDH6nfEy5+tv9DwjlrEYSQHvA+JfsmbJoj0YCYEYsRR1OI+aNavrOk+bMUsmTZkqq9ask8DAG1aHd+rXra2FDbwr5vrTZ87q+iLOI0/u+KKOEELsBcUHiePI0eMy4q13ZMjwt7RRX/r3cjly5Kh4e3mJi/PDv1XSUr/EhJYpkG7ciB2CMP8i1uKr+QseWPbuP6DXJwUEFuIuWrVopgXBps1btSjoN2iYTJg4Wa5cuWpsaZ27kXf1/CTe3l4pnjulTq0a0r9vb8mRPZscOnxUFixcLMNGvq3baPuOXfFECAQX4jrg5blyNbYue/bu10NWGHJJKNIIIcReUHwQzdWAazogNCQ0RJo+85RMnvihzJszU2ZMm6yDHjGs8TBJa/3gSbDGZUMImJkj5l9kk8yf+0WiC7KGkgLDKu1eaK3nQJk6eaLONCpWtIiOdfl02owkA27d3dx1dtTt22FyJ5nYEBMIBgyXTJwwXmbP+ExGDR8idevU0iLmS9VeljO8om6VKlaQ0NCbWshhEjZk9+TNl1fKlYmd9I4QQhwBxQfRIB01WBnq+nXryMsdXtQZMGZPGHOGPOx87LTWD/ENCYcgEFuBtFIIllw5/XQZ/uL9wcNHrA5ZpBR81kyxRRwFxMpbI4bq4ZjgkFAtohIDGUrwyEAUWJtiP/TmTR2XYu7fBOcDEGSM47zWs7u0b9tGZ87Ay2FJjepVJHNmX9m5a48OPsXPA5RVn0kYA0QIIfaE4iODAYOMrBBLI5gS3NxcxdnFRWdHWBpf7GPbjp12m6Y8paS1fgcPHta9fEsOHjqis1IwFwa8EgCxIQXy55N9+w/I0WPxt0eGy3sffKTjOJIaOsHcKX36D9axKNYEjLOzk04LNjlw8JCej+R2WJh+j2EbDKNgGASxG5aTv2F//6xcLWPf/UDPAQKuXw+Ut8a8J+MnfJxoFk5Cj1CunDmlcKFCcunyZT0slMkjk/aUEEKII3F5V2G8Jqnk9OnT8sefS/VY/enTZ2TN2nWyceMmOat61ZZlW7ZskatXr+rAw2PKsK1avcZq2fbtO2J79/XrSfXqsb8Rk1oQlHjhwkU5fOSYXLkaoKdZx0Ri+OXZQ6pXD/d/9apVpGCB+L8j46N6vnv2HZAj6nPwMsCYo9f8/eIlOkPlnjLyOXP66Ym2ACbZwnwWlmVJ7R91wlwXmMysfLnYSaxgXDdt2abjLS5fuiK79uyVHTt3x1vw430QBshoSU39zLqUU8dCiiyGa4KDQ/QkZT/9+psSLffkpfZt4+a1gJHOo8QIhMymLVv1sAyGLg4fQRzFD3Je1R9eBWQKQdzhumFyL08PD6lXt7b+PGJPEAi7/8DBeHWEWEBdcKxnn2mirxHSXadNn6W29df1QmotQKDppctXtGdix849uo3g7fhj6d868BVBoZj3A5OVYWKzM2fPygH/g1o0ARwX5/uvup+wHnN+WAYAo+6uSsht3rJd3x8lS5TQKbwoJ4QQR0HxkQ5gANatX68N5KFDh8Xf318LD4CeqFkGkQEgLPA+sTK89lDGrGfPHlJI9U7TSvFiRbSxxPwN/qp3DSMEg5+UOIDLHpORHT16TI4oMYRARBhGHx8f6d6lsw64hBGzh/hAdgYMIYYaEi7IMMHcJYiFSE39zLpgkrC8eXPLyn9Xa4OO7A5vbx89NFGzelW9rQnmQ8HcHPCK4PM4hv/Bw7oOzzV7Rs/9gXYC1sQH7gcIlOPHT+g67t67T9cRabA4h97quuL6mmD/mAq9yVON9PwlACKgcsUK+rX/QYgKf9mrRFdAwDUpX7aM9H+9d1x8CrxcZcuUUfsIkaPHTqjt9ut9QqwgxfaNAa/ruV8SktnXVwm5/bpemGjO9P4QQoij4PTq6eTsuXOyaNFiHcSXXjDxVcuWLaRGjQen/U4LmNAKwYuIPUhpzxa3A84lKjpK1cdVxwfYoi62Iq31Q1vcvHUrxZ+BuAhTi7OTc6raz8T8PI4DY285Z4cJhozuKmEDz5Q1IM4Q54FzNieOSwzLbZHhY073TgghjyIUH4QQQghxKBzoJYQQQohDofgghBBCiEOh+CCEEEKIQ6H4IIQQQohDofgghBBCiEOh+CCEEEKIQ6H4IIQQQohDofgghBBCiEOh+CCEEEKIQ6H4IIQQQohDofgghBBCiEOh+CCEEEKIQ6H4IIQQQohDofgghBBCiEOh+CCEEEKIQ6H4IIQQQohDofgghBBCiEOh+CCEEEKIQ6H4IIQQQohDofgghBBCiEOh+CCEEEKIQ6H4IIQQQohDofgghBBCiEOh+CCEEEKIQ6H4IIQQQohDofgghBBCiEOh+CCEEEKIQ6H4IIQQQohDofgghBBCiEOh+CCEEEKIQ6H4IIQQQohDofgghBBCiEOh+CCEEEKIQ6H4IIQQQohDofgghBBCiEOh+CCEEEKIAxH5P/LEQ0Uu1x74AAAAAElFTkSuQmCC"}}},{"cell_type":"code","source":"pip install py7zr","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-24T06:54:15.832702Z","iopub.execute_input":"2025-06-24T06:54:15.832979Z","iopub.status.idle":"2025-06-24T06:54:18.835271Z","shell.execute_reply.started":"2025-06-24T06:54:15.832956Z","shell.execute_reply":"2025-06-24T06:54:18.834247Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"导入必要的库","metadata":{}},{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom tqdm import tqdm  # 添加这行\nfrom pathlib import Path\nimport py7zr\nimport time\nimport copy\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nimport torchvision\nfrom torchvision import transforms, datasets, models\nfrom torch.utils.data import DataLoader, random_split, Dataset\nfrom sklearn.metrics import confusion_matrix, classification_report, roc_curve, auc\nfrom sklearn.preprocessing import label_binarize\nfrom PIL import Image","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-24T06:54:18.837156Z","iopub.execute_input":"2025-06-24T06:54:18.837420Z","iopub.status.idle":"2025-06-24T06:54:18.842751Z","shell.execute_reply.started":"2025-06-24T06:54:18.837396Z","shell.execute_reply":"2025-06-24T06:54:18.842123Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"设置随机种子保证结果的可重复性","metadata":{}},{"cell_type":"code","source":"torch.manual_seed(42)\nnp.random.seed(42)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-24T06:54:18.843510Z","iopub.execute_input":"2025-06-24T06:54:18.843754Z","iopub.status.idle":"2025-06-24T06:54:18.849089Z","shell.execute_reply.started":"2025-06-24T06:54:18.843732Z","shell.execute_reply":"2025-06-24T06:54:18.848594Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"使用GPU，加速训练","metadata":{}},{"cell_type":"code","source":"device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"使用设备: {device}\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-24T06:54:18.850504Z","iopub.execute_input":"2025-06-24T06:54:18.850670Z","iopub.status.idle":"2025-06-24T06:54:18.854592Z","shell.execute_reply.started":"2025-06-24T06:54:18.850657Z","shell.execute_reply":"2025-06-24T06:54:18.853985Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"设置输入数据和输出数据的路径","metadata":{}},{"cell_type":"code","source":"INPUT_DIR = Path(\"/kaggle/input/cifar-10\")\nOUTPUT_DIR = Path(\"/kaggle/working\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-24T06:54:18.855237Z","iopub.execute_input":"2025-06-24T06:54:18.855732Z","iopub.status.idle":"2025-06-24T06:54:18.858826Z","shell.execute_reply.started":"2025-06-24T06:54:18.855710Z","shell.execute_reply":"2025-06-24T06:54:18.858300Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"对前面导入的比赛的数据集进行解压，放在输出的working中","metadata":{}},{"cell_type":"code","source":"def extract_cifar10():\n    if (OUTPUT_DIR/\"train\").exists() and (OUTPUT_DIR/\"test\").exists():\n        print(\"数据已解压，跳过解压步骤\")\n        return\n    (OUTPUT_DIR/\"train\").mkdir(exist_ok=True, parents=True)\n    (OUTPUT_DIR/\"test\").mkdir(exist_ok=True, parents=True)\n    with py7zr.SevenZipFile(INPUT_DIR/\"train.7z\", mode='r') as archive:\n        archive.extractall(path=OUTPUT_DIR/\"train\")\n    with py7zr.SevenZipFile(INPUT_DIR/\"test.7z\", mode='r') as archive:\n        archive.extractall(path=OUTPUT_DIR/\"test\")\n    labels = pd.read_csv(INPUT_DIR/\"trainLabels.csv\")\n    labels.to_csv(OUTPUT_DIR/\"trainLabels.csv\", index=False)\n    print(f\"解压完成！文件保存至: {OUTPUT_DIR}\")\n\nif not (OUTPUT_DIR/\"train\").exists():\n    extract_cifar10()","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-24T06:54:18.859462Z","iopub.execute_input":"2025-06-24T06:54:18.859697Z","iopub.status.idle":"2025-06-24T06:54:18.864934Z","shell.execute_reply.started":"2025-06-24T06:54:18.859673Z","shell.execute_reply":"2025-06-24T06:54:18.864322Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"读取数据集，并将标签和ID进行映射，查看所有种类的图片。","metadata":{}},{"cell_type":"code","source":"# 加载标签\nlabels_df = pd.read_csv(OUTPUT_DIR/\"trainLabels.csv\")\nlabel_map = dict(zip(labels_df[\"id\"], labels_df[\"label\"]))\nclass_names = sorted(labels_df['label'].unique().tolist())\nnum_classes = len(class_names)\nprint(f\"类别数量: {num_classes}, 类别名称: {class_names}\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-24T06:54:18.865666Z","iopub.execute_input":"2025-06-24T06:54:18.865912Z","iopub.status.idle":"2025-06-24T06:54:18.901633Z","shell.execute_reply.started":"2025-06-24T06:54:18.865891Z","shell.execute_reply":"2025-06-24T06:54:18.901006Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"定义CIFAR10Dataset，用于对原始数据进行统一的处理。在初始化的阶段，就初始化了根目录、数据转换、以及通过glob的方法，对测试集的数据如何使以png结尾的，就提取出来按照后缀名进排序。该类对训练集、测试集的处理方法不一样，如果使训练集就将标签与数值对应，如果使测试集，就提取每个路径的文件名。对于返回的结果，如果使训练集就是img，也就是经过了数据预处理和数据增强的数据，如果使测试集返回的就是和训练集有同样的预处理经历的数据，如果是返回的标签，那就是0-9的10个类别。","metadata":{}},{"cell_type":"code","source":"class CIFAR10Dataset(Dataset):\n    def __init__(self, root_dir, label_map=None, transform=None, is_test=False):\n        self.root = Path(root_dir)\n        self.transform = transform\n        self.is_test = is_test\n        self.images = sorted([f for f in self.root.glob(\"*.png\")])\n        \n        if not is_test:\n            self.labels = []\n            self.class_to_idx = {c: i for i, c in enumerate(class_names)}\n            for img_path in self.images:\n                img_id = int(img_path.stem)\n                self.labels.append(self.class_to_idx[label_map[img_id]])\n        else:\n            self.filenames = [f.name for f in self.images]\n    \n    def __len__(self):\n        return len(self.images)\n    \n    def __getitem__(self, idx):\n        img_path = self.images[idx]\n        img = Image.open(img_path).convert(\"RGB\")\n        if self.transform:\n            img = self.transform(img)\n        if self.is_test:\n            return img, self.filenames[idx]\n        else:\n            return img, self.labels[idx]","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-24T06:54:18.902408Z","iopub.execute_input":"2025-06-24T06:54:18.902578Z","iopub.status.idle":"2025-06-24T06:54:18.908909Z","shell.execute_reply.started":"2025-06-24T06:54:18.902565Z","shell.execute_reply":"2025-06-24T06:54:18.908372Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2 数据增强","metadata":{}},{"cell_type":"markdown","source":"下面实现的是对数据进行增强，在训练集数据，首先是对数据的形状32进行了保留，随后是裁剪范围从 0.8到0.9，减少过度裁剪，以及设置宽高比更接近原始，避免拉伸过度。其次是对训练的数据按照0.3的概率进行水平翻转，以及随机仿射变换，旋转加平移，旋转范围是8度，平移范围从10%到5%，更轻微。旋转的时候，就不裁剪了。对于测试集数据，就没有进行裁剪和平移的一系列操作了，只是进行标准化。最后使用CIFAR10Dataset封装训练集和测试集数据。","metadata":{}},{"cell_type":"code","source":"import torchvision.transforms as transforms\nfrom PIL import Image\nimport matplotlib.pyplot as plt\ntrain_transform = transforms.Compose([\n    transforms.RandomResizedCrop( 32, \n        scale=(0.9, 1.0),  \n        ratio=(0.9, 1.1)   \n    ),\n    transforms.RandomHorizontalFlip(p=0.3), \n    transforms.RandomAffine(\n        degrees=8,         \n        translate=(0.05, 0.05),  \n        scale=None,    \n        shear=0        \n    ),\ntransforms.ColorJitter(\n    brightness=0.1,  \n    contrast=0.1,     \n    saturation=0.1,  \n    hue=0.05      \n),\n    transforms.ToTensor(),\n    transforms.Normalize(\n        mean=[0.485, 0.456, 0.406], \n        std=[0.229, 0.224, 0.225]\n    )\n])\n\ntest_transform = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Normalize(\n        mean=[0.485, 0.456, 0.406], \n        std=[0.229, 0.224, 0.225]\n    )\n])\ntrain_dataset = CIFAR10Dataset(\n    root_dir=OUTPUT_DIR/\"train/train\",\n    label_map=label_map,\n    transform=train_transform\n)\n\ntest_dataset = CIFAR10Dataset(\n    root_dir=OUTPUT_DIR/\"test/test\",\n    transform=test_transform,\n    is_test=True\n)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-24T06:54:18.909604Z","iopub.execute_input":"2025-06-24T06:54:18.909776Z","iopub.status.idle":"2025-06-24T06:54:23.525954Z","shell.execute_reply.started":"2025-06-24T06:54:18.909762Z","shell.execute_reply":"2025-06-24T06:54:23.525191Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"将前面的训练集进行再次划分，按照训练集：验证集为8：2的比例进行划分，用于评估模型。","metadata":{}},{"cell_type":"code","source":"# 划分训练集和验证集\ntrain_size = int(0.8 * len(train_dataset))\nval_size = len(train_dataset) - train_size\ntrain_subset, val_subset = random_split(train_dataset, [train_size, val_size])\n\nval_dataset = CIFAR10Dataset(\n    root_dir=OUTPUT_DIR/\"train/train\",\n    label_map=label_map,\n    transform=test_transform,\n    is_test=False\n)\nval_dataset.images = [train_dataset.images[i] for i in val_subset.indices]\nval_dataset.labels = [train_dataset.labels[i] for i in val_subset.indices]","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-24T06:54:23.528919Z","iopub.execute_input":"2025-06-24T06:54:23.529132Z","iopub.status.idle":"2025-06-24T06:54:23.950500Z","shell.execute_reply.started":"2025-06-24T06:54:23.529115Z","shell.execute_reply":"2025-06-24T06:54:23.949970Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"设置批次大小为128，将上述的训练集、测试集、验证集都封装为DataLoader,并将数据打乱，在这里可以说明一下，批次是128的意思就是，将所有的样本的总数除以128就是一个epoch跑的显式数量，但是一个epoch跑的其实还是总的样本数量，不过是并行跑的。","metadata":{}},{"cell_type":"code","source":"# 数据加载器\nbatch_size = 64\ntrain_loader = DataLoader(train_subset, batch_size=batch_size, shuffle=True, num_workers=4)\nval_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False, num_workers=4)\ntest_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False, num_workers=4)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-24T06:54:23.951186Z","iopub.execute_input":"2025-06-24T06:54:23.951402Z","iopub.status.idle":"2025-06-24T06:54:24.141924Z","shell.execute_reply.started":"2025-06-24T06:54:23.951387Z","shell.execute_reply":"2025-06-24T06:54:24.141173Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns \ntrain_labels = []\nfor inputs, labels in train_loader:\n    train_labels.extend(labels.cpu().numpy())  # Convert labels to numpy array and flatten\nlabel_counts = pd.Series(train_labels).value_counts()\nplt.figure(figsize=(12, 6))\nsns.barplot(x=label_counts.index, y=label_counts.values, palette=\"viridis\")  # Use seaborn for styling\nplt.title(\"Training Set Label Frequency Distribution\", fontsize=14)\nplt.xlabel(\"Label\", fontsize=12)\nplt.ylabel(\"Frequency\", fontsize=12)\nplt.xticks(rotation=45, ha=\"right\")  \nplt.grid(axis=\"y\", alpha=0.3)  \nfor x, y in zip(label_counts.index, label_counts.values):\n    plt.text(x, y, str(y), ha=\"center\", va=\"bottom\")\nplt.tight_layout()  \nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-24T06:54:24.142565Z","iopub.execute_input":"2025-06-24T06:54:24.142764Z","iopub.status.idle":"2025-06-24T06:54:44.904694Z","shell.execute_reply.started":"2025-06-24T06:54:24.142747Z","shell.execute_reply":"2025-06-24T06:54:44.903855Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"可视化数据增强的结果，我设置的可视化增强3个，当然可以更多。可以发现增强的数据有对图片中飞机的旋转、平移、还要颜色的变换等操作，但是看轮廓还是能知道是一架飞机。","metadata":{}},{"cell_type":"code","source":"def visualize_augmentations(dataset, num_augmentations=5):\n    if not isinstance(dataset, CIFAR10Dataset):\n        raise TypeError(\"dataset必须是CIFAR10Dataset实例\")\n    \n    # 读取原始PIL图像（避免数据集transform的影响）\n    img_path = dataset.images[0]\n    orig_img = Image.open(img_path).convert(\"RGB\")\n    \n    plt.figure(figsize=(15, 5))\n    plt.subplot(1, num_augmentations + 1, 1)\n    plt.imshow(orig_img)\n    plt.title(\"Original\")\n    plt.axis('off')\n    \n    # 构建与train_transform对齐的可视化增强流程（不含ToTensor/Normalize）\n    vis_transform = transforms.Compose([\n        transforms.RandomResizedCrop(\n            32, \n            scale=(0.9, 1.0), \n            ratio=(0.9, 1.1)\n        ),\n        transforms.RandomHorizontalFlip(p=0.3),\n        transforms.RandomAffine(\n            degrees=8, \n            translate=(0.05, 0.05), \n            scale=None, \n            shear=0\n        ),\ntransforms.ColorJitter(\n    brightness=0.1,   # 亮度 ±10% 变化（人眼可察觉，但不极端）\n    contrast=0.1,     # 对比度 ±10% 变化\n    saturation=0.1,   # 饱和度 ±10% 变化\n    hue=0.05          # 色调微小偏移（避免色偏严重）\n)\n    ])\n    \n    for i in range(num_augmentations):\n        # 应用增强（保持PIL格式）\n        aug_img = vis_transform(orig_img)\n        \n        # 手动模拟训练流程的ToTensor和Normalize\n        aug_tensor = transforms.ToTensor()(aug_img)\n        aug_tensor = transforms.Normalize(\n            mean=[0.485, 0.456, 0.406], \n            std=[0.229, 0.224, 0.225]\n        )(aug_tensor)\n        \n        # 转换为PIL图像显示（自动反归一化）\n        aug_img_pil = transforms.ToPILImage()(aug_tensor)\n        \n        plt.subplot(1, num_augmentations + 1, i + 2)\n        plt.imshow(aug_img_pil)\n        plt.title(f\"Aug {i+1}\")\n        plt.axis('off')\n    \n    plt.tight_layout()\n    plt.savefig(\"data_augmentations.png\")\n    plt.show()","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-24T06:54:44.905770Z","iopub.execute_input":"2025-06-24T06:54:44.906056Z","iopub.status.idle":"2025-06-24T06:54:44.913229Z","shell.execute_reply.started":"2025-06-24T06:54:44.906029Z","shell.execute_reply":"2025-06-24T06:54:44.912590Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"visualize_augmentations(val_dataset, num_augmentations=3)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-24T06:54:44.913849Z","iopub.execute_input":"2025-06-24T06:54:44.914029Z","iopub.status.idle":"2025-06-24T06:54:45.324148Z","shell.execute_reply.started":"2025-06-24T06:54:44.914015Z","shell.execute_reply":"2025-06-24T06:54:45.323477Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3 模型构建","metadata":{}},{"cell_type":"markdown","source":"我之前用 ResNet50 的时候，模型总是过拟合，训练效果特别差，后来换成 ResNet34，效果一下子就好多了。\nResNet34 模型结构相对没那么复杂，更适合中小型数据集。它的核心是由一个个基础块搭起来的，每个块里有两层3x3的卷积层，中间夹着批量归一化和激活函数，就像给图像做两次特征提取，再通过残差连接把结果和原来的图像加在一起，这样就避免了深层网络的梯度消失问题。整个模型的结构大概是这样：第一层，先用一个3x3的卷积层、批量归一化和ReLU激活，把输入图像转换成64通道的特征图。中间四层，这是模型的核心，分成四个大层layer1到layer4，每一层由多个“基础块”组成：layer1：3个基础块，每个块处理64通道的特征，不改变图像尺寸。layer2：4个基础块，开始用stride=2的卷积降低图像尺寸，同时把通道数翻倍到128。layer3：6个基础块，再次降低尺寸通道数增加到 256。layer4：3个基础块，最后一次降低尺寸，通道数提到 512。每一层里，如果前后的特征图尺寸或通道数对不上，就用1x1卷积做”通道融合“，保证相加的时候形状一致。最后两层，用全局平均池化把特征图缩成 1x1 的向量，相当于把所有空间信息浓缩成一个 “特征点”。接一个全连接层，输出类别数。为什么ResNet34比50效果好？因为它的层数更少，参数量更少，不容易在小数据集上过拟合。每个基础块的结构简单直接，残差连接又能让梯度顺畅传播，训练起来更稳定。","metadata":{}},{"cell_type":"markdown","source":"下面是定义的残差块","metadata":{}},{"cell_type":"code","source":"# 定义模型（以ResNet34为例，修复原始代码中的层配置）\nclass BasicBlock(nn.Module):\n    expansion = 1\n    def __init__(self, in_channels, out_channels, stride=1, downsample=None):\n        super().__init__()\n        self.conv1 = nn.Conv2d(in_channels, out_channels, 3, stride, 1, bias=False)\n        self.bn1 = nn.BatchNorm2d(out_channels)\n        self.conv2 = nn.Conv2d(out_channels, out_channels, 3, 1, 1, bias=False)\n        self.bn2 = nn.BatchNorm2d(out_channels)\n        self.downsample = downsample\n        self.stride = stride\n    \n    def forward(self, x):\n        identity = x\n        out = self.conv1(x)\n        out = self.bn1(out)\n        out = nn.ReLU()(out)\n        out = self.conv2(out)\n        out = self.bn2(out)\n        if self.downsample is not None:\n            identity = self.downsample(x)\n        out += identity\n        out = nn.ReLU()(out)\n        return out","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-24T06:54:45.325002Z","iopub.execute_input":"2025-06-24T06:54:45.325324Z","iopub.status.idle":"2025-06-24T06:54:45.332356Z","shell.execute_reply.started":"2025-06-24T06:54:45.325297Z","shell.execute_reply":"2025-06-24T06:54:45.331722Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"用卷积层、残差块、池化层和全连接层构成残差网络。","metadata":{}},{"cell_type":"code","source":"class ResNet(nn.Module):\n    def __init__(self, block, layers, num_classes=10):\n        super().__init__()\n        self.in_channels = 64\n        self.conv1 = nn.Sequential(\n            nn.Conv2d(3, 64, 3, 1, 1, bias=False),\n            nn.BatchNorm2d(64),\n            nn.ReLU(inplace=True)\n        )\n        self.layer1 = self._make_layer(block, 64, layers[0], stride=1)\n        self.layer2 = self._make_layer(block, 128, layers[1], stride=2)\n        self.layer3 = self._make_layer(block, 256, layers[2], stride=2)\n        self.layer4 = self._make_layer(block, 512, layers[3], stride=2)\n        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))\n        self.fc = nn.Linear(512 * block.expansion, num_classes)\n    \n    def _make_layer(self, block, out_channels, blocks, stride=1):\n        downsample = None\n        if stride != 1 or self.in_channels != out_channels * block.expansion:\n            downsample = nn.Sequential(\n                nn.Conv2d(self.in_channels, out_channels * block.expansion, 1, stride, bias=False),\n                nn.BatchNorm2d(out_channels * block.expansion)\n            )\n        layers = [block(self.in_channels, out_channels, stride, downsample)]\n        self.in_channels = out_channels * block.expansion\n        for _ in range(1, blocks):\n            layers.append(block(self.in_channels, out_channels))\n        return nn.Sequential(*layers)\n    \n    def forward(self, x):\n        x = self.conv1(x)\n        x = self.layer1(x)\n        x = self.layer2(x)\n        x = self.layer3(x)\n        x = self.layer4(x)\n        x = self.avgpool(x)\n        x = torch.flatten(x, 1)\n        x = self.fc(x)\n        return x","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-24T06:54:45.333147Z","iopub.execute_input":"2025-06-24T06:54:45.333667Z","iopub.status.idle":"2025-06-24T06:54:45.341619Z","shell.execute_reply.started":"2025-06-24T06:54:45.333641Z","shell.execute_reply":"2025-06-24T06:54:45.340998Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"创建图像分类模型","metadata":{}},{"cell_type":"code","source":"def create_resnet34(num_classes=10):\n    return ResNet(BasicBlock, [3, 4, 6, 3], num_classes).to(device)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-24T06:54:45.342266Z","iopub.execute_input":"2025-06-24T06:54:45.342465Z","iopub.status.idle":"2025-06-24T06:54:45.345765Z","shell.execute_reply.started":"2025-06-24T06:54:45.342443Z","shell.execute_reply":"2025-06-24T06:54:45.345188Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4 模型训练","metadata":{}},{"cell_type":"markdown","source":"接下来，使用上述定义的模型，定义训练模型。初始化模型参数，记录最佳模型权重、存储训练历史数据；设置迭代训练，按 epoch 循环，每个 epoch 中，\n训练阶段启用模型训练模式，通过数据加载器批量输入数据，计算损失、反向传播更新参数，并实时显示训练进度和指标损失值、准确率；验证阶段：切换模型为评估模式，用验证集计算损失和准确率，不更新参数；模型优化：自动保存验证集准确率最高的模型权重，避免过拟合；以及学习率的调整，最后训练结束后加载最佳模型权重，返回训练好的模型及训练历史数据损失值和准确率。","metadata":{}},{"cell_type":"code","source":"# 训练函数\ndef train_model(model, train_loader, val_loader, criterion, optimizer, scheduler=None, num_epochs=20, model_name=\"model\"):\n    best_acc = 0.0\n    best_model_wts = copy.deepcopy(model.state_dict())\n    history = {'train_loss': [], 'train_acc': [], 'val_loss': [], 'val_acc': []}\n    \n    for epoch in range(num_epochs):\n        print(f\"Epoch {epoch+1}/{num_epochs}\")\n        print(\"-\" * 10)\n        model.train()\n        running_loss = 0.0\n        running_corrects = 0\n        \n        train_progress = tqdm(train_loader, desc='Train', unit='batch')\n        for inputs, labels in train_progress:\n            inputs = inputs.to(device)\n            labels = labels.to(device)\n            \n            optimizer.zero_grad()\n            outputs = model(inputs)\n            _, preds = torch.max(outputs, 1)\n            loss = criterion(outputs, labels)\n            \n            loss.backward()\n            optimizer.step()\n            \n            running_loss += loss.item() * inputs.size(0)\n            running_corrects += torch.sum(preds == labels)\n            \n            train_progress.set_postfix({\n                'Loss': f'{loss.item():.4f}',\n                'Acc': f'{(preds == labels).float().mean():.4f}'\n            })\n        \n        epoch_loss = running_loss / len(train_loader.dataset)\n        epoch_acc = running_corrects.float() / len(train_loader.dataset)\n        history['train_loss'].append(epoch_loss)\n        history['train_acc'].append(epoch_acc.item())\n        print(f'Train: Loss={epoch_loss:.4f}, Acc={epoch_acc:.4f}')\n        \n        model.eval()\n        val_running_loss = 0.0\n        val_running_corrects = 0\n        \n        val_progress = tqdm(val_loader, desc='Val', unit='batch')\n        with torch.no_grad():\n            for inputs, labels in val_progress:\n                inputs = inputs.to(device)\n                labels = labels.to(device)\n                \n                outputs = model(inputs)\n                _, preds = torch.max(outputs, 1)\n                loss = criterion(outputs, labels)\n                \n                val_running_loss += loss.item() * inputs.size(0)\n                val_running_corrects += torch.sum(preds == labels)\n                \n                val_progress.set_postfix({\n                    'Loss': f'{loss.item():.4f}',\n                    'Acc': f'{(preds == labels).float().mean():.4f}'\n                })\n        \n        val_epoch_loss = val_running_loss / len(val_loader.dataset)\n        val_epoch_acc = val_running_corrects.float() / len(val_loader.dataset)\n        history['val_loss'].append(val_epoch_loss)\n        history['val_acc'].append(val_epoch_acc.item())\n        print(f'Val: Loss={val_epoch_loss:.4f}, Acc={val_epoch_acc:.4f}')\n        \n        if val_epoch_acc > best_acc:\n            best_acc = val_epoch_acc\n            best_model_wts = copy.deepcopy(model.state_dict())\n            torch.save(best_model_wts, f\"{OUTPUT_DIR}/{model_name}_best.pth\")\n            print(\"★ 保存最佳模型\")\n        \n        if scheduler:\n            scheduler.step()\n        print()\n    \n    model.load_state_dict(best_model_wts)\n    return model, history","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-24T06:54:45.346432Z","iopub.execute_input":"2025-06-24T06:54:45.346760Z","iopub.status.idle":"2025-06-24T06:54:45.357420Z","shell.execute_reply.started":"2025-06-24T06:54:45.346732Z","shell.execute_reply":"2025-06-24T06:54:45.356917Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"接下来使用模型进行训练。使用的损失函数是交叉熵损失函数如式（），是多分类任务的标配损失函数，计算模型预测和真实标签的 “差距”。选择的优化器和学习率策略是优化器使用Adam优化器，学习率lr=0.0001通过控制参数更新的 “步幅”，以及正则化的约束是L2正则化值是1e-3，防止过拟合。\n学习率调度器，StepLR每7个epoch把学习率乘以 0.1，从 0.0001降到0.00001，让模型后期慢下来细调，避免错过最佳参数。","metadata":{}},{"cell_type":"markdown","source":"多样本交叉熵损失函数公式为             \n$\nL = -\\frac{1}{N} \\sum_{i=1}^{N} \\sum_{j=1}^{C} y_{ij} \\cdot \\log(p_{ij})$              \n其中                 \n- N为样本总数，C为类别数；  \n- $y_{ij}$ 为样本i属于类别j的真实标签；  \n- $p_{ij}$为模型预测样本i属于类别j的概率,通常通过softmax输出。","metadata":{}},{"cell_type":"code","source":"# 创建并训练模型\nmodel = create_resnet34(num_classes).to(device)\ncriterion = nn.CrossEntropyLoss()\noptimizer = optim.Adam(model.parameters(), lr=0.0001, weight_decay=1e-4) #1e-4\nscheduler = optim.lr_scheduler.StepLR(optimizer, step_size=6, gamma=0.1) #7 #0.1\n\nmodel, history = train_model(\n    model, train_loader, val_loader, criterion, optimizer, scheduler,\n    num_epochs=20, model_name=\"resnet34\"\n)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-24T06:54:45.357975Z","iopub.execute_input":"2025-06-24T06:54:45.358178Z","iopub.status.idle":"2025-06-24T07:19:21.825529Z","shell.execute_reply.started":"2025-06-24T06:54:45.358162Z","shell.execute_reply":"2025-06-24T07:19:21.824532Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"将训练集和验证机的损失值和准确率通过曲线绘制出来，可以观测模型是否稳定，是否过拟合是否收敛等问题。","metadata":{}},{"cell_type":"code","source":"# 训练历史可视化\ndef plot_history(history):\n    plt.figure(figsize=(12, 5))\n    \n    plt.subplot(1, 2, 1)\n    plt.plot(history['train_loss'], label='Train Loss')\n    plt.plot(history['val_loss'], label='Val Loss')\n    plt.title('Loss Curves')\n    plt.xlabel('Epoch')\n    plt.ylabel('Loss')\n    plt.legend()\n    \n    plt.subplot(1, 2, 2)\n    plt.plot(history['train_acc'], label='Train Acc')\n    plt.plot(history['val_acc'], label='Val Acc')\n    plt.title('Accuracy Curves')\n    plt.xlabel('Epoch')\n    plt.ylabel('Accuracy')\n    plt.legend()\n    \n    plt.savefig(f\"{OUTPUT_DIR}/training_history.png\")\n    plt.show()\n\nplot_history(history)\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-24T07:19:21.826758Z","iopub.execute_input":"2025-06-24T07:19:21.827029Z","iopub.status.idle":"2025-06-24T07:19:22.311759Z","shell.execute_reply.started":"2025-06-24T07:19:21.826999Z","shell.execute_reply":"2025-06-24T07:19:22.311007Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 5 模型评估","metadata":{}},{"cell_type":"markdown","source":"接下来是模型的评估，通过计算验证集的一级指标，混淆矩阵，再次基础上计算二级指标，准确率、召回率、查全率等等，以及F1值、ROC曲线。","metadata":{}},{"cell_type":"code","source":"from sklearn.metrics import classification_report, confusion_matrix, roc_curve, auc, accuracy_score\nimport numpy as np\n\ndef evaluate_model(model, loader, class_names, model_name):\n    \"\"\"Evaluate the model and generate various metrics including per-class accuracy\"\"\"\n    model.eval()\n    all_labels = []\n    all_preds = []\n    all_probs = []\n    \n    with torch.no_grad():\n        for inputs, labels in loader:\n            inputs = inputs.to(device)\n            labels = labels.to(device)\n            \n            outputs = model(inputs)\n            _, preds = torch.max(outputs, 1)\n            probs = torch.nn.functional.softmax(outputs, dim=1)\n            \n            all_labels.extend(labels.cpu().numpy())\n            all_preds.extend(preds.cpu().numpy())\n            all_probs.extend(probs.cpu().numpy())\n    \n    # 计算每个类别单独的准确率（新增）\n    per_class_acc = {}\n    for cls in range(len(class_names)):\n        cls_labels = np.array(all_labels) == cls\n        cls_preds = np.array(all_preds) == cls\n        per_class_acc[class_names[cls]] = np.sum(cls_labels & cls_preds) / np.sum(cls_labels) if np.sum(cls_labels) != 0 else 0.0\n    \n    # 打印每个类别准确率（新增）\n    print(f\"\\n{model_name} Per-Class Accuracy:\")\n    for cls, acc in per_class_acc.items():\n        print(f\"  {cls}: {acc:.4f}\")\n    \n    # 生成包含准确率的分类报告（保留原有）\n    print(f\"\\n{model_name} Classification Report:\")\n    print(classification_report(all_labels, all_preds, target_names=class_names))\n    \n    # 绘制混淆矩阵（保留原有）\n    cm = confusion_matrix(all_labels, all_preds)\n    plt.figure(figsize=(10, 8))\n    sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', \n                xticklabels=class_names, yticklabels=class_names)\n    plt.title(f'{model_name} Confusion Matrix')\n    plt.xlabel('Predicted Label')\n    plt.ylabel('True Label')\n    plt.savefig(OUTPUT_DIR/f'{model_name}_confusion_matrix.png')\n    plt.show()\n    \n    # 计算AUC和绘制ROC曲线（保留原有）\n    y_test_bin = label_binarize(all_labels, classes=range(len(class_names)))\n    fpr = dict()\n    tpr = dict()\n    roc_auc = dict()\n    \n    for i in range(len(class_names)):\n        fpr[i], tpr[i], _ = roc_curve(y_test_bin[:, i], np.array(all_probs)[:, i])\n        roc_auc[i] = auc(fpr[i], tpr[i])\n    \n    plt.figure(figsize=(10, 8))\n    for i in range(len(class_names)):\n        plt.plot(fpr[i], tpr[i], label=f'{class_names[i]} (AUC = {roc_auc[i]:.2f})')\n    \n    plt.plot([0, 1], [0, 1], 'k--')\n    plt.xlim([0.0, 1.0])\n    plt.ylim([0.0, 1.05])\n    plt.xlabel('False Positive Rate')\n    plt.ylabel('True Positive Rate')\n    plt.title(f'{model_name} ROC Curves')\n    plt.legend(loc=\"lower right\")\n    plt.savefig(OUTPUT_DIR/f'{model_name}_roc_curve.png')\n    plt.show()\n    \n    return all_preds, all_probs\n\n# 评估自定义ResNet模型（调用方式不变）\nprint(\"\\nEvaluating Custom ResNet Model...\")\nresnet_val_preds, resnet_val_probs = evaluate_model(\n    model, val_loader, class_names, 'Custom ResNet'\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-24T07:19:22.312616Z","iopub.execute_input":"2025-06-24T07:19:22.312848Z","iopub.status.idle":"2025-06-24T07:19:29.458149Z","shell.execute_reply.started":"2025-06-24T07:19:22.312832Z","shell.execute_reply":"2025-06-24T07:19:29.457426Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"模型训练完成之后，可以对测试集进行预测，将预测的结果保存为submission，用于提交最后的结果。","metadata":{}},{"cell_type":"markdown","source":"## 6 结果预测","metadata":{}},{"cell_type":"code","source":"def predict_test_set(model, test_loader, class_names):\n    model.eval()\n    predictions = []\n    file_ids = []\n    \n    # 添加softmax计算置信度\n    confidences = []\n    \n    with torch.no_grad():\n        for inputs, filenames in tqdm(test_loader, desc=\"预测进度\"):\n            inputs = inputs.to(device)\n            outputs = model(inputs)\n            \n            # 获取预测结果和置信度\n            probs = torch.nn.functional.softmax(outputs, dim=1)\n            max_probs, preds = torch.max(probs, 1)\n            \n            predictions.extend(preds.cpu().numpy())\n            confidences.extend(max_probs.cpu().numpy())\n            \n            for f in filenames:\n                try:\n                    file_id = int(f.split('.')[0])\n                    file_ids.append(file_id)\n                except ValueError:\n                    # 记录错误文件名但不中断流程\n                    print(f\"警告: 无效文件名格式 - {f}\")\n\n    # 分析置信度分布\n    print(\"\\n预测置信度分析:\")\n    print(f\"平均置信度: {np.mean(confidences):.4f}\")\n    print(f\"最低置信度: {np.min(confidences):.4f}\")\n    print(f\"最高置信度: {np.max(confidences):.4f}\")\n    \n    plt.hist(confidences, bins=50)\n    plt.title(\"Distribution of Prediction Confidences\")\n    plt.xlabel(\"Confidence Score\")\n    plt.ylabel(\"Number of Samples\")\n    plt.show()\n    # 创建提交文件\n    idx_to_class = {i: cls for i, cls in enumerate(class_names)}\n    predicted_classes = [idx_to_class[p] for p in predictions]\n    \n    submission = pd.DataFrame({\n        'id': file_ids,\n        'label': predicted_classes\n    }).sort_values('id')\n    \n    # 检查ID范围\n    print(f\"\\nID范围: {submission['id'].min()} - {submission['id'].max()}\")\n    print(f\"总样本数: {len(submission)}\")\n    \n    return submission","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-24T07:19:29.459218Z","iopub.execute_input":"2025-06-24T07:19:29.459467Z","iopub.status.idle":"2025-06-24T07:19:29.466594Z","shell.execute_reply.started":"2025-06-24T07:19:29.459447Z","shell.execute_reply":"2025-06-24T07:19:29.465977Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 使用表现最好的模型进行预测\nprint(\"\\n预测测试集...\")#resnet_model，pretrained_model\nsubmission_df = predict_test_set(model,test_loader, class_names)\n\n# 保存提交文件\nsubmission_path = OUTPUT_DIR / 'submission.csv'\nsubmission_df.to_csv(submission_path, index=False)\nprint(f\"\\n提交文件已保存至: {submission_path}\")\n\n# 验证提交文件格式\nprint(\"\\n提交文件格式验证:\")\nprint(f\"行数: {len(submission_df)}\")\nprint(f\"列名: {submission_df.columns.tolist()}\")\nprint(f\"ID范围: {submission_df['id'].min()} 到 {submission_df['id'].max()}\")\nprint(f\"类别分布:\\n{submission_df['label'].value_counts()}\")\n\n# 检查ID是否连续\nexpected_ids = set(range(submission_df['id'].min(), submission_df['id'].max() + 1))\nactual_ids = set(submission_df['id'])\nmissing_ids = expected_ids - actual_ids\nif missing_ids:\n    print(f\"警告: 缺少 {len(missing_ids)} 个ID! 首尾缺少的ID: {sorted(missing_ids)[:5]}...{sorted(missing_ids)[-5:]}\")\nelse:\n    print(\"所有ID连续无缺失\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-24T07:19:29.467152Z","iopub.execute_input":"2025-06-24T07:19:29.467380Z","iopub.status.idle":"2025-06-24T07:22:06.748125Z","shell.execute_reply.started":"2025-06-24T07:19:29.467365Z","shell.execute_reply":"2025-06-24T07:22:06.747254Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-24T07:27:27.611440Z","iopub.execute_input":"2025-06-24T07:27:27.611717Z","iopub.status.idle":"2025-06-24T07:27:27.631879Z","shell.execute_reply.started":"2025-06-24T07:27:27.611698Z","shell.execute_reply":"2025-06-24T07:27:27.631058Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import seaborn as sns\nimport matplotlib.pyplot as plt\n\n# 创建 countplot\nplt.figure(figsize=(10, 6))\nax = sns.countplot(\n    data=submission_df, \n    x='label',  # 指定分类列\n    order=submission_df['label'].value_counts().index,  # 按频率排序\n    palette=\"viridis\"  # 设置颜色为 viridis\n)\n\n# 添加数值标签\nfor p in ax.patches:\n    ax.annotate(\n        f'{p.get_height()}',\n        (p.get_x() + p.get_width() / 2, p.get_height()),\n        ha='center', va='baseline', fontsize=10, xytext=(0, 5),\n        textcoords='offset points'\n    )\n\n# 美化图表（改为英文）\nplt.title('Label Frequency Distribution', fontsize=14)\nplt.xlabel('Class Label', fontsize=12)\nplt.ylabel('Occurrence Frequency', fontsize=12)\nplt.xticks(rotation=15)  # 旋转标签防止重叠\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-24T07:42:19.113700Z","iopub.execute_input":"2025-06-24T07:42:19.114532Z","iopub.status.idle":"2025-06-24T07:42:19.485042Z","shell.execute_reply.started":"2025-06-24T07:42:19.114504Z","shell.execute_reply":"2025-06-24T07:42:19.484185Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}