庄子真的很消极吗?

如果说庄子代表的道家思想是消极的,那么孔子代表的儒家肯定就是积极的了。前者叫你要顺其自然,无所待而逍遥,凡事莫强求;后者劝你要修身、齐家、治国、平天下,学而优则仕,施展远大抱负。

其实在我看来,道家和儒家只是思想的侧重点不同,并没有明确的对立关系。道家的“顺其自然”是指在你“洞察世事之规则后,顺应天道,从而达到心如止水的境界”,这里的天道就是指“自然规律”。顺应天道的前提是在你“洞察世事之规则后”,换句话说,他不是让你无脑躺平、让你消极生活,而是告诉你,平时要多动动脑子,领悟世间万物运行之根本,明白个人与自然宇宙之间的关系,不要有过分的想法和欲望,徒增烦恼,最终达到精神上的真正自由。所以你看,庄子给每个道家信徒都设置了一个门槛,就是首先你得有领悟这个思想的慧根。

庄子在《逍遥游》中说大鹏展翅一飞冲天,借助的是外部的“风”,没有风它就飞不了了,换言之大鹏是有所依赖的,所以做不到真正的自由。庄子还讲了一个“相濡以沫,不如相忘于江湖”的故事,说两条快要干死的鱼在泥土里靠相互的唾沫苟活下去,还不如各自去到大江大河里逍遥快活。庄子极度推崇个人的自由,告诉你要遵从自己的内心,这个前提可能是要求你牺牲掉你的“本来所得”。

严格来讲,儒家的某些思想是反人性的,但是现代社会的发展、生产力的提升,反而恰恰离不开这种“反人性”的约束。每个人有内外的诸多限制,或者说诸多缺陷。这就会让人觉得,从本质上来说,以庄子为代表的这种“逍遥“的境界是人在现实中所无法达到的。但是有的时候理想之所以伟大,并不是因为它可以实现,恰恰是因为它的不可实现性。因为只有这样,它对人才具有更高的提升意义。陶渊明、苏轼他们正是以庄子这种逍遥思想为精神向导,才能留下《归去来兮辞》、《赤壁赋》这种千古名篇。

为什么“吃得苦中苦,方为人上人”的思想很可怕?

努力的目标不应该是为了过人上人的生活

吃得苦中苦,方为人上人

那如果以这一种目标去努力的话

你会发现是非常可怕的

因为你努力的目标

居然是爬在别人身上作威作福

那也就是说你所吃的一切苦

将来都要加倍的找补回来

那这是非常痛苦的

会进入到一个社会达尔文主义的恶性循环

因为每个人都有跌倒有摔跤的时候

每个人都有可能从强变弱的时候

即便你暂时强

还有比你更强的

那如果强者通吃一切

你作为人的尊严可能就会丧失


那些在社会竞争中处于优势的人

一定要知道

他们其实靠的并不单纯是自己的努力

还有一些大量的人所无法掌控的东西

时也,命也,运也

这些是人所无法掌控的


300年前

一个人会打篮球绝对什么都不是

但是生活在300年后的今天

一个人如果是篮球高手

他甚至能进NBA

就能够获取巨大的财富


所以最重要的是内心的充盈和内心的优秀

一个内心优秀的人

他就可以安之若素

泰然自若

与这个时代狭路相逢


努力的目标不是为了成为人上人,是为了能够内心平静

(摘自网络)

什么是RAG?

RAG全称Retrieval-Augmented Generation,翻译成中文是“检索增强生成”。其中检索指的是“文本相似性检索”,生成指的是“基于大语言模型的生成型算法”,比如OpenAI的GPT系列,以及阿里的通义千问系列等等。完整去理解这个术语应该是:用文本语义相似性检索的结果,来丰富大语言模型的输入上下文,以此提升大语言模型的输出效果。RAG技术之所以重要,近几年热度持续上升,其主要原因还是受大语言模型影响,尤其大语言模型技术在实际使用过程中本身存在的一些缺陷。

大语言模型的缺陷

除去模型的复杂性对数据和算力的要求太高(这顶多算是门槛高,造成一般中小型公司无法入坑),大语言模型(LLM)在实际应用过程中有两个缺陷:

(1)知识的局限性

CV领域中常见的YOLO系列检测算法,模型参数量大概在百万~千万级别。而大语言模型参数量通常十亿起步,更复杂的甚至达到了千亿万亿级别。更多的参数量意味着模型能存储(学习)更多的知识,无论从知识深度还是广度都比传统的“小”模型要更优。但是,这个世界是复杂的,不同行业的知识千差万别,俗话说:隔行如隔山,这个在大模型技术中尤为突出。通常一些公司在训练大模型时,采用的数据主要来源于互联网,大部分都是公开的知识数据,模型从这些公开的数据中学习当前数据的基本规律,但是对于那些由于各种原因没有公开到网络上的垂直细分领域的知识数据,大模型在训练过程中完全无法接触到,这也导致模型在实际应用推理过程中,对特定领域问题回答效果不佳、甚至完全一本正经的胡说八道。下面是我向ChatGPT询问“车道收费软件程序无法正常运行如何处理?”,可以看到,ChatGPT在这种专业问题中表现不足,虽然回答的条理清晰(第一张图),但是并不是我们想要的答案(第二张图)。

<ChatGPT回答结果↑>

<标准答案↑>

(2)知识的实时性

大模型掌握的知识受限于训练它用到的数据,模型训练(迭代)一次消耗的时间相对较长(几个月甚至一年),很多知识数据虽然是公开的,但是由于时间关系,并不能及时被大模型掌握。比如前几天(2024/12/3)网络上出现了韩国总统尹锡悦突然发动戒严的新闻,你如果向一个11月份就上线的大模型问韩国的相关历史事件,这个戒严事件肯定不在回答之列,原因很简单,因为模型在训练过程中并没有相关数据。下面是我向ChatGPT询问“韩国近现代有哪些主要的政治事件?”,在它给出的回答中,可以看到回答的主要事件截止时间在2022年尹锡悦上台后不久,回答中没有更新的数据。

<ChatGPT关于韩国近代历史事件的回答↑>

提示工程的重要性

在2022年底ChatGPT刚出来不久,GitHub上就有一个非常火爆的项目仓库叫awesome-chatgpt-prompts(截止2024/12/15已经有114K星星),这个仓库核心就是一个README文件,里面包含了我们在使用ChatGPT时常用到的一些“提示词”。提示词的作用是告诉大模型一些上下文背景,让模型能够根据你的提示词来给出想要的回答。提示词完全使用自然语言编写,好的提示词能够得到更加准确的回答。下面给出两个例子,第一个是让ChatGPT充当Linux操作系统命令行终端(类似ssh工具),响应用户输入的Linux命令;第二个是让通义千问充当一个关键信息提取器,将两个人语音对话中的关键信息提取出来然后按指定格式输出。

<ChatGPT扮演Linux终端↑>

<给通义千问设置提示词↑>

<通义千问根据提示词给出的回答↑>

可以说如何用好大模型,提示词是关键。提示词的设计可以复杂,比如提供一些输入输出的示例,让大模型参考,也可以提供“思考”逻辑或者方向,给大模型提供一些回答问题的思路,当然也可以交代一些背景知识,让大模型“实时”去消化。

既然提示词如此重要,它能够丰富大模型问答上下文,让模型了解更多的背景知识,从而提升大模型的使用效果。那么我们能否使用类似提示词这种方式去弥补前面提到的那些大模型应用缺陷呢?答案是肯定的,这就是RAG采用的技术思路。针对“知识的局限性”和“知识的实时性”问题,我们可以借用类似“提示词”之手告诉大模型,让它能够在“外部补充数据”的上下文环境中,给出我们想要的答案,从而规避知识的局限性和实时性问题。

