Jack Huang's Blog


  • 首页

  • 标签

  • 归档

  • 搜索

模仿学习研究笔记

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

模仿学习定义

模仿学习是指从示教者提供的范例中学习,一般提供人类专家的决策数据

$${ \tau_1,\tau_2,\ldots,\tau_m }$$

,每个决策包含状态和动作序列

$$\tau_i = <s_1^i,a_1^i,s_2^i,a_2^i,\ldots,s_{n_ni+1}^i>$$

,将所有「状态-动作对」抽取出来构造新的集合

$$\mathcal{D}={ (s_1,a_1),(s_2,a_2),(s_3,a_3),\ldots }$$

。之后就可以把状态作为特征(feature),动作作为标记(label)进行分类(对于离散动作)或回归(对于连续动作)的学习从而得到最优策略模型。模型的训练目标是使模型生成的状态-动作轨迹分布和输入的轨迹分布相匹配。

模仿学习意义

在传统的强化学习任务中,通常通过计算累积奖赏来学习最优策略(policy),这种方式简单直接,而且在可以获得较多训练数据的情况下有较好的表现。然而在多步决策(sequential decision)中,学习器不能频繁地得到奖励,且这种基于累积奖赏及学习方式存在非常巨大的搜索空间。而模仿学习(Imitation Learning)的方法经过多年的发展,已经能够很好地解决多步决策问题,在机器人、 NLP 等领域也有很多的应用。

模仿学习实现

当前主要以下几种方法实现模型学习:

行为克隆(Behavior Cloning)

行为克隆(Behavior Cloning)根据人类提供的状态动作对来习得策略,是作为监督学习的模仿学习。

逆强化学习(Inverse Reinforcement Learning )

IRL 是 反过来的 RL,RL 是根据 reward 进行参数的调整,然后得到一个 policy。

但是, IRL 就不同了,因为他没有显示的 reward,只能根据 人类行为,进行 reward的估计(反推 reward 的函数)。在得到 reward 函数估计出来之后,再进行 策略函数的估计。

逆强化学习是在给定一个专家之后(expert policy),通过不断地寻找 reward function 来满足给定的 statement(即,解释专家的行为,explaining expert behavior)。

结构化预测(Structured prediction)

在机器学习过程中,对数据的拟合其实就是在找一个拟合函数f,比如对于回归问题(Regression)来说,这个函数输出一个标量(scalar),对于分类问题(Classification)来说,这个函数输出一个类别(一个one-hot的向量),但是有一类的预测,它并不是输出一个标量或者一个类别,而是输出些有结构的输出,比如,一个序列,一个句子,一个图,一颗树。

生成对抗网络(GAN for Imitation Learning)

那么如何用 GAN 来做这个事情呢?对应到这件事情上,我们知道,我们想得到的 轨迹 是属于某一个高维的空间中,而 expert 给定的那些轨迹,我们假设是属于一个 distribution,我们想让我们的 model,也去 predict 一个分布出来,然后使得这两者之间尽可能的接近。从而完成 actor 的训练过程。

参考链接

  1. 模仿学习(Imitation Learning)完全介绍(一),by 我勒个矗.
  2. 深度强化学习之:模仿学习(imitation learning),by wangxiaocvpr.
  3. 机器人学习Robot Learning之模仿学习Imitation Learning的发展,by c2a2o2.
  4. 行为克隆,by XINGYES.
  5. 最前沿:用模仿学习来学习增强学习,by Flood Sung.
  6. 机器人学习最前沿:一眼模仿学习(One-Shot Imitation Learning)的三级跳,by Flood Sung.
  7. 深度学习课程笔记(七):模仿学习(imitation learning),by WangXiao.

软件架构的一些思考

发表于 2019-01-25 | 更新于 2023-03-21

以前对软件架构总是雾里看花,似懂非懂,最近好像有点悟了,赶紧记录一下。

软件架构的定义(What)

软件架构是有关软件整体结构与组件的抽象描述,用于指导大型软件系统各个方面的设计[1]。

软件体系结构是构建计算机软件实践的基础。与建筑师设定建筑项目的设计原则和目标,作为绘图员画图的基础一样,软件架构师或者系统架构师陈述软件架构以作为满足不同客户需求的实际系统设计方案的基础。从和目的、主题、材料和结构的联系上来说,软件架构可以和建筑物的架构相比拟。一个软件架构师需要有广泛的软件理论知识和相应的经验来实施和管理软件产品的高级设计。软件架构师定义和设计软件的模块化,模块之间的交互,用户界面风格,对外接口方法,创新的设计特性,以及高层事物的对象操作、逻辑和流程。

