精华内容
下载资源
问答
  • 光分配网(ODN)的分光方式主要有两种:一级分光和二...采用二级分光时,第一级分路器一般设置在配线光交处,第二级分路器一般设置在分纤处。 那么,一级分光和二级分光有哪些不同?分别适用于什么场景呢! 1 引...

     

    光分配网(ODN)的分光方式主要有两种:一级分光和二级分光,如图1所示。受PON设备光功率预算及带宽的限制,当前,ODN的总分路比一般为1:64。

    图1 ODN的一级分光和二级分光

    采用一级分光时,分路器一般设置在配线光交处;采用二级分光时,第一级分路器一般设置在配线光交处,第二级分路器一般设置在分纤箱处。

    那么,一级分光和二级分光有哪些不同?分别适用于什么场景呢!

     

    1 引入段光缆纤芯数

     

    当采用一级分光时,分纤箱分配的纤芯数(从配线光交连接到分纤箱的纤芯)主要与分纤箱覆盖的住户数和配线比(配置的端口数/覆盖的住户数)有关;例如,当一个光缆分纤箱覆盖一个12户的住宅单元,如果配线比为50%,则分纤箱分配的纤芯数为12×50%=6(芯);分纤箱分配的光缆纤芯一般为6、8、12芯,如图2所示。

     

    图2 采用一级分光时的引入光缆芯数

    当采用二级分光时,无论分纤箱覆盖的住户数多少,分纤箱分配的纤芯数均为2芯,其中1芯连接分路器,另1芯备用,如图3所示。注意,光缆中分配的纤芯是指该段光缆在光交成端的纤芯数,与光缆的芯数不一定一样。

    图3采用二级分光时的引入光缆芯数

    显然,采用一级分光时,引入光缆的纤芯数随覆盖的住户数增加而增加;采用二级分光时,引入光缆的纤芯数只和分纤箱的数量有关。例如,一个住宅小区共设置了50个分纤箱,每个分纤箱覆盖的平均住户数为20户,配线比为50%,则采用一级分光时的引入光缆总芯数不少于50×20×0.5=500芯,采用二级分光时的引入光缆总芯数不少于50×2=100芯。

     

    2 用户放装的便捷性

     

    当采用一级分光时,光分路器的上行方向端口在工程阶段就已经和上行配线光缆进行了连接,在用户放装时,装维人员一般需经过以下步骤才能完成外线光缆的连接:

    (1)在覆盖待装用户的分纤箱处找到分纤箱内的空闲端口A;

    (2)到安装分路器的交接箱处,找到上述空闲端口A在交接箱内的对应端口B;

    (3)将分路器下行方向的指定空闲端口,连接到端口B。

    当采用二级分光时,第一级光分路器的上、下行端口在工程阶段已经分别与配线、引入光缆的纤芯进行了连接,放装时,只需要根据网管资料,在覆盖待装用户的分纤箱内找到对应的空闲端口即可。

    显然,采用二级分光时的用户放装较一级分光要便捷得多。

     

    3 ODN链路全程衰耗

     

    在同样分路比的情况下,二级分光所采用的2个光分路器的插损之和要大于一级分光的分路器;例如,1个1×64分路器(盒式)的插损为20.9dB,2个1×8分路器(1个盒式、1个插片式)的插损之和为22dB。注意,同样分路比的分路器,封装型式不同,插损也不一样。

    另外,采用二级分光时,分纤箱处采用的是插片式分路器,共有2个活接头;而采用一级分光时,分纤箱处只会有一处活接头;如图4所示。

    图4 ODN中的活接头与分路器插损

    所以,采用二级分光时,ODN链路的全程衰耗要比一级分光大1.6dB左右(活接头的衰耗按0.5dB计算)。

     

    4 PON口及主干纤芯的占用

     

    我们以一个实例来说明采用不同分光方式时PON口及主干纤芯的占用。

    某一多层小区,共60个单元,层高均为6层,每单元总户数12户,小区总户数720户;每个单元设置一个分纤箱。

    工程阶段,采用一级分光方式时,因为没有用户,所以只需安装一个1×64分路器,占用1芯主干和1个PON口;采用二级分光方式时,如果每个分纤箱安装一个1×8分路器,则在配线光交处还需安装8(60/8向上取整)个分路器,共占用8芯主干和8个PON口。

    放装阶段,假如渗透率(实装用户/覆盖住户)达到30%,即219户;当采用一级分光时,则需占用4(219/64向上取整)芯主干和4个PON口;当采用二级分光时,则占用的主干光缆芯数和PON口依然为8。

    可见,采用二级分光方式时PON口及主干纤芯的占用要大于一级分光。

     

    5 扩容方法

     

    原采用一级分光的ODN,扩容后会形成一、二级分光并存的情况,可能会带来网络维护的不便。(下期专门分析)

     

    6 ODN投资

     

    对采用一、二级分光方式的ODN建设投资差异影响较大的因素主要有以下几点:

    (1)一级分光方式下引入光缆的芯数较大;

    (2)一级分光方式下在配线光交、分纤箱处成端的光缆芯数较多;

    (3)二级分光对主干纤芯及PON口的占用较多。

    据测算,采用一级分光方式时,ODN的投资在不同配线比下均比二级分光高,见表1。

     

     

    在住户密度较低的区域,如农村、别墅区,尽管采用一、二级分光方式时光缆的芯数差异不大,但由于光缆成端芯数不一样,投资差异依然存在,只是没那么悬殊而已。

     

    7 一、二级分光使用场景的建议

     

    与二级分光相比,一级分光无论在投资、放装的便利性、扩容的便利性方面均显不足,虽然一级分光ODN链路的衰耗较低,但当前PON设备的光功率预算已完全满足二级分光的需求;所以,当前的ODN主要采用二级分光。

    但在内部管线共建共享的小区,运营商与开发商网络的分界点为配线光交,如图5所示;分纤箱内引入光缆和每个住户的入户光缆是一一对应熔接的,所以,这种情况下宜采用一级分光。

    图5 共建共享小区的配线光交

     


     

    一丁一卯,专注于通信管线和宽带接入工程技术交流与分享

    微信搜索公众号“一丁一卯”添加关注

    转载或引用请注明出处

     

     

    展开全文
  • mybaits一级缓存和二级缓存的实现

    千次阅读 2018-12-10 20:32:00
    近来看了一下关于mybaits的一些缓存讲解的文章,然后自己也顺着文章看了一下mybaits底层的源码。 (本文部分内容转载于美团的 聊聊MyBatis缓存...首先谈的肯定是mybaits的工作原理,大家都知道mybaits有一级缓存...

    近来看了一下关于mybaits的一些缓存讲解的文章,然后自己也顺着文章看了一下mybaits底层的源码。

    (本文部分内容转载于 美团的  聊聊MyBatis缓存机制 文章

    目前而言,myabits是最流行的dao层框架,简洁,易上手,开箱即用,但是如果不了解一下底层的东西,可能会造成其他的问题。比如造成的脏数据

    首先谈的肯定是mybaits的工作原理,大家都知道mybaits有一级缓存和二级缓存,一级缓存是默认开启的,二级缓存需要配置开启。

    mybatis.configuration.cache-enabled = true

    还有其他的配置,比如懒加载什么的。

     

    一级缓存:

    刚才提及到,一级缓存是默认开启的,所以我们开箱即用就好了。

    当server端发起请求时,mybatis会默认先向缓存中请求结果,当缓存未命中时,像mysql查询结果,当从mysql查询到结果时,框架会把查询到的结果信息存到缓存中去,方便我们下次使用,然后返回给用户的是缓存中的信息

    (Tips:mybaits每次返回的信息都是缓存中的数据,即使未命中缓存的情况下,mybaits也会将mysql查询的结果先放到缓存中,然后再从缓存中获取数据)

    一级缓存最主要的是2个区别:对缓存范围的影响。默认是session级别的,就是当前mybaits执行的所有语句都有效,另一种是STATEMENT级别的,可以理解为缓存只对当前执行的这一个Statement有效。

    刚才提交到mybaits会有脏数据的产生,具体的可以看下这个脏数据是怎么出现的。

    @Test
    public void testLocalCacheScope() throws Exception {
            SqlSession sqlSession1 = factory.openSession(true); 
            SqlSession sqlSession2 = factory.openSession(true); 
    
            StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
            StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
    
            System.out.println("studentMapper读取数据: " + studentMapper.getStudentById(1));
            System.out.println("studentMapper读取数据: " + studentMapper.getStudentById(1));
            System.out.println("studentMapper2更新了" + studentMapper2.updateStudentName("小岑",1) + "个学生的数据");
            System.out.println("studentMapper读取数据: " + studentMapper.getStudentById(1));
            System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1));
    }

    从console中可以看到,我们再session2中更改了该条记录的值,但是session1所读取出来的值还是未更改之前的值,这就导致了“脏数据”的产生,同时验证了一级缓存的session范围性

    附一张mybaits的工作流程图片,就是我上面陈述的部分

    先看一下mybaits的底层实现原理,再看二级缓存的实现

    mybaits的核心类是SqlSession,Executor,Cache

    SqlSession的默认实现是DefaultSqlSession,该类提供的是用户与数据库之间的操作方法,不仅仅有curd,还有事务回滚和事务提交的操作。

    Executor是一个更具体的操作,sqlSession负责下发用户的指令,Executor负责和数据库进行具体的交互。Executor大致可以分为两种实现类,一种是BaseExecutor(其他的实现类都是继承了这个抽象类),一种是CachingExecutor。前者主要用于一级缓存,后者是二级缓存。

    Cache:顾名思义,和缓存有关。负责了所有有关缓存相关的操作。

    介绍完了基本类,我们可以看下底层的实现逻辑。

    首先我们需要通过DefaultSqlSessioFactory初始化一个sqlSession

    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;
        try {
          final Environment environment = configuration.getEnvironment();
          final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
          tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
          final Executor executor = configuration.newExecutor(tx, execType);
          return new DefaultSqlSession(configuration, executor, autoCommit);
        } catch (Exception e) {
          closeTransaction(tx); // may have fetched a connection so lets call close()
          throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
        } finally {
          ErrorContext.instance().reset();
        }
      }

    在初始化的过程中会去new一个Executor

    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        executorType = executorType == null ? defaultExecutorType : executorType;
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
        Executor executor;
        if (ExecutorType.BATCH == executorType) {
          executor = new BatchExecutor(this, transaction);
        } else if (ExecutorType.REUSE == executorType) {
          executor = new ReuseExecutor(this, transaction);
        } else {
          executor = new SimpleExecutor(this, transaction);
        }
        if (cacheEnabled) {
          executor = new CachingExecutor(executor);
        }
        executor = (Executor) interceptorChain.pluginAll(executor);
        return executor;
      }

    根据不同的ExecutorType会返回不同的执行器,但是这些类都是基于 BaseExecutor 的。

    当 cacheEnabled 为true时,会new CachingExecutor 。这边是不是很眼熟,正是我们刚才需要开启二级缓存的注解,当设置二级缓存为 true 时,会返回一个不同的执行器。

    拿select方法来说,当执行 sqlSession.selectOne() 方式时,都会执行到 selectList 方法,然后取到第一条

    @Override
      public <T> T selectOne(String statement) {
        return this.<T>selectOne(statement, null);
      }
    
      @Override
      public <T> T selectOne(String statement, Object parameter) {
        // Popular vote was to return null on 0 results and throw exception on too many.
        List<T> list = this.<T>selectList(statement, parameter);
        if (list.size() == 1) {
          return list.get(0);
        } else if (list.size() > 1) {
          throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
        } else {
          return null;
        }
      }

    执行selectList方法时,正和我们刚才说的一样,会交给执行器去进行查询处理

    MappedStatement ms = configuration.getMappedStatement(statement);
          return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    @Override
      public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameter);
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
     }

    当执行器去执行查询语句时候,我们注意到会先调用  createCacheKey() ,方法内部是对 CacheKey类的操作,

    CacheKey cacheKey = new CacheKey();
    cacheKey.update(ms.getId());
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    cacheKey.update(boundSql.getSql());
    // for循环对参数进行优化
    cacheKey.update(value);
    if (configuration.getEnvironment() != null) {
          // issue #176
          cacheKey.update(configuration.getEnvironment().getId());
    }
    

    将MappedStatement的Id、sql的offset、Sql的limit、Sql本身以及Sql中的参数传入了CacheKey这个类,最终构成CacheKey。

    继续往下看query方法

    List<E> list;
        try {
          queryStack++;
          list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
          if (list != null) {
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
          } else {
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
          }
        } finally {
          queryStack--;
        }
    handleLocallyCachedOutputParameters 这个方法从其他地方看到是对于存储过程的使用。这个功力不够,还为能理解其中。

    我们可以看到,query查询时候会优先根据刚才生成的cacheKey去缓存中查询,如果缓存命中,那么就取缓存中的数据,如果缓存未命中,那么会去查询dateBase

    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        List<E> list;
        localCache.putObject(key, EXECUTION_PLACEHOLDER);
        try {
          list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
          localCache.removeObject(key);
        }
        localCache.putObject(key, list);
        if (ms.getStatementType() == StatementType.CALLABLE) {
          localOutputParameterCache.putObject(key, parameter);
        }
        return list;
      }

    当查询数据库时候,会先操作缓存,给当前的key设定一个占位符。当从数据库中取到值时,会将当前key所对应的缓存清除,将新的list放入缓存中,以便下次查询使用。下面判断statementType是为了在存储过程中获取相对应的参数。

    sqlSession的insert和delete方法最后会执行update方法

    @Override
      public int insert(String statement) {
        return insert(statement, null);
      }
    
      @Override
      public int insert(String statement, Object parameter) {
        return update(statement, parameter);
      }
    
    @Override
      public int delete(String statement) {
        return update(statement, null);
      }
    
      @Override
      public int delete(String statement, Object parameter) {
        return update(statement, parameter);
      }

    而update也是一样,sqlSession触发update方法,交给Executor去执行对应的update方法。每次update时,都会先清除缓存,清除的是执行结果的缓存和key,paramter的参数

    public int update(MappedStatement ms, Object parameter) throws SQLException {
        ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
        if (closed) {
          throw new ExecutorException("Executor was closed.");
        }
        clearLocalCache();
        return doUpdate(ms, parameter);
      }
    
    
    @Override
      public void clearLocalCache() {
        if (!closed) {
          //清除缓存的结果
          localCache.clear();
          //清除缓存的参数
          localOutputParameterCache.clear();
        }
      }

    上述就是mybaits的一级缓存的机制。

    二级缓存的执行过程

    二级缓存开启后,同一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享,是一个全局的变量。

    二级缓存和一级缓存的本质区别刚才已经叙说了,主要就是加载的执行器不同,一级缓存的执行器都是基于 BaseExecutor的,而二级缓存是基于 CachingExecutor

    @Override
      public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
          throws SQLException {
        Cache cache = ms.getCache();
        if (cache != null) {
          flushCacheIfRequired(ms);
          if (ms.isUseCache() && resultHandler == null) {
            ensureNoOutParams(ms, boundSql);
            @SuppressWarnings("unchecked")
            List<E> list = (List<E>) tcm.getObject(cache, key);
            if (list == null) {
              list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
              tcm.putObject(cache, key, list); // issue #578 and #116
            }
            return list;
          }
        }
        return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
      }

    每次在执行query时候,二级缓存都是根据MappedStatement是否含有缓存以及其子对象的属性去判断是否需要清空缓存

    而后面的ensureNoOutParams同样也是基于存储过程的方法。

    当缓存命中时,会从tcm中获取数据。如果tcm获取到数据,那么返回tcm的值,否则的话会给当前的cache设立一个标记位,防止报空指针错误。

    如果未从tcm去获取到那么会继续走执行器的query步骤,这个执行器的query步骤就是相当于我们走回了一级缓存的query步骤,查询到数据以后会将数据再放入到tcm中。

    所以,二级缓存最后的查询步骤都会回归于一级缓存的实际操作。

    (什么是tcm?tcm即TransactionalCacheManager的简称,这个Map保存了Cache和用TransactionalCache包装后的Cache的映射关系。TransactionalCache实现了Cache接口,CachingExecutor会默认使用他包装初始生成的Cache,作用是如果事务提交,对缓存的操作才会生效,如果事务回滚或者不提交事务,则不对缓存产生影响。在TransactionalCache的clear,有以下两句。清空了需要在提交时加入缓存的列表,同时设定提交时清空缓存)

    总结

    1. MyBatis的二级缓存相对于一级缓存来说,实现了SqlSession之间缓存数据的共享,同时粒度更加的细,能够到namespace级别,通过Cache接口实现类不同的组合,对Cache的可控性也更强。
    2. MyBatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
    3. 在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis的Cache接口实现,有一定的开发成本,直接使用Redis,Memcached等分布式缓存可能成本更低,安全性也更高。
    展开全文
  • 目录 * 此文仅是个人自学理解的学习笔记,若有不对之处敬请指导更正,谢谢! * 目录 ...方法:clone ...方法: ant-design-pro-cli ...Ant Design Pro 是个企业即用的中后台前...

    #目录
    ** 此文仅是个人自学理解的学习笔记,若有不对之处敬请指导更正,谢谢! **

    Ant Design Pro

    了解

    Ant Design Pro 是一个企业级开箱即用中后台前端/设计解决方案。符合阿里追求的**“敏捷的前端+强大的中台”**的思想。

    Ant Design Pro官网 ClickHere 》
    这里写图片描述

    前台/中台/后台的理解:

    1. 前台要求个性化,能体面公司的特色和对用户需求的满足(理解为用户看的页面)
    2. 中台是第三方业务运营方,是公司把部分业务开放后,为别的公司的业务赋能(实现可抽离的,非单纯属于公司内部运营业务,比如:定位,AI,语音,图像识别等)
    3. 后台是公司内部的业务运营平台(公司内部运营)

    开箱即用的理解:
    out-of-the-box:创造性的,非传统的,立即可以用的

    以我个人来看,在没使用Ant Design Pro的时候,做的中台用的都是像基于Bootstrap框架的ACE后台模板。点击这个ACE github,你可以看到这个是基于一堆的html网页,以及对应的js插件,其中你要用到那些效果就用对应的插件js文件,一开始可能还找不清那个文件是那个插件,插件的改造也难,后期维护难,没有一个很好的架构支撑类MVC思想,好在UI是不怎么写了,然而一个页面也会因为一个无需那么负责功能的插件而导致过大。而Ant Design Pro基于React,用了DvaJs这个基于 redux、redux-saga 和 react-router 的轻量级前端框架,将该项目以MVC的模式搭起来了,数据,组件用起来那也是风生水起,easyQAQ,clone下来后很多功能都是分装好,你可能只需要调个数据。。数据。。就ok了。。。环境也就有NPM,有node就ok,安装一下里面包里的依赖,你就可以查看使用里面的东西,并且拥有已经配置好了的严谨的开发格式(在devDependencies: 项目在开发和测试环境中依赖的包),用于协作代码的统一,语法的完整性。等等。
    基于Bootstrap框架的ACE
    (PS:大一的时候还觉得ACE的UI很高大上,用了Ant Design Pro才觉得甩了ACE不知道多少条街。。)


    知识储备

    HTML Less ReactJs ES6 DvaJs(新PRO用umiJs) Ant Design
    \ 我的笔记 Click Here>> 我的笔记 Click Here>> 我的笔记 Click Here>> \ \
    \ 文档 Click Here>> 文档 Click Here>> 文档 Click Here>> dvaJs文档 Click Here>>
    umiJs文档 Click Here>>
    文档 Click Here>>

    安装使用

    方法一:clone

    git clone https://github.com/ant-design/ant-design-pro.git --depth=1
    cd ant-design-pro
    npm install
    npm start
    

    ##方法二: ant-design-pro-cli
    全局安装集成化的 ant-design-pro-cli 工具,以后只需要pro new就可以直接构建这个项目了。个人是比较喜欢使用这个的。

    npm install ant-design-pro-cli -g
    mkdir pro-demo && cd pro-demo
    pro new
    

    依赖包安装完之后就可以开启项目了

    npm start
    

    若有报错可借鉴文末的:新手常遇到的问题


    实践

    ESlint

    ESLint是一种用于识别和报告ECMAScript / JavaScript代码中的模式的工具,其目标是使代码更加一致并避免错误。

    package.jspn里devDependencies放置项目在开发和测试环境中依赖的包,其中最明显作用的就是ESlint的包,这是一个语法规则和代码风格的检查工具,可以用来保证写出语法正确、风格统一的代码。

    开发过程中获得ESlint错误显示可给编辑器进行设置,此处以webstorm编辑器作为例子
    这里写图片描述
    若语法有错误,则如下图,显示红色波浪线,以及右边的红色色块提示。
    这里写图片描述
    其中还配合 eslint-config-prettier依赖实现规范代码格式。修改.prettierrc文件可设置自定义要求的规范格式。

    细嚼慢咽

    【2018.8.29】过两天实习完,要准备要开学的事宜,觉得此处要写详细得一段时间,提前发表,详细几天后再更=v =~~~

    【2018.9.7】开学至今都在实训以及处理实习证明,今天才知道antDesignPro在9月3号推了新版本v3.9.0,至此页面看起来没什么变化,目录上有了不少改变。其中npm i和cnpm i 都可以安装成功了。待我回炉重造!=n =。。。升级2.0迁移文档:前端构建工具从 roadhog 升级到了 UmiJS!! clickHere》
    这里写图片描述

    细嚼慢咽01理解目录文件结构【2018.9.13】

    dva 项目之前通常都是这种扁平的组织方式(即插件即插件、页面即页面、model即model的组织),用了 umi 后,可以按页面维度进行组织(即model、component**按照功能/页面维护**进行目录组织)。
    详细可参考:使用 umi 改进 dva 项目开发

    ├── config                   # umi 配置,包含路由,构建等配置!
    │	- router.config.js 	## 配置定义每个页面的布局、全局菜单的配置
    ├── mock                     # 本地模拟数据
    ├── public
    │   └── favicon.png          # Favicon
    ├── src
    │   ├── assets               # 本地静态资源
    │   ├── components           # 业务通用组件
    │   ├── e2e                  # 集成测试用例
    │   ├── layouts              # 通用布局
    │   ├── models               # 全局 dva model
    │   ├── pages                # 业务页面入口和常用模板
    	    ├── page01  ##同类型页面文件夹
    		    ├── models   ##对应页面处理数据和逻辑的model
    		        - page011.js
    		        - page012.js
    		        ├── ss
    		          - s.js
    		     - page011.js  ##页面
    		     - page012.js
    		     - page01.less
    	    ├── page02
    		    ├── models
    		         -page021.js
    		     - page021.js
    	    - defaultSettings.js ##页面默认总体布局配置
    │   ├── services             # 后台接口服务
    │   ├── utils                # 工具库
    │   ├── locales              # 国际化资源
    │   ├── global.less          # 全局样式
    │   └── global.js            # 全局 JS
    ├── tests                    # 测试工具
    ├── README.md
    └── package.json
    

    特点:结构更加清晰了,减少耦合(独立,增加可维护性,),一删全删,方便 copy 和共享。

    细嚼慢咽02设置你想要的pro布局【2018.9.13】

    修改src/defaultSettings.js文件。

    module.exports  = {
      navTheme : ' dark ',//导航菜单的主题
      primaryColor : '#1890FF ',//蚂蚁设计的原色
      layout: ' sidemenu ',//导航菜单位置:sidemenu或topmenu
      contentWidth : ' Fluid ',//内容布局:Fluid或Fixed,仅在布局为topmenu时有效
      fixedHeader : false,//粘性标题
      autoHideHeader : false,//自动隐藏标题
      fixSiderbar : false,//粘性siderbar
    };
    

    细嚼慢咽03创建新页面

    待续。。。


    新手常遇到的问题

    【2018.9.3号之后的v3.9.0版本无此问题!】当npm start不能启动完成后会自动打开浏览器访问,但是报错的是The ‘decorators’ plugin requires a ‘decoratorsBeforeExport’ option, whose value must be a boolean.
    解决:如果只是初始项目,把项目中的node_modules文件删除,然后npm install PS:不要用cnpm!!!不要用cnpm!!!不要用cnpm!!!

    问题具体查看issue,大部分问题都有 ClickHere 》


    实战代码Download

    还没push,待续。。。


    快捷链接

    全部React学习笔记的目录 Click Here>>
    全部Javascript学习笔记的目录 Click Here>>
    Less学习笔记 Click Here>>
    安利一波前端开发推荐使用的工具 Click Here>>
    ESLint问题记录 Click Here>>
    github各类实战练习源码下载 Click Here>>
    如果你觉得我的东西能帮到你,无限欢迎给我的github库点个收藏Star~0v 0~


    个人不打算从事程序媛,单纯为兴趣学习不定时更新 。其他特别代码都因为某些原因分开放置以下库

    coding个人主页 Click Here>>
    码云个人主页 Click Here>>


    展开全文
  • 这是我的开篇之作~之前没有写博客的习惯,一般是将笔记存本地,但久而久之发现回看不便,而且无法与大家交流学习。现特此写下开篇之作,若有疏漏之处,敬请指正,谢谢! 本文对遗传算法的原理进行梳理,相关代码...

    加了个小目录~方便定位查看~

    前言

    正文

    一. 基础术语:

    二. 遗传算法基本算子:

    三.完整实现遗传算法:

    四.后记:


    前言

    网上有很多博客讲解遗传算法,但是大都只是“点到即止”,虽然给了一些代码实现,但也是“浅尝辄止”,没能很好地帮助大家进行扩展应用,抑或是进行深入的研究。

    这是我的开篇之作~之前没有写博客的习惯,一般是将笔记存本地,但久而久之发现回看不便,而且无法与大家交流和学习。现特此写下开篇之作,若有疏漏之处,敬请指正,谢谢!

    本文对遗传算法的原理进行梳理,相关代码是基于国内高校学生联合团队开源的高性能实用型进化算法工具箱geatpy来实现,部分教程引用了geatpy的官方文档:

    http://geatpy.com/index.php/details/

    geatpy官网:http://www.geatpy.com

    若有错误之处,恳请同志们指正和谅解,谢谢!

    因为是基于geatpy遗传和进化算法工具箱,所以下文的代码部分在执行前,需要安装geatpy:

    pip install geatpy

    安装时会自动根据系统版本匹配下载安装对应的版本。这里就有个小坑:如果最新版Geatpy没有与当前版本相匹配的包的话,会自动下载旧版的包。而旧版的包在Linux和Mac下均不可用。

    安装好后,在Python中输出版本检查是否是最新版(version 2.6.0):

    import geatpy as ea
    print(ea.__version__)
    

    下面切入主题:

    自然界生物在周而复始的繁衍中,基因的重组、变异等,使其不断具有新的性状,以适应复杂多变的环境,从而实现进化。遗传算法精简了这种复杂的遗传过程而抽象出一套数学模型,用较为简单的编码方式来表现复杂的现象,并通过简化的遗传过程来实现对复杂搜索空间的启发式搜索,最终能够在较大的概率下找到全局最优解,同时与生俱来地支持并行计算。

    下图展示了常规遗传算法(左侧) 和某种在并行计算下的遗传算法(右侧) 的流程。

    本文只研究经典的遗传算法,力求最后能够解决各种带约束单目标优化问题,并能够很轻松地进行扩展,让大家不仅学到算法理论,还能轻松地通过“复制粘贴”就能够将相关遗传算法代码结合到各类不同的现实问题的求解当中。

    从上面的遗传算法流程图可以直观地看出,遗传算法是有一套完整的“固定套路”的,我们可以把这个“套路”写成一个“算法模板”,即把:种群初始化、计算适应度、选择、重组、变异、生成新一代、记录并输出等等这些基本不需要怎么变的“套路”写在一个函数里面,而经常要变的部分:变量范围、遗传算法参数等写在这个函数外面,对于要求解的目标函数,由于在遗传进化的过程中需要进行调用目标函数进行计算,因此可以把目标函数、约束条件写在另一个函数里面。

    另外我们还可以发现,在遗传算法的“套路”里面,执行的“初始化种群”、“选择”、“重组”、“变异”等等,其实是一个一个的“算子”,在geatpy工具箱里,已经提供现行的多种多样的进化算子了,因此直接调用即可。

    Geatpy工具箱提供一个面向对象的进化算法框架,因此一个完整的遗传算法程序就可以写成这个样子:

    关于算法框架的详细介绍可参见:http://geatpy.com/index.php/geatpy%E6%95%99%E7%A8%8B/

    下面就来详细讲一下相关的理论和代码实现:

    正文

    一. 基础术语:

        先介绍一下遗传算法的几个基础的术语,分别是:”个体“、”种群“、”编码与解码“、”目标函数值“、”适应度值“。

    1.个体:“个体”其实是一个抽象的概念,与之有关的术语有:

    (1)个体染色体:即对决策变量编码后得到的行向量。

    比如:有两个决策变量x1=1,x2=2,各自用3位的二进制串去表示的话,写成染色体就是:

    (2)个体表现型:即对个体染色体进行解码后,得到的直接指代各个控制变量的值的行向量。

     比如对上面的染色体“0 0 1 0 1 0”进行解码,得到 “1 2”,它就是个体的表现型,可看出该个体存储着两个变量,值分别是1和2。

    注意:遗传算法中可以进行“实值编码”,即可以不用二进制编码,直接用变量的实际值来作为染色体。这个时候,个体的染色体数值上是等于个体的表现型的。

    (3)染色体区域描述器:用于规定染色体每一位元素范围,详细描述见下文。

    2. 种群:“种群”也是一个抽象的概念,与之有关的术语有:

    (1)种群染色体矩阵(Chrom):它每一行对应一个个体的染色体。此时会发出疑问:一个个体可以有多条染色体吗?答:可以有,但一般没有必要,一条染色体就可以存储很多决策变量的信息了,如果要用到多条染色体,可以用两个种群来表示。

    例如:

    它表示有3个个体(因为有3行),因此有3条染色体。此时需要注意:这些染色体代表决策变量的什么值,我们是不知道的,我们也不知道该种群的染色体采用的是什么编码。染色体具体代表了什么,取决于我们采用什么方式去解码。假如我们采用的是二进制的解码方式,并约定上述的种群染色体矩阵中前3列代表第一个决策变量,后3列代表第二个决策变量,那么,该种群染色体就可以解码成:

    (2)种群表现型矩阵(Phen):它每一行对应一个个体的表现型。比如上图就是根据Chrom种群染色体矩阵解码得到的种群表现型矩阵。同样地,当种群染色体采用的是“实值编码”时,种群染色体矩阵与表现型矩阵实际上是一样的。

    (3)种群个体违反约束程度矩阵(CV):它每一行对应一个个体,每一列对应一种约束条件(可以是等式约束或不等式约束)。CV矩阵中元素小于或等于0表示对应个体满足对应的约束条件,大于0则表示不满足,且越大表示违反约束条件的程度越高。比如有两个约束条件:

    如何计算CV矩阵?可以创建两个列向量CV1和CV2,然后把它们左右拼合而成一个CV矩阵。

    假设x1、x2、x3均为存储着种群所有个体的决策变量值的列向量(这里可以利用种群表现型矩阵Phen得到,比如x1=Phen[:, [0]];x2=Phen[:, [1]]);x3=Phen[:, [2]]),这样就可以得到种群所有个体对应的x1、x2和x3)。

    那么:

    比如在某一代中,种群表现型矩阵Phen为:

    则有:

    此时CV矩阵的值为:

    由此可见,第一个个体满足两个约束条件;第二个个体违反了2个约束条件;第三和第四个个体满足第一个约束条件但违反了第二个约束条件。

    下面看下如何用代码来生成一个种群染色体矩阵:

    代码1. 实整数值种群染色体矩阵的创建:

    import numpy as np
    from geatpy import crtpc
    help(crtpc) # 查看帮助
    # 定义种群规模(个体数目)
    Nind = 4
    Encoding = 'RI' # 表示采用“实整数编码”,即变量可以是连续的也可以是离散的
    # 创建“区域描述器”,表明有4个决策变量,范围分别是[-3.1, 4.2], [-2, 2],[0, 1],[3, 3],
    # FieldDR第三行[0,0,1,1]表示前两个决策变量是连续型的,后两个变量是离散型的
    FieldDR=np.array([[-3.1, -2, 0, 3],
                      [ 4.2,  2, 1, 5],
                      [ 0,    0, 1, 1]])
    # 调用crtri函数创建实数值种群
    Chrom=crtpc(Encoding, Nind, FieldDR)
    print(Chrom)
    

    代码1的运行结果:

    这里要插入讲一下“区域描述器”(见代码1中的FieldDR),它是用于描述种群染色体所表示的决策变量的一些信息,如变量范围、连续/离散性。另外还有一种区域描述器(FIeldD),用于描述二进制/格雷码的种群染色体。FieldDR和FieldD两个合称“Field”,又可以认为它们是“译码矩阵”。FieldDR具有以下的数据结构:

    代码1中的FieldDR矩阵的第三行即为这里的varTypes。它如果为0,表示对应的决策变量是连续型的变量;为1表示对应的是离散型变量。

    另一种则是用于二进制/格雷编码种群染色体解码的译码矩阵FieldD,它是具有以下结构的矩阵:

    其中,lens, lb, ub, codes, scales, lbin, ubin, varTypes均为长度等于决策变量个数的行向量。

    lens 包含染色体的每个子染色体的长度。sum(lens) 等于染色体长度。

    lb 和ub 分别代表每个决策变量的上界和下界。

    codes 指明染色体子串用的是二进制编码还是格雷编码。codes[i] = 0 表示第i 个变量使用的是标准二进制编码;codes[i] = 1 表示使用格雷编码。

    scales 指明每个子串用的是算术刻度还是对数刻度。scales[i] = 0 为算术刻度,scales[i] = 1 为对数刻度。对数刻度可以用于变量的范围较大而且不确定的情况,对于大范围的参数边界,对数刻度让搜索可用较少的位数,从而减少了遗传算法的计算量。(注意:当某个变量是对数刻度时,其取值范围中不能有0,即要么上下界都大于0,要么上下界都小于0。)

    从2.5.0版本开始,取消了对对数刻度的支持,该参数暂时保留,但不在起作用。

    lbin 和ubin 指明了变量是否包含其范围的边界。0 表示不包含边界;1 表示包含边界。

    varTypes 指明了决策变量的类型,元素为0 表示对应位置的决策变量是连续型变量;1 表示对应的是离散型变量。

    对于二进制编码,二进制种群的染色体具体代表决策变量的什么含义是不由染色体本身决定的,而是由解码方式决定的。因此在创建二进制种群染色体之初就要设定好译码矩阵(又称“区域描述器”)。

    因此,可以通过以下代码构建一个二进制种群染色体矩阵

    代码2. 二进制种群染色体矩阵的创建:

    import numpy as np
    from geatpy import crtpc
    help(crtpc) # 查看帮助
    # 定义种群规模(个体数目)
    Nind = 4
    Encoding = 'BG' # 表示采用“实整数编码”,即变量可以是连续的也可以是离散的
    # 创建“译码矩阵”
    FieldD = np.array([[3, 2], # 各决策变量编码后所占二进制位数,此时染色体长度为3+2=5
                       [0, 0], # 各决策变量的范围下界
                       [7, 3], # 各决策变量的范围上界
                       [0, 0], # 各决策变量采用什么编码方式(0为二进制编码,1为格雷编码)
                       [0, 0], # 各决策变量是否采用对数刻度(0为采用算术刻度)
                       [1, 1], # 各决策变量的范围是否包含下界(对bs2int实际无效,详见help(bs2int))
                       [1, 1], # 各决策变量的范围是否包含上界(对bs2int实际无效)
                       [0, 0]])# 表示两个决策变量都是连续型变量(0为连续1为离散)
    # 调用crtpc函数来根据编码方式和译码矩阵来创建种群染色体矩阵
    Chrom=crtpc(Encoding, Nind, FieldD)
    print(Chrom)
    

    代码2运行结果:

    3. 编码与解码

    对于实整数编码(即上面代码1所创建的实整数种群染色体),它是不需要解码,染色体直接就对应着它所代表的决策变量值。而对于代码2生成的二进制种群染色体矩阵,它需要根据译码矩阵FieldD来进行解码。在代码2后面添加以下语句即可解码:

    代码3(上接代码2):

    from geatpy import bs2ri
    help(bs2ri)
    Phen = bs2ri(Chrom, FieldD)
    print('表现型矩阵 = \n', Phen)

    运行效果如下:

    4.目标函数值

    种群的目标函数值存在一个矩阵里面(一般命名为ObjV),它每一行对应一个个体的目标函数值。对于单目标而言,这个目标函数值矩阵只有1列,而对于多目标而言,就有多列了,比如下面就是一个含两个目标的种群目标函数值矩阵:

    (这里Nind表示种群的规模,即种群含多少个个体;Nvar表示决策变量的个数)

    下面来跑一下代码,接着代码3,在对二进制染色体解码成整数值种群后,我们希望计算出f(x,y)=x+y这个目标函数值。同时设置一个等式约束:要求x + y = 3。于是完整代码如下:

    代码4:

    import numpy as np
    from geatpy import crtpc
    from geatpy import bs2ri
    
    def aim(Phen):
        x = Phen[:, [0]] # 取出种群表现型矩阵的第一列,得到所有个体的决策变量x
        y = Phen[:, [1]] # 取出种群表现型矩阵的第二列,得到所有个体的决策变量y
        CV = np.abs(x + y - 3) # 生成种群个体违反约束程度矩阵CV,以处理等式约束:x + y == 3
        f = x + y # 计算目标函数值
        return f, CV # 返回目标函数值矩阵
    
    # 定义种群规模(个体数目)
    Nind = 4
    Encoding = 'BG' # 表示采用“实整数编码”,即变量可以是连续的也可以是离散的
    # 创建“译码矩阵”
    FieldD = np.array([[3, 2], # 各决策变量编码后所占二进制位数,此时染色体长度为3+2=5
                       [0, 0], # 各决策变量的范围下界
                       [7, 3], # 各决策变量的范围上界
                       [0, 0], # 各决策变量采用什么编码方式(0为二进制编码,1为格雷编码)
                       [0, 0], # 各决策变量是否采用对数刻度(0为采用算术刻度)
                       [1, 1], # 各决策变量的范围是否包含下界(对bs2int实际无效,详见help(bs2int))
                       [1, 1], # 各决策变量的范围是否包含上界(对bs2int实际无效)
                       [0, 0]])# 表示两个决策变量都是连续型变量(0为连续1为离散)
    # 调用crtpc函数来根据编码方式和译码矩阵来创建种群染色体矩阵
    Chrom=crtpc(Encoding, Nind, FieldD)
    print('二进制染色体矩阵 = \n', Chrom)
    
    # 解码
    Phen = bs2ri(Chrom, FieldD)
    print('表现型矩阵 = \n', Phen)
    
    # 计算目标函数值矩阵
    ObjV, CV = aim(Phen)
    print('目标函数值矩阵 = \n', ObjV)
    print('CV矩阵 = \n', CV)
    

    运行结果如下:

    由上面对CV矩阵的描述可知,第三个个体的CV值为0,表示第三个个体满足x+y=3这个等式约束。其他都大于0,表示不满足该约束。

    疑问:CV矩阵有什么用呢?

    :CV矩阵既可用于标记非可行解,在含约束条件的优化问题中有用,又可用于度量种群个体违反各个约束条件的程度的高低。对于含约束条件的优化问题,我们可以采用罚函数或者是可行性法则来进行处理。罚函数法这里就不展开赘述了,最简单的罚函数可以是直接找到非可行解个体的索引,然后修改其对应的ObjV的目标函数值即可。

    对于可行性法则,它需要计算每个个体违反约束的程度,并把结果保存在种群类的CV矩阵中。CV矩阵的每一行对应一个个体、每一列对应一个约束条件(可以是等式约束也可以是不等式约束),CV矩阵中元素小于或等于0表示对应个体满足对应的约束条件,否则是违反对应的约束条件,大于0的值越大,表示违反约束的程度越高。生成CV标记之后,在后面调用适应度函数计算适应度时,只要把CV矩阵作为函数传入参数传进函数体,就会自动根据CV矩阵所描述的种群个体违反约束程度来计算出合适的种群个体适应度。

    5.适应度值

    适应度值通俗来说就是对种群个体的”生存能力的评价“。对于简单的单目标优化,我们可以简单地把目标函数值直接当作是适应度值(注意:当用geatpy遗传和进化算法工具箱时,则需要对目标函数值加个负号才能简单地把它当作适应度值,因为geatpy遵循的是”目标函数值越小,适应度值越大“的约定。)

    对于多目标优化,则需要根据“非支配排序”或是其他方法来确定种群个体的适应度值,本文不对其展开叙述。

    种群适应度(FitnV):它是一个列向量,每一行代表一个个体的适应度值:

    (这里Nind表示种群的规模,即种群含多少个个体)

    geatpy遗传和进化算法工具箱里面有几个函数可以计算种群个体的适应度 ,分别是:

    ranking、indexing、powing、scaling。具体的用法,可以用help命令查看,如help(ranking)。

    下面接着代码4,利用ranking(基于目标函数值排序的适应度分配)计算种群的适应度:

    代码5(接着代码4):

    from geatpy import ranking
    help(ranking)
    FitnV = ranking(ObjV, CV)
    print('种群适应度矩阵 = \n', FitnV)

    运行结果:

    分析这个结果我们发现,由于第1、2、4个体违反约束条件,而第三个个体满足约束条件,因此第3个个体的适应度最高。而在第1、2、4个体中,个体1的目标函数值最大,因此适应度最低。可见遵循“最小化目标”的约定,即目标函数值越小,适应度越大。

    好了,基本的术语和用法讲完后,下面讲一下遗传算法的基本算子:

     

    二. 遗传算法基本算子:

    我们不用破费时间去写底层的遗传算子,因为geatpy工具箱提供丰富的进化算子,以下所列算子不仅限于遗传算子:

     

     (如果是在iPython 控制台中调用可视化绘图函数(例如使用Spyder 开发工具),一般图像会默认显示在控制台或者是开发工具中。此时可以在iPython控制台下执行%matplotlib 来设置把图像显示在一个独立窗口中。)

    对于多目标优化,Geatpy中内置以下算子:

    可以用help(算子名)来查看对应的API文档,查看更详细的用法和例子。

    下面讲一下理论:

    1.选择:

    在进化算法中存在两个阶段的选择。第一次是参与进化操作的个体的选择。这个阶段的选择可以是基于个体适应度的、也可以是完全随机地选择交配个体。一旦个体被选中,那么它们就会参与交叉、变异等进化操作。未被选中的个体不会参与到进化操作中。

    第二次是常被称为“重插入”或“环境选择”的选择,它是指在个体经过交叉、变异等进化操作所形成的子代(或称“育种个体”)后用某种方法来保留到下一代从而形成新一代种群的过程。这个选择过程对应的是生物学中的” 自然选择”。它可以是显性地根据适应度(再次注意:适应度并不等价于目标函数值)来进行选择的,也可以是隐性地根据适应度(即不刻意去计算个体适应度)来选择。例如在多目标优化的NSGA-II 算法中,父代与子代合并后,处于帕累托分层中第一层级的个体以及处于临界层中的
    且拥挤距离最大的若干个个体被保留到下一代。这个过程就没有显性地去计算每个个体的适应度。

    经典的选择算子有:“轮盘赌选择”、“随机抽样选择”、“锦标赛选择”、“本地选择”、“截断选择”、“一对一生存者竞争选择”等等,这里不展开叙述了,可以参考:

    http://geatpy.com/index.php/2019/07/28/%E7%AC%AC%E5%9B%9B%E7%AB%A0%EF%BC%9A%E9%80%89%E6%8B%A9/

    这里要注意:遗传算法选择出的后代是可以有重复的。

    下面以低级选择函数:锦标赛选择算子(tour)为例,使用help(tour)查看其API,得到:

    实战演练如下:

    代码6:

    import numpy as np
    from geatpy import tour
    
    help(tour)
    FitnV = np.array([[1.2],[0.8],[2.1], [3.2],[0.6],[2.2],[1.7],[0.2]])
    chooseIdx = tour(FitnV, 6)
    print('个体的适应度为:\n', FitnV)
    print('选择出的个体的下标为:\n', chooseIdx)
    

    运行结果:

    光这样还不够,这里只是得出了选择个体的下标,如果我们需要得到被选中个体的染色体,同时尝试改用高级选择函数“selecting”来调用低级选择算子“tour”来进行选择,则可以如下操作:

    代码7:

    import numpy as np
    from geatpy import selecting
    
    help(selecting)
    Chrom=np.array([[1,11,21],
    [2,12,22],
    [3,13,23],
    [4,14,24],
    [5,15,25],
    [6,16,26],
    [7,17,27],
    [8,18,28]])
    
    FitnV = np.array([[1.2],[0.8],[2.1], [3.2],[0.6],[2.2],[1.7],[0.2]])
    SelCh = Chrom[selecting('tour', FitnV, 6), :] # 使用'tour'锦标赛选择算子,同时片取Chrom得到所选择个体的染色体
    print('个体的适应度为:\n', FitnV)
    print('选择后得到的种群染色矩阵为:\n', SelCh)
    

    代码7运行结果如下:

    将代码7中的'tour'换成工具箱中的其他选择算子的名称(如etour, rws, sus),就可以使用相应的选择算子进行选择。

    2.重组

    在很多的国内教材、博客文章、论文中,只提到“交叉”的概念。事实上,遗传算法的“交叉”是属于“重组”算子里面的。因为交叉算子经常使用,而对于“离散重组”、“中间重组”、“线性重组”等等,因为用的很少,所以我们常常只谈“交叉”算子了。交叉算子实际上是“值互换重组”(Values exchanged recombination)。这里就不展开叙述了,可以参考:

    http://geatpy.com/index.php/2019/07/28/%E7%AC%AC%E4%BA%94%E7%AB%A0%EF%BC%9A%E9%87%8D%E7%BB%84/

    与重组有关的遗传算法参数是“重组概率”(对于交叉而言就是“交叉概率”)(Pc),它是指两个个体的染色体之间发生重组的概率。

    下面以两点交叉(xovdp)为例,类似上面的“tour”那样我们使用help(xovdp)查看其API:

    代码实战演练如下:

    代码8:

    from geatpy import xovdp
    import numpy as np
    
    help(xovdp)
    OldChrom=np.array([[1,1,1,1,1],[1,1,1,1,1],[0,0,0,0,0],[0,0,0,0,0]]) #创建一个二进制种群染色体矩阵
    print('交叉前种群染色矩阵为:\n', OldChrom)
    NewChrom = xovdp(OldChrom, 1) # 设交叉概率为1
    print('交叉后种群染色矩阵为:\n', NewChrom)
    

    代码8运行结果如下:

    由此可见,xovdp是将前一半个体和后一半个体进行配对交叉的,有人认为应该随机选择交叉个体。事实上,在遗传算法进化过程中,各个位置的个体是什么,本身是随机的,不必要在交叉这里再增添一个随机(当然,可以在执行xovdp两点交叉之前,将种群染色体矩阵按行打乱顺序然后再交叉,从而满足自身需求)。

    3.变异

    下面以均匀突变(mutuni)为例,编写代码实现实数值编码的种群染色体的均匀突变,使用help(mutuni)查看API文档。

    代码9:

    from mutuni import mutuni
    import numpy as np
    # 自定义种群染色体矩阵,表示有3个个体,且染色体元素直接表示变量的值(即实值编码)
    OldChrom = np.array([[9,10],
                         [10,10],
                         [10,10]])
    # 创建区域描述器(又称译码矩阵)
    FieldDR = np.array([[7,8],
                        [10,10],
                        [1, 1]])
    # 此处设编码方式为实值编码中的“实整数编码”RI,表示染色体可代表实数和整数
    NewChrom = mutuni('RI', OldChrom, FieldDR, 1)
    print(NewChrom)
    

    代码9的运行结果如下:

    4.重插入

    经典遗传算法通过选择、重组和变异后,我们得到的是育种后代,此时育种后代的个体数有可能会跟父代种群的个体数不相同。这时,为了保持种群的规模,这些育种后代可以重新插入到父代中,替换父代种群的一部分个体,或者丢弃一部分育种个体,最终形成新一代种群。前面曾提到过“重插入”也是一种选择,但它是环境选择,是用于生成新一代种群的;而前面在交叉变异之前的选择是用于选择个体参与交叉变异,那个选择常被称作“抽样”。

    现考虑使用精英个体保留的遗传算法“EGA”,则重插入操作如下:

    代码10:

    from mutuni import mutuni
    import numpy as np
    # 自定义父代种群染色体(仅作为例子):
    Chrom = np.array([[1.1, 1.3],
                      [2.4, 1.2],
                      [3,   2.1],
                      [4,   3.1]])
    # 若父代个体的适应度为:
    FitnV = np.array([[1],
                     [2],
                     [3],
                     [4]])
    # 考虑采用“精英保留策略”的遗传算法,此时从父代选择出4-1=3个个体,经过交叉变异后假设子代的染色体为:
    offChrom = np.array([[2.1, 2.3],
                         [2.3, 2.2],
                         [3.4, 1.1]])
    # 假设直接把目标函数值当作适应度,且认为适应度越大越好。则通过以下代码重插入生成新一代种群:
    bestIdx = np.argmax(FitnV) # 得到父代精英个体的索引
    NewChrom = np.vstack([Chrom[bestIdx, :], offChrom]) # 得到新一代种群的染色体矩阵
    print('新一代种群的染色体矩阵为:\n', NewChrom)
    

    在“EGA”中,假设父代种群规模为N,则选择出(N-1)个个体进行交叉变异,然后找出父代的精英个体,用着个精英个体和交叉变异得到的子代个体进行“拼合”,得到新一代种群。

    除了这种重插入生成新一代种群的方法外,还有“父子两代个体合并选择”等更优秀的生成新一代种群的方法,这里就不一一赘述了。

    讲完上面的基本术语以及遗传算法基本算子后,我们就可以来利用遗传算法的“套路”编写一个遗传算法求解问题的程序了:

     

    三.完整实现遗传算法:

    上文提到遗传算法程序可以写成这个样子:

    其核心是算法模板类。在遗传算法模板里,我们根据遗传算法的“套路”,进行:初始化种群、目标函数值计算、适应度评价、选择、重组、变异、记录各代最优个体等操作。geatpy工具箱内置有开源的“套路模板”,源代码参见:

    https://github.com/geatpy-dev/geatpy/tree/master/geatpy/source-code/templets

    这些内置算法模板有详细的输入输出参数说明,以及遗传算法整个流程的完整注释,它们可以应对简单或复杂的、单目标优化的、多目标优化的、约束优化的、组合优化的等等的问题。

    但为了学习,我这里先不采用框架,直接利用工具箱提供的库函数来写一个带精英个体保留的遗传算法。这样代码量比较大,但有利于入门。

    编写代码 11、12,分别放在同一个文件夹下:

    代码11(目标函数aimfuc.py)(这里要回顾一下前面,Phen是种群表现型矩阵,存储的是种群所有个体的表现型,而不是单个个体。因而计算得到的目标函数值矩阵也是包含所有个体的目标函数值):

    # -*- coding: utf-8 -*-
    """
    aimfunc.py - 目标函数文件
    描述:
        目标:max f = 21.5 + x1 * np.sin(4 * np.pi * x1) + x2 * np.sin(20 * np.pi * x2)
        约束条件:
            x1 != 10
            x2 != 5
            x1 ∈ [-3, 12.1] # 变量范围是写在遗传算法的参数设置里面
            x2 ∈ [4.1, 5.8]
    """
    
    import numpy as np
    
    def aimfunc(Phen, CV):
        x1 = Phen[:, [0]] # 获取表现型矩阵的第一列,得到所有个体的x1的值
        x2 = Phen[:, [1]]
        f = 21.5 + x1 * np.sin(4 * np.pi * x1) + x2 * np.sin(20 * np.pi * x2)
        exIdx1 = np.where(x1 == 10)[0] # 因为约束条件之一是x1不能为10,这里把x1等于10的个体找到
        exIdx2 = np.where(x2 == 5)[0]
        CV[exIdx1] = 1
        CV[exIdx2] = 1
        return [f, CV]
    

    然后是编写算法:

    代码12:

    # -*- coding: utf-8 -*-
    """main.py"""
    import numpy as np
    import geatpy as ea # 导入geatpy库
    from aimfunc import aimfunc # 导入自定义的目标函数
    import time
    
    """============================变量设置============================"""
    x1 = [-3, 12.1] # 第一个决策变量范围
    x2 = [4.1, 5.8] # 第二个决策变量范围
    b1 = [1, 1] # 第一个决策变量边界,1表示包含范围的边界,0表示不包含
    b2 = [1, 1] # 第二个决策变量边界,1表示包含范围的边界,0表示不包含
    ranges=np.vstack([x1, x2]).T # 生成自变量的范围矩阵,使得第一行为所有决策变量的下界,第二行为上界
    borders=np.vstack([b1, b2]).T # 生成自变量的边界矩阵
    varTypes = np.array([0, 0]) # 决策变量的类型,0表示连续,1表示离散
    """==========================染色体编码设置========================="""
    Encoding = 'BG' # 'BG'表示采用二进制/格雷编码
    codes = [0, 0] # 决策变量的编码方式,设置两个0表示两个决策变量均使用二进制编码
    precisions =[4, 4] # 决策变量的编码精度,表示二进制编码串解码后能表示的决策变量的精度可达到小数点后6位
    scales = [0, 0] # 0表示采用算术刻度,1表示采用对数刻度
    FieldD = ea.crtfld(Encoding,varTypes,ranges,borders,precisions,codes,scales) # 调用函数创建译码矩阵
    """=========================遗传算法参数设置========================"""
    NIND      = 100; # 种群个体数目
    MAXGEN    = 200; # 最大遗传代数
    maxormins = [-1] # 列表元素为1则表示对应的目标函数是最小化,元素为-1则表示对应的目标函数是最大化
    selectStyle = 'rws' # 采用轮盘赌选择
    recStyle  = 'xovdp' # 采用两点交叉
    mutStyle  = 'mutbin' # 采用二进制染色体的变异算子
    pc        = 0.7 # 交叉概率
    pm        = 1 # 整条染色体的变异概率(每一位的变异概率=pm/染色体长度)
    Lind = int(np.sum(FieldD[0, :])) # 计算染色体长度
    obj_trace = np.zeros((MAXGEN, 2)) # 定义目标函数值记录器
    var_trace = np.zeros((MAXGEN, Lind)) # 染色体记录器,记录历代最优个体的染色体
    """=========================开始遗传算法进化========================"""
    start_time = time.time() # 开始计时
    Chrom = ea.crtpc(Encoding, NIND, FieldD) # 生成种群染色体矩阵
    variable = ea.bs2ri(Chrom, FieldD) # 对初始种群进行解码
    CV = np.zeros((NIND, 1)) # 初始化一个CV矩阵(此时因为未确定个体是否满足约束条件,因此初始化元素为0,暂认为所有个体是可行解个体)
    ObjV, CV = aimfunc(variable, CV) # 计算初始种群个体的目标函数值
    FitnV = ea.ranking(maxormins * ObjV, CV) # 根据目标函数大小分配适应度值
    best_ind = np.argmax(FitnV) # 计算当代最优个体的序号
    # 开始进化
    for gen in range(MAXGEN):
        SelCh = Chrom[ea.selecting(selectStyle,FitnV,NIND-1),:] # 选择
        SelCh = ea.recombin(recStyle, SelCh, pc) # 重组
        SelCh = ea.mutate(mutStyle, Encoding, SelCh, pm) # 变异
        # 把父代精英个体与子代的染色体进行合并,得到新一代种群
        Chrom = np.vstack([Chrom[best_ind, :].astype(int), SelCh])
        Phen = ea.bs2ri(Chrom, FieldD) # 对种群进行解码(二进制转十进制)
        ObjV, CV = aimfunc(Phen, CV) # 求种群个体的目标函数值
        FitnV = ea.ranking(maxormins * ObjV, CV) # 根据目标函数大小分配适应度值
        # 记录
        best_ind = np.argmax(FitnV) # 计算当代最优个体的序号
        obj_trace[gen,0]=np.sum(ObjV)/ObjV.shape[0] #记录当代种群的目标函数均值
        obj_trace[gen,1]=ObjV[best_ind] #记录当代种群最优个体目标函数值
        var_trace[gen,:]=Chrom[best_ind,:] #记录当代种群最优个体的染色体
    # 进化完成
    end_time = time.time() # 结束计时
    ea.trcplot(obj_trace, [['种群个体平均目标函数值', '种群最优个体目标函数值']]) # 绘制图像
    """============================输出结果============================"""
    best_gen = np.argmax(obj_trace[:, [1]])
    print('最优解的目标函数值:', obj_trace[best_gen, 1])
    variable = ea.bs2ri(var_trace[[best_gen], :], FieldD) # 解码得到表现型(即对应的决策变量值)
    print('最优解的决策变量值为:')
    for i in range(variable.shape[1]):
        print('x'+str(i)+'=',variable[0, i])
    print('用时:', end_time - start_time, '秒')

    执行代码12得到如下结果:

    终于,我们把遗传算法完整地实现了,但扩展性还不够高。下面学习下如何使用Geatpy提供的进化算法框架来求解上述问题:(关于使用框架来优化的介绍可详见http://geatpy.com/index.php/geatpy%E6%95%99%E7%A8%8B/

    在这里我们可以回顾以下在本文开头提到的采用遗传算法的“套路”来编程求解问题的基本流程:

    其中执行脚本和问题类是需要编写的,算法模板类我直接调用Geatpy内置的"soea_EGA_templet"(带精英个体保留的单目标遗传算法模板)。下面开始编写代码:

    代码13:问题类"MyProblem"的编写:

    # -*- coding: utf-8 -*-
    """
    MyProblem.py
    该案例展示了一个简单的连续型决策变量最大化目标的单目标优化问题。
    max f = x * np.sin(10 * np.pi * x) + 2.0
    s.t.
    -1 <= x <= 2
    """
    
    import numpy as np
    import geatpy as ea
    
    class MyProblem(ea.Problem): # 继承Problem父类
        def __init__(self):
            name = 'MyProblem' # 初始化name(函数名称,可以随意设置)
            M = 1 # 初始化M(目标维数)
            maxormins = [-1] # 初始化maxormins(目标最小最大化标记列表,1:最小化该目标;-1:最大化该目标)
            Dim = 2 # 初始化Dim(决策变量维数)
            varTypes = [0] * Dim # 初始化varTypes(决策变量的类型,元素为0表示对应的变量是连续的;1表示是离散的)
            lb = [-3, 4.1] # 决策变量下界
            ub = [12.1, 5.8] # 决策变量上界
            lbin = [1] * Dim # 决策变量下边界
            ubin = [1] * Dim # 决策变量上边界
            # 调用父类构造方法完成实例化
            ea.Problem.__init__(self, name, M, maxormins, Dim, varTypes, lb, ub, lbin, ubin)
        
        def aimFunc(self, pop): # 目标函数
            x1 = pop.Phen[:, [0]] # 获取表现型矩阵的第一列,得到所有个体的x1的值
            x2 = pop.Phen[:, [1]]
            f = 21.5 + x1 * np.sin(4 * np.pi * x1) + x2 * np.sin(20 * np.pi * x2)
            exIdx1 = np.where(x1 == 10)[0] # 因为约束条件之一是x1不能为10,这里把x1等于10的个体找到
            exIdx2 = np.where(x2 == 5)[0]
            pop.CV = np.zeros((pop.sizes, 2))
            pop.CV[exIdx1, 0] = 1
            pop.CV[exIdx2, 1] = 1
            pop.ObjV = f # 计算目标函数值,赋值给pop种群对象的ObjV属性
        

    第二步:编写执行脚本“main.py”

    # -*- coding: utf-8 -*-
    """main.py"""
    import geatpy as ea # import geatpy
    from MyProblem import MyProblem # 导入自定义问题接口
    """===============================实例化问题对象================================"""
    problem = MyProblem() # 生成问题对象
    """==================================种群设置=================================="""
    Encoding = 'BG'       # 编码方式
    NIND = 100            # 种群规模
    Field = ea.crtfld(Encoding, problem.varTypes, problem.ranges, problem.borders) # 创建区域描述器
    population = ea.Population(Encoding, Field, NIND) # 实例化种群对象(此时种群还没被初始化,仅仅是完成种群对象的实例化)
    """================================算法参数设置================================="""
    myAlgorithm = ea.soea_EGA_templet(problem, population) # 实例化一个算法模板对象
    myAlgorithm.MAXGEN = 200 # 最大进化代数
    myAlgorithm.logTras = 1  # 设置每隔多少代记录日志,若设置成0则表示不记录日志
    myAlgorithm.verbose = True  # 设置是否打印输出日志信息
    myAlgorithm.drawing = 1  # 设置绘图方式(0:不绘图;1:绘制结果图;2:绘制目标空间过程动画;3:绘制决策空间过程动画)
    """===========================调用算法模板进行种群进化==============--==========="""
    [BestIndi, population] = myAlgorithm.run()  # 执行算法模板,得到最优个体以及最后一代种群
    BestIndi.save()  # 把最优个体的信息保存到文件中
    """==================================输出结果=================================="""
    print('用时:%f 秒' % myAlgorithm.passTime)
    print('评价次数:%d 次' % myAlgorithm.evalsNum)
    if BestIndi.sizes != 0:
        print('最优的目标函数值为:%s' % BestIndi.ObjV[0][0])
        print('最优的控制变量值为:')
        for i in range(BestIndi.Phen.shape[1]):
            print(BestIndi.Phen[0, i])
    else:
        print('没找到可行解。')

    运行"main.py"执行脚本即可得到以下结果:

                                                                                                         ......

    代码解析:在“main.py”执行脚本中,一开始需要实例化一个问题对象。然后是种群对象的实例化。在实例化种群对象前,需要设定种群的编码方式Encoding、种群规模NIND,并且生成区域描述器Field(或称译码矩阵),因为种群类的构造方法中需要至少用到这三个参数(详见“Population.py”中种群类的构造方法)。

    在完成了问题类对象和种群对象的实例化后,将其传入算法模板类的构造方法来实例化一个算法模板对象。这里我实例化的是单目标优化的EGA算法(即带精英个体保留的遗传算法)的模板类对象,即代码中的"soea_EGA_templet"。里面的进化算法具体是如何操作的,可详见https://github.com/geatpy-dev/geatpy/blob/master/geatpy/templates/soeas/GA/EGA/soea_EGA_templet.py

    采用Geatpy提供的进化算法框架可以既能最大程度地描述清楚所要求解的问题,而且与进化算法是高度脱耦的,即上面在编写问题类的时候完全不需要管后面采用什么算法、采用什么样编码的种群,只需把问题描述清楚即可。

    而且,遗传算法有个好处是:目标函数可以写得相当复杂,可以解决各种复杂的问题,比如神经网络。以BP神经网络为例,可以把神经网络的参数作为决策变量,神经网络的训练误差作为目标函数值,只需把上面的例子修改一下就行了。

    而且,一般而言我们不需要像我刚刚最开始那样刻意去手写进化算法,可以直接调用geatpy内置的算法模板就可以解决问题了。geatpy工具箱提供这些内置的算法模板:

    应用Geatpy求解数学建模、工业设计、资源调度等实际优化问题的的朋友们可以直接使用这些算法模板快速解决各种灵活的优化问题。

    四.后记:

    最后十分感谢由Geatpy团队提供的高性能实用型遗传和进化算法工具箱,它提供开源的进化算法框架为遗传算法求解单目标/多目标优化、约束优化、组合优化等等给出了相当准确和快捷的解决方案。据称,geatpy的运行性能相当的高,远高于matlab的遗传算法工具箱、以及采用JAVA、matlab或者Python编写的一些进化优化平台或框架,比如jMetal、platemo、pymoo、deap等,后面有时间我将进行详细的性能对比实验分析,有相关经验的读者也可以自行对比性能。而且依我的体验来看,这是我网上到处找代码、找资料学习、碰了无数次壁后所看到的比较易学易用的工具箱了。

    最后值得注意的是Geatpy的顶层是面向对象的进化算法框架,底层是面向过程的进化算子。下面放一张geatpy的UML类图、算法调用的层次结构和库函数调用关系图,以此记录方便查看:

    下面附一张一位同行朋友使用犀牛软件(Rhinoceros)结合geatpy工具箱进行产品优化设计的截图:

    很多工程软件都提供Python接口,当需要用到进化优化时,就可以编写Python代码进行优化了。

    下一篇博客将介绍如何用遗传算法求解有向图的最短路径问题:

    https://blog.csdn.net/weixin_37790882/article/details/100622338

    后续我将继续学习和挖掘该工具箱的更多深入的用法。希望这篇文章在帮助自己记录学习点滴之余,也能帮助大家!

     

    展开全文
  • 页面左侧二级菜单20种案例

    千次阅读 多人点赞 2019-02-15 10:44:52
    jQuery作为款主流的JavaScript前端开发框架,深受广告开发者的亲睐,同时jQuery有着不计其数的插件,特别是菜单插件更为丰富,本文将要为大家介绍20个绚丽而实用的jQuery侧边栏菜单,这些侧边栏菜单可以用在不同...
  • 一级摆杆都是均匀的刚体; 忽略同步带的弹性; 驱动力与放大器输入成正比,并无延迟地施加于小车; 所有摩擦力足够小,可忽略不计; 粒子群寻优MATLAB主程序: %% 清空环境 clear clc %% 参数设置 w = 0....
  • 章-宝箱抽奖模块与代码设计(

    万次阅读 多人点赞 2016-08-24 14:53:31
    宝箱抽奖模块与代码设计()宝箱抽奖模块与代码设计 无聊的开场白 每篇文章的背后都有个高大上的故事 怎样的代码才算是好的代码 简单的需求 简单的代码Java实现 一些基本的类 玩家类 抽奖服务类 先大致确定抽奖...
  • 等保2.0二级安全要求

    千次阅读 2020-06-07 12:22:39
    二级安全保护能力:应能够防护免受来自外部小型组织的、拥有少量资源的威胁源发起的恶意攻击、一般的自然灾害,以及其他相当危害程度的威胁所造成的重要资源损害...以下加粗字段为等保二级一级区别,需重点关注。
  • 面试官:兄弟,说说基本类型包装类型的区别

    万次阅读 多人点赞 2019-09-29 08:36:07
    Java 的每个基本类型都对应了个包装类型,比如说 int 的包装类型为 Integer,double 的包装类型为 Double。基本类型包装类型的区别主要有以下 4 点。
  • 市面上的音箱形形色色,但无论哪种,都是由喇叭单元(术语叫扬声器单元)箱体这两大最基本的部分组成,另外,绝大多数音箱至少使用了两只或两只以上的喇叭单元实行所谓的多路分音重放,所以分频器也电告不可少的...
  • sap中二级品报工问题解决方案

    千次阅读 2012-05-28 16:05:42
    如工单中为100箱,实际产出90箱良品、8箱二级品,2箱废品。 1. co11n报工时产量填90箱、报废填10箱,工时填实际生产100箱的工时,成本分摊到90箱中 2、90箱入储运库:migo或mb31+101 3. 8箱二级品入库:mb1c+531 ...
  • 箱形图(Box-plot)又称为盒须图、盒式图或线图,是种用作显示组数据分散情况资料的统计图。因形状如箱子而得名。在各种领域也经常被使用,常见于品质管理。(来源:百度百科【型图】词条) 箱形图有5...
  • Java工具

    千次阅读 2019-01-13 21:19:43
    本文类似于个Java开发的“工具”,会记录一些Java开发者常用到的工具,例如:工具的一些环境配置、常用的技术网站等。本文不会下子就有很多内容,会不断的完善。 、JDK环境变量配置 Windows环境 1.首先...
  • 文章目录、智能合约库简介、痛点及解决方式痛点:计算可能溢出痛点:转换不够便捷痛点三:数组操作不够丰富痛点四:不提供字符串内置操作痛点五:高级数据结构不完备总结 、智能合约库简介 作为一门实现...
  • 知识点目录 考试内容(考试大纲) 基本要求 考试内容 、计算机基础知识 二、Word 的功能使用 ...计算机二级公共基础知识 计算机的发展、类型及其应用领域 计算机软硬件系统的组成及主要技术指标 计算机中...
  • 原标题:计算机二级证书含金量到底有多高?你真的知道吗?2019年9月(第56次)全国计算机等级考试定于9月21日至23日举行。在众多考试中,全国计算机等级考试可以说是相对热门也最基础的考试。然而就是这最基础的考试...
  • 遗传算法工具使用1

    千次阅读 2016-02-28 22:37:00
    使用谢菲尔德大学的MATLAB遗传算法工具 1.简单一元函数优化:计算函数最小值 f(x) = sin(10*pi*x)/x , x范围[1,2] clc clear all close all %%画出函数图 figure(1); hold on; lb = 1;ub = 2; %函数自变量范围 ...
  • HTML/CSS/JS实现二级菜单导航+轮播图

    万次阅读 多人点赞 2018-02-05 15:57:22
    学习了HTML、CSSJavaScript后,总得做点东西练练手,于是干脆跟着效果做了个类似于慕课网的轮播效果,配上二级菜单导航,具体效果如下: 首先来说说这个项目中用的比较多的知识点: CSS定位——绝对定位...
  • 集装RFID物流运输管理系统应用

    千次阅读 2020-10-13 10:22:09
    将RFID技术应用于集装物流运输管理,利用RFID技术在自动识别领域的优势,开发出集装信息化管理平台,大幅提高集装运输管理的效率效益是非常有意义的。 1.行业背景 集装是国际物流的主要运输装备,国际货运...
  • 外观工艺就是从音箱外表的第部象来判断该次品质优劣:用天然原木精工打造的音箱当然最好,许多天价的世界名牌至尊音箱,包括意大利的Chario(卓丽)、Guarneri Homage(名琴)等,但此类好因环保、资源匮乏...
  • 种群表示初始化 函数 bs2rv: 进制串到实值的转换  Phen=bs2rv(Chrom,FieldD)   FieldD=[len, lb, ub, code, scale, lbin, ubin]  code(i)=1为标准的进制编码,code(i)=0为格雷编码  
  • 【Java面试】:第章:P6面试

    万次阅读 多人点赞 2021-03-23 18:50:51
    红黑树二叉平衡树的区别,红黑树B树,B+树的区别,Mysql大引擎索引底层实现,HashMap在多线程环境中为何出错?ConcurrentHashMap底层实现,CAS,原子引用,ABA问题,volatile,如何解决HashMap出现的OOM问题?...
  • VSCode插件大全|VSCode高级玩家之第

    万次阅读 多人点赞 2020-04-25 17:55:05
    VSCode没有安装插件,就等同于把枪没有安装配件,打把的时候还是缺失精准度,开枪的时候也会有很重的后坐力。当然没有插件,也可以很好的编写代码,就是没有那么强而已。 所以这篇文章会给大家介绍VSCode中最常用...
  • python--matplotlib(线图)

    千次阅读 2018-04-25 21:12:01
    前言 今天继续我们的python绘图系列,针对离散变量我们可以使用常见的条形图饼图完成数据的可视化工作,那么,针对数值型变量,我们也有很多可视化的方法,例如线图、直方图、折线图、面积图、散点图等等。这...
  • 首先这是我第次做草缸,以前也没养过鱼,一切从零开始,很多知识都是来自度娘,论坛,qq群,再加上自己的见解。知道张大妈里卧虎藏龙,不乏有众多高手高高手,如果文章有哪些地方说错的,请大家指出来,谢谢! ...
  • GDAL工具详解之gdalwarp.exe

    万次阅读 2016-09-14 08:39:12
    本节主要介绍其中基本的项工具——gdalwarp.exe。 该工具位于GDAL文件夹下的apps文件夹中,属于命令行可执行程序。 目录 使用方法 GDAL帮助文件中栅格工具里关于gdalwarp.exe的描述如下: ...
  • MATLAB 图像处理工具 种简单的基于图像处理交通信号灯识别算法 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入段漂亮的代码片生成个...
  • matlab遗传算法gatbx工具介绍及安装说明

    万次阅读 多人点赞 2018-02-15 23:00:31
    . 遗传算法简介: 官网:http://www.geatpy.com/tutorials 遗传算法是以自然选择遗传理论为基础,将生物进化过程中适者生存规则与群体内部染色体的随机信息交换机制相结合的高效全局寻优搜索算法。 这样解释...
  • 1:先上效果图 ...两个地方 个是class里面的selectpage标签 个是data-multiple的多选。 OK! 要是你们能用到且没看懂可以关注我公众号( YoursAPI ) 下载笔记:(发送"selectpage",...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 88,659
精华内容 35,463
关键字:

一级箱和二级箱的区别