Jack Huang's Blog


  • 首页

  • 标签

  • 归档

  • 搜索

面向方面编程简介

发表于 2019-01-23

什么是面向方面编程(What)

面向侧面的程序设计(aspect-oriented programming,AOP,又译作面向方面的程序设计、观点导向编程、剖面导向程序设计)是计算机科学中的一个术语,指一种程序设计范型。该范型以一种称为侧面(aspect,又译作方面)的语言构造为基础,侧面是一种新的模块化机制,用来描述分散在对象、类或函数中的横切关注点(crosscutting concern)。

侧面的概念源于对面向对象的程序设计的改进,但并不只限于此,它还可以用来改进传统的函数。与侧面相关的编程概念还包括元对象协议、主题(subject)、混入(mixin)和委托。

基本概念

  • 关注点(concern):对软件工程有意义的小的、可管理的、可描述的软件组成部分,一个关注点通常只同一个特定概念或目标相关联。
  • 主关注点(core concern):一个软件最主要的关注点。
  • 关注点分离(separation of concerns,SOC):标识、封装和操纵只与特定概念、目标相关联的软件组成部分的能力,即标识、封装和操纵关注点的能力。
  • 方法(method):用来描述、设计、实现一个给定关注点的软件构造单位。
  • 横切(crosscut):两个关注点相互横切,如果实现它们的方法存在交集。
  • 支配性分解(dominant decomposition):将软件分解成模块的主要方式。传统的程序设计语言是以一种线性的文本来描述软件的,只采用一种方式(比如:类)将软件分解成模块;这导致某些关注点比较好的被捕捉,容易进一步组合、扩展;但还有一些关注点没有被捕捉,弥散在整个软件内部。支配性分解一般是按主关注点进行模块分解的。
  • 横切关注点(crosscutting concerns):在传统的程序设计语言中,除了主关注点可以被支配性分解方式捕捉以外,还有许多没有被支配性分解方式捕捉到的关注点,这些关注点的实现会弥散在整个软件内部,这时这些关注点同主关注点是横切的。
  • 侧面(aspect):在支配性分解的基础上,提供的一种辅助的模块化机制,这种新的模块化机制可以捕捉横切关注点。

从主关注点中分离出横切关注点是面向侧面的程序设计的核心概念。分离关注点使得解决特定领域问题的代码从业务逻辑中独立出来,业务逻辑的代码中不再含有针对特定领域问题代码的调用,业务逻辑同特定领域问题的关系通过侧面来封装、维护,这样原本分散在在整个应用程序中的变动就可以很好的管理起来。

对于一个信用卡应用程序来说,存款、取款、帐单管理是它的主关注点,日志和持久化将成为横切整个对象结构的横切关注点。

为什么需要面向方面编程(Why)

AOP技术的优势是显而易见的。在面向对象的世界里,人们提出了各种方法和设计原则来保障系统的可复用性与可扩展性,以期建立一个松散耦合、便于扩展的软件系统。例如GOF提出的“设计模式”,为我们提供了设计的典范与准则。设计模式通过最大程度的利用面向对象的特性,诸如利用继承、多态,对责任进行分离、对依赖进行倒置,面向抽象,面向接口,最终设计出灵活、可扩展、可重用的类库、组件,乃至于整个系统的架构。在设计的过程中,通过各种模式体现对象的行为、暴露的接口、对象间关系、以及对象分别在不同层次中表现出来的形态。然而鉴于对象封装的特殊性,“设计模式”的触角始终在接口与抽象中大做文章,而对于对象内部则无能为力。

通过“横切”技术,AOP技术就能深入到对象内部翻云覆雨,截取方法之间传递的消息为我所用。由于将核心关注点与横切关注点完全隔离,使得我们能够独立的对“方面”编程。它允许开发者动态地修改静态的OO模型,构造出一个能够不断增长以满足新增需求的系统,就象现实世界中的对象会在其生命周期中不断改变自身,应用程序也可以在发展中拥有新的功能。

设计软件系统时应用AOP技术,其优势在于:

  • 在定义应用程序对某种服务(例如日志)的所有需求的时候。通过识别关注点,使得该服务能够被更好的定义,更好的被编写代码,并获得更多的功能。这种方式还能够处理在代码涉及到多个功能的时候所出现的问题,例如改变某一个功能可能会影响到其它的功能,在AOP中把这样的麻烦称之为“纠结(tangling)”。
  • 利用AOP技术对离散的方面进行的分析将有助于为开发团队指定一位精于该项工作的专家。负责这项工作的最佳人选将可以有效利用自己的相关技能和经验。
  • 持久性。标准的面向对象的项目开发中,不同的开发人员通常会为某项服务编写相同的代码,例如日志记录。随后他们会在自己的实施中分别对日志进行处理以满足不同单个对象的需求。而通过创建一段单独的代码片段,AOP提供了解决这一问题的持久简单的方案,这一方案强调了未来功能的重用性和易维护性:不需要在整个应用程序中一遍遍重新编写日志代码,AOP使得仅仅编写日志方面(logging aspect)成为可能,并且可以在这之上为整个应用程序提供新的功能。

总而言之,AOP技术的优势使得需要编写的代码量大大缩减,节省了时间,控制了开发成本。同时也使得开发人员可以集中关注于系统的核心商业逻辑。此外,它更利于创建松散耦合、可复用与可扩展的大型软件系统。

如何实现面向方面编程(How)

使用js实现before(前置通知)、after(后置通知)、around(环绕通知)。

before(前置通知)

