21/8/10 读书笔记 软件构建中的设计 线性模型
21/8/10 读书笔记
Code Complete 软件构建中的设计
险恶(wicked)的问题:必须首先将这个问题“解决”一遍,然后再次解决这个问题,才能得到一个可行的解决方案。
软件开发存在偶然性和本质性的难题,本质性的难题的根源来自于复杂度。软件的首要技术是管理复杂度。有两种方法进行管理:
- 任何人在同一时间处理的本质复杂度的量减到最少
- 不要让偶然复杂度无谓增长
高扇入:让大量的类使用某个给定的类;低扇出:一个给定的类少量或适中地使用其他的类
层次性要求我们能够在任意层次上都能对系统进行观察而无需进入其他层次,这使得我们能够屏蔽一些尴尬的设计实现。
设计的层次分为:
- 软件系统
- 子系统或者包:限制子系统间的通信来使得每个子系统更有存在意义。程序间不应该有任何环形关系,环形关系使得我们难以单独测试每个模块。
- 类:定义类的同时定义好这些类与系统其他部分的通信细节,尤其是接口。
- 子程序:主要是细化类的private子程序
信息隐藏是设计中的一种启发式方法,涉及的秘密有:
- 隐藏复杂度:将一个实现起来很复杂的部分同其他部分隔离开来,使得我们不再需要去应付它。
- 隐藏变化源:将一个随时可能被迭代和修改的部分隔离开来,将修改限制到一定范围内。任何使用了将会被改变的类的其他类都不会察觉到变化,这需要类的接口承担保护类隐私的责任。
隐藏一个特定变量时,不仅通过限制访问来隐藏其值,更隐藏其有关的所有内容,包括它的具体数据类型。
按信息隐藏的原则来思考,能够激发一些面向对象原则下思考所无法得出的设计决策。
预料程序中可能发生的改动,可以先找出程序中对于用户有用的最小子集,这一部分作为不易改变的核心,然后一步一步扩充,每扩充一个部分就建立信息隐藏。
较好的耦合关系包括:
- 通过简单数据类型或者第三方对象作为参数来进行数据传递
- 一个模块单纯实例化一个对象而导致的耦合关系
耦合关系中最复杂的就是一个模块不仅使用了另一个模块的语法元素,更使用了那个类的语义元素,也就是说默认知晓了另一个模块的内部信息。比如A模块将拥有7个属性的对象传递给B模块,而A知道B只会使用其中3个属性,于是只对该对象初始那三个属性。这种耦合使得被调用者的修改会破坏调用者,且编译器无法检测出来。
设计模式的应用中的两个潜在陷阱:
- 强迫让代码适用某种模式:对一段代码做出巨大改动以使其符合标准模式。
- 为了模式而模式:为了使用某种模式而去使用,而不考虑该模式本身是否适合这一任务场景。
内聚性指类内部的子程序在支持一个中心目标上的紧密程度。我们希望类是高内聚性的。这是我们在进行抽象时所需要的考虑的。
为了测试而进行设计,更容易产生规整的类接口。
对于每一段有意义的代码,应该只有一个唯一的地方可以看到它,且只能在一个位置进行可能的维护性修改,这个地方就是中央控制点。这个点可能是一个类,一个子程序,甚至一个宏或者具名常量。
如果尝试了一些设计方案仍然没有很好地解决问题,不如将问题留在未解决的状态,而不要退而求其次。
在设计-实践构成的循环中,我们既能够从高层次的设计角度又能从低层次的实现角度看待问题,这种高低层次的互动有助于实现更好的软件。
在建立软件原型时,需要遵守的原则是“用最少的代码回答问题”。其中可能存在的问题有:
- 设计的问题不够特殊和具体,导致我们在建立原型时没有明确的目标
- 开发人员总不想抛弃原型系统的代码,而根本写不出来最少数量的原型系统的代码。
最大的设计问题通常来自我认为是简单而没有进行任何设计的地方。
应用某种设计方法时越教条化,你所能解决的现实中的问题就越少。这或许更加轻松,但是也更加缺乏创新性。
机器学习 线性模型
线性回归任务中,基于均方误差最小化来进行模型求解的方法称为最小二乘法。
对于具有多个属性的样本的回归称为多元线性回归。在对于m个样本的d元线性回归下,会涉及到矩阵\(X^TX\),其中\(X\)是一个\(m\times (d+1)\)的矩阵,每一行对应于一个样本,前d个元素对应每个属性值,最后一个恒为1。由于m不严格等于(d+1),所以解出的能使得均方误差最小的线性回归参数向量也不止一种,因此需要根据学习器的归纳偏好决定选择哪一个,一般采用引入正则化项的方式。
正则化项:在求均方误差最小值时加上正则化项。分为L1范数和L2范数,L1范数更容易产生稀疏解,稀疏解使得训练时仅有向量\(W\)对应的非零值的特征才出现在最终模型中,更有助于避免过拟合和提高可解释性。
我们考虑单调可微函数\(g(x)\),则\(y=g^{-1}(\bold w^T\bold x+b)\) 对应的称为广义线性模型,这里虽然y和x不成线性关系,但是能够通过\(g(.)\)构成线性联系。广义线性模型使得我们能够用一个单调可微函数将分类任务的真实标记和线性回归模型预测值联系起来,从而用回归模型解决分类问题。常用的一个函数就是对数几率函数\(g^{-1}(z)=\frac{1}{1+e^{-z}}\),这是一种Sigmod函数。对数几率函数是任意阶可导的凸函数,因此具有很好的数学性质,使得很多优化算法都能直接用于求取问题最优解。
线性判别分析(Linear Discriminant Analysis, LDA)是经典的线性学习方法,又称Fisher判别分析。对于二分类认为,其核心思想是找到一种投影方式,将样例投影到一维直线上,使得同类样例的投影点尽可能相近、异类样例的投影点尽可能远,由此根据协方差、均值向量等构建的目标函数称为广义瑞丽商,LDA需要尽可能地最大化之。对于多分类任务,假设为N分类,那么LDA将会把样本投影到N-1维空间,这通常远小于数据原有的属性数构成的空间维度,因此LDA常被视为一种经典的监督降维技术。
多分类任务有时可以由二分类任务直接推广,但是更多情况下还是通过一定的策略将多分类任务拆分为多个二分类任务并集成,拆分方式分为:
-
一对一,OvO:两两配对,产生\(\frac{(N-1)}{2}\)个分类器。
-
一对其余,OvR:每次将一个类作为正例,其余类作为反例,产生\(N\)个分类器。
-
多对多,MvM:采取一定的设计,每次将若干类作为正例,若干类作为反例。
-
最常用的MvM技术是纠错输出码(Error Correcting Output Code, ECOC),对N个类,分别按MvM方式训练k个分类器,使得每种类的在k个分类器下分别被分为正例和反例,对应一个长为k的二进制字符串。然后需要进行测试样本的判断时,将测试样例输入也能得到一个长为k的二进制字符串,计算该字符串与N个类对应字符串的距离,将该样本分类至距离最近的类中。三元ECOC中变成三进制字符串,其中新增的中间值表示该分类器训练时并不包含该类。
ECOC技术对于分类器的错误有一定容错能力,ECOC编码越长,纠错能力越强,但是训练开销也越高。理论上,任意类别之间的编码距离越远,分类能力越强,但是我们总是不能取得最优的编码方式,因为这是NP难问题。事实上,每个分类器进行二分类的难度也不同,有的类之间就是很难区分,因此ECOC码的分类能力也受其影响。
-
类别不平衡问题,指训练样例中正反例数量差异巨大的情况。我们让分类器的预测几率大于观测几率(即训练样本的正反例比例)时就将判定为正例,这称为再缩放。但是训练样本通常不能保证是真实样本总体的无偏采样,因此有现有技术分为三种:
- 欠采样:去除一些占优势的样本,使得正反例数目接近。为了不丢失珍贵的样本信息,典型方法是集成学习机制。
- 过采样:增加一些占劣势的样本,使得正反例数目接近。不能简单地重复采样,否则导致严重过拟合,通常采用插值方法。
- 阈值移动:直接采用原始训练集,但在预测时改变判断概率的阈值。
课后答案可参看该链接机器学习(周志华)课后作业/习题答案

浙公网安备 33010602011771号