为了让大模型能够掌握额外的背景知识,我们引进了“外部补充数据”,那么我们应该如何获取这个外部补充数据呢?很明显这个获取的过程很关键,有两个原因:

1、外部补充数据一定要与用户query有关,而且相关性越高越好,否则就是误导模型思考,补充数据越详细越好,模型获取的外部信息就越充足。

2、我们事先并不知道用户需要query什么问题,所以获取外部补充数据一定是一个检索的过程,从一个大规模特定领域的非结构化数据集合中检索与query相关的部分,这个检索是实时性的,针对不同的query可能都需要这个操作。

现在我们将上一张图更新一下,大概可以修改成这样:

针对用户的每个query,我们先从“知识库”(大规模特定领域的非结构化数据集合,可随时更新)检索出与query相关的信息,然后再和原始query一起合并输入到大模型。目前这里的检索方式通常采用“文本语义相似性检索”,这个跟CV领域图像相似性检索类似,一般基于高维特征去做相似度匹配。

文本语义相似性检索

一般在讲语义相似性检索的时候,通常也会提到关键字检索,这是两种不同的文本检索方式。下面是两种方式特点:

1、语义相似性检索。基于文本内容含义去做匹配,算法能够理解句子(词语)的内在含义,然后找出意义与之相似的内容。比如“这个产品令人眼前一亮”和“这个产品设计很新颖”虽然文本内容本身不尽相同,但是其意义相似,那么算法认为它们相似度很高。类似的,“汽车”和“轿车”相似度很高,“跑步鞋”和“耐克”相似度很高,“晴朗的天”和“万里无云”相似度很高。

2、关键字检索。基于文本内容本身去做匹配,算法只匹配句子(词语)内容本身,“晴朗的天”和“万里无云”如果从关键字去匹配的话,相似度基本为零。在学习《数据结构和算法

》课程的时候,里面提到的“汉明距离”可以算作关键字匹配的一种方式,主要用来计算字符串之间的相似度。

我们从上面的解释可以了解到,语义相似性检索方式显得更加“聪明”,更贴近人类思考的方式,所以在RAG技术栈中,一般采用语义相似性检索的方式去获取前面提到的“外部补充数据”。文本的语义相似性检索一般基于“高维特征”去完成,大概思路就是事先对知识库中的文本内容进行特征编码,生成高维特征向量(通常128/256/512维度),最后存入专用向量数据库(比如Faiss/Milvus向量数据库),形成底库。在检索阶段,先对query文本使用相同的特征编码算法生成特征向量,然后使用该向量去向量数据库中检索前TOP N个相似特征,最后映射回前TOP N个原始文本内容。那么得到的这些原始文本内容,就和query文本在语义上存在很高的相似度。

上面图中,A和B是特征向量底库,C是query特征向量,最终C和A之间的相似度要比C和B之间更高,所以“今天万里无云,天气很好”可以匹配到“真是一个晴朗的天”。那么如何衡量特征向量之间的相似度呢?以2维向量为例(3维或更高维类似),可以把特征向量看做是二维坐标系中的一个点,最后计算两点之间的距离,通常有两种距离计算方式,一个叫“欧氏距离(直线距离)”,一个叫“余弦距离”。

欧氏距离越小,两个点越靠近,代表越相似(图中红色);余弦距离代表的是点与原点连线之间的夹角,夹角越小(余弦值越大,图中紫色),代表越相似。(关于特征编码,以及特征相似度计算可以参考之前的文章)。目前网络已经有很多开源的文本特征编码模型,针对不同国家语言,可以为其提取文本特征标识。下面是我对鲁迅的《故乡》全文做特征编码,然后去检索“我情不自禁地伤心起来。”这句话的例子,最后返回TOP 10个与之语义相似的句子,可以看到整体表现还算可以,尤其排名靠前的几个结果,基本和query句子的含义比较接近。

从“单阶段推理”到“两阶段推理”

我们现在回顾一下大语言模型的工作流程,基本上就是用户提供query问题输入(可以携带一些简单的上下文,比如历史对话记录),大模型直接给出回答,注意这里大模型完全基于训练时积累的经验给出的答案。这个可以看做是一个“端到端”的推理过程,我们称之为“单阶段推理”(可以类比CV领域中经典的YOLO系列检测算法)。那么相比较而言,RAG就是“两阶段推理”,问答系统先要从知识库中检索与用户query有关的信息,然后再与query一起,传入大模型,之后的流程就和单阶段推理一致。以此可以看出,RAG指的并不是具体哪一个算法或者技术,而是一套解决问题的技术方案,它需要用到文本特征编码算法、向量数据库、以及大语言模型等等。Github上有很多RAG相关的框架,都提供了灵活的接口,可以适配不同的文本编码模型、不同的向量数据库、不同的大语言模型。

总结RAG的优劣势和应用场景

1、RAG的优势

可以有效解决单纯使用大语言模型时碰到的一些问题,比如知识的局限性和实时性,能够解决大模型在垂直细分领域落地的难题,让大模型更接地气、给出的回答更贴近标准答案而不是一本正经的胡说八道。同时,让中小型企业可以基于开源大模型快速搭建自己的知识库问答(建议)系统,而无需对其进行二次训练或微调(算力和数据,包括大模型的训练门槛都是相当之高)。

2、RAG的劣势

引入了更多的技术栈,提升了系统的复杂性,无论开发还是后期维护工作量更高。同时,由于前期引入了“语义检索”的流程,涉及到准确性问题,一旦前期检索环节出问题,直接影响后面大模型效果。

3、RAG应用场景

RAG可以用在垂直细分领域的知识问答(建议)场景,对数据隐私要求比较高,同时对知识库实时更新有要求。当然除了本文介绍的这种基于知识库问答系统,类似的还可以用在基于数据库、基于搜索引擎等问答系统,原理基本类似。

 

什么是神经网络

(2019年文章)

大部分介绍神经网络的文章中概念性的东西太多,而且夹杂着很多数学公式,读起来让人头疼,尤其没什么基础的人完全get不到作者想要表达的思想。本篇文章尝试零公式(但有少量数学知识)说清楚什么是神经网络,并且举例来说明神经网络能干什么。另外一些文章喜欢举“根据历史交易数据预测房子价值”或者“根据历史数据来预测未来几天是否下雨”的例子来引入“机器学习/深度学习/神经网络/监督学习”的主题,并介绍他们的作用,这种例子的样本(输入X输出Y)都是数值,数字到数字的映射,简单易懂,但是现实应用中还有很多场景并非如此,比如本文后面举的“图像分类”例子,输入是图片并不是简单的数值输入。

分类和回归

我们平时讨论的机器学习/深度学习/神经网络大部分时候说的是“监督学习”范畴,监督学习应用最为广泛,也是神经网络发挥巨大作用的领域,因此,本文所有内容都是基于监督学习。从带有标签的样本数据中学习“经验”,最后将经验作用于样本之外的数据,得到预测结果,这就是监督学习。监督学习主要解决两大类问题:

(1)分类

分类很好理解,就是根据输入特征,预测与之对应的分类,输出是离散数值,比如明天是否下雨(下雨/不下雨)、短信是否是垃圾短信(是/否)、图片中包含的动物是猫、狗还是猴子(猫/狗/猴子)等等。分类模型的输出一般是N维向量(N为分类数),每个向量值代表属于此分类的概率。