before函数,用来实现函数的前置通知。在目标函数的前面执行一些前置操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// AOP 前置通知函数声明
/**
* 给方法加入前置切片函数
* 可以在执行方法之前执行一些操作,
* 前置切片的返回值为false时,不影响原方法的执行
* @param func {Function} 被前置执行的函数
* @return {Function} 加入前置通知的函数
*/
Function.prototype._before = function(func){
var __self = this;
return function(){
func.apply(__self, arguments);
return __self.apply(__self, arguments);
}
}

// 代码
function a(){
console.log('I\'m a');
}

a = a._before(function(){
console.log('before');
});

a();
// 结果:
// before
// I'm a

after(后置通知)

after函数,用来实现函数的后置通知。在目标函数的后面面执行一些后置操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// AOP 后置通知函数声明
/**
* 给方法加入后置切片函数
* 可以在执行方法之之后执行一些操作
* 后置切片的返回值为false时,不影响原方法的执行
* @param func {Function} 被后置执行的函数
* @return {Function} 加入后置通知的函数
* @constructor
*/
Function.prototype._after = function(func){
var __self = this;
return function(){
var ret = __self.apply(__self, arguments);
func.apply(__self, arguments);
return ret;
}
}

// 代码
function b(){
console.log('I\'m b');
}

b = b._after(function(){
console.log('after');
});

b();
// 结果:
// I'm b
// after

around(环绕通知)

在around函数中,引入了一个JoinPoint对象。JoinPoint对象封装了目标函数和目标函数的参数。在调用JoinPoint对象的invoke函数时,会去调用原来的目标函数。在调用invoke时,如果需要改变目标函数的this对象,需要将对象传入到invoke的参数中。around函数,可以在目标函数的前面和后面随意加入逻辑代码,也可以根据条件判断是否执行目标函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// AOP 环绕通知函数声明
/**
* 切入点对象
* 不允许切入对象多次调用
* @param obj 对象
* @param args 参数
* @constructor
*/
function JoinPoint(obj, args){
var isapply = false; // 判断是否执行过目标函数
var result = null; // 保存目标函数的执行结果

this.source = obj; // 目标函数对象
this.args = args; // 目标函数对象传入的参数

/**
* 目标函数的代理执行函数
* 如果被调用过,不能重复调用
* @return {object} 目标函数的返回结果
*/
this.invoke = function(thiz){
if(isapply){ return; }
isapply = true;
result = this.source.apply(thiz || this.source, this.args);
return result;
};

// 获取目标函数执行结果
this.getResult = function(){
return result;
}
}

/**
* 方法环绕通知
* 原方法的执行需在环绕通知方法中执行
* @param func {Function} 环绕通知的函数
* 程序会往func中传入一个JoinPoint(切入点)对象, 在适当的时机
* 执行JoinPoint对象的invoke函数,调用目标函数
*
* @return {Function} 切入环绕通知后的函数,
*/
Function.prototype._around = function(func){
var __self = this;
return function(){
var args = [new JoinPoint(__self, arguments)];
return func.apply(this, args);
}
}

// 代码

var isAdmin = true;

function c(){
console.log('show user list');
}

c = c._around(function(joinpoint){
if(isAdmin){ // 满足条件时,执行目标函数
console.log('is admin');
joinpoint.invoke(this);
}
});

c();
// 结果
// if isAdmin == true
// is admin
// show user list
// if isAdmin == false

参考链接

  1. 面向侧面的程序设计,by wikipedia.
  2. 什么是面向切面编程AOP?,by 知乎.
  3. 什么是面向方面编程,by liuweitoo.
  4. AOP面向方面编程,by 规速.
  5. 团队开发框架实战—面向切面的编程 AOP,by Bobby0322.
  6. 轻松理解AOP(面向切面编程),by -望远-.
  7. AOP在JS中的实现及应用,by _Sirius.

如何写好一篇论文的十条基本原则

发表于 2019-01-17

看到一篇有关如何撰写科技论文的好文章,赶紧摘抄一下。

原则(规则 1–4)

写作即交流。因此,读者体验是首等重要的,所有的写作服务于这一目标。当你写作时,心中要时时有读者。以下四条规则是关于如何赢取读者。

规则 1:论文有一个中心主旨,并体现在标题中

规则 2:假设写作对象对论文内容一无所知

规则 3:坚持原因、内容和结论(Context-Content-Conclusion/C-C-C)结构

规则 4:避免委婉,使用并行优化的逻辑流

论文组成(规则 5-8)

论文的摘要、介绍、结果与讨论都适用于 C-C-C 结构,但各有些许不同。在下图中,我们将讨论这些专门结构的问题:

论文结构

规则 5:在摘要中总结所有要点

规则 6:在介绍中讨论这篇论文的重要性

规则 7:用多个逻辑相关的陈述句(可辅以图表)给出支持论文中心思想的结果

规则 8:讨论如何填补差距、论文的局限性和论文与该领域的相关性

写作流程(规则 9 和 10)

规则 9:把时间花费到关键的地方:题目、摘要、图和大纲

规则 10:获取反馈,然后简化、重新使用、再次构造这个故事

参考链接

  1. 从标题到写作流程:写好一篇论文的十条基本原则,by 机器之心.

面向对象编程的设计原则

发表于 2019-01-17 | 更新于 2019-05-05

说来惭愧,我虽然计算机科学专业科班出生,但是对面向对象编程的理解并不透彻。但在看到“如何写一手漂亮的模型:面向对象编程的设计原则综述”后,感觉收获不少,欣喜之余赶紧摘抄记录一下。

