深度学习入门路径

深度学习入门路径

作为一个刚刚接触深度学习一个月的小白,不知道有没有资格谈这个话题?每个人情况不同,路径也不一样,这个算是自个学习过程的一个记录和反思,供大家参考和批判,也希望各位深度学习大佬多多指教,让小白少走弯路。

准备理论知识

很多人被深度学习中会用到的“公式”吓跑,包括几年前的我。可是当下决心去搞一下深度学习的时候,你会发现能够理解甚至推导那些公式自然好,但只要理清思路,搞明白原理就好,公式就暂且留给吴恩达们去论证吧:-)。当然,如果你期望对深度学习有理论上的贡献,还是需要比较深厚的数学功底的,另当别论。
我个人是希望将深度学习应用到工程领域,因此理论知识准备阶段的目标只限于“明原理”,快速学习了以下的课程:
数学知识
如果只是将深度学习应用到工程领域,大学的数学课基本就够用了,包括微积分、偏微分、基本的线性代数,基本的概率知识。我的数学知识遗忘的比较厉害,颇费了些周折才重新了解了一些基本概念,主要参考了网易云课堂的这个课程:机器学习中的数学知识,个人觉得雷老师讲的还是不错的。这个数学知识课程我没有全部听完,机器学习这一块就转门到吴恩达了。
机器学习
深度学习是机器学习的一个分支,因此要学习深度学习就不能不搞清楚机器学习的基本原理,这方面吴恩达是绝好的引路人,他的课程个人认为是不二之选:吴恩达机器学习。吴恩达的这个课程有必要全部听完,我目前也只是听了一大半,需要继续努力。

搭建开发环境

关于python,我对编程还算比较在行,因此花在学习python上的时间不多,主要是快速的看了一下廖雪峰的python教程,打算边学tensorflow边进一步熟悉python,遇到不熟悉的语法google一下,遇到不熟悉的包google一下,目前看来这个策略还是比较顺利的。也就是说,没有必要花大量的时间学习python,如果你有比较好的程序设计基础的话。
推荐使用jupyter开始你的python和tensorflow学习之旅,你一定会惊叹,怎么会有这么奇妙的编程和文档结合的这么好的工具呢?使用jupyter用于python和机器学习的教学,个人认为是目前的最佳选择,没有之一,不接受反驳。甚至,目前我都把jupyter当做一个调试的工具了。当然,pycharm也是很不错的。

阅读tensorflow的官网资料

在基本学完2的网课后,需要学习一下tensorflow 1.x版本(毕竟目前大量的案例是基于tensorflow1.x的),并大量阅读tensorflow官网的一些资料,帮助加深对tensorflow和深度学习的理解,主要的资料包括:
  • tutorial中的get started,Learn and ML的全部,research and experimentation的全部,Sequence中的Text generation with RNN,Load Data,Data representation。
  • guide中的keras,eager execution,importing data,embeddings。
  • 熟悉API文档的结构。
这些资料尽量阅读英文版的,中文版的翻译有的地方不及时。
我目前只看了上面这些,其他资料可以用到的时候再看再学。
注:tensorflow 1.x和2.0版本的差别对于初学者来说不算很大,找时间写一下给大家参考。

先玩转一个方向

根据自己的兴趣,选一个方向比较深入的玩下去。弄明白代码的每一行非常重要,同时要思考数据流在每一个步骤中的shape以及为什么是这样的shape。我个人是选择了RNN,觉得非常好玩。

开始行动比雄心万丈更重要!

从源代码编译tensorflow 2.0

趁tensorflow 2.0.0-alpha0刚刚发布,尝个鲜,却不料遭到蒙头一棍:pip install tensorflow-gpu==2.0.0-alpha0安装起来,简单的导入tensorflow会导致core dump!尝试了不同的python版本,也尝试了cpu版本,结果都是一样。郁闷中打开core dump,挑了其中的几个关键词放狗去搜,才发现是因为我电脑的老U不支持AVX:当年的推土机已垂垂老矣。tensorflow预编译版本都默认打开了AVX,因此在老的不支持AVX的CPU上面安装tensorflow 2,无论CPU版本还是GPU版本都会core dump。无奈,只好从源代码自己编译一个出来。在https://www.tensorflow.org/install/source里面已经很详细的说明了编译tensorflow的步骤,下面是几个不大不小的坑:

版本的选取

从github clone下来tensorflow之后,要checkout相应的版本再进行编译的操作。对于我的情况,是首先执行如下的命令:

git branch -a # 检查有哪些分支?

git checkout v2.0 # 捡取v2.0分支进行编译

创建必要的python虚拟环境

编译bazel的时候需要用到keras的一些依赖库,因此最好是在编译之前创建虚拟环境并安装keras:

conda create -n tf2 python=3.7

conda activate tf2

conda install keras-preprocessing

bazel build ……

然后,在整个编译过程中,都要在这个虚拟环境中进行。

下载合适的bazel版本

编译tensorflow不要安装最新的bazel。我这边使用23.0版本刚刚好,再新一点点都会报错,非常奇怪的问题。

最好设置git代理

编译过程中要从github下载好几个软件,因此最好设置git代理以加快下载速度:

git config –global http.proxy localhost:1080

git config –global https.proxy localhost:1080

# 下面的命令可以取消代理设置:

# git config –global –unset http.proxy

# git config –global –unset https.proxy

有充分的耐心

整个编译过程在我的机器上要耗费5-6个小时。

一道简单概率题的求解-使用贝叶斯公式

一道简单概率题的求解-使用贝叶斯公式

题目:保险公司认为人可以分为两类,一类易出事故,另一类则不易出事故。统计表明,一个易出事故者在一年内出事故的概率为0.4,而对不易出事故者来说,这个概率降低到0.2。若假定第一类人(易出事故)占人口比例为30%,现有一个新人来投保,那么该人在购买保单后一年内出事故的概率是多少?
解析:我们无法知晓这个新人是什么类型的人,所以无法直接求解此人在一年内出事故的概率。根据贝叶斯公式,如果我们能够找到此人的所有可能类型(事件),然后再知道每种类型一年内出事故的概率(加权值),那么此人在一年内出事故的概率则可以求出。因此,设事件A为此人一年内会出事故,设事件B为此人是易出事故者,则



B

c


表示此人为不易出事故者。所求概率为:


P(
A
)
=P(

A|B

)
P(
B
)
+P(

A|

B

c



)
P(


B

c


)
=P(

A|B

)
P(
B
)
+P(

A|

B

c



)
(

1P(
B
)


)
=0.4*0.3+0.2*0.7=0.26

反过来,如果此人在投保一年内出了事故,那么此人是易出事故者的概率是多大?所求概率为:

P(

B|A

)
=


P(

AB

)




P(
A
)



=


P(

A|B

)
P(
B
)




P(
A
)



=


0.4*0.3


0.26

=

6

13



python 3.7下安装tensorflow-cpu

python 3.7下安装tensorflow-cpu

由于tensorflow还没有进入python 3.7的pip安装源,所以在3.7下安装tensorflow需要手工安装,步骤如下(以virtualenv为例):
  1. python3 -m venv $HOME/tensorflow # 创建一个tensorflow的虚拟环境
  2. source $HOME/tensorflow/bin/activate # 激活tensorflow虚拟环境
  3. 从这里下载tensorflow 1.12版本:https://pypi.org/project/tensorflow/#files,最新的版本目前是:tensorflow-1.12.0-cp36-cp36m-manylinux1_x86_64.whl 。
  4. 把下载的文件改一下名字:mv tensorflow-1.12.0-cp36-cp36m-manylinux1_x86_64.whl tensorflow-1.12.0-cp37-cp37m-manylinux1_x86_64.whl
  5. 执行pip install tensorflow-1.12.0-cp37-cp37m-manylinux1_x86_64.whl

dia无法输入中文的终极解决方法

dia无法输入中文的终极解决方法

网上流传了多种解决方法,感觉下面这个比较正宗:

从启动栏或者快捷方式启动dia的中文输入问题