如上图,根据样本数据(黄色圆形、蓝色正方形、绿色棱形),监督学习可以确定两条边界线,对于任何样本之外的数据(图中灰色正方形),可以预测它所属分类为B,对应的预测输出可以是[0.04, 0.90, 0.06],代表属于A类的概率为0.04,属于B类的概率为0.90,属于C类的概率为0.06,属于B类的概率最大,因此我们可以认为它的分类为B。请注意图中用来划分类型区域的两条虚线,同类样本并没有完全按照虚线分割开来,有黄色的圆形被划分到B类中,也有蓝色的正方形被划分到A类中。这种情况之所以出现,是因为监督学习得到的经验应该具备一定程度的泛化能力,所以允许学习过程中出现一定的误差,这样的学习才是有效的。

(2)回归

与分类相反,回归主要解决一些输出为具体数值的问题,比如明天的气温(20、21、30等)、明天股票开盘价(100、102、200等)。回归模型的输出一般是具体数值(包含向量,向量中包含每个具体的数值)。

如上图,根据样本数据(图中蓝色正方形,平面坐标系点),监督学习可以确定一条直线y=1.5x+1,对于任何样本之外的输入(Xn),可以预测对应的输出Y为1.5*Xn+1。请注意通过监督学习得到的直线y=1.5*x+1,事实上并不是每个样本都刚好落在该条直线上,大部分分布在直线周围。原因跟上面提到的一样,监督学习过程允许出现一定的误差,这样才是有效的学习。

学习的过程

不管是分类还是回归问题,监督学习都是从样本数据中学习经验,然后将经验应用到样本之外的数据。那么这个经验具体指什么?学习的本质是什么呢?

以上面回归问题为例,我们得到直线y=1.5*x+1的过程如下:

(1)确定样本数据呈直线分布(近似直线分布);

(2)设定一个目标函数:y=w*x+b;

(3)调整w和b的值,使样本数据点尽可能近地分布在直线周围(可以使用最小二乘法);

(4)得到最优的w和b的值。

以上是4步完成学习的过程,这个也是最简单的监督学习过程。至于其中“如何确定样本呈直线分布”、“如何判断目标函数为y=w*x+b”以及“如何去调整w和b的值,可以使样本数据点尽可能近的分布在直线周围”这些问题,后面一一介绍。

我们经常听到的深度学习中模型训练,其实指的就是学习的过程,最后输出的模型中主要包含的就是w和b的值,换句话说,训练的过程,主要是确定w和b的值,可以称这些参数为“经验”。

监督学习的过程就是找出X->Y的映射关系,这里的输入X可以称之为“特征”,特征可以是多维的,实际上X大多数情况都是多维向量,类似[1, 1.002, 0.2, …],输出Y称为“预测值”,预测值也可以是多维的,类似[0.90, 0.08, 0.02],比如前面提到的分类问题中,输出Y为多维向量,每个向量值代表预测对应分类的概率大小。

全连接神经网络

全连接神经网络由许许多多的“神经元”连接而成,每个神经元可以接收多个输入,产生一个输出,类似前面提到的X->Y的映射,如果输入是多维的,格式就是[x1, x2, …, xn]->Y(对于单个神经元来讲,输出都是一个数值)。多个神经元相互连接起来,就形成了神经网络,神经元的输入可以是来自其他多个神经元的输出,该神经元的输出又可以作为其他神经元的输入(的一部分)。下图为一个神经元的结构:

如上图所示,一个神经元接收[x1, x2, …, xn]作为输入,对于每个输入Xi,都会乘以一个权重Wi,将乘积结果相加再经过函数f作用后,产生输出Y。多个神经元相互连接之后,得到神经网络:

如上图,多个神经元相互连接起来组成全连接神经网络(图中只包含w参数,省略了b),图中神经网络一共包含3层(Layer1,Layer2和Layer3),上一层每个神经元的输出全部作为后一层每个神经元的输入,这种网络叫“全连接神经网络”(顾名思义,全连接的意思)。图中黄色部分就是两个完整的神经元结构,第一个神经元有三个输入(x1,x2和x3),分别乘以对应的权重w31,w32和w33,第二个神经元有四个输入(分别来自于Layer1层中的4个输出)。该神经网络可以接受一个3维向量作为输入(格式为[x1, x2, x3]),从左往右计算,最后输出一个2维向量(格式为[y1, y2])。对应它学习到的经验,可以用来处理符合如下映射关系的“分类”或者“回归”问题:

全连接神经网络是结构最简单的神经网络,相邻两层之间的神经元每个之间都有连接,因为结构最简单,因此通常以它作为入口来介绍其他结构更复杂的网络。注意,大部分神经网络并不是每个神经元都有连接关系,而且有些并不是严格遵从“数据从左往右移动”这种顺序。

神经网络中的矩阵计算

对于单个神经元的计算过程而言,是非常简单的,分三步:

(1)计算每个输入参数Xi和对应权重Wi的乘积;

(2)将乘积加起来,再加上一个偏移值b;

(3)最后将函数f作用在(2)中的结果上,得到神经元的输出。

但是对于神经网络这种包含大量的神经元而言,如何可以更加方便、代码中更简洁地去实现呢?答案是使用矩阵,线性代数搞忘记的同学也不要紧,这里仅仅是利用了“矩阵相乘”和“矩阵相加”的规则。

(1)矩阵相加

矩阵相加要求两个矩阵维度相同,矩阵中对应的数字直接相加即可,生成一个新的矩阵,维度跟之前一样:

(2)矩阵相乘

矩阵相乘要求第一个矩阵包含的列数和第二个矩阵包含的行数相同,M*N的矩阵乘以N*T的矩阵,得到M*T的一个新矩阵:

第一个矩阵A的第一行每个元素与第二个矩阵B的第一列各个元素相乘然后加起来,作为结果矩阵C中的第一行第一列,第一个矩阵A的第一行每个元素与第二个矩阵B的第二列各个元素相乘然后加起来,作为结果矩阵C中的第一行第二列,以此类推。上图中3*3的矩阵乘以3*2的矩阵,得到一个3*2的新矩阵。如果将上图7中A矩阵换成神经网络中的参数W(W11,W12,W22…),将B矩阵换成输入X特征(X1, X2, X3…),那么全连接神经网络中每一层(可以包含多个神经元)的计算过程可以用矩阵表示成:

如上图,使用矩阵我们可以批量操作。对于图4中第一层(Layer1)所有神经元的计算过程,可以通过图8一次性计算完成。图中W矩阵先和X矩阵相乘,再加上偏移值B矩阵,得到一个中间结果(也是一个矩阵),然后再将中间结果传给函数f,输出另外一个新矩阵Y,那么这个Y就是神经网络第一层Layer1的输出,它会作为下一层Layer2的输入,后面以此类推。注意,函数f接受一个矩阵为参数,并作用于矩阵中每个元素,返回一个维度一样的新矩阵,后面会提到。可以看到,之前需要计算4次f(w*x+b),现在只需要一次就可以了。

通过前面的介绍,可以得知,神经网络的训练过程就是找到最合适的W矩阵(多个)和最合适的b矩阵(多个)使得神经网络的输出与真实值(标签)最接近,这个过程也叫做模型训练或者调参(当然模型训练远不止这样,还有其他诸如超参数的确定)。

非线性变换

