什么是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

图像Resize方式对深度学习模型效果的影响

在基于卷积神经网络的应用过程中,图像Resize是必不可少的一个步骤。通常原始图像尺寸比较大,比如常见监控摄像机出来的是1080P高清或者720P准高清画面,而网络模型输入一般没有这么大,像Yolo系列目标检测的网络模型输入大小一般为608*608/512*512 等等。那么如何将大尺寸图像输入到网络模型呢?很容易想到的一个方法就是对原始图像进行Resize,将1920*1080的原始图像Resize到网络模型输入尺寸,比如608*608。在压缩图像的过程中,有以下两个问题需要重点讨论:

1、图像Resize前后,是否应该保持宽高比例一致?图像内容变形是否对模型效果有影响

2、图像Resize过程,应该选择什么样的插值方式?

对于第一个问题,其实两种方式均可,前提是要保证模型训练和模型推理时的操作方式一致。也就是说,如果在网络模型训练时,所有的训练素材都是直接拉伸到网路的输入尺寸(不保持宽高比例),那么模型推理时也应该如此,反之亦然。其中保持宽高比例的做法一般是用增加padding的方式,然后用固定颜色填充,保证图像画面中部的内容不变形。下图说明两种方式的差异:

其实对于网络模型来讲,图像是否变形其实不太重要。如果在训练的时候,模型认为一个变形的动物是猫,那么经过大量数据拟合后,在推理阶段,它同样会正确识别出变形的目标。当然根据相关资料显示,通常一般推荐使用直接拉伸的方式去做图像Resize,原因是增加padding填充后会对网络带来一定噪音,影响模型准确性,具体影响有多大我目前没有具体数据证明。这里需要指出的是,一些算法应用框架对细节封装得太好,对原始图像进行Resize的过程被隐藏起来,具体Resize的方式也不得而知。如果你发现模型集成后的准确性下降严重,这时候就需要检查一下框架对图像Resize的方式跟我们模型训练时是否一致。

对于第二个问题,图像Resize过程应该选择什么插值方式?如果对插值不太了解的朋友可以上网搜索一下。这里简单介绍一下图像插值的含义:我们在对图像进行上下采样时(缩放),有时候要在原有像素基础上删除一些像素值(缩小),有时候要在原有像素基础上增加一些像素值(放大),增加/删除像素的方式叫图像插值算法。对OpenCV比较熟悉的朋友可能知道它里面的Resize函数其实有一个‘插值模式’的参数,这个参数有一个默认值:INTER_LINER线性插值。它是一种插值方式,如果你在调用Resize函数时没有修改该参数值,那么该函数就以“线性插值”的方式进行图像缩放。除此之外,还有其他的一些插值方式,每种插值算法的区别请具体参考OpenCV文档。

通过上面的介绍,图像在进行Resize操作时,本质上是改变数字图像矩阵大小和矩阵内容,Resize时采用不同的插值方式最终会得到不同的结果(这里说的结果是指微观上像素矩阵,可能肉眼查看画面差别不大)。那么在深度学习应用过程中,我们应该采用什么样的插值方式呢?经过实际测试验证,不管用哪种方式进行插值,模型训练阶段对图像Resize的插值方式跟模型推理阶段对图像Resize的插值方式最好能保持一致,前后两个阶段不同的插值方式确实会影响最终模型的效果。

除了Resize插值方式应该保持一致之外,Resize的次数最好也能保持统一,如果在模型训练阶段,我们将原始图像素材从1000*800缩放到400*400,然后输入网络进行训练,那么我们在模型推理阶段,同样应该将原始图像以相同的插值方式一次性缩放到400*400,然后输入网络进行推理。之所以强调一次性缩放,因为有些算法应用框架在做图像预处理时隐藏了图像缩放的细节,有可能不止一次缩放操作,比如先将原图缩放到800*800,然后再进行二次缩放,最终变成400*400,虽然两次用到的插值方式都跟模型训练阶段保持一致,但是由于进行了两次操作,还是会影响最终推理效果。

