21/8/19 读书笔记

目录

  • 21/8/19 读书笔记
  • Code Complete 开发者测试
  • 机器学习 半监督学习

Code Complete 开发者测试

开发者测试是“白盒”测试,需要由开发者进行完成。与之相对的是独立测试,由测试人员完成。测试一般按层次分为:

  • 单元测试:对一个完整的类、子程序或者小程序进行测试。通常只涉及一个程序员或团队
  • 组件测试:将一个包、组件从系统中隔离出来进行测试。通常涉及多个程序员或团队。
  • 集成测试:多个子系统联合测试。
  • 系统测试:在软硬件系统均达到最终阶段时进行测试,考察安全、性能和资源损耗。
  • 回归测试:在系统发生修改后,重复执行之前的测试用例,以保证修改没有引入错误。

测试(testing)和调试(debugging)的概念略有不同,前者注重检查并找出错误,后者注重诊断错误是如何发生的并修复它。

测试和编码应该是平起平坐的关系,事实上测试花费的时间和精力一点不比编码少。

测试对于绝大部分程序员来说都是煎熬,消除错误的效果也比不上协同构建技术。如果要提升软件的质量,仅仅做更多的测试是没有用的,而是需要更高质量的开发。

错误的来源是多方面的,可能是编码时的笔误、程序员对设计的理解出现的偏差、团队协作出现的漏洞,因此测试的另一个重要作用在于发现软件开发以外的管理和沟通漏洞。

对于开发人员,无论集成测试或者系统测试策略如何,都应该在将同一部分同其他部分结合之前,你都需要对它进行彻底的单元测试。单独测试他们,比集成后再进行测试会简单很多。

测试的要点在于测试每一项相关需求、每一个设计上的关注点,并设立一个核查表记录下最常犯的错误。

测试先行的编程启发我们在开始写代码之前先写测试用例,这迫使我们在编写代码前思考一下需求和设计,并且更早地把需求上的问题暴露出来。保留好你的测试用例,并在编程后还能进行测试。

“肮脏测试”是让代码发生失效的测试,理论上应该让肮脏测试主导测试用例的设计。事实上,开发者倾向于用“干净测试”来验证代码功能是否完成,而“肮脏测试”过少。

结构化测试的基本思想是测试程序中的每个语句至少一次。我们针对代码中的控制流(逻辑分支控制)来设计测试用例,从而使得每一条语句都能够被执行到。这种测试顾及了控制流的所有可能情况,称为基础测试

数据流测试与控制流测试不同,其专注于对于变量定义-使用-销毁的测试,以及他们与控制流进入和退出子程序之间的先后顺序。首先我们先分辨是否存在不合理的数据流,比如先使用后定义,我们需要修正这种情况;然后我们对所有的定义以及每种定义-使用的组合进行测试,让所有的数据取值和使用情况尽可能被覆盖到。我们通常在基础测试之上添加基于数据流测试的测试用例。

一般的边界值分析注重三个边界情况:比边界略小、比边界略高、恰等于边界。另一种更为隐晦的边界存在于边界条件涉及多个相互关联的变量时,比如两个int值相乘,我们需要考虑他们的积可能越界。

利用坏数据来进行测试,包括不合规范的数据、未初始化的数据、空数据(没有数据)、规模极大的数据等。

研究表明,80%的错误来自于20%的类和子程序中,50%的错误来自于5%的类中。这些类和子程序通常较为复杂,难以维护。对这些系统对象进行辨别,并进行重构和替换,有助于提高系统出现错误的可能性。

大多数错误的影响范围是有限的,修正起来是简单的。但是这并不妨碍一个错误可能导致极大的损失。

测试代码本身也可能存在错误,实际上测试代码的错误可能比被测试代码更多。

脚手架(scaffolding)可以帮助我们更方便地测试代码。我们可以将一些非常底层的程序从系统中抽取出来,放在脚手架下单独测试,节约了很多的调试时间。很多的框架为我们提供了方便的脚手架,例如JUnit等。脚手架程序可以与被测代码保留在一起,以便再次测试。

无论是个人还是团队,都应该对测试过程进行记录和保存。我们需要考虑错误的严重等级、修复错误花费的时间、错误修正所影响的范围、错误的溯源和归类、测试用例的代码覆盖率、测试用例的缺陷发现数、对类和子程序的缺陷数进行排序以找出最容易出错的对象等。


机器学习 半监督学习

半监督学习处理的是存在未标记样本的情况。我们假设所有样本(包括有标记样本和未标记样本)都是来自于同一个数据源的独立同分布采样,因此即使未标记样本没有标记信息,它们包含的有关数据分布的信息对于建立模型也有着显著的作用,这也是半监督学习的基础。