即使输入是高维向量,经过简单的W*X+b这样处理之后,输出和输入仍然呈线性关系。但是现实场景中大部分待解决的问题都不是线性模型,因此我们需要在输入和输出之间增加一个非线性变换,也就是前面多次提到的f函数(又称为激活函数)。由于各种原因(这里涉及到神经网络具体的训练过程,反向传播计算权重值,暂不过多解释),常见可用的激活函数并不多,这里举两个函数为例:

(1)Sigmoid函数

Sigmoid函数能将任意实数映射到(0, 1)之间,具体函数图像如下:

上图中Sigmoid函数将任意输入映射到(0, 1)之间的值,因此Sigmoid函数又经常被称为逻辑函数,常用于二分类预测问题,假设有两个分类A和B,对于任何输入特征X,Sigmoid返回值越趋近于1,那么预测分类为A,反之则为B。

(2)ReLu函数

ReLu函数很简单,返回值为max(x, 0),具体函数图像为:

上图中ReLu函数将任意输入的负数转换为0,其他输入原样输出。ReLu函数是目前深度学习中应用最多的激活函数,具体原因这里不做解释。这里需要说一下,深度学习/神经网络中有些东西并没有非常充足的理论依据,完全靠前人经验总结而来,比如这里的ReLu函数看似简单为什么在大部分场合下效果最好,或者神经网络训练中神经元到底如何组织准确性最高等等问题。

神经网络解决分类问题

经过前面的介绍不难得出,神经网络可以解决复杂映射关系的“分类”问题。将特征输入到神经网络,经过一系列计算得到输出。下图举一个形象的例子来说明神经网络如何解决分类问题:

上图显示一个和全连接神经网络同样结构的管道网状结构,从上到下有多个阀门可以调节控制液体走向(图中①),经过事先多次样本液体训练(使用不同品牌、不同酒精度、不同子型号的白酒),我们将阀门调节到最佳状态。随后将一杯白酒从最顶部倒入网状结构,最后经过管道所有液体会分别流进三个玻璃杯中(图中③)。如果我们将一杯五粮液倒入管道,理论情况所有的液体应该完全流进第一个玻璃杯中(图中左侧),但是实际上由于神经网络具备泛化能力,对于任何输入(包括训练样本),大部分时候不会跟正确结果100%一致,最终只会保证第一个玻璃杯中的液体最多(比如占85%),其余两个玻璃杯同样存在少量液体(图中右侧)。

那么现在有个问题,神经网络最后输出的是数值(或多维向量,向量包含具体数值),结果是如何体现“分类”的概念呢?本文最开始讲到过,分类问题最后都是通过概率来体现,某个分类的概率最高,那么就属于该分类,下图显示如何将数值转换成概率:

如上图所示,对于2分类问题,我们通常使用前面提到的Sigmoid函数将其转换成(0,1)之间的概率值,然后再根据概率值划分类别。对于N分类(N也可以为2),我们要使用另外一个函数Softmax,该函数接受一个向量作为参数,返回一个新向量,维度跟输入一致,新向量的每个值均分布在在(0, 1)之前,并且所有概率之和为1。注意该函数作用在整个向量上,向量中的每个值之间相互有影响,感兴趣的同学上网查一下公式。

图像分类任务

图像分类又称为图像识别,给定一张图,要求输出图中包含的目标类型,比如我们常见的“微软识花”、“识别猫还是狗”等等,这是计算机视觉中最典型的“分类”问题。图像分类是其他诸如“目标检测”、“目标分割”的基础。

(1)图像的定义

数字图像本质上是一个多维矩阵,常见的RGB图像可以看作是3个二维矩阵,矩阵中每个值表示对应颜色通道上的值(0~255),还有其他比如灰度图,可以看作是是1个二维矩阵,矩阵中每个值表示颜色的像素值(0~255)。

如上图所示,一张RGB全彩数字图片大小为180*200,对应3个矩阵,大小都是180*200,矩阵中的数值范围都在0~255。对于单通道灰度图而言,对应1个矩阵,大小也是180*200:

(2)使用全连接神经网络做图像分类

前面已经讲到如何使用全连接神经网络解决“分类”的问题,图像分类同样属于分类问题,因此也可以使用神经网络的方式解决,唯一的区别是前面提到的都是数值特征输入[x1, x2, x3, …],那么对于图像而言,该将什么输入给神经网络呢?答案是图像矩阵,图像矩阵中包含数值,将一个M*N的二维矩阵展开后,得到一个M*N维向量,将该向量输入神经网络,经过神经网络计算,输出各个分类概率。下面以“手写数字图像识别”为例,介绍全连接神经网络如何做图像分类。手写数字图像识别是深度学习中的一个HelloWorld级的任务,大部分教程均以此为例子讲解图像识别,下图为手写数字图片:

上图显示4张手写数字图片,分别为“5”、“0”、“4”、“1”,每张图片大小为28*28,即长宽都为28像素,图片都是灰度图像,也就是说每张图片对应1个28*28维矩阵,将该矩阵展开得到一个28*28维向量,直接输入到全连接神经网络中。从0到9一共10个分类,因此神经网络的输出是一个10维向量。

如上图所示,原始输入图片大小为28*28,将其展开成[784*1]的特征X传入神经网络。神经网络一共包含两层,第一层W矩阵大小为[1000*784],W*X之后得到大小为[1000*1]的输出,该输出作为第二层的输入X,第二层W矩阵大小为[10*1000],W*X之后得到大小为[10*1]的输出,该输出(经过Softmax作用后)即为数字0~9的概率。

注意上面定义的神经网络结构中,只包含两层(图中蓝色和绿色,黄色部分不算),第一层的W矩阵尺寸为[1000*784],这里的1000是随意设定的,可以是500甚至2000,它和神经元数量保持一致。第二层的W矩阵尺寸为[10*1000],这里的1000跟前面一样,这里的10是分类数,因为一共10个分类,所以为10,如果100分类,这里是100。神经网络的层数和每层包含的神经元个数都可以调整,这个过程就是我们常说的“修改网络结构”。

通过上面的方式做手写数字图片识别的准确性可能不高(我没有试验过),即使已经很高了它也不是一种非常好的方式,这种方式也许对于手写数字图片识别的任务很有效,但是对于其他图片比如猫、狗识别仍然很有效吗?答案是否定的,原因很简单:直接将整张图片的数据完全输入到神经网络中,包含的特征太复杂,或者噪音太多,这种现象可能在手写数字这种简单的图片中有效,一旦换成复杂的图片后可能就不行了。那么针对一般图像分类的任务,在将数据传到神经网络进行分类之前,我们还需要做什么呢?

(3)图像特征

图像特征在计算机视觉中是一个非常非常重要的概念,它在一定程度上可以当作图片的特定标识,每张图片都包含一些人眼看不到的特征。关于图像特征的介绍,大家可以参考我之前的一篇博客:https://www.cnblogs.com/xiaozhi_5638/p/11512260.html

在使用神经网络对图片进行分类之前,我们需要先提取图像特征,然后再将提取到的特征输入到全连接神经网络中进行分类,因此解决图像分类问题的正确神经网络结构应该是这样的:

如上图所示,在全连接神经网络之前增加了一个模块,该模块也是神经网络的一部分,同样由许许多多的神经元组成,但是可能不再是全连接这种结构了,它可以自动提取图片特征,然后将特征输入到后面的全连接网络进行分类,我们通常把这里的全连接网络称为“分类器”(是不是似曾相识?)。这样一来,全连接网络的输入特征大小不再是[784*1]了(图中黄色部分),而应该根据前面的输出来定。图中这种由全连接神经网络(分类器)和特征提取部分组合起来的神经网络有一个专有名词,叫“卷积神经网络”,之所以叫“卷积”,因为在提取特征的时候使用了卷积操作,具体后面介绍。

