back to index
【機器學習2021】神經網路壓縮 (Network Compression) (一) - 類神經網路剪枝 (Pruning) 與大樂透假說 (Lottery Ticket Hypothesis)

link |
好,那這一堂課呢,要跟大家講的是Network Compression,那在這個課堂上啊,我們已經看過了很多碩大無朋的模型,舉例來說BERT或者是GPT。
link |
那在這一節課裡面,我們要跟大家分享的事情是,我們能不能夠把這些碩大無朋的模型把它縮小,我們能不能夠簡化這些模型,讓它有比較少量的參數,但是跟原來的效能其實是差不多的呢?這就是Network Compression想要做的事情。
link |
那為什麼我們會在意Network Compression這件事呢?因為很多時候我們會需要把這些模型用在resource constraint的環境下,用在資源比較有限的環境下。
link |
什麼樣的環境是資源比較有限的呢?有時候你會需要把這些機器學習的模型,舉例來說,跑在智能手錶上,舉例來說,跑在drone上面。
link |
那在這些edge device,在這些IoT的device上面,只有比較少的memory,只有比較少的computing power,所以我們的模型如果太過巨大,你的手錶可能會是跑不動的,所以我們會需要比較少的模型。
link |
那講到這邊,有人就會問說,為什麼我們會需要在這些edge device上面跑模型呢?我們為什麼不把資料傳到雲端,直接在雲端上做運算,再把結果傳回到edge device,比如說你的手錶就好了呢?為什麼一定要在手錶上面做運算呢?
link |
一個常見的理由是latency的問題,假設你今天需要把資料傳到雲端,雲端計算完再傳回來,那中間就會有一個時間差。
link |
假設你今天的應用,你今天的edge device上面,你今天的edge device是自駕車的一個sensor,也許自駕車的sensor需要做非常及時的回應,你需要把資料傳到雲端再傳回來,那中間的latency太長了,也許會長到是不能接受的。
link |
那接下來有人又會問說,在未來5G的時代,會不會latency根本就可以忽略不計呢?那這個時候呢,有人會給你另外一個我們需要在edge device上面做computing的理由,這個理由就是privacy。
link |
如果我們今天需要把資料傳到雲端,那這個雲端的這個系統的持有者不就看到我們的資料了嗎?也許我在做什麼事情,我不想讓雲端系統的持有者知道。所以,為了保障隱私,也許在智能手錶上直接進行運算,在智能手錶上直接進行決策,是一個可以保障隱私的做法。
link |
那這邊呢,就跟大家講一下network compression的種種理由。那在這份投影片裡面呢,會跟大家介紹5個network compression的技術。那這5個技術啊,都是以軟體為導向的,我們只是在軟體上面對network進行壓縮,那我們都不考慮硬體加速的部分。
link |
當然,另外一個支線的研究是,我們想辦法在硬體上加速模型的運算,讓edge device上面跑深度學習的模型更加有效率。不過這是另外一個研究的面向,我們這邊就不討論任何跟硬體有關係的東西,我們只討論跟軟體有關係的東西。
link |
那第一個要跟大家分享的是network pruning這個技術。那我們今天講完network pruning呢,我們就下課。network pruning顧名思義就是,我們要把network裡面的一些參數把它剪掉。pruning就是修剪的意思,我們把network裡面的一些參數把它剪掉。
link |
為什麼我們可以把network裡面的一些參數剪掉呢?因為你知道,俗話說得好,樹大必有枯枝。一個這麼大的network,裡面有很多很多的參數,那每一個參數不一定都有在做事啊。
link |
參數這麼多的時候,也許很多參數它就只是在滑水,它是打醬油的,它什麼事也沒有做。那這些沒有做的參數放在那邊,就只是占空間而已,浪費運算資源而已,何不就把它們剪掉呢?
link |
所以network pruning的基本概念就是,把一個大的network,其中沒有用的那些參數把它找出來,把它扔掉。
link |
我高中的時候在生物學課本上看過這個圖,這個跟network pruning好像也有一點關係。這個圖是告訴我們說,人剛出生的時候,腦袋是空空的,這些圖是腦的神經元,腦袋空空的神經元跟神經元間沒什麼連結。
link |
在六歲的時候會長出非常多的連結,但是隨著年齡漸長,有一些連結就慢慢消失了。這個跟我們等一下要做的network pruning有異曲同工之妙。
link |
其實network pruning這件事不是太新的概念。早在90年代,央勒克就有一篇文章是講network pruning的,那篇文章的title是optimal brain damage,它把network pruning,把它剪掉一些weight,看成是一種腦損傷,brain damage。
link |
optimal的意思就是,我們要找出最好的pruning的方法,讓一些weight被剪掉之後,但是對這個腦的損傷是最小的。
link |
好,那network pruning的概念大概是怎麼樣進行的呢?它的framework大概是這樣子的。首先,你先train一個最大的network,你train一個大的network。
link |
接下來,你去量這個大的network裡面每一個參數或者是每一個neuron的重要性,去評估一下有沒有哪些參數它是沒在做事的,或有沒有哪些neuron它是沒在做事的。
link |
怎麼評估某一個參數有沒有在做事呢?怎麼評估某一個參數重要不重要呢?最簡單的方法也許就是看它的絕對值,如果這個參數的絕對值越大,那它可能對整個network的影響越大。
link |
如果它的絕對值越接近零,那也許對整個network的影響越小,也許對我們任務的影響越小。或者是,其實你也可以套用lifelong learning那邊的想法,你記得在lifelong learning裡面,我們不是也要看說哪一些參數比較重要嗎?
link |
我們不是有一大堆的方法看哪些參數重要,哪些參數不重要,然後決定bi那個所謂的值嗎?也許我們也可以就把每一個參數的bi算出來,那我們就可以知道那個參數重要不重要,然後把不重要的參數剪掉。
link |
也可以評估每一個神經元的重要性,我們也可以把神經元當作修剪的單位,那怎麼看一個神經元重不重要呢?
link |
你就可以比如說計算這個神經元輸出不為零的次數等等,總之有非常多的方法來判斷一個參數或一個神經元是否重要,那我們在這邊就不細講。
link |
那把不重要的神經元或者不重要的參數就剪掉,就把它從模型裡面移除,那你就得到一個比較小的nevo。
link |
但是你做完這個修剪以後,通常你的正確率、你的模型的效能就會掉一點,因為有一些參數被拿掉了嘛,所以這個nevo當然是受到一些損傷,所以正確率就掉一點。
link |
但是我們會想辦法讓這個正確率再回升一點,怎麼讓正確率再回升一點呢?就是把這個比較小的nevo,把剩餘沒有被剪掉的參數再重新做微調。
link |
你就把你的訓練資料拿出來,把這個比較小的nevo再重新訓練一下,然後訓練完之後,其實你還可以重新再去評估一次每一個參數的正確性。
link |
你還可以再remove掉,再剪掉更多的參數,然後再重新進行微調,那這個步驟可以反覆進行多次。
link |
那為什麼我們不一次剪掉大量的參數呢?因為在實驗上發現說,如果你一次剪掉大量的參數,可能對你的nevo的傷害太大了,可能會大到你用finetune也沒有辦法復原。
link |
所以一次先剪掉一點參數,比如說只剪掉10%的參數,然後再重新訓練,然後再重新剪掉10%的參數,再重新訓練,反覆這個過程,你可以剪掉比較多的參數。
link |
當你的nevo夠小以後,那整個過程就完成了,你就得到一個比較小的nevo,而且這個比較小的nevo,也許它的正確率跟大的nevo是沒有太大的差別的。
link |
我們剛才講到說,修剪的單位可以以參數為單位,也可以以神經元來當作單位。
link |
那用這兩者當作單位有什麼不同呢?用這兩者當作單位,在實作上會是有蠻顯著的差距的。
link |
如果我們現在是以參數當作單位,會發生什麼事?假設我們是要評估說某一個參數要不要被去掉,某一個參數對整個任務而言重不重要,能不能夠被去掉。
link |
那我們把這個不重要的參數去掉以後,我們得到的nevo,它的形狀可能會是不規則的。
link |
所謂不規則的意思是說,舉例來說,我們來看紅色的這個neuro,它連到接下來三個綠色的neuro。
link |
那第二個紅色的neuro,它只連到兩個綠色的neuro,或這個紅色的neuro,它的輸入只有兩個藍色的neuro,而這個紅色的neuro,它的輸入有四個藍色的neuro。
link |
如果你是把參數當作單位來進行修剪的話,那你修剪完以後的nevo,它的形狀會是不規則的。形狀不規則會造成什麼樣的問題呢?
link |
最大的問題就是,你不好實作啊。你想想看,你用PyTorch要實作這種形狀不規則的nevo,你好實作嗎?你不好實作啊。
link |
因為在PyTorch裡面,你在定義一個nevo的時候,你的定義方法都是每一層有幾個neuro,對不對?
link |
你都是定義說,我現在每一層要輸入有幾個neuro,輸出有幾個neuro,或者輸入多長的vector,輸出有多長的vector。
link |
這種形狀不固定的nevo,你根本就不好寫啊,你自己實作的時候,你根本就不好實作。
link |
而且就算你硬是把這種形狀不規則的nevo把它實作出來,你用GPU加速也不好加速啊。
link |
GPU在加速的時候,就是把nevo的運算看成一個矩陣的乘法,但是當nevo是不規則的時候,你就不容易用矩陣的乘法來進行加速,你不容易用GPU來進行加速。
link |
所以實際上,在做weight pruning的時候,在實作上,你可能會把那些prune掉的weight直接補0,就是prune掉的weight不是不存在,它的值只是設為0。
link |
這樣的好處就是,你的實作就比較容易用GPU加速。
link |
這樣的問題是什麼呢?這樣的問題是,你根本就沒有真的把nevo變小啊,你這邊說這個nevo這個weight它的值是0,你還是存了這個參數啊,你還是存了一個參數在你的memory裡面啊。
link |
你並沒有真的把nevo變小,你只是在想像中把它變小,在自嗨,你覺得這個nevo有變小,但實際上這個nevo並沒有真的變小。
link |
所以這個是以參數為單位來做pruning的時候,你在實作上會遇到的問題。
link |
這個文獻上的實驗就是想要跟你展示說,以參數為單位做pruning的時候,你會遇到什麼樣的問題。
link |
我們先來看紫色的這一條線,紫色的這一條線呢,它說是sparsity。
link |
這個sparsity是什麼意思?這個sparsity就是有多少百分比的參數現在被prune掉了。
link |
那你發現說,這邊啊,這個紫色的這條線它的值都很接近1,什麼意思?
link |
代表有接近大概95%以上的參數都被prune掉了。
link |
這個nevo pruning的方法其實是一個非常有效率的方法,往往你可以prune到95%以上的參數,但是你的accuracy只掉一兩%而已。
link |
所以這邊參數prune的是prune得非常兇的,有95%的參數都被丟掉了。
link |
照理說丟掉了95%的參數只剩下5%的參數,這個nevo變得很小,它的運算應該很快吧,但實際上你發現根本就沒有加速多少,甚至可以說根本就沒有加速。
link |
如果你看這些長條圖,這些長條圖顯示的是在三種不同的computing的resource上面,你speedup的程度,你加速的程度。
link |
那加速的程度要大過1才有加速嘛,加速程度小於1其實是變慢的。
link |
結果你發現說,在多數情況下根本就沒有加速,多數情況下其實都是變慢。
link |
也就是你把一些位prune掉,結果你的nevo形狀變得不規則,然後你真的用GPU加速的時候,你反而沒有辦法真的加速它,所以webpruning不見得是一個特別有效的方法。
link |
那neuron pruning,以神經元為單位來做pruning,也許是一個比較有效的方法。
link |
如果我們用神經為做單位來pruning,丟掉一些神經元以後,你nevo的架構仍然是規則的。
link |
簡單來說就是,你用PyTorch比較好實作啦,你實作的時候你只要改每一個layer input output的dimension就好了,所以你比較好實作,也比較好用GPU來加速。
link |
這個是在nevo pruning實作上可能會遇到的問題。
link |
有同學問說,這個減值應該是減掉的減,還是減少的減?
link |
在我的認知裡面是減掉的減啦,但是如果我有說錯,你再糾正我。
link |
有同學問說,請問CNN中做完pruning,減掉遮罩中一些參數,它的運算量是不是沒變?
link |
不好意思,我沒有非常懂這個問題,遮罩的參數是什麼?
link |
我這邊沒有非常了解,也許你可以重新再formulate一下你的問題,那我等一下再來看。
link |
有同學問說,pruning有沒有效率是韓式庫的問題?
link |
那如果你可以想辦法寫一個irregular的nevo也很有效的韓式庫的話,那你就可以用微pruning。
link |
但是問題是,大家都沒有要自己寫韓式庫啊,你都是用PyTorch嘛,所以你用微pruning就沒有辦法加速。
link |
如果反過來想,在處理一個任務時把nevo慢慢擴大,可以比直接用大nevo有更好的表現嗎?
link |
有同學問說,如果我們先train一個小的nevo,再把它慢慢變大,會不會比較好呢?
link |
我們剛才講nevo pruning的時候是把大的nevo慢慢變小,那如果我們先train小的nevo再慢慢變大會不會比較好呢?
link |
答案是不會,我們在下一頁投影片就會回答你的問題。
link |
然後有同學說,可變nevo本來就比較難做。
link |
接下來我們就要問一個問題,你說我們先train一個大的nevo,再把它變小,而且說小的nevo跟大的nevo,他們的正確率沒有差太多,那我們怎麼不直接train一個小的nevo就好了呢?
link |
直接train一個小的nevo比較有效率吧,還train大的nevo變小幹嘛,根本是捨本逐末,為什麼不直接train小的nevo?
link |
那一個普遍的答案是,大的nevo比較好train。
link |
你會發現說,如果你直接train一個小的nevo,你往往沒有辦法得到跟大的nevo一樣的正確率。
link |
你可以先train一個大的nevo,再把它變小,正確率沒有掉太多,但直接去那個小的nevo,你得不到跟pruning大的nevopruning完變成小的nevo一樣的正確率。
link |
至於大的nevo為什麼比較好train,那你可以參看以下這個影片的連結,我在過去的課程有試圖解釋這件事。
link |
但是為什麼大的nevo比較好train呢?那這邊有一個假說叫做大樂透假說。
link |
不過它既然叫做假說,就代表說它不算是完全被實證的一個理論,如果它是已經被證明出來的,那它就是一個理論嘛,但是它現在只是一個假說而已。
link |
那這個大樂透假說是怎麼解釋為什麼大的nevo比較容易train,直接train一個小的nevo沒有辦法得到跟大的nevo一樣的效果,一定要大的nevopruning變小,結果才會好呢?
link |
我們知道說訓練nevo是一個看人品的事情,我們現在大家都做過這麼多作業了,我相信你都一定有很多的心酸血淚。
link |
你知道訓練nevo就是看人品的,每次訓練的結果不一定會一樣,你抽到一組好的initial的參數,你就會得到好的結果,抽到一組壞的initial參數,就會得到壞的結果。
link |
那就好像說樂透也是一個看人品的東西,但是怎麼在樂透這個遊戲裡面得到比較高的中獎率呢?
link |
是不是就是包牌買比較多的彩券,就可以增加你的中獎率?所以對一個大的nevo來說也是一樣的。
link |
大的nevo可以視為是很多小的subnevo的組合,我們可以想成是一個大的nevo裡面其實包含了很多小的nevo。
link |
當我們去訓練這個大的nevo的時候,我們等於是同時訓練很多小的nevo,每一個小的nevo不一定可以成功地被訓練出來。
link |
所謂成功地訓練出來是說,它不一定可以透過gradient descent找到一個好的solution,我們不一定可以訓練出一個好的結果,我們不一定可以讓它的loss變低。
link |
但是在眾多的subnevo裡面,只要其中一個人成功,就可以一人得到雞犬升天。其中一個subnevo成功,大的nevo它就成功了。
link |
而今天一個大的nevo裡面,如果包含的小的nevo越多,那就好像是去買樂透的時候包牌包比較多的彩券一樣。
link |
彩券越多,中獎的機率就越高,所以一個nevo越大,它就越有可能成功地被訓練起來。
link |
那這個大樂透假說,它在實驗上是怎麼被證實的呢?它在實驗上的證實方式跟nevo的pruning非常有關係。
link |
我們就直接看一下,在實驗上是怎麼證實大樂透假說的。
link |
你現在有一個大的nevo,在這個大的nevo上面,一開始的參數是隨機初始化的。
link |
把參數隨機初始化以後,得到一組訓練完的參數,訓練完的參數我們用紫色來表示。
link |
接下來你用nevo pruning的技術,把一些紫色的參數丟掉,得到一個比較小的nevo。
link |
如果你現在直接把這個小的nevo裡面的參數再重新隨機的去初始化,也就是你重建一個一樣大小的小的nevo,
link |
就是你把這個nevo複製一次,一樣大小,但是參數完全不一樣,重新再訓練一次,你會發現訓練不起來。
link |
直接訓練這個小的nevo訓練不起來,訓練一個大的,再把它變小,沒問題,但是直接訓練小的,訓練不起來。
link |
但是假設這個小的nevo,我們在重新初始化參數的時候,我們用的跟這組紅色的參數是一模一樣的,就訓練得起來。
link |
大家可以了解這兩者的差別嗎?就是這兩組參數雖然都是random initialized,但是這組綠色的參數跟這組紅色的參數是沒有關係的。
link |
而這邊這些random initialized的參數是直接從這邊的紅色參數裡面選出對應的參數。
link |
就是這邊有四個參數,我們就是把這邊對應到的這四個參數直接把它複製過來。
link |
這邊有四個參數,我們就把這裡面對應到的四個參數直接複製過來,把這裡面的參數直接複製過來,就訓練得起來。
link |
如果用大樂透假說來解釋的話,就是這裡面有很多sub-nevo,而這一組initialized的參數就是幸運的那一組可以train得起來的sub-nevo。
link |
所以當我們今天把這個大的nevo train完再pwn掉的時候,你留下來的就是幸運的那些參數,可以訓練得起來的那些參數。
link |
所以這一組初始化的參數,它是可以訓練得起來的一個sub-nevo。但是如果你在重新隨機初始化的話,那你如果運氣比較不好,你就抽不到可以成功訓練起來的參數。
link |
那大樂透假說非常的知名啦,它在IKEA 2019,應該是2019沒錯,得到Best Paper Award,所以它是一個非常知名的一個假說。
link |
那後面也有很多後續的研究,比如說有一篇有趣的研究叫做Deconstructing Lottery Tickets,解構大樂透。
link |
那這個解構大樂透裡面有什麼有趣的結論呢?我們就直接講它的結論。
link |
第一個,它試了不同的pwning的strategy,然後發現說某兩個pwning的strategy是最有效的。
link |
這個細節我們就不講,它做了一個非常完整的實驗告訴你說pwning有哪些可能的strategy。
link |
然後它發現說如果訓練前跟訓練後,它的絕對值差距越大,那pwning掉那些network得到的結果是越有效的。
link |
那另外一個比較有趣的結果是,到底我們今天這一組好的initialization是好在哪裡呢?
link |
它發現說,如果我們只要不改變參數的正負號,就可以訓練起來。
link |
就是小的network只要不改變正負號就可以訓練起來。
link |
什麼意思呢?就是假設你pwning完以後,那你再把原來random initialized的那些參數拿出來,
link |
它的值是這個樣子,0.9、3.1、-9.1、8.5。
link |
你可以完全不管它的數值,直接把正的數值大於0的,通通都用正α來取代,小於0的都用-α來取代。
link |
用這組參數去initialize你的model,這樣也train得起來,會跟用這組參數去initialize差不多。
link |
所以這個實驗告訴我們說,正負號是初始化參數能不能夠訓練起來的關鍵。
link |
它在文章的章節標題上,你乍看之下會以為它想要講significance of initial weights,
link |
但它故意在SIGN後面加了一個dash,告訴你說是significance,正負號是很重要的。
link |
它想要玩梗一語雙關,不過好像沒什麼人注意到就是了。
link |
最後一個神奇的發現是,既然我們在想說一個大的network裡面有一些subnetwork,
link |
它是特別好的初始化的參數,它訓練起來會特別的順利,
link |
那會不會一個大的network裡面甚至其實已經有一個subnetwork,
link |
它連訓練都不用訓練,直接拿出來就是一個好的network呢?
link |
我們完全不用訓練network,我們直接把大的networkput in put,
link |
就得到一個可以拿來做分類的classifier了。
link |
有沒有可能是這個樣子的呢?就好像米開朗基羅說,它是怎麼雕出大衛像的呢?
link |
它不是雕出大衛像,它是把大衛像從石頭裡面釋放出來。
link |
大衛像原來就是在石頭裡面,它只是把多餘的地方把它去掉而已。
link |
那會不會在整個大的network裡面,雖然參數都是隨機的,
link |
但其中已經有一組參數,它就已經可以做分類了,
link |
把多餘的東西拿掉,直接就可以得到好的分類結果了呢?
link |
答案是是這樣子,你可以自己去讀一下那個文章,
link |
其實可以得到跟supervised其實很接近的正確率。
link |
其實我看到這篇文章的時候,這個結論已經沒有讓我覺得非常神奇了,
link |
因為在這個解構大樂透這篇文章發表的前幾個月,
link |
有一篇文章叫做Web Agnostic Neural Network,
link |
它是說它弄了一個神奇的network,這個network裡面所有的數值要嘛是隨機的,
link |
要嘛通通都設1.5這樣,結果這個network也可以得到一定程度的結果。
link |
所以看起來就算你的network裡面參數都是隨機的,
link |
我根本就給它一個constant,也有可能可以得到好的performance。
link |
所以這個結論其實沒有讓人特別訝異,因為在幾個月之前就已經有同樣的文章了。
link |
這邊放了這兩篇文章的archive連結,
link |
這邊放的連結是他們最後一個上傳到archive的版本,
link |
所以如果你看最後一個版本上傳的月份的話,
link |
你會覺得Web Agnostic Network是後出來,然後解構大樂透是先出來。
link |
但如果你看第一個上傳的版本的話,是先有Web Agnostic Network,
link |
然後才有解構大樂透,所以我是先讀到這一篇才讀到解構大樂透的。
link |
但是大樂透假說,它一定是對的嗎?不一定。
link |
有一篇文章是打臉大樂透假說,這篇文章叫做Rethinking the Value of Network Printing。
link |
而且神奇的是,這篇文章跟大樂透假說是同時出來的,
link |
他們同時出現在iClear 2019,所以就是在iClear 2019裡面有兩篇文章,
link |
這篇文章說的是什麼呢?這篇文章說它試了兩個data set,
link |
還有好幾種不同的模型,然後它說這個是沒有print過的network的正確率,
link |
然後它試著去print了一下network,然後再重新去做fine tune,
link |
小的network可以跟大的network得到差不多的正確率。
link |
然後它說,一般人的想像是,如果我們直接去train這個小的network,
link |
正確率會不如大的networkprint完以後的結果。
link |
它試了第一次實驗,叫做Scratch1,Scratch1的意思就是它的參數是隨機初始化的。
link |
大家注意一下,它的隨機初始化跟大樂透假說裡面的隨機初始化是不一樣的,
link |
它的隨機初始化就是真的是隨機初始化。
link |
大樂透假說裡面的隨機初始化是從原來的那一組隨機的參數裡面,
link |
我知道這個很拗口,希望你知道我在說什麼。
link |
這邊這個Scratch1的意思是說,我們就真的隨機初始化參數,
link |
訓練一個小的network,跟這個有print過的network它的大小是一樣的,
link |
發現果然差了一點,跟多數人的想像好像是一樣的。
link |
但是接下來它說,如果我們在update的時候多update幾個APAC會怎樣呢?
link |
之前的人設定的APAC數目,小的network的APAC的訓練數目都跟大的network一樣。
link |
但是如果小的network的APAC數目多設一點會怎樣呢?
link |
多設一點,就比print過以後的結果就好了。
link |
所以之前覺得小的network訓練不起來,一定要先訓練大的再做printing,
link |
會不會就是一個illusion、幻覺、都市傳說。
link |
直接訓練小的network,APAC多一點,反正就是訓練的起來了。
link |
這篇文章放在iClear審查的時候,當然就有reviewer去問說,
link |
你這個跟大樂透假說正好是相反的,你有沒有什麼comment?
link |
其實在這篇文章裡面,他也有對大樂透假說做出一些回應。
link |
他覺得大樂透假說觀察到的現象,也許只有在某一些特定的情況下才觀察得到。
link |
根據這篇文章的實驗是說,只有在learning rate設比較小的時候,
link |
還有unstructured的時候,unstructured的時候就是我們print的時候,
link |
以weight作為單位來做printing的時候,才能觀察到大樂透假說的現象。
link |
他發現說learning rate調大,他就觀察不到大樂透假說的這個現象。
link |
所以到底大樂透假說有多正確、是真是假,這個還未來要上代更多的研究來證實。