数字通信中语音信号的抽样频率得t3,44.736Mb的信号,帧格式什么样的?是怎么由t1复用成的?求完整描述的itu

&p&首先必须肯定CMake。它简化了cross platform, OS, compiler的开发成本,同时支持多个IDE(VS, QtCreator, Eclipse, CLion...etc.),并且有一个成熟开源社区。 当你挣扎于将based on autotools的项目迁移至OSX或者Windows的时候,你就会感谢CMake的美好。 网络上有各种资源可供你使用学习,运气好可以直接照用别人的CMakeLists.txt。&/p&&p&支持多种Generator。make, Eclipse, Ninja, MSVC... 个人强推Ninja。 至少我司所有CMake Developer都偏爱它。 突出一个字 - 快。 极大提升了开发效率。&/p&&p&&br&&/p&&p&下面来说说问题。&/p&&p&赞同@Xi Yang和@pilot的说法。 首先CMake的learning curve真的不是一般的高。 build system本来就难,configuration + generation + compile time 任何一个环节都可能出错。上手需要踩过无数的坑,你才能初步入门。&/p&&p&&br&&/p&&p&第二,CMake没有好的paradigm, 就连我司的官方教程都是上古旧书
- CMake 3.1。 上周同事刚发布CMake 3.11.3,距离3.1已经过去4年多了。虽然官方的documentation很详细,但特么都是描述性的。也就是说CMake告诉你他能做什么,我有多屌,但是他不告诉你该怎么做,以及为什么要这么做。从而开发过程就变成了一个试错过程。 加这个link path解决问题了,慢着不对,为什么windows上又炸了;我该link那个python environment,那我hard code一个path吧。 若干个月后,你可能会恨自己做了这件事。。。 CMake当然有正确的paradigm,但往往它是&口口相传&,局限于某几个圈子里。 往往做完了几个大feature要求同事作code review的时候,我才能系统的从他们一大堆comments和change requests下学习正确的方式。 &/p&&p&&br&&/p&&p&第三,CMake的本身属性一定程度上限制了它的发展。 开源意味着有更大的用户群体和一种让世界更美好的愿景,但是也随之带来了funding和customer service的问题。CMake的发展进步有赖于社区的维护,但是随着开发人员的新老交替,无可避免的会慢慢产生冗余代码(参见FindBoost.cmake)。 我有问过同事们为什么我们不每隔1,2年更新Mastering CMake这本官方教程,同事的回答让我觉得很有道理:“我们也想啊! 但是问题是funding从哪里来? 虽然我们负责维护,开发和发布CMake新版本,但这不是我们的主业。 编写教程需要大量的时间,除非客户愿意资助我们,我们也爱莫能助。我们也要养家糊口啊。。。” &/p&&p&CMake的推动有赖于他的用户群体。 i.e. 客户A要我们帮他们开发一个新软件。 开发的过程中,我们需要拓展CMake去支持一系列新功能。我们说服客户这些新功能是必要的,并希望他们能支持把这些代码回馈给CMake master, 于是CMake得到了拓展。如果没有这些客户,以及其他community CMake的开发者们,也不会有CMake的今天。 使用过程自然而然会遇到问题,但是人们往往只能求助stackOverFlow 或者CMake mailing list. 由于没有人会赞助CMake的customer service所以一切都是out of courtesy。帮你是好心,没人有义务帮你。遇上一些暴脾气的在mailing list里骂人的,我们一般都当午休时候的笑料- -&/p&&p&&br&&/p&&p&最后,针对提到的一些问题,我要为CMake辩护下:&/p&&p&Modern CMake在target based的开发理念下,其实已经有了相当强的模块化能力。`include` keyword已经淡出了历史舞台,成为legacy。以下两篇blog我觉得针对这个理念写的不错:&/p&&p&&a href=&//link.zhihu.com/?target=https%3A//pabloariasal.github.io//its-time-to-do-cmake-right/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&It’s Time To Do CMake Right&/a&&/p&&p&&a href=&//link.zhihu.com/?target=https%3A//gist.github.com/mbinna/c61dbb39bca0e4fb7d1f73b0d66a4fd1& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&gist.github.com/mbinna/&/span&&span class=&invisible&&c61dbb39bca0e4fb7d1f73b0d66a4fd1&/span&&span class=&ellipsis&&&/span&&/a&&/p&&p&&br&&/p&&p&自动解决依赖问题早就不是什么问题了: Package developers应该生成package files给upstream users使用。如果你创建了一个library foo, 理论上别人应当只需要find_package(foo)便足以。&/p&&p&&a href=&//link.zhihu.com/?target=https%3A//cmake.org/cmake/help/git-master/manual/cmake-packages.7.html%23creating-packages& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&cmake-packages(7) - CMake 3.12.4bb Documentation&/a&&/p&&p&Windows上微软出了一个叫vcpkg的管理工具,据说挺好用的,可以了解下。&/p&&p&&它虽然是过程式语言,但函数TMD没有返回值!&好问题。。。我午休去问问同事&/p&&p&“作为一个string based language, 很少有利用CMake来处理functional programming的cases. 加上CMake诞生之初要兼容fortran, 所以这一功能也不了了之。 虽然community有几个人提过这个feature request,但是没有一个令人信服的使用场景 ”by Brad King。就这样。&/p&
首先必须肯定CMake。它简化了cross platform, OS, compiler的开发成本,同时支持多个IDE(VS, QtCreator, Eclipse, CLion...etc.),并且有一个成熟开源社区。 当你挣扎于将based on autotools的项目迁移至OSX或者Windows的时候,你就会感谢CMake的美好。 网络…
&p&发布与我的专栏:&/p&&a href=&https://zhuanlan.zhihu.com/p/& data-draft-node=&block& data-draft-type=&link-card& data-image=&https://pic3.zhimg.com/v2-e63cf56a670ef3baafee6de0x120.jpg& data-image-width=&1920& data-image-height=&1080& class=&internal&&miccall:当我们在说&渲染&的时候,我们在说什么? -- 从渲染到渲染方程,图形学的一路&/a&&p&假设我们在一个空的三维空间中,创建了三个点&/p&&figure&&img src=&https://pic3.zhimg.com/50/v2-c2acce7be8fc0fb01b62fea9_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&758& data-rawheight=&423& class=&origin_image zh-lightbox-thumb& width=&758& data-original=&https://pic3.zhimg.com/50/v2-c2acce7be8fc0fb01b62fea9_r.jpg&&&/figure&&p&我们知道,要确定一个面,至少得有三个点,而一个物体的构成,就是一个个的面,在图形学中,我们一般用三角面代替 &/p&&figure&&img src=&https://pic3.zhimg.com/50/v2-fb4a4f2dbde74cf915aaa7_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&736& data-rawheight=&376& class=&origin_image zh-lightbox-thumb& width=&736& data-original=&https://pic3.zhimg.com/50/v2-fb4a4f2dbde74cf915aaa7_r.jpg&&&/figure&&p&这是在我们假象的三维空间中,但是我们要在二维的显示器中看到他,我们就称之为把三角形从三维空间渲染到二维空间中 。 &/p&&figure&&img src=&https://pic1.zhimg.com/50/v2-020f5feb73f76f518cfaad9a0e23a272_b.jpg& data-caption=&& data-size=&normal& class=&content_image&&&/figure&&p&&br&&/p&&p&在现实世界中,三角形会将光线向各个方向散射。但是对于一台电脑,根本不可能去计算所有不同方向的光线&/p&&figure&&img src=&https://pic3.zhimg.com/50/v2-a46238e1afc856eb49d6310_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&662& data-rawheight=&301& class=&origin_image zh-lightbox-thumb& width=&662& data-original=&https://pic3.zhimg.com/50/v2-a46238e1afc856eb49d6310_r.jpg&&&/figure&&p&所以,我们计算机渲染图像,仅仅计算了那些散射到我们人眼方向得光线 , CG里面也叫摄像机方向 ,图形学中称之为 视角方向 。&/p&&p&&br&&/p&&p&所以让我们在 3D 空间中添加一个摄像头 ,并在前面透视点让我们放置一个屏幕网格,其中每个框是我们渲染图像的一个像素。 &/p&&figure&&img src=&https://pic2.zhimg.com/50/v2-c2cbad5ddabed8_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&707& data-rawheight=&380& class=&origin_image zh-lightbox-thumb& width=&707& data-original=&https://pic2.zhimg.com/50/v2-c2cbad5ddabed8_r.jpg&&&/figure&&p&现在,我们只画出与我们相机的透视点相交的一条光线。 如果这些光线相交于我们的屏幕,那么屏幕就能知道我们观察这个三角形的边界&/p&&figure&&img src=&https://pic4.zhimg.com/50/v2-2e1bc7e92bf9_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&738& data-rawheight=&421& class=&origin_image zh-lightbox-thumb& width=&738& data-original=&https://pic4.zhimg.com/50/v2-2e1bc7e92bf9_r.jpg&&&/figure&&p&我们将标出这个三角形的边框:&/p&&figure&&img src=&https://pic3.zhimg.com/50/v2-9eabd69bc4be95a3506a0_b.jpg& data-caption=&& data-size=&normal& class=&content_image&&&/figure&&p&我们知道边界之后,在边界与像素之间重叠得部分渲染像素,其余地方不做处理,我们就得到了这个三角形的图像:&/p&&figure&&img src=&https://pic1.zhimg.com/50/v2-e25fdc2a036e23c1aac16d_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&664& data-rawheight=&414& class=&origin_image zh-lightbox-thumb& width=&664& data-original=&https://pic1.zhimg.com/50/v2-e25fdc2a036e23c1aac16d_r.jpg&&&/figure&&p&这种投影到像素格子的方法就是是计算机图形学中的光栅化渲染方法。&/p&&p&&br&&/p&&p&这是七十年代的图形科学家所研究的内容,并且至今仍然是GPU渲染管线的一部分&/p&&p&&br&&/p&&p&除了光栅化,还有另一种方法 - ray casting :&/p&&p&让我们回到我们的3D对象空间,但这次我们将添加第二个三角形:&/p&&figure&&img src=&https://pic3.zhimg.com/50/v2-cfe8bdd5ab80f4_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&739& data-rawheight=&409& class=&origin_image zh-lightbox-thumb& width=&739& data-original=&https://pic3.zhimg.com/50/v2-cfe8bdd5ab80f4_r.jpg&&&/figure&&p&光栅化是以物体为中心的,这意味着我们从相机捕捉 物体所发射到相机的光线,而&/p&&p&Ray casting 以图像为中心 ,也就是说,如果我们只考虑那些实际有用的光线&/p&&p&(进入相机的光线) - 那为什么不把这些光线从相机发射到场景中去呢 ? &/p&&p&&br&&/p&&p&所以这一次,我们将从我们的虚拟相机开始,并通过相机向每一个像素都发射一条光线:&/p&&figure&&img src=&https://pic4.zhimg.com/50/v2-36acce3d_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&749& data-rawheight=&418& class=&origin_image zh-lightbox-thumb& width=&749& data-original=&https://pic4.zhimg.com/50/v2-36acce3d_r.jpg&&&/figure&&p&现在我们将判断每条射线是否射中了我们的每一个三角形 ,如果一个光线遇到多个物体 - 我们会取最近的那个点。&/p&&figure&&img src=&https://pic4.zhimg.com/50/v2-ef_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&769& data-rawheight=&381& class=&origin_image zh-lightbox-thumb& width=&769& data-original=&https://pic4.zhimg.com/50/v2-ef_r.jpg&&&/figure&&p&这个过程解决了困扰光栅化技术的一个重要问题,那就时可见性问题,如果是光栅化,那时候还不知道怎么解决远近遮挡问题 。但是后来,光栅化的解决方案是一种称为Z缓冲器的技术 , 它创建了一个深度图,然后根据该深度图检查了所有内容。但是Ray casting就不用这么麻烦。&/p&&figure&&img src=&https://pic3.zhimg.com/50/v2-d4a61c8d5474ade1f5b781_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&713& data-rawheight=&343& class=&origin_image zh-lightbox-thumb& width=&713& data-original=&https://pic3.zhimg.com/50/v2-d4a61c8d5474ade1f5b781_r.jpg&&&/figure&&p&但是他也有很大的弊端,就是计算量实在是太大了,因为他要判断相当多的射线与物体的三角面是否相交 ,假设我们有一个1000 x 1000 像素的图像,那么我们就要计算 1,000,000 条光线来检查是否和场景中一个多边形相交,对计算机来说,这计算也是相当的费劲,即使现在在算法上有了很大的改进,但仍然需要进行大量计算。 &/p&&p&&br&&/p&&p&由于这个原因,在20世纪70年代的大部分时间里,光线投射并没有被广泛的研究&/p&&p&但是光栅化有三个问题一直没有人能够很好地解决:那就是如何模拟真正的阴影,反射和折射。 &/p&&p&&br&&/p&&p&后来,解决办法还是回到Ray Casting ,并在这项旧技术上添加新的变化。&/p&&p&&br&&/p&&p&1980年,在贝尔实验室工作的一位名叫Turner Whitted 的工程师在 SIGGRAPH 发表了一篇论文,题为“An Improved illumination Model for Shaded Display”,一手解决了阴影,反射和折射问题。 &/p&&p&Whitted的技术称为递归的 射线追踪 Ray tracing
。&/p&&p&我们还是从相机的射线开始像之前一样。&/p&&figure&&img src=&https://pic3.zhimg.com/50/v2-cda16f0d498dac470f8d57_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&570& data-rawheight=&340& class=&origin_image zh-lightbox-thumb& width=&570& data-original=&https://pic3.zhimg.com/50/v2-cda16f0d498dac470f8d57_r.jpg&&&/figure&&p&这些被称为主光线。但是,当主光线接触一个表面,Whitted的方法是画出反射射线。 &/p&&figure&&img src=&https://pic1.zhimg.com/50/v2-44a8f6bd2a44bdf7a9a668d2d77529ca_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&592& data-rawheight=&294& class=&origin_image zh-lightbox-thumb& width=&592& data-original=&https://pic1.zhimg.com/50/v2-44a8f6bd2a44bdf7a9a668d2d77529ca_r.jpg&&&/figure&&p&为了解决阴影问题,我们通过在反射方向上绘制二次光线来绘制阴影光线,只要这个光线可以反射到光源,那么我们就知道是光源照亮的这个物体。&/p&&figure&&img src=&https://pic3.zhimg.com/50/v2-14dbd9ea57dc3fb7619e8cf_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&772& data-rawheight=&417& class=&origin_image zh-lightbox-thumb& width=&772& data-original=&https://pic3.zhimg.com/50/v2-14dbd9ea57dc3fb7619e8cf_r.jpg&&&/figure&&p&&br&&/p&&p&如果我们发现光与表面之间存在物体,这时,表面就处于阴影中 。 &/p&&figure&&img src=&https://pic2.zhimg.com/50/v2-9ebef564fca6_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&694& data-rawheight=&416& class=&origin_image zh-lightbox-thumb& width=&694& data-original=&https://pic2.zhimg.com/50/v2-9ebef564fca6_r.jpg&&&/figure&&p&当光线遇到表面反射,我们使用入射角绘制反射光线,以不断反射下去,看看这条反射光线在哪里。&/p&&figure&&img src=&https://pic4.zhimg.com/50/v2-8ce02fcfb0bef643b47f5fc_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&814& data-rawheight=&456& class=&origin_image zh-lightbox-thumb& width=&814& data-original=&https://pic4.zhimg.com/50/v2-8ce02fcfb0bef643b47f5fc_r.jpg&&&/figure&&p&如此不断的反射,所以被称之为 ,递归射线追踪。 &/p&&p&&br&&/p&&p&如果对象是透明的,则需要类似的过程。但是使用角就变成使用折射率来确定新的折射光线的角度:&/p&&figure&&img src=&https://pic1.zhimg.com/50/v2-762b585d2b5de1f6ac381e2_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&778& data-rawheight=&406& class=&origin_image zh-lightbox-thumb& width=&778& data-original=&https://pic1.zhimg.com/50/v2-762b585d2b5de1f6ac381e2_r.jpg&&&/figure&&p&&br&&/p&&p&最终光线追踪得到的效果就是 :&/p&&figure&&img src=&https://pic2.zhimg.com/50/v2-fd92ca9b9e794_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&814& data-rawheight=&453& class=&origin_image zh-lightbox-thumb& width=&814& data-original=&https://pic2.zhimg.com/50/v2-fd92ca9b9e794_r.jpg&&&/figure&&p&所以,正如你所看到的那样,让Ray Casting 更好的解决方案就是 绘制和分析 更多的光线。 &/p&&p&&br&&/p&&figure&&img src=&https://pic3.zhimg.com/50/v2-c267da3caa7c09ce01c1f8f_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&624& data-rawheight=&466& class=&origin_image zh-lightbox-thumb& width=&624& data-original=&https://pic3.zhimg.com/50/v2-c267da3caa7c09ce01c1f8f_r.jpg&&&/figure&&p&这是Turner Whitted 1980年在他的论文中第一张光线追踪图像之一 , 在这张图片中,明显的阴影,反射和折射效果。这512x512渲染图花了74 分钟。&/p&&p&&br&&/p&&p&不像光栅化,递归光线追踪 实际上是模拟真实光线在物体周围反射时的真实行为,但是对真实的表现,还是远远不够的,后来又有大量的人们开始进入该领域深入研究,并在计算机科学中,投入大量的时间尽力,开始研究光线追踪算法 ,以此来真正模拟光的规律。 &/p&&p&&br&&/p&&p&即使光线追踪产生非常逼真的阴影,反射和折射,像运动模糊和景深场这样的问题可以相对容易地解决,但是最多具有极复杂的光线模拟:&/p&&p&&br&&/p&&p&其中,最重要的应该是间接照明 , 相对直接照明是指光直接照射并被物体反射。但在真实世界的光线不仅来自光源&/p&&figure&&img src=&https://pic4.zhimg.com/50/v2-24f2e7aa3b20e568b2e38bdf9f5aceec_b.jpg& data-size=&normal& data-rawwidth=&1920& data-rawheight=&1078& class=&origin_image zh-lightbox-thumb& width=&1920& data-original=&https://pic4.zhimg.com/50/v2-24f2e7aa3b20e568b2e38bdf9f5aceec_r.jpg&&&figcaption&间接光被用于全局光照&/figcaption&&/figure&&figure&&img src=&https://pic1.zhimg.com/50/v2-cb96ece159b80cb3eec8df_b.jpg& data-size=&normal& data-rawwidth=&595& data-rawheight=&678& class=&origin_image zh-lightbox-thumb& width=&595& data-original=&https://pic1.zhimg.com/50/v2-cb96ece159b80cb3eec8df_r.jpg&&&figcaption&直接光与间接光&/figcaption&&/figure&&p&1986年,James Kajiya 发表了一篇名为“rendering equation”的论文。于是世界就不一样了。这个方程我们稍后再讲,&/p&&figure&&img src=&https://pic3.zhimg.com/50/v2-a4b83ac64f73ceba7b56d15318bdfb1f_b.jpg& data-size=&normal& data-rawwidth=&381& data-rawheight=&251& class=&content_image& width=&381&&&figcaption&James Kajiya&/figcaption&&/figure&&p&它基于Conservation of energy【能量守恒】和 麦克斯韦方程,来正确地模拟应该被感知的光线 。 &/p&&figure&&img src=&https://pic4.zhimg.com/50/v2-326e50dcaf3ad6c427f7_b.jpg& data-size=&normal& data-rawwidth=&914& data-rawheight=&240& class=&origin_image zh-lightbox-thumb& width=&914& data-original=&https://pic4.zhimg.com/50/v2-326e50dcaf3ad6c427f7_r.jpg&&&figcaption&辐射率大小对环境光的影响&/figcaption&&/figure&&p&同样,渲染方程最难的一点也是计算 。 &/p&&p&所以在发展过程中,游戏渲染采用了许多策略来尝试找到计算的捷径。&/p&&p&最早的是尝试从许多不同的方面呈现基本光线追踪图像的光能传递角度和求取他平均值。&/p&&p&后来 , 又出现了蒙特卡罗积分,这是基于概率的一种近似方式,可以通过求平均来求解积分,这个方法运用了大量的随机值 。来对光线进行采样和优化 。&/p&&p&&br&&/p&&p&后来运用到渲染方程已经是十年后了,皮克斯的第一部全长CGI电影:玩具总动员于1995年问世 : &/p&&figure&&img src=&https://pic2.zhimg.com/50/v2-b89bfacf17ae_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1920& data-rawheight=&1080& class=&origin_image zh-lightbox-thumb& width=&1920& data-original=&https://pic2.zhimg.com/50/v2-b89bfacf17ae_r.jpg&&&/figure&&p&此后的日子里,人们就像这样,仿佛找到了研究的方向,不断的寻求积分的最优解,有快解。&/p&&p&于是,不断的追求速度与精度,就成了图形学发展的最重要的方向 。 &/p&
发布与我的专栏:假设我们在一个空的三维空间中,创建了三个点我们知道,要确定一个面,至少得有三个点,而一个物体的构成,就是一个个的面,在图形学中,我们一般用三角…
如果你用过BSD,autoconf,就很容易组合出这几个目录的原始逻辑是什么了。&br&&br&autoconf很大程度上代表了传统的unix应用程序设计逻辑,autoconf有一个prefix的概念,指定这个工具(被编译的应用程序)的根在什么地方,基于这个根:&br&1. 一般可执行程序放在bin中&br&2. 系统可执行程序放在sbin中&br&3. 全局配置文件放在etc中&br&4. 库放在lib中&br&……&br&&br&当你编译这些程序的时候,一般是先:&br&&div class=&highlight&&&pre&&code class=&language-text&&./configure --prefix=/usr/local
&/code&&/pre&&/div&然后&br&&div class=&highlight&&&pre&&code class=&language-bash&&make
make install
&/code&&/pre&&/div&这样对应的文件就被安装到对应的目录中了。&br&&br&所以,/usr, /usr/local,都是prefix。&br&&br&然后我们看看这些prefix是怎么选择的。如果你编译过FreeBSD一类的系统,你会发现,这些系统的系统库,基础工具和内核是放到一套代码树中的,编译这个代码,内核和核心库,工具是一同完成的,这些都被认为是操作系统的一部分。这些核心文件,就以根目录作为prefix。所以,/是所有操作系统核心程序的prefix。&br&&br&在这个核心之外增加新的程序,构成一个发行版,这个发行版增加的程序就用/usr作为prefix。&br&&br&你把发行版安装好了,安装发行版之外应用程序,那些程序通常用/opt, /srv作为prefix。&br&&br&但如果你自己从源代码开始编译一个应用程序,这些程序是专门向你这个Site编译的,这种情况下,默认的prefix是/usr/local。&br&&br&但这些仅仅是个习惯,我们无法保证每个人都用一样习惯使用这些目录。Filesystem Hierarchy Standard尝试把这个定义为一个标准(CGL也有自己的标准,现在两者是否已经合并我就懒得查了),但现在来说,没有什么标准是被所有发行版都赞成的。当然,更约束不了你。&br&&br&我个人特别喜欢把自己编译的程序直接放在$HOME/local中。
如果你用过BSD,autoconf,就很容易组合出这几个目录的原始逻辑是什么了。 autoconf很大程度上代表了传统的unix应用程序设计逻辑,autoconf有一个prefix的概念,指定这个工具(被编译的应用程序)的根在什么地方,基于这个根: 1. 一般可执行程序放在bin中 …
写程序其实就跟写作文、遣词造句一样,会用成语会让你的文章显得老练,但是滥用成语就让人受不了了。程序也一样,重要的是要理解什么样的逻辑应当用什么样的语法来表达。其中关键的是让读代码的人是否容易理解,而不是执行起来是否正确。&br&&br&以下是一些个人的见解。总的来说原则是:&b&在代码中提供恰到好处的信息量,既不缺少,也不冗余&/b&&br&&br&&b&列表推导与循环&/b&&br&什么时候列表推导作为代码来说比较清晰?当读者能够清晰理解你这条语句是要生成一个列表,除此以外什么都没有做的时候。&br&比较:&br&&div class=&highlight&&&pre&&code class=&language-python&& &span class=&n&&new_list&/span& &span class=&o&&=&/span& &span class=&p&&[&/span&&span class=&n&&v&/span&&span class=&p&&[&/span&&span class=&mi&&1&/span&&span class=&p&&]&/span& &span class=&k&&for&/span& &span class=&n&&v&/span& &span class=&ow&&in&/span& &span class=&n&&old_list&/span& &span class=&k&&if&/span& &span class=&n&&v&/span&&span class=&p&&[&/span&&span class=&mi&&2&/span&&span class=&p&&]]&/span&
&/code&&/pre&&/div&和&br&&div class=&highlight&&&pre&&code class=&language-python&&&span class=&n&&new_list&/span& &span class=&o&&=&/span& &span class=&p&&[]&/span&
&span class=&k&&for&/span& &span class=&n&&v&/span& &span class=&ow&&in&/span& &span class=&n&&old_list&/span&&span class=&p&&:&/span&
&span class=&k&&if&/span& &span class=&n&&v&/span&&span class=&p&&[&/span&&span class=&mi&&2&/span&&span class=&p&&]:&/span&
&span class=&n&&new_list&/span&&span class=&o&&.&/span&&span class=&n&&append&/span&&span class=&p&&(&/span&&span class=&n&&v&/span&&span class=&p&&[&/span&&span class=&mi&&1&/span&&span class=&p&&])&/span&
&/code&&/pre&&/div&前者(对于熟练的程序员来说)明显更容易理解你的意图,即便不能一眼看清楚你所有的条件,他也能明白:这句语句只是将列表进行了一个格式的调整。&br&再比较&br&&div class=&highlight&&&pre&&code class=&language-python&&&span class=&n&&result&/span& &span class=&o&&=&/span& &span class=&p&&[&/span&&span class=&n&&v&/span& &span class=&k&&for&/span& &span class=&n&&v&/span& &span class=&ow&&in&/span& &span class=&nb&&iter&/span&&span class=&p&&(&/span&&span class=&bp&&self&/span&&span class=&o&&.&/span&&span class=&n&&read_socket&/span&&span class=&p&&,&/span& &span class=&s&&''&/span&&span class=&p&&)&/span& &span class=&k&&if&/span& &span class=&ow&&not&/span& &span class=&n&&v&/span&&span class=&o&&.&/span&&span class=&n&&startswith&/span&&span class=&p&&(&/span&&span class=&s&&'+'&/span&&span class=&p&&)]&/span&
&/code&&/pre&&/div&和&br&&div class=&highlight&&&pre&&code class=&language-python&&&span class=&n&&result&/span& &span class=&o&&=&/span& &span class=&p&&[]&/span&
&span class=&k&&while&/span& &span class=&bp&&True&/span&&span class=&p&&:&/span&
&span class=&n&&v&/span& &span class=&o&&=&/span& &span class=&bp&&self&/span&&span class=&o&&.&/span&&span class=&n&&read_socket&/span&&span class=&p&&()&/span&
&span class=&k&&if&/span& &span class=&n&&v&/span& &span class=&o&&==&/span& &span class=&s&&''&/span&&span class=&p&&:&/span&
&span class=&k&&break&/span&
&span class=&k&&if&/span& &span class=&ow&&not&/span& &span class=&n&&v&/span&&span class=&o&&.&/span&&span class=&n&&startswith&/span&&span class=&p&&(&/span&&span class=&s&&'+'&/span&&span class=&p&&):&/span&
&span class=&n&&result&/span&&span class=&o&&.&/span&&span class=&n&&append&/span&&span class=&p&&(&/span&&span class=&n&&v&/span&&span class=&p&&)&/span&
&/code&&/pre&&/div&前者明显就很糟糕,read_socket可能是个非常复杂、有明显副作用的过程,读代码的人不会期待你在这么短的语句当中完成个非常重要的逻辑。read_socket可能在里面抛出异常,你将没有机会给它加上try...except来进行处理,你也不会喜欢抛出的异常的堆栈信息里面有一堆&lambda& &genexpr&这样的意义不明的东西,而不是一个明确的行号。而且,很明显这是这段代码最重要的逻辑,这意味着这个逻辑以后可能会变更、会重构,你不会想要把一个列表推导变更得越来越复杂直到被迫推倒重来。&br&另外,用到map和filter的都要尽量重写成列表推导。&br&&br&&b&lambda表达式与具名函数&/b&&br&Python是支持函数嵌套定义的,在已有的函数中可以嵌套定义新的函数:&br&&div class=&highlight&&&pre&&code class=&language-python&&&span class=&k&&def&/span& &span class=&nf&&my_func&/span&&span class=&p&&():&/span&
&span class=&k&&def&/span& &span class=&nf&&subfunc&/span&&span class=&p&&():&/span&
&span class=&o&&...&/span&
&span class=&n&&subfunc&/span&&span class=&p&&()&/span&
&span class=&o&&...&/span&
&/code&&/pre&&/div&嵌套的具名函数可以完全替代lambda表达式,而且有许多优点:&br&&ol&&li&一个函数名可以迅速告诉读代码的人这个函数在做什么&br&&/li&&li&抛出异常的时候,有函数名称明显要比显示为&lambda&强&/li&&li&可以添加比较复杂的逻辑&/li&&li&可以使用decorator&/li&&li&具名函数可以用yield(也就是说可以定义嵌套的具名的generator,但不能定义lambda的generator)&br&&/li&&li&需要作为返回值的一部分的时候,在repr表达式当中能显示函数名,方便调试&/li&&/ol&&p&一般来说lambda表达式的使用一定要严格限定为(与关系):&/p&&ol&&li&非常简单的逻辑,尤其最好不要在lambda当中再嵌套列表推演或者生成器表达式或者其他lambda表达式,非常不清晰&/li&&li&没有副作用,或者只包装一个有副作用的表达式&/li&&li&一次性使用(绝对不要用f = lambda x: ...这样的语句,虽然只有一行,但读代码的时候会很难找到f的定义)&/li&&li&单返回值——使用tuple或者list的多返回值会让括号嵌套变得复杂难读懂。&br&&/li&&/ol&&p&例如:&/p&&div class=&highlight&&&pre&&code class=&language-python&&&span class=&k&&return&/span& &span class=&k&&lambda&/span& &span class=&n&&x&/span&&span class=&p&&:&/span& &span class=&k&&lambda&/span& &span class=&n&&y&/span&&span class=&p&&:&/span& &span class=&n&&x&/span& &span class=&o&&+&/span& &span class=&n&&y&/span&
&/code&&/pre&&/div&&p&和&/p&&div class=&highlight&&&pre&&code class=&language-python&&&span class=&k&&def&/span& &span class=&nf&&add_closure&/span&&span class=&p&&(&/span&&span class=&n&&x&/span&&span class=&p&&):&/span&
&span class=&k&&def&/span& &span class=&nf&&add_func&/span&&span class=&p&&(&/span&&span class=&n&&y&/span&&span class=&p&&):&/span&
&span class=&k&&return&/span& &span class=&n&&x&/span& &span class=&o&&+&/span& &span class=&n&&y&/span&
&span class=&k&&return&/span& &span class=&n&&add_func&/span&
&span class=&k&&return&/span& &span class=&n&&add_closure&/span&
&/code&&/pre&&/div&&p&同样是嵌套的闭包,明显后一种写法要清晰得多,以后调试起来也要容易。&/p&&p&可以在列表推导当中嵌套lambda,但最好不要在lambda当中嵌套列表推导。在列表推导中使用lambda的时候,首先确定这个逻辑是必要的;其次,给这个列表起一个非常明确的变量名,说明这个列表中的函数起什么作用;第三,给lambda表达式加上括号,让人能比较清楚地看到lambda表达式的开始和结束;最后,一定要警惕闭包封闭循环变量的方式,非常容易出意料之外的bug。&/p&&div class=&highlight&&&pre&&code class=&language-text&&multipliers = [(lambda x, i = i: x * i) for i in range(0, 20)]
&/code&&/pre&&/div&&br&&p&&b&修饰符/注解(decorator)&/b&&/p&&p&修饰符是让代码变得优雅易读的非常重要的工具,正确运用能有非常多的好处。但也一定要注意:&br&&/p&&br&&p&1. decorator中只做定义和初始化的工作,不要用decorator来执行某个操作。或者说,decorator不要有除了定义以外的副作用&/p&&p&例如,严格杜绝下面的用法:&/p&&div class=&highlight&&&pre&&code class=&language-text&&def execute_once(f):
@execute_once
def my_func(param):
&/code&&/pre&&/div&&p&没有人会从代码中判断这个函数会在import的时候自动执行。而且,没有人会懂为什么my_func的值是None。&br&&/p&&br&&p&2. 用decorator修饰一个函数得到另一个函数的时候,原函数的逻辑仍然是新函数的中心,而decorator增加的是相对无关紧要的或者外围的功能;尤其不要改变原函数的执行逻辑。&br&&/p&&p&严格杜绝下面的例子:&/p&&div class=&highlight&&&pre&&code class=&language-python&&&span class=&k&&def&/span& &span class=&nf&&revert&/span&&span class=&p&&(&/span&&span class=&n&&f&/span&&span class=&p&&):&/span&
&span class=&nd&&@wraps&/span&&span class=&p&&(&/span&&span class=&n&&f&/span&&span class=&p&&)&/span&
&span class=&k&&def&/span& &span class=&nf&&newf&/span&&span class=&p&&(&/span&&span class=&o&&*&/span&&span class=&n&&args&/span&&span class=&p&&,&/span& &span class=&o&&**&/span&&span class=&n&&kwargs&/span&&span class=&p&&):&/span&
&span class=&k&&return&/span& &span class=&ow&&not&/span& &span class=&n&&f&/span&&span class=&p&&(&/span&&span class=&o&&*&/span&&span class=&n&&args&/span&&span class=&p&&,&/span& &span class=&o&&**&/span&&span class=&n&&kwargs&/span&&span class=&p&&)&/span&
&span class=&c&&# No, please DON'T, really&/span&
&span class=&k&&return&/span& &span class=&n&&newf&/span&
&span class=&nd&&@revert&/span&
&span class=&k&&def&/span& &span class=&nf&&is_ok&/span&&span class=&p&&(&/span&&span class=&n&&my_str&/span&&span class=&p&&):&/span&
&span class=&k&&return&/span& &span class=&n&&my_str&/span& &span class=&o&&==&/span& &span class=&s&&'OK'&/span&
&/code&&/pre&&/div&3. 即使去掉修饰符,整个函数的逻辑仍然是完整、清晰、可读的。&br&严格杜绝下面的例子:&br&&div class=&highlight&&&pre&&code class=&language-python&&&span class=&k&&def&/span& &span class=&nf&&repeat&/span&&span class=&p&&(&/span&&span class=&n&&f&/span&&span class=&p&&):&/span&
&span class=&nd&&@wraps&/span&&span class=&p&&(&/span&&span class=&n&&f&/span&&span class=&p&&)&/span&
&span class=&k&&def&/span& &span class=&nf&&newf&/span&&span class=&p&&(&/span&&span class=&o&&*&/span&&span class=&n&&args&/span&&span class=&p&&,&/span& &span class=&o&&**&/span&&span class=&n&&kwargs&/span&&span class=&p&&):&/span&
&span class=&n&&data&/span&&span class=&p&&,&/span& &span class=&n&&num&/span& &span class=&o&&=&/span& &span class=&n&&f&/span&&span class=&p&&(&/span&&span class=&o&&*&/span&&span class=&n&&args&/span&&span class=&p&&,&/span& &span class=&o&&**&/span&&span class=&n&&kwargs&/span&&span class=&p&&)&/span&
&span class=&k&&return&/span& &span class=&p&&[&/span&&span class=&n&&data&/span&&span class=&p&&]&/span& &span class=&o&&*&/span& &span class=&n&&num&/span&
&span class=&k&&return&/span& &span class=&n&&newf&/span&
&span class=&nd&&@repeat&/span&
&span class=&k&&def&/span& &span class=&nf&&zeros&/span&&span class=&p&&(&/span&&span class=&n&&n&/span&&span class=&p&&):&/span&
&span class=&k&&return&/span& &span class=&p&&(&/span&&span class=&mi&&0&/span&&span class=&p&&,&/span&&span class=&n&&n&/span&&span class=&p&&)&/span&
&span class=&c&&# What is it???&/span&
&/code&&/pre&&/div&&br&&b&with语句&/b&&br&with比起try...finally...来说,更强调“作用域”的概念。with接口一般有两种,一种是整个对象作为with表达式,一种是某个方法的返回值作为with表达式。应当遵循以下的规则:&br&&ol&&li&with的__enter__和__exit__过程中不应当执行过于复杂的操作。尤其应当避免在__exit__中抛出异常——在__exit__中抛出新的异常可能会覆盖旧的异常。&/li&&li&with过程应当有明确的语义,一般来说代表某种生命周期(比如file,在with退出的时候会自动close),或者某种互斥过程(比如锁);如果要表达其他含义,最好用一个方法来返回一个with对象,方便读代码&/li&&li&对于可能重入的过程(即:每次with是独立的,可以在多线程中同时进行多个),应当使用函数返回with对象的形式,这样实现起来不容易出bug&/li&&li&能运用contextlib的情况下运用contextlib而不是手工实现__enter__和__exit__(不过这意味着要实现成函数返回with对象)&/li&&li&with应当事先比较次要的逻辑。即使去掉with,with内的逻辑看上去仍然比较清晰、完整。&/li&&li&能返回一个有意义的对象的时候,要返回一个有意义的对象,方便使用with ... as ...的语法&br&&/li&&/ol&&p&比如,比起&/p&&div class=&highlight&&&pre&&code class=&language-python&&&span class=&n&&client&/span& &span class=&o&&=&/span& &span class=&n&&MyClient&/span&&span class=&p&&()&/span&
&span class=&k&&with&/span& &span class=&n&&client&/span&&span class=&p&&:&/span&
&span class=&n&&client&/span&&span class=&o&&.&/span&&span class=&n&&send&/span&&span class=&p&&(&/span&&span class=&o&&...&/span&&span class=&p&&)&/span&
&/code&&/pre&&/div&&p&应当用&/p&&div class=&highlight&&&pre&&code class=&language-python&&&span class=&n&&client&/span& &span class=&o&&=&/span& &span class=&n&&MyClient&/span&&span class=&p&&()&/span&
&span class=&k&&with&/span& &span class=&n&&client&/span&&span class=&o&&.&/span&&span class=&n&&transact&/span&&span class=&p&&()&/span& &span class=&k&&as&/span& &span class=&n&&trans_obj&/span&&span class=&p&&:&/span&
&span class=&n&&client&/span&&span class=&o&&.&/span&&span class=&n&&send&/span&&span class=&p&&(&/span&&span class=&o&&...&/span&&span class=&p&&)&/span&
&span class=&bp&&self&/span&&span class=&o&&.&/span&&span class=&n&&_logger&/span&&span class=&o&&.&/span&&span class=&n&&info&/span&&span class=&p&&(&/span&&span class=&s&&'Transact &/span&&span class=&si&&%r&/span&&span class=&s&& succeeded.'&/span&&span class=&p&&,&/span& &span class=&n&&trans_obj&/span&&span class=&o&&.&/span&&span class=&n&&id&/span&&span class=&p&&)&/span&
&/code&&/pre&&/div&&br&&p&&b&__getattr__, __getattribute__, property(descriptor), __setattr__,__delattr__&br&&/b&&/p&&p&Python可以以很多种方式重新定义对象属性的特性。应当注意:&/p&&ol&&li&实施之前请再次思考是否真的必要。尤其是__setattr__和__getattribute__,它可能严重拖慢性能,因为每一个属性读写都会调用。&/li&&li&优先使用property&br&&/li&&li&get过程不要修改对象的状态——增加cache之类的是可以的。&/li&&li&考虑实现__dir__来让对象可以正确列出属性。&/li&&li&如果重写了__getattr__和__setattr__,请至少考虑让下划线开头的属性遵循原始的规则,对于许多实现来说会提供一些便利。&/li&&/ol&&br&&p&&b&metaclass&/b&&/p&&ol&&li&如果你是新手,尤其如果你不知道什么是metaclass,不要去强行用它。能使用其他方案比如decorator代替的要用decorator&/li&&li&不要修改基类和类名&/li&&li&总是从type派生出metaclass,你不会希望isinstance(myclass, type)返回False&/li&&li&有清晰的文档说明这个metaclass的特性&/li&&/ol&&br&&p&大概就这些吧。&/p&
写程序其实就跟写作文、遣词造句一样,会用成语会让你的文章显得老练,但是滥用成语就让人受不了了。程序也一样,重要的是要理解什么样的逻辑应当用什么样的语法来表达。其中关键的是让读代码的人是否容易理解,而不是执行起来是否正确。 以下是一些个人的…
发一个老段子:马屁股决定航天飞机火箭助推器的宽度&br&&br&现代铁路两条铁轨之间的标准距离是四英尺又八点五英寸。原来,早期的铁路是由建电车的人所设计的,而四英尺又八点五英寸正是电车所用的轮距标准。&br&&br&那么,电车的标准又是从哪里来的呢?&br&&br&最先造电车的人以前是造马车的,所以电车的标准是沿用马车的轮距标准。&br&&br&马车又为什么要用这个轮距标准呢?英国马路辙迹的宽度是四英尺又八点五英寸,所以,如果马车用其他轮距,它的轮子很快会在英国的老路上撞坏。这些辙迹又是从何而来的呢?从古罗马人那里来的。因为 包括英国的长途老路都是由罗马人为它的军队所铺设的,而四英尺又八点五英寸正是罗马战车的 任何其他轮宽的战车在这些路上行驶的话,轮子的寿命都不会很长。&br&&br&可以再问,罗马人为什么以四英尺又八点五英寸为战车的轮距宽度呢?&br&&br&原因很简单,这是牵引一辆战车的两匹马屁股的宽度。&br&&br&故事到此还没有结束。美国航天飞机燃料箱的两旁有两个火箭推进器,因为这些推进器造好之后要用 路上又要通过一些隧道,而这些隧道的宽度只比火车轨道宽一点,因此火箭助推器的宽度是由铁轨的宽度所决定的。&br&&br&&p&所以,最后的结论是:“路径依赖”导致了美国航天飞机火箭助推器的宽度,而这个宽度竟然是两千年前由两匹马屁股的宽度决定的。&/p&&br&&p&一旦人们做了某种判断,就好比走上了一条不归之路,惯性的力量会使这一选择不断自我强化,并让你轻易走不出去,这种现象就被称为“路径依赖”。&/p&&br&------------------------------------------------------------------------------------------------&br&对于各种高速串行信号来讲,也有同样的“路径依赖”,源头是从哪里开始的,这个说不清楚了,我个人认为一个源头就是数据传输。&br&&br&上世纪七八十年代就出来了各种数据传输的协议,比如T1/E1载波系统(2.048Mbps)、X.25中继系统、ISDN(综合业务数字网)等,那时的速度还比较慢的,到了九十年代,SDH(Synchronous Digital Hierarchy,同步数字体系)和SONET(Synchronous Optical Network同步光纤网)标准出现,其基本速度就是STM-1 155.520Mbps,STM-4为622.080Mbps,STM-16为Mbps,到更后来WDM(Wavelength Division Multiplexing, 波分复用)技术,再到最新的OTN(OpticalTransportNetwork,光传送网),这里面最重要的个概念就是TDM(Time Division Multiplexing, 时分复用)。&br&&br&&b&时分多路复用&/b&(&b&Time-Division Multiplexing,TDM&/b&)是一种数字的或者模拟(较罕见)的多路复用技术。使用这种技术,两个以上的&a href=&//link.zhihu.com/?target=https%3A//zh.wikipedia.org/wiki/%25E4%25BF%25A1%25E5%258F%25B7& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&信号&/a&或数据流可以同时在一条通信线路上传输,其表现为同一通信信道的子信道。但在&a href=&//link.zhihu.com/?target=https%3A//zh.wikipedia.org/wiki/%25E7%%25E7%& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&物理&/a&上来看,信号还是轮流占用物理通道的。时间域被分成&a href=&//link.zhihu.com/?target=https%3A//zh.wikipedia.org/wiki/%25E5%%25E6%259C%259F& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&周期&/a&循环的一些小段,每段时间长度是固定的,每个时段用来传输一个子信道。例如子信道1的采样,可能是&a href=&//link.zhihu.com/?target=https%3A//zh.wikipedia.org/wiki/%25E5%25AD%%258A%2582& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&字节&/a&或者是&a href=&//link.zhihu.com/?target=https%3A//zh.wikipedia.org/wiki/%25E6%%25E6%258D%25AE%25E5%259D%2597& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&数据块&/a&,使用时间段1,子信道2使用时间段2,等等。一个TDM的帧包含了一个子信道的一个时间段,当最后一个子信道传输完毕,这样的过程将会再重复来传输新的帧,也就是下个信号片段。来源维基百科:&a href=&//link.zhihu.com/?target=https%3A//zh.wikipedia.org/wiki/%25E6%%25E5%E5%25A4%259A%25E8%25B7%25AF%25E5%25A4%258D%25E7%& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&zh.wikipedia.org/wiki/%&/span&&span class=&invisible&&E6%97%B6%E5%88%86%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8&/span&&span class=&ellipsis&&&/span&&/a&&br&&br&数字传输就像打包裹,最基本单元是一个小包裹,四个小包裹打成一个中的,再四个中的打成一个大的,再四个大的打成一个更大,然后再特大的。比如SONET的传输速度就是STM-1/-4/-16等这样叠加上去,以2的指数倍往上翻。其中TDM-16速度为MBps,就是我们通常说的2.5Gbps。&br&&br&上面说了堆协议,那总要具体的物理实现,一般选用铜线或光缆进行远距离传输。以光缆为例,数据先由电路中的并行数据变成串行传送出去,然后再经过光纤接口,变成光信号在光纤里传输,接收时先由光信号变成电信号,再由串行变成并行到内部使用。其中由并行到串行/串行到并行经过的就称为SERDES PHY,高速SERDES的技术实现难度较高,得由模拟电路实现,在很多场合就是一块单独的SERDES PHY芯片,那就有专门的公司来做这个事情,比如在业界大名鼎鼎的TI德州仪器,其TI芯片就卖得很好。逐渐实现这样的产业链:做数字电路的、模拟电路的、测试设备的、生产制造的(包括PCB和SERDES PHY、光口、光纤等),已经定了个基本速率后,再往上的更新换代往往是X2地叠加,在数字电路上最好实现,在模拟电路上也有这样的动力,整个技术就一直这样往前走下去。&br&&br&回到标题高速串行接口由什么决定的来,PCI总线由Intel公司于91年提出,之后移交给第三方机构PCI SIG。PCI SIG由多家业内公司组成的联盟,别的公司也可以申请加入成为会员,TI也是早期会员之一。就像联合国一样,Intel等公司像常任理事国一样拥有更大的主导权;USB于94年由带头大哥Intel联合微软、HP、NEC等电脑公司组成USB-IF组织,96年推出USB 1.0标准;(同期还有Apple推出的FireWire火线,也红火了好多年)由此可见,Intel对PCI/PCIE和USB的建立和发展一直拥有极大的主导权。&br&&br&2001年PCIE开始制定,决定以串行方式代替并行的PCI总线时,那时产业内2.5G PHY已经比较成熟了,PCI组织PCI-SIG决定直接借鉴此速度就很正常;等到PCIE 2.0发布已经是过2007年,就直接X2变成5G了; USB 3.0于2008年发布,直接借鉴业界比较成熟的5G方案也就很正常了; 而PCIE 3.0发布是2010年时(为什么PCIE 3.0是8G而不是10G,这算是个折衷吧,速度越快对PCB走线设计和生产、线缆、测试仪器等要求越高,3.0采用64b/66b或128b/130b编码方案,8G*64/66=7.88G,解码后的速度几乎就是2.0的二倍,2.0采用传统的8b/10b编码,解码后速度5G*8/10=4G)。&br&&br&等到USB3.1发布,也就是最近的事情(2014年),觉得10G PHY也比较成熟了,那也直接采用10G吧,USB 3.1采用128b/132b编码,效率与PCIE 3.0是等效的,它直接向PCIE借鉴了很多内容。&br&&br&而ThunderBolt,定位在更高速速度传输,其1.0速度最开始设计时就是一 路10G PHY(大约2011年),而后2.0就成两路10G PHY了,最近的3.0成两路20G PHY,为什么不直接成40G PHY,工艺做不上去啊。&br&&br&很早前,业界有个传说,铜界质PCB走线最高速度只能到16G,几年前就已经打破了,28G甚至32G以上跑铜界质的高速PHY已经有DEMO演示了,ThunderBolt 2.0推出两路10G PHY,自然也是业界有这样能力去推出成熟产品。&br&&br&不出意外的是,ThunderBolt定位在高端,从最先推出1.0接口的MAC电脑(2011年),到现在已经四年过去了,相对来说还很不普及,只在高端电脑上才有配备,其外设产品,比如支持该接口的外接存储和高清显示器见到过报道,但市场上卖得真不太多,比起这几年一下子普及开来的USB 3.0还是相差不少。与此类似待遇的是DisplayPort接口,显示器接口从最早的VGA到DVI,到同时支持声音图像传输的HDMI、DisplayPort接口,HDMI逐渐变得常见,尤其是电视接口上,而DisplayPort仍然不太多见。而ThunderBolt在外观上与Mini DP接口兼容,在功能上可认为是图像传输接口DP和数据传输协议PCIE的合体。&br&&br&这不,Intel一琢磨,那ThunderBolt 3.0改成USB3.1-C接口兼容吧,这样支持ThunderBolt 3.0的外设既可以连接对应的ThunderBolt 3.0 host,享受40G的高速,也可以接在USB3.1-C上,尽管只能跑USB 3.0 5G速率(注意,资料显示所兼容的控制器是USB 3.0,而不是最新的3.1; 也有人指出Intel推出的控制器是支持10G速度的。anyway,PHY通道是支持的,这主要取决于控制器部分),但是更常见啊,这样对于外设厂商也是一大利好,用户也可以放心地买啦,不用担心接口不支持啦。&br&&br&最后做个总结:高速串行接口速度由什么决定?当时协议公布时前代技术的积累与影响和已成熟技术,二者占重要因素。比如2.5G速率和STM-1 155M的关系,比如不同年代PHY技术的成熟度,再者还有业界领先公司在制定标准时的号召力及技术前瞻性,如Intel在多种协议上的主导力。&br&&br&这里顺便吐槽下动不动国产OS、国产某技术、国产CPU……试图与业界流行的产品和技术对抗,想另起炉灶,而其本质往往只是在人家已有技术的基础上借鉴人家的思路,抄袭或模仿成类似的产品,尤其是ZF在其中扮演主导角色,这种努力往往失败,也许在某些不开放场合有一席之地,只要是开放市场,几乎无竞争力,不提其它,一个路径依赖就把你拍死了。那些号称的自主、国产,开发过程和产品里仍然有大量开放世界里的工具、成熟产品、思维、架构体系,那做出来的东西只是在某一个细节上对已有产品形成了可替代力,但其上游、下游还是与业界紧密联系在一起,这种情况下很难有所发展。&br&&br&-------------------------------------------------&br&前不久还写了个回答,也一起贴大这里:&br&&a class=&internal& href=&http://www.zhihu.com/question//answer/&&如何看待 Thunderbolt 3 改用 USB-C 接口?&/a&&br&手头正用的是Macbook Pro 2012年版,用的视频输出接口是Thunderbolt 1,接口与Mini DisplayPort接口兼容,现用HDMI接口外接显示器,要使用个转接器。&br&&br&为什么Thunderbolt 3要改成USB3.1-C兼容接口?因为它是Intel啊,这两家标准都基本以Intel为主导啊,PHY team的人碰碰头,小会议室一坐,说咱们做成一样的吧,就成啦!&br&&br&从商业角度来讲,统一标准有多少好处,Thunderbolt现在流行范围还不广泛且由Intel主导,阻力比较小等考虑就不多说啦,谈点技术上的实现。&br&&br&为什么插上去可以实现兼容?因为它们都属于高速串行传输协议,在技术上有很多共用的地方,且在速率上不同的代本身是匹配的(Thunderbolt 1的速度就是10Gbps,与USB3.1一致),再者不同的高速串行协议往往是上层协议不同,在底层实现上是通用的,也就是控制器不同,而PHY可以通用。&br&&br&我在S公司做过较长时间的PCIE原型平台验证,主要是数字电路部分的控制器代码烧录到FPGA中,已经流片成功被做过基础电学测试的PHY测试芯片做成单独的子卡扣在原型平台上,再与PC主板连接进行测试。最高支持10Gbps的PHY子卡不仅仅用于PCIE GEN3 8Gbps,同时还支持SATA GEN3 6Gbps和USB3 5Gbps(是否还支持USB3.1 10Gbps还要确认下,按理说支持),子卡上留有多种接口,PCIE金手指、SATA接口、USB接口等,哪个项目要测试了,直接从一个平台拔下来,换上接口放另一个平台就可。这个过程中,FPGA内的控制器代码是变化的,同时上电时会对PHY子卡配置一组向量去支持不同的模式。&br&&br&并且这些协议不同的代数间往往速率是成倍翻的,不仅是PHY的速率成倍翻,而且控制器内部的运行时钟或数据位宽也是成倍翻。比如PCIE GEN1 2.5G, GEN2 5G, GEN3 8G(编码方式从之前的8b/10b变成64b/66b或128b/130b,实现有效负载是翻倍的),而数字控制器内部有两种方法:如果数据位宽固定都是32bit,那GEN1时core clock的速率为62.5MHZ,GEN2时为125MHZ,GEN3时为250MHZ;如果是clock速率保持为125MHZ不变,则GEN1时数据位宽为16bit,GEN2时为32bit,GEN3时为64bit。GEN3的芯片同时支持在GEN1和GEN2模式下运行,并且最开始初始化时即从GEN1开始,然后硬件双方进行握手,看一起能支持的最大速率是多少,然后再调节到最大速率;如果在高速率下出现不稳定、自检错误过多,还可以自动回退到低速率上安全运行。这样的设计方式是非常合理的&br&&br&同样,对于Thunderbolt来说,GEN1是10Gbps X1,GEN2是10Gbps X2, GEN3是20Gbps X2,它本身是可以运行在GEN1 10Gbps,接在USB3.1-C接口上时,是以USB 3.0的控制器协议运行的,也就是5G的速度,并不是USB 3.1 10G,这点是最近几天看到资料才知道的。这么做应该是出于设计成本和稳定性的考虑吧,毕竟USB 3.1协议出来没多久,设计公司还没有成熟稳定的代码,那ThunderBolt 3.1就要使用USB 3.1的协议的话,设计ThunderBolt 3.1控制器部分时就比较困难了,还不如直接采用成熟的USB 3.0。这也从一个角度说明了,协议的设计是比较接地气的,要考虑多方面的因素,而不是一昧地求新求快。&br&&br&也有网友留言说现在Intel推出的控制器支持10G。anyway,PHY通道是支持的,这主要取决于控制器部分。&br&------------------------------------------------------------------------&br&Thunderbolt可以看成是PCIE GEN3与DisplayPort视频接口的合体,Thunderbolt控制器一端接PCIE数据流和DisplayPort数据流,一端连接PHY接口。控制器对进入的数据经过少量的处理,仍基本保持原来的数据格式形式,比如其中的PCIE 3.0数据仍为64b/66b或128b/130b编码形式,而DisplayPort保持8b/10b编码形式。&br&&br&当同时连接PCIE设备和DisplayPort设备时,DisplayPort通道拥有更高的优先权,如下图所示,Data Only时,下行和上行数据速度最大为22G 当接一个5K高清设备时,下行数据速度减小为18Gbps,上行保持不变; 当接两个4K高清设备时,下行数据速度减小为8Gbps,上行保持不变。&br&&br&&figure&&img src=&https://pic4.zhimg.com/50/3d57a5b5e95c7f5c1ad64c27d81b3ba2_b.jpg& data-rawwidth=&1180& data-rawheight=&533& class=&origin_image zh-lightbox-thumb& width=&1180& data-original=&https://pic4.zhimg.com/50/3d57a5b5e95c7f5c1ad64c27d81b3ba2_r.jpg&&&/figure&&br&&br&从上可见,Thunderbolt 3.0单通道的速率就是20Gbps,这个对物理通道的要求是相当高的,通常铜介质做到16Gbps就很困难了(目前还有做到28G甚至30G以上的铜介质传输的报道),想要完美地运行如此高的速率,官方认证的原装线缆必不可少,价钱也很昂贵罗。&br&&br&觉得写得不错大家点个赞,说错的地方请轻砸啊。
发一个老段子:马屁股决定航天飞机火箭助推器的宽度 现代铁路两条铁轨之间的标准距离是四英尺又八点五英寸。原来,早期的铁路是由建电车的人所设计的,而四英尺又八点五英寸正是电车所用的轮距标准。 那么,电车的标准又是从哪里来的呢? 最先造电车的人以…
&figure&&img src=&https://pic1.zhimg.com/v2-44f1c8cdad30f2d98ce5_b.jpg& data-rawwidth=&1014& data-rawheight=&680& class=&origin_image zh-lightbox-thumb& width=&1014& data-original=&https://pic1.zhimg.com/v2-44f1c8cdad30f2d98ce5_r.jpg&&&/figure&&blockquote&【新智元导读】 不久前, 上海交通大学的两位研究者发布了一项题为“利用脸部照片自动推断犯罪性”的研究,利用基于有监督的机器学习的方法,根据人的脸部特征预测一个人是否有犯罪倾向,“准确率接近90%”。该研究在国内外引起了广泛的争议。近日,谷歌的几名研究员撰文对这一研究进行了批驳,回顾了机器学习技术的底层运作方式和技术细节,并探讨机器学习等先进技术在融入现实中所遇到的难题和挑战。&/blockquote&&br&&h2&&b&任何关心如何确保 AI 技术朝着有利于人类发展的人都是本文的读者&/b&&/h2&1844 年,意大利南部一个小城镇举办了一场审判会,一个名叫 Giuseppe Villella 的劳工因涉嫌窃取了“5 个里考塔(注释:意大利奶制品,类似凝乳),一块硬奶酪,两块面包……和两只小山羊”,最终被判定为“brigante”(暴匪)。当时,意大利南部正因盗匪和国家暴动陷入恐慌。Villella 于 1864 年在意大利北部帕维亚的监狱中死亡。&br&&br&Villella 的死亡促使了现代犯罪学的诞生。当时镇里居住的一位名叫 Cesare Lombroso 的科学家和外科医生,他认为“brigante”是一种原始的人,天生容易犯罪。检查 Villella 的遗体后,Lombroso 发现了所谓的“证据”,证实了他的猜想:Villella 头骨枕头上的凹陷让人联想到“野人和猿猴”的头骨特征。&br&&br&使用精确的测量仪器,Lombroso 记录下了他在 Villella 遗体上发现的更多显示其有精神错乱(derangement)的物理特征,包括“不对称的脸”。Lombroso 写道,犯罪分子“生下来就是罪犯”。他认为犯罪行为是会遗传的,并且在遗传时会带有伴随的物理特征,可以用卡钳和颅骨等仪器来测量[1]。这个想法很自然地证明了他之前的假设,即意大利南部人种相比北意大利人要落后原始许多。&br&&br&使用人的外观推断其内在特征的做法被称为相面(physiognomy)。虽然在今天相面被认为是伪科学,但在民间一直流传着,可以从某个人的面部和身体特征识别出较差的“类型”的人,这一观点也在不同时期被编入国家法律,为很多行为提供了基础,比如购买土地、禁止移民、证明奴隶制合理,以及将种族灭绝正当化。在实践中,相面的伪科学成为科学种族主义(scientific racism)的伪科学。&br&&br&人工智能和机器学习的快速发展使科学种族主义进入了一个新的时代。其中,人类行为中存在的偏见也被带入了机器学习模型的开发过程中。无论是有意还是无意,这种通过计算机算法对人类偏见的“洗白”可能会使这些偏见看来是客观的。&br&&br&最近的一个例子便是,上海交通大学 Xiaolin Wu 和 Xi Zhang 在 2016 年 11 月传到 arXiv 上的论文《使用脸部图像自动推断罪犯》(Automated Inference on Criminality Using Face Images)。吴和张认为,机器学习技术可以预测一个人是否是犯罪分子(不是犯罪嫌疑人),号称准确度几乎 90%,而他们使用的数据仅仅是类似美国驾驶执照上人脸的证件照。虽然该论文没有经过同行评议,但其调查结果激发了一系列新闻报道。[2]&br&&br&研究界的许多人都认为吴和张的分析在道德和科学上都是有问题的。在某种意义上,这不是什么新鲜事。然而,使用现代机器学习方法(性能强大,但对很多人来说也是神秘的),可以使这些过去的说法看上去有了新的可信度。&br&&br&在摄像机和大数据无所不在的时代,机器学习相面也可以前所未有的规模得到应用。鉴于社会越来越多地依赖机器学习实现常规认知任务的自动化,开发人员、评论家和用户都迫切需要了解人工智能技术的局限和相面这一伪科学的历史,更何况后者如今还披上了一层和现代技术的外衣。&br&&br&因此,我们在这里面向广泛的受众撰写了这篇深度文章:不仅对研究人员、工程师、记者和政策制定者,任何关心如何确保 AI 技术朝着有利于人类发展的人都是本文的读者。&br&&br&接下来,我们将首先回顾机器学习技术的底层运作方式,然后讨论机器学习将如何延续人类的偏见。&br&&br&&h2&&b&如何用机器学习了解图像&/b&&/h2&计算机可以根据某个人的图片进行计算来分析这个人的身体特征。这是很普通的一个图像问题:计算机程序分析照片、根据照片做出一些决定,然后得出某些有意义的判断(比如说,“这张照片中的人很可能在 18 岁和 23 岁之间”)。&br&&br&照片和计算机反馈之间的关系由一组参数确定,这些参数会在机器学习的阶段进行调整,这就是“机器学习”的由来。机器学习最常见的方法是监督学习,会使用大量带标记的样本工作,也就是样本图像与每个理想输出都进行配对。当参数设置为随机值时,机器只能纯粹凭运气作出回答;但即使给出了一个随机的起点,人可以慢慢地调整一个或多个参数,并问“这种变化是更好还是更差?”这样,计算机就能自我优化,学习任务。通常的训练项目会涉及数百万、数十亿或数万亿的参数选择,计算机在这个过程中稳步提高完成任务的性能。最终,计算机提高的水平放缓并趋于平稳,根据给定任务的固有困难程度以及机器和数据的局限性,预测准确性可能已经达到了最佳状态。&br&&br&训练时,要避免的一个情况是过拟合(overfitting)。过拟合就是机器能够记住个别训练样本的正确答案,但不能进行泛化,泛化则是适用于不同的数据。避免过拟合最简单的方法是在验证系统时,使用没有在训练中出现过标记数据集。如果系统在验证时性能和训练时大致相同,那么就可以确信系统真的学会了如何发现数据中的一般模式,而不仅仅是记住了训练样本。这实际上和让学生考试的理由相同,测验时考的都是以前没有见过的问题,而不仅仅是重复在课堂上学到的例子。&br&&br&每个机器学习系统都有参数——否则就没什么可学习的了。简单的系统可能只有比较少的参数。增加参数数量可以让系统学会更复杂的关系,成为更强大的学习者,输入输出间的关系越复杂,系统错误率就越低。另一方面,更多的参数也让系统能够记住更多的训练数据,因而也更容易产生过拟合。这意味着在参数数量和所需的训练数据的数量之间有一个关系。&br&&br&现代的复杂的机器学习技术,如卷积神经网络(CNN)有数百万个参数,因此需要大量的训练数据避免发生过拟合。获得足够多带标签的数据来训练和测试系统,通常是机器学习研究者面临的最大的实际挑战。&br&&br&&b&示例:确定照片拍摄时间&/b&&br&&br&卷积神经网络应用十分广泛,性能也非常强大。例如,Ilya Kostrikov 和 Tobias Weyand 提出的 ChronoNet,这个 CNN 可以猜测拍摄照片的年份。他们使用的数据是在过去 100 年间拍摄已知的日期的照片,这些照片都带了某种程度的标签(在这种情况下为日期照片),因此获取标记数据用于训练这个网络相对来说比较简单。&br&&br&一旦网络被训练好,就可以输入照片,可以看出系统猜测拍摄的那一年。 例如,以下两张照片都是 ChronoNet 猜测1951(左)和1971(右):&br&&br&&figure&&img src=&https://pic4.zhimg.com/v2-23f00512ed8def46670b2_b.jpg& data-rawwidth=&1000& data-rawheight=&353& class=&origin_image zh-lightbox-thumb& width=&1000& data-original=&https://pic4.zhimg.com/v2-23f00512ed8def46670b2_r.jpg&&&/figure&图2 深度学习猜测拍摄年份的照片&br&&br&这些都是很好的猜测。左边的照片在 1950 年在斯德哥尔摩海滨拍摄的,右边的照片则是 1972 年尼克松在亚特兰大州发表竞选演说,旁边是尼克森夫人。&br&&br&神经网络究竟是如何计算出来的?从机械学的角度来看,数百万个学习参数只是一系列加权平均计算中使用的权重。从原始像素值开始,加权平均值被组合,然后用作相似的计算集合的输入,然后又被用作另一个类似的计算集合的输入,等等——在多层网络中创建一个级联的加权平均计算。[3] 在 ChronoNet 中,最后一层的输出对应的是照片拍摄可能年份的概率值。虽然在技术上是正确的,但这个“概率”是无法解释的;让一位人类专家判断这两张照片的年代,他同样可以说:“我这样回答,是因为我的神经元就是这么连在一起的。”&br&&br&事实上,像人类专家一样,人工神经网络很可能学到了发现各种细微线索,从低级属性,如胶片颗粒和色域(电影处理技术在 20 世纪得到了长足的发展)到衣服和发型,乃至车型和字体。上面那张斯德哥尔摩照片中的扬声器和婴儿车的风格也可能是线索。自 2006 年以来,所谓的深度学习进一步加快了人工智能的快速发展,与任务(颜色、汽车模型等)相关的特征可以被隐含地学习,为更高层次的目标(比如猜测照片拍摄年代)服务。[4]&br&&br&以前的机器学习方法也可能已经达到了猜测照片拍摄年代的高级目标,但是需要手工编写计算机代码,从原始图像中提取字体和发型等特征。让计算机能够端到端的学习一个复杂的问题,省去了编码这样的定制工作,大大加快了开发速度,也经常大幅地提高了结果的准确率。&br&&br&这既机器学习的力量也是这种方法的危险,特别是深度学习。深度学习的力量我们是清楚的:一般的方法可以发现各种不同问题中的隐含关系;系统本身会去寻找去学习的内容。而深度学习的危险则来自于一个科学家或工程师可以轻松地设计一个分类任务,让机器在不了解任务实际测量的内容,或者系统实际发现的模式的前提下,进行很好的学习。这种情况下,机器“如何”或“为什么”做出判断就变得很重要了,尤其是涉及到判断一个人的性格或犯罪情况时。&br&&br&&b&&u&论文摘要&/u&&/b&&br&&br&我们首次进行基于静止的人脸图像自动推测犯罪性的研究。通过有监督机器学习,我们使用 1856 张真实的人的面部照片建四个分类器(逻辑回归,KNN,SVM,CNN),这些人中有近一半是已被定罪的犯罪者,其余是非犯罪者,我们以民族、性别、年龄和面部表情作为控制要素,让计算机区分犯罪者和非犯罪者。四个分类器都表现良好,为根据脸部特征自动预测犯罪性提供了有效性证据,尽管围绕该主题存在历史性争议。此外,我们发现一些可以预测犯罪性的结构上的区别特征,例如嘴角的弧度、眼内角间宽、以及所谓的鼻唇角角度。这项研究最重要的发现是,犯罪者和非犯罪者的面部照片在表情的多样性方面非常不同。犯罪者的面部表情变化明显大于非犯罪者。由两组照片组成的两个流形看起来是同心的,非犯罪者的流形的跨度较小,表现出正常的规律。换句话说,一般守法公民的面貌与犯罪者的面貌相比具有更大程度上的相似性,也就是说,犯罪分子在面部表情上的差异比普通人更大。&br&&br&&h2&&b&通过机器学习来推断一个人是否是“犯罪分子”?&/b&&/h2&《使用脸部图像自动推理罪犯》要做的,也是 ChronoNet 类似的事情,除了后者是推测任意照片拍摄的年代,而前者则是根据人脸部图像推测一个人是否有犯罪记录。因此,吴和张在论文中写道,这是首次“为自动根据人脸推理罪犯提供了证据”。&br&&br&为了说明为什么这种说法有问题,接下来我们将更详细地解说其研究方法和结果。&br&&br&&h2&方法和结果&/h2&吴和张的数据集是中国政府颁发的身份证照片,一组含有 1,856 张 80x80 像素的中国男性面孔近照(closely cropped)。这些男性年龄介于 18 至 55 岁之间,图像中没有面部毛发,也没有疤痕或其他明显痕迹。图像中的 730 个人被标记为“罪犯”,或者更确切地说,&br&&br&“&i&……其中 330 人是中国公安部和广东省、江苏省、辽宁省等公安部门公布的犯罪嫌疑人;其他则是由中国一个城市警察部门根据保密协议提供。……在 730 名罪犯中,235 人犯有包括谋杀、强奸、殴打、绑架和抢劫等暴力罪行;其余 536 人被定罪为非法暴力罪行,例如盗窃、欺诈、腐败、伪造和敲诈勒索罪。&/i&”&br&&br&其他 1126 张人脸图像则是:&br&&br&“&i&使用网络爬虫从互联网获取的非犯罪分子头像,覆盖广泛的专业和社会地位,包括服务员、建筑工人、出租车和卡车司机、房地产经纪人、医生、律师和教授;……大约有一半的人拥有大学学位。&/i&”&br&&br&需要特别强调的是,所有这些人脸图像都来自政府颁发的身份证——这些被视为“犯罪分子”的图片不是犯罪现场照片。&br&&br&吴和张用这些带标签的样本做监督学习。他们训练计算机看一张脸像,并产生一个“是/否”的回答:这个图片上的人属于“罪犯”组还是“非犯罪分子”组?他们使用了4种不同复杂程度的机器学习技术,也就是参数数量多少不同,更复杂的技术具有更多的参数,因此能够学会图像中细微的关系。其中,一个不太复杂的技术使用自定义代码对图像进行预处理,提取特定已知面部特征的位置(如眼睛和嘴角),然后使用较旧(older)的方法学习与这些面部特征位置相关的模式。作者还使用了 AlexNet,其架构与 ChronoNet 类似。AlexNet 是最现代化的模型和参数最多的 CNN 之一,性能也十分强大,分类精度高达近 90%。不过,即使使用较老的方法,论文给出的精度也远高于 75%。&br&&br&这带来了几个问题,也许第一个就是“这可能是真的吗?”更确切地说,&br&&ul&&li&这些数字是否可信?&br&&/li&&li&机器学习学到的是什么?&/li&&li&这与犯罪行为和刑事判决有什么关系?&/li&&/ul&&br&&br&&h2&可能的假象&/h2&&br&要看准确率高达 90% 是个什么概念,我们来对比另外一篇论文。计算机视觉研究人员 Gil Levi 和 Tal Hassner 在一篇精心控制的 2015 年论文中发现,具有相同架构的卷积神经网络(AlexNet)在推测快照中人脸性别[5] 时的准确率只有 86.8%[6]。另外,吴和张在论文中声称基于 CNN 方法的“假阳性”(即将“非罪犯”误识别为“罪犯”的错误率)只超过 6% 一点点。新的研究显示,药物检测一般会在 5% 至 10% 的病例中产生假阳性结果,10% 至 15% 的病例中为假阴性。&br&&br&我们认为论文中声称的准确度高得有些不切实际。一个技术问题是,少于 2000 个样本实际上是不足以训练和测试像 AlexNet 这样的 CNN 而不会过拟合的。论文采用较旧的非深度学习方法给出的较低的准确率(其实还是很高了)可能更为真实。&br&&br&还应该注意,作者无法可靠地推断出他们从网络获取的身份证图像都是“非犯罪分子”的;如果我们假设这些人是一般人群中抽取的随机样本,根据统计学,其中一部分人也可能从事犯罪活动。&br&&br&另一方面,论文中使用的数据集都是来自 18 只 55 岁的男性,这可能也有问题,因为法官在判决时可能会首先考虑排除年龄偏见。&br&&br&同样,论文中所示的 3 个“非罪犯”图像(见下文)中都穿着白领衬衫,而另外 3 名被判别为“罪犯”的都没有。当然,只有 3 个例子,我们不知道这是否代表整个数据集。但是,我们知道,深度学习技术是强大的,并且能够学会所有接收到的线索,正如 ChronoNet 除了图像内容的不同之外,还提取了细节,如胶片颗粒度。&br&&br&机器学习不会区分因果关系和偶然的相关性。&br&&br&&h2&&b&机器学习究竟学到了什么?&/b&&/h2&&br&排除可能会影响论文所声称准确度的技术错误和混淆,图像中捕获的人脸外观与“罪犯”组中的成员之间可能确实存在相关性。这些被称为“罪犯”的人脸部有什么独特的特征吗?&br&&br&吴和张使用了各种技巧对此作了详细的探讨。对于较为简单的机器学习方法,其中会测量标准面部标记(landmark)之间的关系,这是特别容易的。他们总结说,&br&&br&“……犯罪分子从两边嘴角到鼻尖的角度 θ 平均值比非犯罪者的平均值要小19.6%,差异较大(has a larger variance)。而且,犯罪分子的上唇曲率 ρ 平均比非罪犯大 23.4%。另一方面,犯罪分子内眼角之间的距离 d 比非犯罪分子略窄(5.6%)。”[7]&br&&br&关于这一点,我们可以从论文中的配图得到直观的了解。下图是论文中的图1,上面一排是“罪犯”,下面一排则是“非犯罪分子”。&br&&br&&figure&&img src=&https://pic3.zhimg.com/v2-02d18d47db7dcaa99863a_b.jpg& data-rawwidth=&740& data-rawheight=&523& class=&origin_image zh-lightbox-thumb& width=&740& data-original=&https://pic3.zhimg.com/v2-02d18d47db7dcaa99863a_r.jpg&&&/figure&&br&上排是“罪犯”,下排是“非犯罪分子”。上排的人脸表情皱着眉头(frowning),而下排没有。深度学习系统可能会“学会”这样表面的区别。&br&&br&论文作者只公开了上面这 6 个例子,这也有可能是故意挑选的。我们也做了随机调查(包括中国和西方国家的同事),如果必须在二者中选择一组,很多人也认为下面一排的 3 个人是罪犯的可能性小一些。一方面,尽管作者声称对面部表情做了控制,但是底部 3 张图像似乎都是显得在微笑的,而上排的 3 个人则似乎是皱着眉头。&br&&br&如果这 6 幅图像确实是典型的样本,那么我们怀疑让一名人类法官将图像从微笑到皱眉来排个序,也可以很好地将“非罪犯”与“犯罪分子”区别开来。后面我们会阐述这一点。&br&&br&&h2&人类又从中发现了什么?&/h2&值得强调的是,在这种(或任何)机器学习应用中没有超人的魔力。虽然非专家只能大概估计一张照片的拍摄年代,但大多数人[8]在识别人脸方面都非常敏感。我们能一眼就从比较远的距离认出自己熟悉的人,而且这样的人可能有成百上千个,注意到别人的凝视和表情的细微差别,并且所有这些都在十分之一秒内完成。[9]&br&&br&吴和张并没有声称他们的机器学习技术在识别人脸面部细微特点(cue)方面,比不需要计算机辅助的普通人要强。不过,他们将其工作与 2011 年在心理学期刊发表的一项研究(Valla 等人,基于面部外观推断犯罪分子的准确性[The Accuracy of Inferences About Criminality Based on Facial Appearance])联系在一起,那篇论文也使用人类的判断得出了类似的结论:&br&&br&“&i&……研究人员给实验参与者展示了一组罪犯和非罪犯的头像,这些图片都控制了性别、种族、年龄、吸引力和情感表现之后,也消去了任何显示图片来源的线索,结果表明,实验参与者都能够可靠地区分这两个群体。&/i&”&br&&br&虽然吴和张使用的身份证 ID 照片而不是犯罪嫌疑人照片(mugshot),我们应该注意,Valla 等人的论文(尽管他们声称已经对摄影条件做了控制),作者比较的是被定罪人的照片和在校园里拍摄的学生的照片。可以认为,被捕后身处威胁和侮辱性的环境中,那时所拍摄的照片看起来与在大学校园里拍摄的照片看上去不同,因而论文的结论也值得商榷。&br&&br&吴和张也将他们的工作与 2014 年心理学期刊 Psychological Science 发表的一篇论文(Cogsdill 等人,从人脸推断性格:关于发育的研究[Inferring Character From Faces: A Developmental Study])联系起来。这篇论文的其中一位作者就是我们中的一个人。这篇论文发现,即使是 3 岁到 4 岁的孩子,也能准确地区分“善意”(nice)和“不友好”(mean)的脸部图像。但关键是,没有人声称这些这些印象与一个人的性格有关。本文研究的是在人类发育早期对人脸表情类型(facial stereotype)的识别,使用的也是将这些不同类型的表情可视化的照片。[译注:这里指实验中使用的是心理学研究中常用的代表不同表情的人脸照片。]&br&&br&所谓“友善”和“不友善”的脸看起来是什么样子?过去 10 年有关人脸表情社会感知的研究表明,人对一张脸的印象可以浓缩到一些基本层面,包括强势(dominance)、吸引力(attractiveness)和价值(valence,与“值得信赖”、“外向”等积极评价有关)。科学家开发了各种方法,将这些维度上的典型面部表情可视化。其中一种是,让实验参与者评判随机合成的面孔,是否可靠(trustworthy)和强势(dominance)。由于合成的人脸是根据不同面部特征的相对大小或位置得出的统计模型,所以可以计算出代表“值得信赖”或“不可信任”的人脸的平均特征;对于白人男性,可靠与不可靠的脸分别看起来像这样:&br&&br&&figure&&img src=&https://pic4.zhimg.com/v2-93cdf1cbce_b.jpg& data-rawwidth=&800& data-rawheight=&403& class=&origin_image zh-lightbox-thumb& width=&800& data-original=&https://pic4.zhimg.com/v2-93cdf1cbce_r.jpg&&&/figure&图4. 根据儿童和成人的判断,典型的“友善”(左)和“不友善”的人脸&br&&br&看起来“不值得信任”的脸与吴和张论文中“罪犯”的脸(图3)看起来相似。&br&&br&&h2&&b&客观的谬误&/b&&/h2&吴和张在论文中并没有危言耸听,将人对一张脸的印象(如“不可信赖”)和所谓的客观现实(如“罪犯”)联系起来,而是声称我们看到的右边的面部特征潜在预示(imply)犯罪行为。这种不正确的断言依赖的是推定的客观性和输入、输出自己算法之间的独立性。&br&&br&因为吴和张使用的算法是基于一种高度通用的深度学习技术——卷积神经网络,后者可以从任何类型的图像数据中学习模式——这种方法可以说是客观的,也就是说,深度学习/卷积神经网络本身并不对人脸面部特征或犯罪行为带有偏见。&br&&br&输入被认为是客观的,因为吴和张的论文使用的是标准化的 ID 照片。输出也被认为是客观的,因为它是一种合法的判决(legal judgement)——是独立于输入的,因为在绝大多数文献中“正义”(justice)都被认为是“看不见的”(注释:正义女神经常被造成带眼罩的形象,代表其客观、不徇私、一视同仁的平等精神)。正如作者所说,&br&&br&“&i&由人类观察者主观判断会导致偏见,而我们是首次在没有任何人为偏见的情况下,研究了根据脸部特征自动推断犯罪分子。&/i&”&br&&br&在这里,论文中声称输入和输出的客观性是具有误导性的。但是,这项工作最令人不安的是,它引用了两种不同形式的权威力量——科学和法律,让人群中存在高低贵贱之分的这种说法再次复苏并且予以证明。那些上唇弧度更加弯曲,眼睛更靠近的人都处于社会较底层,容易出现(用吴和张的话说就是)“一大堆异常(不合群)的个人特征”,最终导致这些人在法律上被判定为“罪犯”的可能性很高。&br&&br&这种论调与 Cesare Lombroso 的话很相似。在探索面部外观输入与刑事判决输出之间相关性的可能原因之前,我们有必要停下来,回顾这些声称的历史。&br&&br&&h2&&b&“面相学”——科学种族主义&/b&&/h2&面相学和“类型”理论[10]&br&&br&“面相学”的根源在于人类倾向于相关地、隐喻地、甚至是诗意地解释一个人的外表。这种想法至少可以追溯到古希腊人[11],在文艺复兴时期的多米尼加·詹巴蒂斯塔·德拉·波塔的“人类生理学”(De humanaphysiognomonia)中尤其明显,书中展示:一个长得像猪的人就像猪一样[12]:&br&&br&&figure&&img src=&https://pic3.zhimg.com/v2-af_b.jpg& data-rawwidth=&800& data-rawheight=&549& class=&origin_image zh-lightbox-thumb& width=&800& data-original=&https://pic3.zhimg.com/v2-af_r.jpg&&&/figure&图5. 一半像男人,一半像公猪:摘自 Giambattista dellaPorta 的 De humanaphysiognomonia(那不勒斯,1586)&br&&br&要在启蒙运动中理解这样的想法,有必要从中去除诗意的成分,集中精力在更具体的身体和行为特征上。在十七世纪,瑞士神学家约翰·卡斯帕拉夫特(Johann CasparLavater)基于眼睛、眉毛、嘴巴和鼻子的形状和位置来分析人的性格,以确定一个人是否具有“欺骗性”、“充满恶意”、“愚蠢”、还是”疯狂“。&br&&br&在这种情况下,维多利亚时代的博学家弗朗西斯·加尔顿(Francis Galton,)试图通过将犯罪分子的人像曝光叠加在同一张底板上来实证地表征“犯罪”类型。大约在同一时间,Lombroso 采取了更为“科学”的犯罪学方法进一步进行了现实的测量。[13]虽然Lombroso 可以被认为是第一个试图系统研究犯罪行为的人之一,但他也可以被认为是第一个使用现代科学来对定义“矮化”的“人类类型”的人之一。&br&&br&&figure&&img src=&https://pic4.zhimg.com/v2-00ccbb26addf0f863a18dbbedccd2788_b.jpg& data-rawwidth=&698& data-rawheight=&257& class=&origin_image zh-lightbox-thumb& width=&698& data-original=&https://pic4.zhimg.com/v2-00ccbb26addf0f863a18dbbedccd2788_r.jpg&&&/figure&图6.弗朗西斯·加尔顿(Francis Galton)试图重建“通用犯罪分子”的肖像。&br&&br&严谨的科学方法凭借时间、同行评议和迭代来去除错误假设; 但使用科学语言和方法并不能阻止研究人员进行有缺陷的实验,并提出错误的结论——特别是当他们先入为主时。这种认识与种族主义本身一样古老。&br&&br&&h2&&b&1850——1950年的科学种族主义&/b&&/h2&Lombroso 的信仰伴随着对意大利的“南方人”的尊重,隐含着一种带有政治色彩的种族等级观念。但是19 世纪的美国面相学家们更加注重合理化这个等级:他们是奴隶主。塞缪尔·莫顿(Samuel Morton)用颅骨的测量和民族学的论据来表达白人至上的地位; 正如他的追随者Josiah Nott和George Gliddon 在他们《1854 年的人类类型》中引用的:&br&&br&“智慧,有活力,有野心,进步,解剖学上更为高级,这是一些种族的特点; 愚蠢,懒惰,低活动能力,野蛮,解剖学更为低等,是另一些种族的特点。在所有情况下,崇高的文明都是由“白种人”团体完成的。”&br&&br&尽管这本书以学术论著自居了几个世纪,它显示出等同于德拉·波塔的论文中显而易见的面部特征推理和动物类比,尽管在现代语境下,更具侵略性:&br&&figure&&img src=&https://pic3.zhimg.com/v2-ee87c6c9bf2ac569508e_b.jpg& data-rawwidth=&800& data-rawheight=&479& class=&origin_image zh-lightbox-thumb& width=&800& data-original=&https://pic3.zhimg.com/v2-ee87c6c9bf2ac569508e_r.jpg&&&/figure&图7.人类的劣势类型的观念和一些人比其他人更像动物这样科学上无效的想法相关。来自Nott 和Gliddon,《1854年的人类类型》。&br&&br&在19世纪后期,达尔文进化理论反驳了人类类型的认识,即所谓种族是如此不同,它们必须由上帝独立创造。然而,通过明确说明人类实际上是动物,而且与其他猿类有着密切的关系,它为莫顿离散的种族等级“学说”提供了肥沃的土壤,这一学说区分出“更人性化”的人(在身体、智力和行为方面更为进化)和“少人性”(进化不足,身体上更接近其他猿,不那么聪明,不太“文明”)。 [14] 达尔文在他的1871年的《人类的由来》中写道:&br&&br&“&i&&b&[...]人的身体结构清楚地体现出从某种低级形式的演变轨迹; 或者是一个野蛮人和现代人之间的道德和智力方面的差异。比如由老导航员拜伦描绘的男人,他们把孩子摔在岩石上,就因为孩子弄掉了一篮子海胆;你也想象不出野蛮人会像牛顿或者莎士比亚那样使用抽象术语。最高种族和最低种族的野人之间的这种差异是渐变的。&/b&&&/i&&br&&br&不足为奇,达尔文认为人性的高峰体现在物理学家艾萨克·牛顿、剧作家威廉·莎士比亚、废奴主义}

我要回帖

更多关于 数字通信中,在对语音信号 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信