卷积神经网络

卷积神经网络中包含一个特征提取的结构,该结构主要负责对原始输入数据(比如图像,注意还可以是其他东西)进行特征提取、抽象化、降维等操作,它主要包括以下几个内容:

(1)卷积层

卷积层主要负责特征提取,它使用一个卷积核(一个小型矩阵)以从左到右、从上到下的顺序依次作用于原始输入矩阵,然后生成一个(或多个)新矩阵,这些新矩阵我们称之为feature maps。具体操作过程如下图:

如上图所示,图中绿色部分为原始输入矩阵,黄色矩阵为卷积核(一个3*3的矩阵),经过卷积操作后生成一个新的矩阵(粉色),该矩阵称为feature map。卷积核可以有多个,每个卷积核不同,同一个输入矩阵经过不同的卷积核处理之后会得到不同的feature map。因此在卷积层中,存在多个卷积核处理之后就会生成多个feature maps,这些feature map各不相同,每个都代表一定的特征。

如果原始输入矩阵是一张图片,经过卷积核处理之后,生成的多个feature maps虽然仍然是矩阵的形式,但是不能再把它们当作图片来对待。下图显示一张图片经过两个不同的卷积核处理之后生成的两个feature maps,我们用工具将这两个feature maps以图片的形式显示出来:

如上图所示,一张原始图片经过一次卷积处理之后,生成的feature map以图片的方式显示出来之后似乎还是可以人眼识别出来。但是,如果经过多次卷积处理之后,那么最终的feature map就无法人眼识别了。上图还可以看出,不同的卷积核处理同一张输入图片后,生成的feature map之间有差别。

这里再次强调,虽然经过卷积操作得到的feature maps仍然可以以图片的形式显示出来,但是它不在是我们通常理解中的“图片”了。虽然人眼看不再有任何意义,但是对于计算机来讲,意义非常重大。卷积层可以存在多个,一个卷积层后面可以紧跟另外一个卷积层,前一层的输出是下一层的输入。卷积层中的一些参数,比如卷积核矩阵中的具体数值,都需要通过训练得到,这个道理跟前面提到的W和b参数一样,也是需要通过训练去拟合。

(2)非线性变换(激活函数)

和前面讲全连接神经网络一样,经过卷积层处理之后生成的feature maps仍然需要进行非线性转换,这里的方式跟前面一样,使用常见的激活函数,比如ReLu函数作用在feature map上的效果如下图:

如上图,feature map经过激活函数处理之后,得到另外一个矩阵,我们称之为 Rectified feature map。根据前面介绍ReLu的内容,我们可以得知,该激活函数(max(0, x))将原feature map矩阵中的所有负数全部变成了0。

(3)池化层

只有卷积操作和激活处理还是不够,因为到目前为止,(Rectified) feature maps包含的特征数据还是太大,为了让模型具备一定的泛化能力,我们需要对feature maps进行降维,这个过程称之为池化:

如上图,池化层在原始feature maps上进行操作,还是按照“从左往右从上到下”的顺序,选择一个子矩阵(图中圆圈部分2*2,类似前面的卷积核),选取该子矩阵范围内最大的值作为新矩阵中的值,依次处理后最后组成一个全新矩阵,这个全新矩阵尺寸比原来的小。除了取最大值外,还有取平均值和求和的做法,但是经过前人实践证明,取最大值(最大池化)效果最好。

经过池化层处理之后的feature maps仍然可以以图片的方式显示出来,还是和前面一样,人眼已经分不清是啥了,但是对于计算机来讲意义重大。

如上图所示,一张feature map经过两种方式池化,取最大值求和,分别得到不同的新矩阵,然后将新矩阵以图片的方式显示出来,可以看到差别还是非常大(虽然人眼已经分不清内容)。

通常情况下,卷积层后面不需要都紧跟一个池化层,可以经过多个卷积层之后再加一个池化层,也就是说,卷积和池化可以不按照1:1的比例进行组合。卷积神经网络中特征提取部分就是使用卷积层、激活处理、池化层等组合而成,可以根据需要修改相应网络层的数量(通常所说的“调整网络结构”)。最后一个池化层输出的结果就是我们提取得到的图像特征,比如最后一个池化层输出T个矩阵(feature maps),每个大小为M*N,那么将其展开后得到一个T*M*N维向量,那么这个向量就是图像特征。到这里应该就很清楚了,我们如果将这个特征向量传到一个“分类器”中,通过分类器就可以得到最终的分类结果,分类器可以使用前面讲到的全连接神经网络。

(4)全连接层(分类器)

其实看到这里的同学,如果前面的内容都看懂了,这块就不难了。图像特征已经得到了,直接将它输入到全连接神经网络中去,就可以得到最终分类结果。下图显示将一个手写数字图片传入卷积神经网络中的过程,先分别经过两个卷积层和两个池化层(交叉相连而成,图中忽略了激活处理等其他操作),然后将最后一个池化层的输出先展开再作为全连接网络的输入,经过两个全连接层,最终得到一个10*1的输出结果。

关于卷积神经网络的配图均来自:https://ujjwalkarn.me/2016/08/11/intuitive-explanation-convnets/

关于模型训练

一些深度学习框架会帮我们去做模型训练的具体工作,比如上面提到的w和b的确定,找出最合适的w和b尽量使预测值与真实值之间的误差最小。下面举个例子,使用tensorflow来优化 loss=4*(w-1)^2这个函数,找到最合适的w使loss最小:

如上图所示,我们学过的数学知识告诉我们,w等于1时loss最小,这个过程可以通过求导得出(导数等于0的时候)。那么使用tensorflow来帮我们确定w该是怎样呢?下面是使用tensorflow来优化该函数,确定最有w的值:

w = tf.get_variable(“w”, initializer = 3.0)
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.1)
for i in range(5):
  optimizer.minimize(lambda: 4*(w-1)*(w-1))
  print(w.numpy())
使用梯度下降优化算法去寻找最合适的w,最后的输出是:

1.4
1.0799999
1.016
1.0032
1.00064

我们可以看到,经过5次寻找,我们得到最优的w为1.00064,已经非常接近1了。这个过程其实就是深度学习框架训练模型的简单版本

注意:

  1. 本篇文章没有涉及到具体模型训练的原理,也就是求W和b矩阵的具体过程,因为该过程比较复杂而且涉及到很多数学公式,读者只需要知道:模型训练的本质就是使用大量带有标签的样本数据找到相对比较合适的W和b矩阵,之后这些矩阵参数可以作用于样本之外的数据。
  2. 深度学习很多做法缺乏实际理论依据,大部分还是靠经验,比如到底多少层合适,到底用什么激活函数效果更好,很多时候针对不同的数据集(或者问题)可能有不同的答案。
  3. 除了名字相同外,深度学习中的神经网络跟人脑神经网络工作原理没有关系,之前以为有关系,所以取了一个类似的名字,后来科学家发现好像没什么关系,因为人脑太复杂。

 

一个例子说明机器学习和深度学习的区别

(2019年文章)