要利用未标记样本,我们就必须基于其包含的有关数据分布的信息与实际类别标记间存在的联系,比如“聚类假设”认为同一簇的样本属于同一类别,“流形假设”认为在某个流形结构上输出值相近的样本属于同一类别。他们的本质都是“相似的样本更可能具有相似的(模型预测)输出”。

半监督学习进一步分为纯半监督学习直推学习,主要区别在于前者学习的模型预测的对象是训练中未使用的测试集,后者学习的模型预测的对象是训练使用的未标记数据。

生成式的半监督学习方法直接假设对所有数据均由一个潜在的模型“生成”,并认为未标记样本的真实标记视作这个潜在模型的缺失参数,通常利用EM算法进行极大似然估计求解。对潜在模型的不同假设将产生不同的学习方法,同时我们还必须意识到现实中往往很难做出合适的模型假设,虽然生成式方法在有标记数据极少的情况下性能通常较好。该书中展示了基于高斯混合模型假设的利用EM算法对参数进行极大似然估计的学习过程。

半监督支持向量机是SVM在半监督学习上的推广,主要改变在于让划分超平面尽可能穿过数据低密度区。这一类算法最著名的是TSVM(Transductive Support Vector),其试图为所有未标记样本赋予可能的标记,然后在每种可能的情况下进行SVM的超平面划分,取间隔最大的那个超平面对应的可能标记指派作为未标记样本的预测结果。实际上由于所有可能的指派情况太多了,所以TSVM采用了局部搜索的方式迭代寻求一个近似解。其中涉及到搜索可能出现标记指派错误的一对未标记样本,这是一个计算开销很大的优化问题,因此半监督SVM的重点在于如何设计高效的优化求解策略。同时,由于半监督SVM中可能存在多个低密度划分情况,所以可能做出不利的选择。

图半监督学习将所有样本视作图中的结点,并将样本间的相似度作为两个结点之间边的权值。我们优化的目标是让相似的点尽可能地被指派为同一个标记,从图的角度看就是有标记样本结点向周围未标记样本结点传播自己的标记。在图上定义亲和矩阵用于描述各个结点间的相似度,并设计能量函数表达并尽可能使其最小化来实现我们的优化目标。图半监督学习算法易于利用矩阵来进行分析,但是在存储开销上不占优势,而且新增样本时需要重新进行标记传播或者借助其他预测机制。本书分别介绍了二分类情境和多分类情境下的图半监督学习过程。

基于分歧的方法基于协同训练,认为多个有差异的弱学习器可以通过“相互学习,共同进步”来获得很高的泛化性能。其具体过程是让每个学习器利用有标记样本训练出一个分类器,然后让每个分类器分别选出自己最有把握的未标记样本来赋予一个“伪标记”,随后将伪标记样本提供给其他学习器作为有标记样本进行重新训练更新……直到学习器趋于稳定或达到设定轮次。基于分歧的方法需要基学习器之间存在显著的差异(分歧)即可,很少受到模型假设等其他方面的影响,且在理论和事实上都具有较好的性能。但是在有标记样本较少时很难得到显著分歧且性能尚可的基学习器。

多视角数据:假设对于视频,图像数据和音频数据就是多视角数据,他们彼此之间具有“相容互补性”,相容代表他们关于输出空间的信息是一致的,比如视频的类型即决定了图像也决定了音频,互补代表他们所包含的信息是可以实现相互补充的,比如光从图像信息无法得出视频类型的准确信息,但是根据音频信息中的“我爱你”就能有很大把握认为是爱情片。

协同训练:利用充分且条件独立的视图,充分指每个视图的数据都能训练出足够优秀的模型,条件独立指给定标记的情况下两个视图相互独立。然后利用每个视图来各自训练学习器,通过迭代来提高对未标记样本的预测能力。事实上,多视图的意义就在于训练出具有显著分歧的模型。

半监督聚类是指聚类分析在半监督场景下的拓展。对于聚类分析任务,我们能够获得的监督信息分为:

  • “必连”和“勿连”约束:有的样本不能分为一个簇,有的样本必须分为一个簇。通常在传统的聚类算法中考虑这种约束即可完成半监督聚类分析。
  • 少量的有标记样本:有些样本已经有了明确的标记。将这些有标记样本作为“种子”来初始化聚类中心,并且在聚类迭代更新过程中不改变种子的簇隶属关系。

半监督学习目前仍然不能保证未标记样本的加入能够使得泛化性能不下降,也就是说目前没有一般的“安全”的半监督学习算法。