修改/usr/share/applications/dia.desktop文件,把Exec=dia %F改成Exec=env GTK_IM_MODULE=xim dia %F

在终端启动时增加启动设置

启动命令Dia前边增加env GTK_IM_MODULE=xim,即用env GTK_IM_MODULE=xim dia来启动Dia,为了避免每次启动都要输入这么一长串,我们设置别名alias,执行命令alias dia=”env GTK_IM_MODULE=xim dia”,以后再启动Dia时还是使用dia就可以了。

linux中启用输入时禁用触摸板

linux中启用输入时禁用触摸板

在输入文字时,如果没有禁用笔记本电脑的触摸板,可就有罪受了。在Ubuntu下面,可以通过安装touchpad-indicator这个PPA,实时检测你是否正在输入文字:如果你正在不停的敲击键盘,touchpad-indicator自动禁止触摸板。这么体贴的功能实在应该收进正式发布版中!
安装touchpad-indicator的命令如下:
sudo add-apt-repository ppa:atareao/atareao
sudo apt update
sudo apt install touchpad-indicator

Java Class文件结构:常量池

Java Class文件结构:常量池

概念的引入

Java的Class文件在描述类、属性、方法等时要用到一系列的“常量”。比如下面的属性:
int var;
描述这个属性,需要两个常量:
  1. 属性名称“var”是一个字符串常量;
  2. 属性的数据类型是int,在class文件中通过单个大写字母I(这也是一个字符串常量)表示int;
因此,上述的属性就可以通过两个字符串常量来描述(不包括冒号):
var:I
通过常量来描述类、属性、方法等的好处大概有三个

1

who knows?

  • 复用常量,压缩class文件的尺寸。比如描述int m;这个属性时,就可以复用I(表示int)这个字符串了。
  • 可使用精简的符号表示复杂的含义,比如上述的I表示int,进一步压缩class文件的尺寸。
  • 方便扩展。当需要定义新的常量时,只需要在常量池中添加即可。

一些术语

2.1 全限定名(Fully Qualified Name)

全限定名=包名+类/接口的名称。显然,只有全限定名才能唯一定位类或者接口的位置,因此在class文件中一律使用全限定名。
但是,由于当初设计class文件时考虑不周,class文件内部使用的全限定名被定义为使用/分割,比如java/lang/Object,这和大家习惯的包名表达方式(java.lang.Object)不一致。不得已,JVM规范将class内部使用的全限定名称为“internal form”(内部形式)的全限定名。由于本文仅涉及class的内部结构,因此引用到全限定名时皆指诸如java/lang/Object的内部形式。

2.2 描述符(Descriptor)

在表达属性、方法时,除了其名称(全限定名)外,其他的特性刻画需要依靠描述符(Descriptor)。

2.2.1 属性描述符

属性描述符仅表达了属性的数据类型。1列出了属性描述符中数据类型的表示方式。大部分的数据类型描述方式一目了然,和数据类型的首字符是一一对应的。下面重点阐述对象、数组的描述符。
对象的描述符要特别注意两点:一是类名使用全限定名,二是最后的分号(;)

2

为什么对象的描述符最后要有一个这样的小尾巴呢?因为在属性描述符中,其他数据类型都是单个字母,很容易确定整个属性描述符的界限。但是在对象描述符中,类的全限定名长度是可变的,无法事先确定,因此只好使用分号(;)来表示对象的描述符的结束。其实在这里,能够表示对象的描述符结束的符号很多,只要不是常规字母和/即可,至于为什么选择了分号作为结束符,尚未见考证材料。

。比如属性定义:String str的描述符为:Ljava/lang/String;

