吳恩達deeplearning.ai系列課程筆記+編程作業(11)第四課 卷積神經網絡-第二週 深度卷積網絡:實例探究(Deep convolutional models: case studies)

第四門課 卷積神經網絡(Convolutional Neural Networks)

第二週 深度卷積網絡:實例探究(Deep convolutional models: case studies)


本週編程作業見:第二週:編程作業:Keras - Tutorial - Happy House 第二週:編程作業:Residual Networks

2.1 爲什麼要進行實例探究?(Why look at case studies?)

       這周我們首先來看看一些卷積神經網絡的實例分析,爲什麼要看這些實例分析呢?上週我們講了基本構建,比如卷積層、池化層以及全連接層這些組件。事實上,過去幾年計算機視覺研究中的大量研究都集中在如何把這些基本構件組合起來,形成有效的卷積神經網絡。最直觀的方式之一就是去看一些案例,就像很多人通過看別人的代碼來學習編程一樣,通過研究別人構建有效組件的案例是個不錯的辦法。實際上在計算機視覺任務中表現良好的神經網絡框架往往也適用於其它任務,也許你的任務也不例外。也就是說,如果有人已經訓練或者計 算出擅長識別貓、狗、人的神經網絡或者神經網絡框架,而你的計算機視覺識別任務是構建一個自動駕駛汽車,你完全可以借鑑別人的神經網絡框架來解決自己的問題。

       最後,學完這幾節課,你應該可以讀一些計算機視覺方面的研究論文了,我希望這也是你學習本課程的收穫。當然,讀論文並不是必須的,但是我希望當你發現你可以讀懂一些計算機視覺方面的研究論文或研討會內容時會有一種滿足感。言歸正傳,我們進入主題。

       這是後面幾節課的提綱,首先我們來看幾個經典的網絡。

       在這裏插入圖片描述
       LeNet-5網絡,我記得應該是1980年代的,經常被引用的AlexNet,還有VGG網絡。這些都是非常有效的神經網絡範例,當中的一些思路爲現代計算機視覺技術的發展奠定了基礎。論文中的這些想法可能對你大有裨益,對你的工作也可能有所幫助。

       然後是ResNet,又稱殘差網絡。神經網絡正在不斷加深,對此你可能有所瞭解。ResNet神經網絡訓練了一個深達152層的神經網絡,並且在如何有效訓練方面,總結出了一些有趣的想法和竅門。課程最後,我們還會講一個Inception神經網絡的實例分析。

       瞭解了這些神經網絡,我相信你會對如何構建有效的卷積神經網絡更有感覺。即使計算機視覺並不是你的主要方向,但我相信你會從ResNetInception網絡這樣的實例中找到一些不錯的想法。這裏面有很多思路都是多學科融合的產物。總之,即便你不打算構建計算機視覺應用程序,試着從中發現一些有趣的思路,對你的工作也會有所幫助。

