环境新增一台node节点。查看kubelet和kube-proxy 启动正常,把调度到上面正常运行,通过该节点去访问机器其他服务正常。说明机器之间的访问网络没有问题的。但是查看该节点的log报错,error from server:Get https://xxxxx
看报错是证书的错误。https的地址不给访问。
梳理下证书逻辑,证书master主节点签的ca证书,新加节点的认证下。这个就应该是节点的问题。首先需要判断就是nodename和ip的解析有没有问题。
问题很明显。node节点的kubelet使用的是new-k8s-node9 这个名字。但是master的hosts解析却是使用NEWK8SNode09这个name。导致master找不到new-k8s-node9 应该连接什么ip。连接失败。
处理方法 最小影响的改动。追加一个域名解析: ip new-k8s-node9 。
再去kubectl logs 正常了。
kali安装微信 下载地址:https://github.com/geeeeeeeeek/electronic-wechat/releases tar -zxvf linux-x64.tar.gz -C /opt vim /usr/share/applications/webchat.desktop [Desktop Entry] Encoding=UTF-8 Version=v2.0 Name=wechat GenericName=wechat Comment=wechat_web Exec=/opt/electronic-wechat-linux-x64/electronic-wechat Icon=/opt/electronic-wechat-linux-x64/wechat.png Terminal=false Type=Application Categories=Application;Utility;Network;InstantMessaging; StartupNotify=false 常见错误及解决: R: ./electronic-wechat: error while loading shared libraries: libgconf-2.so.4: cannot open shared object file: No such file or directory P: sudo apt install libgconf-2-4
文章目录 1.组件使用的细节1.2 子标签内使用组件1.2 非根组件的data必须是一个函数1.3 在Vue中操作DOM:使用ref属性 2.父子组件间传值2.1 父组件向子组件传值:通过属性传值2.2 子组件向父组件传值:通过事件this.$emit()2.3 组件参数校验2.4 非props特性2.5 给子组件绑定原生事件2.6 非父子组件之间的传值 3.插槽4.作用域插槽5.动态组件 1.组件使用的细节 1.2 子标签内使用组件 假设想在表格便签内使用一个组件作为行,直接使用组件是不行的,原因在于tbody内只能放tr便签,否则浏览器就会将tbody内的标签解析到外面,从而导致结构的错乱。
为了解决这个问题,我们可以使用is特性,将is特性设置为组件名称,这样既保证了tbody和tr之间的层级关系,也使用了组件。
1.2 非根组件的data必须是一个函数 一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝。
1.3 在Vue中操作DOM:使用ref属性 在Vue中,可以为DOM节点设置ref属性,从而可以通过实例名(实例引用this).$ref.属性名来访问DOM节点。
2.父子组件间传值 2.1 父组件向子组件传值:通过属性传值 Prop 是你可以在组件上注册的一些自定义 attribute。当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个 property。
<body> <div id="root"> <counter :count="count1"></counter> <counter :count="count2"></counter> </div> <script> var counter = { props:['count'], data:function(){ // 子组件不能直接修改父组件传过来的值,因此需要将附件传过来的值复制一份 return { number:this.count } }, template:'<div @click="handleClick">{{number}}</div>', methods:{ handleClick:function(){ this.number ++; } } } var vm = new Vue({ el:"
Tomcat 9 WEB 容器给我们提供了很多组件,比如:Servlet、JSP、Filter、Listener
JSP 使用特殊,WEB 容器内部的 web.xml 配置文件替我们已做好配置并找到解析引擎自动转化为 Servlet而 Servlet、Filter、Listener,在我们使用时必须遵循规则(继承或实现类、方法重写)还必须要配置工程中的 web.xml 配置文件 但是配置 web.xml 是很繁琐的,一个 Servlet 就至少需要配置 8 行信息。
为了优化配置,WEB框架(比如 Struts2、SpringMVC…)应运而生
框架出现之后,Web容器(Tomcat)也更新了相关的版本,WEB3.0以后支持注解(替代 web.xml 文件的配置)
WEB 注解 @WebServlet、@WebFilter、@WebListener,分别简化 Servlet、Filter、Listener 的配置
@WebServlet (1-1)配置 web.xml 文件
如果是手动配置 web.xml 文件,至少需要八行
<servlet> <servlet-name>test</servlet-name> <servlet-class>controller.TestController</servlet-class> </servlet> <servlet-mapping> <servlet-name>test</servlet-name> <url-pattern>/test</url-pattern> </servlet-mapping> (1-2)在控制层的 Servlet 类上配置注解:
//1. @WebServlet(urlPatterns = "/test") //2.如果只有一个请求关联,并且不携带配置信息 @WebServlet(value = "/test") //2.或者 @WebServlet("/test") //3.如果有多个请求关联到该 Servlet 类 @WebServlet(urlPatterns = {"/test","/test2"}) (2-1)配置 web.xml 文件
Servlet 类是由生命周期托管方式实现懒加载,可以在 web.
可以使用相机的enable方法实现相机的切换,Unity中enable是控制一个物体是否在屏幕上渲染或显示,而物体实际还是存在,只是相当于隐身,而物体本身的碰撞体还依然存在。 利用这个方法可以实现相机的切换效果,具体使用方法为: 1,在场景中创建两个相机
2,创建一个脚本,然后添加下面的代码
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class camera_change : MonoBehaviour { public Camera camera_one; public Camera camera_two; private void Start() { camera_one.enabled = true; camera_two.enabled = false; } private void Update() //通过点击不同的按键实现相机的切换 { if(Input.GetKeyDown(KeyCode.Alpha1)) { camera_one.enabled = false; camera_two.enabled = true; } if(Input.GetKeyDown(KeyCode.Alpha2)) { camera_one.enabled = true; camera_two.enabled = false; } } } 3,将代码添加到任一相机上面,然后把相机拖入即可(注意第一个是主相机,为默认相机)
.
找到IDEA的安装目录bin,修改这个文件
修改为:
-Xms128m
-Xmx1024m
-XX:MaxPermSize=250m
-XX:ReservedCodeCacheSize=150m
Tomcat 9 在讲述 Cookie 之前,有必要谈谈 Session
session 对象是服务器管理的(session对象存储在服务器端)session 对象是无状态的(每一个session对象与浏览器不存在直接的对应关系)session 需要一个标识来寻找 标识(JSESSIONID )标识是谁分配的?服务器标识长成什么样?是一个 String(唯一)标识存储在哪里?存储在浏览器端什么时候存进去的?服务器在第一次给浏览器响应的时候携带标识标识具体存储在哪里?浏览器端的 Cookie 请求与响应过程简述
先启动服务器(携带项目:有许多资源,比如 Servlet、JSP、Filter…)浏览器主动发送请求(第一次请求时没有 标识)服务器解析请求,根据请求找资源,然后响应信息给浏览器的时候携带有 标识浏览器解析响应信息,并将 标识 存储,然后每一次新请求都携带 标识(正确来说是全部 Cookie 信息,标识 存储在 Cookie 中) 服务器端操作 Cookie (1)服务器端创建 Cookie
Cookie cookie = new Cookie("key", "value"); response.addCookie(cookie); 服务器端响应信息时携带 Cookie 信息下一次浏览器发送请求的时候会携带 Cookie 信息 (2)服务器端获取 Cookie
Cookie[] = request.getCookies(); (3)Cookie 对象常用方法
cookie.getName(); //获取cookie的key cookie.getValue(); //获取cookie的value cookie.setMaxAge(int); //设置cookie的失效时间,单位:秒 cookie.getDomain(); //返回cookie的域名 cookie.getPath(); cookie.getVerson(); ... cookie.setMaxAge(int); 默认值是 -1,当前 Session 结束时 Cookie 消失 (4)清除 Cookie
背景 之前公司面临磁盘不足的问题,虽然通过增加磁盘来缓解了。但是clickhouse集群节点扩充是发展迟早要面临的问题,所以尝试思考解决方案。
ck不同于hadoop体系,hdfs当集群增减节点时可以通过balance命令去自动调节。但ck集群不能自动感知集群拓扑变化,也不能自动 balance 数据。当集群数据量较大,复制表和分布式表过多时、想做到表维度、或者集群之间的数据平衡会导致运维成本很高。
方案 这里提供三个解决思路
方式一、复制 当我们追求分布式表在新集群的平衡,数据量不大的情况,可以在新集群建立临时表B、然后将原始表A写入B中,再删除A表重新创建,之后将B表写入A中。
这种方式并不是数据量大、表数量过多的情形。
方式二、配置权重 当我们先用磁盘不够用的情况、通过配置权重指定大部分数据写入新的节点,随着时间流逝,原节点具有TTL的部分数据自动删除,数据会趋于均衡,最后调回原始权重。是一种比较可行的方案。只需要配置/etc/metrika.xml 中各个shard的权重即可。
<clickhouse_remote_servers> <ck_cluster> <shard> <weight>99</weight> <replica> <host>dc-sit-225</host> <port>9000</port> <user>default</user> <password></password> </replica> </shard> <shard> <weight>1</weight> <replica> <host>dc-sit-226</host> <port>9000</port> <user>default</user> <password></password> </replica> </shard> </ck_cluster> </clickhouse_remote_servers> 列如我这里设置dc-sit-225节点权重为99,dc-sit-226节点权重为1。需要注意的是权重必须大于0(经过测试,小于0时节点会启动失败,等于0时配置不会生效)。
方式三、直接扩容不迁移数据 我们可以给每个节点中的表设置TTL保留时间,随着时间的进行,已经保留时间长的数据会逐渐的被清除掉。到最后,节点的数据会均很分布
优点:横向扩容,配置简单不需要额外操作,只需将每个节点的配置文件更新即可缺点:要想达到数据均衡所需的时间较长,期间查询历史数据还是由原来节点合并完成,没有完全发挥分布式的优势 步骤 1、将配置文件进行拷贝到待安装节点 2、更改配置文件中的 metrika.xml 中关于 副本、分片的配置。例如: (1)添加分片、副本
<clickhouse_remote_servers> <report_shards_replicas> <shard> <weight>1</weight> <!-- false代表一次性写入所有副本,true表示写入其中一个副本,配合zk来进行数据复制 --> <internal_replication>true</internal_replication> <replica> <host>172.17.0.3</host> <port>9000</port> <user>default</user> <password>default</password> </replica> </shard> <shard> <weight>1</weight> <!-- false代表一次性写入所有副本,true表示写入其中一个副本,配合zk来进行数据复制 --> <internal_replication>true</internal_replication> <replica> <host>172.17.0.4</host> <port>9000</port> <user>default</user> <password>default</password> </replica> </shard> <shard> <weight>1</weight> <!
0x00 前言 shiro反序列化漏洞这个从 shiro 550 开始,在2016年就爆出来, 但是到现在在各种攻防演练中也起到了显著作用
这个漏洞一直都很好用,特别是一些红蓝对抗HW的下边界突破很好用
遂研究一下这个漏洞的成因和分析一下代码
0x01 Shiro 550 漏洞描述 Apache Shiro RememberMe 反序列化导致的命令执行漏洞
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理
编号:Shiro-550, CVE-2016-4437
版本:Apache Shiro (由于密钥泄露的问题, 部分高于1.2.4版本的Shiro也会受到影响)
0x02 环境搭建 基础环境 编辑器:IDEA 2020
java版本:jdk1.7.0_80
Server版本 : Tomcat 8.5.56
shiro版本:shiro-root-1.2.4
组件:commons-collections4
搭建过程 如果闲配置麻烦,也可以直接用我弄好的GitHub地址
https://github.com/godzeo/shiro_1.2.4_sample.git
正常搭建 直接下载:
https://codeload.github.com/apache/shiro/zip/shiro-root-1.2.4
下载好以后直接解压
然后偷偷的进入samples/web目录,直接修改pom文件,主要修改下面这些
... <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <!-- 这里需要将jstl设置为1.2 --> <version>1.2</version> <scope>runtime</scope> </dependency> ..... <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> <version>4.0</version> </dependency> <dependencies> 然后部署,我就直接使用IEDA 部署了
坑点: 如果有使用maven打包搭建的话,可能遇到
Failed to execute goal org.
修改如下:原来为font 改为了none 就好了
参考https://blog.csdn.net/weixin_33971205/article/details/85563116?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.channel_param
报错代码: \begin{figure} \centering \includegraphics[width=0.7\linewidth]{my_features} \caption{my_features} \label{fig:my_features} \end{figure} 报错信息:
解决方法:
在导言区引入 graphicx 包
\usepackage{graphicx}
1、catkin_create_pkg learning_tf roscpp rospy tf turtlesim
2、拷贝程序
3、配置cmakelist
add_executable(turtle_tf_broadcaster src/turtle_tf_broadcaster.cpp)
target_link_libraries(turtle_tf_broadcaster ${catkin_LIBRARIES})
add_executable(turtle_tf_listener src/turtle_tf_listener.cpp)
target_link_libraries(turtle_tf_listener ${catkin_LIBRARIES})
4、 rosrun learning_tf turtle_tf_broadcaster __name:=turtle1_tf_broadcaster /turtle1
rosrun learning_tf turtle_tf_broadcaster __name:=turtle2_tf_broadcaster /turtle2
rosrun learning_tf turtle_tf_listener
rosrun turtlesim turtle_teleop_key
1、搭配view 控制显示
<view wx:if="{{boolean==true}}"> <view class="bg_black"></view> </view> <view wx:elif="{{boolean==false}}"> <view class="bg_red"></view> </view> <view wx:else> <view class="bg_red"></view> </view> 2、搭配block 控制显示
<block wx:if="{{boolean==true}}"> <view class="bg_black"></view> </block> <block wx:elif="{{boolean==false}}"> <view class="bg_red"></view> </block> <block wx:else> <view class="bg_red"></view> </block> 小程序 wx:if多条件
<view wx:if="{{a}}">单个条件</view> <view wx:if="{{a || b || c}}">多个或条件</view> <view wx:if="{{a && b && c}}">多个且条件</view> wx:if else 的判断
<view wx:if="{{a > 5}}">6</view> <view wx:elif="{{a < 5}}">4</view> <view wx:else>5</view> 注意:wx:if不仅仅是view标签可以用,其他的标签里也可以用
谢谢认真观读本文的每一位小伙伴,衷心欢迎小伙伴给我指出文中的错误,也欢迎小伙伴与我交流学习。
欢迎爱学习的小伙伴加群一起进步:[点击链接加入群聊【编程之美】
参考书:《计算机体系结构量化研究方法》 作者:John L. Hennessy
一、 基本概念 先理解几个基本概念:
流水线:是一种将多条指令重叠执行的实现技术。一条指令的执行需要多个操作,流水线技术充分利用了这些操作之间的并行性。
流水级:不同步骤并行完成不同指令的不同部分。这些步骤中的每一步都称为流水级或流水段
吞吐量:由指令退出流水线的频率决定。
处理器周期:一条指令在流水线中下移一步所需要的时间。一个处理器周期通常为1个时钟周期。(又是为2个,但要少见的多)
流水线的作用:缩短每条指令的平均执行时间。降低了每条指令时钟周期数(CPI)
1.1 RISC精简指令集基础知识 以MIPS指令集为例,其提供了32个寄存器,通常由以下三类指令:
1)ALU指令:取两个寄存器或者一个寄存器与一个符号扩展立即数进行运算并存储到第三个寄存器中
2)载入和存储指令:这些指令获取一个寄存器源(基址寄存器)和一个立即数字段(16位,偏移量),作为操作数计算有效地址用作存储器地址。
3)分支与跳转:条件转移。通常由两种方式指定分支条件:其一,采用一组条件位(条件码);其二通过两个寄存器之间、寄存器与0之间的对比来设定。MIPS采用后者。
1.2 RISC指令集的简单实现 RISC子集中的每条指令都可以在最多5个时钟周期内实现,如下
1)指令提取周期(IF)
将程序计数器PC发送存储器,从存储器提取当前指令。向程序计数器加4(因为每条指令的长度为4个字节),将程序计数器更新到下一个连续计数器。
此周期操作:a.读取PC,b.更新PC
2)指令译码/寄存器提取周期(ID)
此周期操作:a.将指令译码,b.读取相应寄存器,c.判断是否为分支
这几点在下面都会有详细解释
3)执行/有效地址周期(EX)
此周期操作(其中之一):a.计算有效地址(基址寄存器+偏移量)b.在寄存器与寄存器之间进行运算,c.在寄存器与立即数之间进行运算
4)存储器访问(MEM)
操作(执行其中之一):a.将寄存器写入存储器(写入)b.将存储器的内容读取到寄存器中(载入)
5)写回周期(WB)
将程序结果(来自ALU运算或载入指令)写入寄存器堆
1.3 RISC经典五级流水线 下面这种情况没有采用流水线结构,是简单的顺序执行,在执行完一个指令的五个周期后,才能执行下一条指令,如图所示:
这种方法存在着一个问题,比如参与运算的ALU单元平均在五个周期种只被使用了一次,硬件资源造成的很大程度上的浪费。于是我们采用了流水线结构,如下图所示:
流水线结构在每个时钟周期都会启动一条指令,其性能会达到非流水化处理器的5倍(理想情况下)。比如ALU单元,在每一个时钟周期都被用到了,相比于非流水化大大提高了硬件的利用率。IF ID EX MEM WB值得是指令集的5个时钟周期。
可能很难相信流水线是这么地简单,实际上,它的确不是这么简单。
下图是流水化形式的RISC数据路径的简化版本。如图:
流水线可以看作一系列随时间移动的数据路径。上图给出了数据路径不同部分之间的重叠,时钟周期数5(CC5)表示稳定状态,此状态下,一个时钟周期内,运行着五条指令的不同部分。寄存器左侧虚线表示写入,右侧虚线表示读取。IM表示指令存储器,DM表示数据存储器,CC表示时钟周期。
我们也可以看出来,主要功能单元(如ALU)是在不同周期使用的,因此多条指令的重叠执行不会引发多少冲突(实际上,不可避免会存在些许冲突即冒险问题,在后面会提到及其解决方案)。以下三点可以看出:
1)采用分离的指令存储器及数据存储器,并采用分离缓存的方式避免二者的访问冲突。不过相应开销是:存储器系统必须提供5倍的带宽。
2)在两个阶段都使用了寄存器堆(MIPS中由32个寄存器):一是在ID中进行读取,一是在WB中进行写入。每次时钟周期会执行两次读取和一次写入。我们在时钟的前半周期写寄存器,后半周期读寄存器
3)为了每个时钟周期都会启动一条新指令,必须在IF阶段增加一加法器,一边为下一条指令做准备。此外,我们会在ID期间计算潜在的分支目标。
你会发现流水线最重要的就是:
1. 确保流水线中的指令不会试图在相同时间使用硬件资源。
2. 不同流水级中的指令不会互相干扰
因此在连续流水级中引入了流水线寄存器,如图所示
在流水化处理器中,如果要将中间结果从一级传送到另外一级,而源寄存器与目标地址可能并非直接相邻,这时候就是流水线寄存器发挥作用的时候了。例如:要在存储指令中存储的寄存器值是在ID期间进行读取的,但要等到MEM才会真正用到;他在MEM级中通过两个流水线寄存器传送到数据寄存器。于此类似,ALU指令的结果是在EX期间计算的,但要等到WB才会实际存储;
所以有必要对每个寄存器进行命名,称为:IF/ID,ID/EX,EX/MEM,MEM/WB。
流水化作用:提高了CPU指令吞吐量(单位时间内完成的指令数)。但不会缩短单条指令的执行时间。实际上它还会产生额外的开销
流水线开销:流水线寄存器延迟和时钟偏差。
二、 流水化冒险 上面介绍的流水线,我们假定指令之间都没有相互依赖关系。可实际上,流水线中的指令可能是存在依赖关系的。
若产生了依赖关系,再按照之前无停顿的顺序执行,就会产生冒险问题。有以下三种冒险:
1)结构冒险:比如两个指令都要访问存储器端口,可存储器只有一个写端口,这咋办?这时候就产生了资源冲突即结构冒险。
2)数据冒险:实际上,指令是存在一些先后顺序的,如果一些指令取决于先前的指令的结果,就可能导致数据冒险。
3)控制冒险:比如C语言中的if else语句,分支指令及其他改变程序计数器的指令实现流水化时可能会导致控制冒险。
这三种冒险总结如下表所示:
2.1 结构冒险 出现原因:
题目:计算用户输入的日期离1900年1月1日相距多少天 解析: 用户输入日期,所以需要使用Scanner获取用户键盘输入数据计算输入日期距离xxxx多少天,所以就是获取相差年份的天数+相差月份天数+相差天数 注意: 获取年份相差天数时要注意闰年平年,闰年366天,而平年是365天;获取年份相差天数时,要注意不需要获取输入的年份当年的天数,因为输入的年份的天数是由输入的月份和天数决定的(极大可能不满365天)在获取月份时,如果是一月,是不能直接加31天的(1月还没过完,这里可以直接跳过加输入年的月份,直接加日数即可)在获取月份时,如果是1月和2月,注意2月的天数是由用户输入的决定,还不需要计算平年闰年;当用户输入的月份是3月及更后(此时二月肯定已经过完了,这时候计算输入的年份是否是闰年来决定总天数是否需要+1)在计算完年份月份的天数时,需要加的日数需要减一(题目是相加多少天) 代码实现: public class Demo { public static void main(String[] args) { Scanner input = new Scanner(System.in); System.out.print("请输入年份:"); int year = input.nextInt(); System.out.print("请输入月份:"); int month = input.nextInt(); int arr[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30};//这里列出(平年)1到11月的天数,就算用户输入的是12月,需要加的天数也是由输入日数决定的 System.out.print("请输入日数:"); int day = input.nextInt(); int sum = 0;// 声明变量存放相距的天数 for (int i = 1900; i < year; i++) {//此处不能等于年份year if ((i % 4 == 0 && i % 100 !
chrome浏览器每次请求都会产生一个新的session的问题 vue 相关问题 每次请求session id 不一样 验证码从后台校验登录 验证码是从 session 所以导致登录不了 我用的是vue 跨域请求的 分析了下原因是
之前 我这个上面 的写的是127.0.0.1 改成localhost 就可以了
$.ajax() data{} 传参三种常见写法及ajax()方法参数详解 作者:admin 时间:2019-8-30 9:16:12 浏览:6369
$.ajax() data{} 异步请求的写法有很多,这里介绍简单的常见的三种写法,主要是介绍其中的data{}传参写法。
第一种写法:拼接URL function getFormInfo(){ var name='wen'; var user='chen'; $.ajax({ url: "/login/authenticate?name="+name+"&user="+user, type: "POST", data:{}, dataType: "json", success: function(data){ }, error:function(err){ console.log(err.statusText); console.log('异常'); } });} 第二种写法:带json数据 function getFormInfo(){ $.ajax({ url: "http://192.168.10.32:6833/login/authenticate", type: "POST", data:{ name:'chem', user:'wen' }, cache:false, dataType: "json", success: function(data){ }, error:function(err){ } });} 第三种写法:拼接data function getFormInfo(){ var name='chen'; var user='wen'; $.ajax({ url: "http://192.168.10.32:6833/login/authenticate", type: "POST", data:'name='+name+'&user='+user, cache:false, dataType: "
webpack "regeneratorRuntime is not defined"报错 原因:这是因为在打包的文件中使用了高级的es6语法,但是webpack并没有帮我们去进行校验,
解决办法:
安装对应的安装依赖和开发依赖
npm install @babel/plugin-transform-runtime -D npm install @babel/runtime -S 并且在对应的webpack配置中添加插件
// webpack.config.js { entry: '', output: {}, module: { rules: [{ test: /\.js$/, use: { // 使用到的loader loader: 'babel-loader', options: { presets: [], plugins: [ '@babel/plugin-transform-runtime' ] } } }] } } 官方文档 @babel/plugin-transform-runtime
声明:文章仅作知识整理、分享,如有侵权请联系作者删除博文,谢谢!
Batch Normalization视神经网络的经典结构,本文对BN的引入,训练、推理过程及BN的特性,进行整理。
1、数据预算处理(Data Preprocessing) 为什么输入数据需要归一化(Normalized Data)
归一化后有什么好处呢?原因在于:
1)神经网络学习过程本质就是为了学习数据分布,一旦训练数据与测试数据的分布不同,那么网络的泛化能力也大大降低;
2)另外一方面,一旦每批训练数据的分布各不相同(batch 梯度下降),那么网络就要在每次迭代都去学习适应不同的分布,这样将会大大降低网络的训练速度。
这也正是为什么我们需要对数据都要做一个归一化预处理的原因。对于深度网络的训练是一个复杂的过程,只要网络的前面几层发生微小的改变,那么后面几层就会被累积放大下去。一旦网络某一层的输入数据的分布发生改变,那么这一层网络就需要去适应学习这个新的数据分布,所以如果训练过程中,训练数据的分布一直在发生变化,那么将会影响网络的训练速度。
2、批量归一化(BN: Batch Normalization) 2.1、BN训练 1)随机梯度下降法(SGD)对于训练深度网络简单高效,但是它有个毛病,就是需要我们人为的去选择参数,比如学习率、参数初始化、权重衰减系数、Drop out比例等。这些参数的选择对训练结果至关重要,以至于我们很多时间都浪费在这些的调参上。那么使用BN(详见论文《Batch Normalization_ Accelerating Deep Network Training by Reducing Internal Covariate Shift》)之后,你可以不需要那么刻意的慢慢调整参数。
2)神经网络一旦训练起来,那么参数就要发生更新,除了输入层的数据外(因为输入层数据,我们已经人为的为每个样本归一化),后面网络每一层的输入数据分布是一直在发生变化的,因为在训练的时候,前面层训练参数的更新将导致后面层输入数据分布的变化。以网络第二层为例:网络的第二层输入,是由第一层的参数和input计算得到的,而第一层的参数在整个训练过程中一直在变化,因此必然会引起后面每一层输入数据分布的改变。我们把网络中间层在训练过程中,数据分布的改变称之为:“Internal Covariate Shift”。Paper所提出的算法,就是要解决在训练过程中,中间层数据分布发生改变的情况,于是就有了Batch Normalization,这个牛逼算法的诞生。
3)BN的地位:与激活函数层、卷积层、全连接层、池化层一样,BN(Batch Normalization)也属于网络的一层。
4)BN的本质原理:在网络的每一层输入的时候,又插入了一个归一化层,也就是先做一个归一化处理(归一化至:均值0、方差为1),然后再进入网络的下一层。不过文献归一化层,可不像我们想象的那么简单,它是一个可学习、有参数(γ、β)的网络层。
5)归一化公式:
6)如果是仅仅使用上面的归一化公式,对网络某一层A的输出数据做归一化,然后送入网络下一层B,这样是会影响到本层网络A所学习到的特征的。比如我网络中间某一层学习到特征数据本身就分布在S型激活函数的两侧,你强制把它给我归一化处理、标准差也限制在了1,把数据变换成分布于s函数的中间部分,这样就相当于我这一层网络所学习到的特征分布被你搞坏了,这可怎么办?于是文献使出了一招惊天地泣鬼神的招式:变换重构,引入了可学习参数γ、β,这就是算法关键之处:
上面的公式表明,通过学习到的重构参数γ、β,是可以恢复出原始的某一层所学到的特征的。
7)引入了这个可学习重构参数γ、β,让我们的网络可以学习恢复出原始网络所要学习的特征分布。最后Batch Normalization网络层的前向传导过程公式就是:
8)BN层是对于每个神经元做归一化处理,甚至只需要对某一个神经元进行归一化,而不是对一整层网络的神经元进行归一化。既然BN是对单个神经元的运算,那么在CNN中卷积层上要怎么搞?假如某一层卷积层有6个特征图,每个特征图的大小是100*100,这样就相当于这一层网络有6*100*100个神经元,如果采用BN,就会有6*100*100个参数γ、β,这样岂不是太恐怖了。因此卷积层上的BN使用,其实也是使用了类似权值共享的策略,把一整张特征图当做一个神经元进行处理。
9)卷积神经网络经过卷积后得到的是一系列的特征图,如果min-batch sizes为m,那么网络某一层输入数据可以表示为四维矩阵(m,f,w,h),m为min-batch sizes,f为特征图个数,w、h分别为特征图的宽高。在CNN中我们可以把每个特征图看成是一个特征处理(一个神经元),因此在使用Batch Normalization,mini-batch size 的大小就是:m*w*h,于是对于每个特征图都只有一对可学习参数:γ、β。说白了吧,这就是相当于求取所有样本所对应的一个特征图的所有神经元的平均值、方差,然后对这个特征图神经元做归一化。
10) 在使用BN前,减小学习率、小心的权重初始化的目的是:使其输出的数据分布不要发生太大的变化。
11) BN的作用:
1、改善流经网络的梯度;
2、允许更大的学习率,大幅提高训练速度;
你可以选择比较大的初始学习率,让你的训练速度飙涨。以前还需要慢慢调整学习率,甚至在网络训练到一半的时候,还需要想着学习率进一步调小的比例选择多少比较合适,现在我们可以采用初始很大的学习率,然后学习率的衰减速度也很大,因为这个算法收敛很快。当然这个算法即使你选择了较小的学习率,也比以前的收敛速度快,因为它具有快速训练收敛的特性;
3、减少对初始化的强烈依赖;
4、改善正则化策略:作为正则化的一种形式,轻微减少了对dropout的需求;
你再也不用去理会过拟合中drop out、L2正则项参数的选择问题,采用BN算法后,你可以移除这两项了参数,或者可以选择更小的L2正则约束参数了,因为BN具有提高网络泛化能力的特性;
5、再也不需要使用使用局部响应归一化层了(局部响应归一化是Alexnet网络用到的方法,搞视觉的估计比较熟悉),因为BN本身就是一个归一化网络层;
6、可以把训练数据彻底打乱(防止每批训练的时候,某一个样本都经常被挑选到,文献说这个可以提高1%的精度)。
注:以上为学习过程,在测试时,均值和方差(mean/std)不基于小批量进行计算, 可取训练过程中的激活值的均值。
2.2、BN测试 1)实际测试时,我们依然使用下面的公式:
这里的均值和方差已经不是针对某一个Batch了,而是针对整个数据集而言。因此,在训练过程中除了正常的前向传播和反向求导之外,我们还要记录每一个Batch的均值和方差,以便训练完成之后按照下式计算整体的均值和方差:
上面简单理解就是:对于均值来说直接计算所有batch u值的平均值;然后对于标准偏差采用每个batch σB的无偏估计。最后测试阶段,BN的使用公式就是:
2)BN可以应用于一个神经网络的任何神经元上。文献主要是把BN变换,置于网络激活函数层的前面。在没有采用BN的时候,激活函数层是这样的: z=g(Wu+b)