面向对象编程的设计原则

为了写出清晰的、高质量的、可维护并且可扩展的代码,面向对象编程(OOP)将是我们最佳的选择。

对象类型

因为我们要围绕对象来建立代码,所以区分它们的不同责任和变化是有用的。一般来说,面向对象的编程有三种类型的对象。

实体对象

这类对象通常对应着问题空间中的一些现实实体。比如我们要建立一个角色扮演游戏(RPG),那么简单的 Hero 类就是一个实体对象。

这类对象通常包含关于它们自身的属性(例如 health 或 mana),这些属性根据具体的规则都是可修改的。

控制对象(Control Object)

控制对象(有时候也称作管理对象)主要负责与其它对象的协调,这是一些管理并调用其它对象的对象。我们上面的 RPG 案例中有一个很棒的例子,Fight 类控制两个英雄,并让它们对战。

在这种类中,为对战封装编程逻辑可以给我们提供多个好处:其中之一就是动作的可扩展性。我们可以很容易地将参与战斗的英雄传递给非玩家角色(NPC),这样它们就能利用相同的 API。我们还可以很容易地继承这个类,并复写一些功能来满足新的需要。

边界对象(Boundary Object)

这些是处在系统边缘的对象。任何一个从其它系统获取输入或者给其它系统产生输出的对象都可以被归类为边界对象,无论那个系统是用户,互联网或者是数据库。

这些边界对象负责向系统内部或者外部传递信息。例如对要接收的用户指令,我们需要一个边界对象来将键盘输入(比如一个空格键)转换为一个可识别的域事件(例如角色的跳跃)。

Bonus:值对象(Value Object)

价值对象代表的是域(domain)中的一个简单值。它们无法改变,不恒一。

如果将它们结合在我们的游戏中,Money 类或者 Damage 类就表示这种对象。上述的对象让我们容易地区分、寻找和调试相关功能,然而仅使用基础的整形数组或者整数却无法实现这些功能。

它们可以归类为实体对象的子类别。

关键设计原则

设计原则是软件设计中的规则,过去这些年里已经证明它们是有价值的。严格地遵循这些原则有助于软件达到一流的质量。

抽象(Abstraction)

抽象就是将一个概念在一定的语境中简化为原始本质的一种思想。它允许我们拆解一个概念来更好的理解它。

上面的游戏案例阐述了抽象,让我们来看一下 Fight 类是如何构建的。我们以尽可能简单的方式使用它,即在实例化的过程中给它两个英雄作为参数,然后调用 fight() 方法。不多也不少,就这些。

封装

封装可以被认为是将某些东西放在一个类以内,并限制了它向外部展现的信息。在软件中,限制对内部对象和属性的访问有助于保证数据的完整性。

将内部编程逻辑封装成黑盒子,我们的类将更容易管理,因为我们知道哪部分可以被其它系统使用,哪些不行。这意味着我们在保留公共部分并且保证不破坏任何东西的同时能够重用内部逻辑。此外,我们从外部使用封装功能变得更加简单,因为需要考虑的事情也更少。

分解

分解就是把一个对象分割为多个更小的独立部分,这些独立的部分更易于理解、维护和编程。

试想我们现在希望 Hero 类能结合更多的 RPG 特征,例如 buffs,资产,装备,角色属性。

解决方案就是将 Hero 对象分解为多个更小的对象,每个小对象可承担一些功能。

下面是三种分解关系:

  • 关联:在两个组成部分之间定义一个松弛的关系。两个组成部分不互相依赖,但是可以一起工作。例如 Hero 对象和 Zone 对象。
  • 聚合:在整体和部分之间定义一个弱「包含」关系。这种关系比较弱,因为部分可以在没有整体的时候存在。例如 HeroInventory(英雄财产)和 Item(条目)。HeroInventory 可以有很多 Items,而且一个 Items 也可以属于任何 HeroInventory(例如交易条目)。
  • 组成:一个强「包含」关系,其中整体和部分不能彼此分离。部分不能被共享,因为整体要依赖于这些特定的部分。例如 Hero(英雄)和 HeroAttributes(英雄属性)。

泛化

泛化可能是最重要的设计原则,即我们提取共享特征,并将它们结合到一起的过程。我们都知道函数和类的继承,这就是一种泛化。

做一个比较可能会将这个解释得更加清楚:尽管抽象通过隐藏非必需的细节减少了复杂性,但是泛化通过用一个单独构造体来替代多个执行类似功能的实体。

在给出的例子中,我们将常用的 Hero 类和 NPC 类泛化为一个共同的父类 Entity,并通过继承简化子类的构建。

这里,我们通过将它们的共同功能移动到基本类中来减少复杂性,而不是让 NPC 类和 Hero 类将所有的功能都实现两次。

组合

组合就是把多个对象结合为一个更复杂对象的过程。这种方法会创建对象的示例,并且使用它们的功能,而不是直接继承它。

使用组合原则的对象就被称作组合对象(composite object)。这种组合对象在要比所有组成部分都简单,这是非常重要的一点。当把多个类结合成一个类的时候,我们希望把抽象的层次提高一些,让对象更加简单。

组合对象的 API 必须隐藏它的内部模块,以及内部模块之间的交互。就像一个机械时钟,它有三个展示时间的指针,以及一个设置时间的旋钮,但是它内部包含很多运动的独立部件。

