精华内容
下载资源
问答
  • 针对数据缺失情况下网络的结构特征是否能够保持,在现有文献研究基础上将无偏的随机抽样扩展到偏抽样,并将对幂律度分布这一单一结构特征的考察扩展到对网络多重结构特征的考察.通过对一个社会网络典型模型的仿真...
  • 社交网络中的多重影响最大化
  • 通过多重相关社交网络传播病毒影响的条件
  • 健康邻居对多重网络中流行病传播的影响
  • 移动社交网络多重分形影响因素分析
  • 行业分类-物理装置-一种有限填土条件下的土拱效应模拟装置及其试验方法.zip
  • 复杂行为响应对多重网络中非对称相互作用扩散动力学的影响
  • 在此基础上,计算网络度量指标,对比分析影响移动社交网络多重分形的内在影响因素实验结果表明,网络度分布表现为幂律分布,当同配系数r《0时,网络多重分形特征表现越明显,网络内部结构分布越不规则。
  • 为确保质量,每种无线通讯和连结方式都必须进行适当的测试,但如以传统的对每种通讯方式进行串行测试,生产成本必然非常高昂而最后直接影响产品价格和利润,如何能确保产品质量同时最少化测试时间和成本便成各厂商的...
  • Java基础知识面试题(2020最新版)

    万次阅读 多人点赞 2020-02-19 12:11:27
    原理是什么Java语言哪些特点什么是字节码?采用字节码的最大好处是什么什么是Java程序的主类?应用程序和小程序的主类何不同?Java应用程序与小程序之间那些差别?Java和C++的区别Oracle JDK 和 OpenJDK 的...

    Java面试总结(2021优化版)已发布在个人微信公众号【技术人成长之路】,优化版首先修正了读者反馈的部分答案存在的错误,同时根据最新面试总结,删除了低频问题,添加了一些常见面试题,对文章进行了精简优化,欢迎大家关注!😊😊

    【技术人成长之路】,助力技术人成长!更多精彩文章第一时间在公众号发布哦!

    文章目录

    Java面试总结汇总,整理了包括Java基础知识,集合容器,并发编程,JVM,常用开源框架Spring,MyBatis,数据库,中间件等,包含了作为一个Java工程师在面试中需要用到或者可能用到的绝大部分知识。欢迎大家阅读,本人见识有限,写的博客难免有错误或者疏忽的地方,还望各位大佬指点,在此表示感激不尽。文章持续更新中…

    序号内容链接地址
    1Java基础知识面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104390612
    2Java集合容器面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104588551
    3Java异常面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104390689
    4并发编程面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104863992
    5JVM面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104390752
    6Spring面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104397516
    7Spring MVC面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104397427
    8Spring Boot面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104397299
    9Spring Cloud面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104397367
    10MyBatis面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/101292950
    11Redis面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/103522351
    12MySQL数据库面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104778621
    13消息中间件MQ与RabbitMQ面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104588612
    14Dubbo面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104390006
    15Linux面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104588679
    16Tomcat面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104397665
    17ZooKeeper面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104397719
    18Netty面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104391081
    19架构设计&分布式&数据结构与算法面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/105870730

    Java概述

    何为编程

    编程就是让计算机为解决某个问题而使用某种程序设计语言编写程序代码,并最终得到结果的过程。

    为了使计算机能够理解人的意图,人类就必须要将需解决的问题的思路、方法、和手段通过计算机能够理解的形式告诉计算机,使得计算机能够根据人的指令一步一步去工作,完成某种特定的任务。这种人和计算机之间交流的过程就是编程。

    什么是Java

    Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程 。

    jdk1.5之后的三大版本

    • Java SE(J2SE,Java 2 Platform Standard Edition,标准版)
      Java SE 以前称为 J2SE。它允许开发和部署在桌面、服务器、嵌入式环境和实时环境中使用的 Java 应用程序。Java SE 包含了支持 Java Web 服务开发的类,并为Java EE和Java ME提供基础。
    • Java EE(J2EE,Java 2 Platform Enterprise Edition,企业版)
      Java EE 以前称为 J2EE。企业版本帮助开发和部署可移植、健壮、可伸缩且安全的服务器端Java 应用程序。Java EE 是在 Java SE 的基础上构建的,它提供 Web 服务、组件模型、管理和通信 API,可以用来实现企业级的面向服务体系结构(service-oriented architecture,SOA)和 Web2.0应用程序。2018年2月,Eclipse 宣布正式将 JavaEE 更名为 JakartaEE
    • Java ME(J2ME,Java 2 Platform Micro Edition,微型版)
      Java ME 以前称为 J2ME。Java ME 为在移动设备和嵌入式设备(比如手机、PDA、电视机顶盒和打印机)上运行的应用程序提供一个健壮且灵活的环境。Java ME 包括灵活的用户界面、健壮的安全模型、许多内置的网络协议以及对可以动态下载的连网和离线应用程序的丰富支持。基于 Java ME 规范的应用程序只需编写一次,就可以用于许多设备,而且可以利用每个设备的本机功能。

    JVM、JRE和JDK的关系

    JVM
    Java Virtual Machine是Java虚拟机,Java程序需要运行在虚拟机上,不同的平台有自己的虚拟机,因此Java语言可以实现跨平台。

    JRE
    Java Runtime Environment包括Java虚拟机和Java程序所需的核心类库等。核心类库主要是java.lang包:包含了运行Java程序必不可少的系统类,如基本数据类型、基本数学函数、字符串处理、线程、异常处理类等,系统缺省加载这个包

    如果想要运行一个开发好的Java程序,计算机中只需要安装JRE即可。

    JDK
    Java Development Kit是提供给Java开发人员使用的,其中包含了Java的开发工具,也包括了JRE。所以安装了JDK,就无需再单独安装JRE了。其中的开发工具:编译工具(javac.exe),打包工具(jar.exe)等

    JVM&JRE&JDK关系图

    什么是跨平台性?原理是什么

    所谓跨平台性,是指java语言编写的程序,一次编译后,可以在多个系统平台上运行。

    实现原理:Java程序是通过java虚拟机在系统平台上运行的,只要该系统可以安装相应的java虚拟机,该系统就可以运行java程序。

    Java语言有哪些特点

    简单易学(Java语言的语法与C语言和C++语言很接近)

    面向对象(封装,继承,多态)

    平台无关性(Java虚拟机实现平台无关性)

    支持网络编程并且很方便(Java语言诞生本身就是为简化网络编程设计的)

    支持多线程(多线程机制使应用程序在同一时间并行执行多项任)

    健壮性(Java语言的强类型机制、异常处理、垃圾的自动收集等)

    安全性

    什么是字节码?采用字节码的最大好处是什么

    字节码:Java源代码经过虚拟机编译器编译后产生的文件(即扩展为.class的文件),它不面向任何特定的处理器,只面向虚拟机。

    采用字节码的好处

    Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。

    先看下java中的编译器和解释器

    Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在Java中,这种供虚拟机理解的代码叫做字节码(即扩展为.class的文件),它不面向任何特定的处理器,只面向虚拟机。每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行,这就是上面提到的Java的特点的编译与解释并存的解释。

    Java源代码---->编译器---->jvm可执行的Java字节码(即虚拟指令)---->jvm---->jvm中解释器----->机器可执行的二进制机器码---->程序运行。
    

    什么是Java程序的主类?应用程序和小程序的主类有何不同?

    一个程序中可以有多个类,但只能有一个类是主类。在Java应用程序中,这个主类是指包含main()方法的类。而在Java小程序中,这个主类是一个继承自系统类JApplet或Applet的子类。应用程序的主类不一定要求是public类,但小程序的主类要求必须是public类。主类是Java程序执行的入口点。

    Java应用程序与小程序之间有那些差别?

    简单说应用程序是从主线程启动(也就是main()方法)。applet小程序没有main方法,主要是嵌在浏览器页面上运行(调用init()线程或者run()来启动),嵌入浏览器这点跟flash的小游戏类似。

    Java和C++的区别

    我知道很多人没学过C++,但是面试官就是没事喜欢拿咱们Java和C++比呀!没办法!!!就算没学过C++,也要记下来!

    • 都是面向对象的语言,都支持封装、继承和多态
    • Java不提供指针来直接访问内存,程序内存更加安全
    • Java的类是单继承的,C++支持多重继承;虽然Java的类不可以多继承,但是接口可以多继承。
    • Java有自动内存管理机制,不需要程序员手动释放无用内存

    Oracle JDK 和 OpenJDK 的对比

    1. Oracle JDK版本将每三年发布一次,而OpenJDK版本每三个月发布一次;

    2. OpenJDK 是一个参考模型并且是完全开源的,而Oracle JDK是OpenJDK的一个实现,并不是完全开源的;

    3. Oracle JDK 比 OpenJDK 更稳定。OpenJDK和Oracle JDK的代码几乎相同,但Oracle JDK有更多的类和一些错误修复。因此,如果您想开发企业/商业软件,我建议您选择Oracle JDK,因为它经过了彻底的测试和稳定。某些情况下,有些人提到在使用OpenJDK 可能会遇到了许多应用程序崩溃的问题,但是,只需切换到Oracle JDK就可以解决问题;

    4. 在响应性和JVM性能方面,Oracle JDK与OpenJDK相比提供了更好的性能;

    5. Oracle JDK不会为即将发布的版本提供长期支持,用户每次都必须通过更新到最新版本获得支持来获取最新版本;

    6. Oracle JDK根据二进制代码许可协议获得许可,而OpenJDK根据GPL v2许可获得许可。

    基础语法

    数据类型

    Java有哪些数据类型

    定义:Java语言是强类型语言,对于每一种数据都定义了明确的具体的数据类型,在内存中分配了不同大小的内存空间。

    分类

    • 基本数据类型
      • 数值型
        • 整数类型(byte,short,int,long)
        • 浮点类型(float,double)
      • 字符型(char)
      • 布尔型(boolean)
    • 引用数据类型
      • 类(class)
      • 接口(interface)
      • 数组([])

    Java基本数据类型图

    switch 是否能作用在 byte 上,是否能作用在 long 上,是否能作用在 String 上

    在 Java 5 以前,switch(expr)中,expr 只能是 byte、short、char、int。从 Java5 开始,Java 中引入了枚举类型,expr 也可以是 enum 类型,从 Java 7 开始,expr 还可以是字符串(String),但是长整型(long)在目前所有的版本中都是不可以的。

    用最有效率的方法计算 2 乘以 8

    2 << 3(左移 3 位相当于乘以 2 的 3 次方,右移 3 位相当于除以 2 的 3 次方)。

    Math.round(11.5) 等于多少?Math.round(-11.5)等于多少

    Math.round(11.5)的返回值是 12,Math.round(-11.5)的返回值是-11。四舍五入的原理是在参数上加 0.5 然后进行下取整。

    float f=3.4;是否正确

    不正确。3.4 是双精度数,将双精度型(double)赋值给浮点型(float)属于下转型(down-casting,也称为窄化)会造成精度损失,因此需要强制类型转换float f =(float)3.4; 或者写成 float f =3.4F;。

    short s1 = 1; s1 = s1 + 1;有错吗?short s1 = 1; s1 += 1;有错吗

    对于 short s1 = 1; s1 = s1 + 1;由于 1 是 int 类型,因此 s1+1 运算结果也是 int型,需要强制转换类型才能赋值给 short 型。

    而 short s1 = 1; s1 += 1;可以正确编译,因为 s1+= 1;相当于 s1 = (short(s1 + 1);其中有隐含的强制类型转换。

    编码

    Java语言采用何种编码方案?有何特点?

    Java语言采用Unicode编码标准,Unicode(标准码),它为每个字符制订了一个唯一的数值,因此在任何的语言,平台,程序都可以放心的使用。

    注释

    什么Java注释

    定义:用于解释说明程序的文字

    分类

    • 单行注释
      格式: // 注释文字
    • 多行注释
      格式: /* 注释文字 */
    • 文档注释
      格式:/** 注释文字 */

    作用

    在程序中,尤其是复杂的程序中,适当地加入注释可以增加程序的可读性,有利于程序的修改、调试和交流。注释的内容在程序编译的时候会被忽视,不会产生目标代码,注释的部分不会对程序的执行结果产生任何影响。

    注意事项:多行和文档注释都不能嵌套使用。

    访问修饰符

    访问修饰符 public,private,protected,以及不写(默认)时的区别

    定义:Java中,可以使用访问修饰符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。

    分类

    private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
    default (即缺省,什么也不写,不使用任何关键字): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
    protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。
    public : 对所有类可见。使用对象:类、接口、变量、方法

    访问修饰符图

    运算符

    &和&&的区别

    &运算符有两种用法:(1)按位与;(2)逻辑与。

    &&运算符是短路与运算。逻辑与跟短路与的差别是非常巨大的,虽然二者都要求运算符左右两端的布尔值都是true 整个表达式的值才是 true。&&之所以称为短路运算,是因为如果&&左边的表达式的值是 false,右边的表达式会被直接短路掉,不会进行运算。

    注意:逻辑或运算符(|)和短路或运算符(||)的差别也是如此。

    关键字

    Java 有没有 goto

    goto 是 Java 中的保留字,在目前版本的 Java 中没有使用。

    final 有什么用?

    用于修饰类、属性和方法;

    • 被final修饰的类不可以被继承
    • 被final修饰的方法不可以被重写
    • 被final修饰的变量不可以被改变,被final修饰不可变的是变量的引用,而不是引用指向的内容,引用指向的内容是可以改变的

    final finally finalize区别

    • final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表
      示该变量是一个常量不能被重新赋值。
    • finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块
      中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
    • finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调
      用,当我们调用System.gc() 方法的时候,由垃圾回收器调用finalize(),回收垃圾,一个对象是否可回收的
      最后判断。

    this关键字的用法

    this是自身的一个对象,代表对象本身,可以理解为:指向对象本身的一个指针。

    this的用法在java中大体可以分为3种:

    1.普通的直接引用,this相当于是指向当前对象本身。

    2.形参与成员名字重名,用this来区分:

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    

    3.引用本类的构造函数

    class Person{
        private String name;
        private int age;
        
        public Person() {
        }
     
        public Person(String name) {
            this.name = name;
        }
        public Person(String name, int age) {
            this(name);
            this.age = age;
        }
    }
    

    super关键字的用法

    super可以理解为是指向自己超(父)类对象的一个指针,而这个超类指的是离自己最近的一个父类。

    super也有三种用法:

    1.普通的直接引用

    与this类似,super相当于是指向当前对象的父类的引用,这样就可以用super.xxx来引用父类的成员。

    2.子类中的成员变量或方法与父类中的成员变量或方法同名时,用super进行区分

    class Person{
        protected String name;
     
        public Person(String name) {
            this.name = name;
        }
     
    }
     
    class Student extends Person{
        private String name;
     
        public Student(String name, String name1) {
            super(name);
            this.name = name1;
        }
     
        public void getInfo(){
            System.out.println(this.name);      //Child
            System.out.println(super.name);     //Father
        }
     
    }
    
    public class Test {
        public static void main(String[] args) {
           Student s1 = new Student("Father","Child");
           s1.getInfo();
     
        }
    }
    

    3.引用父类构造函数

    3、引用父类构造函数

    • super(参数):调用父类中的某一个构造函数(应该为构造函数中的第一条语句)。
    • this(参数):调用本类中另一种形式的构造函数(应该为构造函数中的第一条语句)。

    this与super的区别

    • super: 它引用当前对象的直接父类中的成员(用来访问直接父类中被隐藏的父类中成员数据或函数,基类与派生类中有相同成员定义时如:super.变量名 super.成员函数据名(实参)
    • this:它代表当前对象名(在程序中易产生二义性之处,应使用this来指明当前对象;如果函数的形参与类中的成员数据同名,这时需用this来指明成员变量名)
    • super()和this()类似,区别是,super()在子类中调用父类的构造方法,this()在本类内调用本类的其它构造方法。
    • super()和this()均需放在构造方法内第一行。
    • 尽管可以用this调用一个构造器,但却不能调用两个。
    • this和super不能同时出现在一个构造函数里面,因为this必然会调用其它的构造函数,其它的构造函数必然也会有super语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。
    • this()和super()都指的是对象,所以,均不可以在static环境中使用。包括:static变量,static方法,static语句块。
    • 从本质上讲,this是一个指向本对象的指针, 然而super是一个Java关键字。

    static存在的主要意义

    static的主要意义是在于创建独立于具体对象的域变量或者方法。以致于即使没有创建对象,也能使用属性和调用方法

    static关键字还有一个比较关键的作用就是 用来形成静态代码块以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。

    为什么说static块可以用来优化程序性能,是因为它的特性:只会在类加载的时候执行一次。因此,很多时候会将一些只需要进行一次的初始化操作都放在static代码块中进行。

    static的独特之处

    1、被static修饰的变量或者方法是独立于该类的任何对象,也就是说,这些变量和方法不属于任何一个实例对象,而是被类的实例对象所共享

    怎么理解 “被类的实例对象所共享” 这句话呢?就是说,一个类的静态成员,它是属于大伙的【大伙指的是这个类的多个对象实例,我们都知道一个类可以创建多个实例!】,所有的类对象共享的,不像成员变量是自个的【自个指的是这个类的单个实例对象】…我觉得我已经讲的很通俗了,你明白了咩?

    2、在该类被第一次加载的时候,就会去加载被static修饰的部分,而且只在类第一次使用时加载并进行初始化,注意这是第一次用就要初始化,后面根据需要是可以再次赋值的。

    3、static变量值在类加载的时候分配空间,以后创建类对象的时候不会重新分配。赋值的话,是可以任意赋值的!

    4、被static修饰的变量或者方法是优先于对象存在的,也就是说当一个类加载完毕之后,即便没有创建对象,也可以去访问。

    static应用场景

    因为static是被类的实例对象所共享,因此如果某个成员变量是被所有对象所共享的,那么这个成员变量就应该定义为静态变量

    因此比较常见的static应用场景有:

    1、修饰成员变量 2、修饰成员方法 3、静态代码块 4、修饰类【只能修饰内部类也就是静态内部类】 5、静态导包

    static注意事项

    1、静态只能访问静态。 2、非静态既可以访问非静态的,也可以访问静态的。

    流程控制语句

    break ,continue ,return 的区别及作用

    break 跳出总上一层循环,不再执行循环(结束当前的循环体)

    continue 跳出本次循环,继续执行下次循环(结束正在执行的循环 进入下一个循环条件)

    return 程序返回,不再执行下面的代码(结束当前的方法 直接返回)

    在 Java 中,如何跳出当前的多重嵌套循环

    在Java中,要想跳出多重循环,可以在外面的循环语句前定义一个标号,然后在里层循环体的代码中使用带有标号的break 语句,即可跳出外层循环。例如:

    public static void main(String[] args) {
        ok:
        for (int i = 0; i < 10; i++) {
            for (int j = 0; j < 10; j++) {
                System.out.println("i=" + i + ",j=" + j);
                if (j == 5) {
                    break ok;
                }
    
            }
        }
    }
    

    面向对象

    面向对象概述

    面向对象和面向过程的区别

    面向过程

    优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。

    缺点:没有面向对象易维护、易复用、易扩展

    面向对象

    优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护

    缺点:性能比面向过程低

    面向过程是具体化的,流程化的,解决一个问题,你需要一步一步的分析,一步一步的实现。

    面向对象是模型化的,你只需抽象出一个类,这是一个封闭的盒子,在这里你拥有数据也拥有解决问题的方法。需要什么功能直接使用就可以了,不必去一步一步的实现,至于这个功能是如何实现的,管我们什么事?我们会用就可以了。

    面向对象的底层其实还是面向过程,把面向过程抽象成类,然后封装,方便我们使用的就是面向对象了。

    面向对象三大特性

    面向对象的特征有哪些方面

    面向对象的特征主要有以下几个方面

    抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。

    封装

    封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。

    继承

    继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。

    关于继承如下 3 点请记住:

    1. 子类拥有父类非 private 的属性和方法。

    2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。

    3. 子类可以用自己的方式实现父类的方法。(以后介绍)。

    多态

    所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。

    在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。

    其中Java 面向对象编程三大特性:封装 继承 多态

    封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式,将变化隔离,便于使用,提高复用性和安全性。

    继承:继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承可以提高代码复用性。继承是多态的前提。

    关于继承如下 3 点请记住

    1. 子类拥有父类非 private 的属性和方法。

    2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。

    3. 子类可以用自己的方式实现父类的方法。

    多态性:父类或接口定义的引用变量可以指向子类或具体实现类的实例对象。提高了程序的拓展性。

    在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。

    方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)。

    一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。运行时的多态是面向对象最精髓的东西,要实现多态需要做两件事:

    • 方法重写(子类继承父类并重写父类中已有的或抽象的方法);
    • 对象造型(用父类型引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。

    什么是多态机制?Java语言是如何实现多态的?

    所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。

    多态分为编译时多态和运行时多态。其中编辑时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的函数,通过编辑之后会变成两个不同的函数,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是我们所说的多态性。

    多态的实现

    Java实现多态有三个必要条件:继承、重写、向上转型。

    继承:在多态中必须存在有继承关系的子类和父类。

    重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。

    向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。

    只有满足了上述三个条件,我们才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而达到执行不同的行为。

    对于Java而言,它多态的实现机制遵循一个原则:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。

    面向对象五大基本原则是什么(可选)

    • 单一职责原则SRP(Single Responsibility Principle)
      类的功能要单一,不能包罗万象,跟杂货铺似的。
    • 开放封闭原则OCP(Open-Close Principle)
      一个模块对于拓展是开放的,对于修改是封闭的,想要增加功能热烈欢迎,想要修改,哼,一万个不乐意。
    • 里式替换原则LSP(the Liskov Substitution Principle LSP)
      子类可以替换父类出现在父类能够出现的任何地方。比如你能代表你爸去你姥姥家干活。哈哈~~
    • 依赖倒置原则DIP(the Dependency Inversion Principle DIP)
      高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。就是你出国要说你是中国人,而不能说你是哪个村子的。比如说中国人是抽象的,下面有具体的xx省,xx市,xx县。你要依赖的抽象是中国人,而不是你是xx村的。
    • 接口分离原则ISP(the Interface Segregation Principle ISP)
      设计时采用多个与特定客户类有关的接口比采用一个通用的接口要好。就比如一个手机拥有打电话,看视频,玩游戏等功能,把这几个功能拆分成不同的接口,比在一个接口里要好的多。

    类与接口

    抽象类和接口的对比

    抽象类是用来捕捉子类的通用特性的。接口是抽象方法的集合。

    从设计层面来说,抽象类是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。

    相同点

    • 接口和抽象类都不能实例化
    • 都位于继承的顶端,用于被其他实现或继承
    • 都包含抽象方法,其子类都必须覆写这些抽象方法

    不同点

    参数抽象类接口
    声明抽象类使用abstract关键字声明接口使用interface关键字声明
    实现子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现子类使用implements关键字来实现接口。它需要提供接口中所有声明的方法的实现
    构造器抽象类可以有构造器接口不能有构造器
    访问修饰符抽象类中的方法可以是任意访问修饰符接口方法默认修饰符是public。并且不允许定义为 private 或者 protected
    多继承一个类最多只能继承一个抽象类一个类可以实现多个接口
    字段声明抽象类的字段声明可以是任意的接口的字段默认都是 static 和 final 的

    备注:Java8中接口中引入默认方法和静态方法,以此来减少抽象类和接口之间的差异。

    现在,我们可以为接口提供默认实现的方法了,并且不用强制子类来实现它。

    接口和抽象类各有优缺点,在接口和抽象类的选择上,必须遵守这样一个原则:

    • 行为模型应该总是通过接口而不是抽象类定义,所以通常是优先选用接口,尽量少用抽象类。
    • 选择抽象类的时候通常是如下情况:需要定义子类的行为,又要为子类提供通用的功能。

    普通类和抽象类有哪些区别?

    • 普通类不能包含抽象方法,抽象类可以包含抽象方法。
    • 抽象类不能直接实例化,普通类可以直接实例化。

    抽象类能使用 final 修饰吗?

    不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类

    创建一个对象用什么关键字?对象实例与对象引用有何不同?

    new关键字,new创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。一个对象引用可以指向0个或1个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有n个引用指向它(可以用n条绳子系住一个气球)

    变量与方法

    成员变量与局部变量的区别有哪些

    变量:在程序执行的过程中,在某个范围内其值可以发生改变的量。从本质上讲,变量其实是内存中的一小块区域

    成员变量:方法外部,类内部定义的变量

    局部变量:类的方法中的变量。

    成员变量和局部变量的区别

    作用域

    成员变量:针对整个类有效。
    局部变量:只在某个范围内有效。(一般指的就是方法,语句体内)

    存储位置

    成员变量:随着对象的创建而存在,随着对象的消失而消失,存储在堆内存中。
    局部变量:在方法被调用,或者语句被执行的时候存在,存储在栈内存中。当方法调用完,或者语句结束后,就自动释放。

    生命周期

    成员变量:随着对象的创建而存在,随着对象的消失而消失
    局部变量:当方法调用完,或者语句结束后,就自动释放。

    初始值

    成员变量:有默认初始值。

    局部变量:没有默认初始值,使用前必须赋值。

    使用原则

    在使用变量时需要遵循的原则为:就近原则
    首先在局部范围找,有就使用;接着在成员位置找。

    在Java中定义一个不做事且没有参数的构造方法的作用

    Java程序在执行子类的构造方法之前,如果没有用super()来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用super()来调用父类中特定的构造方法,则编译时将发生错误,因为Java程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。

    在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?

    帮助子类做初始化工作。

    一个类的构造方法的作用是什么?若一个类没有声明构造方法,改程序能正确执行吗?为什么?

    主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。

    构造方法有哪些特性?

    名字与类名相同;

    没有返回值,但不能用void声明构造函数;

    生成类的对象时自动执行,无需调用。

    静态变量和实例变量区别

    静态变量: 静态变量由于不属于任何实例对象,属于类的,所以在内存中只会有一份,在类的加载过程中,JVM只为静态变量分配一次内存空间。

    实例变量: 每次创建对象,都会为每个对象分配成员变量内存空间,实例变量是属于实例对象的,在内存中,创建几次对象,就有几份成员变量。

    静态变量与普通变量区别

    static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。

    还有一点就是static成员变量的初始化顺序按照定义的顺序进行初始化。

    静态方法和实例方法有何不同?

    静态方法和实例方法的区别主要体现在两个方面:

    1. 在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。
    2. 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制

    在一个静态方法内调用一个非静态成员为什么是非法的?

    由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。

    什么是方法的返回值?返回值的作用是什么?

    方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果!(前提是该方法可能产生结果)。返回值的作用:接收出结果,使得它可以用于其他的操作!

    内部类

    什么是内部类?

    在Java中,可以将一个类的定义放在另外一个类的定义内部,这就是内部类。内部类本身就是类的一个属性,与其他属性定义方式一致。

    内部类的分类有哪些

    内部类可以分为四种:成员内部类、局部内部类、匿名内部类和静态内部类

    静态内部类

    定义在类内部的静态类,就是静态内部类。

    public class Outer {
    
        private static int radius = 1;
    
        static class StaticInner {
            public void visit() {
                System.out.println("visit outer static  variable:" + radius);
            }
        }
    }
    

    静态内部类可以访问外部类所有的静态变量,而不可访问外部类的非静态变量;静态内部类的创建方式,new 外部类.静态内部类(),如下:

    Outer.StaticInner inner = new Outer.StaticInner();
    inner.visit();
    
    成员内部类

    定义在类内部,成员位置上的非静态类,就是成员内部类。

    public class Outer {
    
        private static  int radius = 1;
        private int count =2;
        
         class Inner {
            public void visit() {
                System.out.println("visit outer static  variable:" + radius);
                System.out.println("visit outer   variable:" + count);
            }
        }
    }
    

    成员内部类可以访问外部类所有的变量和方法,包括静态和非静态,私有和公有。成员内部类依赖于外部类的实例,它的创建方式外部类实例.new 内部类(),如下:

    Outer outer = new Outer();
    Outer.Inner inner = outer.new Inner();
    inner.visit();
    
    局部内部类

    定义在方法中的内部类,就是局部内部类。

    public class Outer {
    
        private  int out_a = 1;
        private static int STATIC_b = 2;
    
        public void testFunctionClass(){
            int inner_c =3;
            class Inner {
                private void fun(){
                    System.out.println(out_a);
                    System.out.println(STATIC_b);
                    System.out.println(inner_c);
                }
            }
            Inner  inner = new Inner();
            inner.fun();
        }
        public static void testStaticFunctionClass(){
            int d =3;
            class Inner {
                private void fun(){
                    // System.out.println(out_a); 编译错误,定义在静态方法中的局部类不可以访问外部类的实例变量
                    System.out.println(STATIC_b);
                    System.out.println(d);
                }
            }
            Inner  inner = new Inner();
            inner.fun();
        }
    }
    

    定义在实例方法中的局部类可以访问外部类的所有变量和方法,定义在静态方法中的局部类只能访问外部类的静态变量和方法。局部内部类的创建方式,在对应方法内,new 内部类(),如下:

     public static void testStaticFunctionClass(){
        class Inner {
        }
        Inner  inner = new Inner();
     }
    
    匿名内部类

    匿名内部类就是没有名字的内部类,日常开发中使用的比较多。

    public class Outer {
    
        private void test(final int i) {
            new Service() {
                public void method() {
                    for (int j = 0; j < i; j++) {
                        System.out.println("匿名内部类" );
                    }
                }
            }.method();
        }
     }
     //匿名内部类必须继承或实现一个已有的接口 
     interface Service{
        void method();
    }
    

    除了没有名字,匿名内部类还有以下特点:

    • 匿名内部类必须继承一个抽象类或者实现一个接口。
    • 匿名内部类不能定义任何静态成员和静态方法。
    • 当所在的方法的形参需要被匿名内部类使用时,必须声明为 final。
    • 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。

    匿名内部类创建方式:

    new/接口{ 
      //匿名内部类实现部分
    }
    

    内部类的优点

    我们为什么要使用内部类呢?因为它有以下优点:

    • 一个内部类对象可以访问创建它的外部类对象的内容,包括私有数据!
    • 内部类不为同一包的其他类所见,具有很好的封装性;
    • 内部类有效实现了“多重继承”,优化 java 单继承的缺陷。
    • 匿名内部类可以很方便的定义回调。

    内部类有哪些应用场景

    1. 一些多算法场合
    2. 解决一些非面向对象的语句块。
    3. 适当使用内部类,使得代码更加灵活和富有扩展性。
    4. 当某个类除了它的外部类,不再被其他的类使用时。

    局部内部类和匿名内部类访问局部变量的时候,为什么变量必须要加上final?

    局部内部类和匿名内部类访问局部变量的时候,为什么变量必须要加上final呢?它内部原理是什么呢?

    先看这段代码:

    public class Outer {
    
        void outMethod(){
            final int a =10;
            class Inner {
                void innerMethod(){
                    System.out.println(a);
                }
    
            }
        }
    }
    

    以上例子,为什么要加final呢?是因为生命周期不一致, 局部变量直接存储在栈中,当方法执行结束后,非final的局部变量就被销毁。而局部内部类对局部变量的引用依然存在,如果局部内部类要调用局部变量时,就会出错。加了final,可以确保局部内部类使用的变量与外层的局部变量区分开,解决了这个问题。

    内部类相关,看程序说出运行结果

    public class Outer {
        private int age = 12;
    
        class Inner {
            private int age = 13;
            public void print() {
                int age = 14;
                System.out.println("局部变量:" + age);
                System.out.println("内部类变量:" + this.age);
                System.out.println("外部类变量:" + Outer.this.age);
            }
        }
    
        public static void main(String[] args) {
            Outer.Inner in = new Outer().new Inner();
            in.print();
        }
    
    }
    

    运行结果:

    局部变量:14
    内部类变量:13
    外部类变量:12
    

    重写与重载

    构造器(constructor)是否可被重写(override)

    构造器不能被继承,因此不能被重写,但可以被重载。

    重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?

    方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。

    重载:发生在同一个类中,方法名相同参数列表不同(参数类型不同、个数不同、顺序不同),与方法返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分

    重写:发生在父子类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类(里氏代换原则);如果父类方法访问修饰符为private则子类中就不是重写。

    对象相等判断

    == 和 equals 的区别是什么

    == : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型 == 比较的是值,引用数据类型 == 比较的是内存地址)

    equals() : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:

    情况1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。

    情况2:类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来两个对象的内容相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。

    举个例子:

    public class test1 {
        public static void main(String[] args) {
            String a = new String("ab"); // a 为一个引用
            String b = new String("ab"); // b为另一个引用,对象的内容一样
            String aa = "ab"; // 放在常量池中
            String bb = "ab"; // 从常量池中查找
            if (aa == bb) // true
                System.out.println("aa==bb");
            if (a == b) // false,非同一对象
                System.out.println("a==b");
            if (a.equals(b)) // true
                System.out.println("aEQb");
            if (42 == 42.0) { // true
                System.out.println("true");
            }
        }
    }
    

    说明:

    • String中的equals方法是被重写过的,因为object的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值。
    • 当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个String对象。

    hashCode 与 equals (重要)

    HashSet如何检查重复

    两个对象的 hashCode() 相同,则 equals() 也一定为 true,对吗?

    hashCode和equals方法的关系

    面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?”

    hashCode()介绍

    hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode()函数。

    散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)

    为什么要有 hashCode

    我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode

    当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head first java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。

    hashCode()与equals()的相关规定

    如果两个对象相等,则hashcode一定也是相同的

    两个对象相等,对两个对象分别调用equals方法都返回true

    两个对象有相同的hashcode值,它们也不一定是相等的

    因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖

    hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)

    对象的相等与指向他们的引用相等,两者有什么不同?

    对象的相等 比的是内存中存放的内容是否相等而 引用相等 比较的是他们指向的内存地址是否相等。

    值传递

    当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递

    是值传递。Java 语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性可以在被调用过程中被改变,但对对象引用的改变是不会影响到调用者的

    为什么 Java 中只有值传递

    首先回顾一下在程序设计语言中有关将参数传递给方法(或函数)的一些专业术语。按值调用(call by value)表示方法接收的是调用者提供的值,而按引用调用(call by reference)表示方法接收的是调用者提供的变量地址。一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。 它用来描述各种程序设计语言(不只是Java)中方法参数传递方式。

    Java程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。

    下面通过 3 个例子来给大家说明

    example 1

    public static void main(String[] args) {
        int num1 = 10;
        int num2 = 20;
    
        swap(num1, num2);
    
        System.out.println("num1 = " + num1);
        System.out.println("num2 = " + num2);
    }
    
    public static void swap(int a, int b) {
        int temp = a;
        a = b;
        b = temp;
    
        System.out.println("a = " + a);
        System.out.println("b = " + b);
    }
    

    结果

    a = 20
    b = 10
    num1 = 10
    num2 = 20
    

    解析

    img

    在swap方法中,a、b的值进行交换,并不会影响到 num1、num2。因为,a、b中的值,只是从 num1、num2 的复制过来的。也就是说,a、b相当于num1、num2 的副本,副本的内容无论怎么修改,都不会影响到原件本身。

    通过上面例子,我们已经知道了一个方法不能修改一个基本数据类型的参数,而对象引用作为参数就不一样,请看 example2.

    example 2

        public static void main(String[] args) {
            int[] arr = { 1, 2, 3, 4, 5 };
            System.out.println(arr[0]);
            change(arr);
            System.out.println(arr[0]);
        }
    
        public static void change(int[] array) {
            // 将数组的第一个元素变为0
            array[0] = 0;
        }
    

    结果

    1
    0
    

    解析

    img

    array 被初始化 arr 的拷贝也就是一个对象的引用,也就是说 array 和 arr 指向的时同一个数组对象。 因此,外部对引用对象的改变会反映到所对应的对象上。

    通过 example2 我们已经看到,实现一个改变对象参数状态的方法并不是一件难事。理由很简单,方法得到的是对象引用的拷贝,对象引用及其他的拷贝同时引用同一个对象。

    很多程序设计语言(特别是,C++和Pascal)提供了两种参数传递的方式:值调用和引用调用。有些程序员(甚至本书的作者)认为Java程序设计语言对对象采用的是引用调用,实际上,这种理解是不对的。由于这种误解具有一定的普遍性,所以下面给出一个反例来详细地阐述一下这个问题。

    example 3

    public class Test {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            Student s1 = new Student("小张");
            Student s2 = new Student("小李");
            Test.swap(s1, s2);
            System.out.println("s1:" + s1.getName());
            System.out.println("s2:" + s2.getName());
        }
    
        public static void swap(Student x, Student y) {
            Student temp = x;
            x = y;
            y = temp;
            System.out.println("x:" + x.getName());
            System.out.println("y:" + y.getName());
        }
    }
    

    结果

    x:小李
    y:小张
    s1:小张
    s2:小李
    

    解析

    交换之前:

    img

    交换之后:

    img

    通过上面两张图可以很清晰的看出: 方法并没有改变存储在变量 s1 和 s2 中的对象引用。swap方法的参数x和y被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝

    总结

    Java程序设计语言对对象采用的不是引用调用,实际上,对象引用是按值传递的。

    下面再总结一下Java中方法参数的使用情况:

    • 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型》
    • 一个方法可以改变一个对象参数的状态。
    • 一个方法不能让对象参数引用一个新的对象。

    值传递和引用传递有什么区别

    值传递:指的是在方法调用时,传递的参数是按值的拷贝传递,传递的是值的拷贝,也就是说传递后就互不相关了。

    引用传递:指的是在方法调用时,传递的参数是按引用进行传递,其实传递的引用的地址,也就是变量所对应的内存空间的地址。传递的是值的引用,也就是说传递前和传递后都指向同一个引用(也就是同一个内存空间)。

    Java包

    JDK 中常用的包有哪些

    • java.lang:这个是系统的基础类;
    • java.io:这里面是所有输入输出有关的类,比如文件操作等;
    • java.nio:为了完善 io 包中的功能,提高 io 包中性能而写的一个新包;
    • java.net:这里面是与网络有关的类;
    • java.util:这个是系统辅助类,特别是集合类;
    • java.sql:这个是数据库操作的类。

    import java和javax有什么区别

    刚开始的时候 JavaAPI 所必需的包是 java 开头的包,javax 当时只是扩展 API 包来说使用。然而随着时间的推移,javax 逐渐的扩展成为 Java API 的组成部分。但是,将扩展从 javax 包移动到 java 包将是太麻烦了,最终会破坏一堆现有的代码。因此,最终决定 javax 包将成为标准API的一部分。

    所以,实际上java和javax没有区别。这都是一个名字。

    IO流

    java 中 IO 流分为几种?

    • 按照流的流向分,可以分为输入流和输出流;
    • 按照操作单元划分,可以划分为字节流和字符流;
    • 按照流的角色划分为节点流和处理流。

    Java Io流共涉及40多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java I0流的40多个类都是从如下4个抽象类基类中派生出来的。

    • InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
    • OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

    按操作方式分类结构图:

    IO-操作方式分类

    按操作对象分类结构图:

    IO-操作对象分类

    BIO,NIO,AIO 有什么区别?

    简答

    • BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
    • NIO:Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
    • AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。

    详细回答

    • BIO (Blocking I/O): 同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
    • NIO (New I/O): NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。NIO中的N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 SocketServerSocket 相对应的 SocketChannelServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发
    • AIO (Asynchronous I/O): AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步IO的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。

    Files的常用方法都有哪些?

    • Files. exists():检测文件路径是否存在。
    • Files. createFile():创建文件。
    • Files. createDirectory():创建文件夹。
    • Files. delete():删除一个文件或目录。
    • Files. copy():复制文件。
    • Files. move():移动文件。
    • Files. size():查看文件个数。
    • Files. read():读取文件。
    • Files. write():写入文件。

    反射

    什么是反射机制?

    JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

    静态编译和动态编译

    • **静态编译:**在编译时确定类型,绑定对象
    • **动态编译:**运行时确定类型,绑定对象

    反射机制优缺点

    • 优点: 运行期类型的判断,动态加载类,提高代码灵活度。
    • 缺点: 性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的java代码要慢很多。

    反射机制的应用场景有哪些?

    反射是框架设计的灵魂。

    在我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring/Hibernate 等框架也大量使用到了反射机制。

    举例:①我们在使用JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序;②Spring框架也用到很多反射机制,最经典的就是xml的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:1) 将程序内所有 XML 或 Properties 配置文件加载入内存中; 2)Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息; 3)使用反射机制,根据这个字符串获得某个类的Class实例; 4)动态配置实例的属性

    Java获取反射的三种方法

    1.通过new对象实现反射机制 2.通过路径实现反射机制 3.通过类名实现反射机制

    public class Student {
        private int id;
        String name;
        protected boolean sex;
        public float score;
    }
    
    public class Get {
        //获取反射机制三种方式
        public static void main(String[] args) throws ClassNotFoundException {
            //方式一(通过建立对象)
            Student stu = new Student();
            Class classobj1 = stu.getClass();
            System.out.println(classobj1.getName());
            //方式二(所在通过路径-相对路径)
            Class classobj2 = Class.forName("fanshe.Student");
            System.out.println(classobj2.getName());
            //方式三(通过类名)
            Class classobj3 = Student.class;
            System.out.println(classobj3.getName());
        }
    }
    

    网络编程

    网络编程的面试题可以查看我的这篇文章重学TCP/IP协议和三次握手四次挥手,内容不仅包括TCP/IP协议和三次握手四次挥手的知识,还包括计算机网络体系结构,HTTP协议,get请求和post请求区别,session和cookie的区别等,欢迎大家阅读。

    常用API

    String相关

    字符型常量和字符串常量的区别

    1. 形式上: 字符常量是单引号引起的一个字符 字符串常量是双引号引起的若干个字符
    2. 含义上: 字符常量相当于一个整形值(ASCII值),可以参加表达式运算 字符串常量代表一个地址值(该字符串在内存中存放位置)
    3. 占内存大小 字符常量只占两个字节 字符串常量占若干个字节(至少一个字符结束标志)

    什么是字符串常量池?

    字符串常量池位于堆内存中,专门用来存储字符串常量,可以提高内存的使用率,避免开辟多块空间存储相同的字符串,在创建字符串时 JVM 会首先检查字符串常量池,如果该字符串已经存在池中,则返回它的引用,如果不存在,则实例化一个字符串放到池中,并返回其引用。

    String 是最基本的数据类型吗

    不是。Java 中的基本数据类型只有 8 个 :byte、short、int、long、float、double、char、boolean;除了基本类型(primitive type),剩下的都是引用类型(referencetype),Java 5 以后引入的枚举类型也算是一种比较特殊的引用类型。

    这是很基础的东西,但是很多初学者却容易忽视,Java 的 8 种基本数据类型中不包括 String,基本数据类型中用来描述文本数据的是 char,但是它只能表示单个字符,比如 ‘a’,‘好’ 之类的,如果要描述一段文本,就需要用多个 char 类型的变量,也就是一个 char 类型数组,比如“你好” 就是长度为2的数组 char[] chars = {‘你’,‘好’};

    但是使用数组过于麻烦,所以就有了 String,String 底层就是一个 char 类型的数组,只是使用的时候开发者不需要直接操作底层数组,用更加简便的方式即可完成对字符串的使用。

    String有哪些特性

    • 不变性:String 是只读字符串,是一个典型的 immutable 对象,对它进行任何操作,其实都是创建一个新的对象,再把引用指向该对象。不变模式的主要作用在于当一个对象需要被多线程共享并频繁访问时,可以保证数据的一致性。

    • 常量池优化:String 对象创建之后,会在字符串常量池中进行缓存,如果下次创建同样的对象时,会直接返回缓存的引用。

    • final:使用 final 来定义 String 类,表示 String 类不能被继承,提高了系统的安全性。

    String为什么是不可变的吗?

    简单来说就是String类利用了final修饰的char类型数组存储字符,源码如下图所以:

    /** The value is used for character storage. */
    private final char value[];
    

    String真的是不可变的吗?

    我觉得如果别人问这个问题的话,回答不可变就可以了。 下面只是给大家看两个有代表性的例子:

    1) String不可变但不代表引用不可以变

    String str = "Hello";
    str = str + " World";
    System.out.println("str=" + str);
    

    结果:

    str=Hello World
    

    解析:

    实际上,原来String的内容是不变的,只是str由原来指向"Hello"的内存地址转为指向"Hello World"的内存地址而已,也就是说多开辟了一块内存区域给"Hello World"字符串。

    2) 通过反射是可以修改所谓的“不可变”对象

    // 创建字符串"Hello World", 并赋给引用s
    String s = "Hello World";
    
    System.out.println("s = " + s); // Hello World
    
    // 获取String类中的value字段
    Field valueFieldOfString = String.class.getDeclaredField("value");
    
    // 改变value属性的访问权限
    valueFieldOfString.setAccessible(true);
    
    // 获取s对象上的value属性的值
    char[] value = (char[]) valueFieldOfString.get(s);
    
    // 改变value所引用的数组中的第5个字符
    value[5] = '_';
    
    System.out.println("s = " + s); // Hello_World
    

    结果:

    s = Hello World
    s = Hello_World
    

    解析:

    用反射可以访问私有成员, 然后反射出String对象中的value属性, 进而改变通过获得的value引用改变数组的结构。但是一般我们不会这么做,这里只是简单提一下有这个东西。

    是否可以继承 String 类

    String 类是 final 类,不可以被继承。

    String str="i"与 String str=new String(“i”)一样吗?

    不一样,因为内存的分配方式不一样。String str="i"的方式,java 虚拟机会将其分配到常量池中;而 String str=new String(“i”) 则会被分到堆内存中。

    String s = new String(“xyz”);创建了几个字符串对象

    两个对象,一个是静态区的"xyz",一个是用new创建在堆上的对象。

    String str1 = "hello"; //str1指向静态区
    String str2 = new String("hello");  //str2指向堆上的对象
    String str3 = "hello";
    String str4 = new String("hello");
    System.out.println(str1.equals(str2)); //true
    System.out.println(str2.equals(str4)); //true
    System.out.println(str1 == str3); //true
    System.out.println(str1 == str2); //false
    System.out.println(str2 == str4); //false
    System.out.println(str2 == "hello"); //false
    str2 = str1;
    System.out.println(str2 == "hello"); //true
    

    如何将字符串反转?

    使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。

    示例代码:

    // StringBuffer reverse
    StringBuffer stringBuffer = new StringBuffer();
    stringBuffer. append("abcdefg");
    System. out. println(stringBuffer. reverse()); // gfedcba
    // StringBuilder reverse
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder. append("abcdefg");
    System. out. println(stringBuilder. reverse()); // gfedcba
    

    数组有没有 length()方法?String 有没有 length()方法

    数组没有 length()方法 ,有 length 的属性。String 有 length()方法。JavaScript中,获得字符串的长度是通过 length 属性得到的,这一点容易和 Java 混淆。

    String 类的常用方法都有那些?

    • indexOf():返回指定字符的索引。
    • charAt():返回指定索引处的字符。
    • replace():字符串替换。
    • trim():去除字符串两端空白。
    • split():分割字符串,返回一个分割后的字符串数组。
    • getBytes():返回字符串的 byte 类型数组。
    • length():返回字符串长度。
    • toLowerCase():将字符串转成小写字母。
    • toUpperCase():将字符串转成大写字符。
    • substring():截取字符串。
    • equals():字符串比较。

    在使用 HashMap 的时候,用 String 做 key 有什么好处?

    HashMap 内部实现是通过 key 的 hashcode 来确定 value 的存储位置,因为字符串是不可变的,所以当创建字符串时,它的 hashcode 被缓存下来,不需要再次计算,所以相比于其他对象更快。

    String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的

    可变性

    String类中使用字符数组保存字符串,private final char value[],所以string对象是不可变的。StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,char[] value,这两种对象都是可变的。

    线程安全性

    String中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。

    性能

    每次对String 类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String 对象。StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StirngBuilder 相比使用StringBuffer 仅能获得10%~15% 左右的性能提升,但却要冒多线程不安全的风险。

    对于三者使用的总结

    如果要操作少量的数据用 = String

    单线程操作字符串缓冲区 下操作大量数据 = StringBuilder

    多线程操作字符串缓冲区 下操作大量数据 = StringBuffer

    Date相关

    包装类相关

    自动装箱与拆箱

    装箱:将基本类型用它们对应的引用类型包装起来;

    拆箱:将包装类型转换为基本数据类型;

    int 和 Integer 有什么区别

    Java 是一个近乎纯洁的面向对象编程语言,但是为了编程的方便还是引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java 为每一个基本数据类型都引入了对应的包装类型(wrapper class),int 的包装类就是 Integer,从 Java 5 开始引入了自动装箱/拆箱机制,使得二者可以相互转换。

    Java 为每个原始类型提供了包装类型:

    原始类型: boolean,char,byte,short,int,long,float,double

    包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double

    Integer a= 127 与 Integer b = 127相等吗

    对于对象引用类型:==比较的是对象的内存地址。
    对于基本数据类型:==比较的是值。

    如果整型字面量的值在-128到127之间,那么自动装箱时不会new新的Integer对象,而是直接引用常量池中的Integer对象,超过范围 a1==b1的结果是false

    public static void main(String[] args) {
        Integer a = new Integer(3);
        Integer b = 3;  // 将3自动装箱成Integer类型
        int c = 3;
        System.out.println(a == b); // false 两个引用没有引用同一对象
        System.out.println(a == c); // true a自动拆箱成int类型再和c比较
        System.out.println(b == c); // true
    
        Integer a1 = 128;
        Integer b1 = 128;
        System.out.println(a1 == b1); // false
    
        Integer a2 = 127;
        Integer b2 = 127;
        System.out.println(a2 == b2); // true
    }
    

    常用工具类库

    单元测试

    日志

    展开全文
  • 计算机出现多重网络及其解决方案

    千次阅读 2018-05-08 14:12:52
    在已经联网(拨号上网)的情况下,在“网络和共享中心”设置页面中,显示我的网络是“多重网络”,并且在“活动网络”中出现了两个连接:一个是本地连接,显示是链接到了Inernet;另一个是未识别网络,无法访问...

    一、问题描述

    在已经联网(拨号上网)的情况下,在“网络和共享中心”设置页面中,显示我的网络是“多重网络”,并且在“活动网络”中出现了两个连接:一个是本地连接,显示是链接到了Inernet;另一个是未识别网络,无法访问Internet。并且,在我能够正常的上网浏览情况下,出现了几个问题:

    (1)桌面右下角的网络图标是叉;

    (2)本地连接只有一个,但在cmd中使用ipconfig命令发现有多个本地连接和隧道适配器链接;

    (3)虚拟机使用“桥接模式”的系统无法上网,并且无法和宿主机之间无法Ping通。

     

    二、问题搜索和解决过程

    在搜索之前,要逐一排查问题,首先从设备连接入手,然后再是从网络设备、系统等方面进行排查。

    1、设备连接排查

    [具体问题说明]

    我接入的是实验室搭建的局域网络,在该局域网内的其他主机均正常。我将我的网口与另一台主机的网口进行交换,重启本地连接,发现以上问题仍然存在。然后我拔掉本地连接网口,接入无线网卡,使用无线网卡上网,依然是相同的问题。

     

    [个人分析]

    不是局域网络造成的问题,外部设备的连接也没有问题的,问题可能处在网络适配器和系统服务本身。

     

    2、网络适配器排查

    [具体问题说明]

    首先,网卡够正常上网,说明网卡的上网功没有问题,使用网络检测工具也是显示网卡驱动正常,说明网卡设备是没有问题的。但是硬件是们玄学,自己了解又不深,保不准会是什么情况,所以我将本地的网络驱动全部卸载,然后重新安装。

    其次,确保“本地连接”只启用了一个,其他的连接全部都禁用。然后运行cmd,使用ipconfig查看连接信息。我当前本地连接的那一栏的所有信息都是正常的,但是还是显示了其他本地连接信息,并且其他本地连接的IP地址是异常的,也没有默认网关。而且还出现了隧道适配器的相关信息,然而我在网络适配器配置中已经将IPV6的选项给关闭了,按理来说是不应该出现这种情况的。

     

    [个人分析]

    这里虽然只启用了一个本地连接,但是使用ipconfig命令却显示有多个本地连接,这很可能就形成了多重网络。

    已经启用的本地连接的所有信息都正常,而其他的本地连接都是异常,很有可能是DHCP服务出现了问题。

    至于隧道适配器的问题,虽然启用的本地连接已经取消了IPV6的选项,但这很可能是其他本地连接选用了IPV6所造成的。

     

    [解决方法]

    在进行下列操作之前,如果发现了有多个网卡驱动程序,那么先只保留本地连接的那一个网卡驱动,其余的都先卸载。如果还是不能解决问题,说明还有其他原因。

    在运行中输入services.msc,找到DHCP服务,设置为自动,并重新启动服务,重启适配或重启主机。

    关闭隧道配置器命令:

    netsh interface isatap set state disable

    netsh interface teredo set state disable

    netsh interface 6to4 set state disable

    一直使用的IPV4的网络,所以关闭了IPV6的相关配置是没有影响的,如果要使用IPV6可以重新配置。

    一般使用了这种方法之后,再使用ipconfig去查看网络信息,所有本地连接应该都能恢复正常,但是多重网络的情况不一定能够解决,此时就还需要其他的操作。

     

    3、注册列表排查

    [具体问题说明]

    上面也提到多重网络很能是因为多个本地连接叠加造成的,我能想到的比较直接的做法就是删除或者合并网络,然而按照搜索到的方法出又出现了两个新的问题:

    (1)无法删除和合并两个网络:在两个网络并存的情况下,我无法进行删除或合并操作;

    (2)强行合并之后本“本地连接”消失:在适配器选项中禁用了本地连接,然后在活动网络中进行合并,结果就是适配器中的本地连接消失了;最终是重新安装网卡驱动才得以解决。

    然后,问题回到了原点:连接仍然是多重网络。

     

    [个人分析]

    问题还是多重网络,原因如前面提到的,是多个本地连接造成的,之前的方法也都是为了消除多个本地连接。多余的网卡都已经卸载,多余的本地连接都已经禁用,该启用的服务也启用了。剩下的我能想到与网络相关的就只有只有注册列表的问题了。可能在注册列表中仍然存在其他网络信息,导致用ipconfig去查询的时候仍然是多个本地连接。

     

    [解决方法]

    在运行中输入regedit,在

    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Network\{4D36E972-E325-11CE-BFC1-08002BE10318}中删除多余注册信息就可以了。点开Connection就可以查看对应名称。

     

     

    三、个人总结

    虽然当遇到问题的时候,我直接搜索了“win7上多重网络怎么解决”和“活动网络中出现两个网络”两个问题,但根据搜到的方法操作都没有效果,于是就重头开始弄了。从硬件问题着手,逐步确定问题所在。我个人感觉,如果能直接搜索到相关问题并解决那是最好的,但是如果不能直接找到问题的话就要使用缩小范围的方法逐个排查然后就解决。最后如果是真的没有办法解决,那就重装系统吧。

     

     

     

     


    展开全文
  • 意识传播和自我启动的意识行为对流行病传播的影响-基于多重网络的方法
  • SNS服务多重使用行为影响因素的研究,顾睿,胡立斌,社交网络服务(Social Networking Service, SNS)作为建立在网站平台上的以社交为主要目的的互联网服务,兼具网络外部性和高度依赖于网站流
  • 针对零担快运和快递干线运输的特点,考虑车辆容量限制和节点任务的多重时效性约束,建立了轴辐式网络下的车辆调度模型,设计了基于启发式调度规则的节约算法进行求解。通过中国邮政广州邮区的运营数据进行算例分析,...
  • 针对复数域上具有$1+N$节点的多重边赋权驱动响应复杂网络的函数投影同步问题,提出一种简单有效的控制方法.首先,构建$1+N$多重边赋权...仿真结果验证了所提出方法的有效性,同时揭示了网络节点重边数对同步速度的影响.
  • 开放式最短路径优先(OSPF)是一种流行的链接状态路由协议,广泛用于... 我们还提供了实验研究,以了解多种故障对收敛的影响。 结果表明,多个故障更可能延迟收敛。 这表明运营商在配置OSPF网络时应考虑到这一点。
  • 卷积神经网络入门详解

    万次阅读 多人点赞 2017-11-08 15:52:14
      本文主要内容为 CS231n 课程的学习笔记,主要参考 学习...  在之前的博客《十四、卷积神经网络(1):介绍卷积神经网络》《十五、卷积神经网络(2):卷积神经网络的结构》中只是介绍性的阐述了一些关于卷积神...

      本文主要内容为 CS231n 课程的学习笔记,主要参考 学习视频 和对应的 课程笔记翻译 ,感谢各位前辈对于深度学习的辛苦付出。在这里我主要记录下自己觉得重要的内容以及一些相关的想法,希望能与大家多多交流~

    0. 回顾之前所写过的博客

      在之前的博客《十四、卷积神经网络(1):介绍卷积神经网络》《十五、卷积神经网络(2):卷积神经网络的结构》中只是介绍性的给出了一些关于卷积神经网络的知识。这主要因为《Neural Networks and Deep Learning》这本书中的大部分章节在介绍神经网络,而仅在最后一个部分介绍了卷积神经网络,只介绍了卷积神经网络中的相关概念,如“感受野”、“卷积层”、“池化层”、“权值共享”等等,而且也只介绍了一种较为简单的卷积神经网络结构。而且在这上述两节内容中的图像也是单通道的图像,并没有考虑多通道图像。

      作为补充和完善,在本文中将对卷积神经网络中的多个问题具体展开讲解。

    1. 卷积神经网络结构概述

      如果用全连接神经网络处理大尺寸图像具有三个明显的缺点:

      (1)将图像展开为向量会丢失空间信息;

      (2)参数过多效率低下,训练困难;

      (3)大量的参数也很快会导致网络过拟合。

    而使用卷积神经网络可以很好地解决上面的三个问题。

      与全连接神经网络不同,卷积神经网络的各层中的神经元是3维排列的:宽度、高度和深度。其中的宽度和高度是很好理解的,因为本身卷积就是一个二维模板,但是在卷积神经网络中的深度指的是激活数据体的第三个维度,而不是整个网络的深度,整个网络的深度指的是网络的层数。

      举个例子来理解什么是宽度,高度和深度,假如使用 CIFAR-10 中的图像是作为卷积神经网络的输入,该输入数据体的维度是32x32x3(宽度,高度和深度)。**卷积神经网络中,层中的神经元将只与前一层中的一小块区域连接,而不是采取全连接方式。**对于用来分类CIFAR-10中的图像的卷积网络,其最后的输出层的维度是1x1x10,因为在卷积神经网络结构的最后部分将会把全尺寸的图像压缩为包含分类评分的一个向量,向量是在深度方向排列的。下面是例子:

    图 1. 全连接神经网络与卷积神经网络的对比

    图1中左侧是一个3层的神经网络;右侧是一个卷积神经网络,将它的神经元在成3个维度(宽、高和深度)进行排列。卷积神经网络的每一层都将3D的输入数据变化为神经元3D的激活数据并输出。在图1的右侧,红色的输入层代表输入图像,所以它的宽度和高度就是图像的宽度和高度,它的深度是3(代表了红、绿、蓝3种颜色通道),与红色相邻的蓝色部分是经过卷积和池化之后的激活值(也可以看做是神经元) ,后面是接着的卷积池化层。

    2. 构建卷积神经网络的各种层

      卷积神经网络主要由这几类层构成:输入层、卷积层,ReLU层、池化(Pooling)层和全连接层(全连接层和常规神经网络中的一样)。通过将这些层叠加起来,就可以构建一个完整的卷积神经网络。在实际应用中往往将卷积层与ReLU层共同称之为卷积层,所以卷积层经过卷积操作也是要经过激活函数的。具体说来,卷积层和全连接层(CONV/FC)对输入执行变换操作的时候,不仅会用到激活函数,还会用到很多参数,即神经元的权值w和偏差b;而ReLU层和池化层则是进行一个固定不变的函数操作。卷积层和全连接层中的参数会随着梯度下降被训练,这样卷积神经网络计算出的分类评分就能和训练集中的每个图像的标签吻合了。

    2.1 卷积层

      卷积层是构建卷积神经网络的核心层,它产生了网络中大部分的计算量。注意是计算量而不是参数量

    2.1.1 卷积层作用

      1. 滤波器的作用或者说是卷积的作用。卷积层的参数是有一些可学习的滤波器集合构成的。每个滤波器在空间上(宽度和高度)都比较小,但是深度和输入数据一致(这一点很重要,后面会具体介绍)。直观地来说,网络会让滤波器学习到当它看到某些类型的视觉特征时就激活,具体的视觉特征可能是某些方位上的边界,或者在第一层上某些颜色的斑点,甚至可以是网络更高层上的蜂巢状或者车轮状图案。

      2. 可以被看做是神经元的一个输出。神经元只观察输入数据中的一小部分,并且和空间上左右两边的所有神经元共享参数(因为这些数字都是使用同一个滤波器得到的结果)。

      3. 降低参数的数量。这个由于卷积具有“权值共享”这样的特性,可以降低参数数量,达到降低计算开销,防止由于参数过多而造成过拟合。

    2.1.2 感受野(重点理解)

      在处理图像这样的高维度输入时,让每个神经元都与前一层中的所有神经元进行全连接是不现实的。相反,我们让每个神经元只与输入数据的一个局部区域连接。该连接的空间大小叫做神经元的感受野(receptive field),它的尺寸是一个超参数(其实就是滤波器的空间尺寸)。在深度方向上,这个连接的大小总是和输入量的深度相等。需要再次强调的是,我们对待空间维度(宽和高)与深度维度是不同的:连接在空间(宽高)上是局部的,但是在深度上总是和输入数据的深度一致,这一点会在下面举例具体说明。

    图 2. 举例说明感受野的连接及尺寸说明

      在图 2 中展现的卷积神经网络的一部分,其中的红色为输入数据,假设输入数据体尺寸为[32x32x3](比如CIFAR-10的RGB图像),如果感受野(或滤波器尺寸)是5x5,那么卷积层中的每个神经元会有输入数据体中[5x5x3]区域的权重,共5x5x3=75个权重(还要加一个偏差参数)。注意这个连接在深度维度上的大小必须为3,和输入数据体的深度一致。其中还有一点需要注意,对应一个感受野有75个权重,这75个权重是通过学习进行更新的,所以很大程度上这些权值之间是不相等(也就是对于某一个特定的卷积核,在它输入数据体的每一个深度切片上的权重都是独特的,不是将 N × N × 1 N \times N \times 1 N×N×1 重复输入数据题的深度那么多次就可以的)。在卷积神经网络中,某一个特定的卷积核,可以根据输入数据题体的维度,拆解为一个一个传统意义上的 N × N × 1 N \times N \times 1 N×N×1 的卷积模板,输入数据题的深度切片与和自己对应的卷积模板做完卷积之后,再将各个深度的结果加起来,再加上偏置(注意是一个偏置,无论输入输入数据是多少层,一个卷积核就对应一个偏置)就获得了这个卷基层卷积之后的结果。

    2.1.3 神经元的空间排列

      感受野讲解了卷积层中每个神经元与输入数据体之间的连接方式,但是尚未讨论输出数据体中神经元的数量,以及它们的排列方式。3个超参数控制着输出数据体的尺寸:深度(depth),步长(stride)和零填充(zero-padding)。

      (1) 输出数据体的深度:它是一个超参数,和使用的滤波器的数量一致,而每个滤波器在输入数据中寻找一些不同的东西,即图像的某些特征。如图2 所示,将沿着深度方向排列、感受野相同的神经元集合称为深度列(depth column),也有人使用纤维(fibre)来称呼它们。

      (2) 在滑动滤波器的时候,必须指定步长。当步长为1,滤波器每次移动1个像素;当步长为2,滤波器滑动时每次移动2个像素,当然步长也可以是不常用的3,或者更大的数字,但这些在实际中很少使用 ,这个操作会让输出数据体在空间上变小。

      (3) 有时候将输入数据体用 0 在边缘处进行填充是很方便的。这个零填充(zero-padding)的尺寸是一个超参数。零填充有一个良好性质,即可以控制输出数据体的空间尺寸与输入数据体的空间尺寸相同,使得输入和输出的宽高都相等。但是要注意,只能保证宽和高相等,并不能保证深度,刚刚讲过,深度与滤波器(或者称卷积核)的数量有关。

      输出数据体在空间上的尺寸 W 2 × H 2 × D 2 W_2 \times H_2 \times D_2 W2×H2×D2 可以通过输入数据体尺寸 W 1 × H 1 × D 1 W_1 \times H_1 \times D_1 W1×H1×D1,卷积层中神经元的感受野尺寸(F),步长(S),滤波器数量(K)和零填充的数量(P)计算输出出来。

    W 2 = ( W 1 − F + 2 P ) / S + 1 H 2 = ( H 1 − F + 2 P ) / S + 1 D 2 = K \begin{matrix} W_2=(W_1-F+2P)/S+1 \\ H_2=(H_1-F+2P)/S+1 \\ D_2=K \\ \end{matrix} W2=(W1F+2P)/S+1H2=(H1F+2P)/S+1D2=K

      一般说来,当步长S=1时,零填充的值是P=(F-1)/2,这样就能保证输入和输出数据体有相同的空间尺寸。

      步长的限制:注意这些空间排列的超参数之间是相互限制的。举例说来,当输入尺寸W=10,零填充 P=0,滤波器尺寸 F=3,此时步长 S=2 是行不通,因为 (W-F+2P)/S+1=(10-3+0)/2+1=4.5,结果不是整数,也就是说神经元不能整齐对称地滑过输入数据体。因此,这些超参数的设定就被认为是无效的,一个卷积神经网络库可能会报出一个错误,通过修改零填充值、修改输入数据体尺寸,或者其他什么措施来让设置合理。在后面的卷积神经网络结构小节中,读者可以看到合理地设置网络的尺寸让所有的维度都能正常工作,是相当让人头痛的事;而使用零填充和遵守其他一些设计策略将会有效解决这个问题。

    2.1.4 权值共享

      在卷积层中权值共享被用来控制参数的数量。假如在一个卷积核中,每一个感受野采用的都是不同的权重值(卷积核的值不同),那么这样的网络中参数数量将是十分巨大的。

      权值共享是基于这样的一个合理的假设:如果一个特征在计算某个空间位置 ( x 1 , y 1 ) (x_1,y_1) (x1,y1) 的时候有用,那么它在计算另一个不同位置 ( x 2 , y 2 ) (x_2,y_2) (x2,y2) 的时候也有用。基于这个假设,可以显著地减少参数数量。举个例子,我们将深度维度上一个单独的2维切片看做深度切片(depth slice),对于一个经过卷积后尺寸为 [55x55x96] 的数据体就有96个深度切片,每个尺寸为[55x55],其中在每个深度切片上的结果都使用同样的权重和偏差获得的。对于 [55x55x96] 的这个数据体,我们可以知道他经过的卷基层有96个卷积核,也就是对应 96 个不同的权重集了,一个权重集对应一个深度切片,如果卷积核的大小是 11x11的,图像是RGB 3 通道的,那么就共有96x11x11x3=34,848个不同的权重,总共有34,944个参数(因为要+96个偏差),并且在每个深度切片中的55x55 的结果使用的都是同样的参数(即权值共享)。

      在反向传播的时候,都要计算每个神经元对它的权重的梯度,但是需要把同一个深度切片上的所有神经元对权重的梯度累加,这样就得到了对共享权重的梯度。这样,每个切片只更新一个权重集。这样做的原因可以通过下面这张图进行解释

    图 3. 将卷积层用全连接层的形式表示

    如上图所示,左侧的神经元是将每一个感受野展开为一列之后串联起来(就是展开排成一列,同一层神经元之间不连接)。右侧的 Deep1i 是深度为1的神经元的第 i 个, Deep2i 是深度为2的神经元的第 i 个,同一个深度的神经元的权值都是相同的,黄色的都是相同的(上面4个与下面4个的参数相同),蓝色都是相同的。所以现在回过头来看上面说的卷积神经网络的反向传播公式对梯度进行累加求和也是基于这点考虑(同一深度的不同神经元共用一组参数,所以累加);而每个切片只更新一个权重集的原因也是这样的,因为从图3 中可以看到,不同深度的神经元不会公用相同的权重,所以只能更新一个权重集。

      注意,如果在一个深度切片中的所有权重都使用同一个权重向量,那么卷积层的前向传播在每个深度切片中可以看做是在计算神经元权重和输入数据体的卷积(这就是“卷积层”名字由来)。这也是为什么总是将这些权重集合称为滤波器(filter)(或卷积核(kernel)),因为它们和输入进行了卷积。

      注意,有时候参数共享假设可能没有意义,特别是当卷积神经网络的输入图像是一些明确的中心结构时候。这时候我们就应该期望在图片的不同位置学习到完全不同的特征(而一个卷积核滑动地与图像做卷积都是在学习相同的特征)。一个具体的例子就是输入图像是人脸,人脸一般都处于图片中心,而我们期望在不同的位置学习到不同的特征,比如眼睛特征或者头发特征可能(也应该)会在图片的不同位置被学习。在这个例子中,通常就放松参数共享的限制,将层称为局部连接层(Locally-Connected Layer)。

    2.1.5 卷积层的超参数及选择

      由于参数共享,每个滤波器包含 F ⋅ F ⋅ D 1 F \cdot F\cdot D_1 FFD1 个权重(字符的具体含义在2.1.3中有介绍),卷积层一共有$ F\cdot F\cdot D_1\cdot K$个权重和 K K K 个偏置。在输出数据体中,第d个深度切片(空间尺寸是 W 2 × H 2 W_2\times H_2 W2×H2),用第d个滤波器和输入数据进行有效卷积运算的结果(使用步长S),最后在加上第d个偏差。

      对这些超参数,常见的设置是 F = 3 , S = 1 , P = 1 F=3,S=1,P=1 F=3S=1P=1。同时设置这些超参数也有一些约定俗成的惯例和经验,可以在下面的“卷积神经网络结构”中查看。

    2.1.6 卷积层演示

      因为3D数据难以可视化,所以所有的数据(输入数据体是蓝色,权重数据体是红色,输出数据体是绿色)都采取将深度切片按照列的方式排列展现。输入数据体的尺寸是 W 1 = 5 , H 1 = 5 , D 1 = 3 W_1=5,H_1=5,D_1=3 W1=5,H1=5,D1=3,卷积层参数 K = 2 , F = 3 , S = 2 , P = 1 K=2,F=3,S=2,P=1 K=2,F=3,S=2,P=1。就是说,有2个滤波器,滤波器的尺寸是 3 ⋅ 3 3\cdot 3 33,它们的步长是2。因此,输出数据体的空间尺寸是 ( 5 − 3 + 2 ) / 2 + 1 = 3 (5-3+2)/2+1=3 (53+2)/2+1=3。注意输入数据体使用了零填充 P = 1 P=1 P=1,所以输入数据体外边缘一圈都是0。下面的例子在绿色的输出激活数据上循环演示,展示了其中每个元素都是先通过蓝色的输入数据和红色的滤波器逐元素相乘,然后求其总和,最后加上偏差得来。

    无法正常显示,请参考http://cs231n.github.io/convolutional-networks/ 的动图
    图 4. 卷积层演示过程

    2.1.7 用矩阵乘法实现卷积

      卷积运算本质上就是在滤波器和输入数据的局部区域间做点积。卷积层的常用实现方式就是利用这一点,将卷积层的前向传播变成一个巨大的矩阵乘法。

      (1) 输入图像的局部区域被 i m 2 c o im2co im2co l操作拉伸为列。比如输入是[227x227x3],要与尺寸为11x11x3的滤波器以步长为4进行卷积,就依次取输入中的[11x11x3]数据块,然后将其拉伸为长度为11x11x3=363的列向量。重复进行这一过程,因为步长为4,所以经过卷积后的宽和高均为(227-11)/4+1=55,共有55x55=3,025个个神经元。因为每一个神经元实际上都是对应有 363 的列向量构成的感受野,即一共要从输入上取出 3025 个 363 维的列向量。所以经过im2col操作得到的输出矩阵 X c o l X_col Xcol 的尺寸是[363x3025],其中每列是拉伸的感受野。注意因为感受野之间有重叠,所以输入数据体中的数字在不同的列中可能有重复。

      (2) 卷积层的权重也同样被拉伸成行。举例,如果有96个尺寸为[11x11x3]的滤波器,就生成一个矩阵 W r o w W_row Wrow,尺寸为[96x363]。

      (3) 现在卷积的结果和进行一个大矩阵乘法 n p . d o t ( W r o w , X c o l ) np.dot(W_row, X_col) np.dot(Wrow,Xcol) 是等价的了,能得到每个滤波器和每个感受野间的点积。在我们的例子中,这个操作的输出是[96x3025],给出了每个滤波器在每个位置的点积输出。注意其中的 n p . d o t np.dot np.dot 计算的是矩阵乘法而不是点积。

      (4) 结果最后必须被重新变为合理的输出尺寸[55x55x96]。

      这个方法的缺点就是占用内存太多,因为在输入数据体中的某些值在 X c o l X_col Xcol中被复制了多次;优点在于矩阵乘法有非常多的高效底层实现方式(比如常用的BLAS API)。还有,同样的im2col思路可以用在池化操作中。反向传播:卷积操作的反向传播(同时对于数据和权重)还是一个卷积(但是和空间上翻转的滤波器),所以依然可以使用矩阵相乘来计算卷积的反向传播。

      为什么卷积的反向传播还是卷积,这个可以参考另一篇文章 《CNN中卷积层与转置卷积层的关系(转置卷积又称反卷积、分数步长卷积)》 2.2 卷积的前向传播和反向传播 中的内容。

    2.1.8 其他形式的卷积操作

      1x1卷积:一些论文中使用了1x1的卷积,这个方法最早是在论文Network in Network中出现。人们刚开始看见这个1x1卷积的时候比较困惑,尤其是那些具有信号处理专业背景的人。因为信号是2维的,所以1x1卷积就没有意义。但是,在卷积神经网络中不是这样,因为这里是对3个维度进行操作,滤波器和输入数据体的深度是一样的。比如,如果输入是[32x32x3],那么1x1卷积就是在高效地进行3维点积(因为输入深度是3个通道);另外的一种想法是将这种卷积的结果看作是全连接层的一种实现方式,详见本文2.4.2 部分。

      扩张卷积:最近一个研究(Fisher Yu和Vladlen Koltun的论文)给卷积层引入了一个新的叫扩张(dilation)的超参数。到目前为止,我们只讨论了卷积层滤波器是连续的情况。但是,让滤波器中元素之间有间隙也是可以的,这就叫做扩张。如图5 为进行1扩张。

    图 4. 扩张卷积的例子及扩张前后的叠加效果

    在某些设置中,扩张卷积与正常卷积结合起来非常有用,因为在很少的层数内更快地汇集输入图片的大尺度特征。比如,如果上下重叠2个3x3的卷积层,那么第二个卷积层的神经元的感受野是输入数据体中5x5的区域(可以成这些神经元的有效感受野是5x5,如图5 所示)。如果我们对卷积进行扩张,那么这个有效感受野就会迅速增长。

    2.2 池化层

      通常在连续的卷积层之间会周期性地插入一个池化层。它的作用是逐渐降低数据体的空间尺寸,这样的话就能减少网络中参数的数量,使得计算资源耗费变少,也能有效控制过拟合。汇聚层使用 MAX 操作,对输入数据体的每一个深度切片独立进行操作,改变它的空间尺寸。最常见的形式是汇聚层使用尺寸2x2的滤波器,以步长为2来对每个深度切片进行降采样,将其中75%的激活信息都丢掉。每个MAX操作是从4个数字中取最大值(也就是在深度切片中某个2x2的区域),深度保持不变。

      汇聚层的一些公式:输入数据体尺寸 W 1 ⋅ H 1 ⋅ D 1 W_1\cdot H_1\cdot D_1 W1H1D1,有两个超参数:空间大小 F F F和步长 S S S;输出数据体的尺寸 W 2 ⋅ H 2 ⋅ D 2 W_2\cdot H_2\cdot D_2 W2H2D2,其中
    W 2 = ( W 1 − F ) / S + 1 H 2 = ( H 1 − F ) / S + 1 D 2 = D 1 \begin{matrix} W_2=(W_1-F)/S+1 \\ H_2=(H_1-F)/S+1 \\ D_2=D1 \\ \end{matrix} W2=(W1F)/S+1H2=(H1F)/S+1D2=D1
    这里面与之前的卷积的尺寸计算的区别主要在于两点,首先在池化的过程中基本不会进行另补充;其次池化前后深度不变。

      在实践中,最大池化层通常只有两种形式:一种是 F = 3 , S = 2 F=3,S=2 F=3,S=2,也叫重叠汇聚(overlapping pooling),另一个更常用的是 F = 2 , S = 2 F=2,S=2 F=2,S=2。对更大感受野进行池化需要的池化尺寸也更大,而且往往对网络有破坏性。
      普通池化(General Pooling):除了最大池化,池化单元还可以使用其他的函数,比如平均池化(average pooling)或L-2范式池化(L2-norm pooling)。平均池化历史上比较常用,但是现在已经很少使用了。因为实践证明,最大池化的效果比平均池化要好。

      反向传播:回顾一下反向传播的内容,其中max(x,y)函数的反向传播可以简单理解为将梯度只沿最大的数回传。因此,在向前传播经过汇聚层的时候,通常会把池中最大元素的索引记录下来(有时这个也叫作道岔(switches)),这样在反向传播的时候梯度的时候就很高效。

      下面将简单介绍平均池化和最大池化反向传播的计算过程。首先是平均池化,在前向传播的过程中,是计算窗口中的均值,很容易理解;在反向传播的过程中,是将要反向传播的梯度组分在平均分在四个部分传回去,具体如下所示

    forward: [1 3; 2 2] -> [2]
    backward: [2] -> [0.5 0.5; 0.5 0.5]
    

    对于最大池化,就如上面所说的,会把池中最大元素的索引记录下来,在反向传播的时候只对这样的位置进行反向传播,忽略其他的位置

    forward: [1 3; 2 2] -> 3
    backward: [3] -> [0 3; 0 0]
    

      不使用汇聚层:很多人不喜欢汇聚操作,认为可以不使用它。比如在Striving for Simplicity: The All Convolutional Net一文中,提出使用一种只有重复的卷积层组成的结构,抛弃汇聚层。通过在卷积层中使用更大的步长来降低数据体的尺寸。有发现认为,在训练一个良好的生成模型时,弃用汇聚层也是很重要的。比如变化自编码器(VAEs:variational autoencoders)和生成性对抗网络(GANs:generative adversarial networks)。现在看起来,未来的卷积网络结构中,可能会很少使用甚至不使用汇聚层

    2.3 归一化层

      在卷积神经网络的结构中,提出了很多不同类型的归一化层,有时候是为了实现在生物大脑中观测到的抑制机制。但是这些层渐渐都不再流行,因为实践证明它们的效果即使存在,也是极其有限的

    2.4 全连接层

      这个和常规神经网络中的一样,它们的激活可以先用矩阵乘法,再加上偏差。

    2.4.1 将卷积层转化成全连接层

      对于任意一个卷积层,都存在一个能实现和它一样的前向传播函数的全连接层。该全连接层的权重是一个巨大的矩阵,除了某些特定块(感受野),其余部分都是零;而在非 0 部分中,大部分元素都是相等的(权值共享),具体可以参考图3。如果把全连接层转化成卷积层,以输出层的 Deep11 为例,与它有关的输入神经元只有上面四个,所以在权重矩阵中与它相乘的元素,除了它所对应的4个,剩下的均为0,这也就解释了为什么权重矩阵中有为零的部分;另外要把“将全连接层转化成卷积层”和“用矩阵乘法实现卷积”区别开,这两者是不同的,后者本身还是在计算卷积,只不过将其展开为矩阵相乘的形式,并不是"将全连接层转化成卷积层",所以除非权重中本身有零,否则用矩阵乘法实现卷积的过程中不会出现值为0的权重。

    2.4.2 将全连接层转化成卷积层

      任何全连接层都可以被转化为卷积层。比如,一个K=4096的全连接层,输入数据体的尺寸是 7 × 7 × 512 7\times 7\times 512 7×7×512,这个全连接层可以被等效地看做一个 F = 7 , P = 0 , S = 1 , K = 4096 F=7,P=0,S=1,K=4096 F=7,P=0,S=1,K=4096的卷积层。换句话说,就是将滤波器的尺寸设置为和输入数据体的尺寸设为一致的。因为只有一个单独的深度列覆盖并滑过输入数据体,所以输出将变成 1 × 1 × 4096 1\times 1\times 4096 1×1×4096,这个结果就和使用初始的那个全连接层一样了。这个实际上也很好理解,因为,对于其中的一个卷积滤波器,这个滤波器的的深度为512,也就是说,虽然这个卷积滤波器的输出只有1个,但是它的权重有 7 × 7 × 512 7\times 7\times 512 7×7×512,相当于卷积滤波器的输出为一个神经元,这个神经元与上一层的所有神经元相连接,而这样与前一层所有神经元相连接的神经元一共有4096个,这不就是一个全连接网络嘛~

      在上述的两种变换中,将全连接层转化为卷积层在实际运用中更加有用。假设一个卷积神经网络的输入是224x224x3的图像,一系列的卷积层和汇聚层将图像数据变为尺寸为7x7x512的激活数据体(在AlexNet中就是这样,通过使用5个汇聚层来对输入数据进行空间上的降采样,每次尺寸下降一半,所以最终空间尺寸为224/2/2/2/2/2=7)。从这里可以看到,AlexNet使用了两个尺寸为4096的全连接层,最后一个有1000个神经元的全连接层用于计算分类评分。我们可以将这3个全连接转化为3个卷积层:

      (1) 针对第一个连接区域是[7x7x512]的全连接层,令其滤波器尺寸为F=7,这样输出数据体就为[1x1x4096]了。

      (2) 针对第二个全连接层,令其滤波器尺寸为F=1,这样输出数据体为[1x1x4096]。

      (3) 对最后一个全连接层也做类似的,令其F=1,最终输出为[1x1x1000]。

      这样做的目的是让卷积网络在一张更大的输入图片上滑动,得到多个输出,这样的转化可以让我们在单个向前传播的过程中完成上述的操作。

      举个例子,如果我们想让224x224尺寸的浮窗,以步长为32在384x384的图片上滑动,把每个经停的位置都带入卷积网络,最后得到6x6个位置的类别得分。上述的把全连接层转换成卷积层的做法会更简便。如果224x224的输入图片经过卷积层和汇聚层之后得到了[7x7x512]的数组,那么,384x384的大图片直接经过同样的卷积层和汇聚层之后会得到[12x12x512]的数组(因为途径5个汇聚层,尺寸变为384/2/2/2/2/2 = 12)。然后再经过上面由3个全连接层转化得到的3个卷积层,最终得到[6x6x1000]的输出(因为(12 - 7)/1 + 1 = 6)。这个结果正是浮窗在原图经停的6x6个位置的得分!

      面对384x384的图像,让(含全连接层)的初始卷积神经网络以32像素的步长独立对图像中的224x224块进行多次评价,其效果和使用把全连接层变换为卷积层后的卷积神经网络进行一次前向传播是一样的。相较于使用被转化前的原始卷积神经网络对所有36个位置进行迭代计算,使用转化后的卷积神经网络进行一次前向传播计算要高效得多,因为36次计算都在共享计算资源。

      这里有几个问题,首先为什么是以32为步长,如果我以64为步长呢?再或者如果我们想用步长小于32(如16)的浮窗怎么办

      首先回答其中的第一个问题。这个是因为其中一个有五个汇聚层,因为 2 5 = 32 2^5=32 25=32,也就是在原始图像上的宽或者高增加 32 32 32 个像素,经过这些卷积和汇聚后,将变为一个像素。现在进行举例说明,虽然例子并没有32那么大的尺寸,但是意义都是一样的。假设较小的输入图像尺寸为 4×4,进行一次卷积核为 F = 3 , S = 1 , P = 1 F=3, S=1, P=1 F=3,S=1,P=1的卷积后,再进行一次 2 × 2 2\times 2 2×2 的 maxpooling;然后在进行一次这样的卷积,再进行一次池化。假设较大的原始图像的尺寸为 8×8,以步长为4进行滑窗操作,对于每个窗内进行同样的卷积池化操作,如图5所示

    图 5. 以步长为4在原始图像上滑动取出4×4窗口再计算卷积的结果

    对较小的原始图像(图5左图红框)进行卷积得到的结果是图5右图红色框内的结果,通过划窗取样再进行卷积运算获得的结果为图5中右侧四种颜色加在一起的样子。所以以步长为4在8x8的图片上滑动,把每个经停的位置都带入卷积网络,最后得到2x2个位置的卷积结果,但是如果直接使用卷积核 F = 3 , S = 1 , P = 1 F=3, S=1, P=1 F=3,S=1,P=1进行两次卷积池化的话,得到的结果的大小显然也是2×2的。

      所以从获得结果来看,这两者是相同的,但是不同点在哪呢?如图6所示,是在整个图像上进行卷积运算和以步长为4在8x8的图片上滑动所经停的第一个位置,这两种方法使用相同的卷积核进行计算的对比图。

    这里写图片描述
    图6. 使用整张图像和一部分图像计算某一点处的卷积

    如图6所示,左图代表使用整张图像时计算a点处的卷积,右图代表使用滑动的方法第一次经停图像上a点的卷积,两张图中的a点是同一个a点。虽然同一个卷积模板进行计算,但是在计算卷积的过程是不同的!因为在右图中a的右侧及右下侧是0,而在左图中是原始的像素值,所以计算的卷积一定是不同的。但是要怎么理解这样的差别呢?这要从补零的意义讲起,补零是因为如果不补零的话,图像经过卷积之后的尺寸会小于原始的尺寸,补零可以保证图像的尺寸不变,所以归根结底补零实际上是一种图像填充的方法。左图中a的右边及右下角的像素是原始图像的像素,相当于在计算a点的时候,不是用0进行的补充,而是原始像素值进行补充,这样的不仅可以保持卷积前后图像的大小不变,而且可以这种图像填充方法得到的结果显然要比填0更接近与原始图像,保留的信息更多。

      小结

      (1) 用一整图像进行卷积和在较大的图像上通过滑动窗提取出一个个子图象进行卷积得到的效果是相同的。

      (2) 可以这样做的主要原因在于将最后的全连接层改写成了卷积层。

      (3) 在一整张图像做卷积的效率要远远高于在图像上滑动的效率,因为前者只需要一次前向传播,而后者需要多次

      (4) 用整张图像计算与滑动窗口的方法对比,所补充的零更少(如上所讲,不用零而是用在其旁边的像素代替),提取的信息损失的更少。

      即,用整张图像直接计算卷积不仅仅在效率上高于使用滑动窗口的方法,而且更多的保留了图像的细节,完胜!

      还可以得到另一个结论在较大的图像上以步长为 2 L 2^L 2L进行滑动然后再计算的卷积结果,其效果与 以步长为 1 在大图像直接卷积得到的结果上 移动 所获得的结果 是等价的。如图5中的大红色框对应小红色框,大黄色框对应小黄色框。所以当步长为64时,将相当于以步长为2在大图的卷积结果上移动。

      对于第二个问题,如果我非要以16为步长呢?是可以的,只是这个时候所获得结果不再是如图5的那种滑动的计算方式了。还是举例说明,在上一个例子中,我们将步长32改为4,所以这里将步长16改为2进行分析。假如说原始的输入图像为一个 4×4 的图像,现在将使用一个比原来大的图像,是一个8×8的图像,使用卷积核为 4×4 大小,步长为4,则在图像进行卷积运算的如图6左侧的4个部分(红黄绿蓝),而图6 右侧的是步长为2时与原始相比增加的部分。将图 6中两个部分相加就可以得到步长为2它时所有进行卷积运算的部分了。

    这里写图片描述
    图6 .步长为4时原始图像进行卷积的部分及将步长改为2时比原来多出的部分

      获得步长为2的时进行卷积的区域。首先像之前一样对原始图像做以4为步长的卷积,这时进行卷积的部分就是图6中左侧的部分;其次将原始图片沿宽度方向平移2个像素之后,依旧进行步长为4的卷积,这个时候进行卷积的部分为图6中的红色部分和绿色部分;然后沿高度方向平移2个像素之后,按步长为4进行卷积,这个时候进行卷积的部分为图6中的蓝色部分和黄色部分;最后沿高度方向和宽度方向同时移动2个像素,按步长为4进行卷积,这个时候进行卷积的部分为图6中的紫色部分。将这些部分加在一起就是进行卷积运算你得所有区域了。

      这个结果明显是无法通过像图5中的那样滑动得到了,这样的方法所需要进行卷积的区域要远远大于以4为步长时所需要就进行卷积运算的区域;后续的卷积都是在这一卷积的结果上进行的,所以后面的都会发生改变。

      综上,步长为32的正整数倍只是保证 直接在最后的卷积结果上进行滑动和滑窗效果相等 的下限值。

    3. 卷积神经网络的结构

      卷积神经网络通常是由三种层构成:卷积层,汇聚层(除非特别说明,一般就是最大值汇聚)和全连接层(简称FC)。ReLU激活函数也应该算是是一层,它逐元素地进行激活函数操作,常常将它与卷积层看作是同一层。

    3.1 层的排列规律

      卷积神经网络最常见的形式就是将一些卷积层和ReLU层放在一起,其后紧跟汇聚层,然后重复如此直到图像在空间上被缩小到一个足够小的尺寸,在某个地方过渡成成全连接层也较为常见。最后的全连接层得到输出,比如分类评分等。换句话说,最常见的卷积神经网络结构如下:
    I N P U T → [ C O N V → R E L U ] ∗ N → [ P O O L ? ] ∗ M → [ F C → R E L U ] ∗ K → F C INPUT \rightarrow [CONV \rightarrow RELU]*N \rightarrow [POOL?]*M \rightarrow [FC \rightarrow RELU]*K \rightarrow FC INPUT[CONVRELU]N[POOL?]M[FCRELU]KFC

    其中*指的是重复次数,POOL?指的是一个可选的汇聚层。其中N >=0,通常N<=3,M>=0,K>=0,通常K<3。例如,下面是一些常见的网络结构规律:

    • INPUT -> FC ,实现一个线性分类器,此处N = M = K = 0。

    • INPUT -> CONV -> RELU -> FC,单层的卷积神经网络

    • *INPUT -> [CONV -> RELU -> POOL]2 -> FC -> RELU -> FC,此处在每个汇聚层之间有一个卷积层,这种网络就是简单的多层的卷积神经网络。

    • **INPUT -> [CONV -> RELU -> CONV -> RELU -> POOL]3 -> [FC -> RELU]2 -> FC ,此处每个汇聚层前有两个卷积层,这个思路适用于更大更深的网络(比如说这个思路就和VGG比较像),因为在执行具有破坏性的汇聚操作前,多重的卷积层可以从输入数据中学习到更多的复杂特征。

      最新进展:传统的将层按照线性进行排列的方法已经受到了挑战,挑战来自谷歌的Inception结构和微软亚洲研究院的残差网络(Residual Net)结构。这两个网络的特征更加复杂,连接结构也不同。

    3.2 卷积层的大小选择

      几个小滤波器卷积层的组合比一个大滤波器卷积层好。假设你一层一层地重叠了3个3x3的卷积层(层与层之间有非线性激活函数)。在这个排列下,第一个卷积层中的每个神经元都对输入数据体有一个3x3的视野。第二个卷积层上的神经元对第一个卷积层有一个3x3的视野,也就是对输入数据体有5x5的视野。同样,在第三个卷积层上的神经元对第二个卷积层有3x3的视野,也就是对输入数据体有7x7的视野。假设不采用这3个3x3的卷积层,而是使用一个单独的有7x7的感受野的卷积层,那么所有神经元的感受野也是7x7,但是这样有一些缺点。首先,多个卷积层与非线性的激活层交替的结构,比单一卷积层的结构更能提取出深层的更好的特征。其次,假设所有的数据有C个通道,那么单独的7x7卷积层将会包含 C × ( 7 × 7 × C ) = 49 C 2 C\times (7\times 7\times C)=49C^2 C×(7×7×C)=49C2个参数,而3个3x3的卷积层的组合仅有 3 × ( C × ( 3 × 3 × C ) ) = 27 C 2 3\times (C\times (3\times 3\times C))=27C^2 3×(C×(3×3×C))=27C2个参数。直观说来,最好选择带有小滤波器的卷积层组合,而不是用一个带有大的滤波器的卷积层。前者可以表达出输入数据中更多个强力特征,使用的参数也更少。唯一的不足是,在进行反向传播时,中间的卷积层可能会导致占用更多的内存。所以他减少的是参数量,而非计算量。

    3.3 层的尺寸设置规律

    • 输入层 ,应该能被2整除很多次。常用数字包括32(比如CIFAR-10),64,96(比如STL-10)或224(比如ImageNet卷积神经网络),384和512。

    • 卷积层 ,应该使用小尺寸滤波器(比如3x3或最多5x5),使用步长S=1。还有一点非常重要,就是对输入数据进行零填充,这样卷积层就不会改变输入数据在空间维度上的尺寸。比如,当F=3,那就使用P=1来保持输入尺寸。当F=5,P=2,一般对于任意F,当P=(F-1)/2的时候能保持输入尺寸。如果必须使用更大的滤波器尺寸(比如7x7之类),通常只用在第一个面对原始图像的卷积层上。

    • 汇聚层 ,负责对输入数据的空间维度进行降采样。最常用的设置是用用2x2感受野(即F=2)的最大值汇聚,步长为2(S=2)。注意这一操作将会把输入数据中75%的激活数据丢弃(因为对宽度和高度都进行了2的降采样)。另一个不那么常用的设置是使用3x3的感受野,步长为2。最大值汇聚的感受野尺寸很少有超过3的,因为汇聚操作过于激烈,易造成数据信息丢失,这通常会导致算法性能变差。

      上文中展示的两种设置(卷积层F=3,P=1,汇聚层F=2,P=2)是很好的,因为所有的卷积层都能保持其输入数据的空间尺寸,汇聚层只负责对数据体从空间维度进行降采样。如果使用的步长大于1并且不对卷积层的输入数据使用零填充,那么就必须非常仔细地监督输入数据体通过整个卷积神经网络结构的过程,确认所有的步长和滤波器都尺寸互相吻合,卷积神经网络的结构美妙对称地联系在一起。

      为何使用零填充?使用零填充除了前面提到的可以让卷积层的输出数据保持和输入数据在空间维度的不变,还可以提高算法性能。如果卷积层值进行卷积而不进行零填充,那么数据体的尺寸就会略微减小,那么图像边缘的信息就会过快地损失掉。

      因为内存限制所做的妥协:在某些案例(尤其是早期的卷积神经网络结构)中,基于前面的各种规则,内存的使用量迅速飙升。例如,使用64个尺寸为3x3的滤波器对224x224x3的图像进行卷积,零填充为1,得到的激活数据体尺寸是[224x224x64]。这个数量就是一千万的激活数据,或者就是72MB的内存(每张图就是这么多,激活函数和梯度都是)。因为GPU通常因为内存导致性能瓶颈,所以做出一些妥协是必须的。在实践中,人们倾向于在网络的第一个卷积层做出妥协。例如,可以妥协可能是在第一个卷积层使用步长为2,尺寸为7x7的滤波器(比如在ZFnet中)。在AlexNet中,滤波器的尺寸的11x11,步长为4。

    4. 案例学习

      下面是卷积神经网络领域中比较有名的几种结构:

    • LeNet ,第一个成功的卷积神经网络应用,是Yann LeCun在上世纪90年代实现的。当然,最著名还是被应用在识别数字和邮政编码等的LeNet结构。

    • AlexNet ,AlexNet卷积神经网络在计算机视觉领域中受到欢迎,它由Alex Krizhevsky,Ilya Sutskever和Geoff Hinton实现。AlexNet在2012年的ImageNet ILSVRC 竞赛中夺冠,性能远远超出第二名(16%的top5错误率,第二名是26%的top5错误率)。这个网络的结构和LeNet非常类似,但是更深更大,并且使用了层叠的卷积层来获取特征(之前通常是只用一个卷积层并且在其后马上跟着一个汇聚层)。

    • ZF Net ,Matthew Zeiler和Rob Fergus发明的网络在ILSVRC 2013比赛中夺冠,它被称为 ZFNet(Zeiler & Fergus Net的简称)。它通过修改结构中的超参数来实现对AlexNet的改良,具体说来就是增加了中间卷积层的尺寸让第一层的步长和滤波器尺寸更小

    • GoogLeNet ,ILSVRC 2014的胜利者是谷歌的Szeged等实现的卷积神经网络。它主要的贡献就是实现了一个奠基模块,它能够显著地减少网络中参数的数量(AlexNet中有60M,该网络中只有4M)。还有,这个论文中没有使用卷积神经网络顶部使用全连接层,而是使用了一个平均汇聚,把大量不是很重要的参数都去除掉了。GooLeNet还有几种改进的版本,最新的一个是Inception-v4

    • VGGNet ,ILSVRC 2014的第二名是Karen Simonyan和 Andrew Zisserman实现的卷积神经网络,现在称其为VGGNet。它主要的贡献是展示出网络的深度是算法优良性能的关键部分。他们最好的网络包含了16个卷积/全连接层。网络的结构非常一致,从头到尾全部使用的是3x3的卷积和2x2的汇聚。他们的预训练模型是可以在网络上获得并在Caffe中使用的。VGGNet不好的一点是它耗费更多计算资源,并且使用了更多的参数,导致更多的内存占用(140M)。其中绝大多数的参数都是来自于第一个全连接层。后来发现这些全连接层即使被去除,对于性能也没有什么影响,这样就显著降低了参数数量。

    • ResNet ,残差网络(Residual Network)是ILSVRC2015的胜利者,由何恺明等实现。它使用了特殊的跳跃链接,大量使用了批量归一化(batch normalization)。这个结构同样在最后没有使用全连接层。读者可以查看何恺明的的演讲,以及一些使用Torch重现网络的实验。ResNet当前最好的卷积神经网络模型(2016年五月)。何开明等最近的工作是对原始结构做一些优化,可以看论文Identity Mappings in Deep Residual Networks,2016年3月发表。

    4.1 VGGNet的细节

      我们进一步对VGGNet的细节进行分析学习。整个VGGNet中的卷积层都是以步长为1进行3x3的卷积,使用了1的零填充,汇聚层都是以步长为2进行了2x2的最大值汇聚。可以写出处理过程中每一步数据体尺寸的变化,然后对数据尺寸和整体权重的数量进行查看:

    INPUT: [224x224x3]        memory:  224*224*3=150K   weights: 0
    CONV3-64: [224x224x64]  memory:  224*224*64=3.2M   weights: (3*3*3)*64 = 1,728
    CONV3-64: [224x224x64]  memory:  224*224*64=3.2M   weights: (3*3*64)*64 = 36,864
    POOL2: [112x112x64]  memory:  112*112*64=800K   weights: 0
    CONV3-128: [112x112x128]  memory:  112*112*128=1.6M   weights: (3*3*64)*128 = 73,728
    CONV3-128: [112x112x128]  memory:  112*112*128=1.6M   weights: (3*3*128)*128 = 147,456
    POOL2: [56x56x128]  memory:  56*56*128=400K   weights: 0
    CONV3-256: [56x56x256]  memory:  56*56*256=800K   weights: (3*3*128)*256 = 294,912
    CONV3-256: [56x56x256]  memory:  56*56*256=800K   weights: (3*3*256)*256 = 589,824
    CONV3-256: [56x56x256]  memory:  56*56*256=800K   weights: (3*3*256)*256 = 589,824
    POOL2: [28x28x256]  memory:  28*28*256=200K   weights: 0
    CONV3-512: [28x28x512]  memory:  28*28*512=400K   weights: (3*3*256)*512 = 1,179,648
    CONV3-512: [28x28x512]  memory:  28*28*512=400K   weights: (3*3*512)*512 = 2,359,296
    CONV3-512: [28x28x512]  memory:  28*28*512=400K   weights: (3*3*512)*512 = 2,359,296
    POOL2: [14x14x512]  memory:  14*14*512=100K   weights: 0
    CONV3-512: [14x14x512]  memory:  14*14*512=100K   weights: (3*3*512)*512 = 2,359,296
    CONV3-512: [14x14x512]  memory:  14*14*512=100K   weights: (3*3*512)*512 = 2,359,296
    CONV3-512: [14x14x512]  memory:  14*14*512=100K   weights: (3*3*512)*512 = 2,359,296
    POOL2: [7x7x512]  memory:  7*7*512=25K  weights: 0
    FC: [1x1x4096]  memory:  4096  weights: 7*7*512*4096 = 102,760,448
    FC: [1x1x4096]  memory:  4096  weights: 4096*4096 = 16,777,216
    FC: [1x1x1000]  memory:  1000 weights: 4096*1000 = 4,096,000
    
    TOTAL memory: 24M * 4 bytes ~= 93MB / image (only forward! ~*2 for bwd)
    TOTAL params: 138M parameters
    

      注意,大部分的内存和计算时间都被前面的卷积层占用,大部分的参数都用在后面的全连接层,这在卷积神经网络中是比较常见的。在这个例子中,全部参数有140M,但第一个全连接层就包含了100M的参数

    5. 计算上的考量

      在构建卷积神经网络结构时,最大的瓶颈是内存瓶颈,所以如何降低内存消耗量是一个值得思考的问题。三种内存占用来源:

    • 1 来自中间数据体尺寸:卷积神经网络中的每一层中都有激活数据体的原始数值,以及损失函数对它们的梯度(和激活数据体尺寸一致)。通常,大部分激活数据都是在网络中靠前的层中(比如第一个卷积层)。在训练时,这些数据需要放在内存中,因为反向传播的时候还会用到。但是在测试时可以聪明点:让网络在测试运行时候每层都只存储当前的激活数据,然后丢弃前面层的激活数据,这样就能减少巨大的激活数据量。这实际上是底层问题,在编写框架的过程中,设计者会进行这方面的考虑。

    • 2 来自参数尺寸:即整个网络的参数的数量,在反向传播时它们的梯度值,以及使用momentum、Adagrad或RMSProp等方法进行最优化时的每一步计算缓存。因此,存储参数向量的内存通常需要在参数向量的容量基础上乘以3或者更多。

    • 3 卷积神经网络实现还有各种零散的内存占用,比如成批的训练数据,扩充的数据等等。

      一旦对于所有这些数值的数量有了一个大略估计(包含激活数据,梯度和各种杂项),数量应该转化为以GB为计量单位。把这个值乘以4,得到原始的字节数(因为每个浮点数占用4个字节,如果是双精度浮点数那就是占用8个字节),然后多次除以1024分别得到占用内存的KB,MB,最后是GB计量。如果你的网络工作得不好,一个常用的方法是降低批尺寸(batch size),因为绝大多数的内存都是被激活数据消耗掉了。

    展开全文
  • Iris鸢尾花卉数据集,是一类多重变量分析的数据集。数据集包含150个数据样本,分为3类,每类50个数据,每个数据包含4个属性,分别对应花萼长度,花萼宽度,花瓣长度,花瓣宽度。 本次实验目的是用Bp神经网络对iris...

    数据集已上传,结尾链接下载即可!!!

    一、实验内容

    Iris鸢尾花卉数据集,是一类多重变量分析的数据集。数据集包含150个数据样本,分为3类,每类50个数据,每个数据包含4个属性,分别对应花萼长度,花萼宽度,花瓣长度,花瓣宽度。
    本次实验目的是用Bp神经网络对iris数据集进行分类,并进行分析。

    二、实验过程

    1.将数据集加载到工作区:load iris.dat;

    在这里插入图片描述

    2.数据预处理

    (1)将原始iris数据集随机抽取118行作为训练数据集。再在这118行之外抽取32行(即剩余的32行)作为测试数据集。然后分别保存在trainData1.txt和testData1.txt中。
    A1=randsample(150,118,false) ;
    trainData1=iris(A1,:);
    A2=randsample(150,32,false) ;
    testData1=iris(A2,:);
    save(‘trainData1.txt’,‘trainData1’,’-ascii’),
    save(‘testData1.txt’,‘testData1’,’-ascii’),
    在这里插入图片描述
    在这里插入图片描述
    (2)将训练数据每行相同格式的数据按语法读取150次,然后得到每列。
    f1 f2 f3 f4是四个特征值,class是类别。
    [f1,f2,f3,f4,class] = textread(‘trainData1.txt’ , ‘%f%f%f%f%f’,150);
    (3)特征值归一化
    把输入的4个属性矩阵归一化到[-1,1],并保存到input矩阵中。
    即下式为归一化公式:
    在这里插入图片描述
    [input,minI,maxI] = premnmx( [f1 , f2 , f3 , f4 ]’)
    在这里插入图片描述
    (4)构造输出矩阵
    s = length( class) ; %得到类别矩阵长度:118
    Output =zeros(s,3); %生成s行3列的全零阵:预先分配内存空间大小效率更高
    for i = 1 : s
    output( i , class( i ) ) = 1 ;
    end
    在这里插入图片描述
    output矩阵即为输出矩阵
    例第一个样本即第一行:0 1 0 则表示属于第二类,而第二个样本即第二行: 1 0 0 则表示它属于第一类。
    而 0 0 1则表示属于第三类。

    3.创建神经网络

    net = newff(minmax(input),[10 3] , { ‘logsig’ ‘logsig’ } , ‘traingdx’ ); minmax(input):设定输入特征的范围—获取4个输入信号(存储在f1 f2 f3 f4中)的最大值和最小值;
    { ‘logsig’ ‘logsig’ }:隐层和输出层的传递函数–这里隐层和输出层传递函数都为logsig(S形传输函数)
    ‘traingdx’:表示学习规则采用的学习方法,即学习训练函数为traingdx(梯度下降)

    4.设置训练参数

    net.trainparam.show = 50 ;% 显示中间结果的周期
    net.trainparam.epochs = 500 ;%最大迭代次数(学习次数)
    net.trainparam.goal = 0.01;%神经网络训练的目标误差
    net.trainParam.lr = 0.01 ;%学习速率

    5.开始训练

    将原先创建的神经网络以input为输入,output为目标函数的基础上训练epochs次即500次。
    net = train( net, input , output’ ) ;
    这里训练就结束了,以下开始测试。

    6.开始测试

    (1)读取测试数据
    [t1, t2, t3, t4, c] = textread(‘testData1.txt’ , ‘%f%f%f%f%f’,150);
    在这里插入图片描述
    (2)測试数据归一化
    [testInput,minI,maxI] = premnmx( [t1 , t2 , t3 , t4 ]’) ;
    在这里插入图片描述
    (3)仿真
    Y = sim( net , testInput ) ; %其中net为训练后得到的网络
    Y是最终得到的输出结果,在该样本中,哪一类的值更大,则将该样本分给哪一类。
    比如说第一列:就是第一个样本:(2.9182e-05,0.322,0.9606)中0.9606更大,那么将第一个样本分到第三类;再举例说第32列,即第32个样本:(0.0028,0.9408,0.0628)中0.9408更大,那么将第32个样本分给第二类。
    在这里插入图片描述
    在这里插入图片描述
    7.统计识别正确率
    [s1,s2] = size(Y) ; %返回矩阵的行-s1,列-s2
    hitNum = 0 ;
    for i = 1 : s2
    [m,Index]= max(Y(:,i)); % m返回的是Y矩阵第i列中最大的数(归为类)
    if(Index ==c(i)) % index返回的是Y矩阵第i列中最大的数下标
    hitNum =hitNum + 1 ;
    end
    end
    sprintf(‘识别率是 %3.3f%%’,100 * hitNum / s2 )

    最终命令窗口得到识别的正确率如下:基本稳定在96.875%
    在这里插入图片描述

    三、实验结果分析及其对比(三层网络结构)

    1.改变输出层的传递函数 {‘logsig’ ‘logsig’}

    (1)左图输出层为s型传递函数即logsig ,右图为线性传递函数即purelin
    在这里插入图片描述
    Performance:性能-用mse评估,进度条右边是设定的均方误差,即通过参数net.trainparam.goal设定。
    Gradient:梯度-当梯度值达到了1.00e-05时,则停止训练
    Validation check:泛化能力检查-若连续6次训练误差不降反升(误差不再减小,不会再有更好的效果了),则停止训练。

    (2)训练集性能
    在这里插入图片描述
    从纵坐标就可以知道,它通过均方差开衡量网络的性能。由图可看到随着迭代次数的增大,均方误差越来越小,性能越好。
    总结:改变输出层传递函数对训练周期、训练性能有一定影响,本例中左图迭代到208次收敛,均方误差0.00991;而右图迭代到492次才收敛,均方误差0.0144,超过了目标误差0.01;所以输出层为s型传递函数要优于线性传递函数。

    2.改变隐层的传递函数 {‘losig’ ‘losig’}

    (1)左图隐层为s型传递函数即logsig ,右图隐层为双曲正切S型传递函数即tansig
    在这里插入图片描述
    (2)训练集性能
    在这里插入图片描述

    总结:改变隐层传递函数对训练的性能、训练周期都有影响,本例中左图迭代到208次收敛,均方误差0.00991;而右图迭代到183次收敛,均方误差0.00873;所以隐层传递函数使用双曲正切s型传递函数要比S型传递函数更好一些。

    3.改变学习率

    (1)左图学习率为0.01 ,右图为0.05
    在这里插入图片描述
    (2)训练集性能
    在这里插入图片描述
    总结:学习速率的选取大了可能导致系统不稳定,小了会导致训练周期过长、收敛慢,达不到要求的误差。一般通过观察误差下降曲线来判断。同时,由于网络规模大小的不同,学习率选择应当针对其进行调整。

    四、代码(matlab实现)

    参考别人代码,本人仅仅做了一点改动,并加以分析。

    %读取训练数据
    clear
    clc
    load iris.dat;
    
    %随机提取完数据并保存之后,要注释掉以下这6行代码
    %因为每次训练都必须是同样的训练集和测试集,不能每训练一次随机提取一次。
    %A1=randsample(150,118,false) ;
    %trainData1=iris(A1,:);
    %A2=randsample(150,32,false) ;
    %testData1=iris(A2,:);
    %save('trainData1.txt','trainData1','-ascii'),
    %save('testData1.txt','testData1','-ascii'),
    
    %f1 f2 f3 f4是四个特征值
    [f1,f2,f3,f4,class] = textread('trainData1.txt' , '%f%f%f%f%f',150);
    %特征值归一化
    [input,minI,maxI] = premnmx( [f1 , f2 , f3 , f4 ]')  ;
    %构造输出矩阵
    s = length( class) ;
    output = zeros( s , 3  ) ;
    for i = 1 : s 
       output( i , class( i )  ) = 1 ;
    end
    %创建神经网络
    net = newff( minmax(input) , [10 3] , { 'logsig' 'logsig' } , 'traingdx' ) ; 
    %{
        minmax(input):获取4个输入信号(存储在f1 f2 f3 f4中)的最大值和最小值;
        [10,3]:表示使用2层网络,第一层网络节点数为10,第二层网络节点数为3;
        { 'logsig' 'purelin' }:
            表示每一层相应神经元的激活函数;
            即:第一层神经元的激活函数为logsig(对数S形转移函数),第二层为purelin(线性函数)
        'traingdx':表示学习规则采用的学习方法为traingdx(梯度下降自适应学习率训练函数)
    %}
    %设置训练參数
    net.trainparam.show = 50 ;% 显示中间结果的周期
    net.trainparam.epochs = 500 ;%最大迭代次数(学习次数)
    net.trainparam.goal = 0.01;%神经网络训练的目标误差
    net.trainParam.lr = 0.01 ;%学习速率(Learning rate)
    %开始训练
    %其中input为训练集的输入信号,对应output为训练集的输出结果
    net = train( net, input , output' ) ;
    
    %================================训练完成====================================%
    %% 读取测试数据并归一化
    %=============================接下来进行测试=================================%
    %读取测试数据
    [t1, t2, t3, t4, c] = textread('testData1.txt' , '%f%f%f%f%f',150);
    %測试数据归一化
    [testInput,minI,maxI] = premnmx( [t1 , t2 , t3 , t4 ]')  ;
    %仿真
    %其中net为训练后得到的网络,返回的Y为
    Y = sim( net , testInput ) ;
    %统计识别正确率
    [s1 , s2] = size( Y ) ;
    hitNum = 0 ;
    for i = 1 : s2
        [m , Index] = max( Y( : ,  i ) ) ;
        if( Index  == c(i)   ) 
           hitNum = hitNum + 1 ; 
       end
    end
    sprintf('识别率是 %3.3f%%',100 * hitNum / s2 )
    

    数据集链接我已上传,好久没看评论!!!鸢尾花数据集

    展开全文
  • 入门学习Linux常用必会60个命令实例详解doc/txt

    千次下载 热门讨论 2011-06-09 00:08:45
    本资源来自网络,如纰漏还请告知,如觉得还不错,请留言告知后来人,谢谢!!!!! 入门学习Linux常用必会60个命令实例详解 Linux必学的60个命令 Linux提供了大量的命令,利用它可以有效地完成大量的工作,如...
  • 针对多相整流电路的特点,采用对输出电压分段平均值...设计了该算法的神经网络结构,并讨论了网络结构参数对故障诊断结果的影响;此方法简单易行,误判率低,训练时间短,对各种多重多相化的整流电路具有普遍的适用性。
  • 具有信息多重蒸馏网络的轻型图像超分辨率(ACM MM 2019) IMDN的简化版本在“约束的超分辨率挑战赛”(Track1和Track2)中获得了第一名。 测试代码可从以下网址获得: IMDN的超轻量级版本在“超分辨率算法性能比较...
  • 1.什么是Linux? 回答:Linux是基于Linux内核的操作系统。它是一个开源操作系统,可以在不同的硬件平台上运行。它为用户提供了免费的低成本操作系统。这是一个用户友好的环境,他们可以在其中轻松修改和创建源代码...
  • 软件测试面试题汇总

    万次阅读 多人点赞 2018-09-27 12:31:09
    在使用的过程中,有什么问题? ................................................................................. 5 7 、描述测试用例设计的完整过程? ......................................................
  • 【数据库学习】数据库总结

    万次阅读 多人点赞 2018-07-26 13:26:41
    数据库是长期存储在计算机内、组织的、可共享的大量数据的集合。 常见数据库管理系统:Access、mysql、sql server 2)特点 ①数据库数据特点 永久存储、组织、可共享。 (数据的最小存取...
  • C#基础教程-c#实例教程,适合初学者

    万次阅读 多人点赞 2016-08-22 11:13:24
    C#语言不支持多重继承。 1.2 编写控制台应用程序 使用SDK命令行工具编写控制台程序 第一个程序总是非常简单的,程序首先让用户通过键盘输入自己的名字,然后程序在屏幕上打印一条欢迎信息。程序的代码是这样的: ...
  • 随机森林多重插补数据Missing data is a common problem in data science — one that tends to cause a lot of headaches. Some algorithms simply can’t handle it. Linear regression, support vector machines,...
  • 线性回归

    万次阅读 多人点赞 2016-06-03 10:22:43
    在实际数据中,变量之间的多重共线性是一个非常普遍的现象,其产生机理及相关解决方案在“特征选择和评估”中介绍。 基于基函数的线性回归模型 前面已经提到过,线性回归模型的基本特征就是,模型是参数...
  • 首先运用偏最小二乘法对导水裂缝带高度的影响因素进行分析,对原始数据降维处理提取主成分,优化了原始数据,克服了变量间因样本量小而产生的多重相关性影响,并对自变量、因变量具有很强的解释能力。再将提取的主成分...
  • 户外环境监测中的无线信道非常复杂,受到多径衰落和噪声等多重因素的影响,严重降低了信号的接收质量。对衰落信道特性的深入研究,助于网络更好地接收信号,提高系统检测性能。详细分析了信道衰落的影响因子,研究...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 43,435
精华内容 17,374
关键字:

多重网络有什么影响