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 href="http://www.imooc.com" 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" href="http://www.sifangku.com" 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小时内与您取得联系。