软件架构的意义(Why)

软件架构是一个系统的草图。软件架构描述的对象是直接构成系统的抽象组件。各个组件之间的连接则明确和相对细致地描述组件之间的通讯。在实现阶段,这些抽象组件被细化为实际的组件,比如具体某个类或者对象。在面向对象领域中,组件之间的连接通常用接口来实现。

软件架构是软件架构师与项目干系人沟通的工具。例如:软件架构师与客户商谈概念上的事情,与经理商谈广泛的设计问题,与软件工程师商谈创新的结构特性,与程序员商谈实现技巧,外观和风格。

软件架构的选择(How)

用户需求决定软件架构的选择。用户需求可分成功能性需求和非功能性需求。功能性需求即用户要求软件产品应实现什么样的功能,非功能性需求即用户要求软件产品的质量属性达到一定标准。以开发一个购物网站为例,用户要求实现商品展示功能、购物车功能即功能性需求,用户要求在1秒内打开购物网站、网站平均无故障时间是一个月即非功能性需求。通常用户的非功能性需求对软件架构产生决定性影响。

常见的软件架构

分层架构

分层架构(layered architecture)是最常见的软件架构,也是事实上的标准架构。如果你不知道要用什么架构,那就用它。

这种架构将软件分成若干个水平层,每一层都有清晰的角色和分工,不需要知道其他层的细节。层与层之间通过接口通信。

虽然没有明确约定,软件一定要分成多少层,但是四层的结构最常见:

  • 表现层(presentation):用户界面,负责视觉和用户互动
  • 业务层(business):实现业务逻辑
  • 持久层(persistence):提供数据,SQL 语句就放在这一层
  • 数据库(database) :保存数据

事件驱动架构

事件(event)是状态发生变化时,软件发出的通知。

事件驱动架构(event-driven architecture)就是通过事件进行通信的软件架构。它分成四个部分。

  • 事件队列(event queue):接收事件的入口
  • 分发器(event mediator):将不同的事件分发到不同的业务逻辑单元
  • 事件通道(event channel):分发器与处理器之间的联系渠道
  • 事件处理器(event processor):实现业务逻辑,处理完成后会发出事件,触发下一步操作

微核架构

微核架构(microkernel architecture)又称为”插件架构”(plug-in architecture),指的是软件的内核相对较小,主要功能和业务逻辑都通过插件实现。

内核(core)通常只包含系统运行的最小功能。插件则是互相独立的,插件之间的通信,应该减少到最低,避免出现互相依赖的问题。

微服务架构

微服务架构(microservices architecture)是服务导向架构(service-oriented architecture,缩写 SOA)的升级。

每一个服务就是一个独立的部署单元(separately deployed unit)。这些单元都是分布式的,互相解耦,通过远程通信协议(比如REST、SOAP)联系。

微服务架构分成三种实现模式。

  • RESTful API 模式:服务通过 API 提供,云服务就属于这一类
  • RESTful 应用模式:服务通过传统的网络协议或者应用协议提供,背后通常是一个多功能的应用程序,常见于企业内部
  • 集中消息模式:采用消息代理(message broker),可以实现消息队列、负载均衡、统一日志和异常处理,缺点是会出现单点失败,消息代理可能要做成集群

云架构

云结构(cloud architecture)主要解决扩展性和并发的问题,是最容易扩展的架构。

它的高扩展性,主要原因是没使用中央数据库,而是把数据都复制到内存中,变成可复制的内存数据单元。然后,业务处理能力封装成一个个处理单元(prcessing unit)。访问量增加,就新建处理单元;访问量减少,就关闭处理单元。由于没有中央数据库,所以扩展性的最大瓶颈消失了。由于每个处理单元的数据都在内存里,最好要进行数据持久化。

这个模式主要分成两部分:处理单元(processing unit)和虚拟中间件(virtualized middleware)。

  • 处理单元:实现业务逻辑
  • 虚拟中间件:负责通信、保持sessions、数据复制、分布式处理、处理单元的部署。

虚拟中间件又包含四个组件。

  • 消息中间件(Messaging Grid):管理用户请求和session,当一个请求进来以后,决定分配给哪一个处理单元。
  • 数据中间件(Data Grid):将数据复制到每一个处理单元,即数据同步。保证某个处理单元都得到同样的数据。
  • 处理中间件(Processing Grid):可选,如果一个请求涉及不同类型的处理单元,该中间件负责协调处理单元
  • 部署中间件(Deployment Manager):负责处理单元的启动和关闭,监控负载和响应时间,当负载增加,就新启动处理单元,负载减少,就关闭处理单元。