2.2 經典網絡(Classic networks)

       這節課,我們來學習幾個經典的神經網絡結構,分別是LeNet-5AlexNetVGGNet,開始吧。

       首先看看LeNet-5的網絡結構,假設你有一張32×32×1的圖片,LeNet-5可以識別圖中的手寫數字,比如像這樣手寫數字7。LeNet-5是針對灰度圖片訓練的,所以圖片的大小隻有32×32×1。實際上LeNet-5的結構和我們上週講的最後一個範例非常相似,使用6個5×5的過濾器,步幅爲1。由於使用了6個過濾器,步幅爲1,padding爲0,輸出結果爲28×28×6,圖像尺寸從32×32縮小到28×28。然後進行池化操作,在這篇論文寫成的那個年代,人們更喜歡使用平均池化,而現在我們可能用最大池化更多一些。在這個例子中,我們進行平均池化,過濾器的寬度爲2,步幅爲2,圖像的尺寸,高度和寬度都縮小了2倍,輸出結果是一個14×14×6的圖像。我覺得這張圖片應該不是完全按照比例繪製的,如果嚴格按照比例繪製,新圖像的尺寸應該剛好是原圖像的一半。
       在這裏插入圖片描述
       接下來是卷積層,我們用一組16個5×5的過濾器,新的輸出結果有16個通道。LeNet-5的論文是在1998年撰寫的,當時人們並不使用padding,或者總是使用valid卷積,這就是爲什麼每進行一次卷積,圖像的高度和寬度都會縮小,所以這個圖像從14到14縮小到了10×10。然後又是池化層,高度和寬度再縮小一半,輸出一個5×5×16的圖像。將所有數字相乘,乘積是400。

       下一層是全連接層,在全連接層中,有400個節點,每個節點有120個神經元,這裏已經有了一個全連接層。但有時還會從這400個節點中抽取一部分節點構建另一個全連接層,就像這樣,有2個全連接層。

       最後一步就是利用這84個特徵得到最後的輸出,我們還可以在這裏再加一個節點用來預測 y ^ \hat{y} 的值, y ^ \hat{y} 有10個可能的值,對應識別0-9這10個數字。在現在的版本中則使用softmax函數輸出十種分類結果,而在當時,LeNet-5網絡在輸出層使用了另外一種,現在已經很少用到的分類器。

       相比現代版本,這裏得到的神經網絡會小一些,只有約6萬個參數。而現在,我們經常看到含有一千萬到一億個參數的神經網絡,比這大1000倍的神經網絡也不在少數。

       不管怎樣,如果我們從左往右看,隨着網絡越來越深,圖像的高度和寬度在縮小,從最初的32×32縮小到28×28,再到14×14、10×10,最後只有5×5。與此同時,隨着網絡層次的加深,通道數量一直在增加,從1增加到6個,再到16個。
       在這裏插入圖片描述
       這個神經網絡中還有一種模式至今仍然經常用到,就是一個或多個卷積層後面跟着一個池化層,然後又是若干個卷積層再接一個池化層,然後是全連接層,最後是輸出,這種排列方式很常用。

       對於那些想嘗試閱讀論文的同學,我再補充幾點。接下來的部分主要針對那些打算閱讀經典論文的同學,所以會更加深入。這些內容你完全可以跳過,算是對神經網絡歷史的一種回顧吧,聽不懂也不要緊。

       讀到這篇經典論文時,你會發現,過去,人們使用sigmod函數和tanh函數,而不是ReLu函數,這篇論文中使用的正是sigmod函數和tanh函數。這種網絡結構的特別之處還在於,各網絡層之間是有關聯的,這在今天看來顯得很有趣。

       比如說,你有一個 n H × n W × n C n_{H} \times n_{W} \times n_{C} 的網絡,有 n C n_{C} 個通道,使用尺寸爲 f × f × n C f×f×n_{C} 的過濾器,每個過濾器的通道數和它上一層的通道數相同。這是由於在當時,計算機的運行速度非常慢,爲了減少計算量和參數,經典的LeNet-5網絡使用了非常複雜的計算方式,每個過濾器都採用和輸入模塊一樣的通道數量。論文中提到的這些複雜細節,現在一般都不用了。

       我認爲當時所進行的最後一步其實到現在也還沒有真正完成,就是經典的LeNet-5網絡在池化後進行了非線性函數處理,在這個例子中,池化層之後使用了sigmod函數。如果你真的去讀這篇論文,這會是最難理解的部分之一,我們會在後面的課程中講到。

       下面要講的網絡結構簡單一些,幻燈片的大部分類容來自於原文的第二段和第三段,原文的後幾段介紹了另外一種思路。文中提到的這種圖形變形網絡如今並沒有得到廣泛應用,所以在讀這篇論文的時候,我建議精讀第二段,這段重點介紹了這種網絡結構。泛讀第三段,這裏面主要是一些有趣的實驗結果。

       我要舉例說明的第二種神經網絡是AlexNet,是以論文的第一作者Alex Krizhevsky的名字命名的,另外兩位合著者是ilya SutskeverGeoffery Hinton
       在這裏插入圖片描述
       AlexNet首先用一張227×227×3的圖片作爲輸入,實際上原文中使用的圖像是224×224×3,但是如果你嘗試去推導一下,你會發現227×227這個尺寸更好一些。第一層我們使用96個11×11的過濾器,步幅爲4,由於步幅是4,因此尺寸縮小到55×55,縮小了4倍左右。然後用一個3×3的過濾器構建最大池化層, f = 3 f=3 ,步幅 s s 爲2,卷積層尺寸縮小爲27×27×96。接着再執行一個5×5的卷積,padding之後,輸出是27×27×276。然後再次進行最大池化,尺寸縮小到13×13。再執行一次same卷積,相同的padding,得到的結果是13×13×384,384個過濾器。再做一次same卷積,就像這樣。再做一次同樣的操作,最後再進行一次最大池化,尺寸縮小到6×6×256。6×6×256等於9216,將其展開爲9216個單元,然後是一些全連接層。最後使用softmax函數輸出識別的結果,看它究竟是1000個可能的對象中的哪一個。

       實際上,這種神經網絡與LeNet有很多相似之處,不過AlexNet要大得多。正如前面講到的LeNetLeNet-5大約有6萬個參數,而AlexNet包含約6000萬個參數。當用於訓練圖像和數據集時,AlexNet能夠處理非常相似的基本構造模塊,這些模塊往往包含着大量的隱藏單元或數據,這一點AlexNet表現出色。AlexNetLeNet表現更爲出色的另一個原因是它使用了ReLu激活函數。
       在這裏插入圖片描述
       同樣的,我還會講一些比較深奧的內容,如果你並不打算閱讀論文,不聽也沒有關係。第一點,在寫這篇論文的時候,GPU的處理速度還比較慢,所以AlexNet採用了非常複雜的方法在兩個GPU上進行訓練。大致原理是,這些層分別拆分到兩個不同的GPU上,同時還專門有一個方法用於兩個GPU進行交流。
       在這裏插入圖片描述

       論文還提到,經典的AlexNet結構還有另一種類型的層,叫作「局部響應歸一化層」(Local Response Normalization),即LRN層,這類層應用得並不多,所以我並沒有專門講。局部響應歸一層的基本思路是,假如這是網絡的一塊,比如是13×13×256,LRN要做的就是選取一個位置,比如說這樣一個位置,從這個位置穿過整個通道,能得到256個數字,並進行歸一化。進行局部響應歸一化的動機是,對於這張13×13的圖像中的每個位置來說,我們可能並不需要太多的高激活神經元。但是後來,很多研究者發現LRN起不到太大作用,這應該是被我劃掉的內容之一,因爲並不重要,而且我們現在並不用LRN來訓練網絡。

       如果你對深度學習的歷史感興趣的話,我認爲在AlexNet之前,深度學習已經在語音識別和其它幾個領域獲得了一些關注,但正是通過這篇論文,計算機視覺羣體開始重視深度學習,並確信深度學習可以應用於計算機視覺領域。此後,深度學習在計算機視覺及其它領域的影響力與日俱增。如果你並不打算閱讀這方面的論文,其實可以不用學習這節課。但如果你想讀懂一些相關的論文,這是比較好理解的一篇,學起來會容易一些。

       AlexNet網絡結構看起來相對複雜,包含大量超參數,這些數字(55×55×96、27×27×96、27×27×256……)都是Alex Krizhevsky及其合著者不得不給出的。
       在這裏插入圖片描述
       這節課要講的第三個,也是最後一個範例是VGG,也叫作VGG-16網絡。值得注意的一點是,VGG-16網絡沒有那麼多超參數,這是一種只需要專注於構建卷積層的簡單網絡。首先用3×3,步幅爲1的過濾器構建卷積層,padding參數爲same卷積中的參數。然後用一個2×2,步幅爲2的過濾器構建最大池化層。因此VGG網絡的一大優點是它確實簡化了神經網絡結構,下面我們具體講講這種網絡結構。
       在這裏插入圖片描述
       假設要識別這個圖像,在最開始的兩層用64個3×3的過濾器對輸入圖像進行卷積,輸出結果是224×224×64,因爲使用了same卷積,通道數量也一樣。VGG-16其實是一個很深的網絡,這裏我並沒有把所有卷積層都畫出來。
       在這裏插入圖片描述
       假設這個小圖是我們的輸入圖像,尺寸是224×224×3,進行第一個卷積之後得到224×224×64的特徵圖,接着還有一層224×224×64,得到這樣2個厚度爲64的卷積層,意味着我們用64個過濾器進行了兩次卷積。正如我在前面提到的,這裏採用的都是大小爲3×3,步幅爲1的過濾器,並且都是採用same卷積,所以我就不再把所有的層都畫出來了,只用一串數字代表這些網絡。

       接下來創建一個池化層,池化層將輸入圖像進行壓縮,從224×224×64縮小到多少呢?沒錯,減少到112×112×64。然後又是若干個卷積層,使用129個過濾器,以及一些same卷積,我們看看輸出什麼結果,112×112×128.然後進行池化,可以推導出池化後的結果是這樣(56×56×128)。接着再用256個相同的過濾器進行三次卷積操作,然後再池化,然後再卷積三次,再池化。如此進行幾輪操作後,將最後得到的7×7×512的特徵圖進行全連接操作,得到4096個單元,然後進行softmax激活,輸出從1000個對象中識別的結果。
       在這裏插入圖片描述
       順便說一下,VGG-16的這個數字16,就是指在這個網絡中包含16個卷積層和全連接層。確實是個很大的網絡,總共包含約1.38億個參數,即便以現在的標準來看都算是非常大的網絡。但VGG-16的結構並不複雜,這點非常吸引人,而且這種網絡結構很規整,都是幾個卷積層後面跟着可以壓縮圖像大小的池化層,池化層縮小圖像的高度和寬度。同時,卷積層的過濾器數量變化存在一定的規律,由64翻倍變成128,再到256和512。作者可能認爲512已經足夠大了,所以後面的層就不再翻倍了。無論如何,每一步都進行翻倍,或者說在每一組卷積層進行過濾器翻倍操作,正是設計此種網絡結構的另一個簡單原則。這種相對一致的網絡結構對研究者很有吸引力,而它的主要缺點是需要訓練的特徵數量非常巨大。
       在這裏插入圖片描述
       有些文章還介紹了VGG-19網絡,它甚至比VGG-16還要大,如果你想了解更多細節,請參考幻燈片下方的註文,閱讀由Karen SimonyanAndrew Zisserman撰寫的論文。由於VGG-16的表現幾乎和VGG-19不分高下,所以很多人還是會使用VGG-16。我最喜歡它的一點是,文中揭示了,隨着網絡的加深,圖像的高度和寬度都在以一定的規律不斷縮小,每次池化後剛好縮小一半,而通道數量在不斷增加,而且剛好也是在每組卷積操作後增加一倍。也就是說,圖像縮小的比例和通道數增加的比例是有規律的。從這個角度來看,這篇論文很吸引人。

       以上就是三種經典的網絡結構,如果你對這些論文感興趣,我建議從介紹AlexNet的論文開始,然後就是VGG的論文,最後是LeNet的論文。雖然有些晦澀難懂,但對於瞭解這些網絡結構很有幫助。