深度学习现在这么火热,大部分人都会有‘那么它与机器学习有什么关系?’这样的疑问,网上比较它们的文章也比较多,如果有机器学习相关经验,或者做过类似数据分析、挖掘之类的人看完那些文章可能很容易理解,无非就是一个强调‘端到端’全自动处理,一个在特征工程上需要耗费大量时间和精力(半自动处理);一个算法更复杂、需要更多的数据和算力,能解决更复杂的问题,一个算法可解释性强,在少量数据集上就可以到达一定的效果。但是如果对于一个之前并没有多少机器学习相关背景、半路出道直接杀入深度学习领域的初学者来讲,可能那些文章太过理论。本篇文章尝试使用传统机器学习和深度学习两种不同的方法去解决同一个问题,告诉你它们之间有哪些联系。

首先需要指出的是,主流定义上机器学习包含深度学习,后者是前者的一个分支。机器学习中有不同的算法,比如线性回归、逻辑回归、SVM、决策树、神经网络等等。由于使用神经网络算法的机器学习比较特殊,所以单独命名这类机器学习为‘深度学习’(为什么叫深度,后面详细说)。因此,比较两者联系更准确的表述应该是:传统机器学习和深度学习的关系(这里的传统机器学习不包含使用神经网络算法这一类)。另外需要明确的是,在处理监督学习问题中,机器学习不管采用什么算法,解决问题最终方式都是一致的:即找出X->Y的映射关系。比如你的模型用线性回归或者神经网络算法,最后都是要从训练素材中找输入和输出之间的映射关系。

现在就以一个图片二分类的任务为例,分别使用基于神经网络的深度学习和基于逻辑回归算法的传统机器学习两种方式解决,让我们看看它们在解决问题上的区别和联系。这个例子并没有源代码,我希望用图片来说明问题。

如上图,有一堆风景照片,我们需要训练一个模型来判断给定图片是否属于绿植风景照,这是一个二分类问题,绿植风景照属于第一类,其他属于第二类。输入一张图片,模型输出图片类型。现在我们分别用深度学习和传统机器学习的方法尝试去解决该问题。这里需要明确的是,对于图片分类而言(或其他大部分跟CV有关的应用),不管是用深度学习还是传统机器学习,都是需要先得到每张图片的特征表示(特征向量),特征向量是一张图片的信息压缩表示,如果不太了解何为图像特征,可以参考这篇博客:https://www.cnblogs.com/xiaozhi_5638/p/11512260.html,里面介绍了图像特征的作用和传统图像特征提取方式。

深度学习

对于深度学习而言,这个图片二分类问题太简单了,网上深度学习入门教程一大堆,比如猫狗识别跟这个差不多。在神经网络开始,我们使用几个(卷积层-池化层)的block块,提取图片的高维特征,然后再使用几个连续的(卷积层)块提取低维特征。在神经网络末尾,我们再加一个MLP全连接网络做为特征分类器,分类器最后包含一个输出节点(使用Sigmoid激活函数),代表预测为绿植风景照的概率,概率越接近1代表它为绿植风景照的可信度越高。这个网络结构可以参考2012年将深度学习带入大众视野的AlexNet网络。

如上图,图片直接输入到模型,神经网络负责特征提取,并且对特征进行分类,最后输出概率值。我们可以看到,对于深度学习方式而言,我们在预测一张图的分类时,只需要将图片传给神经网络(可能需要事先调整一下图片尺寸),然后在神经网络的输出端就可以直接得到它所属分类的概率值。这种全自动、无需人工干预的工作方式我们称之为“端到端”(End-To-End)的方式。我们可以将上述网络结构(like-alexnet)使用python代码构建出来,然后图像化显示:

如上图所示,神经网络在处理该图片分类任务时,从开始到结束一条龙服务。神经网络接收一张214*214大小的3通道彩图,矩阵形状为(214,214,3)。然后经过特征提取,得到一个256维的特征向量。最后进行特征分类,直接输出它的概率。注意上图为了简化结构,神经网络仅仅包含必要的卷积层、池化层以及全连接层,实际情况可能还需要归一化、Dropout等结构。(忽略上图Input和Output中的?号,它表示batch-size,即穿过神经网络的图片数量)需要说明的是,随着问题的复杂性加大(比如图片特征不明显,分类数量增多,数据复杂等等),我们还可以灵活调整上图中神经网络的结构,图中是最简单的直线型网络结构(Sequential结构),我们可以设计出来分支结构、循环结构、残差结构,这些都是可以用来提取更复杂的特征、解决更复杂的问题,当然这样的话训练需要的数据、算力、时间相应就会增加。关于神经网络的输入输出可以参考这篇博客:https://www.cnblogs.com/xiaozhi_5638/p/12591612.html

传统机器学习

看完深度学习解决该问题的流程,我们再来看一下如何使用传统机器学习来解决该问题。传统机器学习做不到‘端到端’的全自动处理,对于一个具体的任务需要拆分成几步去解决,第一步就是特征工程,(以图片分类任务为例)需要人为确定使用哪种特征以及特征提取方式,第二步才是对已有特征进行训练,得到一个特征分类模型。这里有两个问题,一是人为确定使用哪种特征,需要专业人士判断;而是知道要使用什么特征后,如何去提取?相比深度学习而言,传统机器学习可以总结为‘半自动’模式:

如上图所示,传统机器学习在解决当前具体问题时,需要人工确认使用什么特征,以及提取该特征的方法,最后才能用得到的特征去训练机器学习模型去做分类。那么这里有个比较重要:选择什么特征更有利于问题的解决呢?既要考虑特征对原数据的代表性,又要考虑特征提取的可行性。具体到当前图片二分类任务时,我们可以看到数据集中,绿植风景照大部分都是绿色,和其他图片在像素分布上有很大差异,因此我们可以选取‘颜色分布’来做为解决本次任务的图像特征,具体采用‘颜色直方图’的方式去生成每张图片的特征向量。

颜色直方图简单理解就是统计图片中每种颜色所占比例,RGB图片每个通道颜色值在0-255之间,如果我们将这个区间分成10等份(子区间)然后计算每个子区间颜色占比(和为1),那么就可以得到3个10维向量,将这3个向量合并组成一个30维的向量,那么这个30维向量就是基于颜色分布的图像特征。由于这种方式提取到的特征没有考虑颜色在图片中的位置分布,因此通常做法是,先将一张图切成若干等份,然后分别计算单个图片区域的特征向量,最后将所有图片区域的特征向量拼接起来得到最终的图像特征。如果将图片切成5份,那么最终得到的特征向量维度为:5*3*10=150,这个特征向量从一定程度上代表了颜色位置分布。

如上图所示,利用颜色直方图可以为每张图片提取到一个150维的特征向量,后面我们再用这些特征向量训练机器学习模型,由于是一个二分类问题,我们直接选用‘逻辑回归’算法即可。需要明确的是,一些常见的图像特征点提取方法比如SIFT、SURF等等在这里是无效的,因为这些方式提取得到的特征向量更侧重描述原图像中像素之间的局部联系,很显然对于我们这个图片二分类任务而言,根据颜色分布提取到的特征更适合解决本问题。这也同时说明,在传统机器学习中的特征提取环节非常重要,特征工程也是制约传统机器学习发展的一大瓶颈。

现在总结一下

