-
全面理解Java内存模型
2016-09-21 18:39:21Java内存模型即Java Memory Model,简称JMM。JMM定义了Java 虚拟机(JVM)在计算机内存(RAM)中的工作方式。JVM是整个计算机虚拟模型,所以JMM是隶属于JVM的。如果我们要想深入了解Java并发编程,就要先理解好Java内存...Java内存模型即Java Memory Model,简称JMM。JMM定义了Java 虚拟机(JVM)在计算机内存(RAM)中的工作方式。JVM是整个计算机虚拟模型,所以JMM是隶属于JVM的。
如果我们要想深入了解Java并发编程,就要先理解好Java内存模型。Java内存模型定义了多线程之间共享变量的可见性以及如何在需要的时候对共享变量进行同步。原始的Java内存模型效率并不是很理想,因此Java1.5版本对其进行了重构,现在的Java8仍沿用了Java1.5的版本。
关于并发编程
在并发编程领域,有两个关键问题:线程之间的通信和同步。
线程之间的通信
线程的通信是指线程之间以何种机制来交换信息。在命令式编程中,线程之间的通信机制有两种共享内存和消息传递。
在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信,典型的共享内存通信方式就是通过共享对象进行通信。
在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信,在java中典型的消息传递方式就是wait()和notify()。
关于Java线程之间的通信,可以参考线程之间的通信(thread signal)。
线程之间的同步
同步是指程序用于控制不同线程之间操作发生相对顺序的机制。
在共享内存并发模型里,同步是显式进行的。程序员必须显式指定某个方法或某段代码需要在线程之间互斥执行。
在消息传递的并发模型里,由于消息的发送必须在消息的接收之前,因此同步是隐式进行的。
Java的并发采用的是共享内存模型
Java线程之间的通信总是隐式进行,整个通信过程对程序员完全透明。如果编写多线程程序的Java程序员不理解隐式进行的线程之间通信的工作机制,很可能会遇到各种奇怪的内存可见性问题。
Java内存模型
上面讲到了Java线程之间的通信采用的是过共享内存模型,这里提到的共享内存模型指的就是Java内存模型(简称JMM),JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。
从上图来看,线程A与线程B之间如要通信的话,必须要经历下面2个步骤:
1. 首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。 2. 然后,线程B到主内存中去读取线程A之前已更新过的共享变量。
下面通过示意图来说明这两个步骤:
如上图所示,本地内存A和B有主内存中共享变量x的副本。假设初始时,这三个内存中的x值都为0。线程A在执行时,把更新后的x值(假设值为1)临时存放在自己的本地内存A中。当线程A和线程B需要通信时,线程A首先会把自己本地内存中修改后的x值刷新到主内存中,此时主内存中的x值变为了1。随后,线程B到主内存中去读取线程A更新后的x值,此时线程B的本地内存的x值也变为了1。
从整体来看,这两个步骤实质上是线程A在向线程B发送消息,而且这个通信过程必须要经过主内存。JMM通过控制主内存与每个线程的本地内存之间的交互,来为java程序员提供内存可见性保证。
上面也说到了,Java内存模型只是一个抽象概念,那么它在Java中具体是怎么工作的呢?为了更好的理解上Java内存模型工作方式,下面就JVM对Java内存模型的实现、硬件内存模型及它们之间的桥接做详细介绍。
JVM对Java内存模型的实现
在JVM内部,Java内存模型把内存分成了两部分:线程栈区和堆区,下图展示了Java内存模型在JVM中的逻辑视图:
JVM中运行的每个线程都拥有自己的线程栈,线程栈包含了当前线程执行的方法调用相关信息,我们也把它称作调用栈。随着代码的不断执行,调用栈会不断变化。线程栈还包含了当前方法的所有本地变量信息。一个线程只能读取自己的线程栈,也就是说,线程中的本地变量对其它线程是不可见的。即使两个线程执行的是同一段代码,它们也会各自在自己的线程栈中创建本地变量,因此,每个线程中的本地变量都会有自己的版本。
所有原始类型(boolean,byte,short,char,int,long,float,double)的本地变量都直接保存在线程栈当中,对于它们的值各个线程之间都是独立的。对于原始类型的本地变量,一个线程可以传递一个副本给另一个线程,当它们之间是无法共享的。
堆区包含了Java应用创建的所有对象信息,不管对象是哪个线程创建的,其中的对象包括原始类型的封装类(如Byte、Integer、Long等等)。不管对象是属于一个成员变量还是方法中的本地变量,它都会被存储在堆区。
下图展示了调用栈和本地变量都存储在栈区,对象都存储在堆区:
一个本地变量如果是原始类型,那么它会被完全存储到栈区。
一个本地变量也有可能是一个对象的引用,这种情况下,这个本地引用会被存储到栈中,但是对象本身仍然存储在堆区。对于一个对象的成员方法,这些方法中包含本地变量,仍需要存储在栈区,即使它们所属的对象在堆区。
对于一个对象的成员变量,不管它是原始类型还是包装类型,都会被存储到堆区。Static类型的变量以及类本身相关信息都会随着类本身存储在堆区。
堆中的对象可以被多线程共享。如果一个线程获得一个对象的应用,它便可访问这个对象的成员变量。如果两个线程同时调用了同一个对象的同一个方法,那么这两个线程便可同时访问这个对象的成员变量,但是对于本地变量,每个线程都会拷贝一份到自己的线程栈中。
下图展示了上面描述的过程:
硬件内存架构
不管是什么内存模型,最终还是运行在计算机硬件上的,所以我们有必要了解计算机硬件内存架构,下图就简单描述了当代计算机硬件内存架构:
现代计算机一般都有2个以上CPU,而且每个CPU还有可能包含多个核心。因此,如果我们的应用是多线程的话,这些线程可能会在各个CPU核心中并行运行。
在CPU内部有一组CPU寄存器,也就是CPU的储存器。CPU操作寄存器的速度要比操作计算机主存快的多。在主存和CPU寄存器之间还存在一个CPU缓存,CPU操作CPU缓存的速度快于主存但慢于CPU寄存器。某些CPU可能有多个缓存层(一级缓存和二级缓存)。计算机的主存也称作RAM,所有的CPU都能够访问主存,而且主存比上面提到的缓存和寄存器大很多。
当一个CPU需要访问主存时,会先读取一部分主存数据到CPU缓存,进而在读取CPU缓存到寄存器。当CPU需要写数据到主存时,同样会先flush寄存器到CPU缓存,然后再在某些节点把缓存数据flush到主存。
Java内存模型和硬件架构之间的桥接
正如上面讲到的,Java内存模型和硬件内存架构并不一致。硬件内存架构中并没有区分栈和堆,从硬件上看,不管是栈还是堆,大部分数据都会存到主存中,当然一部分栈和堆的数据也有可能会存到CPU寄存器中,如下图所示,Java内存模型和计算机硬件内存架构是一个交叉关系:
当对象和变量存储到计算机的各个内存区域时,必然会面临一些问题,其中最主要的两个问题是:1. 共享对象对各个线程的可见性 2. 共享对象的竞争现象
共享对象的可见性
当多个线程同时操作同一个共享对象时,如果没有合理的使用volatile和synchronization关键字,一个线程对共享对象的更新有可能导致其它线程不可见。
想象一下我们的共享对象存储在主存,一个CPU中的线程读取主存数据到CPU缓存,然后对共享对象做了更改,但CPU缓存中的更改后的对象还没有flush到主存,此时线程对共享对象的更改对其它CPU中的线程是不可见的。最终就是每个线程最终都会拷贝共享对象,而且拷贝的对象位于不同的CPU缓存中。
下图展示了上面描述的过程。左边CPU中运行的线程从主存中拷贝共享对象obj到它的CPU缓存,把对象obj的count变量改为2。但这个变更对运行在右边CPU中的线程不可见,因为这个更改还没有flush到主存中:
要解决共享对象可见性这个问题,我们可以使用java volatile关键字。 Java’s volatile keyword. volatile 关键字可以保证变量会直接从主存读取,而对变量的更新也会直接写到主存。volatile原理是基于CPU内存屏障指令实现的,后面会讲到。竞争现象
如果多个线程共享一个对象,如果它们同时修改这个共享对象,这就产生了竞争现象。
如下图所示,线程A和线程B共享一个对象obj。假设线程A从主存读取Obj.count变量到自己的CPU缓存,同时,线程B也读取了Obj.count变量到它的CPU缓存,并且这两个线程都对Obj.count做了加1操作。此时,Obj.count加1操作被执行了两次,不过都在不同的CPU缓存中。
如果这两个加1操作是串行执行的,那么Obj.count变量便会在原始值上加2,最终主存中的Obj.count的值会是3。然而下图中两个加1操作是并行的,不管是线程A还是线程B先flush计算结果到主存,最终主存中的Obj.count只会增加1次变成2,尽管一共有两次加1操作。
要解决上面的问题我们可以使用java synchronized代码块。synchronized代码块可以保证同一个时刻只能有一个线程进入代码竞争区,synchronized代码块也能保证代码块中所有变量都将会从主存中读,当线程退出代码块时,对所有变量的更新将会flush到主存,不管这些变量是不是volatile类型的。
volatile和 synchronized区别
支撑Java内存模型的基础原理
指令重排序
在执行程序时,为了提高性能,编译器和处理器会对指令做重排序。但是,JMM确保在不同的编译器和不同的处理器平台之上,通过插入特定类型的
Memory Barrier
来禁止特定类型的编译器重排序和处理器重排序,为上层提供一致的内存可见性保证。- 编译器优化重排序:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
- 指令级并行的重排序:如果不存l在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
- 内存系统的重排序:处理器使用缓存和读写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
数据依赖性
如果两个操作访问同一个变量,其中一个为写操作,此时这两个操作之间存在数据依赖性。
编译器和处理器不会改变存在数据依赖性关系的两个操作的执行顺序,即不会重排序。as-if-serial
不管怎么重排序,单线程下的执行结果不能被改变,编译器、runtime和处理器都必须遵守as-if-serial语义。
内存屏障(Memory Barrier )
上面讲到了,通过内存屏障可以禁止特定类型处理器的重排序,从而让程序按我们预想的流程去执行。内存屏障,又称内存栅栏,是一个CPU指令,基本上它是一条这样的指令:
- 保证特定操作的执行顺序。
- 影响某些数据(或则是某条指令的执行结果)的内存可见性。
编译器和CPU能够重排序指令,保证最终相同的结果,尝试优化性能。插入一条Memory Barrier会告诉编译器和CPU:不管什么指令都不能和这条Memory Barrier指令重排序。
Memory Barrier所做的另外一件事是强制刷出各种CPU cache,如一个
Write-Barrier
(写入屏障)将刷出所有在Barrier之前写入 cache 的数据,因此,任何CPU上的线程都能读取到这些数据的最新版本。这和java有什么关系?上面java内存模型中讲到的volatile是基于Memory Barrier实现的。
如果一个变量是
volatile
修饰的,JMM会在写入这个字段之后插进一个Write-Barrier
指令,并在读这个字段之前插入一个Read-Barrier
指令。这意味着,如果写入一个volatile
变量,就可以保证:- 一个线程写入变量a后,任何线程访问该变量都会拿到最新值。
- 在写入变量a之前的写入操作,其更新的数据对于其他线程也是可见的。因为Memory Barrier会刷出cache中的所有先前的写入。
happens-before
从jdk5开始,java使用新的JSR-133内存模型,基于happens-before的概念来阐述操作之间的内存可见性。
在JMM中,如果一个操作的执行结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系,这个的两个操作既可以在同一个线程,也可以在不同的两个线程中。
与程序员密切相关的happens-before规则如下:
- 程序顺序规则:一个线程中的每个操作,happens-before于该线程中任意的后续操作。
- 监视器锁规则:对一个锁的解锁操作,happens-before于随后对这个锁的加锁操作。
- volatile域规则:对一个volatile域的写操作,happens-before于任意线程后续对这个volatile域的读。
- 传递性规则:如果 A happens-before B,且 B happens-before C,那么A happens-before C。
注意:两个操作之间具有happens-before关系,并不意味前一个操作必须要在后一个操作之前执行!仅仅要求前一个操作的执行结果,对于后一个操作是可见的,且前一个操作按顺序排在后一个操作之前。
参考文档 :
1. http://www.infoq.com/cn/articles/java-memory-model-1
2. http://www.jianshu.com/p/d3fda02d4cae -
Tensorflow加载预训练模型和保存模型
2017-11-10 19:07:16使用tensorflow过程中,训练结束后我们需要用到模型文件。有时候,我们可能也需要用到别人训练好的模型,并在这个基础上再次训练。这时候我们需要掌握如何操作这些模型数据。看完本文,相信你一定会有收获!最近看到一个巨牛的人工智能教程,分享一下给大家。教程不仅是零基础,通俗易懂,而且非常风趣幽默,像看小说一样!觉得太牛了,所以分享给大家。平时碎片时间可以当小说看,【点这里可以去膜拜一下大神的“小说”】。
使用tensorflow过程中,训练结束后我们需要用到模型文件。有时候,我们可能也需要用到别人训练好的模型,并在这个基础上再次训练。这时候我们需要掌握如何操作这些模型数据。看完本文,相信你一定会有收获!
1 Tensorflow模型文件
我们在checkpoint_dir目录下保存的文件结构如下:
|--checkpoint_dir | |--checkpoint | |--MyModel.meta | |--MyModel.data-00000-of-00001 | |--MyModel.index
1.1 meta文件
MyModel.meta文件保存的是图结构,meta文件是pb(protocol buffer)格式文件,包含变量、op、集合等。
1.2 ckpt文件
ckpt文件是二进制文件,保存了所有的weights、biases、gradients等变量。在tensorflow 0.11之前,保存在**.ckpt**文件中。0.11后,通过两个文件保存,如:
MyModel.data-00000-of-00001 MyModel.index
1.3 checkpoint文件
我们还可以看,checkpoint_dir目录下还有checkpoint文件,该文件是个文本文件,里面记录了保存的最新的checkpoint文件以及其它checkpoint文件列表。在inference时,可以通过修改这个文件,指定使用哪个model
2 保存Tensorflow模型
tensorflow 提供了
tf.train.Saver
类来保存模型,值得注意的是,在tensorflow中,变量是存在于Session环境中,也就是说,只有在Session环境下才会存有变量值,因此,保存模型时需要传入session:saver = tf.train.Saver() saver.save(sess,"./checkpoint_dir/MyModel")
看一个简单例子:
import tensorflow as tf w1 = tf.Variable(tf.random_normal(shape=[2]), name='w1') w2 = tf.Variable(tf.random_normal(shape=[5]), name='w2') saver = tf.train.Saver() sess = tf.Session() sess.run(tf.global_variables_initializer()) saver.save(sess, './checkpoint_dir/MyModel')
执行后,在checkpoint_dir目录下创建模型文件如下:
checkpoint MyModel.data-00000-of-00001 MyModel.index MyModel.meta
另外,如果想要在1000次迭代后,再保存模型,只需设置
global_step
参数即可:saver.save(sess, './checkpoint_dir/MyModel',global_step=1000)
保存的模型文件名称会在后面加
-1000
,如下:checkpoint MyModel-1000.data-00000-of-00001 MyModel-1000.index MyModel-1000.meta
在实际训练中,我们可能会在每1000次迭代中保存一次模型数据,但是由于图是不变的,没必要每次都去保存,可以通过如下方式指定不保存图:
saver.save(sess, './checkpoint_dir/MyModel',global_step=step,write_meta_graph=False)
另一种比较实用的是,如果你希望每2小时保存一次模型,并且只保存最近的5个模型文件:
tf.train.Saver(max_to_keep=5, keep_checkpoint_every_n_hours=2)
注意:tensorflow默认只会保存最近的5个模型文件,如果你希望保存更多,可以通过
max_to_keep
来指定如果我们不对
tf.train.Saver
指定任何参数,默认会保存所有变量。如果你不想保存所有变量,而只保存一部分变量,可以通过指定variables/collections。在创建tf.train.Saver
实例时,通过将需要保存的变量构造list或者dictionary,传入到Saver中:import tensorflow as tf w1 = tf.Variable(tf.random_normal(shape=[2]), name='w1') w2 = tf.Variable(tf.random_normal(shape=[5]), name='w2') saver = tf.train.Saver([w1,w2]) sess = tf.Session() sess.run(tf.global_variables_initializer()) saver.save(sess, './checkpoint_dir/MyModel',global_step=1000)
3 导入训练好的模型
在第1小节中我们介绍过,tensorflow将图和变量数据分开保存为不同的文件。因此,在导入模型时,也要分为2步:构造网络图和加载参数
3.1 构造网络图
一个比较笨的方法是,手敲代码,实现跟模型一模一样的图结构。其实,我们既然已经保存了图,那就没必要在去手写一次图结构代码。
saver=tf.train.import_meta_graph('./checkpoint_dir/MyModel-1000.meta')
上面一行代码,就把图加载进来了
3.2 加载参数
仅仅有图并没有用,更重要的是,我们需要前面训练好的模型参数(即weights、biases等),本文第2节提到过,变量值需要依赖于Session,因此在加载参数时,先要构造好Session:
import tensorflow as tf with tf.Session() as sess: new_saver = tf.train.import_meta_graph('./checkpoint_dir/MyModel-1000.meta') new_saver.restore(sess, tf.train.latest_checkpoint('./checkpoint_dir'))
此时,W1和W2加载进了图,并且可以被访问:
import tensorflow as tf with tf.Session() as sess: saver = tf.train.import_meta_graph('./checkpoint_dir/MyModel-1000.meta') saver.restore(sess,tf.train.latest_checkpoint('./checkpoint_dir')) print(sess.run('w1:0')) ##Model has been restored. Above statement will print the saved value
执行后,打印如下:
[ 0.51480412 -0.56989086]
4 使用恢复的模型
前面我们理解了如何保存和恢复模型,很多时候,我们希望使用一些已经训练好的模型,如prediction、fine-tuning以及进一步训练等。这时候,我们可能需要获取训练好的模型中的一些中间结果值,可以通过
graph.get_tensor_by_name('w1:0')
来获取,注意w1:0
是tensor的name。假设我们有一个简单的网络模型,代码如下:
import tensorflow as tf w1 = tf.placeholder("float", name="w1") w2 = tf.placeholder("float", name="w2") b1= tf.Variable(2.0,name="bias") #定义一个op,用于后面恢复 w3 = tf.add(w1,w2) w4 = tf.multiply(w3,b1,name="op_to_restore") sess = tf.Session() sess.run(tf.global_variables_initializer()) #创建一个Saver对象,用于保存所有变量 saver = tf.train.Saver() #通过传入数据,执行op print(sess.run(w4,feed_dict ={w1:4,w2:8})) #打印 24.0 ==>(w1+w2)*b1 #现在保存模型 saver.save(sess, './checkpoint_dir/MyModel',global_step=1000)
接下来我们使用
graph.get_tensor_by_name()
方法来操纵这个保存的模型。import tensorflow as tf sess=tf.Session() #先加载图和参数变量 saver = tf.train.import_meta_graph('./checkpoint_dir/MyModel-1000.meta') saver.restore(sess, tf.train.latest_checkpoint('./checkpoint_dir')) # 访问placeholders变量,并且创建feed-dict来作为placeholders的新值 graph = tf.get_default_graph() w1 = graph.get_tensor_by_name("w1:0") w2 = graph.get_tensor_by_name("w2:0") feed_dict ={w1:13.0,w2:17.0} #接下来,访问你想要执行的op op_to_restore = graph.get_tensor_by_name("op_to_restore:0") print(sess.run(op_to_restore,feed_dict)) #打印结果为60.0==>(13+17)*2
注意:保存模型时,只会保存变量的值,placeholder里面的值不会被保存
如果你不仅仅是用训练好的模型,还要加入一些op,或者说加入一些layers并训练新的模型,可以通过一个简单例子来看如何操作:
import tensorflow as tf sess = tf.Session() # 先加载图和变量 saver = tf.train.import_meta_graph('my_test_model-1000.meta') saver.restore(sess, tf.train.latest_checkpoint('./')) # 访问placeholders变量,并且创建feed-dict来作为placeholders的新值 graph = tf.get_default_graph() w1 = graph.get_tensor_by_name("w1:0") w2 = graph.get_tensor_by_name("w2:0") feed_dict = {w1: 13.0, w2: 17.0} #接下来,访问你想要执行的op op_to_restore = graph.get_tensor_by_name("op_to_restore:0") # 在当前图中能够加入op add_on_op = tf.multiply(op_to_restore, 2) print (sess.run(add_on_op, feed_dict)) # 打印120.0==>(13+17)*2*2
如果只想恢复图的一部分,并且再加入其它的op用于fine-tuning。只需通过
graph.get_tensor_by_name()
方法获取需要的op,并且在此基础上建立图,看一个简单例子,假设我们需要在训练好的VGG网络使用图,并且修改最后一层,将输出改为2,用于fine-tuning新数据:...... ...... saver = tf.train.import_meta_graph('vgg.meta') # 访问图 graph = tf.get_default_graph() #访问用于fine-tuning的output fc7= graph.get_tensor_by_name('fc7:0') #如果你想修改最后一层梯度,需要如下 fc7 = tf.stop_gradient(fc7) # It's an identity function fc7_shape= fc7.get_shape().as_list() new_outputs=2 weights = tf.Variable(tf.truncated_normal([fc7_shape[3], num_outputs], stddev=0.05)) biases = tf.Variable(tf.constant(0.05, shape=[num_outputs])) output = tf.matmul(fc7, weights) + biases pred = tf.nn.softmax(output) # Now, you run this with fine-tuning data in sess.run()
Reference
http://cv-tricks.com/tensorflow-tutorial/save-restore-tensorflow-models-quick-complete-tutorial/
-
如何动态的修改模型形状
2020-08-07 16:50:36比如我要往模型的胸部丢一个鸡蛋,然后鸡蛋碎了,在胸部炸开一个鸡蛋,留下一点东西,然后改变胸部的模型样子 -
PyTorch 实战(模型训练、模型加载、模型测试)
2019-07-30 10:25:18保存模型->加载模型->测试模型 自定义数据集 参考我的上一篇博客:自定义数据集处理 数据加载 默认小伙伴有对深度学习框架有一定的了解,这里就不做过多的说明了。 好吧,还是简单的说一下吧: 我们在做好...-
本次将一个使用Pytorch的一个实战项目,记录流程:自定义数据集->数据加载->搭建神经网络->迁移学习->保存模型->加载模型->测试模型
-
自定义数据集
参考我的上一篇博客:自定义数据集处理 -
数据加载
默认小伙伴有对深度学习框架有一定的了解,这里就不做过多的说明了。
好吧,还是简单的说一下吧:
我们在做好了自定义数据集之后,其实数据的加载和MNSIT 、CIFAR-10 、CIFAR-100等数据集的都是相似的,过程如下所示:- 导入必要的包
import torch from torch import optim, nn import visdom from torch.utils.data import DataLoader
- 加载数据
可以发现和MNIST 、CIFAR的加载基本上是一样的
train_db = Pokemon('pokeman', 224, mode='train') val_db = Pokemon('pokeman', 224, mode='val') test_db = Pokemon('pokeman', 224, mode='test') train_loader = DataLoader(train_db, batch_size=batchsz, shuffle=True, num_workers=4) val_loader = DataLoader(val_db, batch_size=batchsz, num_workers=2) test_loader = DataLoader(test_db, batch_size=batchsz, num_workers=2)
- 搭建神经网络
ResNet-18网络结构:
ResNet全名Residual Network残差网络。Kaiming He 的《Deep Residual Learning for Image Recognition》获得了CVPR最佳论文。他提出的深度残差网络在2015年可以说是洗刷了图像方面的各大比赛,以绝对优势取得了多个比赛的冠军。而且它在保证网络精度的前提下,将网络的深度达到了152层,后来又进一步加到1000的深度。论文的开篇先是说明了深度网络的好处:特征等级随着网络的加深而变高,网络的表达能力也会大大提高。因此论文中提出了一个问题:是否可以通过叠加网络层数来获得一个更好的网络呢?作者经过实验发现,单纯的把网络叠起来的深层网络的效果反而不如合适层数的较浅的网络效果。因此何恺明等人在普通平原网络的基础上增加了一个shortcut, 构成一个residual block。此时拟合目标就变为F(x),F(x)就是残差:
- 训练模型
def evalute(model, loader): model.eval() correct = 0 total = len(loader.dataset) for x, y in loader: x, y = x.to(device), y.to(device) with torch.no_grad(): logits = model(x) pred = logits.argmax(dim=1) correct += torch.eq(pred, y).sum().float().item() return correct / total def main(): model = ResNet18(5).to(device) optimizer = optim.Adam(model.parameters(), lr=lr) criteon = nn.CrossEntropyLoss() best_acc, best_epoch = 0, 0 global_step = 0 viz.line([0], [-1], win='loss', opts=dict(title='loss')) viz.line([0], [-1], win='val_acc', opts=dict(title='val_acc')) for epoch in range(epochs): for step, (x, y) in enumerate(train_loader): x, y = x.to(device), y.to(device) model.train() logits = model(x) loss = criteon(logits, y) optimizer.zero_grad() loss.backward() optimizer.step() viz.line([loss.item()], [global_step], win='loss', update='append') global_step += 1 if epoch % 1 == 0: val_acc = evalute(model, val_loader) if val_acc > best_acc: best_epoch = epoch best_acc = val_acc viz.line([val_acc], [global_step], win='val_acc', update='append') print('best acc:', best_acc, 'best epoch:', best_epoch) model.load_state_dict(torch.load('best.mdl')) print('loaded from ckpt!') test_acc = evalute(model, test_loader)
- 迁移学习
提升模型的准确率:
# model = ResNet18(5).to(device) trained_model=resnet18(pretrained=True) # 此时是一个非常好的model model = nn.Sequential(*list(trained_model.children())[:-1], # 此时使用的是前17层的网络 0-17 *:随机打散 Flatten(), nn.Linear(512,5) ).to(device) # x=torch.randn(2,3,224,224) # print(model(x).shape) optimizer = optim.Adam(model.parameters(), lr=lr) criteon = nn.CrossEntropyLoss()
- 保存、加载模型
pytorch保存模型的方式有两种:
第一种:将整个网络都都保存下来
第二种:仅保存和加载模型参数(推荐使用这样的方法)
# 保存和加载整个模型 torch.save(model_object, 'model.pkl') model = torch.load('model.pkl')
# 仅保存和加载模型参数(推荐使用) torch.save(model_object.state_dict(), 'params.pkl') model_object.load_state_dict(torch.load('params.pkl'))
可以看到这是我保存的模型:
其中best.mdl是第二中方法保存的
model.pkl则是第一种方法保存的
- 测试模型
这里是训练时的情况
看这个数据准确率还是不错的,但是还是需要实际的测试这个模型,看它到底学到东西了没有,接下来简单的测试一下:
import torch from PIL import Image from torchvision import transforms device = torch.device('cuda') transform=transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225]) ]) def prediect(img_path): net=torch.load('model.pkl') net=net.to(device) torch.no_grad() img=Image.open(img_path) img=transform(img).unsqueeze(0) img_ = img.to(device) outputs = net(img_) _, predicted = torch.max(outputs, 1) # print(predicted) print('this picture maybe :',classes[predicted[0]]) if __name__ == '__main__': prediect('./test/name.jpg')
实际的测试结果:
效果还是可以的,完整的代码:
https://github.com/huzixuan1/Loader_DateSet
数据集下载链接:
https://pan.baidu.com/s/12-NQiF4fXEOKrXXdbdDPCg
由于笔者能力水平有限,在表述上可能有些不准确;有问题可以联系QQ:1017190168 -
-
jvm 内存模型
2019-08-15 14:28:512. jvm 内存模型划分 根据JVM规范,JVM 内存共分为虚拟机栈,堆,方法区,程序计数器,本地方法栈五个部分。 程序计数器(线程私有): 是当前线程锁执行字节码的行号治时期,每条线程都有一个独立的程序计数器...二. jvm 内存模型划分
根据JVM规范,JVM 内存共分为虚拟机栈,堆,方法区,程序计数器,本地方法栈五个部分。
程序计数器(线程私有):
是当前线程锁执行字节码的行号治时期,每条线程都有一个独立的程序计数器,这类内存也称为“线程私有”的内存。正在执行java方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如果是Natice方法,则为空。java 虚拟机栈
也是线程私有的。
每个方法在执行的时候也会创建一个栈帧,存储了局部变量,操作数,动态链接,方法返回地址。
每个方法从调用到执行完毕,对应一个栈帧在虚拟机栈中的入栈和出栈。
通常所说的栈,一般是指在虚拟机栈中的局部变量部分。
局部变量所需内存在编译期间完成分配,
如果线程请求的栈深度大于虚拟机所允许的深度,则StackOverflowError。
如果虚拟机栈可以动态扩展,扩展到无法申请足够的内存,则OutOfMemoryError。
本地方法栈(线程私有)
和虚拟机栈类似,主要为虚拟机使用到的Native方法服务。也会抛出StackOverflowError 和OutOfMemoryError。Java堆(线程共享)
被所有线程共享的一块内存区域,在虚拟机启动的时候创建,用于存放对象实例。
对可以按照可扩展来实现(通过-Xmx 和-Xms 来控制)
当队中没有内存可分配给实例,也无法再扩展时,则抛出OutOfMemoryError异常。
方法区(线程共享)
被所有方法线程共享的一块内存区域。
用于存储已经被虚拟机加载的类信息,常量,静态变量等。
这个区域的内存回收目标主要针对常量池的回收和堆类型的卸载。3.jvm 1.8 内存区域划分
程序计数器
每个线程一块内存,指向当前正在执行的字节码的行号。如果当前线程是native方法,则其值为null。ps(程序计数器内存划分)
因为处理器在一个确定是时刻只会执行一个线程中的指令,线程切换后,是通过计数器来记录执行痕迹的,因而可以看出,程序计数器是每个线程私有的。
如果执行的是java方法,那么记录的是正在执行的虚拟机字节码指令的地址的地址,如果是native方法,计数器的值为空(undefined)。Java虚拟机栈
ps: 虚拟机栈中的名词解释
局部变量表:
存放编译期可知的各种基本数据类型、对象引用类型和returnAddress类型(指向一条字节码指令的地址:函数返回地址)。
long、double占用两个局部变量控件Slot。
局部变量表所需的内存空间在编译期确定,当进入一个方法时,方法在栈帧中所需要分配的局部变量控件是完全确定的,不可动态改变大小。
异常:线程请求的栈帧深度大于虚拟机所允许的深度—StackOverFlowError,如果虚拟机栈可以动态扩展(大部分虚拟机允许动态扩展,也可以设置固定大小的虚拟机栈),但是无法申请到足够的内存—OutOfMemorError。操作数栈:
后进先出LIFO,最大深度由编译期确定。栈帧刚建立使,操作数栈为空,执行方法操作时,操作数栈用于存放JVM从局部变量表复制的常量或者变量,提供提取,及结果入栈,也用于存放调用方法需要的参数及接受方法返回的结果。
操作数栈可以存放一个jvm中定义的任意数据类型的值。
在任意时刻,操作数栈都一个固定的栈深度,基本类型除了long、double占用两个深度,其它占用一个深度
动态连接:
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。Class文件的常量池中存在有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用,一部分会在类加载阶段或第一次使用的时候转化为直接引用(如final、static域等),称为静态解析,另一部分将在每一次的运行期间转化为直接引用,这部分称为动态连接。方法返回地址:
当一个方法被执行后,有两种方式退出该方法:执行引擎遇到了任意一个方法返回的字节码指令或遇到了异常,并且该异常没有在方法体内得到处理。无论采用何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行。方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。一般来说,方法正常退出时,调用者的PC计数器的值就可以作为返回地址,栈帧中很可能保存了这个计数器值,而方法异常退出时,返回地址是要通过异常处理器来确定的,栈帧中一般不会保存这部分信息。
方法退出的过程实际上等同于把当前栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,如果有返回值,则把它压入调用者栈帧的操作数栈中,调整PC计数器的值以指向方法调用指令后面的一条指令。堆
堆时JVM内存占用最大,管理最复杂的一个区域。唯一的途径就是存放对象实例:所有的对象实例以及数组都在堆上进行分配。jdk1.7以后,字符串常量从永久代中剥离出来,存放在堆中。堆具有进一步的内存划分。按照GC分代手机角度划分
老年代:2/3的堆空间
年轻代:1/3的堆空间
eden区:8/10 的年轻代
survivor0: 1/10 的年轻代
survivor1:1/10的年轻代元数据区域
元数据区域取代了1.7版本及以前的永久代。元数据和永久代本质上都时方法区的实现。方法区皴法虚拟机加载的类型西,静态变量,常量数据。
参数设置:-XX:MetaspaceSize=18m
-XX:MaxMetaspaceSize=60m
直接内存
java.nio 中使用DirectBuffer相关使用(此处未完待续。。。。。。。。。) -
基于CNN的垃圾分类模型
2018-01-10 15:44:21基于TensorFlow和Keras的垃圾分类模型 本篇博客主要介绍基于TensorFlow和Keras实现垃圾分类模型,目前是一篇占坑的博客,由于该项目目前用于参加比赛,因此暂时不能提供代码,感兴趣的可以私信我一起交流,识别... -
Hadoop的架构模型
2020-10-06 11:32:52Hadoop的架构模型(1.x,2.x的各种架构模型介绍)1.x的版本架构模型介绍2.x的版本架构模型介绍 1.x的版本架构模型介绍 文件系统核心模块: NameNode:集群当中的主节点,主要用于管理集群当中的各种数据 ... -
4.4.2分类模型评判指标(一) - 混淆矩阵(Confusion Matrix)
2018-05-31 15:16:19混淆矩阵是ROC曲线绘制的基础,同时它也是衡量分类型模型准确度中最基本,最直观,计算最简单的方法。 一句话解释版本: 混淆矩阵就是分别统计分类模型归错类,归对类的观测值个数,然后把结果放在一个表里展示... -
数据库中的常用的数据模型 层次模型 网状模型 关系模型
2019-04-14 13:41:45层次数据模型 定义:层次数据模型是用树状<层次>结构来组织数据的数据模型。 满足下面两个条件的基本层次联系的集合为层次模型 1. 有且只有一个结点没有双亲结点,这个结点称为根结点... -
OSI七层协议模型、TCP/IP四层模型学习笔记
2016-07-31 16:46:411.1OSI引入了服务、接口、协议、分层的概念,TCP/IP借鉴了OSI的这些概念建立TCP/IP模型。 1.2OSI先有模型,后有协议,先有标准,后进行实践;而TCP/IP则相反,先有协议和应用再提出了模型,且是参照的OSI模型。 ... -
软件工程——瀑布模型、快速原型模型、增量模型、螺旋模型
2019-07-25 16:24:48一、瀑布模型 1.1什么是瀑布模型 1.2特点 1.3优缺点 1.4客户需求 二、快速原型模型 2.1什么是快速原型模型 2.2优缺点 2.3快速原型模型的思想产生、原理及运用方式 2.4类型 2.5开发步骤 三、增量模型 3.1... -
keras系列︱Sequential与Model模型、keras基本结构功能(一)
2017-06-04 11:23:08不得不说,这深度学习框架更新太快了尤其到了Keras2.0版本,快到Keras中文版好多都是错的,快到官方文档也有旧的没更新,... 笔者先学的caffe,从使用来看,比caffe简单超级多,非常好用,特别是重新训练一个模型,但是 -
pytorch 加载(.pth)格式的模型
2018-12-28 22:42:02有一些非常流行的网络如 resnet、squeezenet、densenet等在pytorch里面都有,包括网络结构和训练好的模型。 pytorch自带模型网址:...按官网加载预训练好的模型: import torchvision.models... -
[NLP自然语言处理]谷歌BERT模型深度解析
2018-10-15 17:49:18我的机器学习教程「美团」算法工程师带你入门机器学习 已经开始更新了,欢迎大家订阅~ 任何关于算法、编程、AI行业知识或博客内容的问题,可以随时扫码关注公众号「图灵的猫」,加入”...BERT模型代码已经发布,... -
常见JAVA IO/NIO模型
2019-07-09 12:33:28我们常见的IO模型有:阻塞 IO 模型、非阻塞 IO 模型、多路复用 IO 模型、信号驱动 IO 模型、异步 IO 模型;下面我们就简单介绍一下以上IO模型。 1、阻塞 IO 模型 最传统的一种IO 模型,即在读写数据过程中会发生... -
时间序列模型 (一):模型概述
2019-04-21 21:47:12时间序列模型 (一):模型概述 时间序列模型 (二):移动平均法 时间序列模型 (三):指数平滑法 时间序列模型 (四):差分指数平滑法、 自适应滤波法v 时间序列模型 (五): 趋势外推预测方法 时间序列... -
生成模型与判别模型
2012-11-17 23:24:42生成模型与判别模型 zouxy09@qq.com http://blog.csdn.net/zouxy09 一直在看论文的过程中遇到这个问题,折腾了不少时间,然后是下面的一点理解,不知道正确否。若有错误,还望各位前辈不吝指正,以免小弟一错再... -
Apollo代码学习(二)—车辆运动学模型
2018-10-11 20:53:13Apollo代码学习—车辆运动学与动力学模型前言车辆模型车辆运动学模型车辆动力学模型 Apollo(阿波罗)是一个开放的、完整的、安全的自动驾驶平台,以灵活和高性能的技术架构,为全自动驾驶提供支持。 前言 要... -
数学建模常用模型05 :多元回归模型
2018-08-03 13:43:37所以在遇到有些无法用机理分析建立数学模型的时候,通常采取搜集大量数据的办法,基于对数据的统计分析去建立模型,其中用途最为广泛的一类随即模型就是统计回归模型。 回归模型确定的变量之间是相关关系,在大量的... -
软件测试过程模型特点(V模型 W模型 X模型 H模型)
2018-10-03 14:43:381.V模型: V模型和瀑布模型有一些共同的特性,V模型中的过程从左到右,描述了基本的开发 过程和测试行为。 优点:V模型的价值在于它非常明确地标明了测试过程中存在的不同级别,并且清楚地描述了这些测试阶段和开发... -
软件开发模型总结归纳(瀑布模型、螺旋模型、迭代模型、增量模型、敏捷模型)
2019-08-04 00:18:18文章目录0. 软件的生命周期1. 瀑布模型2. 螺旋模型3. 迭代模型4. 增量模型5.... 瀑布模型是最早出现的软件开发模型,是所有其他软件开发模型的基础框架。与软件的生命周期不同的是,它缺少了软... -
CTR模型:FNN模型
2020-09-07 21:46:42FNN 模型是2016年提出的,基于传统机器学习模型,如LR,FM等,的CTR预测方案被称为基于浅层模型的方案。 优点是模型简单,预测性能较好,可解释性强。缺点是,很难自动提取高阶组合特征携带的信息。 于是, 基于因子... -
一份简短又全面的数学建模技能图谱:常用模型&算法总结
2019-05-04 08:34:41本文总结了常用的数学模型方法和它们的主要用途,主要包括数学和统计上的建模方法,关于在数学建模中也挺常用的机器学习算法暂时不作补充,以后有时间就补。至于究竟哪个模型更好,需要用数据来验证,还有求解方法也... -
数学建模竞赛常考三大模型及十大算法【预测模型、优化模型、评价模型】
2020-08-31 10:36:47三大模型 1、预测模型 2、优化模型 3、评价模型 数学建模的十大常用算法 预测模型:神经网络预测、灰色预测、拟合插值预测(线性回归)、时间序列预测、马尔科夫链预测、微分方程预测、Logistic 模型等等。 应用... -
浅析:领域模型、贫血模型和充血模型
2017-08-11 13:12:45一、领域模型 领域模型是领域内的概念类或现实世界中对象的可视化表示,又称为概念模型或分析对象模型,它专注于分析问题领域本身,发掘重要的业务领域概念,并建立业务领域概念之间的关系。 领域模型从业务... -
数据库三种概念模型——层次模型,网状模型,关系模型的优缺点总结
2019-04-08 10:49:02层次模型,网状模型,关系模型的优缺点总结 -
瀑布模型、演化模型、螺旋模型、喷泉模型等等的区别
2018-12-08 18:44:56软件开发模型(SoftwareDevelopmentModel)是指软件开发全部过程、活动和任务的结构框架。软件开发包括需求、设计、编码和测试等阶段,有时也包括维护阶段。 软件开发模型能清晰、直观地表达软件开发全过程,明确... -
模型编辑之模型编辑
2019-06-10 14:56:39模型编辑的相关内容主要就是包含对模型中心点坐标和旋转的相关操作。主要包括了“模型平移”、“模型旋转”、“批量平移”和“坐标归零”功能。 下面就跟着小编来一个一个的看... -
数据库建模 : 概念模型 , 逻辑模型和物理模型
2019-03-08 10:56:41概念模型设计 , 逻辑模型设计 , 物理模型设计 是数据库及数据仓库模型设计的三个主要步骤 1. 概念模型 概念模型就是在了解了用户的需求 , 用户的业务领域工作情况以后 , 经过分析和总结 , 提炼出来的用以描述用户... -
软件开发模型2:增量模型/螺旋模型/敏捷模型
2019-01-07 08:43:04文章目录4 增量模型(Incremental Model)4.1 优点4.2 缺点5 螺旋模型(Spiral Model)6 敏捷模型(Agile Modeling) 转载自博文: 软件开发的11种模式 https://blog.csdn.net/xi_gua_gua/article/details/53107283 ...
-
小学生C++入门班与提高班(2021.01.20).pdf
-
计算机考研408历年真题和答案汇编(2009-2021).pdf
-
js数组问题 大佬们救救我
-
抢购京东平台的茅台1499
-
易语言显示鼠标座标.e
-
【2021】UI自动化测试框架(Selenium3)
-
thinkphp5.1博客后台实战视频
-
jquery的插件有哪些
-
gin 日志分割
-
智联万物,京东IoT技术创新与实践
-
Kotlin协程极简入门与解密
-
瞎折腾——魔百和M301H(SM)
-
RabbitMQ消息中间件实战(附讲义和源码)
-
转行做IT-第5章 流程控制语句
-
WPF上位机数据采集与监控系统零基础实战
-
2020牛客暑期多校集训营第三场题解.pdf
-
中文说明worldserver.conf
-
【2021】UI自动化测试Selenium3
-
转行做IT-第2章 HTML入门及高级应用
-
mac系统渗透测试小技巧-将命令行工具能够tab补全