2.3 殘差網絡(ResNets)(Residual Networks (ResNets))

       非常非常深的神經網絡是很難訓練的,因爲存在梯度消失和梯度爆炸問題。這節課我們學習跳躍連接(Skip connection),它可以從某一層網絡層獲取激活,然後迅速反饋給另外一層,甚至是神經網絡的更深層。我們可以利用跳躍連接構建能夠訓練深度網絡的ResNets,有時深度能夠超過100層,讓我們開始吧。

       ResNets是由殘差塊(Residual block)構建的,首先我解釋一下什麼是殘差塊。
       在這裏插入圖片描述
       這是一個兩層神經網絡,在 L L 層進行激活,得到 a [ l + 1 ] a^{\left\lbrack l + 1 \right\rbrack} ,再次進行激活,兩層之後得到 a [ l + 2 ] a^{\left\lbrack l + 2 \right\rbrack} 。計算過程是從 a [ l ] a^{[l]} 開始,首先進行線性激活,根據這個公式: z [ l + 1 ] = W [ l + 1 ] a [ l ] + b [ l + 1 ] z^{\left\lbrack l + 1 \right\rbrack} = W^{\left\lbrack l + 1 \right\rbrack}a^{[l]} + b^{\left\lbrack l + 1 \right\rbrack} ,通過 a [ l ] a^{[l]} 算出 z [ l + 1 ] z^{\left\lbrack l + 1 \right\rbrack} ,即 a [ l ] a^{[l]} 乘以權重矩陣,再加上偏差因子。然後通過ReLU非線性激活函數得到 a [ l + 1 ] a^{\left\lbrack l + 1 \right\rbrack} a [ l + 1 ] = g ( z [ l + 1 ] ) a^{\left\lbrack l + 1 \right\rbrack} =g(z^{\left\lbrack l + 1 \right\rbrack}) 計算得出。接着再次進行線性激活,依據等式 z [ l + 2 ] = W [ 2 + 1 ] a [ l + 1 ] + b [ l + 2 ] z^{\left\lbrack l + 2 \right\rbrack} = W^{\left\lbrack 2 + 1 \right\rbrack}a^{\left\lbrack l + 1 \right\rbrack} + b^{\left\lbrack l + 2 \right\rbrack} ,最後根據這個等式再次進行ReLu非線性激活,即 a [ l + 2 ] = g ( z [ l + 2 ] ) a^{\left\lbrack l + 2 \right\rbrack} = g(z^{\left\lbrack l + 2 \right\rbrack}) ,這裏的 g g 是指ReLU非線性函數,得到的結果就是 a [ l + 2 ] a^{\left\lbrack l + 2 \right\rbrack} 。換句話說,信息流從 a [ l ] a^{\left\lbrack l \right\rbrack} a [ l + 2 ] a^{\left\lbrack l + 2 \right\rbrack} 需要經過以上所有步驟,即這組網絡層的主路徑。
       在這裏插入圖片描述
       在殘差網絡中有一點變化,我們將 a [ l ] a^{[l]} 直接向後,拷貝到神經網絡的深層,在ReLU非線性激活函數前加上 a [ l ] a^{[l]} ,這是一條捷徑。 a [ l ] a^{[l]} 的信息直接到達神經網絡的深層,不再沿着主路徑傳遞,這就意味着最後這個等式( a [ l + 2 ] = g ( z [ l + 2 ] ) a^{\left\lbrack l + 2 \right\rbrack} = g(z^{\left\lbrack l + 2 \right\rbrack}) )去掉了,取而代之的是另一個ReLU非線性函數,仍然對 z [ l + 2 ] z^{\left\lbrack l + 2 \right\rbrack} 進行 g g 函數處理,但這次要加上 a [ l ] a^{[l]} ,即:   a [ l + 2 ] = g ( z [ l + 2 ] + a [ l ] ) \ a^{\left\lbrack l + 2 \right\rbrack} = g\left(z^{\left\lbrack l + 2 \right\rbrack} + a^{[l]}\right) ,也就是加上的這個 a [ l ] a^{[l]} 產生了一個殘差塊。
       在這裏插入圖片描述
       在上面這個圖中,我們也可以畫一條捷徑,直達第二層。實際上這條捷徑是在進行ReLU非線性激活函數之前加上的,而這裏的每一個節點都執行了線性函數和ReLU激活函數。所以 a [ l ] a^{[l]} 插入的時機是在線性激活之後,ReLU激活之前。除了捷徑,你還會聽到另一個術語「跳躍連接」,就是指 a [ l ] a^{[l]} 跳過一層或者好幾層,從而將信息傳遞到神經網絡的更深層。

       ResNet的發明者是何凱明Kaiming He)、張翔宇Xiangyu Zhang)、任少卿Shaoqing Ren)和孫劍Jiangxi Sun),他們發現使用殘差塊能夠訓練更深的神經網絡。所以構建一個ResNet網絡就是通過將很多這樣的殘差塊堆積在一起,形成一個很深神經網絡,我們來看看這個網絡。
       在這裏插入圖片描述
       這並不是一個殘差網絡,而是一個普通網絡(Plain network),這個術語來自ResNet論文。
       在這裏插入圖片描述
       把它變成ResNet的方法是加上所有跳躍連接,正如前一張幻燈片中看到的,每兩層增加一個捷徑,構成一個殘差塊。如圖所示,5個殘差塊連接在一起構成一個殘差網絡。
       在這裏插入圖片描述
       如果我們使用標準優化算法訓練一個普通網絡,比如說梯度下降法,或者其它熱門的優化算法。如果沒有殘差,沒有這些捷徑或者跳躍連接,憑經驗你會發現隨着網絡深度的加深,訓練錯誤會先減少,然後增多。而理論上,隨着網絡深度的加深,應該訓練得越來越好纔對。也就是說,理論上網絡深度越深越好。但實際上,如果沒有殘差網絡,對於一個普通網絡來說,深度越深意味着用優化算法越難訓練。實際上,隨着網絡深度的加深,訓練錯誤會越來越多。

       但有了ResNets就不一樣了,即使網絡再深,訓練的表現卻不錯,比如說訓練誤差減少,就算是訓練深達100層的網絡也不例外。有人甚至在1000多層的神經網絡中做過實驗,儘管目前我還沒有看到太多實際應用。但是對 x x 的激活,或者這些中間的激活能夠到達網絡的更深層。這種方式確實有助於解決梯度消失和梯度爆炸問題,讓我們在訓練更深網絡的同時,又能保證良好的性能。也許從另外一個角度來看,隨着網絡越來深,網絡連接會變得臃腫,但是ResNet確實在訓練深度網絡方面非常有效。