正如我所说的,组合要优于继承,这意味着我们应该努力将共用功能移动到一个独立的对象中,然后其它类就使用这个对象的功能,而不是将它隐藏在所继承的基本类中。

批判性思考

尽管这些设计原则是在数十年经验中形成的,但盲目地将这些原则应用到代码之前进行批判性思考是很重要的。

任何事情都是过犹不及!有时候这些原则可以走得很远,但是实际上有时会变成一些很难使用的东西。

作为一个工程师,我们需要根据独特的情境去批判地评价最好的方法,而不是盲目地遵从并应用任意的原则。

关注点的内聚、耦合和分离

内聚(Cohesion)

内聚代表的是模块内部责任的分明,或者是模块的复杂度。

如果我们的类只执行一个任务,而没有其它明确的目标,那么这个类就有着高度内聚性。另一方面,如果从某种程度而言它在做的事情并不清楚,或者具有多于一个的目标,那么它的内聚性就非常低。

我们希望代码具有较高的内聚性,如果发现它们有非常多的目标,或许我们应该将它们分割出来。

耦合

耦合获取的是连接不同类的复杂度。我们希望类与其它的类具有尽可能少、尽可能简单的联系,所以我们就可以在未来的事件中交换它们(例如改变网络框架)。

在很多编程语言中,这都是通过大量使用接口来实现的,它们抽象出处理特定逻辑的类,然后表征为一种适配层,每个类都可以嵌入其中。

分离关注点

分离关注点(SoC)是这样一种思想:软件系统必须被分割为功能上互不重叠的部分。或者说关注点必须分布在不同的地方,其中关注点表示能够为一个问题提供解决方案。

网页就是一个很好的例子,它具有三个层(信息层、表示层和行为层),这三个层被分为三个不同的地方(分别是 HTML,CSS,以及 JS)。

如果重新回顾一下我们的 RPG 例子,你会发现它在最开始具有很多关注点(应用 buffs 来计算袭击伤害、处理资产、装备条目,以及管理属性)。我们通过分解将那些关注点分割成更多的内聚类,它们抽象并封装了它们的细节。我们的 Hero 类现在仅仅作为一个组合对象,它比之前更加简单。

结语

对小规模的代码应用这些原则可能看起来很复杂。但是事实上,对于未来想要开发和维护的任何一个软件项目而言,这些规则都是必须的。在刚开始写这种代码会有些成本,但是从长期来看,它会回报以几倍增长。

这些原则保证我们的系统更加:

可扩展:高内聚使得不用关心不相关的功能就可以更容易地实现新模块。
可维护:低耦合保证一个模块的改变通常不会影响其它模块。高内聚保证一个系统需求的改变只需要更改尽可能少的类。
可重用:高内聚保证一个模块的功能是完整的,也是被妥善定义的。低耦合使得模块尽可能少地依赖系统的其它部分,这使得模块在其它软件中的重用变得更加容易。

参考链接

  1. 如何写一手漂亮的模型:面向对象编程的设计原则综述,by 机器之心.
  2. 什么是面向切面编程AOP?,by 知乎.
  3. 什么是面向方面编程,by liuweitoo.
  4. AOP面向方面编程,by 规速.
  5. 团队开发框架实战—面向切面的编程 AOP,by Bobby0322.
  6. 轻松理解AOP(面向切面编程),by -望远-.
  7. 依赖注入,by android-cn.

机器学习之线性回归问题求解

发表于 2019-01-12

在统计学中,线性回归(Linear regression)是利用称为线性回归方程的最小二乘函数对一个或多个自变量和因变量之间关系进行建模的一种回归分析。这种函数是一个或多个称为回归系数的模型参数的线性组合。只有一个自变量的情况称为简单回归,大于一个自变量情况的叫做多元回归。下面以简单线性回归为例,以机器学习的方法求解此问题。

问题设定

已知有 $N$ 个 $x, y$ 对构成数据集 $X, Y$ ,他们在坐标轴上的分布如下图:

数据集

现在希望找到一个函数:

$$h(x) = wx+b$$

这个函数会尽可能的拟合数据集 $X, Y$ ,为了做到这点,我们希望这个函数 $h(x)$ 在 $X$ 上每一个取值 $x_i$ 的函数值 $h(x_i)$ 与 $Y$ 上每一个对应的 $y_i$ 的平方差尽可能小。即找到一组 $w, b$ ,能使得 $loss(w, b)$ 最小。

$$loss(w, b) = \frac{1}{N}\sum^{N}_{i=0}(wx_i+b-y_i)^2$$

问题求解

采用梯度下降法找到目标 $w, b$,先随机初始化一对 $w_0, b_0$。由于函数的负梯度方向是函数值下降最快的方向,因此对 $w, b$ 求其偏微分:

$$\begin{aligned} \frac{\partial loss(w, b)}{\partial w} &= \frac{2}{N}\sum^{N}{i=0}(wx_i+b-y_i)\cdot x_i, \ \frac{\partial loss(w, b)}{\partial b} &= \frac{2}{N}\sum^{N}{i=0}(wx_i+b-y_i) \end{aligned}$$

再通过下式在每次迭代中更新 $w, b$ :

$$\begin{aligned} w_{t+1} &= w_t - \eta \frac{\partial l(w_t, b_t)}{\partial w_t} \ b_{t+1} &= b_t - \eta \frac{\partial l(w_t, b_t)}{\partial b_t} \end{aligned}$$

其中, $\eta$ 是学习率。

python实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# -*- coding: utf-8 -*-

# %matplotlib inline

import matplotlib.pyplot as plt
import numpy as np