最后总结一下图像缩放方式对模型效果的影响:在模型训练和模型推理阶段,应保持相同的图像预处理方式,这样才能充分发挥模型的推理效果。原因很简单,模型训练的过程就是寻找数据集规律的过程,如果训练用到的和实际推理的数据规律不一样,必然会影响模型效果。当然,本文虽然讨论图像缩放的不同方式对模型效果有影响,但是由于深度学习是一个基于大量数据统计的过程,在有大量数据拟合的情况下,这种影响可能相对来讲并不大,如果你非常在意(或者实际观察发现影响非常大),那么本文讲到的问题可能对你有帮助。

从ReID到AI换脸:图像特征

最近在做视频搜索的技术调研,已经初步有了一些成果输出,算法准确性还可以接受,基本达到了调研的预期。现将该技术调研过程中涉及到的内容总结一篇文章分享出来,内容比较多,初看起来可能关系不大,但是如果接触面稍微广一些,就会发现其实原理都是差不多的。

先描述一下我要解决的问题:上传任意一个车辆截图,需要从海量的监控视频中(高速监控)找到该车辆目标历史经过点位的历史视频录像。这个问题本质上其实就是图像检索或者叫Object-ReId问题,唯一不同的是,找到车辆目标后需要定位到视频录像,后者其实很简单,只需要事先建立好图片和录像片段之间的索引关系即可,跟我们今天要讨论的内容关系不大。(本文图片点击查看更清楚)

图像检索的本质

首先要清楚的是,机器是无法直接理解图像或者声音这种多媒体数据的,甚至也包括一些复杂的结构化数据(比如数据库中的表数据)。传统机器学习中一个常见的概念是“特征工程”,说的是从原始的、复杂的数据中提取出有一定代表意义的特征数据(简单表示,比如用多维向量),这些特征数据相比原数据要简单得多!然后再用算法去分析、学习这些特征数据,得出规律。基于神经网络的深度学习中已经慢慢弱化了“特征工程”这一概念,因为深度学习主流的方式基本都是端到端的流程,输入直接产生输出,特征提取的过程已经在神经网络中的某个部分帮你做完了。

那么现在图片检索的问题,其实已经被转变成“特征数据检索的问题”了。原来需要进行图像比对,现在只需要进行特征比对,而显然机器更擅长后者。

Object-ReId/Person-ReId/Vehicle-ReId的原理

ReId技术一般用于多摄像机目标重识别的场合,目标经过多个点位被多个摄像机拍摄录像存储,输入该目标的一张截图,可以利用ReId的技术将该目标经过的点位找出来,用于后续的运行轨迹分析,该技术一般用于安防/公安领域,目标一般可以是行人(Person-ReId)和车辆(Vehicle-ReId)。ReId的核心就是前面提到的图像检索技术,从海量图片中(视频由图片组成)检索指定的图片,那么这个检索的准确性就依赖于前面提到的特征比对算法准确性了。

上图描述了Vehicle-ReId的一个完整流程,我们可以看到特征比对只是其中的一个环节,完整的流程还要包括车辆目标提取(目标检测)、特征提取、索引建立。

图像特征提取

前面已经知道了图像检索的本质其实就是特征的比对,那么这个特征应该如何提取得到呢?

传统的机器学习可能需要手工设计算法去提取特征,提取的方式有多种多样,拿图像而言,可以从颜色角度入手,提取图像的颜色特征。比如大部分人可能比较熟悉的颜色直方图,这个算法就可以用来计算图像的“像素组成”(比如各种颜色分别占比多少),像素组成确实可以在一定程度上代表原始图片。在某些数据集中,像素组成相似的原始图片也比较相似。然后拿像素组成数据去做分类/聚类,基本就可以搞定这种机器学习任务。

现在流行的深度学习已经抛弃了人工提取特征的做法,取而代之的是直接上神经网络。还是拿图像而言,直接用卷积网络无脑卷一卷,就可以得到对应的图像特征。这种方式提取到的特征是unreadable的,不像像素组成,它确实可以被人理解。卷积网络最后提取到的特征人工无法直观理解,它可能仅仅是一个高维向量,不做处理的话,你都无法在二维/三维空间中显示出来。所以很多人说深度学习(神经网络)是不可解释的,在某种程度上它确实无法被解释。