2.4 殘差網絡爲什麼有用?(Why ResNets work?)

       爲什麼ResNets能有如此好的表現,我們來看個例子,它解釋了其中的原因,至少可以說明,如何構建更深層次的ResNets網絡的同時還不降低它們在訓練集上的效率。希望你已經通過第三門課瞭解到,通常來講,網絡在訓練集上表現好,才能在Hold-Out交叉驗證集或dev集和測試集上有好的表現,所以至少在訓練集上訓練好ResNets是第一步。

       先來看個例子,上節我們瞭解到,一個網絡深度越深,它在訓練集上訓練的效率就會有所減弱,這也是有時候我們不希望加深網絡的原因。而事實並非如此,至少在訓練ResNets網絡時,並非完全如此,舉個例子。
       在這裏插入圖片描述
       假設有一個大型神經網絡,其輸入爲 X X ,輸出激活值 a [ l ] a^{[l]} 。假如你想增加這個神經網絡的深度,那麼用Big NN表示,輸出爲 a [ l ] a^{\left\lbrack l\right\rbrack} 。再給這個網絡額外添加兩層,依次添加兩層,最後輸出爲 a [ l + 2 ] a^{\left\lbrack l + 2 \right\rbrack} ,可以把這兩層看作一個ResNets塊,即具有捷徑連接的殘差塊。爲了方便說明,假設我們在整個網絡中使用ReLU激活函數,所以激活值都大於等於0,包括輸入 X X 的非零異常值。因爲ReLU激活函數輸出的數字要麼是0,要麼是正數。
       在這裏插入圖片描述
       我們看一下 a [ l + 2 ] a^{\left\lbrack l + 2\right\rbrack} 的值,也就是上節課講過的表達式,即 a [ l + 2 ] = g ( z [ l + 2 ] + a [ l ] ) a^{\left\lbrack l + 2\right\rbrack} = g(z^{\left\lbrack l + 2 \right\rbrack} + a^{\left\lbrack l\right\rbrack}) ,添加項 a [ l ] a^{\left\lbrack l\right\rbrack} 是剛添加的跳躍連接的輸入。展開這個表達式 a [ l + 2 ] = g ( W [ l + 2 ] a [ l + 1 ] + b [ l + 2 ] + a [ l ] ) a^{\left\lbrack l + 2 \right\rbrack} = g(W^{\left\lbrack l + 2 \right\rbrack}a^{\left\lbrack l + 1 \right\rbrack} + b^{\left\lbrack l + 2 \right\rbrack} + a^{\left\lbrack l\right\rbrack}) a^{\left\lbrack l + 2 \right\rbrack} = g(W^{\left\lbrack l + 2 \right\rbrack}a^{\left\lbrack l + 1 \right\rbrack} + b^{\left\lbrack l + 2 \right\rbrack} + a^{\left\lbrack l\right\rbrack}),其中 z [ l + 2 ] = W [ l + 2 ] a [ l + 1 ] + b [ l + 2 ] z^{\left\lbrack l + 2 \right\rbrack} = W^{\left\lbrack l + 2 \right\rbrack}a^{\left\lbrack l + 1 \right\rbrack} + b^{\left\lbrack l + 2\right\rbrack} 。注意一點,如果使用L2正則化或權重衰減,它會壓縮 W [ l + 2 ] W^{\left\lbrack l + 2\right\rbrack} 的值。如果對 b b 應用權重衰減也可達到同樣的效果,儘管實際應用中,你有時會對 b b 應用權重衰減,有時不會。這裏的 W W 是關鍵項,如果 W [ l + 2 ] = 0 W^{\left\lbrack l + 2 \right\rbrack} = 0 ,爲方便起見,假設 b

相關文章
相關標籤/搜索