多维数组的描述符使用了多个[来表达,比如double[][]的描述符为[[D
我们在方法描述符中会大量使用到属性描述符来描述参数列表。
描述符
数据类型
说明
B
byte
字节
C
char
字符
D
double
双精度
F
float
单精度
I
int
整数
J
long
长整数
L ClassName ;
reference
ClassName对象
S
short
短整数
Z
boolean
true/false
[
reference
一维数组
表 1: 属性描述符的数据类型定义

2.2.2 方法描述符

方法定义
方法描述符
String doSomething(int a, double b,Thread t){...}
(IDLjava/lang/Thread;)Ljava/lang/String
void doSomething(int a,double b,Thread t){...}
(IDLjava/lang/Thread;)V
void doSomething(int[][] a)
([[I)V
表 2: 方法描述符示例
这里引申出一个比较有意思的问题:Java的方法允许有多少个参数[1, p92][2]?结论是,Java的方法最多允许255个单位的参数,其中int等类型的参数算作一个单位,long和double类型的参数算作2个单位

3

为什么long和double算作两个2单位?这大概和JVM设计之初的一个不合理规定有关,参见xxx。

。这个问题作者还没有搞的太清楚,应该需要从Java虚拟机调用方法的栈机制去调查,从方法描述符的限制中找不到线索。

常量池的总体结构

常量池的总体结构如3所示,一个u2表示常量池的表项数量,后面紧跟着constant_pool_length个表项。这里要注意两点:第一,constant_pool_length不是常量池的总字节尺寸,而是表项数量。表项结构各不相同,因此常量池的总长度由所有表项的长度相加计算而得;第二,constant_pool_length的索引是从1开始的,因此真正的表项数量是constant_pool_length – 1。如1所示,我们的示例代码Person.class中,常量池表项的数量应为:0x0014 – 1项,即19项。
image: http://softlab.sdut.edu.cn/blog/subaochen/wp-content/uploads/sites/4/2018/12/0_home_subaochen_git_blog_imgs_java_class-cp-length-2.png
图 1: 常量池表项数量
到目前为止(Java SE 11),常量池共有17种常量表项,见4,每一种常量表项都有特定的数据结构。常量表项数据结构的第一个字节用于标识不同的表项,称为“标志”(Tag),比如Utf8编码的字符串表项(CONSTANT_Utf8_info)的标志为1,参见5
常量表项类型
标志
Java SE
描述
CONSTANT_Utf8_info
1
1.0.2
Utf8编码的字符串
CONSTANT_Integer_info
3
1.0.2
整数
CONSTATN_Float_info
4
1.0.2
单精度浮点数
CONSTANT_Long_info
5
1.0.2
长整数
CONSTANT_Double_info
6
1.0.2
双精度浮点数
CONSTANT_Class_info
7
1.0.2
CONSTANT_String_info
8
1.0.2
字符串
CONSTANT_Fieldref_info
9
1.0.2
属性
CONSTANT_Methodref_info
10
1.0.2
方法
CONSTANT_InterfaceMethodref_info
11
1.0.2
接口方法
CONSTANT_NameAndType_info
12
1.0.2
名称和类型
CONSTANT_MethodHandler_info
15
7
方法句柄?
CONSTANT_MethodType_info
16
7
方法类型
CONSTANT_Dynamic_info
17
11
CONSTANT_InvokeDynamic_info
18
7
CONSTANT_Module_info
19
9
模块
CONSTANT_Package_info
20
9
表 4: 常量池的表项类型
常量池实际上是class文件的“符号体系”,所有在表达class文件的结构、意义和Java语法时会用到的“词汇”,均需要在常量池中定义,因此常量池的分量很重,往往是一个class文件的主要内容。常量池的这种设计也非常巧妙,通过扩展表项即可扩展Java语言的表达能力。

Utf8字符串常量

数据类型
描述
备注
u1
tag
标签=1
u2
length
字符串的实际长度
u1
bytes[length]
字符串
表 5: Utf8编码的字符串表项结构
Person.class中的其中一个(第一个)Utf8字符串常量如2所示。实际上,根据ghex这个软件右栏的提示,我们很容易找到其他的Utf8编码的字符串常量:本例中的Utf8编码常量均是一个字节的ASCII码字符,因此即很方便的显示在了ghex的右栏,也很容易通过数字节的方式识别出来。
image: http://softlab.sdut.edu.cn/blog/subaochen/wp-content/uploads/sites/4/2018/12/1_home_subaochen_git_blog_imgs_java_class-utf8-info-2.png
图 2: Person.class的Utf8字符串常量示例
实际上,通过java -p更容易识别常量池项目。4是java -p的输出结果中关于常量池的部分,可以一目了然的看到,在常量池中共有19个表项,其中13个是Utf8编码的字符串常量,这些常量也很容易从hgex的右栏找出,如3所示。
image: http://softlab.sdut.edu.cn/blog/subaochen/wp-content/uploads/sites/4/2018/12/2_home_subaochen_git_blog_imgs_java_class-utf8-infos_svg-2.png
图 3: 所有的Utf8编码的字符串常量
Constant pool:
   #1 = Methodref          #4.#16         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#17         // Person.age:I
   #3 = Class              #18            // Person
   #4 = Class              #19            // java/lang/Object
   #5 = Utf8               age
   #6 = Utf8               I|\longremark{属性的数据类型:int}|
   #7 = Utf8               <init>|\longremark{构造方法}|
   #8 = Utf8               ()V|\longremark{方法的descriptor,这里是一个参数列表为空,返回值为void的方法描述符}|
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               isAdult
  #12 = Utf8               ()Z|\longremark{方法的描述符(descriptor),参数列表为空,返回值为boolean}|
  #13 = Utf8               StackMapTable
  #14 = Utf8               SourceFile
  #15 = Utf8               Person.java
  #16 = NameAndType        #7:#8          // "<init>":()V|\longremark{由\#7和\#8号Utf8字符串组成的名称和类型常量表项}|
  #17 = NameAndType        #5:#6          // age:I|\longremark{由\#5和\#6号Utf8字符串常量表项组成的名称和类型常量表项}|
  #18 = Utf8               Person
  #19 = Utf8               java/lang/Object
|\showremarks|

整数常量(Integer)和单精度浮点数(Float)常量

数据类型
描述
备注
u1
tag
标签=3
u4
bytes
整数
表 6: 整数常量的表项结构
Person.class中没有整数常量。

长整数(Long)和双精度浮点数(Double)常量

数据类型
描述
备注
u1
tag
标签=5
u4
high_bytes
u4
low_bytes
表 8: 长整数常量的表项结构
Person.class中没有长整数常量和双精度浮点数常量。

类(Class)常量

整个class文件不就表示了一个类吗?这里的类常量只是表达了类的名称而已,其结构如10所示。类名的索引指向了一个Utf8编码的字符串常量,因此Person.class的类常量如4所示,其中Person是类本身,java/lang/Object是Person的父类。我们知道,如果没有特别声明父类,则每一个类(Object本身除外)都会自动从Object继承下来,所以在常量池中一定会存在Object这个类常量。
image: http://softlab.sdut.edu.cn/blog/subaochen/wp-content/uploads/sites/4/2018/12/3_home_subaochen_git_blog_imgs_java_class-class-info_svg.png
图 4: Person.class中的类常量示例
接口也是通过类常量来表示的。在《从C到Java》一书中,作者曾有阐述,接口就是纯的抽象类,即接口其实是类的一种。类常量可以用来表达接口更加确认了这一点。比如7所示的简单的接口定义,其javap -v的输出如2所示,其常量池有两个类常量,一个是IPerson,一个是java/lang/Object,即IPerson接口是从Object继承下来的:IPerson是一个经过包装的抽象类。
public interface IPerson{
	boolean isAdult();
}
Classfile /home/subaochen/git/blog/src/java/IPerson.class
  Last modified 2018年12月9日; size 119 bytes
  MD5 checksum 2b865ff3610a20333b6df52384c1c38d
  Compiled from "IPerson.java"
public interface IPerson
  minor version: 0
  major version: 55
  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
  this_class: #1                          // IPerson
  super_class: #2                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 1, attributes: 1
Constant pool:
  #1 = Class              #7              // IPerson
  #2 = Class              #8              // java/lang/Object
  #3 = Utf8               isAdult
  #4 = Utf8               ()Z
  #5 = Utf8               SourceFile
  #6 = Utf8               IPerson.java
  #7 = Utf8               IPerson
  #8 = Utf8               java/lang/Object
{
  public abstract boolean isAdult();
    descriptor: ()Z
    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
}
SourceFile: "IPerson.java"

字符串(String)常量

字符串常量用来表示一个String的实例,其指向了一个Utf8编码的字符串常量,其数据结构如11所示。
数据类型
描述
备注
u1
tag
标签=8
u2
string_index
指向Utf8编码的字符串常量
表 11: 字符串常量结构
注意区分字符串常量表项(CONSTANT_String_info)和Utf8编码的字符串常量表项(CONSTANT_Utf8_info)的不同用途:Utf8编码的字符串常量表项用来表示最终的字符串,也就是说,所有的字符串最终都是存储在Utf8编码的字符串常量表项中的;而字符串常量表项,仅用来表示String的实例所代表的字符串的索引,其真正的内容保存在Utf8编码的字符串常量表项中。
如果类中没有使用到String的实例,就不存在字符串常量表项,比如Person.java类。但是,每一个类中一定存在若干个Utf8编码的字符串常量表项用来表达类名、字段名等等。

名称和类型(NameAndType)常量

数据类型
描述
备注
u1
tag
标签=12(0x0C)
u2
name_index
字段和属性的名称索引
u2
descriptor_index
字段和属性的描述符索引
表 12: 名称和类型常量结构
Person.class中的名称和类型常量表项如5所示,第一个名称和类型常量表项由#7(<init>)和#8(()V)Utf8字符串常量表项组成,表达了构造方法的名称和类型;第二个名称和类型cl表项由#5(age)和#6(I)Utf8字符串常量表项组成,表达了字段age的名称和类型。
image: http://softlab.sdut.edu.cn/blog/subaochen/wp-content/uploads/sites/4/2018/12/4_home_subaochen_git_blog_imgs_java_class-name-and-type-info_svg.png
图 5: 名称和类型常量表项示例

字段、方法和接口方法常量

字段、方法以及接口的方法常量表项非常类似,如131415所示。
数据类型
描述
备注
u1
tag
标签=9
u2
class_index
类名称索引
u2
name_and_type_index
名称和类型索引
表 13: 字段常量结构
Person.class的字段常量表项如6所示,方法常量表项如7所示。结合javap -v person.class的输出更容易解读这两个常量表项,此处截取片段说明:
#1 = Methodref          #4.#16         // java/lang/Object."<init>":()V
#2 = Fieldref           #3.#17         // Person.age:I                
#3 = Class              #18            // Person      
#4 = Class              #19            // java/lang/Object
...
#16 = NameAndType        #7:#8          // "<init>":()V
#17 = NameAndType        #5:#6          // age:I 
Person.class中没有接口方法常量表项。
image: http://softlab.sdut.edu.cn/blog/subaochen/wp-content/uploads/sites/4/2018/12/5_home_subaochen_git_blog_imgs_java_class-fieldref-info.png
图 6: Person.class的字段常量表项示例
由于类的方法和接口的方法使用了不同的常量表项来描述,因此方法常量表项中的class_index必须是类,不能是接口;同样的,接口方法常量表项中的class_index必须是接口,不能是类。
字段常量表项中的class_index即可以是类,也可以是接口。

方法句柄(MethodHandle_info)常量表项

方法类型(MethodType_info)常量表项

这个表项很简单,代表了方法的类型,即方法的描述符,其结构如17所示。但是这个常量和NameAndType_info不是重复了吗?MethodType_info在什么地方会用到呢?有待进一步考察。
数据类型
描述
备注
u1
tag
标签=16(0x10)
u2
descriptor_index
方法描述符索引
表 17: 方法类型常量结构

模块(Module_info)常量表项

模块常量用来表示一个模块,其结构如所示,其中name_index指向一个存储了模块名称的Utf8编码的字符串。

包(Package_info)常量表项

包常量表示一个模块中输出或者打开的包,其结构如所示,其中name_index指向一个存储了包名称的Utf8编码的字符串。

CONSTANT_Dynamic_info和CONSTANT_InvokeDynamic_info常量表项

TBD

引用

1 Oracle, “JVM specification“.
2 汪先生, “你知道Java方法能定义多少个参数吗?” (2018).