深度学习在解决问题的时候采用‘端到端’的全自动模式,中间无需人为干预,能够自动从原有数据中提取到有利于解决问题的特征,该部分原理相对来讲‘可解释性弱’。同时,神经网络的结构多变,可以根据问题的复杂程度灵活调整网络结构,更复杂的网络结构需要更多更丰富的训练数据去拟合参数,相对应对算力的要求也高一些。而对于传统机器学习来讲,一个很重要的工作就是特征工程,我们必须人工筛选(挑选)什么特征有利于问题的解决,比如本篇文章中的例子,像素分布就是一个很好的特征,同时我们还需要人工去提取这些特征,这部分原理相对来讲‘可解释性更强’。对于特征工程这块工作而言,它对人工专业性要求较高,因为对于稍微复杂的问题,很难识别出数据集的哪些特征有利于解决问题,尤其像图片、语音、文本等等非结构化数据,这个也是制约传统机器学习发展的瓶颈之一。不管怎样,其实深度学习和传统机器学习解决问题的思路基本是一致的,我们可以看到本文中两种解决问题的过程中都会生成一个特征向量,一个256维,一个150维,最后根据特征向量分类。有问题的朋友欢迎留言讨论。

基于目标检测的多目标跟踪

与多目标跟踪(Multiple Object Tracking简称MOT)对应的是单目标跟踪(Single Object Tracking简称SOT),按照字面意思来理解,前者是对连续视频画面中多个目标进行跟踪,后者是对连续视频画面中单个目标进行跟踪。由于大部分应用场景都涉及到多个目标的跟踪,因此多目标跟踪也是目前大家主要研究内容,本文也主要介绍多目标跟踪。跟踪的本质是关联视频前后帧中的同一物体(目标),并赋予唯一TrackID。

随着深度学习的兴起,目标检测的准确性越来越高,常见的yolo系列从V1到现在的V8,mAP一个比一个高,因此基于深度学习的目标检测算法实际工程落地也越来越广泛,基于目标检测的跟踪我们称为Tracking By Detecting,目标检测算法的输出就是这种跟踪算法的输入,比如left, top,width,right坐标值。这种Tracking By Detecting的跟踪算法是大家讲得比较多、工业界用得也比较广的跟踪算法,我觉得主要还是归功于目标检测的成熟度越来越高。下面这张图描述了Tracking By Detecting的跟踪算法流程:

由上图可以看出,这种跟踪算法要求有一种检测算法配合起来使用,可想而知,前面检测算法的稳定性会严重影响后面跟踪算法的效果。图中实线圆形代表上一帧检测到的目标,虚线圆形代表当前帧检测到的目标,如何将前后帧目标正确关联起来就是这类跟踪算法需要解决的问题。目标跟踪是目标检测的后续补充,它是某些视频结构化应用中的必备环节,比如一些行为分析的应用系统中都需要先对检测出来的目标进行跟踪,然后再对跟踪到的轨迹进行分析。

目标关联

文章开头提到过,目标跟踪的本质是关联视频前后帧中的同一物体(目标),第T帧中有M个检测目标,第T+1帧中有N个检测目标,将前一帧中M个目标和后一帧中N个目标一一关联起来,并赋予唯一标识TrackID,这个过程就是Tracking By Detecting跟踪算法的宏观流程。

上图描述目标关联的具体流程,在实际目标关联过程中,我们需要考虑的有:

  1. 如何处理中途出现的新目标
  2. 如何处理中途消失的目标
  3. 正确目标关联

理想情况下,同一个物体(目标)在视频画面中从出现到消失,跟踪算法应该能赋予它唯一一个标识(TrackID),不管目标是否被遮挡、目标是否发生严重形变、是否和其他目标相距太近(相互干扰),只要这个目标被正确检测出来,跟踪算法都应该能够正确关联上。但实际上,物体遮挡是跟踪算法最难解决的难题之一,物体被频繁遮挡是TrackID变化的主要原因。原因很简单,物体被遮挡后(或其他原因),检测算法检测不到,跟踪算法无法连续关联到每帧的数据,等该物体再出现时,物体在画面中的位置、物体的外观形状与消失之前相比都发生了很大变化,而跟踪算法恰恰主要是根据物体的位置、外观来进行数据关联的。下面主要介绍目标跟踪中两种方式,一种容易实现、速度快,算法纯粹基于目标在画面中的位置来进行数据关联;另一种相对复杂,速度慢,算法需要提取前后帧中每个目标的图像特征(features),然后根据特征匹配去做数据关联。

基于坐标的目标关联

基于坐标(目标中心点+长宽)的目标关联是相对简单的一种目标跟踪方式,算法认为前后帧中挨得近的物体为同一个目标,因为物体移动是平滑缓慢的,具体可以通过IOU(交并比,前后两帧中目标检测方框的重叠程度)来计算,这种算法速度快、实现容易,在前面检测算法相对稳定的前提下,这种跟踪方式能够取得还不错的效果,由于速度快,这种方式一般可以用于对实时性(realtime)要求比较高的场合。缺点也很明显,因为它仅仅是以目标的坐标(检测算法的输出)为依据进行跟踪的,所以受检测算法影响非常大,如果检测算法不稳定,对于一个视频帧序列中的目标,检测算法经常漏检,那么通过这种方式去跟踪效果就非常差。另外如果场景比较复杂,目标比较密集,这种跟踪方式的效果也比不会太好,因为目标密集,相邻目标的坐标(left、top、width、height)重合度比较高,这给基于坐标的目标关联带来困难。

如上图,在T+1帧中,我们根据目标前面若干帧的坐标预测它在本帧中的坐标(预测坐标),然后再将该预测坐标与本帧实际检测的目标坐标进行数据关联。之所以需要先进行预测再关联,是因为为了减少关联过程的误差,常见预测算法可以使用卡尔曼滤波,根据目标前面若干坐标值预测下一坐标值,并且不断地进行自我修正,卡尔曼滤波算法网上有开源代码。IOU(交并比)是衡量两个矩形方框的重叠程度,IOU值越大代表矩形框重叠面积越大,它是目标检测中常见的概念。在这里,我们认为IOU越大,两个目标为同一物体的可能性越大。

基于特征的目标关联

纯粹基于坐标的目标跟踪算法有一定的局限性,单靠目标坐标去关联前后帧的同一目标在有些场合下效果比较差。在此基础上,有人提出结合目标外观特征匹配做目标关联,换句话说,在做目标关联的时候,除了依赖目标坐标外,还考虑目标的外观特征,道理很简单:

“前后两帧中挨得近的物体外观长得比较像的物体为同一目标。”

这样的跟踪方式准确率更高,但是同时出现了一个问题:如何判断两个物体外观长得像?在计算机视觉中,有一个专门的研究领域叫Target Re-Identification(目标重识别),先通过对两个待比较目标进行特征编码(特征提取),然后再根据两个特征的相似度,来判断这两个目标是否为同一个物体,两个特征越相似代表两个目标为同一个物体的可能性越大。Target Re-Identification常用在图像搜索、轨迹生成(跨摄像机目标重识别)以及今天这里要说的目标跟踪。

熟悉深度学习的童鞋应该很清楚,神经网络的主要作用就是对原始输入数据进行特征编码,尤其在计算机视觉中,卷积神经网络主要用于图像的特征提取(Feature Extraction),从二维图像中提取高维特征,这些特征是对原始输入图像的一种抽象表示,因此训练神经网络的过程也可以称为Representation Learning。相同或者相似的输入图片,神经网络提取到的特征应该也是相同或者相似的。我们只要计算两个特征的相似度,就可以判断原始输入图像的相似性。

那么如何计算两个图像特征的相似度呢?图像特征的数学表示是一串数字,组合起来就是一个Vector向量,二维向量可以看成是平面坐标系中的点,三维向量可以看成立体空间中的点,依次类推,因此图像特征也被称作为“特征向量”。有很多度量标准来衡量两个特征向量的相似程度,最常见的是“欧式距离”,即计算两点之间的直线距离,二维三维空间中两点之间的直线距离我们都非常熟悉,更高维空间中两点距离计算原理跟二三维空间保持一致。另外除了“欧式距离”之外,还有一种常见距离度量标准叫“余弦距离”,计算两个向量(点到中心原点的射线)之间的夹角,夹角越小,代表两个向量越相似。