由前面的内容我们不难发现,特征提取是非常重要的一步,直接关系到后面基于特征的一切应用的准确性。特征是对原始数据的一种表达,是计算机容易识别的一种理想格式。理想情况下,特征之间的特性和规律可以直接反应原始数据之间的特性和规律。传统机器学习过程中,如何找到合适的特征提取方法是一项非常难的事情,现在主流的深度学习过程中,已经简化了该步骤。

需要注意的是,一些论文、博客、文章中对特征的称呼不尽相同,比如Features(特征)/Representation(表达或表示)/Embedding(嵌入)/Encoding(编码)等等基本都是一个意思(注意中文翻译可能不太准确)。其实从这些英文单词不难看出,不管用什么词,人们想要表达的意思大概都是差不多的,即特征是对原数据的简要表达。

上图是深度学习中利用神经网络来提取特征,原始神经网络是一个多分类网络,我们可以使用分类数据集去拟合该神经网络中的参数,待训练完毕后,去掉最上(最右)用于分类的网络层,倒数第二层即可输出128维的特征数据。基于这个128维的特征数据,我们可以做很多事情:

1、原网络做的分类任务。例子中原网络本身就是一个分类网络,对这些特征数据进行分类,推理出原输入图片的类型。看看是巩俐还是奥巴马;

2、本文的重点。特征数据比对,用于图像检索、人脸识别、Vehicle-ReId等;

3、用于无监督学习。先对一堆没有标签的图片数据集合进行特征提取,基于这些特征数据利用K-Means或DBSCAN等算法自动将这些图片分成若干类,类似Iphone相册自动分类功能(比如相同的人脸归为一类)。

总之,特征数据非常有用,是一切机器学习(深度学习)任务中的重中之重。

图像特征比对

前面已经多次提到特征比对,那么特征比对的方式有哪些呢?二维空间中的2个点,我们可以通过计算之间的直线距离来判断它们是否相似(越小越相似,为零表示完全相同。反之亦然);三维空间中的2个点,我们照样可以通过计算之间的直线距离来判断它们是否相似(越小越相似,为零表示完全相同。反之亦然)。那么对于更高维的点呢?照样可以用这种方式去做比较!

这里需要说的是,直线距离只是手段之一,还有其他距离可以计算。比如不太常见的余弦距离,它代表两个点到坐标原点线段之间的夹角余弦值,角度越小代表2点距离越近。余弦距离跟直线距离不同,一个是用角度大小衡量、一个是用线段长短衡量。

我们可以看到,直线距离(欧氏距离)关注点是2个特征的各个维度具体数值,而余弦距离关注点是2个特征的维度分布。直线距离为零,代表2个特征的各个维度数值完全相同;而余弦距离为零,代表2个特征的维度分布完全相同。(1, 1, 1)和(2, 2, 2)两个特征的直线距离不为零,因为它们各个维度的数值不同,但是它们的余弦距离为零,因为它们的维度分布是完全一样的,都是1:1:1。

举一个实际的例子,张三的语数外三科的成绩为(80, 80, 80),李四的语数外三科的成绩为(90, 90, 90),这两的直线距离不为零,李四的三科成绩明显跟张三不同。但是这两的余弦距离为零,可以理解为李四的三科平衡程度跟张三一致,都不偏科。所以不同的距离代表含义不同,直线距离可以用来衡量他们的成绩是否差不多,而余弦距离则可以用来衡量他们偏科程度是否差不多。两个距离,视角不一样。

高维特征降维和可视化

前面举例子用的是二维或者三维数据,其实特征数据大部分时候都是高维的,比如128维或1024维等等。在不做任何处理的情况下,我们无法直观看到这些高维数据之间的关系,因为它既不是二维的我们可以画到平面坐标系中、也不是三维的我们可以画在立体坐标系中。如果想要直观看到数据之间的关系,我们需要对这些特征再次进行降维处理,将之前的高维数据一一映射到二维或者三维空间。比如现在提取到了1000张图片的特征数据,每个数据都是128维,我们如果想要在二维或三维空间观察这1000个特征数据之间的关系(比如特征数据之间的紧密程度),从而判断原始图片之间的关系、或已经知道原始图片之间的关系我们需要验证提取到的特征数据是否合理。