客户端/服务器架构

这种架构由两部分组成:一个服务器和多个客户端。服务器组件将为多个客户端组件提供服务。客户端从服务器请求服务,服务器为这些客户端提供相关服务。此外,服务器持续侦听客户机请求。

模型/视图/控制器架构

这种架构,也称为MVC模式,把一个交互式应用程序划分为3个部分,

  • 模型:包含核心功能和数据
  • 视图:将信息显示给用户(可以定义多个视图)
  • 控制器:处理用户输入的信息

这样做是为了将信息的内部表示与信息的呈现方式分离开来,并接受用户的请求。它分离了组件,并允许有效的代码重用。

参考文献

  1. 软件架构,by wikipedia.
  2. 软件架构入门,by 阮一峰.
  3. 10种常见的软件架构模式,by 尽信书不如无书.
  4. MVC,by wikipedia.
  5. 1.软件架构编年史(译),by qinyu.
  6. MVC MVP MVVM Redux 架构介绍,by 吃货要健康.
  7. 深入理解MVC,by 陈宏鸿.
  8. 什么是MVVM框架?,by siki学院.

FlightGear技术分析

发表于 2019-01-24 | 更新于 2022-11-21

FlightGear是一个始于1997年多平台飞行模拟器、开源软件项目[1]。该项目适用的操作系统主要包括Linux、Microsoft Windows和Mac OS X,采用C++编写。

FlightGear体系结构

当前体系结构

FlightGear体系结构基于一个名为“主循环”的无限循环[2]。其流程图如图1所示。在主循环中将依次完成如下工作:

  • ATC模拟
  • 控制AI对象
  • 在多用户环境中更新其他飞机
  • 飞行动力学计算
  • 风景更新
  • 音频调度
  • 渲染

FlightGear主循环流程图

图1 FlightGear主循环流程图

高级体系结构

FlightGear正在支持高级体系结构(High Level Architecture, HLA)[3][4]。

HLA简介

高级体系结构(HLA)是分布式仿真的标准,用于通过组合(联合)多个仿真来构建用于更大目的的仿真。该标准是在美国国防部的领导下于90年代开发的,后来转变为开放的国际IEEE标准。它是北约通过STANAG 4603推荐的标准。今天,HLA被用于许多领域,包括国防和安全以及民用应用。该体系结构指定以下组件:

  • 运行时基础结构(RTI),通过不同的编程语言提供标准化的服务集。这些服务包括信息交换,同步和联合管理
  • 联邦成员(Federates)是使用RTI服务的单独仿真系统,由多个联邦成员对象构成。
  • 联邦成员对象模型(FOM),指定用于交换数据的对象类和交互类。 FOM可以描述任何域的信息。

HLA构成

图2 HLA构成

HLA标准由三部分组成:

  • IEEE Std 1516-2010框架和规则,它规定了组件或整个联合应遵守的十个体系结构规则。
  • IEEE Std 1516.1-2010联邦接口规范,规定了RTI应提供的服务。这些服务以C ++和Java API以及Web服务的形式提供。
  • IEEE Std 1516.2-2010对象模型模板规范,它规定了HLA对象模型(如FOM)应使用的格式。

HLA优势

与单机仿真相比,这有三大优势[5](例如FlightGear V3.6):

  • 它提供了一个强大的环境,使模拟器具有多线程,利用具有多个内核的计算机,或者在不同的计算机(甚至包括不同的平台和操作系统)上运行模拟的不同部分。
  • 它允许我们分离模拟器的部分,如AI(通过解耦AI交通系统)、FDM、Nasal脚本和渲染器,以及较少时间关键的子系统,如天气,这样我们就可以获得一致(也许更高)的帧速率(即减少Nasal垃圾收集对帧速率的影响)。
  • 它提供了一个非常好的框架,允许任何人使用除C / C ++之外的编程语言(想想Ada,Java,Python等)创建与FlightGear交互的组件,这些编程语言可能在他们自己的线程中运行,并且驻留在单独的二进制文件中,这也更容易调试/排除故障(想想回归测试,即在专用的gdb / valgrind会话中运行一个自包含的子系统),而不必知道如何修改/补丁和重建FlightGear。

FlightGear组件构成

FlightGear由很多开源组件或程序构成[6],具体包括:

RTI

RTI是HLA架构的关键组件,相当于中间件。

OpenRTI