# 生成100对x, y
data_count = 100
w_cache, b_cache, l_cache, = [], [], []
# 学习速度
learning_rate = 0.003
# 迭代次数
training_steps = 3000

x_data = np.linspace(-20, 20, data_count)
y_data = np.multiply(4, x_data) + 7 + np.random.normal(loc=0, scale=8.0, size=(data_count,))

# 初始化w和b
w = np.random.rand()
b = np.random.rand()
y_predict = w * x_data + b

# 梯度下降迭代3000次
for iteration in range(training_steps):
y_predict = w * x_data + b
diff = y_predict - y_data
error = np.sum(np.square(diff)) / data_count
grad_w = np.mean(diff * x_data)
grad_b = np.mean(diff)
w -= learning_rate * grad_w
b -= learning_rate * grad_b
w_cache.append(w)
b_cache.append(b)
l_cache.append(error)

y_predict = w * x_data + b

# 绘制结果
plt.figure(figsize=(10, 6))
plt.scatter(x_data, y_data, s=10, color='g')
plt.plot(x_data, y_predict)
plt.title('y=4x+7')
plt.xlabel('x')
plt.ylabel('y')
plt.show()

tensorflow实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# -*- coding: utf-8 -*-

import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np

# 初始化变量和模型参数,定义训练闭环中的运算

# 生成100对x, y
data_count = 100

# 超参数,实际的训练迭代次数
training_steps=1000

# 超参数,学习速率
learning_rate=0.003

# 定义tf graph输入
X = tf.placeholder(tf.float32)
Y = tf.placeholder(tf.float32)
# 定义模型参数
W = tf.Variable(np.random.randn(), name="weight", dtype=tf.float32)
b = tf.Variable(np.random.randn(), name="bias", dtype=tf.float32)

def inference(X):
# 计算推断模型在数据X上的输出,并将结果返回
pred = tf.add(tf.multiply(W, X), b)
return pred

def loss(X,Y):
# 依据训练数据X及其期望输出Y计算损失
pred = tf.add(tf.multiply(W, X), b)
cost = tf.reduce_sum(tf.pow(pred-Y, 2)) / data_count
return cost

def inputs():
# 读取或生成训练数据X及其期望输出Y
x_data = np.linspace(-20, 20, data_count)
y_data = np.multiply(4, x_data) + 7 + np.random.normal(loc=0, scale=8.0, size=(data_count,))
return (x_data,y_data)

def train(total_loss):
# 依据计算的总损失训练或调整模型参数
optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(total_loss)
return optimizer

def evaluate(sess,X,Y):
# 对训练得到的模型进行评估
# 因为是线性回归,这里只图示
plt.figure(figsize=(10, 6))
plt.scatter(X, Y, s=10, color='g')
pred=inference(X)
plt.plot(X, sess.run(pred))
plt.title('y=4x+7')
plt.xlabel('x')
plt.ylabel('y')
plt.show()

# 在一个会话对象中启动数据流图,搭建流程
with tf.Session() as sess:
tf.initialize_all_variables().run()

X,Y=inputs()

total_loss=loss(X,Y)
train_op=train(total_loss)

coord=tf.train.Coordinator()
threads=tf.train.start_queue_runners(sess=sess,coord=coord)

for step in range(training_steps):
sess.run([train_op])
# 出于调试和学习的目的,查看损失在训练过程中递减的情况
if step % 10 ==0:
print("loss: ",sess.run([total_loss]))

evaluate(sess,X,Y)

coord.request_stop()
coord.join(threads)
sess.close()

参考文献

  1. 线性回归, by wikipedia.
  2. 重拾基础 - 线性回归(一), by Cerulean.

开源跨平台下载利器Aria2

发表于 2019-01-07

长期以来,迅雷一直是我在Windows平台上首选的下载工具。但是随着迅雷软件升级更新后的下载限速和广告推销的愈演愈烈,我终于下定决心卸载了它。下面我推荐一款开源跨平台的下载利器Aria2。

Aria2简介

Aria2是一款自由、跨平台命令行界面的下载管理器,该软件根据GPLv2许可证进行分发。支持的下载协议有:HTTP、HTTPS、FTP、Bittorrent和Metalink。

不同于Wget这样的的命令行界面下载器,Aria2不仅支持BitTorrent,还能够从各种来源多路检索所请求的文件。包括HTTP,HTTPS,FTP和BitTorrent协议。aria2使用Metalink数据块的校验和自动查验BitTorrent下载的数据部分。

安装配置

从Aria2官网下载最新Aria2安装包,当前Aria2最新的版本为1.34.0。

将下载的Aria2安装包aria2-1.34.0-win-64bit-build1解压到C盘目录,并将Aria2安装目录添加到Windows环境变量PATH。

重启后,打开Windows终端,即可在命令行中使用Aria2下载文件。

使用帮助

从Web中下载文件:

1
aria2c http://example.org/mylinux.iso

从2个源下载文件:

1
aria2c http://a/f.iso ftp://b/f.iso

使用2个连接下载文件:

1
aria2c -x2 http://a/f.iso

BitTorrent下载:

1
aria2c http://example.org/mylinux.torrent

BitTorrent Magnet URI下载:

1
aria2c 'magnet:?xt=urn:btih:248D0A1CD08284299DE78D5C1ED359BB46717D8C'

按照txt中给出的URI下载:

1
aria2c -i uris.txt

参考链接

  1. Aria2,by wikipedia.
  2. Aria2 Homepage,by Aria2.

飞行仿真之刚体六自由度方程