值得高兴的是,已经有非常成熟的降维技术可以使用,比如常见的PCA和t-SNE算法,直接可以将高维数据降到二维或者三维,而依然保留原始数据的特性。通过这些手段我们可以直观看到高维特征数据在二维/三维空间中的呈现,从而观察原数据之间的关系。下图是我提取高速公路视频画面中车辆目标的特征数据,原始特征是128维,然后利用t-SNE算法进行降维处理,最后得到的二维格式数据并在二维坐标系中将原始图片一一对应绘制出来。

我们可以看到,外观相似的车辆(这些图片是随机抽取的,并没有标签数据)聚集在一起,用前面讲到的距离来说,就是越相似的图片特征距离越近。这个可视化的过程基本可以证明我前面设计的特征提取网络是合理的,这个网络用于提取其他类似车辆图片的特征也是OK的。

看到这里的朋友其实可能已经注意到,机器学习(或深度学习)的主要工作其实说白了就是一个不断对数据进行降维的过程,我们可以将原始非结构化数据诸如文字/图片/音频看成是一个维度很高(超高维)的数据格式,然后设计算法将这些超高维数据降到低维格式,再去应用。前面讲到的特征提取也算是降维的一种。

自编码器

谈到降维技术,这里我想介绍一个超级牛逼的结构,学名叫auto-encoder(翻译过来就是自编码器)。我刚开始接触这个东西的时候就感叹于它的神奇强大,因为它结构相当简单,而且理解起来并不费劲,但是起到的效果惊人。它的结构是对称的,前面半部分主要对输入(一般指图片)进行编码,其实就是特征提取,比如提取得到一个128维的特征数据。后半部分马上对该特征进行解码,还原成原来的图片。前半部分叫编码器,后半部分叫生成器。这个东西可以由两个神经网络组成,大概结构类似如下图:

如上图这种结构的神经网络训练也相当容易,你的训练数据集不需要提前标注,因为网络的输出就是网络的输入,换句话说,你可以把它当作无监督学习!有人可能就要问了,一编一解到底想要干什么呢?这样操作的主要目的是得到中间的特征数据(论文术语叫space representation),没错,用这种方式训练出来的前半部分可以当作一种特征提取器(原定义叫编码器),将它作用在其他类似图片数据上,就可以得到对应的特征数据,起到的作用跟前面介绍的其他特征提取方式差不多。

这种自编码器的一大优势是训练它的数据集合不需要标注,训练是一个无监督学习过程。它不像前面提到的那些特征提取方法,大部分都是基于监督学习的。也就是虽然我们的目的是训练一个特征提取的网络(网络输出是高维特征数据),但是往往需要提前准备带有标签的训练数据(如分类数据)。当然,除了这里提到的自编码器之外,还有其他的一些特征提取结构,也属于无监督学习的范畴,比如孪生网络、或者采用triplet loss训练时,这些都是无监督学习的例子。

AI换脸技术

这个话题其实跟今天谈到的特征数据(提取/比对)关系不是特别大,只是前面我已经提到了自编码器,知道了这个结构的前半部分能够应用于特征提取的任务,而刚才没说的是,它的后半部分(生成器)是可以用于AI换脸的,之前火爆全网的AI换脸可以采用类似技术实现。

其实AI换脸原理也非常简单,自编码器的前半部分用于人脸编码(特征数据,下同),它的后半部分基于该编码进行人脸还原(图像生成),这个过程即是我们进行网络训练的过程:一个人脸输入,不断拟合网络让它输出同一个人脸。如果我们在应用该网络结构的时候稍微改变一下:将A人脸输入到它的编码器,得到它的人脸编码后,不要使用对应的生成器去还原人脸,而是改用另外B人脸的生成器去还原人脸!那么会得到什么呢?答案是:得到一张A的脸部轮廓+B的五官细节。下图显示AI换脸的技术原理:

如上图可知,编码器输出的人脸编码在某种意义上可以看作是脸部轮廓的表示,生成器基于该轮廓进行五官细节恢复,最终得到一个合成后的人脸。下面是一个将赵本山五官换到杨澜脸部的例子:

通过AI换脸的这个例子我们可以得知,特征提取相当重要,整个流程能够正常work(或work得很好)大部分依靠中间生成的特征数据(人脸编码)。神经网络的神奇之处就在于,有些东西你无法解释,但是就是凑效。

其他常见的无监督学习

既然提到了AI换脸,索性就将本篇文章的主题扯远一些。自编码器的训练过程属于无监督学习的范畴,根据相关大神的名言:无监督学习才是真正的人工智能。确实没错,监督学习在某些场合有非常多的局限性。那么除了上面提到的自编码器训练属于无监督学习,机器学习领域还有哪些无监督学习的例子呢?

1、类似K-Means这些聚类算法,算法可以自动从给定的数据(特征数据)寻找规律,无需事先提供参考样例

2、类似t-SNE这种降维算法,算法可以自动从给定的数据(特征数据)寻找规律,无需事先提供参考样例

3、类似上面提到的自编码器,以及其他一些生成型网络,包括GAN相关技术,都属于无监督学习

4、类似采取triplet loss等技术直接操控特征数据的网络训练方式(基于特征数据计算loss),也属于无监督学习

只要在训练过程中无需事先提供参考样例(标注样本)的机器学习过程全部都可以看作是无监督学习,无监督学习跟算法并没什么直接关系,传统机器学习、现在主流基于神经网络的深度学习都可以有无监督学习方式。

好了,本篇文章到这里结束了。由于时间原因,以及查资料验证费时间,前前后后花了半个月功夫。其实主要目的是为了说明特征数据在机器学习(深度学习)领域的重要性,这个领域基本所有的东西全部围绕它展开的,所有的原始非结构化数据/结构化数据都需要先转成特征数据,再被机器学习算法(深度学习神经网络)学习。

生成型神经网络

最简单的X->Y映射

读书时代课本上的函数其实就是我们最早接触关于“映射”的概念,f(x)以“某种逻辑”将横坐标轴上的值映射到纵坐标轴。后来学习编程之后接触了函数(或者叫方法)的概念,它以“某种逻辑”将函数输入映射成输出,映射逻辑就是函数本身的实现过程。映射就是将若干输入以某种逻辑转换成若干输出,课本上的函数是这样,编程中的函数是这样,深度学习中神经网络同样是这样。

最简单的映射逻辑是线性映射,类似f(x)=w*x + b这样,它的函数图像是一条直线,其中w和b是映射过程用到的参数。高中数学中的抛物线f(x)=a*x*x + b*x +c 是一种比线性映射更复杂的映射关系,同样a、b、c是该映射过程要用到的参数。 这里不管是w还是abc这样的参数,同样存在于神经网络这种高复杂度的映射过程之中,事实上,我们完全可以将神经网络类比为我们更为熟悉的直线、抛物线结构。

与我们读书学习直线、抛物线不同的是,书本中直线、抛物线函数的参数大部分时候都是已知的,而神经网络中的参数是未知的,或者说不是最优的,我们需要使用已知的样本(输入/输出)去拟合网络,从而得出最优的参数值,然后将拟合(学习)到的参数应用到新数据中(只有输入),拟合的这个过程叫做“训练模型”或者“学习”。

神经网络的输入输出

前面说到映射过程有输入和输出,同样神经网络也有输入和输出。就计算机视觉领域相关任务来讲,有图像分类网络,有目标检测、分割等网络,也有专门用于图像特征提取的网络,这些网络的输入基本都是图片,输出要么是分类概率值,要么是跟目标有关的像素坐标值,或者是高维特征向量值,这些输出有一个共同的特点,都属于数字类输出,就像我们刚接触机器学习时预测房价的案例,算法输出是房价,也是数字类型。那么神经网络是否还有更复杂的输出格式呢?答案是有,神经网络可以输出更高维的数据结构,比如图片。虽然严格来讲,图片也是由数字组成,但是为了与前面提到的简单输出类型区分,我们可以将输出图片的这类神经网络称为“生成型网络”。顾名思义,生成型网络的特点是可以生成类似图片这种直观结果(或者类似网络输入那种更高维数据)。