OpenRTI是一个包含了rti 1.3、rti 1516、rti 1516e标准接口实现的rti库。OpenRTI有如下关键特性:

  • 易用性高,非常便于使用;
  • 直接提供C++调用接口,也可以提供Java接口,但目前还没有编码实现;
  • 可扩展性强;
  • 一直在维护中的RTI开源项目;
  • 树状的服务器结构;
  • 最短路径在内存中不拷贝数据;
  • 不依赖其它项目,仅仅需要C++编译器,特别的不需要boost;
  • 支持线程间通信、rti通信和管道间通信,未来可能支持http通信;
  • 到处都可以运行,能够在Linux、Win32、MacOS和Solaris上运行。

FDM

飞行动力学模型(Flight Dynamics Model, FDM)是模拟器内控制飞机物理飞行的数学模型。飞机的3D模型实质是一张图片,其与飞行动力学无关,本质上由FDM控制飞机如何飞行。在FlightGear中主要使用JSBSim和YASim两个飞行动力学模型。

JSBSim

JSBSim是一个用C++实现的开源跨平台飞行器动力学模型软件。Flightgear也采用了JSBSim作为其中的飞行器动力模型之一。同时JSBSim也可以作为一个单独的动力学模型软件进行运行。

YASim

YASim使用飞机的几何形状生成基本飞行特征。虽然这表明了一种“现实的”或开箱即用的方法,但在获得接近现实主义的结果之前,这只是一种粗略的近似,需要进行大量的调整。如果您的飞机有稳定的飞行数据,例如风洞数据,或者您希望最终生成超逼真的模拟,那么JSBSim可能是更好的方法。 如果你缺乏这样的数据但是知道飞机的几何形状并且能够获得与真实飞行员相同的飞行特性和限制,那么YASim可以提供足以满足大多数模拟需求的解决方案。

Atlas

Atlas旨在为FlightGear(一种开源飞行模拟器)的用户制作和展示世界高质量的图表。 这是通过两个主要部分实现的:地图创建者(简称为Map)和Atlas查看器。

  • 地图创建者从FlightGear获取风景数据并将其转换为漂亮的地图图片,可以使用您可能已安装的任何位图图像程序或使用Atlas查看应用程序查看。

  • Atlas查看应用程序可用于浏览您的地图,但也可以直接连接到FlightGear,并在所谓的移动地图显示上显示您的飞机当前位置。

FlightGear Multiplayer Server

FGMS或FlightGear多人游戏服务器是FlightGear的独立网络服务器,并根据GPL许可。它允许通过FGFS内的网络与其他飞行员一起飞行。

可以在服务器配置中配置的服务器列表类型:

  • 中继服务器 - 网络中的其他服务器。 每个都必须有完整的列表(减去自己)以获得适当的网络功能。
  • 交叉馈送服务器 - 服务器从本地用户和其他服务器接收的所有内容都将转发到交叉馈送服务器。 用于在同一主机上运行多个连接的fgms实例,例如 用于提供跟踪和未跟踪服务,而不会产生额外的外部流量。
  • 跟踪器 - 服务器每10秒向跟踪器发送一个每个本地用户的摘要更新。
  • HUB - 通常服务器不会将从服务器接收的数据包发送到其他中继。 HUB服务器将数据从服务器发送到它知道的所有中继。

FGCOM

FGCom是一种语音通信功能。这样您就可以在飞行途中与其他飞行员和空域管制员进行通信。

主要思想是重现真实的航空通信,换句话说,此功能旨在使您的航班期间尽可能真实地进行无线电通信。

FGCom有两种方式:

集成到FlightGear(FGCom内置):这当然是更好的解决方案,因为更容易使用
外部软件(FGCom standalone):只有在计划在特殊情况下使用FGCom时才应使用此软件

MPmap

MPMap是一个实用程序,可以在世界地图上显示在FlightGear世界中飞行的飞机。 除此之外,它还提供对导航数据的访问,例如ILS频率,跑道号和修正。由于它使用谷歌地图,人们可以选择地图或卫星视图。

SimGear

SimGear是FlightGear使用的一组开源软件库。该项目仍在开发中,但其目标是成为“仿真内核”,并由其他非FlightGear项目使用。该项目于2000年启动。SimGear与FlightGear和TerraGear一样,需要PLIB进行构建。

SimGear是一个仿真架构工具集(simulation construction tools),是FlightGear的仿真引擎,完成了数据结构操作、星历计算、模拟天空、坐标系转换等大部分的工作,它也是一个开源库。

PLib