发表于 2019-01-06 | 更新于 2020-07-27

在飞行仿真中,获取飞机的位姿是基本要求。将飞行器视为刚体,根据牛顿第二定律和动量矩定理,即可推导出飞机的质心运动方程和飞机绕质心转动的动力学方程,即刚体的六自由度方程。下面即从《航空飞行器飞行动力学》中摘抄刚体六自由度方程的推导过程。

飞行器质心运动方程

根据牛顿第二定理:

$$m\frac{dV}{dt}=F \tag{1}$$

式中$m$为飞行器质量,$V$为飞行器飞行速度矢量,$F$为作用于质心处外力的合力矢量。

具体研究飞行器质心运动规律时,工程上常建立投影正在一动坐标系的标量方程,并认为大气时静止的。

一般动坐标系中质心动力学方程

取原点位于飞行器质心的一动坐标系$Oxyz$,它相对惯性坐标系$O_gx_gy_gz_g$有一转动角速度$w$。质心的绝对速度为$V$,如图1所示。

动系相对于惯性坐标系的关系

图1 动系相对于惯性坐标系的关系

将速度$V$和角速度$w$分别在动坐标系上投影,则有

$$V=V_xi+V_yj+V_zk \tag{2}$$
$$w=w_xi+w_yj+w_zk \tag{3}$$

式中$i,j,k$为动坐标系$Oxyz$的单位矢量。由于$w$存在,其方向将随时间变化。

将公式$(2)和(3)$带入公式$(1)$,则速度$V$的微分,即质心的绝对加速度为:

$$
\frac{F}{m}=\frac{dV}{dt}=\frac{dV_x}{dt}i+\frac{dV_y}{dt}j+\frac{dV_z}{dt}k+V_x\frac{di}{dt}+V_y\frac{dj}{dt}+V_z\frac{dk}{dt} \tag{4}
$$

式中单位矢量导数$di/dt$是矢量端点$i$的速度,此时矢端曲线是绕$w$旋转的圆,因此:

$$\frac{di}{dt}=w\times i$$
$$\frac{dj}{dt}=w\times y$$
$$\frac{dk}{dt}=w\times z$$

将上述关系代入公式$(4)$,即可知质心的绝对加速度可表示为

$$\frac{F}{m}=\frac{dV}{dt}=\frac{\delta V}{\delta t}+w \times V \tag{5}$$

其中:

$$\frac{\delta V}{\delta t}=\frac{dV_x}{dt}i+\frac{dV_y}{dt}j+\frac{dV_z}{dt}k$$

式(5)中 $\frac{\delta V}{\delta t}$ 为动系角速度 $w=0$ 时的加速度,即相当于观察者站在动坐标系中所看到的质心加速度;$w\times V$为由于存在角速度 $w$ 使 $V$ 相对于动坐标系方向发生变化而产生的加速度;$\frac{dV}{dt}$ 为质心的绝对加速度,即观察者在地面坐标系上所看到的加速度。

同样合力矢量$F$用动坐标系上投影表示为:

$$F=F_xi+F_yj+F_zk$$

于是式(5)在动坐标系$Oxyz$上投影的质心动力学标量方程如下:

$$m(\frac{dV_x}{dt}+V_zw_y-V_yw_z)=F_x$$
$$m(\frac{dV_y}{dt}+V_xw_z-V_zw_x)=F_y$$
$$m(\frac{dV_z}{dt}+V_yw_x-V_xw_y)=F_z$$

上述方程组适用于任何动坐标系。

飞行器绕质心的动力学方程

根据动量矩定理,飞行器绕质心的转动运动可表示为:

$$M=\frac{dh}{dt} \tag{6}$$

式中$h$为飞行器对坐标系原点的动量矩;$M$为作用在飞行器上的外力对原点的合力矩。

对质心的动量矩

图2 对质心的动量矩

根据动量矩定义,飞行器上任意微元质量为dm,对坐标系原点的动量矩为:

$$\Delta h=r\times V dm$$

式中$r$为微元质量到坐标系原点的矢径;$V$为该微元质量的速度矢量,则

$$V=V_o+w\times r$$

式中$V_o$为坐标系原点速度(如坐标原点取为飞行器质心,则为质心速度);$w$为坐标系转动角速度。

于是飞行器的总动量矩可积分得出

$$h=\int r\times V dm=\int r dm \times V_o +\int r \times (w \times r) dm$$

取坐标系原点为质心时,有

$$\int r dm=0$$

飞行器动量矩简化为

$$h=\int r \times (w\times r) dm \tag{7}$$

上式表明,飞行器的动量矩只取决于转动产生的速度部分,而与质心运动速度$V_o$无关。矢径$r$和角速度$w$用坐标系中投影分量表示为:

$$r=xi+yj+zk$$
$$w=w_xi+w_yj+w_zk$$

将上述关系式代入式$(7)$,经整理得:

$$h_x=w_xI_x-w_yI_{xy}-w_zI_{zx}$$
$$h_y=w_yI_y-w_xI_{xy}-w_zI_{yz}$$
$$h_z=w_zI_z-w_xI_{zx}-w_yI_{yz}$$

式中$I_x$,$I_y$,$I_z$分别为飞行器对$O_x$轴,$O_y$轴,$O_z$轴地惯性矩,分别为:

$$I_x=\int (y^2+z^2) dm$$
$$I_y=\int (x^2+z^2) dm$$
$$I_z=\int (x^2+y^2) dm$$