外观特征提取是一个耗时过程,因此对实时性要求比较高或者需要同时处理视频路数比较多的场合可能不太适合。但是这种基于外观特征的跟踪方式效果相对更好,对遮挡、目标密集等问题鲁棒性更好,因为目标遮挡再出现后,只要特征提取网络训练得够好,目标尺寸、角度变化对它的外观特征影响不大,因此关联准确性也更高。类似的,这个也适用于目标密集场景。外观特征提取需要定义一个合适的神经网络结构,采用相关素材去训练这个网络,网上有很多公开的Person-ReId数据集可以用来训练行人跟踪的特征提取网络,类似的,还有一些Vehicle-ReId数据集可以用来训练车辆跟踪的特征提取网络,关于这块的内容,也是一个值得深入研究的领域,由于本篇文章主要介绍目标跟踪,所以暂不展开讲述了。

本文开头第一张图是基于坐标的跟踪方式效果图,上图是基于外观特征的跟踪方式效果图,我们可以看到,第一张图中目标被遮挡再出现后,目标ID发生了变化,而第二张图中大部分时候目标ID都比较稳定,同样,人群密集场合中,同一目标ID发生改变的几率也小。实际上,同一目标ID是否发生变化是衡量跟踪算法好坏的一个重要指标,叫IDSwitch,同一目标ID变化次数越少,可以一定程度代表算法跟踪效果越好。

参考论文

1 Simple Online Real-time Tracking https://arxiv.org/pdf/1602.00763.pdf
2 Simple Online Real-time Tracking with a deep association metric https://arxiv.org/pdf/1703.07402.pdf
3 Multiple Object Tracking: A Literature Review https://arxiv.org/pdf/1409.7618.pdf

目标检测框不稳定不连续?

做过基于目标检测算法应用的人可能会碰到这样一个问题:算法在检测连续视频帧时,视频中同一个目标的检测框经常出现抖动、有时候目标还出现若干帧检测不到的情况(漏检),哪怕整个视频画面保持不变,目标就停在原地不动,照样会出现这个问题。检测算法出现这个问题会对后面整个流程产生非常大的负面影响,因为大部分应用会基于前面的检测输出结果去做一些具体的业务逻辑,比如测速、轨迹分析。如果检测输出框不稳定、不连续,无疑会对整个应用的准确性造成非常大的影响。我将这种问题分为3类:

  1. 目标检测框的宽高不稳定。比如一辆车停在原地不动,正常期望的是,不管在哪帧进行检测,车辆检测输出框的宽高应该固定不变,因为车没挪动。或者说目标从远到近,检测框的宽高应该平滑变化,而不是看上去一闪一闪的感觉
  2. 同理,目标检测框的位置不稳定。同样还是一辆车停在原地,车辆检测输出框的位置(框中心点坐标)应该固定不变。或者从远到近,检测框的中心点应该平滑移动,而不是看上去一抖一抖
  3. 目标停在原地不动,视频画面保持不变,该目标应该连续被检测到,而不会出现若干帧漏检

下面这个动图很好的说明了上面提到的问题(来源youtube见末尾链接):

在搞清楚如何解决(或者说优化)这个问题之前,我们要先搞清楚为什么会出现这个现象?目前所有基于CNN的目标检测算法,输入全部都是像素值,无论做过什么预处理,原始图像的像素组成肯定会影响最终的检测结果。而我们人眼认为的“视频画面没有变化”只是一个错觉,由于图像采集设备各种噪音的影响(可能还有光线亮度变化、解码噪音等等),看似没有变化的视频画面前后两帧其实从微观像素组成上已经发生了很大变化,这些变化对检测算法来讲无疑是巨大的。如果用同一张图片组成一个序列,那么这个序列中的目标检测框肯定非常稳定(实际上可以做到完全一模一样),原因很简单,前后帧的画面一模一样、微观像素组成也完全一样,模型的输出当然也一模一样了(不考虑解码噪音)。

好了,现在清楚了问题出现的原因,就要找办法去解决。可惜的是,目前并没有办法彻底解决这个检测框不稳定/不连续的问题,只有优化该问题的办法。下面直接列出来:

  1. 既然微观上的像素变化对检测结果影响巨大,那么我们尽量充分收集各种场景(比如各种亮度)的素材去训练模型,最终提升模型的泛化能力。但是这个方法局限性太大了,有些图像噪音你无法预估;
  2. 对训练素材质量进行优化,尤其素材的标注质量。检查素材gt标注是否规范(比如是否刚好贴近目标轮廓、是否有漏标目标)。原因很简单,gt框子如果本身都很随便,有时候大有时候小,对最终检测框的稳定性影响肯定很大。如果gt漏标严重,那么最终肯定影响模型对目标的检测判断;
  3. 无法在检测环节解决的,那么我们就在检测之后去处理。这里要提到的就是目标跟踪算法,对目标跟踪不熟悉的童鞋请看前面的旧文章。目标跟踪主要是将视频前后帧中的同一目标进行关联,并且赋予唯一ID。在跟踪的逻辑中,我们可以做一些轨迹平滑处理、轨迹缓存处理,来弥补前面检测环节的不足。下面这张图右边为检测算法的原始输出,左边为跟踪结果:

上面3条,我已经在实际工程中证实过2和3有效。下面重点说第3条,也就是目标跟踪对检测输出结果的优化。下面先看一个检测算法的原始输出效果,它只有检测框:

我们可以看到检测框抖动严重,而且画面中的行人检测断断续续,算法无法逐帧都检测到。这种情况下,我们无法稳定捕捉到行人移动这个动作,所以对应用系统后续的业务逻辑带来巨大的负面影响。现在我在检测输出的基础上增加目标跟踪的逻辑之后:

可以看到,不仅行人目标检测框连续了(ID固定不变),周边车辆运动也更加平滑,对后续车辆测速逻辑有非常大的帮助。目标检测框稳定/连续到底对后续业务逻辑有什么影响?举个例子,如果应用系统在检测到行人目标的时候发出告警的阈值是25帧,也就是系统至少要连续检测到行人目标一秒钟才认为应该报警。那么仅仅按照目标检测的原始输出结果,该行人目标检测无法触发连续25帧的阈值。

具体的目标跟踪算法可以参考SORT和DeepSort两种,这俩是非常典型有代表性的跟踪方式。前者纯粹基于目标物理位置进行跟踪(目标检测框的大小和位置),后者除了考虑目标物理位置之外,还考虑了目标外观特征(Appearance Feature),通过外观特征进一步进行匹配关联,运行速度要比前者慢很多,但是准确性高。在这两种跟踪方式中,都用到了卡尔曼滤波,一个作用就是用来做前后帧目标轨迹预测关联,另一个就是对目标检测框的平滑处理,让目标检测框变化不要那么突兀。至于若干帧漏检的问题,这些跟踪算法中都有缓存机制,当目标偶尔漏检时,之前赋予的ID并不会马上清除,而是保留一段时间(帧),后面如果能匹配到新的检测结果,那么目标重新被激活(ID不变)。对跟踪原理不清楚的童鞋请参考之前的旧文章。

参考资料

1、https://arxiv.org/abs/1602.00763

2、http://cs230.stanford.edu/projects_winter_2019/reports/15812427.pdf

3、https://www.youtube.com/watch?v=vGiHciI-NC0