PLIB(便携式游戏库)是由Steve J. Baker编写的一套用于编写游戏的软件库。 FlightGear在其大部分开发中使用了PLIB。 它也被FlightGear相关程序Atlas使用。 PLIB是开源的,并且是在GNU Library General Public License下发布的。

FlightGear 1.0(2007年发布)是在向OpenSceneGraph(OSG)过渡之前使用PLIB进行3D场景图的最后一次公开发布。 FlightGear仍然使用PLIB执行各种任务; 例如,读取操纵杆输入并显示图形用户界面(GUI)。 在后一种用法中,PLIB的PUI组件最终将由Canvas和Phi在FlightGear中替换,这将极大地改善当前GUI的功能,超出PUI提供的功能。

构建FlightGear时需要PLIB。

OSG

OSG是OpenSceneGraph的缩写,OpenSceneGraph图形系统是一个基于工业标准OpenGL的软件接口,它让程序员能够更加快速、便捷地创建高性能、跨平台的交互式图形程序。相比于工业标准OpenGL或者其他图形库,OpenSceneGraph的优点明显,除了程序开源和平台无关性以外,其封装并提供了数量众多的提升程序运行时性能的算法、针对包括分页数据库在内的几乎所有的主流数据格式的直接数据接口、 以及针对脚本语言系统Pthyon和Tcl的支持。

OpenGL是Open Graphics Library的缩写,其独立于硬件,独立于窗口系统,在运行各种操作系统的各种计算机上都可用,并能在网络环境下以客户/服务器模式工作,是专业图形处理、科学计算等高端应用领域的标准图形库。

场景相关工具

TerraSync

要查看飞机下方的地形,您必须安装相应的风景。这可以通过在安装风景的文章中描述的在飞行之前下载某些景点来实现。

或者,如果您有稳定且相当快速的互联网连接,则可以使用TerraSync。它是一个实用程序,可在模拟器运行时自动下载所需FlightGear场景的最新版本。 TerraSync在后台运行(可选择作为单独的进程),监视您的位置,并从“主时间”服务器“及时”下载(或更新)最新的场景。一段时间以来,TerraSync已经集成到核心FlightGear流程中,因此不需要为典型用户处理TerraSync。

TerraSync的主存储库,即TerraSync从中下载文件的在线资源,每天与FlightGear Scenery数据库同步一次。因此,当使用TerraSync时,您将永远拥有

  • 最新的.stg文件,告诉FlightGear放置对象的位置
  • 最新的对象静态模型。 (静态模型定义仅存在于一个地方的唯一对象,例如着名的建筑物或地标。)
  • 最新的对象共享模型。 (通用模型在不同的地方使用不止一次,每个都可以代表许多不同的对象,如通用房屋或船舶)

TerrGear

TerraGear是开源工具和渲染库的集合,可以在地球的3D表示(即3D模型或3D地图)中转换公开可用的GIS数据,以用于实时渲染项目。 TerraGear可以导入3D数据集,例如DEM地形网格,2D多边形数据集(如海岸线,城市轮廓,湖泊轮廓)和2D栅格数据集,例如1 km NAOO土地利用/土地覆盖数据。它还具有基于可用的FAA数据生成逼真的机场,跑道和照明的工具。

TerraGear是用于为FlightGear项目生成场景的主要工具。

如果没有terragear,可以更改Terrain纹理,但不能更改地形。如果要更改城市的纹理,请更改材质文件。如果你想改变海岸线,你需要terragear。检查目录FGDATA/Material中的材料文件,你需要terragear。

FlightGear事件处理

请参考FlightGear Command Doc

参考链接

  1. FlightGear,by wikipedia.
  2. A NEW ARCHITECTURE FOR FLIGHTGEAR FLIGHT SIMULATOR,by flightgear.
  3. FlightGear high-level architecture support,by flightgear
  4. Developing with HLA,by flightgear.
  5. High-Level Architecture,by flightgear.
  6. High-level architecture,by wikipedia.
  7. FlightGear related projects,by wikipedia.
  8. OpenRTI,by openrti.
  9. JSBSim,by wikipedia.
  10. YASim,by flightgear.
  11. JSBSim vs YASim,by flightgear.
  12. Atlas,by atlas.
  13. FGCom 3.0,by wikipedia.
  14. FlightGear Multiplayer Server,by flightgear.
  15. SimGear,by flightgear.
  16. PLIB,by flightgear.
  17. OSG,by flightgear.
  18. Flightgear操作方式以及几种典型飞机的起飞方法,by Mosquito_蚊子.
  19. Sentry的使用,by KJxiaoxiao.

面向方面编程简介

发表于 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.
上一页1…454647…54下一页

Jack Huang

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