而$I_{xy}$, $I_{yz}$, $I_{zx}$ 则为对 $O_x$ 轴与 $O_y$ 轴,$O_y$ 轴与 $O_z$ 轴,$O_z$ 轴与 $O_x$ 轴的惯性积,分别为:

$$I_{xy}=\int xy dm$$
$$I_{yz}=\int yz dm$$
$$I_{zx}=\int zx dm$$

一般动坐标系中绕质心转动动力学方程

具体研究飞行器绕质心转动规律时,矢量形式的式$(6)$使用不便。工程习惯上将其投影在一动坐标系上建成方程的标量形式。此时动坐标系在空中以$w$转动,类同于加速度$\frac{dV}{dt}$,动量矩可以表示为:

$$M=\frac{dh}{dt}=\frac{\delta h}{\delta t}+w\times h$$

类似一般动坐标系中质心动力学方程的推导,最终可得转动运动方程的标量形式为:

$$\frac{dh_x}{dt}+h_zw_y-h_yw_z=M_x$$
$$\frac{dh_y}{dt}+h_xw_z-h_zw_x=M_y$$
$$\frac{dh_z}{dt}+h_yw_x-h_xw_y=M_z$$

参考文献

  1. 方振平,陈万春,张曙光. 航空飞行器飞行动力学[M]. 2015.
  2. 旋转变换(一)旋转矩阵,by csxiaoshui.
  3. 飞行仿真–3.刚体六自由度方程、变换矩阵与四元数,by WFYX.
  4. 判断三维坐标系旋转正方向的简单方法,by Wonderffee.
  5. (番外)姿态与旋转矩阵(I),by Tam Alex.
  6. 一点关于机器人学和计算机视觉中的坐标变换的理解,by Kissrabbit.
  7. 飞机是怎么飞起来的,by J Pan.
  8. 如何获得飞机运动方程,by J Pan.
  9. 【自动控制原理】1.传递函数,by 李寒潭.
  10. 空空导弹尾部的齿轮有什么用?,by 不沉俾斯麦.
  11. 导弹制导原理第4章,by 张庆振.
  12. 飞行原理术语解析,by 刘斯宁.
  13. 空气动力学术语解析,by 刘斯宁.

MathJax语法笔记

发表于 2019-01-05 | 更新于 2019-01-26

MathJax是一个跨浏览器的JavaScript库,它使用MathML、LaTeX和ASCIIMathML标记在Web浏览器中显示数学符号。MathJax是在Apache许可证下作为开源软件发布的。

安装MathJax

MathJax有三种安装方式:最简单的方法就是使用分布式网络服务中的MathJax的副本,它位于 cdn.mathjax.org ,但是你也可以下载并安装一个MathJax的副本到你的服务器,或者使用在你本地硬盘的副本(这样是不需要使用网络)。 官方文档里有详细的描述。

MathJax语法

如何插入公式

LaTeX的数学公式有两种:行中公式和独立公式。行中公式放在文中与其它文字混编,独立公式单独成行。

行中公式可以用如下两种方法表示:

(数学公式) 或 $数学公式$

独立公式可以用如下两种方法表示:

[数学公式] 或 $$数学公式$$

打Tag和引用公式

如果在某个公式之后,又想要引用原公式并说明原公式的出处,可以用 tagging/labelling/referencing system来做。

可以用 \tag{yourtag} 来给原公式打 Tag。

$$m\frac{dV}{dt}=F \tag{1}$$

如果在后面需要引用它,就在 \tag 后面加上 \label{somelabel},yourtag 和 somelabel不一定要一样,但最好一样。

参考链接

  1. MathJax, by wikipedia.
  2. MathJax使用LaTeX语法编写数学公式教程, by knight.
  3. MathJax Home, by mathjax.

Markdown语法笔记

发表于 2019-01-05

一直使用Markdown编辑文档,偶尔会遇到使用一些特殊Markdown语法,为了防止遗忘,特在此记录一下。

强调

在Markdown中,可以使用 * 和 _ 表示斜体,用 ** 表示加粗。例如:

Coding,让开发更简单

Coding,让开发更简单

Coding,让开发更简单

引用

Markdown 标记区块引用和 email 中用 『>』的引用方式类似,只需要在整个段落的第一行最前面加上 『>』 :

Coding.net 为软件开发者提供基于云计算技术的软件开发平台,包括项目管理,代码托管,运行空间和质量控制等等。

区块引用可以嵌套,只要根据层次加上不同数量的『>』:

这是第一级引用。

这是第二级引用。

现在回到第一级引用。

参考链接

  1. Markdown 语法介绍, by coding.

向量点积叉积及其几何意义

发表于 2019-01-05

在3D游戏开发中,经常用到向量的点积和叉积及其几何意义,为防止遗忘,在此记录一下。

点积

在数学中,点积(德语:Skalarprodukt、英语:Dot Product)又称数量积或标量积(德语:Skalarprodukt、英语:Scalar Product),是一种接受两个等长的数字序列(通常是坐标向量)、返回单个数字的代数运算。在欧几里得几何中,两个笛卡尔坐标向量的点积常称为内积(德语:inneres Produkt、英语:Inner Product),见内积空间。

定义

点积有两种定义方式:代数方式和几何方式。通过在欧氏空间中引入笛卡尔坐标系,向量之间的点积既可以由向量坐标的代数运算得出,也可以通过引入两个向量的长度和角度等几何概念来求解。

代数定义

两个向量 $\vec{a} = [a1, a2,…, an]$和 $\vec{b} = [b1, b2,…, bn]$的点积定义为:

