1.段落標簽<p>
<style> p{margin:0px;} </style>
2.斜體標簽<em>
<em>斜體</em>
3.粗體標簽<strong>
<strong>加粗</strong>
4.<span>標簽
被用來組合文檔中的行內元素。使用 <span> 來組合行內元素,以便通過樣式來格式化它們。
例如:
<style> span{ color:blue; } </style>
這樣,<span>標簽包含的文本就變成了藍色的字體。
5.<q>標簽
作用:段文本引用
例如:
<p>最初知道莊子,是從一首詩<q>莊生曉夢迷蝴蝶。望帝春心托杜鵑。</q>開始的。雖然當時不知道是什么意思,只是覺得詩句挺特別。后來才明白這個典故出自是莊子的《逍遙游》,《逍遙游》代表了莊子思想的最高境界,是對世俗社會的功名利祿及自己的舍棄。</p> 在上面的例子中,“莊生曉夢迷蝴蝶。望帝春心托杜鵑。” 這是一句詩歌,出自晚唐詩人李商隱的《錦瑟》 。因為不是作者自己的文字,所以需要使用<q></q>實現引用。 注意要引用的文本不用加雙引號,瀏覽器會對q標簽自動添加雙引號。 這里用<q>標簽的真正關鍵點不是它的默認樣式雙引號(如果這樣我們不如自己在鍵盤上輸入雙引號就行了),而是它的語義:引用別人的話。 補充知識:語義化網頁結構有助于搜索引擎的收錄。同一個效果可以用很多鐘方式實現,但這只方便了瀏覽者,而搜索引擎不知道這里到底是什么內容,這里如果你使用標簽,那么就告訴瀏覽器這里是引用的話。而且在手持設備或移動設備不能很好支持css的基礎上,瀏覽器會使用默認的效果,因而提供較好可讀性。
6.<blockquote>標簽
作用:長文本引用
例如:
<blockquote>明月出天山,蒼茫云海間。長風幾萬里,吹度玉門關。漢下白登道,胡窺青海灣。由來征戰地,不見有人還。 戍客望邊色,思歸多苦顏。高樓當此夜,嘆息未應閑。</blockquote>
注意:瀏覽器對<blockquote>標簽的解析是縮進樣式
7.<br>標簽
怎么可以讓每一句詩詞后面加入一個折行呢?那就可以用到<br />標簽了,在需要加回車換行的地方加入<br />,<br />標簽作用相當于word文檔中的回車。
語法:
xhtml1.0寫法:
<br/>
html4.01寫法:
<br>
現在一般使用 xhtml1.0 的版本的寫法(其它標簽也是),這種版本比較規范。
與以前我們學過的標簽不一樣,<br />標簽是一個空標簽,沒有HTML內容的標簽就是空標簽,空標簽只需要寫一個開始標簽,這樣的標簽有<br />、<hr />和<img />。
講到這里,你是不是有個疑問,想折行還不好說嘛,就像在 word 文件檔或記事本中,在想要折行的前面輸入回車不就行了嗎? 不好意思,在 html 中是忽略回車和空格的,你輸入的再多回車和空格也是顯示不出來的。
8.<hr>標簽
在信息展示時,有時會需要加一些用于分隔的橫線,這樣會使文章看起來整齊些。
語法:
html4.01版本
<hr>
xhtml1.0版本
<hr/>
注意:
9.<address>標簽
一般網頁中會有一些網站的聯系地址信息需要在網頁中展示出來,這些聯系地址信息如公司的地址就可以<address>標簽。也可以定義一個地址(比如電子郵件地址)、簽名或者文檔的作者身份。
語法:
<address>聯系地址信息</address>
如:
<address>文檔編寫:lilian 北京市西城區德外大街10號</address>
10.<code>標簽
在介紹語言技術的網站中,避免不了在網頁中顯示一些計算機專業的編程代碼,當代碼為一行代碼時,你就可以使用<code>標簽了,如下面例子:
<code>var i=i+300;</code>
注意:在文章中一般如果要插入多行代碼時不能使用<code>標簽了。
語法:
<code>代碼語言</code>
注:如果是多行代碼,可以使用<pre>標簽。
11.<pre>標簽
主要作用:預格式化的文本。被包圍在 pre 元素中的文本通常會保留空格和換行符。
語法:
<pre>語言代碼段</pre>
如下代碼:
<pre> var message="歡迎"; for(var i=1;i<=10;i++) { alert(message); } </pre>
效果如下:
注意:<pre> 標簽不只是為顯示計算機的源代碼時用的,在你需要在網頁中預顯示格式時都可以使用它,只是<pre>標簽的一個常見應用就是用來展示計算機的源代碼。
12.<ul>標簽
ul-li是沒有前后順序的信息列表。
ul{ list-style:circle; }
ul{ list-style:none }
<ul> <li>信息</li> <li>信息</li> ...... </ul>
<ul> <li>精彩少年</li> <li>美麗突然出現</li> <li>觸動心靈的旋律</li> </ul>
13.<ol>標簽
ol-li是有前后順序的信息列表
<ol> <li>信息</li> <li>信息</li> ...... </ol>
<ol> <li>前端開發面試心法 </li> <li>零基礎學習html</li> <li>JavaScript全攻略</li> </ol>
<ol>在網頁中顯示的默認樣式一般為:每項<li>前都自帶一個序號,序號默認從1開始。
14.<div>標簽
15.<table>標簽
1)屬性:border
作用:規定表格邊框的寬度
2)屬性:cellpadding
作用:單元格中的文本與單元格邊框的間距
3)屬性:cellspacing
作用:單元格之間的間距
table、tbody、tr、th、td
1、<table>…</table>:整個表格以<table>標記開始、</table>標記結束。
2、<tbody>…</tbody>:當表格內容非常多時,表格會下載一點顯示一點,但如果加上<tbody>標簽后,這個表格就要等表格內容全部下載完才會顯示。如右側代碼編輯器中的代碼。
3、<tr>…</tr>:表格的一行,所以有幾對tr 表格就有幾行。
4、<td>…</td>:表格的一個單元格,一行中包含幾對<td>...</td>,說明一行中就有幾列。
- 常用屬性: colspan:規定單元格可橫跨的列數,值為數字 rowspan:規定單元格可橫跨的行數,值為數字
5、<th>…</th>:表格的頭部的一個單元格,表格表頭。
6、表格中列的個數,取決于一行中數據單元格的個數。
總結:
16.<caption>標簽
表格還是需要添加一些標簽進行優化,可以添加標題和摘要。
摘要的內容是不會在瀏覽器中顯示出來的。它的作用是增加表格的可讀性(語義化),使搜索引擎更好的讀懂表格內容,還可以使屏幕閱讀器更好的幫助特殊用戶讀取表格內容。語法:
<table summary="表格簡介文本">
用以描述表格內容,標題的顯示位置:表格上方。語法:
<table> <caption>標題文本</caption> <tr> <td>…</td> <td>…</td> … </tr> … </table>
17.<a>標簽
<a href="目標網址" title="鼠標滑過顯示的文本">鏈接顯示的文本</a>
例如:
<a title="點擊進入慕課網">click here!</a>
上面例子作用是單擊click here!文字,網頁鏈接跳轉到http://www.imooc.com這個網頁。
<a href="目標網址" target="_blank">click here!</a>
<a>標簽還有一個作用是可以鏈接Email地址,使用mailto能讓訪問者便捷向網站管理者發送電子郵件。
注意:如果mailto后面同時有多個參數的話,第一個參數必須以“?”開頭,后面的參數每一個都以“&”分隔。引號只有一對!
例子: <a href="mailto:yy@qq.com? cc=xx@qq.com & bcc=aa@qq.com & subject=郵件主題 & body=郵件內容">
那么: 1)A知道自己發送郵件給了B1、B2、B3,并且抄送給了C1、C2、C3,密送給了D1、D2、D3。 2)B1知道這封是A發送給B1、B2、B3的郵件,并且抄送給了C1、C2、C3,但不知道密送給了D1、D2、D3。 3)C1知道這封是A發送給B1、B2、B3的郵件,并且抄送給了C1、C2、C3,但不知道密送給了D1、D2、D3。 4)D1知道這封是A發送給B1、B2、B3的郵件,并且抄送給了C1、C2、C3,而且密送給了自己,但不知道密送給了D2、D3。 5)郵箱地址 mailto: <a href="mailto:qiujie@staff.weibo.com">發送</a> 6)抄送地址 cc: <a href="mailto:qiujie@staff.weibo.com?cc=zz@sina.com">發送</a> 7)密件抄送地址 用分號分隔: <a href="mailto:qiujie@staff.weibo.com?bcc=zz@sina.com">發送</a> 8)多個收件人、抄送人、密送人 ; bcc: <a href="mailto:qiujie@staff.weibo.com;zz@sina.com">發送</a> 9)郵件主題 subject: <a href="mailto:qiujie@staff.weibo.com?subject=郵件主題">發送</a> 10)郵件內容 body: <a href="mailto:qiujie@staff.weibo.com?body=郵件正文">發送</a> 例子: <a href="mailto:yy@imooc.com;10001@qq.com?cc=10002@qq.com&bbc=madanteng@qqhelp.com&subject=觀了不起的蓋茨比有感。&body=你好,對此評論有些想法。">對此影評有何感想,發送郵件給我</a>
18.<img>標簽
在網頁的制作中為使網頁炫麗美觀,肯定是缺少不了圖片,可以使用
標簽來插入圖片。
[站外圖片上傳中……(2)] <img src = "myimage.gif" alt = "My Image" title = "My Image" />
src:標識圖像的位置; alt:指定圖像的描述性文本,當圖像不可見時(下載不成功時),可看到該屬性指定的文本; title:提供在圖像可見時對圖像的描述(鼠標滑過圖片時顯示的文本); 圖像可以是GIF,PNG,JPEG格式的圖像文件。 路徑有兩種填寫方式:絕對路徑、相對路徑 相對路徑:相對于我們當前 html 文件的位置來寫路徑即可! ./表示當前目錄,../表示上一級目錄
19.<form>標簽
注意:
1、所有表單控件(文本框、文本域、按鈕、單選框、復選框等)都必須放在<form></form>標簽之間(否則用戶輸入的信息可提交不到服務器上哦!)。
2、method:post/get的區別這一部分內容屬于后端程序員考慮的問題。
語法:
<form method="傳送方式" action="服務器文件">
<form> :<form>標簽是成對出現的,以<form>開始,以</form>結束。 action :瀏覽者輸入的數據被傳送到的地方,比如一個PHP頁面(save.php)。 method : 數據傳送的方式(get/post)。 <form method="post" action="save.php"> <label for="username">用戶名:</label> <input type="text" name="username" /> <label for="pass">密碼:</label> <input type="password" name="pass" /> </form>
20.<input>標簽
語法:
<form> <input type="text/password" name="名稱" value="文本" /> </form>
舉例: <form> 姓名: <input type="text" name="myName"/><br/> 密碼: <input type="password" name="pass"/> </form> value="xxx" 替換為 placeholder="xxx" 的體驗更好一些,placeholder屬性為 HTML 5 的新屬性。placeholder 屬性提供可描述輸入字段預期值的提示信息(hint)。該提示會在輸入字段為空時顯示,并會在字段獲得焦點時消失。
語法:
<input placeholder="text"/> 注釋:placeholder 屬性適用于以下的 <input> 類型:text, search, url, telephone, email 以及 password。
注意:同一組的單選按鈕,name 取值一定要一致,比如上同一個名稱“gender”,這樣同一組的單選按鈕才可以起到單選的作用!
type:
name:為文本框命名,以備后臺程序ASP 、PHP使用。
value:為文本輸入框設置默認值。(一般起到提示作用)
21.<textarea>標簽
語法:
<textarea rows="行數" cols="列數">文本</textarea>
舉例:
<form method="post" action="save.php"> <label>聯系我們</label> <textarea cols="50" rows="10" >在這里輸入內容...</textarea> </form>
22.<select>標簽
語法:
<select> <option value="提交的值">顯示的值</option> ... </select> 設置selected="selected"屬性,則該選項就被默認選中。 selected="selected"
<select multiple="multiple"> 然后選擇時候按ctrl點鼠標選中
<option disabled="disabled">
把相關的選項組合在一起
屬性 label:給選項組命名
屬性 disabled:禁用該選項組
23.<label>標簽
<label for="控件id名稱">
注意:標簽的 for 屬性中的值應當與相關控件的 id 屬性值一定要相同。
<form> <label for="male">男</label> <input type="radio" name="gender" id="male" /> <br /> <label for="female">女</label> <input type="radio" name="gender" id="female" /> <label for="email">輸入你的郵箱地址</label> <input type="email" id="email" placeholder="Enter email"> </form>
24.<map>標簽
使用 map 標簽可以給圖片某塊區域加超鏈接
使用方法:
1)為 map 標簽首先加上 id 屬性用來為 map 標簽定義一個唯一的名稱
2)為了保證兼容性再加上 name 屬性,屬性值與 id 的值相同
3)為 map 標簽所作用的圖片加上 usemap 屬性,屬性值為 #id 名稱
4)在 map 標簽內嵌套 area 標簽來實現給指定區域加鏈接
<area shape="" coords="" href ="" alt="" /> shape 屬性:定義鏈接區域的形狀,常用值 rect、circle coords 屬性:確定區域的精確位置。填寫坐標即可,以父元素左上角為原點,可借助qq截圖來得到想要的坐標 href 屬性:填寫鏈接地址即可 alt 屬性:給鏈接加一些說明信息
例子:
<map id="img1" name="img1"> <area shape="rect" coords="184,33,391,258" href="http:www.baidu.com" alt="百度一下" target="_blank" /> <area shape="circle" coords="507,287,20" alt="私房庫我的博客" target="_blank" /> </map>
注意:
25.<iframe>標簽
創建包含另外一個文檔的內聯框架(即行內框架)
屬性:
值:1、0
作用:規定是否顯示框架周圍的邊框。
作用:定義 iframe 的寬度
作用:定義高度
作用:給 iframe 命名
值:yes、no、auto
作用:規定是否在 iframe 中顯示滾動條
作用:規定在 iframe 中顯示的文檔的 URL
可以是本地的 html 文件,也可以是遠程的 html 文件
標簽寫法
1)不允許寫結束標簽的元素
area,base,br,col,command,embed,hr,img,input,keygen,link,meta,paran,source,track,wbr。這些標簽都是單標簽例如:br 標簽,不可以這樣<br></br>,只能<br />這樣來關閉標簽。
2)可以省略結束標記的元素有:
li,dt,dd,p,rt,rp,optgroup,option,colgroup,thead,tbody,tfoot,tr,td,th。
3)可以省略全部標記的元素有
html,head,body,colgroup,tbody
例如:disabled,readonly,checked 等只寫屬性而不寫屬性值得時候當做 ture 不寫屬性表示 false
要求:屬性值不包含 空字符串,<,>,=, ‘
標簽嵌套探討
1.html 規定我們必須要嵌套著寫的標簽
例如:頁面頭部是嵌套在 head 標簽里面的,主體內容都是嵌套在 body 標簽里面的表單的內容是嵌套在 form 標簽里面的,dt、dd 是嵌套在 dl 標簽里面的,li 是嵌套到ul 標簽里面的,等等...
2.塊級元素可以嵌套內聯元素,但是內聯元素不能包含塊元素
<div> <span>我是一個 span 元素</span> </div> —— 對 <span> <div>div 元素</div> </span> —— 錯
3.內聯元素可以嵌套內聯元素
<a href="#"> <span></span> </a> —— 對
4.塊級元素與塊級元素嵌套注意點
<p><ol><li></li></ol></p> —— 錯 <p><div></div></p> —— 錯
喜歡前端的小伙伴們可以在評論區留言,尋找和小馮童鞋一樣熱愛前端的友人,讓我們一起玩轉前端的世界!
本Pandas教程中,我們將詳細介紹如何使用Pandas read_html方法從HTML中獲取數據。首先,在最簡單的示例中,我們將使用Pandas從一個字符串讀取HTML。其次,我們將通過幾個示例來使用Pandas read_html從Wikipedia表格中獲取數據。在之前的一篇文章(關于Python中的探索性數據分析)中,我們也使用了Pandas從HTML表格中讀取數據。
在開始學習Python和Pandas時,為了進行數據分析和可視化,我們通常從實踐導入數據開始。在之前的文章中,我們已經了解到我們可以直接在Python中輸入值(例如,從Python字典創建Pandas dataframe)。然而,通過從可用的源導入數據來獲取數據當然更為常見。這通常是通過從CSV文件或Excel文件中讀取數據來完成的。例如,要從一個.csv文件導入數據,我們可以使用Pandas read_csv方法。這里有一個如何使用該方法的快速的例子,但一定要查看有關該主題的博客文章以獲得更多信息。
現在,上面的方法只有在我們已經有了合適格式的數據(如csv或JSON)時才有用(請參閱關于如何使用Python和Pandas解析JSON文件的文章)。
我們大多數人會使用Wikipedia來了解我們感興趣的主題信息。此外,這些Wikipedia文章通常包含HTML表格。
要使用pandas在Python中獲得這些表格,我們可以將其剪切并粘貼到一個電子表單中,然后,例如使用read_excel將它們讀入Python。現在,這個任務當然可以用更少的步驟來完成:我們可以通過web抓取來對它進行自動化。一定要查看一下什么是web抓取。
當然,這個Pandas讀取HTML教程將要求我們安裝Pandas及其依賴項。例如,我們可以使用pip來安裝Python包,比如Pandas,或者安裝一個Python發行版(例如,Anaconda、ActivePython)。下面是如何使用pip安裝Pandas: pip install pandas。
注意,如果出現消息說有一個更新版本的pip可用,請查看這篇有關如何升級pip的文章。注意,我們還需要安裝lxml或BeautifulSoup4,當然,這些包也可以使用pip來安裝: pip install lxml。
下面是如何使用Pandas read_html從HTML表格中抓取數據的最簡單的語法:
現在我們已經知道了使用Pandas讀取HTML表格的簡單語法,接下來我們可以查看一些read_html示例。
第一個示例是關于如何使用Pandas read_html方法的,我們將從一個字符串讀取HTML表格。
現在,我們得到的結果不是一個Pandas DataFrame,而是一個Python列表。也就是說,如果我們使用type函數,我們可以看到:
如果我們想得到該表格,我們可以使用列表的第一個索引(0)
在第二個Pandas read_html示例中,我們將從Wikipedia抓取數據。實際上,我們將得到蟒科蛇(也稱為蟒蛇)的HTML表格。
現在,我們得到了一個包含7個表(len(df))的列表。如果我們去Wikipedia頁面,我們可以看到第一個表是右邊的那個。然而,在本例中,我們可能對第二個表更感興趣。
在第三個示例中,我們將從瑞典的covid-19病例中讀取HTML表。這里,我們將使用read_html方法的一些附加參數。具體來說,我們將使用match參數。在此之后,我們還需要清洗數據,最后,我們將進行一些簡單的數據可視化操作。
如上圖所示,該表格的標題為:“瑞典各郡新增COVID-19病例”。現在,我們可以使用match參數并將其作為一個字符串輸入:
通過這種方式,我們只得到這個表,但它仍然是一個dataframes列表。現在,如上圖所示,在底部,我們有三個需要刪除的行。因此,我們要刪除最后三行。
現在,我們將使用Pandas iloc刪除最后3行。注意,我們使用-3作為第二個參數(請確保你查看了這個Panda iloc教程,以獲得更多信息)。最后,我們還創建了這個dataframe的一個副本。
在下一節中,我們將學習如何將多索引列名更改為單個索引。
現在,我們要去掉多索引列。也就是說,我們將把2列索引(名稱)變成唯一的列名。這里,我們將使用DataFrame.columns 和 DataFrame.columns,get_level_values:
最后,正如你在“date”列中所看到的,我們使用Pandas read_html從WikiPedia表格抓取了一些注釋。接下來,我們將使用str.replace方法和一個正則表達式來刪除它們:
現在,我們繼續使用Pandas set_index將日期列變成索引。這樣一來,我們稍后就可以很容易地創建一個時間序列圖。
現在,為了能夠繪制這個時間序列圖,我們需要用0填充缺失的值,并將這些列的數據類型更改為numeric。這里我們也使用了apply方法。最后,我們使用cumsum方法來獲得列中每個新值累加后的值:
在最后一個示例中,我們使用Pandas read_html獲取我們抓取的數據,并創建了一個時間序列圖。現在,我們還導入了matplotlib,這樣我們就可以改變Pandas圖例的標題的位置:
在這個Pandas教程中,我們學習了如何使用Pandas read_html方法從HTML中抓取數據。此外,我們使用來自一篇Wikipedia文章的數據來創建了一個時間序列圖。最后,我們也可以通過參數index_col來使用Pandas read_html將' Date '列設置為索引列。
英文原文:https://www.marsja.se/how-to-use-pandas-read_html-to-scrape-data-from-html-tables
譯者:一瞬
者丨黎明灰燼
來源|https://zhuanlan.zhihu.com/p/80361782
卷積(Convolution)是神經網絡的核心計算之一,它在計算機視覺方面的突破性進展引領了深度學習的熱潮。卷積的變種豐富,計算復雜,神經網絡運行時大部分時間都耗費在計算卷積,網絡模型的發展在不斷增加網絡的深度,因此優化卷積計算就顯得尤為重要。
隨著技術的發展,研究人員提出了多種優化算法,包括 Im2col、Winograd 等等。本文首先定義卷積神經網絡的概念,繼而簡要介紹幾種常見的優化方法,并討論作者在該領域的一些經驗。
卷積神經網絡(Convolution Neural Networks, CNN)的概念拓展自信號處理領域的卷積。信號處理的卷積定義為
(1)
由于對稱性
卷積計算在直覺上不易理解,其可視化后如圖一所示。圖中紅色滑塊在移動過程中與藍色方塊的積繪制成的三角圖案即為卷積結果 (?)() 在各點上的取值。
圖一:信號處理中的卷積
公式1的離散形式為 :
(2)
將該卷積拓展到二維空間即可得到神經網絡中的卷積,可簡寫為:
(3)
其中 為卷積輸出, 為卷積輸入, 為卷積核。該計算的動態可視化可以參考 conv_arithmetic,這里不再介紹。
當應用到計算機視覺中處理圖片時,圖片的通道(Channel)可以對二維卷積簡單堆疊,即:
(4)
其中 c 是輸入的通道。這便是在三維張量中應用二維卷積的計算。
很多時候,公式描述顯得不是很直觀,圖二是堆疊的二維卷積的可視化。其中,與輸入、輸出、卷積核相關的標記帶有前綴 I、O、K。此外,本文圖例對輸出、輸入、卷積核三者的著色一般為:橙色、黃色、綠色。
圖二:卷積計算定義
當中張量的內存布局為 NHWC 時,卷積計算相應的偽代碼如下。其中外三層循環遍歷輸出 C 的每個數據點,對于每個輸出數據都需要經由內三層循環累加求和得到(點積)。
for (int oh = 0; oh < OH; oh++) {
for (int ow = 0; ow < OW; ow++) {
for (int oc = 0; oc < OC; oc++) {
C[oh][ow][oc] = 0;
for (int kh = 0; kh < KH, kh++){
for (int kw = 0; kw < KW, kw++){
for (int ic = 0; ic < IC, ic++){
C[oh][ow][oc] += A[oh+kh][ow+kw][ic] * B[kh][kw][ic];
}
}
}
}
}
}
和矩陣乘的優化方法類似,我們也可針對該計算進行向量化、并行化、循環展開的基本的優化操作。卷積的問題在于其 和 一般不超過 5 ,這不容易向量化,而計算特征又有輸入在空間維度存在數據復用。該計算的復雜性導致產生了幾種優化方法,下面我們介紹幾種。
作為早期的深度學習框架,Caffe 中卷積的實現采用的是基于 im2col 的方法,至今仍是卷積重要的優化方法之一。
Im2col 是計算機視覺領域中將圖片轉換成矩陣的矩陣列(column)的計算過程。從上一節的介紹中可以看到,二維卷積的計算比較復雜不易優化,因此在深度學習框架發展的早期,Caffe 使用 Im2col 方法將三維張量轉換為二維矩陣,從而充分利用已經優化好的 GEMM 庫來為各個平臺加速卷積計算。最后,再將矩陣乘得到的二維矩陣結果使用 Col2im 將轉換為三維矩陣輸出。
除非特別說明,本文默認采用的內存布局形式為 NHWC 。其他的內存布局和具體的轉換后的矩陣形狀或許略有差異,但不影響算法本身的描述。
圖三:Im2col 算法計算卷積的過程
圖三是使用 Im2col 算法計算卷積的過程示例,具體的過程包括(簡單起見忽略 Padding 的情況,即認為 =,=:
Im2col 計算卷積使用 GEMM 的代價是額外的內存開銷。這是因為原始卷積計算中,卷積核在輸入上滑動以計算輸出時,相鄰的輸出計算在空間上復用了一定的輸入輸出。而用 Im2col 將三維張量展開成二維矩陣時,這些原本可以復用的數據平坦地分布到矩陣中,將輸入數據復制了 ??1 份。
當卷積核尺寸 × 是 1×1 時,上述步驟中的 和 可以消去,即輸入轉換后形狀為 (×)×(),卷積核形狀為 ()×(),卷積計算退化為矩陣乘。注意觀察,對于這種情況,Im2col 過程實際上并沒有改變輸入的形狀,因此矩陣乘操作可以直接在原始輸入上運行,從而省去內存組織的過程(即上述步驟中的 1、2、4 步)。
神經網絡中卷積的內存布局主要有 NCHW 和 NHWC 兩種,不同的內存布局會影響計算運行時訪問存儲器的模式,特別是在運行矩陣乘時。本小節分析采用 Im2col 優化算法時計算性能性能和內存布局的關系。
在完成 Im2col 轉換后,得到用于運行矩陣乘的輸入矩陣和卷積核矩陣。對計算過程施加矩陣計算中常用的數據劃分、向量化等優化方法(相關定義請參考通用矩陣乘(GEMM)優化算法)。下面著重分析在這種場景下,不同內存布局對性能的影響。
首先考慮 NCHW 內存布局,將 NCHW 內存布局的卷積對應到矩陣乘 = 時, 是卷積核(filter), 是輸入(input),各個矩陣的維度如圖四所示。圖中的 ,, 用于標記矩陣乘,即
,同時標記出它們和卷積計算中各個維度的關系。
圖四:NCHW 內存布局卷積轉換成的矩陣乘
對該矩陣施行劃分后,我們詳細分析局部性的表現,并標記在圖四中。其中 Inside 表示 4×4 小塊矩陣乘內部的局部性,Outside 表示在削減維度方向小塊之間的局部性。
因此,用 Im2col 處理卷積計算時,NCHW 布局對內存很不友好。
圖五是與之相對的 NHWC 內存布局的示例。值得注意的是,NHWC 和 NCHW 中 、 矩陣所代表的張量發生了調換——=×(調換一下只是不想多畫一張圖)。具體的拆分方式仍然一樣,也正是上一小節中描述的步驟所構建的矩陣。
圖五:NHWC 內存布局卷積轉換成的矩陣乘
類似地,分析三個張量的訪存表現可知:
兩種內存布局中的卷積核緩存表現并不是問題,因為卷積核在運行期間保持不變,可以在模型加載階段轉換卷積核的內存布局,使其在小塊外的內存對緩存友好(例如將 (××)×() 的布局轉換為 ()×(×× )。這里值得說明的是一般框架或引擎的運行都至少可分為兩個階段:準備階段和運行階段。一些模型的預處理工作可以放在準備階段完成,例如重新排布卷積核的內存布局這種在運行階段保持不變的數據。
因此,當使用 Im2col 方法計算時,整體的訪存表現取決于輸入的情況,即 NHWC 的內存布局要比 NCHW 內存布局更加友好。我們在實踐過程中的一個實驗表明,對于一個 1×1 卷積核的卷積,當采用類似的優化方法時,從 NCHW 轉換為 NHWC 可以將高速緩存缺失率從約 50% 降低到 2% 左右。這種程度的提高可以大幅改進軟件的運行性能(這里特指不使用特別設計過的矩陣乘優化方法)。
Im2col 是一種比較樸素的卷積優化算法,在沒有精心處理的情況下會帶來較大的內存開銷。空間組合(Spatial pack)是一種類似矩陣乘中重組內存的優化算法。
圖六:空間組合優化算法對計算的劃分
空間組合優化算法是一種基于分治法(Divide and Conquer)的方法——它基于空間特性將卷積計算劃分為若干份,分別處理。圖六所示是在空間上將輸出、輸入劃分為四份。
劃分后,一個大的卷積計算被拆分為若干個小的卷積計算。雖然在劃分的過程中計算總量不變,但計算小矩陣時訪存局部性更好,可以借由計算機存儲層次結構獲得性能提升。這通過圖七中的步驟來完成。該步驟和上節中 Im2col 重組內存的過程類似:
圖七:空間組合計算的步驟
值得注意的是,方便起見,上文的描述中忽略了 Padding 的問題。實際在步驟 1 中將輸入張量劃分為若干個小張量時,除了將劃分的小塊中原始數據拷貝外,還需要將相鄰的小張量的邊界數據拷貝。具體而言,如圖八所示,空間拆分得到的小張量的形狀實際上是:
×(/?+2(?1))×(/+(?1))×.(5)
這里的 2(?1) 和 2(?1) 遵循 Padding 規則。規則為
時,它們可以忽略;規則為 SAME 時,位于源張量邊界的一邊 Padding 補
,不在源張量邊界的 Padding 則使用鄰居張量的值。只要考慮一下卷積的計算原理,這是顯而易見的。
圖八:空間組合算法的劃分細節
上面的三個示例圖都是拆分為 4 份的情況,實際應用中可以拆為很多份。例如可以拆成小張量邊長為 4 或者 8 ,從而方便編譯器向量化計算操作。隨著拆分出的張量越小,其局部性也越高,負面作用是消耗的額外內存也越多。這些額外內存是由于 Padding 引入的。當拆分為 ??h?w份時,拆分后 Padding 消耗的內存為:
可以看到,隨著拆分的粒度越小,額外消耗的內存越大。值得注意的是,當拆分到最細粒度時,即將在形狀為 ××× 的輸出的空間上拆分? 份時,空間組合退化為 Im2col 方法。此時在一個元素上的卷積即為矩陣乘計算一個輸出元素時的點積。
只做空間劃分時,劃分與卷積核無關。而如果在輸出的通道維度劃分,卷積核也可做相應的拆分。通道維度的劃分相當于固定空間劃分后簡單的堆疊,不會對影響內存消耗,但會影響局部性。對于不同規模的卷積,尋找合適的劃分方法不是一件容易的事情。正如計算機領域的許多問題一樣,該問題也是可以自動化的,例如 AutoTVM 可以在這種情況下尋找較優的劃分方法。
AutoTVM 鏈接:
https://arxiv.org/abs/1805.08166
前兩節介紹的兩種算法,Im2col 在將三維張量組織成矩陣后調用 GEMM 計算庫,這些計算庫很大程度上使用一些基于訪存局部性的優化;空間組合優化則本身就是利用局部性優化的方法。本小節介紹的 Winograd 優化算法則是矩陣乘優化方法中 Coppersmith–Winograd 算法的一種應用,是基于算法分析的方法。
這部分公式過多,排版不便,有興趣的話可以參考原文或其他相關文獻。
Marat Dukhan 在 QNNPACK(Quantized Neural Network PACKage)中推出了間接卷積算法(The Indirect Convolution Algorithm),似乎到目前為止(2019 年中)依然是所有已公開方法中最快的。最近作者發表了相關的文章來介紹其中的主要思想。
雖然該算法在 QNNPACK 中的實現主要用于量化神經網絡(業界的其他量化優化方案都比較傳統 TensorFlow Lite 使用 Im2col 優化算法、騰訊出品的 NCNN使用 Winograd 優化算法;OpenAI 出品的 Tengine 使用 Im2col 優化算法),但其是一種同樣的優化算法設計。
本文寫作時設計文章尚未公開,而理解該算法設計很多細節內容,最好能結合代碼理解。我的 QNNPACK fork 包含一個 explained 分支,其中對增加了對部分代碼的注釋,可作參考。
QNNPACK 鏈接:
https://github.com/pytorch/QNNPACK
TensorFlow Lite 使用 Im2col 優化算法鏈接:
https://github.com/tensorflow/tensorflow/blob/v2.0.0-beta1/tensorflow/lite/kernels/internal/optimized/integer_ops/conv.h
NCNN使用 Winograd 優化算法鏈接:
https://github.com/Tencent/ncnn/blob/20190611/src/layer/arm/convolution_3x3_int8.h
Tengine 使用 Im2col 優化算法鏈接:
https://github.com/OAID/Tengine/blob/v1.3.2/executor/operator/arm64/conv/conv_2d_fast.cpp
我的 QNNPACK fork 鏈接:
https://github.com/jackwish/qnnpack
間接卷積算法的有效工作以來一個關鍵的前提——網絡連續運行時,輸入張量的內存地址保持不變。這一特性其實比較容易滿足,即使地址真的需要變化,也可以將其拷貝到固定的內存區域中。
圖九:間接卷積算法工作流
圖九是間接卷積算法工作流的詳細過程。最左側部分表示多個輸入使用相同的輸入緩沖區(Input Buffer)。間接卷積算法會在該輸入緩沖區基礎上構建如圖十的「間接緩沖區」(Indirect Buffer),而間接緩沖區是間接卷積算法的核心。如圖中右側,在網絡運行時,每次計算出 × 規模的輸出,其中 為將 × 視作一維后的向量化規模。一般 × 為 4×4、8×8 或 4×8 。在計算 × 規模大小的輸出時,經由間接緩沖區取出對應使用的輸入,并取出權重,計算出結果。計算過程等價于計算 × 和 × 矩陣乘。
在實現中,軟件的執行過程分為兩部分:
圖十:間接緩沖區
如相關章節的討論,Im2col 優化算法存在兩個問題,第一是占用大量的額外內存,第二是需要對輸入進行額外的數據拷貝。這兩點如何才能解決呢?間接卷積算法給出的答案是間接緩沖區(Indirect Buffer),如圖十右半所示。
圖十是常規的 Im2col 優化算法和間接卷積優化算法的對比。正如相關小節介紹的那樣,Im2col 優化算法首先將輸入拷貝到一個矩陣中,如圖十中 Input 的相關箭頭,然后執行矩陣乘操作。間接卷積優化算法使用的間接緩沖區中存儲的其實是指向輸入的指針(這也是間接卷積優化算法要求輸入內存地址固定的原因),在運行時根據這些指針即可模擬出類似于 Im2col 的矩陣計算過程。
間接緩沖區可以理解為是一組卷積核大小的緩沖區,共有 × 個,每個緩沖區大小為 ×——每個緩沖區對應著某個輸出要使用的輸入的地址。每計算一個空間位置的輸出,使用一個間接緩沖區;空間位置相同而通道不同的輸出使用相同的間接緩沖區,緩沖區中的每個指針用于索引輸入中 個元素。在計算時,隨著輸出的移動,選用不同的間接緩沖區,即可得到相應的輸入地址。無需再根據輸出目標的坐標計算要使用的輸入的地址,這等同于預先計算地址。
圖十一繪制了當 × 為 4 、 和 均為 3 時,間接緩沖區的實際使用方法與計算過程。圖中命名為局部間接緩沖區意指目前考慮的是計算 × 時核心計算的過程。
當計算 × 大小的輸出時,使用的輸入為卷積核在對應輸入位置上滑動 步所覆蓋的趨于,即規模 ()×(+2(?1))× 的輸入。在間接卷積算法中,這些輸入內存由 M個間接緩沖區中的指針索引,共有 ×× 個。圖十一中標識出了輸入空間位置左上角輸入和相應的輸入緩沖區的對應關系。可以看到,這里的 A、B、C、D 四個輸入緩沖區,相鄰的兩個緩沖區所指向的地址區域有 (?)/ ,這里即為 2/3 ,各個緩沖區中指針的坐標也已標明。
圖十一:間接緩沖區詳解
圖中將平面緩沖區繪制成三維形式(增加 IC 維度),意在說明間接緩沖區的每個指針可索引 IC 個輸入元素,而每個間接緩沖區索引的內容即為與權重對應的輸入內存區域。
進一步地,左上角的輸入緩沖區排列方式并不是最終的排布方法,實際上這些指針會被處理成圖十一中部間接緩沖區的形式。將左上每個緩沖區中的指針打散,即可得到 × 指針,將 A、B、C、D 四個緩沖區的不同空間位置的指針收集到一起,即可得到圖中上部分的緩沖區排列方式 ××。可以看到, A、B、C、D 四個緩沖區內部相同空間位置的指針被組織到了一起。圖中中上部分是可視化的效果,中下部分則是間接緩沖區的真正組織方式。圖中褐色和深黃色的著色對應著相同的輸入內存或指針。值得注意的是,圖例中 Stride 為 1,當 Stride 不為 1 時,重新組織后 A、B、C、D 相同空間的坐標(對應于在輸入的坐標)不一定是連續的,相鄰的空間位置橫向坐標相差 大小。
我們已經知道了間接緩沖區的組織形式,以及其指針對應于輸入內存的地址趨于,現在來研究在計算過程中如何使用這些緩沖區。
和上一小節一樣,本小節的討論大體都在計算 × 規模的輸出,而這些輸出要使用 個 × 大小的輸入,其中有數據重用。現在回顧一下 Im2col 的算法(如圖十一中下左部分),當向量化運行計算時,對于 × 的矩陣乘計算,每次取出 × 規模的輸入和 × 規模的權重,對該塊運行矩陣乘即可得到相應的部分和結果。其中 是向量化計算在 K維度上的步進因子。
而卷積之所以可以使用 Im2col 優化算法,本質原因在于其拆解后忽略內存復用后的計算過程等價于矩陣乘。而間接緩沖區使得可以通過指針模擬出對輸入的訪存。在實際運行計算 × 輸出的計算核(micro kernel)時,會有 個指針掃描輸入。個指針每次從圖十一中下部分的間接緩沖區結構中取出 個地址,即對應于輸入 × 的內存,如圖中右上角的布局。在這里的步進中,仍是以 × 形式運行,其中 在 維度上運動。當這部分輸入掃描完畢后,這 個指針從間接緩沖區中取出相應的指針,繼續下一次對 × 輸入內存的遍歷,每次計算出 1/(?) 的輸出部分和。當這個過程運行 × 次后,即得到了 × 的輸出。圖十一右下角的偽代碼對應了這一過程。由于間接緩沖區已經被組織成了特定的形狀,因此每次更新 個指針時,只需要從間接緩沖區指針(圖中偽代碼里的 p_indirect_buffer)中獲取。
這個過程的邏輯不易理解,這里再做一點補充說明。當上述的 個指針不斷運動掃描內存時,實際上是在掃描三維輸入 Im2col 之后的矩陣。而輸入緩沖區的特定是它將對二維矩陣的掃描轉化為了對三維張量的掃描。對輸入的掃描過程即是對圖十一中上部分可視化的輸入的掃描,聯系左上和左下對應的輸入關系,不難發現它每次掃描輸入中 × 塊內存。值得注意的是,這里的 × 由 個 1× 張量組成,它們之間 維度的間距為 。
這樣一來,只要運行該計算核心 ??/???/? 次,即可得到全部輸出。
間接卷積優化算法解決了卷積計算的三個問題,第一是空間向量化問題,第二是地址計算復雜問題,第三是內存拷貝問題。一般計算卷積時都需要對輸入補零(對于 × 不是 1×1 的情況),這個過程傳統的方法都會發生內存拷貝。而間接卷積優化算法中的間接緩沖區可以通過間接指針巧妙地解決這一問題。在構造間接緩沖區時額外創建一塊 1× 的內存緩沖區,其中填入零值,對于空間中需要補零的位置,將相應的間接指針指向該緩沖區,那么后續計算時即相當于已經補零。例如圖十一中 A 的左上角對應于輸入空間位置 (0,0) 的,當需要補零時該位置一定要為零值,此時只要修改間接緩沖區的地址即可。
至此,本文探討了一些已經發表或開源的卷積神經網絡的優化方法。這些優化方法或多或少地推動了深度學習技術在云端或移動端的應用,幫助了我們的日常生活。例如,最近上海人民乃至全中國人們頭疼的垃圾分類問題,也可以利用深度學習方法來幫助人們了解如何分類。
從本文的集中優化方法中可以看到,卷積神經網絡的優化算法依然可以囊括在基于算法分析的方法和基于軟件優化的方法。實際上,在現代計算機系統中,基于軟件優化的方法,特別是基于計算機存儲層次結構的優化顯得更為重要,因為其方法更容易挖掘和應用。另一方面也是因為現代計算機新增的專用指令已經可以抹除不同計算的耗時差異。在這種場景下,基于算法分析的方法要和基于軟件優化的方法結合或許能取得更優的優化效果。
最后,本文討論的優化方法都是通用的方法,而隨著神經網絡處理器(如寒武紀 MLU、Google TPU)的發展,以及其他通用計算處理器的拓展(如點積相關的指令:Nvidia GPU DP4A、Intel AVX-512 VNNI、ARM SDOT/UDOT ),深度學習的優化或許還值得繼續投入資源。
寒武紀 MLU 鏈接:
https://www.jiqizhixin.com/articles/2019-06-20-12
Nvidia GPU DP4A 鏈接:
https://devblogs.nvidia.com/mixed-precision-programming-cuda-8/
Intel AVX-512 VNN 鏈接:
https://devblogs.nvidia.com/mixed-precision-programming-cuda-8/
本文寫作過程中參考了以下(包括但不限于)資料:
QNNPACK
( 鏈接:https://github.com/pytorch/QNNPACK )
Convolution in Caffe
( 鏈接:https://github.com/Yangqing/caffe/wiki/Convolution-in-Caffe:-a-memo )
TensorFlow
Wikipedia
Fast Algorithms for Convolutional Neural Networks
( 鏈接:https://github.com/Tencent/ncnn )
NCNN
( 鏈接:https://github.com/OAID/Tengine )
Tengine
( 鏈接:https://jackwish.net/neural-network-quantization-introduction-chn.html )
神經網絡量化簡介
( 鏈接:https://jackwish.net/neural-network-quantization-introduction-chn.html )
通用矩陣乘(GEMM)優化算法
( 鏈接:https://jackwish.net/gemm-optimization.html )
The Indirect Convolution Algorithm
*請認真填寫需求信息,我們會在24小時內與您取得聯系。