diff --git a/examples/community/homomorphic_inference/BestCheckpoint/resnet20-best-609.ckpt b/examples/community/homomorphic_inference/BestCheckpoint/resnet20-best-609.ckpt new file mode 100644 index 0000000000000000000000000000000000000000..7051b91e917b826506b87711c7a327a0842d8e18 Binary files /dev/null and b/examples/community/homomorphic_inference/BestCheckpoint/resnet20-best-609.ckpt differ diff --git a/examples/community/homomorphic_inference/BestCheckpoint/resnet20-best-804.ckpt b/examples/community/homomorphic_inference/BestCheckpoint/resnet20-best-804.ckpt new file mode 100644 index 0000000000000000000000000000000000000000..e3abfd1fb60da0ad3a6a58fdedd3abb734bad4a0 Binary files /dev/null and b/examples/community/homomorphic_inference/BestCheckpoint/resnet20-best-804.ckpt differ diff --git a/examples/community/homomorphic_inference/BestCheckpoint/resnet20-best-834.ckpt b/examples/community/homomorphic_inference/BestCheckpoint/resnet20-best-834.ckpt new file mode 100644 index 0000000000000000000000000000000000000000..f6e0aca53f8a07bc6510f1b81e95933b0eb343c8 Binary files /dev/null and b/examples/community/homomorphic_inference/BestCheckpoint/resnet20-best-834.ckpt differ diff --git a/examples/community/homomorphic_inference/BestCheckpoint/resnet50-best.ckpt b/examples/community/homomorphic_inference/BestCheckpoint/resnet50-best.ckpt new file mode 100644 index 0000000000000000000000000000000000000000..e0e7ab4b3ade3d463dc40094ec77389fe77cdb88 Binary files /dev/null and b/examples/community/homomorphic_inference/BestCheckpoint/resnet50-best.ckpt differ diff --git a/examples/community/homomorphic_inference/README.md b/examples/community/homomorphic_inference/README.md new file mode 100644 index 0000000000000000000000000000000000000000..e8cb13be7f28d2b0f714110fdf3cf13ef94a9942 --- /dev/null +++ b/examples/community/homomorphic_inference/README.md @@ -0,0 +1,29 @@ +# 基于全同态加密的隐私推理 +本项目基于mindspore框架进行多项式残差网络训练,所训练多项式模型用于CKKS密文推理。基于CKKS的密文推理框架来自README中的原型论文,开源库地址:https://github.com/snu-ccl/FHE-MP-CNN + +## 原型论文 + +Eunsang Lee, Joon-Woo Lee, Junghyun Lee, Young-Sik Kim, Yongjune Kim, JongSeon No, and Woosuk Choi. Low-complexity deep convolutional neural networks on fully homomorphic encryption using multiplexed parallel convolutions. In International Conference on Machine Learning, pages 12403–12422. PMLR, 2022. +## 环境要求 + +Mindspore >= 1.9 + + +## 脚本说明 + +```markdown +├── README.md +├── BestCheckpoint //训练后模型 +├── data //数据集 +├── datasets-cifar10-bin //数据集 +├── mindspore_poly +│ ├── coeffResult //预训练多项式系数 +│ ├── degreeResult //预训练多项式阶数 +│ ├── model +│ │ ├── resnet_cifar10.py //多项式网络定义 +│ │ └── utils_approx.py //多项式计算函数 +│ └── train_resnet20_cifar10.py //模型训练 +├── mindspore_resnet20.ipynb //mindspore框架下标准网络训练 +├── mindspore_resnet20.py //mindspore框架下标准网络训练 +└── pytorch_resnet20.py //pytorch框架下标准网络训练 +``` diff --git a/examples/community/homomorphic_inference/data/keep b/examples/community/homomorphic_inference/data/keep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/examples/community/homomorphic_inference/datasets-cifar10-bin/cifar-10-batches-bin/keep b/examples/community/homomorphic_inference/datasets-cifar10-bin/cifar-10-batches-bin/keep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_10.txt b/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_10.txt new file mode 100644 index 0000000000000000000000000000000000000000..616865dd568eef836d880ce8fc765c3c91393aad --- /dev/null +++ b/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_10.txt @@ -0,0 +1,30 @@ +-0.16804881224859701754e-46 +10.854184257744249796 +0.51921340560426110074e-45 +-62.28339252110988471 +-0.16735871500743852985e-44 +114.36922782044335657 +0.11543707669236311121e-44 +-62.80234969730743326 +0.78625356248397092829e-38 +4.1397617098511193064 +-0.71824174164994055658e-37 +-5.8499764021167980684 +0.51787863444278289581e-37 +2.9437625565928026676 +-0.93305974396004940898e-38 +-0.45453043746015205795 +0.37537415358329215971e-38 +3.2995673904373325138 +-0.10453714002088916108e-36 +-7.8422726029135582971 +0.41864789598423106147e-36 +12.890776411556469438 +-0.60951015954085533252e-36 +-12.491711258448624139 +0.40547544124712444853e-36 +6.9416799142807483492 +-0.12677008781584870523e-36 +-2.0429806739994296629 +0.15245219740063652538e-37 +0.24640713892603125782 diff --git a/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_11.txt b/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_11.txt new file mode 100644 index 0000000000000000000000000000000000000000..2c241242595ef908aa331f7af0143e78dbfacac7 --- /dev/null +++ b/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_11.txt @@ -0,0 +1,44 @@ +-0.75773973997740627597e-30 +11.259066740295440722 +0.35029873643910904994e-28 +-65.469293332997393075 +-0.11093952997692154569e-27 +120.69463427775764571 +0.75910235759400087266e-28 +-66.40196953778250699 +0.6707459348521799376e-48 +4.704776242108835538 +-0.35608959461554233042e-47 +-6.7988485159668168237 +0.14359564606992449774e-47 +3.3152510438287307671 +-0.14985342138579284032e-48 +-0.48936293685989724605 +-0.37246666638164325883e-45 +5.3633425765495343159 +0.43873256885377774404e-43 +-35.51695554419623341 +-0.72755862709513560355e-42 +177.80730411564418744 +0.46756335314744358534e-41 +-592.29739541502495217 +-0.1559191149483180791e-40 +1348.9169188936272623 +0.3101987517898984534e-40 +-2158.7644508493821289 +-0.39795227464460684802e-40 +2473.6568558691888017 +0.34425836717395760687e-40 +-2049.1354253624894092 +-0.20544736179116039936e-40 +1227.3931709055906409 +0.84969444743862631574e-41 +-525.82617513400260298 +-0.23985636500344662033e-41 +156.93055871284079877 +0.44222346859769547418e-42 +-30.965859564591263308 +-0.48133142981936186693e-43 +3.6289400081496844467 +0.23518137057881136703e-44 +-0.1911602837499393819 diff --git a/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_12.txt b/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_12.txt new file mode 100644 index 0000000000000000000000000000000000000000..0227ed2a13b0c0ade44d1c7e086c61affae31544 --- /dev/null +++ b/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_12.txt @@ -0,0 +1,52 @@ +0.32867125379815857291e-44 +11.552304235722389133 +-0.29234255228681799398e-42 +-67.779451344096825362 +0.933659553243619424e-42 +125.28374040456207308 +-0.64131951207618758946e-42 +-69.014290823293429674 +0.64108738894863390065e-45 +9.6516763618162694555 +-0.12282232950603783088e-42 +-61.693917453846963018 +0.62062456634083572074e-42 +155.17035165229809557 +-0.996218491919333066e-42 +-182.69758238321474774 +0.72774896827061079047e-42 +112.91072652540604621 +-0.26913492484561434183e-42 +-37.775241177026371927 +0.49323508874283516076e-43 +6.475039097323445253 +-0.35687682645890630524e-44 +-0.44561336572336147215 +0.47771057631279131586e-46 +5.2588835557174597154 +-0.29457192143837593098e-44 +-33.723359379428401924 +0.44327940113287919025e-43 +164.98308501345742927 +-0.27686398551955233933e-42 +-541.40889140699277564 +0.91518100299426399978e-42 +1222.9620799796357356 +-0.18207112804794013148e-41 +-1952.0191056647939988 +0.23485275845578151281e-41 +2240.8402137830087186 +-0.20516930050320509213e-41 +-1866.3491698317060853 +0.12415752822080049282e-41 +1127.2211784312163129 +-0.52285077977730429736e-42 +-488.07047463838013451 +0.15092324971381413086e-42 +147.49784630892093329 +-0.28576304536464352374e-43 +-29.517104887952670173 +0.32076854965476087405e-44 +3.5126952093099453428 +-0.16226398549339504967e-45 +-0.1881018365578797706 diff --git a/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_13.txt b/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_13.txt new file mode 100644 index 0000000000000000000000000000000000000000..43985eb99d0232b81ce9f8a9b0a585eeff315ed6 --- /dev/null +++ b/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_13.txt @@ -0,0 +1,60 @@ +0.13459576929391090569e-32 +24.558941542500461187 +0.48509566723824261626e-31 +-669.66044971689436801 +-0.24454123585384020859e-29 +6672.9984830133931554 +0.18687481194464005187e-28 +-30603.665616389872425 +-0.5762278175772426705e-28 +73188.403298778778129 +0.85368067300925938918e-28 +-94443.321705008449291 +-0.60270147469466762691e-28 +62325.409421254674884 +0.16234284366194031353e-28 +-16494.674411780599848 +0.15326158858563023363e-46 +9.3562563603543978083 +-0.36897212304824964462e-45 +-59.163896393362639749 +0.17425439970330368218e-44 +148.86093062644842385 +-0.32067211000221387429e-44 +-175.8128748785829444 +0.27911573894864588724e-44 +109.11129968595543035 +-0.12259030930610072562e-44 +-36.676883997875556573 +0.26218914255796237778e-45 +6.3184629031129413078 +-0.21666232642127535753e-46 +-0.43711341508217764519 +0.6435519383199838375e-47 +5.078135697588612878 +0.81260103885576212533e-45 +-30.732991813718681529 +-0.16019847467842701065e-43 +144.10974681280942417 +0.10746315446051181804e-42 +-459.66168882614256179 +-0.36344872304451237262e-42 +1021.520644704596761 +0.72520712536978486691e-42 +-1620.5625670887702504 +-0.92730639785365506188e-42 +1864.6764641657026581 +0.79584309735406509106e-42 +-1567.4930087714349494 +-0.46919010314752753297e-42 +960.9703090934222369 +0.19086334965401618657e-42 +-424.32616187164667827 +-0.52743967802069637614e-43 +131.27850925600366538 +0.94704493797478696798e-44 +-26.9812576626115819 +-0.99818156176375019347e-45 +3.3065138731556502914 +0.46939046619219983164e-46 +-0.18274294462753398785 diff --git a/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_14.txt b/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_14.txt new file mode 100644 index 0000000000000000000000000000000000000000..bd64f92a50d2ef058f977bb2d86d4ff7adb9b5b3 --- /dev/null +++ b/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_14.txt @@ -0,0 +1,74 @@ +-0.33857228343349222771e-46 +24.905214319375455558 +0.76706429670786537935e-44 +-682.38305758243000329 +-0.13331852725885950186e-42 +6809.4284539059995199 +0.91946456800204317828e-42 +-31250.710001710598132 +-0.3025478830899497108e-41 +74765.938836375718651 +0.50242602757177002413e-41 +-96504.683847583939449 +-0.40593124032144374049e-41 +63697.792377824620844 +0.12667142782789733501e-41 +-16860.262134719013661 +-0.9279917569679915957e-45 +16.828551192601130179 +0.83240811468667181169e-43 +-339.81175049565943051 +-0.12775656662581185272e-41 +2790.6999879384769844 +0.77015283672913125341e-41 +-11351.415157379078 +-0.24115991880599060476e-40 +26623.001028374524701 +0.44880705621387405238e-40 +-39384.032866197584799 +-0.53482162297220285674e-40 +38788.423034806038289 +0.42572250279855945685e-40 +-26239.530384498866697 +-0.23114662426334729952e-40 +12365.620701653234636 +0.85857146353371829812e-41 +-4053.3646008999918361 +-0.21456494030125522953e-41 +906.04288095108740556 +0.34480336789999280563e-42 +-131.68764920828800121 +-0.32171705933660227767e-43 +11.2176079033623702 +0.13242560040344363915e-44 +-0.42493802046747120845 +0.67287496871653099859e-47 +5.3175549768939158259 +0.56819927580108680432e-45 +-35.437153153157791187 +-0.1351878131554547236e-43 +184.12244132914048722 +0.10553176628958931735e-42 +-655.38683014625320828 +-0.41426651887176019808e-42 +1638.7833542806082222 +0.96309736116631665994e-42 +-2953.8623704822645685 +-0.14455668840936067085e-41 +3908.064233624186516 +0.14726501386448502925e-41 +-3834.9673916513135076 +-0.1047282511696152942e-41 +2799.6065476651709549 +0.5261087287862766673e-42 +-1512.862318866923807 +-0.1860839022225468362e-42 +596.16013934000983037 +0.45364411019946816092e-43 +-166.32173930295850321 +-0.72578228765531381604e-44 +31.098836973988486605 +0.68580052063448574557e-45 +-3.4934937450619072513 +-0.28984981120663795748e-46 +0.17814215695649592896 diff --git a/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_4.txt b/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_4.txt new file mode 100644 index 0000000000000000000000000000000000000000..35f74eb975ea0d5bfa2f14b71515292db8adce3f --- /dev/null +++ b/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_4.txt @@ -0,0 +1,6 @@ +-0.13308328571661910614e-45 +1.7030952550152622128 +0.4282303400683935347e-45 +-0.7119179308972252429 +-0.95007671461412588685e-46 +0.10588838088858641527 diff --git a/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_5.txt b/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_5.txt new file mode 100644 index 0000000000000000000000000000000000000000..b48dbd5cdf0bad09c2ceeb781bfd2d5254abb7f7 --- /dev/null +++ b/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_5.txt @@ -0,0 +1,14 @@ +-0.6557526550356444622e-47 +3.8161318912761891715 +0.16383497014687546643e-45 +-9.5345775055636790739 +-0.56775355546317866411e-45 +13.127053865564633214 +0.69572170343273392104e-45 +-9.2492590908629339246 +-0.3754881050914470711e-45 +3.4159401718320714855 +0.91355542332979866396e-46 +-0.62965100676194727464 +-0.81802023536366356411e-47 +0.045667675284501014142 diff --git a/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_6.txt b/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_6.txt new file mode 100644 index 0000000000000000000000000000000000000000..be7505497ba5e4a7c101d730786bd7b5958c1891 --- /dev/null +++ b/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_6.txt @@ -0,0 +1,12 @@ +0.93844775458124077663e-48 +3.5011230698579893093 +0.22777858120903902345e-48 +-2.9653846348507701015 +0.43185878462241557434e-46 +2.5008586578047593635 +-0.2840296669129957945e-45 +-2.835698539325111711 +0.30424586148848378787e-45 +1.6508795809820303949 +-0.81872372642115644225e-46 +-0.3394796746275417859 diff --git a/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_7.txt b/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_7.txt new file mode 100644 index 0000000000000000000000000000000000000000..e374a801651781a5ad4ff73faec0ace224dbe6f4 --- /dev/null +++ b/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_7.txt @@ -0,0 +1,16 @@ +0.36047157227556087246e-35 +7.3044516495825141119 +-0.50547170420272221469e-34 +-34.682587110865950932 +0.11656466540909508014e-33 +59.859651829882618102 +-0.65429849283953137725e-34 +-31.875522590646616717 +-0.94649140234426094697e-48 +2.4008565221759781728 +0.64174463272534238015e-47 +-2.631254542617839592 +-0.72533856467681473674e-47 +1.5491267477359321766 +0.20691646642181211267e-47 +-0.33117295650430441081 diff --git a/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_8.txt b/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_8.txt new file mode 100644 index 0000000000000000000000000000000000000000..a02fdeaa8a3afe3e4807336f7a6fc7cb9d7e1cf6 --- /dev/null +++ b/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_8.txt @@ -0,0 +1,24 @@ +-0.53048975658957883424e-47 +8.8313307202241685663 +0.21584189100655274133e-45 +-46.457503989551297435 +-0.65893788882613692879e-45 +83.028223472040820362 +0.44320580215223931131e-45 +-44.992847782807097824 +-0.33945728644711295237e-31 +3.9488188508326321993 +0.87774430828590316545e-30 +-12.910301099228299329 +-0.37335685270661501373e-29 +28.086536217465829971 +0.55927380858844763427e-29 +-35.596914896513755454 +-0.33696337530707381012e-29 +26.515937088133732328 +0.5368136791487787625e-30 +-11.418488936844970829 +0.19108101768442763358e-30 +2.6255844388133481275 +-0.55068698294223090738e-31 +-0.24917229999864296671 diff --git a/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_9.txt b/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_9.txt new file mode 100644 index 0000000000000000000000000000000000000000..5f9d660e351d356c9226ca3b3e6b2b927f35928f --- /dev/null +++ b/examples/community/homomorphic_inference/mindspore_poly/coeffResult/coeff_9.txt @@ -0,0 +1,32 @@ +0.38516974123418350064e-43 +18.096628571880738457 +-0.45973041691637794167e-41 +-434.03870327488615935 +0.79629916037569032555e-40 +4154.9710354569631624 +-0.52897711039631686342e-39 +-18684.694361314924706 +0.16721955114891783141e-38 +44165.71778893298258 +-0.26977742479850633046e-38 +-56552.792898340197704 +0.21412459138356943247e-38 +37115.61227257818608 +-0.66172245592719822948e-39 +-9782.4193389278198711 +-0.10450107406385446313e-45 +3.7975332336085665816 +0.42284220981801636912e-44 +-11.77181577711924821 +-0.22557111393663989842e-43 +24.977108667834693744 +0.44246287510686210342e-43 +-31.523884160399347606 +-0.41355419441164577394e-43 +23.729486312672242226 +0.20006015878309407409e-43 +-10.433180019592321979 +-0.48604113271279609616e-44 +2.4674397626083860086 +0.47125621405204993786e-45 +-0.24213010024761749988 diff --git a/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_10.txt b/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_10.txt new file mode 100644 index 0000000000000000000000000000000000000000..4fd8abf006dd2653ee9a35d4be74acedfb1df62e --- /dev/null +++ b/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_10.txt @@ -0,0 +1,3 @@ +7 +7 +13 diff --git a/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_11.txt b/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_11.txt new file mode 100644 index 0000000000000000000000000000000000000000..54bbd27b921974172038afb2b08e9b99fc3606dc --- /dev/null +++ b/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_11.txt @@ -0,0 +1,3 @@ +7 +7 +27 diff --git a/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_12.txt b/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_12.txt new file mode 100644 index 0000000000000000000000000000000000000000..5dffe35a3300b8209e5e231447c9dcc9ded3e6af --- /dev/null +++ b/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_12.txt @@ -0,0 +1,3 @@ +7 +15 +27 diff --git a/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_13.txt b/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_13.txt new file mode 100644 index 0000000000000000000000000000000000000000..9f379df03a1c3068230833a5785d4edad120ae54 --- /dev/null +++ b/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_13.txt @@ -0,0 +1,3 @@ +15 +15 +27 diff --git a/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_14.txt b/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_14.txt new file mode 100644 index 0000000000000000000000000000000000000000..5237cd51c64383682452535557fc8133503ea18e --- /dev/null +++ b/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_14.txt @@ -0,0 +1,3 @@ +15 +27 +29 diff --git a/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_4.txt b/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_4.txt new file mode 100644 index 0000000000000000000000000000000000000000..7ed6ff82de6bcc2a78243fc9c54d3ef5ac14da69 --- /dev/null +++ b/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_4.txt @@ -0,0 +1 @@ +5 diff --git a/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_5.txt b/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_5.txt new file mode 100644 index 0000000000000000000000000000000000000000..b1bd38b62a0800a4f6a80c34e21c5acffae52c7e --- /dev/null +++ b/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_5.txt @@ -0,0 +1 @@ +13 diff --git a/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_6.txt b/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_6.txt new file mode 100644 index 0000000000000000000000000000000000000000..dde5d5d0173bff70bf273c1450c977fdfc17d982 --- /dev/null +++ b/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_6.txt @@ -0,0 +1,2 @@ +3 +7 diff --git a/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_7.txt b/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_7.txt new file mode 100644 index 0000000000000000000000000000000000000000..49019db807899bc5793047943ce0fbb1a09b2e14 --- /dev/null +++ b/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_7.txt @@ -0,0 +1,2 @@ +7 +7 diff --git a/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_8.txt b/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_8.txt new file mode 100644 index 0000000000000000000000000000000000000000..12b65161666836e4cdaf4dba95501c9eb184aa12 --- /dev/null +++ b/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_8.txt @@ -0,0 +1,2 @@ +7 +15 diff --git a/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_9.txt b/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_9.txt new file mode 100644 index 0000000000000000000000000000000000000000..d2de525e8598cc86ffc221bd5689677d5fc94b7f --- /dev/null +++ b/examples/community/homomorphic_inference/mindspore_poly/degreeResult/deg_9.txt @@ -0,0 +1,2 @@ +15 +15 diff --git a/examples/community/homomorphic_inference/mindspore_poly/train_resnet20_cifar10.py b/examples/community/homomorphic_inference/mindspore_poly/train_resnet20_cifar10.py new file mode 100644 index 0000000000000000000000000000000000000000..122c6c2ea7e9cd4657689fcf6b3d6a8c7f0e522c --- /dev/null +++ b/examples/community/homomorphic_inference/mindspore_poly/train_resnet20_cifar10.py @@ -0,0 +1,202 @@ +from __future__ import print_function +from tqdm import * + +import sys +import argparse +import mindspore +import mindspore.nn as nn + + +from model.resnet_cifar10 import * +from model.utils_approx import rangeException + +import mindspore as ms +import mindspore.dataset as ds +import mindspore.dataset.vision as vision +import mindspore.dataset.transforms as transforms +from mindspore import dtype as mstype +parser = argparse.ArgumentParser(description='Implementation of of Section V-A for `Precise Approximation of Convolutional Neural' + + 'Networks for Homomorphically Encrypted Data.`') +parser.add_argument('--approx_method', default='proposed', dest='approx_method', type=str, + help='Method of approximating non-arithmetic operations. `proposed`: proposed composition of minimax polynomials, '\ + '`square`: approximate ReLU as x^2, `relu_aq`: approximate ReLU as 2^-3*x^2+2^-1*x+2^-2. '\ + 'For `square` and `relu_aq`, we use exact max-pooling function.') + +parser.add_argument('--alpha', default=14, dest='alpha', type=int, + help='The precision parameter. Integers from 4 to 14 can be used.') +parser.add_argument('--B_relu', default=50.0, dest='B_relu', type=float, + help='The bound of approximation range for the approximate ReLU function.') +parser.add_argument('--B_max', default=50.0, dest='B_max', type=float, + help='The bound of approximation range for the approximate max-pooling function.') +args = parser.parse_args() + +data_dir = "../datasets-cifar10-bin/cifar-10-batches-bin" # 数据集根目录 +batch_size = 256 # 批量大小 +image_size = 32 # 训练图像空间大小 +workers = 4 # 并行线程个数 +num_classes = 10 # 分类数量 + + +def create_dataset_cifar10(dataset_dir, usage, resize, batch_size, workers): + + data_set = ds.Cifar10Dataset(dataset_dir=dataset_dir, + usage=usage, + num_parallel_workers=workers, + shuffle=True) + + trans = [] + if usage == "train": + trans += [ + vision.RandomCrop((32, 32), (4, 4, 4, 4)), + vision.RandomHorizontalFlip(prob=0.5) + ] + + trans += [ + vision.Resize(resize), + vision.Rescale(1.0 / 255.0, 0.0), + vision.Normalize([0.4914, 0.4822, 0.4465], [0.2023, 0.1994, 0.2010]), + vision.HWC2CHW() + ] + + target_trans = transforms.TypeCast(mstype.int32) + + # 数据映射操作 + data_set = data_set.map(operations=trans, + input_columns='image', + num_parallel_workers=workers) + + data_set = data_set.map(operations=target_trans, + input_columns='label', + num_parallel_workers=workers) + + # 批量操作 + data_set = data_set.batch(batch_size) + + return data_set + + +# 获取处理后的训练与测试数据集 + +dataset_train = create_dataset_cifar10(dataset_dir=data_dir, + usage="train", + resize=image_size, + batch_size=batch_size, + workers=workers) +step_size_train = dataset_train.get_dataset_size() + +dataset_val = create_dataset_cifar10(dataset_dir=data_dir, + usage="test", + resize=image_size, + batch_size=batch_size, + workers=workers) +step_size_val = dataset_val.get_dataset_size() + +approx_dict_list = [{'alpha': args.alpha, 'B': args.B_relu, 'type': args.approx_method}, + {'alpha': args.alpha, 'B': args.B_max, 'type': args.approx_method}] +# 定义ResNet20网络 +network = resnet20(pretrained=False,approx_param_dict_list=approx_dict_list) +print(network) +# 全连接层输入层的大小 +in_channel = network.fc.in_channels +fc = nn.Dense(in_channels=in_channel, out_channels=10) +# 重置全连接层 +network.fc = fc + +# 设置学习率 +num_epochs = 50 +lr = nn.cosine_decay_lr(min_lr=0.00001, max_lr=0.001, total_step=step_size_train * num_epochs, + step_per_epoch=step_size_train, decay_epoch=num_epochs) +# 定义优化器和损失函数 +opt = nn.Momentum(params=network.trainable_params(), learning_rate=lr, momentum=0.9) +loss_fn = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction='mean') + + +def forward_fn(inputs, targets): + logits = network(inputs) + loss = loss_fn(logits, targets) + return loss + + +grad_fn = ms.value_and_grad(forward_fn, None, opt.parameters) + + +def train_step(inputs, targets): + # print(inputs.shape) + # print(targets.shape) + loss, grads = grad_fn(inputs, targets) + opt(grads) + return loss + +import os + +# 创建迭代器 +data_loader_train = dataset_train.create_tuple_iterator(num_epochs=num_epochs) +data_loader_val = dataset_val.create_tuple_iterator(num_epochs=num_epochs) + +# 最佳模型存储路径 +best_acc = 0 +best_ckpt_dir = "../BestCheckpoint" +best_ckpt_path = "../BestCheckpoint/resnet20-best-poly.ckpt" + +if not os.path.exists(best_ckpt_dir): + os.mkdir(best_ckpt_dir) + +import mindspore.ops as ops + + +def train(data_loader, epoch): + """模型训练""" + losses = [] + network.set_train(True) + + for i, (images, labels) in enumerate(data_loader): + # print(images.shape) + # print(labels.shape) + loss = train_step(images, labels) + if i % 100 == 0 or i == step_size_train - 1: + print('Epoch: [%3d/%3d], Steps: [%3d/%3d], Train Loss: [%5.3f]' % + (epoch + 1, num_epochs, i + 1, step_size_train, loss)) + losses.append(loss) + + return sum(losses) / len(losses) + + +def evaluate(data_loader): + """模型验证""" + network.set_train(False) + + correct_num = 0.0 # 预测正确个数 + total_num = 0.0 # 预测总数 + + for images, labels in data_loader: + logits = network(images) + pred = logits.argmax(axis=1) # 预测结果 + correct = ops.equal(pred, labels).reshape((-1, )) + correct_num += correct.sum().asnumpy() + total_num += correct.shape[0] + + acc = correct_num / total_num # 准确率 + + return acc + +# 开始循环训练 +print("Start Training Loop ...") + +for epoch in range(num_epochs): + curr_loss = train(data_loader_train, epoch) + curr_acc = evaluate(data_loader_val) + + print("-" * 50) + print("Epoch: [%3d/%3d], Average Train Loss: [%5.3f], Accuracy: [%5.3f]" % ( + epoch+1, num_epochs, curr_loss, curr_acc + )) + print("-" * 50) + + # 保存当前预测准确率最高的模型 + if curr_acc > best_acc: + best_acc = curr_acc + ms.save_checkpoint(network, best_ckpt_path) + +print("=" * 80) +print(f"End of validation the best Accuracy is: {best_acc: 5.3f}, " + f"save the best ckpt file in {best_ckpt_path}", flush=True) \ No newline at end of file diff --git a/examples/community/homomorphic_inference/mindspore_quick_start.ipynb b/examples/community/homomorphic_inference/mindspore_quick_start.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..0aa775aaa411f1a6af04460ab54d09b8ad059c7b --- /dev/null +++ b/examples/community/homomorphic_inference/mindspore_quick_start.ipynb @@ -0,0 +1,589 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[![下载Notebook](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/master/resource/_static/logo_notebook.svg)](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/master/tutorials/zh_cn/beginner/mindspore_quick_start.ipynb) [![下载样例代码](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/master/resource/_static/logo_download_code.svg)](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/master/tutorials/zh_cn/beginner/mindspore_quick_start.py) [![查看源文件](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/master/resource/_static/logo_source.svg)](https://gitee.com/mindspore/docs/blob/master/tutorials/source_zh_cn/beginner/quick_start.ipynb)\n", + "\n", + "[基本介绍](https://www.mindspore.cn/tutorials/zh-CN/master/beginner/introduction.html) || **快速入门** || [张量 Tensor](https://www.mindspore.cn/tutorials/zh-CN/master/beginner/tensor.html) || [数据加载与处理](https://www.mindspore.cn/tutorials/zh-CN/master/beginner/dataset.html) || [网络构建](https://www.mindspore.cn/tutorials/zh-CN/master/beginner/model.html) || [函数式自动微分](https://www.mindspore.cn/tutorials/zh-CN/master/beginner/autograd.html) || [模型训练](https://www.mindspore.cn/tutorials/zh-CN/master/beginner/train.html) || [保存与加载](https://www.mindspore.cn/tutorials/zh-CN/master/beginner/save_load.html) || [使用静态图加速](https://www.mindspore.cn/tutorials/zh-CN/master/beginner/accelerate_with_static_graph.html) || [自动混合精度](https://www.mindspore.cn/tutorials/zh-CN/master/beginner/mixed_precision.html) ||" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 快速入门\n", + "\n", + "本节通过MindSpore的API来快速实现一个简单的深度学习模型。若想要深入了解MindSpore的使用方法,请参阅各节最后提供的参考链接。" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import mindspore\n", + "from mindspore import nn\n", + "from mindspore.dataset import vision, transforms\n", + "from mindspore.dataset import MnistDataset" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 处理数据集\n", + "\n", + "MindSpore提供基于Pipeline的[数据引擎](https://www.mindspore.cn/docs/zh-CN/master/design/data_engine.html),通过[数据集(Dataset)](https://www.mindspore.cn/tutorials/zh-CN/master/beginner/dataset.html)实现高效的数据预处理。在本教程中,我们使用Mnist数据集,自动下载完成后,使用`mindspore.dataset`提供的数据变换进行预处理。\n", + "\n", + "> 本章节中的示例代码依赖`download`,可使用命令`pip install download`安装。如本文档以Notebook运行时,完成安装后需要重启kernel才能执行后续代码。" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Downloading data from https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/MNIST_Data.zip (10.3 MB)\n", + "\n", + "file_sizes: 100%|██████████████████████████| 10.8M/10.8M [00:01<00:00, 6.73MB/s]\n", + "Extracting zip file...\n", + "Successfully downloaded / unzipped to ./\n" + ] + } + ], + "source": [ + "# Download data from open datasets\n", + "from download import download\n", + "\n", + "url = \"https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/\" \\\n", + " \"notebook/datasets/MNIST_Data.zip\"\n", + "path = download(url, \"./\", kind=\"zip\", replace=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "MNIST数据集目录结构如下:\n", + "\n", + "```text\n", + "MNIST_Data\n", + "└── train\n", + " ├── train-images-idx3-ubyte (60000个训练图片)\n", + " ├── train-labels-idx1-ubyte (60000个训练标签)\n", + "└── test\n", + " ├── t10k-images-idx3-ubyte (10000个测试图片)\n", + " ├── t10k-labels-idx1-ubyte (10000个测试标签)\n", + "\n", + "```\n", + "\n", + "数据下载完成后,获得数据集对象。" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "train_dataset = MnistDataset('MNIST_Data/train')\n", + "test_dataset = MnistDataset('MNIST_Data/test')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "打印数据集中包含的数据列名,用于dataset的预处理。" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['image', 'label']\n" + ] + } + ], + "source": [ + "print(train_dataset.get_col_names())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "MindSpore的dataset使用数据处理流水线(Data Processing Pipeline),需指定map、batch、shuffle等操作。这里我们使用map对图像数据及标签进行变换处理,将输入的图像缩放为1/255,根据均值0.1307和标准差值0.3081进行归一化处理,然后将处理好的数据集打包为大小为64的batch。" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "def datapipe(dataset, batch_size):\n", + " image_transforms = [\n", + " vision.Rescale(1.0 / 255.0, 0),\n", + " vision.Normalize(mean=(0.1307,), std=(0.3081,)),\n", + " vision.HWC2CHW()\n", + " ]\n", + " label_transform = transforms.TypeCast(mindspore.int32)\n", + "\n", + " dataset = dataset.map(image_transforms, 'image')\n", + " dataset = dataset.map(label_transform, 'label')\n", + " dataset = dataset.batch(batch_size)\n", + " return dataset" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "# Map vision transforms and batch dataset\n", + "train_dataset = datapipe(train_dataset, 64)\n", + "test_dataset = datapipe(test_dataset, 64)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "可使用[create_tuple_iterator](https://www.mindspore.cn/docs/zh-CN/master/api_python/dataset/dataset_method/iterator/mindspore.dataset.Dataset.create_tuple_iterator.html) 或[create_dict_iterator](https://www.mindspore.cn/docs/zh-CN/master/api_python/dataset/dataset_method/iterator/mindspore.dataset.Dataset.create_dict_iterator.html)对数据集进行迭代访问,查看数据和标签的shape和datatype。" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Shape of image [N, C, H, W]: (64, 1, 28, 28) Float32\n", + "Shape of label: (64,) Int32\n" + ] + } + ], + "source": [ + "for image, label in test_dataset.create_tuple_iterator():\n", + " print(f\"Shape of image [N, C, H, W]: {image.shape} {image.dtype}\")\n", + " print(f\"Shape of label: {label.shape} {label.dtype}\")\n", + " break" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Shape of image [N, C, H, W]: (64, 1, 28, 28) Float32\n", + "Shape of label: (64,) Int32\n" + ] + } + ], + "source": [ + "for data in test_dataset.create_dict_iterator():\n", + " print(f\"Shape of image [N, C, H, W]: {data['image'].shape} {data['image'].dtype}\")\n", + " print(f\"Shape of label: {data['label'].shape} {data['label'].dtype}\")\n", + " break" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "更多细节详见[数据加载与处理](https://www.mindspore.cn/tutorials/zh-CN/master/beginner/dataset.html)。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 网络构建\n", + "\n", + "`mindspore.nn`类是构建所有网络的基类,也是网络的基本单元。当用户需要自定义网络时,可以继承`nn.Cell`类,并重写`__init__`方法和`construct`方法。`__init__`包含所有网络层的定义,`construct`中包含数据([Tensor](https://www.mindspore.cn/tutorials/zh-CN/master/beginner/tensor.html))的变换过程。" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Network<\n", + " (flatten): Flatten<>\n", + " (dense_relu_sequential): SequentialCell<\n", + " (0): Dense\n", + " (1): ReLU<>\n", + " (2): Dense\n", + " (3): ReLU<>\n", + " (4): Dense\n", + " >\n", + " >\n" + ] + } + ], + "source": [ + "# Define model\n", + "class Network(nn.Cell):\n", + " def __init__(self):\n", + " super().__init__()\n", + " self.flatten = nn.Flatten()\n", + " self.dense_relu_sequential = nn.SequentialCell(\n", + " nn.Dense(28*28, 512),\n", + " nn.ReLU(),\n", + " nn.Dense(512, 512),\n", + " nn.ReLU(),\n", + " nn.Dense(512, 10)\n", + " )\n", + "\n", + " def construct(self, x):\n", + " x = self.flatten(x)\n", + " logits = self.dense_relu_sequential(x)\n", + " return logits\n", + "\n", + "model = Network()\n", + "print(model)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "更多细节详见[网络构建](https://www.mindspore.cn/tutorials/zh-CN/master/beginner/model.html)。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 模型训练" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在模型训练中,一个完整的训练过程(step)需要实现以下三步:\n", + "\n", + "1. **正向计算**:模型预测结果(logits),并与正确标签(label)求预测损失(loss)。\n", + "2. **反向传播**:利用自动微分机制,自动求模型参数(parameters)对于loss的梯度(gradients)。\n", + "3. **参数优化**:将梯度更新到参数上。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "MindSpore使用函数式自动微分机制,因此针对上述步骤需要实现:\n", + "\n", + "1. 定义正向计算函数。\n", + "2. 使用[value_and_grad](https://www.mindspore.cn/docs/zh-CN/master/api_python/mindspore/mindspore.value_and_grad.html)通过函数变换获得梯度计算函数。\n", + "3. 定义训练函数,使用[set_train](https://www.mindspore.cn/docs/zh-CN/master/api_python/nn/mindspore.nn.Cell.html#mindspore.nn.Cell.set_train)设置为训练模式,执行正向计算、反向传播和参数优化。" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "# Instantiate loss function and optimizer\n", + "loss_fn = nn.CrossEntropyLoss()\n", + "optimizer = nn.SGD(model.trainable_params(), 1e-2)\n", + "\n", + "# 1. Define forward function\n", + "def forward_fn(data, label):\n", + " logits = model(data)\n", + " loss = loss_fn(logits, label)\n", + " return loss, logits\n", + "\n", + "# 2. Get gradient function\n", + "grad_fn = mindspore.value_and_grad(forward_fn, None, optimizer.parameters, has_aux=True)\n", + "\n", + "# 3. Define function of one-step training\n", + "def train_step(data, label):\n", + " (loss, _), grads = grad_fn(data, label)\n", + " optimizer(grads)\n", + " return loss\n", + "\n", + "def train(model, dataset):\n", + " size = dataset.get_dataset_size()\n", + " model.set_train()\n", + " for batch, (data, label) in enumerate(dataset.create_tuple_iterator()):\n", + " loss = train_step(data, label)\n", + "\n", + " if batch % 100 == 0:\n", + " loss, current = loss.asnumpy(), batch\n", + " print(f\"loss: {loss:>7f} [{current:>3d}/{size:>3d}]\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "除训练外,我们定义测试函数,用来评估模型的性能。" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "def test(model, dataset, loss_fn):\n", + " num_batches = dataset.get_dataset_size()\n", + " model.set_train(False)\n", + " total, test_loss, correct = 0, 0, 0\n", + " for data, label in dataset.create_tuple_iterator():\n", + " pred = model(data)\n", + " total += len(data)\n", + " test_loss += loss_fn(pred, label).asnumpy()\n", + " correct += (pred.argmax(1) == label).asnumpy().sum()\n", + " test_loss /= num_batches\n", + " correct /= total\n", + " print(f\"Test: \\n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \\n\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "训练过程需多次迭代数据集,一次完整的迭代称为一轮(epoch)。在每一轮,遍历训练集进行训练,结束后使用测试集进行预测。打印每一轮的loss值和预测准确率(Accuracy),可以看到loss在不断下降,Accuracy在不断提高。" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 1\n", + "-------------------------------\n", + "loss: 2.302088 [ 0/938]\n", + "loss: 2.290692 [100/938]\n", + "loss: 2.266338 [200/938]\n", + "loss: 2.205240 [300/938]\n", + "loss: 1.907198 [400/938]\n", + "loss: 1.455603 [500/938]\n", + "loss: 0.861103 [600/938]\n", + "loss: 0.767219 [700/938]\n", + "loss: 0.422253 [800/938]\n", + "loss: 0.513922 [900/938]\n", + "Test: \n", + " Accuracy: 83.8%, Avg loss: 0.529534 \n", + "\n", + "Epoch 2\n", + "-------------------------------\n", + "loss: 0.580867 [ 0/938]\n", + "loss: 0.479347 [100/938]\n", + "loss: 0.677991 [200/938]\n", + "loss: 0.550141 [300/938]\n", + "loss: 0.226565 [400/938]\n", + "loss: 0.314738 [500/938]\n", + "loss: 0.298739 [600/938]\n", + "loss: 0.459540 [700/938]\n", + "loss: 0.332978 [800/938]\n", + "loss: 0.406709 [900/938]\n", + "Test: \n", + " Accuracy: 90.2%, Avg loss: 0.334828 \n", + "\n", + "Epoch 3\n", + "-------------------------------\n", + "loss: 0.461890 [ 0/938]\n", + "loss: 0.242303 [100/938]\n", + "loss: 0.281414 [200/938]\n", + "loss: 0.207835 [300/938]\n", + "loss: 0.206000 [400/938]\n", + "loss: 0.409646 [500/938]\n", + "loss: 0.193608 [600/938]\n", + "loss: 0.217575 [700/938]\n", + "loss: 0.212817 [800/938]\n", + "loss: 0.202862 [900/938]\n", + "Test: \n", + " Accuracy: 91.9%, Avg loss: 0.280962 \n", + "\n", + "Done!\n" + ] + } + ], + "source": [ + "epochs = 3\n", + "for t in range(epochs):\n", + " print(f\"Epoch {t+1}\\n-------------------------------\")\n", + " train(model, train_dataset)\n", + " test(model, test_dataset, loss_fn)\n", + "print(\"Done!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "更多细节详见[模型训练](https://www.mindspore.cn/tutorials/zh-CN/master/beginner/train.html)。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 保存模型\n", + "\n", + "模型训练完成后,需要将其参数进行保存。" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Saved Model to model.ckpt\n" + ] + } + ], + "source": [ + "# Save checkpoint\n", + "mindspore.save_checkpoint(model, \"model.ckpt\")\n", + "print(\"Saved Model to model.ckpt\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 加载模型" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "加载保存的权重分为两步:\n", + "\n", + "1. 重新实例化模型对象,构造模型。\n", + "2. 加载模型参数,并将其加载至模型上。" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[]\n" + ] + } + ], + "source": [ + "# Instantiate a random initialized model\n", + "model = Network()\n", + "# Load checkpoint and load parameter to model\n", + "param_dict = mindspore.load_checkpoint(\"model.ckpt\")\n", + "param_not_load, _ = mindspore.load_param_into_net(model, param_dict)\n", + "print(param_not_load)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> `param_not_load`是未被加载的参数列表,为空时代表所有参数均加载成功。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "加载后的模型可以直接用于预测推理。" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Predicted: \"[3 9 6 1 6 7 4 5 2 2]\", Actual: \"[3 9 6 1 6 7 4 5 2 2]\"\n" + ] + } + ], + "source": [ + "model.set_train(False)\n", + "for data, label in test_dataset:\n", + " pred = model(data)\n", + " predicted = pred.argmax(1)\n", + " print(f'Predicted: \"{predicted[:10]}\", Actual: \"{label[:10]}\"')\n", + " break" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "更多细节详见[保存与加载](https://www.mindspore.cn/tutorials/zh-CN/master/beginner/save_load.html)。" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "MindSpore", + "language": "python", + "name": "mindspore" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.5 (default, Oct 25 2019, 15:51:11) \n[GCC 7.3.0]" + }, + "vscode": { + "interpreter": { + "hash": "8c9da313289c39257cb28b126d2dadd33153d4da4d524f730c81a4aaccbd2ca7" + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/community/homomorphic_inference/mindspore_resnet20.ipynb b/examples/community/homomorphic_inference/mindspore_resnet20.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..cf0f13d535db0d4f5874504214aa2a62c00ff8c4 --- /dev/null +++ b/examples/community/homomorphic_inference/mindspore_resnet20.ipynb @@ -0,0 +1,726 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a987ee48", + "metadata": {}, + "source": [ + "## 数据集准备与加载\n", + "\n", + "[CIFAR-10数据集](http://www.cs.toronto.edu/~kriz/cifar.html)共有60000张32*32的彩色图像,分为10个类别,每类有6000张图,数据集一共有50000张训练图片和10000张评估图片。首先,如下示例使用`download`接口下载并解压,目前仅支持解析二进制版本的CIFAR-10文件(CIFAR-10 binary version)。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1f9b81fb", + "metadata": {}, + "outputs": [], + "source": [ + "# from download import download\n", + "\n", + "# url = \"https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/cifar-10-binary.tar.gz\"\n", + "\n", + "# download(url, \"./datasets-cifar10-bin\", kind=\"tar.gz\", replace=True)" + ] + }, + { + "cell_type": "markdown", + "id": "7e9020ba", + "metadata": {}, + "source": [ + "下载后的数据集目录结构如下:\n", + "\n", + "```text\n", + "datasets-cifar10-bin/cifar-10-batches-bin\n", + "├── batches.meta.text\n", + "├── data_batch_1.bin\n", + "├── data_batch_2.bin\n", + "├── data_batch_3.bin\n", + "├── data_batch_4.bin\n", + "├── data_batch_5.bin\n", + "├── readme.html\n", + "└── test_batch.bin\n", + "\n", + "```\n", + "\n", + "然后,使用`mindspore.dataset.Cifar10Dataset`接口来加载数据集,并进行相关图像增强操作。" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "df7fb621", + "metadata": {}, + "outputs": [], + "source": [ + "import mindspore as ms\n", + "import mindspore.dataset as ds\n", + "import mindspore.dataset.vision as vision\n", + "import mindspore.dataset.transforms as transforms\n", + "from mindspore import dtype as mstype\n", + "\n", + "data_dir = \"./datasets-cifar10-bin/cifar-10-batches-bin\" # 数据集根目录\n", + "batch_size = 256 # 批量大小\n", + "image_size = 32 # 训练图像空间大小\n", + "workers = 4 # 并行线程个数\n", + "num_classes = 10 # 分类数量\n", + "\n", + "\n", + "def create_dataset_cifar10(dataset_dir, usage, resize, batch_size, workers):\n", + "\n", + " data_set = ds.Cifar10Dataset(dataset_dir=dataset_dir,\n", + " usage=usage,\n", + " num_parallel_workers=workers,\n", + " shuffle=True)\n", + "\n", + " trans = []\n", + " if usage == \"train\":\n", + " trans += [\n", + " vision.RandomCrop((32, 32), (4, 4, 4, 4)),\n", + " vision.RandomHorizontalFlip(prob=0.5)\n", + " ]\n", + "\n", + " trans += [\n", + " vision.Resize(resize),\n", + " vision.Rescale(1.0 / 255.0, 0.0),\n", + " vision.Normalize([0.4914, 0.4822, 0.4465], [0.2023, 0.1994, 0.2010]),\n", + " vision.HWC2CHW()\n", + " ]\n", + "\n", + " target_trans = transforms.TypeCast(mstype.int32)\n", + "\n", + " # 数据映射操作\n", + " data_set = data_set.map(operations=trans,\n", + " input_columns='image',\n", + " num_parallel_workers=workers)\n", + "\n", + " data_set = data_set.map(operations=target_trans,\n", + " input_columns='label',\n", + " num_parallel_workers=workers)\n", + "\n", + " # 批量操作\n", + " data_set = data_set.batch(batch_size)\n", + "\n", + " return data_set\n", + "\n", + "\n", + "# 获取处理后的训练与测试数据集\n", + "\n", + "dataset_train = create_dataset_cifar10(dataset_dir=data_dir,\n", + " usage=\"train\",\n", + " resize=image_size,\n", + " batch_size=batch_size,\n", + " workers=workers)\n", + "step_size_train = dataset_train.get_dataset_size()\n", + "\n", + "dataset_val = create_dataset_cifar10(dataset_dir=data_dir,\n", + " usage=\"test\",\n", + " resize=image_size,\n", + " batch_size=batch_size,\n", + " workers=workers)\n", + "step_size_val = dataset_val.get_dataset_size()" + ] + }, + { + "cell_type": "markdown", + "id": "21e86f95", + "metadata": {}, + "source": [ + "对CIFAR-10训练数据集进行可视化。" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "c3ffabb3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Image shape: (256, 3, 32, 32), Label shape: (256,)\n", + "Labels: [4 6 4 1 8 3]\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAgMAAAGDCAYAAAC2gxMSAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy88F64QAAAACXBIWXMAAA9hAAAPYQGoP6dpAABnEElEQVR4nO29eZRd1Xnm/Z7hzvdW3RpVmoVASAgjwEw2yAZDiAngTnAIaTwgHHe7jUOykjgrsZPlBhICONj57M9x7KzPbTuJQzy0gYDpxoTB2JjBshkEiEGgeShVqeaqO5+zvz/cqP0OWBdZUpU4z28t/bG33nuGffbZteu+Tz2v55xzBAAAAIDE4s/2BQAAAABgdsFmAAAAAEg42AwAAAAACQebAQAAACDhYDMAAAAAJBxsBgAAAICEg80AAAAAkHCwGQAAAAASDjYDAAAAQMLBZuAAXH/99eR53mxfBgBt8a1vfYtOPPFEyuVy5HkePf3007N9SQC8YbDuHnmwGQDgTcLw8DB98IMfpGOPPZbuvfdeeuyxx+j444+f7csCABwFhLN9AUBTqVQon8/P9mWAo4yXX36Zms0mfeADH6Bzzz33deMwvwDQJP29wDcDv8A999xDp5xyCmUyGTrmmGPoM5/5jIpxztE//MM/0CmnnEK5XI66urro8ssvp82bN6vY+++/ny644ALq6OigfD5P55xzDj3wwAMs5rWvw5588km6/PLLqauri4499tjDdo/gzcnVV19Na9euJSKi3/3d3yXP8+i8886jq6++morFIj377LP067/+61QqleiCCy4gIqLR0VH62Mc+RgsXLqR0Ok3Lly+nv/zLv6R6vc6OPT4+Th/+8Iepu7ubisUiXXLJJbR582byPI+uv/76I32r4E0G1t05ggPOOefuv/9+FwSBW7t2rbv99tvdd77zHXfGGWe4JUuWuF8cpv/6X/+rS6VS7uMf/7i799573W233eZWrVrl5s2b5wYHB/fH/cu//IvzPM/91m/9lrv99tvd3Xff7S699FIXBIG7//7798ddd911jojc0qVL3Z//+Z+7//iP/3B33nnnEb13cPTzyiuvuC9+8YuOiNxNN93kHnvsMff888+7devWuVQq5ZYtW+Zuvvlm98ADD7jvf//7rlqtujVr1rhCoeA+85nPuPvuu8996lOfcmEYuosvvnj/caMocmvXrnXZbNbdcsst7r777nM33HCDW7FihSMid911183eTYOjHqy7cwdsBv4PZ511lluwYIGrVqv7+yYnJ113d/f+SfnYY485InKf/exn2Wd37Njhcrmc+7M/+zPnnHMzMzOuu7vbvec972FxURS5k08+2Z155pn7+16blP/9v//3w3VrICE89NBDjojcd77znf1969atc0TkvvrVr7LYL3/5y46I3Le//W3W/+lPf9oRkbvvvvucc87dc889jojcl770JRZ38803YzMAfmWw7s4dkCYgopmZGVq/fj29973vpWw2u7+/VCrRe97znv3t733ve+R5Hn3gAx+gVqu1/9/AwACdfPLJ9IMf/ICIiB599FEaHR2ldevWsbg4jumiiy6i9evX08zMDLuG3/7t3z4i9wqSiZxfDz74IBUKBbr88stZ/9VXX01EtP9r1YcffpiIiK644goWd+WVVx6mKwVJAevu3AICQiIaGxujOI5pYGBA/d8v9u3du5ecczRv3jzzOMuXL98fR0Rqof1FRkdHqVAo7G/Pnz//oK4dgAORz+epo6OD9Y2MjNDAwID6863+/n4Kw5BGRkb2x4VhSN3d3Szu9d4BANoF6+7cApsBIurq6iLP82hwcFD93y/29fb2kud59KMf/YgymYyKfa2vt7eXiIi+8IUv0Nve9jbznHJi429qweHCmls9PT30xBNPkHOO/f/Q0BC1Wq39c7inp4darRaNjo6yDYH1rgDwRsC6O7fAZoCICoUCnXnmmXT77bfTrbfeuv8rq6mpKbr77rv3x1166aV0yy230K5du9TXpr/IOeecQ+VymTZu3EjXXnvtYb9+AN4oF1xwAX3729+mO++8ky677LL9/f/8z/+8//+JiM4991z627/9W/rWt75F11xzzf64b37zm0f2gsGbDqy7cwtsBv4Pf/3Xf00XXXQRXXjhhfTxj3+coiiiT3/601QoFGh0dJSIfj7ZPvKRj9CHPvQh+ulPf0rvfOc7qVAo0J49e+iRRx6hk046ia655hoqFov0hS98gdatW0ejo6N0+eWXU39/Pw0PD9MzzzxDw8PD9KUvfWmW7xgkmauuuoq++MUv0rp162jr1q100kkn0SOPPEI33XQTXXzxxfRrv/ZrRER00UUX0TnnnEMf//jHaXJykk477TR67LHH9m8afB+yI3DwYN2dQ8yy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U09PD91yyy3U19dHo6Oj9E//9E901lln0VNPPUUrV658Q8f7h3/4B/rIRz5Cr776Kt1xxx3s/2q1Gr3rXe+iV199lW644QZas2YN/ehHP6Kbb76Znn76abrnnntY/Pe+9z166qmn6JZbbiHP8+jP//zP6ZJLLqF169bR5s2b6e///u9pYmKC/uRP/oR++7d/m55++un9G4+bb76Z/uIv/oKuvPJKuvnmm2lkZISuv/56evvb307r16+nFStW7D9Ps9mkiy++mP7bf/tv9IlPfIIeffRRuvHGG2nbtm109913v6H737hxI5199tm0ZMkS+uxnP0sDAwP0/e9/n/7wD/+Q9u3bR9ddd90bOh6Ym3zwgx+kJ598kv7mb/6Gjj/+eBofH6cnn3ySRkZG9scMDg7S+9//fvr4xz9O1113Hd1xxx30yU9+khYsWEBXXXXVAc9x2WWX0RVXXEEf/ehH6fnnn6dPfepTtHHjRnriiScolUodztsDCeKZZ56htWvXUm9vL/3VX/0VrVixgvbs2UN33XUXNRoNGh0dJSKi6667jgYGBmh6epruuOMOOu+88+iBBx6g8847j+bPn0/33nsvXXTRRfThD3+Y/st/+S9ERPs3CEDgjgJarZZrNBpuxYoV7o//+I/393/ta19zROS2bNnC4h966CFHRO6hhx7a33fJJZe4pUuXqmN/+ctfdkTkvv3tb7P+T3/6046I3H333be/j4jcwMCAm56e3t935513OiJyp5xyiovjeH//5z73OUdEbsOGDc4558bGxlwul3MXX3wxO8/27dtdJpNx73vf+/b3rVu3zhGR+/znP89i/+Zv/sYRkXvkkUf29y1dutStW7duf3vLli2OiNzXvva1/X3vfve73aJFi9zExAQ73rXXXuuy2awbHR1V4wKOPorFovujP/qj1/3/c8891xGRe+KJJ1j/6tWr3bvf/e79bWsOXXfddY6I2PvnnHP/+q//6ojIfeMb3zg0NwGAc+7888935XLZDQ0NtRXfarVcs9l0F1xwgbvsssv29w8PDzsictddd91hutI3D3MyTdBqteimm26i1atXUzqdpjAMKZ1O06ZNm173K/WD5cEHH6RCoUCXX34563/tq6QHHniA9b/rXe+iQqGwv33CCScQEdFv/MZvsNTDa/3btm0jIqLHHnuMqtWq+opq8eLFdP7556vzEBG9//3vZ+33ve99RET00EMPtXt7VKvV6IEHHqDLLruM8vk8tVqt/f8uvvhiqtVq9Pjjj7d9PDB3OfPMM+nrX/863XjjjfT4449Ts9lUMQMDA3TmmWeyvjVr1uyfpwdCzskrrriCwjB8Q3MSgF9GpVKhhx9+mK644opf+lv8l7/8ZXrrW99K2WyWwjCkVCpFDzzwwCH/GZEU5uRm4E/+5E/oU5/6FP3Wb/0W3X333fTEE0/Q+vXr6eSTT6ZqtXpIzzUyMkIDAwPsBzkRUX9/P4VhyL5iJSLq7u5m7XQ6/Uv7a7Xa/vMQ/VypLVmwYIE6TxiG1NPTw/oGBgbYsdphZGSEWq0WfeELX6BUKsX+XXzxxUREtG/fvraPB+Yu3/rWt2jdunX0la98hd7+9rdTd3c3XXXVVTQ4OLg/Rs4pop8LBtt9r16bg6/x2jx9I3MSgF/G2NgYRVFEixYtet2Yv/u7v6NrrrmGzjrrLPrud79Ljz/+OK1fv54uuuiiQ/4zIinMSc3AN77xDbrqqqvopptuYv379u2jcrm8v53NZomIlNDvjfxw6+npoSeeeIKcc2xDMDQ0RK1Wi3p7ew/iDuzzEBHt2bNH/d/u3bvVeVqtFo2MjLDF+7VF3VrQX4+uri4KgoA++MEP0u///u+bMcccc0zbxwNzl97eXvrc5z5Hn/vc52j79u1011130Sc+8QkaGhqie++995CcY3BwkBYuXLi/bc1TAH4Vuru7KQgC2rlz5+vGfOMb36DzzjuPvvSlL7H+qampw315b1rm5DcDnudRJpNhfffccw/t2rWL9S1btoyIiDZs2MD677rrLnXM1/vt54ILLqDp6Wm68847Wf8///M/7///Q8Hb3/52yuVy9I1vfIP179y5kx588EHzPP/6r//K2rfddhsRvbG/l83n8/Sud72LnnrqKVqzZg2dfvrp6h8W8jcfS5YsoWuvvZYuvPBCevLJJw/ZceWc/Pa3v02tVgt/ww0OGblcjs4991z6zne+87q/2Fk/IzZs2ECPPfYY63stBt8WHJg5+c3ApZdeSl//+tdp1apVtGbNGvrZz35Gt956q/ra6IwzzqCVK1fSn/7pn1Kr1aKuri6644476JFHHlHHPOmkk+j222+nL33pS3TaaaeR7/t0+umn01VXXUVf/OIXad26dbR161Y66aST6JFHHqGbbrqJLr74Yvq1X/u1Q3JP5XKZPvWpT9Ff/MVf0FVXXUVXXnkljYyM0A033EDZbFYp+tPpNH32s5+l6elpOuOMM/b/NcFv/MZv0Nq1a9/QuT//+c/T2rVr6R3veAddc801tGzZMpqamqJXXnmF7r77bnrwwQcPyT2C2WNiYoLe9a530fve9z5atWoVlUolWr9+Pd1777303ve+95Cd5/bbb6cwDOnCCy/c/9cEJ598Ml1xxRWH7BwA/N3f/R2tXbuWzjrrLPrEJz5Bxx13HO3du5fuuusu+sd//Ee69NJL6a//+q/puuuuo3PPPZdeeukl+qu/+is65phjqNVq7T9OqVSipUuX0r//+7/TBRdcQN3d3dTb27v/F0nwC8y2gtFibGzMffjDH3b9/f0un8+7tWvXuh/96Efu3HPPdeeeey6Lffnll92v//qvu46ODtfX1+f+4A/+wN1zzz3qrwlGR0fd5Zdf7srlsvM8z/3irY+MjLiPfvSjbv78+S4MQ7d06VL3yU9+0tVqNXYuInK///u/z/peU17feuutrP+1v2j4zne+w/q/8pWvuDVr1rh0Ou06Ozvdb/7mb7rnn3+exaxbt84VCgW3YcMGd95557lcLue6u7vdNddcw/6Swbn2/prgtf7f+73fcwsXLnSpVMr19fW5s88+2914441q/MHRR61Wcx/96EfdmjVrXEdHh8vlcm7lypXuuuuuczMzM865n/81wYknnqg+u27dOvaXNr/srwl+9rOfufe85z2uWCy6UqnkrrzySrd3797DfXsggWzcuNH9zu/8juvp6XHpdNotWbLEXX311a5Wq7l6ve7+9E//1C1cuNBls1n31re+1d15551qLjvn3P333+9OPfVUl8lkHBGx9RL8XzznnJu1nQgA4Kjg+uuvpxtuuIGGh4cPmY4GADB3mJOaAQAAAAAcObAZAAAAABIO0gQAAABAwsE3AwAAAEDCwWYAAAAASDjYDAAAAAAJp23Toe/cqcvmykIoHR0dKkZY/pPvB+2eUhzH+6Xt16NarbC2VWY1lUr/ytfzq8b9IpaMQ7ovzszMqJhcLsfaixcvVjFRFB3wfNb55eesmDjmMZddevEBz3UkOJhnAIBkrsxdue4GwYHXVOvaX6ub8hrW2oB3543T7jyRDortlAC3io/dd999rG1ZMl955ZUHPDa+GQAAAAAS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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "\n", + "data_iter = next(dataset_train.create_dict_iterator())\n", + "\n", + "images = data_iter[\"image\"].asnumpy()\n", + "labels = data_iter[\"label\"].asnumpy()\n", + "print(f\"Image shape: {images.shape}, Label shape: {labels.shape}\")\n", + "\n", + "# 训练数据集中,前六张图片所对应的标签\n", + "print(f\"Labels: {labels[:6]}\")\n", + "\n", + "classes = []\n", + "\n", + "with open(data_dir + \"/batches.meta.txt\", \"r\") as f:\n", + " for line in f:\n", + " line = line.rstrip()\n", + " if line:\n", + " classes.append(line)\n", + "\n", + "# 训练数据集的前六张图片\n", + "plt.figure()\n", + "for i in range(6):\n", + " plt.subplot(2, 3, i + 1)\n", + " image_trans = np.transpose(images[i], (1, 2, 0))\n", + " mean = np.array([0.4914, 0.4822, 0.4465])\n", + " std = np.array([0.2023, 0.1994, 0.2010])\n", + " image_trans = std * image_trans + mean\n", + " image_trans = np.clip(image_trans, 0, 1)\n", + " plt.title(f\"{classes[labels[i]]}\")\n", + " plt.imshow(image_trans)\n", + " plt.axis(\"off\")\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "1ebef3d0", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Type, Union, List, Optional\n", + "import mindspore.nn as nn\n", + "from mindspore.common.initializer import Normal\n", + "from mindspore import load_checkpoint, load_param_into_net\n", + "\n", + "# 初始化卷积层与BatchNorm的参数\n", + "weight_init = Normal(mean=0, sigma=0.02)\n", + "gamma_init = Normal(mean=1, sigma=0.02)\n", + "\n", + "class ResidualBlockBase(nn.Cell):\n", + " expansion: int = 1 # 最后一个卷积核数量与第一个卷积核数量相等\n", + "\n", + " def __init__(self, in_channel: int, out_channel: int,\n", + " stride: int = 1, \n", + " down_sample: Optional[nn.Cell] = None) -> None:\n", + " super(ResidualBlockBase, self).__init__()\n", + " # if not norm:\n", + " # self.norm = nn.BatchNorm2d(out_channel)\n", + " # else:\n", + " # self.norm = norm \n", + "\n", + " \n", + " self.conv1 = nn.Conv2d(in_channel, out_channel,kernel_size=3, stride=stride,weight_init=weight_init)\n", + " self.bn1 = nn.BatchNorm2d(out_channel)\n", + " self.relu = nn.ReLU()\n", + " self.conv2 = nn.Conv2d(out_channel, out_channel, kernel_size=3, weight_init=weight_init)\n", + " self.bn2 = nn.BatchNorm2d(out_channel)\n", + " # self.down_sample = down_sample\n", + " self.down_sample = nn.SequentialCell()\n", + " if stride != 1 or in_channel != out_channel:\n", + " self.down_sample = nn.SequentialCell(\n", + " nn.Conv2d(in_channel, out_channel, kernel_size=1, stride=stride),\n", + " nn.BatchNorm2d(out_channel)\n", + " )\n", + "\n", + " def construct(self, x):\n", + " \"\"\"ResidualBlockBase construct.\"\"\"\n", + " identity = x # shortcuts分支\n", + "\n", + " out = self.conv1(x) # 主分支第一层:3*3卷积层\n", + " out = self.bn1(out)\n", + " out = self.relu(out)\n", + " out = self.conv2(out) # 主分支第二层:3*3卷积层\n", + " out = self.bn2(out)\n", + "\n", + " if self.down_sample is not None:\n", + " identity = self.down_sample(x)\n", + " out += identity # 输出为主分支与shortcuts之和\n", + " out = self.relu(out)\n", + "\n", + " return out\n", + "\n", + "\n", + "# def make_layer(last_out_channel, block: Type[ResidualBlockBase],\n", + "# channel: int, block_nums: int, stride: int = 1):\n", + "# # down_sample = None # shortcuts分支\n", + "\n", + "# # if stride != 1 or last_out_channel != channel * block.expansion:\n", + "\n", + "# # down_sample = nn.SequentialCell([\n", + "# # nn.Conv2d(last_out_channel, channel * block.expansion,\n", + "# # kernel_size=1, stride=stride, weight_init=weight_init),\n", + "# # nn.BatchNorm2d(channel * block.expansion, gamma_init=gamma_init)\n", + "# # ])\n", + "\n", + "# # layers = []\n", + "# # layers.append(block(last_out_channel, channel, stride=stride, down_sample=down_sample))\n", + "\n", + "# # in_channel = channel * block.expansion\n", + "# # # 堆叠残差网络\n", + "# # for _ in range(1, block_nums):\n", + "\n", + "# # layers.append(block(in_channel, channel))\n", + "# strides = [stride] + [1] * (block_nums - 1)\n", + "# layers = []\n", + "# for stride in strides:\n", + "# layers.append(block(last_out_channel, channel, stride))\n", + "# last_out_channel = channel * block.expansion\n", + "# return nn.SequentialCell(layers)\n", + "\n", + "class ResNet(nn.Cell):\n", + " def __init__(self, block: Type[ResidualBlockBase],\n", + " layer_nums: List[int], num_classes: int, input_channel: int) -> None:\n", + " super(ResNet, self).__init__()\n", + " self.in_channels = 16\n", + "\n", + " # 第一个卷积层,输入channel为3(彩色图像),输出channel为16\n", + " self.conv1 = nn.Conv2d(3, 16, kernel_size=3, stride=1, weight_init=weight_init,pad_mode='pad',padding=1)\n", + " self.norm = nn.BatchNorm2d(16)\n", + " self.relu = nn.ReLU()\n", + " # # 最大池化层,缩小图片的尺寸\n", + " # self.max_pool = nn.MaxPool2d(kernel_size=3, stride=2, pad_mode='same')\n", + " # 各个残差网络结构块定义\n", + " self.layer1 = self.make_layer(block, 16, layer_nums[0],stride=1)\n", + " self.layer2 = self.make_layer(block, 32, layer_nums[1], stride=2)\n", + " self.layer3 = self.make_layer(block, 64, layer_nums[2], stride=2)\n", + " # self.layer4 = make_layer(256 * block.expansion, block, 512, layer_nums[3], stride=2)\n", + " # 平均池化层\n", + " self.avg_pool = nn.AvgPool2d(8,1)\n", + " # self.avg_pool = nn.AvgPool2d(pad_mode='pad',padding=(1,1))\n", + " # flattern层\n", + " self.flatten = nn.Flatten()\n", + " # 全连接层\n", + " self.fc = nn.Dense(in_channels=input_channel, out_channels=num_classes)\n", + "\n", + " # def make_layer(self, block, out_channels, num_blocks, stride):\n", + " # strides = [stride] + [1] * (num_blocks - 1)\n", + " # layers = []\n", + " # for stride in strides:\n", + " # layers.append(block(self.in_channels, out_channels, stride))\n", + " # self.in_channels = out_channels * block.expansion\n", + " # return nn.SequentialCell(layers)\n", + " def make_layer(self, block, out_channels, num_blocks, stride):\n", + " layers = []\n", + " layers.append(block(self.in_channels, out_channels, stride))\n", + " self.in_channels = out_channels\n", + " for _ in range(1, num_blocks):\n", + " layers.append(block(out_channels, out_channels))\n", + " return nn.SequentialCell(*layers)\n", + " def construct(self, x):\n", + " # print(x.shape)\n", + " x = self.conv1(x)\n", + " # print(x.shape)\n", + " x = self.norm(x)\n", + " # print(x.shape)\n", + " x = self.relu(x)\n", + " # print(x.shape)\n", + " x = self.layer1(x)\n", + " # print(x.shape)\n", + " x = self.layer2(x)\n", + " # print(x.shape)\n", + " x = self.layer3(x)\n", + " # print(x.shape)\n", + " x = self.avg_pool(x)\n", + " # print(x.shape)\n", + " x = self.flatten(x)\n", + " # print(x.shape)\n", + " x = self.fc(x)\n", + "# (256, 3, 32, 32)\n", + "# (256, 16, 32, 32)\n", + "# (256, 16, 32, 32)\n", + "# (256, 16, 32, 32)\n", + "# (256, 16, 32, 32)\n", + "# (256, 32, 16, 16)\n", + "# (256, 64, 8, 8)\n", + "# (256, 64, 1, 1)\n", + "# (256, 64)\n", + " return x\n", + "\n", + "def _resnet(model_url: str, block: Type[ResidualBlockBase],\n", + " layers: List[int], num_classes: int, pretrained: bool, pretrained_ckpt: str,\n", + " input_channel: int):\n", + " model = ResNet(block, layers, num_classes, input_channel)\n", + "\n", + " if pretrained:\n", + " # 加载预训练模型\n", + " download(url=model_url, path=pretrained_ckpt, replace=True)\n", + " param_dict = load_checkpoint(pretrained_ckpt)\n", + " load_param_into_net(model, param_dict)\n", + "\n", + " return model\n", + "\n", + "def resnet20(num_classes: int = 10, pretrained: bool = False):\n", + " \"\"\"ResNet20模型\"\"\"\n", + " resnet20_url = \"https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/models/application/resnet50_224_new.ckpt\"\n", + " resnet20_ckpt = \"./LoadPretrainedModel/resnet20_new.ckpt\"\n", + " return _resnet(resnet20_url, ResidualBlockBase, [3, 3, 3], num_classes,\n", + " pretrained, resnet20_ckpt, 64)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "9cf10c03", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ResNet<\n", + " (conv1): Conv2d, bias_init=None, format=NCHW>\n", + " (norm): BatchNorm2d\n", + " (relu): ReLU<>\n", + " (layer1): SequentialCell<\n", + " (0): ResidualBlockBase<\n", + " (conv1): Conv2d, bias_init=None, format=NCHW>\n", + " (bn1): BatchNorm2d\n", + " (relu): ReLU<>\n", + " (conv2): Conv2d, bias_init=None, format=NCHW>\n", + " (bn2): BatchNorm2d\n", + " (down_sample): SequentialCell<>\n", + " >\n", + " (1): ResidualBlockBase<\n", + " (conv1): Conv2d, bias_init=None, format=NCHW>\n", + " (bn1): BatchNorm2d\n", + " (relu): ReLU<>\n", + " (conv2): Conv2d, bias_init=None, format=NCHW>\n", + " (bn2): BatchNorm2d\n", + " (down_sample): SequentialCell<>\n", + " >\n", + " (2): ResidualBlockBase<\n", + " (conv1): Conv2d, bias_init=None, format=NCHW>\n", + " (bn1): BatchNorm2d\n", + " (relu): ReLU<>\n", + " (conv2): Conv2d, bias_init=None, format=NCHW>\n", + " (bn2): BatchNorm2d\n", + " (down_sample): SequentialCell<>\n", + " >\n", + " >\n", + " (layer2): SequentialCell<\n", + " (0): ResidualBlockBase<\n", + " (conv1): Conv2d, bias_init=None, format=NCHW>\n", + " (bn1): BatchNorm2d\n", + " (relu): ReLU<>\n", + " (conv2): Conv2d, bias_init=None, format=NCHW>\n", + " (bn2): BatchNorm2d\n", + " (down_sample): SequentialCell<\n", + " (0): Conv2d, bias_init=None, format=NCHW>\n", + " (1): BatchNorm2d\n", + " >\n", + " >\n", + " (1): ResidualBlockBase<\n", + " (conv1): Conv2d, bias_init=None, format=NCHW>\n", + " (bn1): BatchNorm2d\n", + " (relu): ReLU<>\n", + " (conv2): Conv2d, bias_init=None, format=NCHW>\n", + " (bn2): BatchNorm2d\n", + " (down_sample): SequentialCell<>\n", + " >\n", + " (2): ResidualBlockBase<\n", + " (conv1): Conv2d, bias_init=None, format=NCHW>\n", + " (bn1): BatchNorm2d\n", + " (relu): ReLU<>\n", + " (conv2): Conv2d, bias_init=None, format=NCHW>\n", + " (bn2): BatchNorm2d\n", + " (down_sample): SequentialCell<>\n", + " >\n", + " >\n", + " (layer3): SequentialCell<\n", + " (0): ResidualBlockBase<\n", + " (conv1): Conv2d, bias_init=None, format=NCHW>\n", + " (bn1): BatchNorm2d\n", + " (relu): ReLU<>\n", + " (conv2): Conv2d, bias_init=None, format=NCHW>\n", + " (bn2): BatchNorm2d\n", + " (down_sample): SequentialCell<\n", + " (0): Conv2d, bias_init=None, format=NCHW>\n", + " (1): BatchNorm2d\n", + " >\n", + " >\n", + " (1): ResidualBlockBase<\n", + " (conv1): Conv2d, bias_init=None, format=NCHW>\n", + " (bn1): BatchNorm2d\n", + " (relu): ReLU<>\n", + " (conv2): Conv2d, bias_init=None, format=NCHW>\n", + " (bn2): BatchNorm2d\n", + " (down_sample): SequentialCell<>\n", + " >\n", + " (2): ResidualBlockBase<\n", + " (conv1): Conv2d, bias_init=None, format=NCHW>\n", + " (bn1): BatchNorm2d\n", + " (relu): ReLU<>\n", + " (conv2): Conv2d, bias_init=None, format=NCHW>\n", + " (bn2): BatchNorm2d\n", + " (down_sample): SequentialCell<>\n", + " >\n", + " >\n", + " (avg_pool): AvgPool2d\n", + " (flatten): Flatten<>\n", + " (fc): Dense\n", + " >\n" + ] + } + ], + "source": [ + "# 定义ResNet20网络\n", + "network = resnet20(pretrained=False)\n", + "print(network)\n", + "# 全连接层输入层的大小\n", + "in_channel = network.fc.in_channels\n", + "fc = nn.Dense(in_channels=in_channel, out_channels=10)\n", + "# 重置全连接层\n", + "network.fc = fc" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "e1c632ff", + "metadata": {}, + "outputs": [], + "source": [ + "# 设置学习率\n", + "num_epochs = 50\n", + "lr = nn.cosine_decay_lr(min_lr=0.00001, max_lr=0.001, total_step=step_size_train * num_epochs,\n", + " step_per_epoch=step_size_train, decay_epoch=num_epochs)\n", + "# 定义优化器和损失函数\n", + "opt = nn.Momentum(params=network.trainable_params(), learning_rate=lr, momentum=0.9)\n", + "loss_fn = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction='mean')\n", + "\n", + "\n", + "def forward_fn(inputs, targets):\n", + " logits = network(inputs)\n", + " loss = loss_fn(logits, targets)\n", + " return loss\n", + "\n", + "\n", + "grad_fn = ms.value_and_grad(forward_fn, None, opt.parameters)\n", + "\n", + "\n", + "def train_step(inputs, targets):\n", + " # print(inputs.shape)\n", + " # print(targets.shape)\n", + " loss, grads = grad_fn(inputs, targets)\n", + " opt(grads)\n", + " return loss" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "b627e30c", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "# 创建迭代器\n", + "data_loader_train = dataset_train.create_tuple_iterator(num_epochs=num_epochs)\n", + "data_loader_val = dataset_val.create_tuple_iterator(num_epochs=num_epochs)\n", + "\n", + "# 最佳模型存储路径\n", + "best_acc = 0\n", + "best_ckpt_dir = \"./BestCheckpoint\"\n", + "best_ckpt_path = \"./BestCheckpoint/resnet20-best.ckpt\"\n", + "\n", + "if not os.path.exists(best_ckpt_dir):\n", + " os.mkdir(best_ckpt_dir)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "8a5170df", + "metadata": {}, + "outputs": [], + "source": [ + "import mindspore.ops as ops\n", + "\n", + "\n", + "def train(data_loader, epoch):\n", + " \"\"\"模型训练\"\"\"\n", + " losses = []\n", + " network.set_train(True)\n", + "\n", + " for i, (images, labels) in enumerate(data_loader):\n", + " # print(images.shape)\n", + " # print(labels.shape)\n", + " loss = train_step(images, labels)\n", + " if i % 100 == 0 or i == step_size_train - 1:\n", + " print('Epoch: [%3d/%3d], Steps: [%3d/%3d], Train Loss: [%5.3f]' %\n", + " (epoch + 1, num_epochs, i + 1, step_size_train, loss))\n", + " losses.append(loss)\n", + "\n", + " return sum(losses) / len(losses)\n", + "\n", + "\n", + "def evaluate(data_loader):\n", + " \"\"\"模型验证\"\"\"\n", + " network.set_train(False)\n", + "\n", + " correct_num = 0.0 # 预测正确个数\n", + " total_num = 0.0 # 预测总数\n", + "\n", + " for images, labels in data_loader:\n", + " logits = network(images)\n", + " pred = logits.argmax(axis=1) # 预测结果\n", + " correct = ops.equal(pred, labels).reshape((-1, ))\n", + " correct_num += correct.sum().asnumpy()\n", + " total_num += correct.shape[0]\n", + "\n", + " acc = correct_num / total_num # 准确率\n", + "\n", + " return acc" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "562a04ca", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Start Training Loop ...\n", + "Epoch: [ 1/ 50], Steps: [ 1/196], Train Loss: [2.400]\n" + ] + } + ], + "source": [ + "# 开始循环训练\n", + "print(\"Start Training Loop ...\")\n", + "\n", + "for epoch in range(num_epochs):\n", + " curr_loss = train(data_loader_train, epoch)\n", + " curr_acc = evaluate(data_loader_val)\n", + "\n", + " print(\"-\" * 50)\n", + " print(\"Epoch: [%3d/%3d], Average Train Loss: [%5.3f], Accuracy: [%5.3f]\" % (\n", + " epoch+1, num_epochs, curr_loss, curr_acc\n", + " ))\n", + " print(\"-\" * 50)\n", + "\n", + " # 保存当前预测准确率最高的模型\n", + " if curr_acc > best_acc:\n", + " best_acc = curr_acc\n", + " ms.save_checkpoint(network, best_ckpt_path)\n", + "\n", + "print(\"=\" * 80)\n", + "print(f\"End of validation the best Accuracy is: {best_acc: 5.3f}, \"\n", + " f\"save the best ckpt file in {best_ckpt_path}\", flush=True)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "46e28f6f", + "metadata": {}, + "source": [ + "## 可视化模型预测\n", + "\n", + "定义`visualize_model`函数,使用上述验证精度最高的模型对CIFAR-10测试数据集进行预测,并将预测结果可视化。若预测字体颜色为蓝色表示为预测正确,预测字体颜色为红色则表示预测错误。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6ba2fa94", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "\n", + "def visualize_model(best_ckpt_path, dataset_val):\n", + " num_class = 10\n", + " net = resnet20(num_class)\n", + " # 加载模型参数\n", + " param_dict = ms.load_checkpoint(best_ckpt_path)\n", + " ms.load_param_into_net(net, param_dict)\n", + " # 加载验证集的数据进行验证\n", + " data = next(dataset_val.create_dict_iterator())\n", + " images = data[\"image\"]\n", + " labels = data[\"label\"]\n", + " # 预测图像类别\n", + " output = net(data['image'])\n", + " pred = np.argmax(output.asnumpy(), axis=1)\n", + "\n", + " # 图像分类\n", + " classes = []\n", + "\n", + " with open(data_dir + \"/batches.meta.txt\", \"r\") as f:\n", + " for line in f:\n", + " line = line.rstrip()\n", + " if line:\n", + " classes.append(line)\n", + "\n", + " # 显示图像及图像的预测值\n", + " plt.figure()\n", + " for i in range(6):\n", + " plt.subplot(2, 3, i + 1)\n", + " # 若预测正确,显示为蓝色;若预测错误,显示为红色\n", + " color = 'blue' if pred[i] == labels.asnumpy()[i] else 'red'\n", + " plt.title('predict:{}'.format(classes[pred[i]]), color=color)\n", + " picture_show = np.transpose(images.asnumpy()[i], (1, 2, 0))\n", + " mean = np.array([0.4914, 0.4822, 0.4465])\n", + " std = np.array([0.2023, 0.1994, 0.2010])\n", + " picture_show = std * picture_show + mean\n", + " picture_show = np.clip(picture_show, 0, 1)\n", + " plt.imshow(picture_show)\n", + " plt.axis('off')\n", + "\n", + " plt.show()\n", + "\n", + "\n", + "# 使用测试数据集进行验证\n", + "visualize_model(best_ckpt_path=best_ckpt_path, dataset_val=dataset_val)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "base", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/community/homomorphic_inference/mindspore_resnet20.py b/examples/community/homomorphic_inference/mindspore_resnet20.py new file mode 100644 index 0000000000000000000000000000000000000000..03cc4ad9e356da25dce9b66a6ceb58648af2182c --- /dev/null +++ b/examples/community/homomorphic_inference/mindspore_resnet20.py @@ -0,0 +1,305 @@ +import mindspore as ms +import mindspore.dataset as ds +import mindspore.dataset.vision as vision +import mindspore.dataset.transforms as transforms +from mindspore import dtype as mstype + +data_dir = "./datasets-cifar10-bin/cifar-10-batches-bin" # 数据集根目录 +batch_size = 256 # 批量大小 +image_size = 32 # 训练图像空间大小 +workers = 4 # 并行线程个数 +num_classes = 10 # 分类数量 + + +def create_dataset_cifar10(dataset_dir, usage, resize, batch_size, workers): + + data_set = ds.Cifar10Dataset(dataset_dir=dataset_dir, + usage=usage, + num_parallel_workers=workers, + shuffle=True) + + trans = [] + if usage == "train": + trans += [ + vision.RandomCrop((32, 32), (4, 4, 4, 4)), + vision.RandomHorizontalFlip(prob=0.5) + ] + + trans += [ + vision.Resize(resize), + vision.Rescale(1.0 / 255.0, 0.0), + vision.Normalize([0.4914, 0.4822, 0.4465], [0.2023, 0.1994, 0.2010]), + vision.HWC2CHW() + ] + + target_trans = transforms.TypeCast(mstype.int32) + + # 数据映射操作 + data_set = data_set.map(operations=trans, + input_columns='image', + num_parallel_workers=workers) + + data_set = data_set.map(operations=target_trans, + input_columns='label', + num_parallel_workers=workers) + + # 批量操作 + data_set = data_set.batch(batch_size) + + return data_set + + +# 获取处理后的训练与测试数据集 + +dataset_train = create_dataset_cifar10(dataset_dir=data_dir, + usage="train", + resize=image_size, + batch_size=batch_size, + workers=workers) +step_size_train = dataset_train.get_dataset_size() + +dataset_val = create_dataset_cifar10(dataset_dir=data_dir, + usage="test", + resize=image_size, + batch_size=batch_size, + workers=workers) +step_size_val = dataset_val.get_dataset_size() + + + +from typing import Type, Union, List, Optional +import mindspore.nn as nn +from mindspore.common.initializer import Normal +from mindspore import load_checkpoint, load_param_into_net + +# 初始化卷积层与BatchNorm的参数 +weight_init = Normal(mean=0, sigma=0.02) +gamma_init = Normal(mean=1, sigma=0.02) + +class ResidualBlockBase(nn.Cell): + expansion: int = 1 # 最后一个卷积核数量与第一个卷积核数量相等 + + def __init__(self, in_channel: int, out_channel: int, + stride: int = 1, + down_sample: Optional[nn.Cell] = None) -> None: + super(ResidualBlockBase, self).__init__() + # if not norm: + # self.norm = nn.BatchNorm2d(out_channel) + # else: + # self.norm = norm + + + self.conv1 = nn.Conv2d(in_channel, out_channel,kernel_size=3, stride=stride,weight_init=weight_init) + self.bn1 = nn.BatchNorm2d(out_channel) + self.relu = nn.ReLU() + self.conv2 = nn.Conv2d(out_channel, out_channel, kernel_size=3, weight_init=weight_init) + self.bn2 = nn.BatchNorm2d(out_channel) + # self.down_sample = down_sample + self.down_sample = nn.SequentialCell() + if stride != 1 or in_channel != out_channel: + self.down_sample = nn.SequentialCell( + nn.Conv2d(in_channel, out_channel, kernel_size=1, stride=stride), + nn.BatchNorm2d(out_channel) + ) + + def construct(self, x): + """ResidualBlockBase construct.""" + identity = x # shortcuts分支 + + out = self.conv1(x) # 主分支第一层:3*3卷积层 + out = self.bn1(out) + out = self.relu(out) + out = self.conv2(out) # 主分支第二层:3*3卷积层 + out = self.bn2(out) + + if self.down_sample is not None: + identity = self.down_sample(x) + out += identity # 输出为主分支与shortcuts之和 + out = self.relu(out) + + return out + + + + +class ResNet(nn.Cell): + def __init__(self, block: Type[ResidualBlockBase], + layer_nums: List[int], num_classes: int, input_channel: int) -> None: + super(ResNet, self).__init__() + self.in_channels = 16 + + # 第一个卷积层,输入channel为3(彩色图像),输出channel为16 + self.conv1 = nn.Conv2d(3, 16, kernel_size=3, stride=1, weight_init=weight_init,pad_mode='pad',padding=1) + self.norm = nn.BatchNorm2d(16) + self.relu = nn.ReLU() + # # 最大池化层,缩小图片的尺寸 + # self.max_pool = nn.MaxPool2d(kernel_size=3, stride=2, pad_mode='same') + # 各个残差网络结构块定义 + self.layer1 = self.make_layer(block, 16, layer_nums[0],stride=1) + self.layer2 = self.make_layer(block, 32, layer_nums[1], stride=2) + self.layer3 = self.make_layer(block, 64, layer_nums[2], stride=2) + # self.layer4 = make_layer(256 * block.expansion, block, 512, layer_nums[3], stride=2) + # 平均池化层 + self.avg_pool = nn.AvgPool2d(8,1) + # self.avg_pool = nn.AvgPool2d(pad_mode='pad',padding=(1,1)) + # flattern层 + self.flatten = nn.Flatten() + # 全连接层 + self.fc = nn.Dense(in_channels=input_channel, out_channels=num_classes) + + + def make_layer(self, block, out_channels, num_blocks, stride): + layers = [] + layers.append(block(self.in_channels, out_channels, stride)) + self.in_channels = out_channels + for _ in range(1, num_blocks): + layers.append(block(out_channels, out_channels)) + return nn.SequentialCell(*layers) + def construct(self, x): + # print(x.shape) + x = self.conv1(x) + # print(x.shape) + x = self.norm(x) + # print(x.shape) + x = self.relu(x) + # print(x.shape) + x = self.layer1(x) + # print(x.shape) + x = self.layer2(x) + # print(x.shape) + x = self.layer3(x) + # print(x.shape) + x = self.avg_pool(x) + # print(x.shape) + x = self.flatten(x) + # print(x.shape) + x = self.fc(x) + return x + +def _resnet(model_url: str, block: Type[ResidualBlockBase], + layers: List[int], num_classes: int, pretrained: bool, pretrained_ckpt: str, + input_channel: int): + model = ResNet(block, layers, num_classes, input_channel) + + if pretrained: + # 加载预训练模型 + download(url=model_url, path=pretrained_ckpt, replace=True) + param_dict = load_checkpoint(pretrained_ckpt) + load_param_into_net(model, param_dict) + + return model + +def resnet20(num_classes: int = 10, pretrained: bool = False): + """ResNet20模型""" + resnet20_url = "https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/models/application/resnet50_224_new.ckpt" + resnet20_ckpt = "./LoadPretrainedModel/resnet20_new.ckpt" + return _resnet(resnet20_url, ResidualBlockBase, [3, 3, 3], num_classes, + pretrained, resnet20_ckpt, 64) + +# 定义ResNet20网络 +network = resnet20(pretrained=False) +print(network) +# 全连接层输入层的大小 +in_channel = network.fc.in_channels +fc = nn.Dense(in_channels=in_channel, out_channels=10) +# 重置全连接层 +network.fc = fc + +# 设置学习率 +num_epochs = 50 +lr = nn.cosine_decay_lr(min_lr=0.00001, max_lr=0.001, total_step=step_size_train * num_epochs, + step_per_epoch=step_size_train, decay_epoch=num_epochs) +# 定义优化器和损失函数 +opt = nn.Momentum(params=network.trainable_params(), learning_rate=lr, momentum=0.9) +loss_fn = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction='mean') + + +def forward_fn(inputs, targets): + logits = network(inputs) + loss = loss_fn(logits, targets) + return loss + + +grad_fn = ms.value_and_grad(forward_fn, None, opt.parameters) + + +def train_step(inputs, targets): + # print(inputs.shape) + # print(targets.shape) + loss, grads = grad_fn(inputs, targets) + opt(grads) + return loss + +import os + +# 创建迭代器 +data_loader_train = dataset_train.create_tuple_iterator(num_epochs=num_epochs) +data_loader_val = dataset_val.create_tuple_iterator(num_epochs=num_epochs) + +# 最佳模型存储路径 +best_acc = 0 +best_ckpt_dir = "./BestCheckpoint" +best_ckpt_path = "./BestCheckpoint/resnet20-best.ckpt" + +if not os.path.exists(best_ckpt_dir): + os.mkdir(best_ckpt_dir) + +import mindspore.ops as ops + + +def train(data_loader, epoch): + """模型训练""" + losses = [] + network.set_train(True) + + for i, (images, labels) in enumerate(data_loader): + # print(images.shape) + # print(labels.shape) + loss = train_step(images, labels) + if i % 100 == 0 or i == step_size_train - 1: + print('Epoch: [%3d/%3d], Steps: [%3d/%3d], Train Loss: [%5.3f]' % + (epoch + 1, num_epochs, i + 1, step_size_train, loss)) + losses.append(loss) + + return sum(losses) / len(losses) + + +def evaluate(data_loader): + """模型验证""" + network.set_train(False) + + correct_num = 0.0 # 预测正确个数 + total_num = 0.0 # 预测总数 + + for images, labels in data_loader: + logits = network(images) + pred = logits.argmax(axis=1) # 预测结果 + correct = ops.equal(pred, labels).reshape((-1, )) + correct_num += correct.sum().asnumpy() + total_num += correct.shape[0] + + acc = correct_num / total_num # 准确率 + + return acc + +# 开始循环训练 +print("Start Training Loop ...") + +for epoch in range(num_epochs): + curr_loss = train(data_loader_train, epoch) + curr_acc = evaluate(data_loader_val) + + print("-" * 50) + print("Epoch: [%3d/%3d], Average Train Loss: [%5.3f], Accuracy: [%5.3f]" % ( + epoch+1, num_epochs, curr_loss, curr_acc + )) + print("-" * 50) + + # 保存当前预测准确率最高的模型 + if curr_acc > best_acc: + best_acc = curr_acc + ms.save_checkpoint(network, best_ckpt_path) + +print("=" * 80) +print(f"End of validation the best Accuracy is: {best_acc: 5.3f}, " + f"save the best ckpt file in {best_ckpt_path}", flush=True) \ No newline at end of file diff --git a/examples/community/homomorphic_inference/mindspore_resnet50.ipynb b/examples/community/homomorphic_inference/mindspore_resnet50.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..12aed0fe8e29ea7cbd3e9551c0c87e103250f01a --- /dev/null +++ b/examples/community/homomorphic_inference/mindspore_resnet50.ipynb @@ -0,0 +1,1032 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "fa7e3e52", + "metadata": {}, + "source": [ + "[![下载Notebook](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/master/resource/_static/logo_notebook.svg)](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/master/tutorials/zh_cn/cv/mindspore_resnet50.ipynb) [![下载样例代码](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/master/resource/_static/logo_download_code.svg)](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/master/tutorials/zh_cn/cv/mindspore_resnet50.py) [![查看源文件](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/master/resource/_static/logo_source.svg)](https://gitee.com/mindspore/docs/blob/master/tutorials/source_zh_cn/cv/resnet50.ipynb)\n", + "\n", + "# ResNet50图像分类\n", + "\n", + "图像分类是最基础的计算机视觉应用,属于有监督学习类别,如给定一张图像(猫、狗、飞机、汽车等等),判断图像所属的类别。本章将介绍使用ResNet50网络对CIFAR-10数据集进行分类。\n", + "\n", + "## ResNet网络介绍\n", + "\n", + "ResNet50网络是2015年由微软实验室的何恺明提出,获得ILSVRC2015图像分类竞赛第一名。在ResNet网络提出之前,传统的卷积神经网络都是将一系列的卷积层和池化层堆叠得到的,但当网络堆叠到一定深度时,就会出现退化问题。下图是在CIFAR-10数据集上使用56层网络与20层网络训练误差和测试误差图,由图中数据可以看出,56层网络比20层网络训练误差和测试误差更大,随着网络的加深,其误差并没有如预想的一样减小。\n", + "\n", + "![resnet-1](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/master/tutorials/source_zh_cn/cv/images/resnet_1.png)\n", + "\n", + "ResNet网络提出了残差网络结构(Residual Network)来减轻退化问题,使用ResNet网络可以实现搭建较深的网络结构(突破1000层)。论文中使用ResNet网络在CIFAR-10数据集上的训练误差与测试误差图如下图所示,图中虚线表示训练误差,实线表示测试误差。由图中数据可以看出,ResNet网络层数越深,其训练误差和测试误差越小。\n", + "\n", + "![resnet-4](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/master/tutorials/source_zh_cn/cv/images/resnet_4.png)\n", + "\n", + "> 了解ResNet网络更多详细内容,参见[ResNet论文](https://arxiv.org/pdf/1512.03385.pdf)。" + ] + }, + { + "cell_type": "markdown", + "id": "a987ee48", + "metadata": {}, + "source": [ + "## 数据集准备与加载\n", + "\n", + "[CIFAR-10数据集](http://www.cs.toronto.edu/~kriz/cifar.html)共有60000张32*32的彩色图像,分为10个类别,每类有6000张图,数据集一共有50000张训练图片和10000张评估图片。首先,如下示例使用`download`接口下载并解压,目前仅支持解析二进制版本的CIFAR-10文件(CIFAR-10 binary version)。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1f9b81fb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Creating data folder...\n", + "Downloading data from https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/cifar-10-binary.tar.gz (162.2 MB)\n", + "\n", + "file_sizes: 100%|████████████████████████████| 170M/170M [00:17<00:00, 9.76MB/s]\n", + "Extracting tar.gz file...\n", + "Successfully downloaded / unzipped to ./datasets-cifar10-bin\n" + ] + }, + { + "data": { + "text/plain": [ + "'./datasets-cifar10-bin'" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from download import download\n", + "\n", + "url = \"https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/cifar-10-binary.tar.gz\"\n", + "\n", + "download(url, \"./datasets-cifar10-bin\", kind=\"tar.gz\", replace=True)" + ] + }, + { + "cell_type": "markdown", + "id": "7e9020ba", + "metadata": {}, + "source": [ + "下载后的数据集目录结构如下:\n", + "\n", + "```text\n", + "datasets-cifar10-bin/cifar-10-batches-bin\n", + "├── batches.meta.text\n", + "├── data_batch_1.bin\n", + "├── data_batch_2.bin\n", + "├── data_batch_3.bin\n", + "├── data_batch_4.bin\n", + "├── data_batch_5.bin\n", + "├── readme.html\n", + "└── test_batch.bin\n", + "\n", + "```\n", + "\n", + "然后,使用`mindspore.dataset.Cifar10Dataset`接口来加载数据集,并进行相关图像增强操作。" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "df7fb621", + "metadata": {}, + "outputs": [], + "source": [ + "import mindspore as ms\n", + "import mindspore.dataset as ds\n", + "import mindspore.dataset.vision as vision\n", + "import mindspore.dataset.transforms as transforms\n", + "from mindspore import dtype as mstype\n", + "\n", + "data_dir = \"./datasets-cifar10-bin/cifar-10-batches-bin\" # 数据集根目录\n", + "batch_size = 256 # 批量大小\n", + "image_size = 32 # 训练图像空间大小\n", + "workers = 4 # 并行线程个数\n", + "num_classes = 10 # 分类数量\n", + "\n", + "\n", + "def create_dataset_cifar10(dataset_dir, usage, resize, batch_size, workers):\n", + "\n", + " data_set = ds.Cifar10Dataset(dataset_dir=dataset_dir,\n", + " usage=usage,\n", + " num_parallel_workers=workers,\n", + " shuffle=True)\n", + "\n", + " trans = []\n", + " if usage == \"train\":\n", + " trans += [\n", + " vision.RandomCrop((32, 32), (4, 4, 4, 4)),\n", + " vision.RandomHorizontalFlip(prob=0.5)\n", + " ]\n", + "\n", + " trans += [\n", + " vision.Resize(resize),\n", + " vision.Rescale(1.0 / 255.0, 0.0),\n", + " vision.Normalize([0.4914, 0.4822, 0.4465], [0.2023, 0.1994, 0.2010]),\n", + " vision.HWC2CHW()\n", + " ]\n", + "\n", + " target_trans = transforms.TypeCast(mstype.int32)\n", + "\n", + " # 数据映射操作\n", + " data_set = data_set.map(operations=trans,\n", + " input_columns='image',\n", + " num_parallel_workers=workers)\n", + "\n", + " data_set = data_set.map(operations=target_trans,\n", + " input_columns='label',\n", + " num_parallel_workers=workers)\n", + "\n", + " # 批量操作\n", + " data_set = data_set.batch(batch_size)\n", + "\n", + " return data_set\n", + "\n", + "\n", + "# 获取处理后的训练与测试数据集\n", + "\n", + "dataset_train = create_dataset_cifar10(dataset_dir=data_dir,\n", + " usage=\"train\",\n", + " resize=image_size,\n", + " batch_size=batch_size,\n", + " workers=workers)\n", + "step_size_train = dataset_train.get_dataset_size()\n", + "\n", + "dataset_val = create_dataset_cifar10(dataset_dir=data_dir,\n", + " usage=\"test\",\n", + " resize=image_size,\n", + " batch_size=batch_size,\n", + " workers=workers)\n", + "step_size_val = dataset_val.get_dataset_size()" + ] + }, + { + "cell_type": "markdown", + "id": "21e86f95", + "metadata": {}, + "source": [ + "对CIFAR-10训练数据集进行可视化。" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "c3ffabb3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Image shape: (256, 3, 32, 32), Label shape: (256,)\n", + "Labels: [1 3 5 3 8 6]\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAgMAAAGDCAYAAAC2gxMSAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy88F64QAAAACXBIWXMAAA9hAAAPYQGoP6dpAABuC0lEQVR4nO29eZSdVZnv/7xnnuvUXJVKKgMJgQBhJgKBhEQUUFqxlb6gErz2VVHvXba6Wuhe/gJqA+rVi7fb1u7Vq7VdaIu0gthc0SYMgiQYxoSEhEyVueY6VXXm857z/v7wR/189ncL1bmEKvJ+P2u5XHvznHfce5+d83zr+zie53lCCCGEEN8SmOkLIIQQQsjMws0AIYQQ4nO4GSCEEEJ8DjcDhBBCiM/hZoAQQgjxOdwMEEIIIT6HmwFCCCHE53AzQAghhPgcbgYIIYQQn+ObzcCPfvQjueuuu2b6MqbFjTfeKKlUalqxCxYskBtvvHGq3dfXJ47jyPe///3jc3HEdxSLRbn11lvlsccem+lLIT7k1ltvFcdxZvoyTnhCM30BbxY/+tGP5KWXXpLPfOYzM30pbyj33XefZDKZmb4McgJTLBbltttuExGR1atXz+zFEEKOC77ZDJyonH322TN9CYQQQt7izMo0we7du+UjH/mILFmyRBKJhPT09MjVV18tW7duVXHf//73xXEc6evrU/2PPfaYOI4z9bPm6tWr5cEHH5T9+/eL4zhT/3uV0dFR+eQnPyk9PT0SiURk0aJF8td//ddSqVTUcR3HkU9/+tPyve99T5YuXSrxeFzOO+882bRpk3ieJ1//+tdl4cKFkkqlZM2aNbJ79264t3/+53+WM888U2KxmLS0tMg111wjL7/8svU5bNu2TdauXSvJZFLa29vl05/+tBSLRRVjpgn+GLt27ZLrr79eOjo6JBqNyqmnnirf/va3X/dz5K3Njh075LrrrpPOzk6JRqPS29srN9xwg1QqFRkaGpJPfvKTsmzZMkmlUtLR0SFr1qyRJ554YurzfX190t7eLiIit91229Tcmc6YI+Q/y4MPPihnnXWWRKNRWbhwofzP//k/IaZcLsstt9wiCxculEgkIj09PfKpT31KcrmciqtUKvK5z31Ourq6JJFIyKWXXirPPvvstNdMvzErfxk4cuSItLa2yp133int7e0yOjoq//Iv/yIrVqyQ559/XpYuXfqfOt7f//3fy8c+9jHZs2eP3Hfffeq/lctlueyyy2TPnj1y2223yfLly+WJJ56QO+64Q1544QV58MEHVfy///u/y/PPPy933nmnOI4jX/jCF+Rd73qXrFu3Tvbu3St/93d/J+Pj4/LZz35W/vRP/1ReeOGFqY3HHXfcIX/1V38l1113ndxxxx0yMjIit956q1x44YWyefNmWbJkydR5arWaXHXVVfLxj39cbr75ZnnqqafkK1/5iuzfv19+8Ytf/Kfuf/v27XLRRRdJb2+vfOMb35Curi751a9+Jf/jf/wPGR4elvXr1/+njkfeGrz44ouycuVKaWtrky996UuyZMkSOXr0qDzwwANSrVZldHRURETWr18vXV1dks/n5b777pPVq1fLhg0bZPXq1dLd3S0PPfSQXHHFFfLRj35U/vzP/1xEZGqDQMgbxYYNG+Q973mPXHjhhfLjH/9Y6vW6fO1rX5OBgYGpGM/z5L3vfa9s2LBBbrnlFrnkkktky5Ytsn79etm4caNs3LhRotGoiIh85CMfkXvuuUf+8i//UtasWSPbt2+Xa665RiYmJmbqFmc33lsA13W9arXqLVmyxPuLv/iLqf7vfe97noh4+/btU/GPPvqoJyLeo48+OtX3rne9y5s/fz4c+7vf/a4nIt5PfvIT1f/Vr37VExHv17/+9VSfiHhdXV1ePp+f6rv//vs9EfHOOussr9FoTPXfddddnoh4W7Zs8TzP88bGxrx4PO5dddVV6jwHDhzwotGod/3110/1rVu3zhMR71vf+paK/Zu/+RtPRLwnn3xyqm/+/PneunXrptr79u3zRMT73ve+N9X3zne+05s7d643Pj6ujvfpT3/ai8Vi3ujoKDwX8tZnzZo1Xjab9QYHB6cV77quV6vVvLVr13rXXHPNVP/Q0JAnIt769euP05US4nkrVqzw5syZ45VKpam+iYkJr6WlxXv1q+qhhx7yRMT72te+pj57zz33eCLi/eM//qPneZ63bds2T0S8L3zhCyruX//1Xz0RUWsm+T2zMk3guq7cfvvtsmzZMolEIhIKhSQSiciuXbv+6E/qx8ojjzwiyWRS3v/+96v+V39G2rBhg+q/7LLLJJlMTrVPPfVUERG58sorVerh1f79+/eLiMjGjRulVCrBz1Pz5s2TNWvWwHlERD74wQ+q9vXXXy8iIo8++uh0b0/K5bJs2LBBrrnmGkkkEuK67tT/rrrqKimXy7Jp06ZpH4+8NSgWi/L444/Ltdde+5r/iv/ud78r55xzjsRiMQmFQhIOh2XDhg1v+Dwj5LUoFAqyefNmed/73iexWGyqP51Oy9VXXz3VfuSRR0REYB39wAc+IMlkcmodffzxx0VE5Nprr1Vx73//+yUUmpU/iM84s3Iz8NnPfla++MUvynvf+175xS9+IU8//bRs3rxZzjzzTCmVSm/ouUZGRqSrqwv+dKWjo0NCoZCMjIyo/paWFtWORCKv2V8ul6fOIyLS3d0N1zBnzhw4TygUktbWVtXX1dWljjUdRkZGxHVd+du//VsJh8Pqf1dddZWIiAwPD0/7eOStwdjYmNTrdZk7d+4fjfnmN78pN910k6xYsUJ++tOfyqZNm2Tz5s1yxRVXvOHzjJDXYmxsTBqNxtQa94f8Yd/IyIiEQiHY4DqOI11dXVNr46v/39nZqeJs6yr5PbNyi3T33XfLDTfcILfffrvqHx4elmw2O9V+dQdpCv3+M19ura2t8vTTT4vneWpDMDg4KK7rSltb2zHcgf08IiJHjx6F/3bkyBE4j+u6MjIyogZuf3+/OtZ0aG5ulmAwKB/+8IflU5/6lDVm4cKF0z4eeWvQ0tIiwWBQDh069Edj7r77blm9erV85zvfUf2Tk5PH+/IIUTQ3N4vjOFNr3B/yh32tra3iuq4MDQ2pDYHnedLf3y/nn3/+VJyIyMDAgPT09EzFvbquEmRW/jLgOM6UCORVHnzwQTl8+LDqW7BggYiIbNmyRfU/8MADcMxoNGr9187atWsln8/L/fffr/p/8IMfTP33N4ILL7xQ4vG43H333ar/0KFD8sgjj1jP88Mf/lC1f/SjH4nIf+5vvROJhFx22WXy/PPPy/Lly+W8886D/3GnfOIRj8dl1apVcu+99/7RzbFtnm3ZskU2btyo+l6N4a8F5HiRTCblggsukJ/97GdTv6aK/H5j+oeC6VfXSXMd/elPfyqFQmHqv1966aUiInLPPfeouH/7t38T13WPyz281ZmVvwy8+93vlu9///tyyimnyPLly+XZZ5+Vr3/96/CT5/nnny9Lly6Vz3/+8+K6rjQ3N8t9990nTz75JBzzjDPOkJ/97Gfyne98R84991wJBAJy3nnnyQ033CDf/va3Zd26ddLX1ydnnHGGPPnkk3L77bfLVVddJW9/+9vfkHvKZrPyxS9+Uf7qr/5KbrjhBrnuuutkZGREbrvtNonFYqDoj0Qi8o1vfEPy+bycf/75U39NcOWVV8rKlSv/U+f+1re+JStXrpRLLrlEbrrpJlmwYIFMTk7K7t275Re/+MVUHo6cWHzzm9+UlStXyooVK+Tmm2+WxYsXy8DAgDzwwAPyD//wD/Lud79bvvzlL8v69etl1apVsnPnTvnSl74kCxcuVAtmOp2W+fPny89//nNZu3attLS0SFtb29RmnJA3gi9/+ctyxRVXyOWXXy6f+9znpF6vy1e/+lVJJpNTf/ly+eWXyzvf+U75whe+IBMTE3LxxRdP/TXB2WefLR/+8IdFROS0006T6667Tr7xjW9IMBiUNWvWyLZt2+Qb3/iGNDU1SSAwK/8dPLPMtILRxtjYmPfRj37U6+jo8BKJhLdy5UrviSee8FatWuWtWrVKxb7yyiveO97xDi+TyXjt7e3ef//v/9178MEH4a8JRkdHvfe///1eNpv1HMfx/vDWR0ZGvE984hNed3e3FwqFvPnz53u33HKLVy6X1blExPvUpz6l+l5V73/9619X/a/+RcO9996r+v/pn/7JW758uReJRLympibvPe95j7dt2zYVs27dOi+ZTHpbtmzxVq9e7cXjca+lpcW76aab1F8yeN70/prg1f7/+l//q9fT0+OFw2Gvvb3du+iii7yvfOUr8PzJicP27du9D3zgA15ra6sXiUS83t5e78Ybb/TK5bJXqVS8z3/+815PT48Xi8W8c845x7v//vu9devWwV/ePPzww97ZZ5/tRaNRqrHJceOBBx6YWh97e3u9O++801u/fr1ar0ulkveFL3zBmz9/vhcOh73u7m7vpptu8sbGxtSxyuWy99nPftbr6OjwYrGY97a3vc3buHGj19TUpP4qjfwex/M8b+a2IoQQQsibw1NPPSUXX3yx/PCHP5z66yzye7gZIIQQcsLxH//xH7Jx40Y599xzJR6Py4svvih33nmnNDU1yZYtW9SfMJJZqhkghBBC/m/IZDLy61//Wu666y6ZnJyUtrY2ufLKK+WOO+7gRsACfxkghBBCfA4llYQQQojP4WaAEEII8TncDBBCCCE+h5sBQgghxOdM+68JzEI+0+XsFdotLxhG69tSLazatSoqPUOO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fwwXFludh5rh2RCNY878lCULoC8R03E7ugYhZnBE6wjMgkMiIq0ZbfhyZBhzwy/u1McuViwFeoLYl4zpZ9kcw3trSujn35HFgjTdbVnVdhJNEDMTBC3FTcYntGlTzaLBiCX1PUeCmMeenNRag3AMn0skpo2B8pOYs9+ztx/6yhWd205n8N0Nj+dU2wukIKbiaF2BuPg84kn8nOlfFLGMHdPoy6ZTMYsQBS25f9OrKBi0zFOLcUowYBScquE7SqX0O/mzD1wDMdQMEL/DXwYIIYQQn8PNACGEEOJzuBkghBBCfA43A4QQQojPcbzplDMihBBCyAkLfxkghBBCfA43A4QQQojP4WaAEEII8TncDBBCCCE+h5sBQgghxOdwM0AIIYT4HG4GCCGEEJ/DzQAhhBDic7gZIIQQQnwONwOEEEKIz+FmgBBCCPE53AwQQgghPoebAUIIIcTncDNACCGE+BxuBgghhBCfw80AIYQQ4nO4GSCEEEJ8DjcDhBBCiM/hZoAQQgjxOdwMEEIIIT6HmwFCCCHE53AzQAghhPgcbgYIIYQQn8PNACGEEOJzuBkghBBCfA43A4QQQojP4WaAEEII8TncDBBCCCE+h5sBQgghxOdwM0AIIYT4HG4GCCGEEJ/DzQAhhBDic7gZIIQQQnwONwOEEEKIz+FmgBBCCPE53AwQQgghPoebgTeJYrEot956qzz22GMzfSnEp6xevVpOP/30143r6+sTx3Hk+9///vG/KEL+P+655x457bTTJB6Pi+M48sILL8z0JfmK0ExfgF8oFoty2223icjvF2VCZivd3d2yceNGOemkk2b6UohPGBoakg9/+MNyxRVXyN///d9LNBqVk08+eaYvy1dwM0AIUUSjUXnb294205dBfMQrr7witVpNPvShD8mqVav+aFyxWJREIvEmXpl/YJpgGuzYsUOuu+466ezslGg0Kr29vXLDDTdIpVKRoaEh+eQnPynLli2TVColHR0dsmbNGnniiSemPt/X1yft7e0iInLbbbeJ4zjiOI7ceOONM3RH5ERkaGhIPvaxj8m8efMkGo1Ke3u7XHzxxfLwww+ruM2bN8sll1wiiURCFi1aJHfeeac0Go2p/25LE9x6663iOI48//zz8r73vU8ymYw0NTXJhz70IRkaGnqzbpGcgNx4442ycuVKERH5sz/7M3EcR1avXi033nijpFIp2bp1q7zjHe+QdDota9euFRGR0dFR+eQnPyk9PT0SiURk0aJF8td//ddSqVTUsXO5nHz0ox+VlpYWSaVS8q53vUv27t0rjuPIrbfe+mbf6qyGvwy8Di+++KKsXLlS2tra5Etf+pIsWbJEjh49Kg888IBUq1UZHR0VEZH169dLV1eX5PN5ue+++2T16tWyYcMGWb16tXR3d8tDDz0kV1xxhXz0ox+VP//zPxcRmdogEPJG8OEPf1iee+45+Zu/+Rs5+eSTJZfLyXPPPScjIyNTMf39/fLBD35QPve5z8n69evlvvvuk1tuuUXmzJkjN9xww+ue45prrpFrr71WPvGJT8i2bdvki1/8omzfvl2efvppCYfDx/P2yAnKF7/4RbngggvkU5/6lNx+++1y2WWXSSaTka997WtSrVblT/7kT+TjH/+43HzzzeK6rpTLZbnssstkz549ctttt8ny5cvliSeekDvuuENeeOEFefDBB0VEpNFoyNVXXy3PPPOM3HrrrXLOOefIxo0b5YorrpjhO56leOQ1WbNmjZfNZr3BwcFpxbuu69VqNW/t2rXeNddcM9U/NDTkiYi3fv3643SlxO+kUinvM5/5zB/976tWrfJExHv66adV/7Jly7x3vvOdU+19+/Z5IuJ973vfm+pbv369JyLeX/zFX6jP/vCHP/RExLv77rvfmJsgvuTRRx/1RMS79957p/rWrVvniYj3z//8zyr2u9/9rici3k9+8hPV/9WvftUTEe/Xv/6153me9+CDD3oi4n3nO99RcXfccQfXYgtME7wGxWJRHn/8cbn22mtf81/x3/3ud+Wcc86RWCwmoVBIwuGwbNiwQV5++eU38WqJ37ngggvk+9//vnzlK1+RTZs2Sa1Wg5iuri654IILVN/y5ctl//790zrHBz/4QdW+9tprJRQKyaOPPnrsF07Ia/Cnf/qnqv3II49IMpmU97///ar/1bTrhg0bRETk8ccfF5Hfj9E/5LrrrjtOV/rWhpuB12BsbEzq9brMnTv3j8Z885vflJtuuklWrFghP/3pT2XTpk2yefNmueKKK6RUKr2JV0v8zj333CPr1q2Tf/qnf5ILL7xQWlpa5IYbbpD+/v6pmNbWVvhcNBqd9ljt6upS7VAoJK2trSoVQcgbRSKRkEwmo/pGRkakq6tLHMdR/R0dHRIKhabG4sjIiIRCIWlpaVFxnZ2dx/ei36JQM/AatLS0SDAYlEOHDv3RmLvvvltWr14t3/nOd1T/5OTk8b48QhRtbW1y1113yV133SUHDhyQBx54QG6++WYZHByUhx566A05R39/v/T09Ey1XdeVkZER6yaDkP9bzC98kd9vaJ9++mnxPE/998HBQXFdV9ra2qbiXNeV0dFRtSH4w80x+f/hLwOvQTwel1WrVsm9994rw8PD1hjHcSQajaq+LVu2yMaNG1XfqzH8tYC8GfT29sqnP/1pufzyy+W55557w477wx/+ULV/8pOfiOu69M4gbxpr166VfD4v999/v+r/wQ9+MPXfRWTqTxTvueceFffjH//4+F/kWxD+MvA6fPOb35SVK1fKihUr5Oabb5bFixfLwMCAPPDAA/IP//AP8u53v1u+/OUvy/r162XVqlWyc+dO+dKXviQLFy4U13WnjpNOp2X+/Pny85//XNauXSstLS3S1tYmCxYsmLmbIycM4+Pjctlll8n1118vp5xyiqTTadm8ebM89NBD8r73ve8NO8/PfvYzCYVCcvnll0/9NcGZZ54JeVlCjhc33HCDfPvb35Z169ZJX1+fnHHGGfLkk0/K7bffLldddZW8/e1vFxGRK664Qi6++GL53Oc+JxMTE3LuuefKxo0bpzYNgQD/LayYaQXjW4Ht27d7H/jAB7zW1lYvEol4vb293o033uiVy2WvUql4n//8572enh4vFot555xzjnf//fd769at8+bPn6+O8/DDD3tnn322F41GPRHx1q1bNyP3Q048yuWy94lPfMJbvny5l8lkvHg87i1dutRbv369VygUPM/7/V8TnHbaafBZc6y+1l8TPPvss97VV1/tpVIpL51Oe9ddd503MDBwvG+PnOD8sb8mSCaT1viRkRHvE5/4hNfd3e2FQiFv/vz53i233OKVy2UVNzo66n3kIx/xstmsl0gkvMsvv9zbtGmTJyLet771reN6T281HM/zvBndjRBCZj233nqr3HbbbTI0NDSVkyXkrciPfvQj+eAHPyi//e1v5aKLLprpy5k1ME1ACCHkhORf//Vf5fDhw3LGGWdIIBCQTZs2yde//nW59NJLuREw4GaAEELICUk6nZYf//jH8pWvfEUKhYJ0d3fLjTfeKF/5yldm+tJmHUwTEEIIIT6HckpCCCHE53Az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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "\n", + "data_iter = next(dataset_train.create_dict_iterator())\n", + "\n", + "images = data_iter[\"image\"].asnumpy()\n", + "labels = data_iter[\"label\"].asnumpy()\n", + "print(f\"Image shape: {images.shape}, Label shape: {labels.shape}\")\n", + "\n", + "# 训练数据集中,前六张图片所对应的标签\n", + "print(f\"Labels: {labels[:6]}\")\n", + "\n", + "classes = []\n", + "\n", + "with open(data_dir + \"/batches.meta.txt\", \"r\") as f:\n", + " for line in f:\n", + " line = line.rstrip()\n", + " if line:\n", + " classes.append(line)\n", + "\n", + "# 训练数据集的前六张图片\n", + "plt.figure()\n", + "for i in range(6):\n", + " plt.subplot(2, 3, i + 1)\n", + " image_trans = np.transpose(images[i], (1, 2, 0))\n", + " mean = np.array([0.4914, 0.4822, 0.4465])\n", + " std = np.array([0.2023, 0.1994, 0.2010])\n", + " image_trans = std * image_trans + mean\n", + " image_trans = np.clip(image_trans, 0, 1)\n", + " plt.title(f\"{classes[labels[i]]}\")\n", + " plt.imshow(image_trans)\n", + " plt.axis(\"off\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "76c96f76", + "metadata": {}, + "source": [ + "## 构建网络\n", + "\n", + "残差网络结构(Residual Network)是ResNet网络的主要亮点,ResNet使用残差网络结构后可有效地减轻退化问题,实现更深的网络结构设计,提高网络的训练精度。本节首先讲述如何构建残差网络结构,然后通过堆叠残差网络来构建ResNet50网络。\n", + "\n", + "### 构建残差网络结构\n", + "\n", + "残差网络结构图如下图所示,残差网络由两个分支构成:一个主分支,一个shortcuts(图中弧线表示)。主分支通过堆叠一系列的卷积操作得到,shortcuts从输入直接到输出,主分支输出的特征矩阵$F(x)$加上shortcuts输出的特征矩阵$x$得到$F(x)+x$,通过Relu激活函数后即为残差网络最后的输出。\n", + "\n", + "![residual](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/master/tutorials/source_zh_cn/cv/images/resnet_3.png)\n", + "\n", + "残差网络结构主要由两种,一种是Building Block,适用于较浅的ResNet网络,如ResNet18和ResNet34;另一种是Bottleneck,适用于层数较深的ResNet网络,如ResNet50、ResNet101和ResNet152。\n", + "\n", + "#### Building Block\n", + "\n", + "Building Block结构图如下图所示,主分支有两层卷积网络结构:\n", + "\n", + "+ 主分支第一层网络以输入channel为64为例,首先通过一个$3\\times3$的卷积层,然后通过Batch Normalization层,最后通过Relu激活函数层,输出channel为64;\n", + "+ 主分支第二层网络的输入channel为64,首先通过一个$3\\times3$的卷积层,然后通过Batch Normalization层,输出channel为64。\n", + "\n", + "最后将主分支输出的特征矩阵与shortcuts输出的特征矩阵相加,通过Relu激活函数即为Building Block最后的输出。\n", + "\n", + "![building-block-5](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/master/tutorials/source_zh_cn/cv/images/resnet_5.png)\n", + "\n", + "主分支与shortcuts输出的特征矩阵相加时,需要保证主分支与shortcuts输出的特征矩阵shape相同。如果主分支与shortcuts输出的特征矩阵shape不相同,如输出channel是输入channel的一倍时,shortcuts上需要使用数量与输出channel相等,大小为$1\\times1$的卷积核进行卷积操作;若输出的图像较输入图像缩小一倍,则要设置shortcuts中卷积操作中的`stride`为2,主分支第一层卷积操作的`stride`也需设置为2。\n", + "\n", + "如下代码定义`ResidualBlockBase`类实现Building Block结构。" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "c7ac0e2d", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Type, Union, List, Optional\n", + "import mindspore.nn as nn\n", + "from mindspore.common.initializer import Normal\n", + "\n", + "# 初始化卷积层与BatchNorm的参数\n", + "weight_init = Normal(mean=0, sigma=0.02)\n", + "gamma_init = Normal(mean=1, sigma=0.02)\n", + "\n", + "class ResidualBlockBase(nn.Cell):\n", + " expansion: int = 1 # 最后一个卷积核数量与第一个卷积核数量相等\n", + "\n", + " def __init__(self, in_channel: int, out_channel: int,\n", + " stride: int = 1, norm: Optional[nn.Cell] = None,\n", + " down_sample: Optional[nn.Cell] = None) -> None:\n", + " super(ResidualBlockBase, self).__init__()\n", + " if not norm:\n", + " self.norm = nn.BatchNorm2d(out_channel)\n", + " else:\n", + " self.norm = norm\n", + "\n", + " self.conv1 = nn.Conv2d(in_channel, out_channel,\n", + " kernel_size=3, stride=stride,\n", + " weight_init=weight_init)\n", + " self.conv2 = nn.Conv2d(in_channel, out_channel,\n", + " kernel_size=3, weight_init=weight_init)\n", + " self.relu = nn.ReLU()\n", + " self.down_sample = down_sample\n", + "\n", + " def construct(self, x):\n", + " \"\"\"ResidualBlockBase construct.\"\"\"\n", + " identity = x # shortcuts分支\n", + "\n", + " out = self.conv1(x) # 主分支第一层:3*3卷积层\n", + " out = self.norm(out)\n", + " out = self.relu(out)\n", + " out = self.conv2(out) # 主分支第二层:3*3卷积层\n", + " out = self.norm(out)\n", + "\n", + " if self.down_sample is not None:\n", + " identity = self.down_sample(x)\n", + " out += identity # 输出为主分支与shortcuts之和\n", + " out = self.relu(out)\n", + "\n", + " return out" + ] + }, + { + "cell_type": "markdown", + "id": "aaa15d3c", + "metadata": {}, + "source": [ + "#### Bottleneck\n", + "\n", + "Bottleneck结构图如下图所示,在输入相同的情况下Bottleneck结构相对Building Block结构的参数数量更少,更适合层数较深的网络,ResNet50使用的残差结构就是Bottleneck。该结构的主分支有三层卷积结构,分别为$1\\times1$的卷积层、$3\\times3$卷积层和$1\\times1$的卷积层,其中$1\\times1$的卷积层分别起降维和升维的作用。\n", + "\n", + "+ 主分支第一层网络以输入channel为256为例,首先通过数量为64,大小为$1\\times1$的卷积核进行降维,然后通过Batch Normalization层,最后通过Relu激活函数层,其输出channel为64;\n", + "+ 主分支第二层网络通过数量为64,大小为$3\\times3$的卷积核提取特征,然后通过Batch Normalization层,最后通过Relu激活函数层,其输出channel为64;\n", + "+ 主分支第三层通过数量为256,大小$1\\times1$的卷积核进行升维,然后通过Batch Normalization层,其输出channel为256。\n", + "\n", + "最后将主分支输出的特征矩阵与shortcuts输出的特征矩阵相加,通过Relu激活函数即为Bottleneck最后的输出。\n", + "\n", + "![building-block-6](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/master/tutorials/source_zh_cn/cv/images/resnet_6.png)\n", + "\n", + "主分支与shortcuts输出的特征矩阵相加时,需要保证主分支与shortcuts输出的特征矩阵shape相同。如果主分支与shortcuts输出的特征矩阵shape不相同,如输出channel是输入channel的一倍时,shortcuts上需要使用数量与输出channel相等,大小为$1\\times1$的卷积核进行卷积操作;若输出的图像较输入图像缩小一倍,则要设置shortcuts中卷积操作中的`stride`为2,主分支第二层卷积操作的`stride`也需设置为2。\n", + "\n", + "如下代码定义`ResidualBlock`类实现Bottleneck结构。" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "0d46f98e", + "metadata": {}, + "outputs": [], + "source": [ + "class ResidualBlock(nn.Cell):\n", + " expansion = 4 # 最后一个卷积核的数量是第一个卷积核数量的4倍\n", + "\n", + " def __init__(self, in_channel: int, out_channel: int,\n", + " stride: int = 1, down_sample: Optional[nn.Cell] = None) -> None:\n", + " super(ResidualBlock, self).__init__()\n", + "\n", + " self.conv1 = nn.Conv2d(in_channel, out_channel,\n", + " kernel_size=1, weight_init=weight_init)\n", + " self.norm1 = nn.BatchNorm2d(out_channel)\n", + " self.conv2 = nn.Conv2d(out_channel, out_channel,\n", + " kernel_size=3, stride=stride,\n", + " weight_init=weight_init)\n", + " self.norm2 = nn.BatchNorm2d(out_channel)\n", + " self.conv3 = nn.Conv2d(out_channel, out_channel * self.expansion,\n", + " kernel_size=1, weight_init=weight_init)\n", + " self.norm3 = nn.BatchNorm2d(out_channel * self.expansion)\n", + "\n", + " self.relu = nn.ReLU()\n", + " self.down_sample = down_sample\n", + "\n", + " def construct(self, x):\n", + "\n", + " identity = x # shortscuts分支\n", + "\n", + " out = self.conv1(x) # 主分支第一层:1*1卷积层\n", + " out = self.norm1(out)\n", + " out = self.relu(out)\n", + " out = self.conv2(out) # 主分支第二层:3*3卷积层\n", + " out = self.norm2(out)\n", + " out = self.relu(out)\n", + " out = self.conv3(out) # 主分支第三层:1*1卷积层\n", + " out = self.norm3(out)\n", + "\n", + " if self.down_sample is not None:\n", + " identity = self.down_sample(x)\n", + "\n", + " out += identity # 输出为主分支与shortcuts之和\n", + " out = self.relu(out)\n", + "\n", + " return out" + ] + }, + { + "cell_type": "markdown", + "id": "d1d8dfc9", + "metadata": {}, + "source": [ + "#### 构建ResNet50网络\n", + "\n", + "ResNet网络层结构如下图所示,以输入彩色图像$224\\times224$为例,首先通过数量64,卷积核大小为$7\\times7$,stride为2的卷积层conv1,该层输出图片大小为$112\\times112$,输出channel为64;然后通过一个$3\\times3$的最大下采样池化层,该层输出图片大小为$56\\times56$,输出channel为64;再堆叠4个残差网络块(conv2_x、conv3_x、conv4_x和conv5_x),此时输出图片大小为$7\\times7$,输出channel为2048;最后通过一个平均池化层、全连接层和softmax,得到分类概率。\n", + "\n", + "![resnet-layer](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/master/tutorials/source_zh_cn/cv/images/resnet_2.png)\n", + "\n", + "对于每个残差网络块,以ResNet50网络中的conv2_x为例,其由3个Bottleneck结构堆叠而成,每个Bottleneck输入的channel为64,输出channel为256。\n", + "\n", + "如下示例定义`make_layer`实现残差块的构建,其参数如下所示:\n", + "\n", + "+ `last_out_channel`:上一个残差网络输出的通道数。\n", + "+ `block`:残差网络的类别,分别为`ResidualBlockBase`和`ResidualBlock`。\n", + "+ `channel`:残差网络块1*1卷积层的输出通道数\n", + "+ `block_nums`:残差网络块堆叠的个数。\n", + "+ `stride`:卷积移动的步幅。" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "3dfa40a1", + "metadata": {}, + "outputs": [], + "source": [ + "def make_layer(last_out_channel, block: Type[Union[ResidualBlockBase, ResidualBlock]],\n", + " channel: int, block_nums: int, stride: int = 1):\n", + " down_sample = None # shortcuts分支\n", + "\n", + " if stride != 1 or last_out_channel != channel * block.expansion:\n", + "\n", + " down_sample = nn.SequentialCell([\n", + " nn.Conv2d(last_out_channel, channel * block.expansion,\n", + " kernel_size=1, stride=stride, weight_init=weight_init),\n", + " nn.BatchNorm2d(channel * block.expansion, gamma_init=gamma_init)\n", + " ])\n", + "\n", + " layers = []\n", + " layers.append(block(last_out_channel, channel, stride=stride, down_sample=down_sample))\n", + "\n", + " in_channel = channel * block.expansion\n", + " # 堆叠残差网络\n", + " for _ in range(1, block_nums):\n", + "\n", + " layers.append(block(in_channel, channel))\n", + "\n", + " return nn.SequentialCell(layers)" + ] + }, + { + "cell_type": "markdown", + "id": "67dae353", + "metadata": {}, + "source": [ + "ResNet50网络共有5个卷积结构,一个平均池化层,一个全连接层,以CIFAR-10数据集为例:\n", + "\n", + "+ **conv1**:输入图片大小为$32\\times32$,输入channel为3。首先经过一个卷积核数量为64,卷积核大小为$7\\times7$,stride为2的卷积层;然后通过一个Batch Normalization层;最后通过ReLu激活函数。该层输出feature map大小为$16\\times16$,输出channel为64。\n", + "+ **conv2_x**:输入feature map大小为$16\\times16$,输入channel为64。首先经过一个卷积核大小为$3\\times3$,stride为2的最大下采样池化操作;然后堆叠3个$[1\\times1,64;3\\times3,64;1\\times1,256]$结构的Bottleneck。该层输出feature map大小为$8\\times8$,输出channel为256。\n", + "+ **conv3_x**:输入feature map大小为$8\\times8$,输入channel为256。该层堆叠4个[1×1,128;3×3,128;1×1,512]结构的Bottleneck。该层输出feature map大小为$4\\times4$,输出channel为512。\n", + "+ **conv4_x**:输入feature map大小为$4\\times4$,输入channel为512。该层堆叠6个[1×1,256;3×3,256;1×1,1024]结构的Bottleneck。该层输出feature map大小为$2\\times2$,输出channel为1024。\n", + "+ **conv5_x**:输入feature map大小为$2\\times2$,输入channel为1024。该层堆叠3个[1×1,512;3×3,512;1×1,2048]结构的Bottleneck。该层输出feature map大小为$1\\times1$,输出channel为2048。\n", + "+ **average pool & fc**:输入channel为2048,输出channel为分类的类别数。\n", + "\n", + "如下示例代码实现ResNet50模型的构建,通过用调函数`resnet50`即可构建ResNet50模型,函数`resnet50`参数如下:\n", + "\n", + "+ `num_classes`:分类的类别数,默认类别数为1000。\n", + "+ `pretrained`:下载对应的训练模型,并加载预训练模型中的参数到网络中。" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "1ebef3d0", + "metadata": {}, + "outputs": [], + "source": [ + "from mindspore import load_checkpoint, load_param_into_net\n", + "\n", + "\n", + "class ResNet(nn.Cell):\n", + " def __init__(self, block: Type[Union[ResidualBlockBase, ResidualBlock]],\n", + " layer_nums: List[int], num_classes: int, input_channel: int) -> None:\n", + " super(ResNet, self).__init__()\n", + "\n", + " self.relu = nn.ReLU()\n", + " # 第一个卷积层,输入channel为3(彩色图像),输出channel为64\n", + " self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, weight_init=weight_init)\n", + " self.norm = nn.BatchNorm2d(64)\n", + " # 最大池化层,缩小图片的尺寸\n", + " self.max_pool = nn.MaxPool2d(kernel_size=3, stride=2, pad_mode='same')\n", + " # 各个残差网络结构块定义\n", + " self.layer1 = make_layer(64, block, 64, layer_nums[0])\n", + " self.layer2 = make_layer(64 * block.expansion, block, 128, layer_nums[1], stride=2)\n", + " self.layer3 = make_layer(128 * block.expansion, block, 256, layer_nums[2], stride=2)\n", + " self.layer4 = make_layer(256 * block.expansion, block, 512, layer_nums[3], stride=2)\n", + " # 平均池化层\n", + " self.avg_pool = nn.AvgPool2d()\n", + " # flattern层\n", + " self.flatten = nn.Flatten()\n", + " # 全连接层\n", + " self.fc = nn.Dense(in_channels=input_channel, out_channels=num_classes)\n", + "\n", + " def construct(self, x):\n", + "\n", + " x = self.conv1(x)\n", + " x = self.norm(x)\n", + " x = self.relu(x)\n", + " x = self.max_pool(x)\n", + "\n", + " x = self.layer1(x)\n", + " x = self.layer2(x)\n", + " x = self.layer3(x)\n", + " x = self.layer4(x)\n", + " print(x.shape)\n", + " x = self.avg_pool(x)\n", + " print(x.shape)\n", + " x = self.flatten(x)\n", + " print(x.shape)\n", + " x = self.fc(x)\n", + "\n", + " return x" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "d16e658e", + "metadata": {}, + "outputs": [], + "source": [ + "def _resnet(model_url: str, block: Type[Union[ResidualBlockBase, ResidualBlock]],\n", + " layers: List[int], num_classes: int, pretrained: bool, pretrained_ckpt: str,\n", + " input_channel: int):\n", + " model = ResNet(block, layers, num_classes, input_channel)\n", + "\n", + " if pretrained:\n", + " # 加载预训练模型\n", + " download(url=model_url, path=pretrained_ckpt, replace=True)\n", + " param_dict = load_checkpoint(pretrained_ckpt)\n", + " load_param_into_net(model, param_dict)\n", + "\n", + " return model\n", + "\n", + "\n", + "def resnet50(num_classes: int = 1000, pretrained: bool = False):\n", + " \"\"\"ResNet50模型\"\"\"\n", + " resnet50_url = \"https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/models/application/resnet50_224_new.ckpt\"\n", + " resnet50_ckpt = \"./LoadPretrainedModel/resnet50_224_new.ckpt\"\n", + " return _resnet(resnet50_url, ResidualBlock, [3, 4, 6, 3], num_classes,\n", + " pretrained, resnet50_ckpt, 2048)" + ] + }, + { + "cell_type": "markdown", + "id": "d40bd05a", + "metadata": {}, + "source": [ + "## 模型训练与评估\n", + "\n", + "本节使用[ResNet50预训练模型](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/models/application/resnet50_224_new.ckpt)进行微调。调用`resnet50`构造ResNet50模型,并设置`pretrained`参数为True,将会自动下载ResNet50预训练模型,并加载预训练模型中的参数到网络中。然后定义优化器和损失函数,逐个epoch打印训练的损失值和评估精度,并保存评估精度最高的ckpt文件(resnet50-best.ckpt)到当前路径的./BestCheckPoint下。\n", + "\n", + "由于预训练模型全连接层(fc)的输出大小(对应参数`num_classes`)为1000, 为了成功加载预训练权重,我们将模型的全连接输出大小设置为默认的1000。CIFAR10数据集共有10个分类,在使用该数据集进行训练时,需要将加载好预训练权重的模型全连接层输出大小重置为10。\n", + "\n", + "> 此处我们展示了5个epochs的训练过程,如果想要达到理想的训练效果,建议训练80个epochs。" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "9cf10c03", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ResNet<\n", + " (relu): ReLU<>\n", + " (conv1): Conv2d, bias_init=None, format=NCHW>\n", + " (norm): BatchNorm2d\n", + " (max_pool): MaxPool2d\n", + " (layer1): SequentialCell<\n", + " (0): ResidualBlock<\n", + " (conv1): Conv2d, bias_init=None, format=NCHW>\n", + " (norm1): BatchNorm2d\n", + " (conv2): Conv2d, bias_init=None, format=NCHW>\n", + " (norm2): BatchNorm2d\n", + " (conv3): Conv2d, bias_init=None, format=NCHW>\n", + " (norm3): BatchNorm2d\n", + " (relu): ReLU<>\n", + " (down_sample): SequentialCell<\n", + " (0): Conv2d, bias_init=None, format=NCHW>\n", + " (1): BatchNorm2d\n", + " >\n", + " >\n", + " (1): ResidualBlock<\n", + " (conv1): Conv2d, bias_init=None, format=NCHW>\n", + " (norm1): BatchNorm2d\n", + " (conv2): Conv2d, bias_init=None, format=NCHW>\n", + " (norm2): BatchNorm2d\n", + " (conv3): Conv2d, bias_init=None, format=NCHW>\n", + " (norm3): BatchNorm2d\n", + " (relu): ReLU<>\n", + " >\n", + " (2): ResidualBlock<\n", + " (conv1): Conv2d, bias_init=None, format=NCHW>\n", + " (norm1): BatchNorm2d\n", + " (conv2): Conv2d, bias_init=None, format=NCHW>\n", + " (norm2): BatchNorm2d\n", + " (conv3): Conv2d, bias_init=None, format=NCHW>\n", + " (norm3): BatchNorm2d\n", + " (relu): ReLU<>\n", + " >\n", + " >\n", + " (layer2): SequentialCell<\n", + " (0): ResidualBlock<\n", + " (conv1): Conv2d, bias_init=None, format=NCHW>\n", + " (norm1): BatchNorm2d\n", + " (conv2): Conv2d, bias_init=None, format=NCHW>\n", + " (norm2): BatchNorm2d\n", + " (conv3): Conv2d, bias_init=None, format=NCHW>\n", + " (norm3): BatchNorm2d\n", + " (relu): ReLU<>\n", + " (down_sample): SequentialCell<\n", + " (0): Conv2d, bias_init=None, format=NCHW>\n", + " (1): BatchNorm2d\n", + " >\n", + " >\n", + " (1): ResidualBlock<\n", + " (conv1): Conv2d, bias_init=None, format=NCHW>\n", + " (norm1): BatchNorm2d\n", + " (conv2): Conv2d, bias_init=None, format=NCHW>\n", + " (norm2): BatchNorm2d\n", + " (conv3): Conv2d, bias_init=None, format=NCHW>\n", + " (norm3): BatchNorm2d\n", + " (relu): ReLU<>\n", + " >\n", + " (2): ResidualBlock<\n", + " (conv1): Conv2d, bias_init=None, format=NCHW>\n", + " (norm1): BatchNorm2d\n", + " (conv2): Conv2d, bias_init=None, format=NCHW>\n", + " (norm2): BatchNorm2d\n", + " (conv3): Conv2d, bias_init=None, format=NCHW>\n", + " (norm3): BatchNorm2d\n", + " (relu): ReLU<>\n", + " >\n", + " (3): ResidualBlock<\n", + " (conv1): Conv2d, bias_init=None, format=NCHW>\n", + " (norm1): BatchNorm2d\n", + " (conv2): Conv2d, bias_init=None, format=NCHW>\n", + " (norm2): BatchNorm2d\n", + " (conv3): Conv2d, bias_init=None, format=NCHW>\n", + " (norm3): BatchNorm2d\n", + " (relu): ReLU<>\n", + " >\n", + " >\n", + " (layer3): SequentialCell<\n", + " (0): ResidualBlock<\n", + " (conv1): Conv2d, bias_init=None, format=NCHW>\n", + " (norm1): BatchNorm2d\n", + " (conv2): Conv2d, bias_init=None, format=NCHW>\n", + " (norm2): BatchNorm2d\n", + " (conv3): Conv2d, bias_init=None, format=NCHW>\n", + " (norm3): BatchNorm2d\n", + " (relu): ReLU<>\n", + " (down_sample): SequentialCell<\n", + " (0): Conv2d, bias_init=None, format=NCHW>\n", + " (1): BatchNorm2d\n", + " >\n", + " >\n", + " (1): ResidualBlock<\n", + " (conv1): Conv2d, bias_init=None, format=NCHW>\n", + " (norm1): BatchNorm2d\n", + " (conv2): Conv2d, bias_init=None, format=NCHW>\n", + " (norm2): BatchNorm2d\n", + " (conv3): Conv2d, bias_init=None, format=NCHW>\n", + " (norm3): BatchNorm2d\n", + " (relu): ReLU<>\n", + " >\n", + " (2): ResidualBlock<\n", + " (conv1): Conv2d, bias_init=None, format=NCHW>\n", + " (norm1): BatchNorm2d\n", + " (conv2): Conv2d, bias_init=None, format=NCHW>\n", + " (norm2): BatchNorm2d\n", + " (conv3): Conv2d, bias_init=None, format=NCHW>\n", + " (norm3): BatchNorm2d\n", + " (relu): ReLU<>\n", + " >\n", + " (3): ResidualBlock<\n", + " (conv1): Conv2d, bias_init=None, format=NCHW>\n", + " (norm1): BatchNorm2d\n", + " (conv2): Conv2d, bias_init=None, format=NCHW>\n", + " (norm2): BatchNorm2d\n", + " (conv3): Conv2d, bias_init=None, format=NCHW>\n", + " (norm3): BatchNorm2d\n", + " (relu): ReLU<>\n", + " >\n", + " (4): ResidualBlock<\n", + " (conv1): Conv2d, bias_init=None, format=NCHW>\n", + " (norm1): BatchNorm2d\n", + " (conv2): Conv2d, bias_init=None, format=NCHW>\n", + " (norm2): BatchNorm2d\n", + " (conv3): Conv2d, bias_init=None, format=NCHW>\n", + " (norm3): BatchNorm2d\n", + " (relu): ReLU<>\n", + " >\n", + " (5): ResidualBlock<\n", + " (conv1): Conv2d, bias_init=None, format=NCHW>\n", + " (norm1): BatchNorm2d\n", + " (conv2): Conv2d, bias_init=None, format=NCHW>\n", + " (norm2): BatchNorm2d\n", + " (conv3): Conv2d, bias_init=None, format=NCHW>\n", + " (norm3): BatchNorm2d\n", + " (relu): ReLU<>\n", + " >\n", + " >\n", + " (layer4): SequentialCell<\n", + " (0): ResidualBlock<\n", + " (conv1): Conv2d, bias_init=None, format=NCHW>\n", + " (norm1): BatchNorm2d\n", + " (conv2): Conv2d, bias_init=None, format=NCHW>\n", + " (norm2): BatchNorm2d\n", + " (conv3): Conv2d, bias_init=None, format=NCHW>\n", + " (norm3): BatchNorm2d\n", + " (relu): ReLU<>\n", + " (down_sample): SequentialCell<\n", + " (0): Conv2d, bias_init=None, format=NCHW>\n", + " (1): BatchNorm2d\n", + " >\n", + " >\n", + " (1): ResidualBlock<\n", + " (conv1): Conv2d, bias_init=None, format=NCHW>\n", + " (norm1): BatchNorm2d\n", + " (conv2): Conv2d, bias_init=None, format=NCHW>\n", + " (norm2): BatchNorm2d\n", + " (conv3): Conv2d, bias_init=None, format=NCHW>\n", + " (norm3): BatchNorm2d\n", + " (relu): ReLU<>\n", + " >\n", + " (2): ResidualBlock<\n", + " (conv1): Conv2d, bias_init=None, format=NCHW>\n", + " (norm1): BatchNorm2d\n", + " (conv2): Conv2d, bias_init=None, format=NCHW>\n", + " (norm2): BatchNorm2d\n", + " (conv3): Conv2d, bias_init=None, format=NCHW>\n", + " (norm3): BatchNorm2d\n", + " (relu): ReLU<>\n", + " >\n", + " >\n", + " (avg_pool): AvgPool2d\n", + " (flatten): Flatten<>\n", + " (fc): Dense\n", + " >\n" + ] + } + ], + "source": [ + "# 定义ResNet50网络\n", + "network = resnet50(pretrained=False)\n", + "\n", + "# 全连接层输入层的大小\n", + "in_channel = network.fc.in_channels\n", + "fc = nn.Dense(in_channels=in_channel, out_channels=10)\n", + "# 重置全连接层\n", + "network.fc = fc\n", + "print(network)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "e1c632ff", + "metadata": {}, + "outputs": [], + "source": [ + "# 设置学习率\n", + "num_epochs = 5\n", + "lr = nn.cosine_decay_lr(min_lr=0.00001, max_lr=0.001, total_step=step_size_train * num_epochs,\n", + " step_per_epoch=step_size_train, decay_epoch=num_epochs)\n", + "# 定义优化器和损失函数\n", + "opt = nn.Momentum(params=network.trainable_params(), learning_rate=lr, momentum=0.9)\n", + "loss_fn = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction='mean')\n", + "\n", + "\n", + "def forward_fn(inputs, targets):\n", + " logits = network(inputs)\n", + " loss = loss_fn(logits, targets)\n", + " return loss\n", + "\n", + "\n", + "grad_fn = ms.value_and_grad(forward_fn, None, opt.parameters)\n", + "\n", + "\n", + "def train_step(inputs, targets):\n", + " loss, grads = grad_fn(inputs, targets)\n", + " opt(grads)\n", + " return loss" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "b627e30c", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "# 创建迭代器\n", + "data_loader_train = dataset_train.create_tuple_iterator(num_epochs=num_epochs)\n", + "data_loader_val = dataset_val.create_tuple_iterator(num_epochs=num_epochs)\n", + "\n", + "# 最佳模型存储路径\n", + "best_acc = 0\n", + "best_ckpt_dir = \"./BestCheckpoint\"\n", + "best_ckpt_path = \"./BestCheckpoint/resnet50-best.ckpt\"\n", + "\n", + "if not os.path.exists(best_ckpt_dir):\n", + " os.mkdir(best_ckpt_dir)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8a5170df", + "metadata": {}, + "outputs": [ + { + "ename": "", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[1;31m在当前单元格或上一个单元格中执行代码时 Kernel 崩溃。\n", + "\u001b[1;31m请查看单元格中的代码,以确定故障的可能原因。\n", + "\u001b[1;31m单击此处了解详细信息。\n", + "\u001b[1;31m有关更多详细信息,请查看 Jupyter log。" + ] + } + ], + "source": [ + "import mindspore.ops as ops\n", + "\n", + "\n", + "def train(data_loader, epoch):\n", + " \"\"\"模型训练\"\"\"\n", + " losses = []\n", + " network.set_train(True)\n", + "\n", + " for i, (images, labels) in enumerate(data_loader):\n", + " loss = train_step(images, labels)\n", + " if i % 100 == 0 or i == step_size_train - 1:\n", + " print('Epoch: [%3d/%3d], Steps: [%3d/%3d], Train Loss: [%5.3f]' %\n", + " (epoch + 1, num_epochs, i + 1, step_size_train, loss))\n", + " losses.append(loss)\n", + "\n", + " return sum(losses) / len(losses)\n", + "\n", + "\n", + "def evaluate(data_loader):\n", + " \"\"\"模型验证\"\"\"\n", + " network.set_train(False)\n", + "\n", + " correct_num = 0.0 # 预测正确个数\n", + " total_num = 0.0 # 预测总数\n", + "\n", + " for images, labels in data_loader:\n", + " logits = network(images)\n", + " pred = logits.argmax(axis=1) # 预测结果\n", + " correct = ops.equal(pred, labels).reshape((-1, ))\n", + " correct_num += correct.sum().asnumpy()\n", + " total_num += correct.shape[0]\n", + "\n", + " acc = correct_num / total_num # 准确率\n", + "\n", + " return acc" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "562a04ca", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Start Training Loop ...\n", + "(256, 2048, 1, 1)\n", + "(256, 2048, 1, 1)\n", + "(256, 2048)\n", + "Epoch: [ 1/ 5], Steps: [ 1/196], Train Loss: [2.524]\n", + "(256, 2048, 1, 1)\n", + "(256, 2048, 1, 1)\n", + "(256, 2048)\n", + "(256, 2048, 1, 1)\n", + "(256, 2048, 1, 1)\n", + "(256, 2048)\n", + "(256, 2048, 1, 1)\n", + "(256, 2048, 1, 1)\n", + "(256, 2048)\n" + ] + } + ], + "source": [ + "# 开始循环训练\n", + "print(\"Start Training Loop ...\")\n", + "\n", + "for epoch in range(num_epochs):\n", + " curr_loss = train(data_loader_train, epoch)\n", + " curr_acc = evaluate(data_loader_val)\n", + "\n", + " print(\"-\" * 50)\n", + " print(\"Epoch: [%3d/%3d], Average Train Loss: [%5.3f], Accuracy: [%5.3f]\" % (\n", + " epoch+1, num_epochs, curr_loss, curr_acc\n", + " ))\n", + " print(\"-\" * 50)\n", + "\n", + " # 保存当前预测准确率最高的模型\n", + " if curr_acc > best_acc:\n", + " best_acc = curr_acc\n", + " ms.save_checkpoint(network, best_ckpt_path)\n", + "\n", + "print(\"=\" * 80)\n", + "print(f\"End of validation the best Accuracy is: {best_acc: 5.3f}, \"\n", + " f\"save the best ckpt file in {best_ckpt_path}\", flush=True)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "46e28f6f", + "metadata": {}, + "source": [ + "## 可视化模型预测\n", + "\n", + "定义`visualize_model`函数,使用上述验证精度最高的模型对CIFAR-10测试数据集进行预测,并将预测结果可视化。若预测字体颜色为蓝色表示为预测正确,预测字体颜色为红色则表示预测错误。\n", + "\n", + "> 由上面的结果可知,5个epochs下模型在验证数据集的预测准确率在70%左右,即一般情况下,6张图片中会有2张预测失败。如果想要达到理想的训练效果,建议训练80个epochs。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6ba2fa94", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "\n", + "def visualize_model(best_ckpt_path, dataset_val):\n", + " num_class = 10\n", + " net = resnet50(num_class)\n", + " # 加载模型参数\n", + " param_dict = ms.load_checkpoint(best_ckpt_path)\n", + " ms.load_param_into_net(net, param_dict)\n", + " # 加载验证集的数据进行验证\n", + " data = next(dataset_val.create_dict_iterator())\n", + " images = data[\"image\"]\n", + " labels = data[\"label\"]\n", + " # 预测图像类别\n", + " output = net(data['image'])\n", + " pred = np.argmax(output.asnumpy(), axis=1)\n", + "\n", + " # 图像分类\n", + " classes = []\n", + "\n", + " with open(data_dir + \"/batches.meta.txt\", \"r\") as f:\n", + " for line in f:\n", + " line = line.rstrip()\n", + " if line:\n", + " classes.append(line)\n", + "\n", + " # 显示图像及图像的预测值\n", + " plt.figure()\n", + " for i in range(6):\n", + " plt.subplot(2, 3, i + 1)\n", + " # 若预测正确,显示为蓝色;若预测错误,显示为红色\n", + " color = 'blue' if pred[i] == labels.asnumpy()[i] else 'red'\n", + " plt.title('predict:{}'.format(classes[pred[i]]), color=color)\n", + " picture_show = np.transpose(images.asnumpy()[i], (1, 2, 0))\n", + " mean = np.array([0.4914, 0.4822, 0.4465])\n", + " std = np.array([0.2023, 0.1994, 0.2010])\n", + " picture_show = std * picture_show + mean\n", + " picture_show = np.clip(picture_show, 0, 1)\n", + " plt.imshow(picture_show)\n", + " plt.axis('off')\n", + "\n", + " plt.show()\n", + "\n", + "\n", + "# 使用测试数据集进行验证\n", + "visualize_model(best_ckpt_path=best_ckpt_path, dataset_val=dataset_val)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "base", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}