$$\vec{a}\cdot \vec{b} = \sum_{i=1}^n a_ib_i = a_1b_1 + a_2b_2 + \cdots + a_nb_n$$
这里的Σ是求和符号,而n是向量空间的维数。

几何定义

在欧几里得空间中,点积可以直观地定义为

$$\vec{a} \cdot \vec{b} = |\vec{a}| , |\vec{b}| \cos \theta ;$$

这里 $|\vec{x}|$ 表示 $\vec{x}$的模(长度), $\theta$ 表示两个向量之间的角度。

叉积

在数学和向量代数领域,叉积(英语:Cross product)又称向量积(英语:Vector product),是对三维空间中的两个向量的二元运算,使用符号 $\times$。与点积不同,它的运算结果是向量。对于线性无关的两个向量 $\mathbf {a}$ 和 $\mathbf {b}$ ,它们的叉积写作 ${\mathbf {a} \times \mathbf {b} }$,是 $\mathbf {a}$ 和 $\mathbf {b}$ 所在平面的法线向量,与 $\mathbf {a}$ 和 $\mathbf {b}$ 都垂直。叉积被广泛运用于数学、物理、工程学、计算机科学领域。

定义

两个向量 $\mathbf {a}$ 和 $\mathbf {b}$ 的叉积仅在三维空间中有定义,写作 ${\displaystyle \mathbf {a} \times \mathbf {b} }$。在物理学中,叉积有时也被写成${\displaystyle \mathbf {a} \wedge \mathbf {b} }$,但在数学中 ${\displaystyle \mathbf {a} \wedge \mathbf {b} }$ 是外代数中的外积。

叉积 ${\displaystyle \mathbf {a} \times \mathbf {b} }$ 是与 $\mathbf {a}$ 和 $\mathbf {b}$ 都垂直的向量 $\mathbf {c}$ 。其方向由右手定则决定,模长等于以两个向量为边的平行四边形的面积。

右手定则

叉积可以定义为:

$${\displaystyle \mathbf {a} \times \mathbf {b} =|\mathbf {a} ||\mathbf {b} |\sin(\theta )\ \mathbf {n} }$$

其中$\theta$ 表示 $\mathbf {a}$ 和 $\mathbf {b}$ 在它们所定义的平面上的夹角( ${\displaystyle 0^{\circ }\leq \theta \leq 180^{\circ }}$)。 ${\displaystyle |\mathbf {a} |}$ 和 ${\displaystyle |\mathbf {b} |}$ 是向量$\mathbf {a}$ 和 $\mathbf {b}$ 的模长,而 $\mathbf{n}$ 则是一个与 $\mathbf {a}$ 、 $\mathbf {b}$ 所构成的平面垂直的单位向量,方向由右手定则决定。根据上述公式,当$\mathbf {a}$ 与 $\mathbf {b}$ 平行(即 $\theta$ 为 0° 或 180°)时,它们的叉积为零向量 $\mathbf{0}$。

在右手坐标系中的向量积

矩阵表示

叉积可以表达为这样的行列式:

$${\displaystyle \mathbf {u\times v} ={\begin{vmatrix}\mathbf {i} &\mathbf {j} &\mathbf {k} \u_{1}&u_{2}&u_{3}\v_{1}&v_{2}&v_{3}\\end{vmatrix}}}$$

这个行列式可以使用萨吕法则或拉普拉斯展开计算。使用拉普拉斯展开可以沿第一行展开为:

$${\displaystyle {\begin{aligned}\mathbf {u\times v} &={\begin{vmatrix}u_{2}&u_{3}\v_{2}&v_{3}\end{vmatrix}}\mathbf {i} -{\begin{vmatrix}u_{1}&u_{3}\v_{1}&v_{3}\end{vmatrix}}\mathbf {j} +{\begin{vmatrix}u_{1}&u_{2}\v_{1}&v_{2}\end{vmatrix}}\mathbf {k} \&=(u_{2}v_{3}-u_{3}v_{2})\mathbf {i} -(u_{1}v_{3}-u_{3}v_{1})\mathbf {j} +(u_{1}v_{2}-u_{2}v_{1})\mathbf {k} \end{aligned}}}$$
可以直接得到结果向量。

参考链接

  1. 叉积, by wikipedia.
  2. 数量积, by wikipedia.
  3. 向量点乘(内积)和叉乘(外积、向量积)概念及几何意义解读, by -牧野-.

Python批量重命名文件

发表于 2019-01-01 | 更新于 2022-05-15

最近从iData中下载了很多学术论文,这些论文文件名都以“www.cn-ki.net_”开头,一个个重命名太麻烦,于是使用如下python3脚本批量重命名文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Python3 code to rename multiple  
# files in a directory or folder

# importing os module
import os

# Function to rename multiple files
def main():

# search file in current directory
for filename in os.listdir("."):

if os.path.isfile(filename):
if "www.cn-ki.net_" in filename[0:14]:
src=filename
dst=filename[14:]
# rename the special file
os.rename(src, dst)

# Driver Code
if __name__ == '__main__':

# Calling main() function
main()

参考文献

  1. How to sort with lambda in Python,by linuxhint.
  2. 用Python复制文件的9个方法,by 景略集智.
  3. shutil — 高阶文件操作,by python.
  4. python glob.glob使用,by mantoureganmian.
上一页1…454647…54下一页

Jack Huang

532 日志
69 标签
© 2025 Jack Huang
由 Hexo 强力驱动
|
主题 — NexT.Muse