生成型网络的训练方式一般比较特别,大部分都是无监督学习,也就是事先无需对样本做人工标注。事实上,了解生成型网络是我们熟悉无监督学习的一种非常好的途径。

生成型网络的实现方式常见有两种,一个是AutoEncoder,翻译成中文叫“自编码器”,基本原理就是将输入图片先编码,生成一个特征表达,然后再基于该特征重新解码生成原来的输入图片,编码和解码是两个独立的环节,训练的时候合并、使用的时候拆开再交叉组合。前几年非常火的AI换脸就是基于AutoEncoder实现的,代码非常简单,之前的一篇文章对它有非常详细的介绍。另一个是GAN,全称Generative Adversarial Networks,翻译成中文是“生成对抗型网络”,它也是由两部分组成,一个负责生成一张图片,一个负责对图片进行分类(判断真假),训练的时候一起训练,使用的时候只用前面的生成网络。GAN也是本篇文章后面介绍的重点内容。

GAN生成对抗型网络工作原理

首先要明确地是,最原始的GAN是2014年提出来的,它可以基于一个无任何标注的图片样本集,生成类似风格的全新图片,就像一个画家,看了一些示例后,可以画出类似风格的画。2014年提出来的GAN结构相对简单,也存在一些缺陷,比如生成的图片分辨率太低、网络训练很难收敛。后面有人陆陆续续提出了各种改进版本的GAN(变种),虽然各方面都有所调整,但是整体思想仍然沿用第一版,这里也是介绍最原始GAN网络的工作原理。

GAN网络主要由两子网络组成,一个学名叫Generative Network(顾名思义,生成网络),输入随机数,输出一张固定尺寸的图片(比如64*64大小),另一个学名叫Discriminative Network(顾名思义,辨别网络),输入前一个网络生成的图片以及训练样本集中的图片,输出真和假(生成的为假、样本集中的为真)。如果单独分开来看,这两没什么特别的,一个负责生成图片、一个负责判别真假。但是,GAN高妙之处就在于这两在训练过程中并不是独立的。我们先来看下GAN的结构:

上面显示的GAN网络结构其实非常简单,但是只看这张图的人可能会有一个疑问?训练样本集只作为辨别网络(Discriminative network,简称D)的输入并参与训练,它是怎样对生成网络(Generative network,简称G)起到作用的?答案在GAN的训练过程中,GAN的训练方式主要可以归纳成3个要点:

1、将G的输出作为D的输入,并以False标签来训练D网络,更新D网络的参数,让D知道什么是假的;

2、将训练样本集输入D,并以True标签来训练D网络,更新D网络的参数,让D知道什么是真的;

3、将D的参数冻结,再将D和G两个网络串起来,G的输出还是作为D的输入,但是这次以True标签来训练整个组合网络。由于D的参数已经冻结,所以整个训练过程只会更新前面G网络的参数,让G知道如何生成看起来像“真的”图片。

上面的过程1和过程2让D越来越聪明,知道什么是真的、什么是假的。过程3让G也越来越聪明,知道如何调整自己的参数,才能让生成的图片更能欺骗D。随着上面的过程不断反复,G不断的更新自己的参数,虽然输入的一直是随机数,但是输出的图片却越来越像训练样本集中的图片,到此完成了对G的训练。下面用图来说明上面的过程:

GAN(变种网络)可以解决哪些任务?

基于GAN思想的生成型网络有很多应用,比如素材生成,当你已经有一定数量的图片样本后,你可以利用GAN生成更多类似但完全不同的图片,提升样本库的丰富性。目前比较成熟的风格迁移(style transfer)也用到了GAN思想,可以将一张手机拍摄的自然风景图片转换成山水画、卡通画风格。下面是利用Pro-GAN网络生成的图公路像素材,刚开始生成的图像模糊,随着训练时间增加,生成的图像越来越清晰、并且能够保持与已有样本集一致的风格。