Java 引用
0x00 前言
说一说Java中的引用,Java没有C++中标记地那么明显,在C++中,引用类型是必需用&标记出来的,而Java不用,这就可能对初学者造成一定的困惑。
0x01 引用传递和值传递
在Java中,对于基本数据类型(如int, char, double等),值就直接保存在变量中。而对于对象类型(如String),我们使用如下的代码创建一个对象:
1String str = "Hello World!";
str实质上为一个String引用类型变量,变量中保存的实际上为这个对象在堆内存中的地址信息,而不是实际的对象数据。
对于基本数据类型,赋值操作会直接覆盖变量的值,原来的数据将被覆盖掉,被替换为新值。而对于引用类型变量str,赋值运算符会修改其所保存对象的地址信息,原来对象的地址被覆盖掉,重新写入新的对象的地址,但是原来对象本身并没有发生改变,当垃圾回收机制检测到这个对象在内存中没有引用的话,即被垃圾回收。
如下代码即会输出"Hello World!",因为在test函数中,引用类型变量a传入进来的时候一开始保存的是引用类型变 ...
Executor 初探
0x00 Executor
Executor的主要功能如下:
无需创建任何的Thread对象,如果想执行一个线程的话,仅需要将这个线程任务实例(一个实现了Runnable接口的类)递交给executor执行即可,executor将会自主管理线程并执行任务。
executor会自动复用线程,类似于数据库连接池一样,其也会创建一个线程池,线程池里面有规划好的预先启动的线程(worker-threads),新任务到达之后自动分配给线程池中的线程执行,在没有任务时线程池中的线程处于一种等待的状态。这样就避免了每次都要重新创建线程的时间消耗。
易实现对executor内部资源的控制,我们可以在创建executor时指定内部线程池的线程数量,如果有超出线程数的任务被安排进来了,其会首先进入一个任务队列,executor中的工作线程完成一项线程任务之后,将会自动从这个任务队列中取出任务来执行。
必需使用代码显式结束executor的运行。这里一般是指调用executor的shutdown方法来结束线程池的运行。否则线程池中的线程将会处于一直等待的状态,在Java中主线程会等待所有的子线程结束之后才 ...
TensorFlow 基础
0x00 数据流图
首先明确一个问题,Tensorflow是基于数据流图的计算。就比如说如下代码:
123456789import tensorflow as tfa = tf.constant([[1, 2]])b = tf.constant([[3], [4]])c = tf.matmul(a, b)with tf.Session() as sess: result = sess.run(c) print(result)
a和b是两个矩阵,c是a和b这两个矩阵乘法运算后的结果。在代码执行到第8行sess.run(c)之前,c的值时没有经过计算。相反,之前的代码只是简单地创建了一个数据流图中的一个结点。只有当执行到sess.run(c)的时候,Tensorflow才开始计算c的值,继而顺着数据流图首先计算出a和b的值,然后再计算两个矩阵的乘积得出c的结果。
0x01 reduce_sum和reduce_mean
一开始的时候着实搞不懂这两个函数到底是干什么的。说得直白一点,reduce就是消除一个维度,sum就是使用求和的方法,reduce_sum即为使用求和的方法来消除 ...
线性单元和梯度下降
0x00 线性单元
上节所讲的感知器在实际应用中存在一个问题,即当处理的数据集不是线性可分的时候,其可能面临着无法收敛的问题,无法收敛就意味着我们可能永远无法完成一个感知器的训练。为了解决这个问题,我们使用一个可导的线性函数来代替感知器的阶跃函数。这种感知器即称为线性单元。当线性单元遇到数据不可分的情况时,会收敛到一个最佳的近似上。
线性可分
概念:可以用一个线性函数把两类样本分开
在二维空间中,要分开两个线性可分的点集合,我们需要找到一条分类直线:
在三维空间中,我们需要找到一个分类面:
0x01 线性模型的表示
我们实际上在做的工作就是通过输入值x来预测输出值y。比方说输入你午饭吃掉的食品种类及克数计算你所摄入的卡路里的数量。假设说我们午饭吃掉了米饭200克、西瓜100克、肉制品50克的话。我们可以将每种食品的食用量都看做一个输入特征,由此就可以使用一个向量来表示我们的输入,这个向量即称为特征向量,如下:
x=[20010050]x=\left[
\begin{matrix}
200\\ 100\\ 50
\end{matrix}
\right]
x=200 ...
循环神经网络
0x00 循环神经网络
循环神经网络(Recurrent Neural Network)适用于学习和处理成序列的信息,其最早在自然语言处理领域被应用,因其可以对语言进行建模。这是个很有意思的话题,来看一个例子:
我昨天上学迟到了,老师批评了____。
我们让电脑预测空白处应填什么词,初期的算法,语言模型处理主要使用N-Gram算法,N是一个数字,它的含义是假设一个词出现的频率,只与前面N个词有关。比如对于上述的例子使用3-Gram算法,即假设空白处所推测的值只与词组『批评了』相关,于是电脑就会在词库中搜索,词组『批评了』后面最有可能出现的词是什么,然后进行填充。但是这样是远远不够的,因为我们真正要填的词『我』,在整句话的开头,词组『批评了』的大前方。这个时候,我们可以通过提升N的值,来做更让电脑作出精确的预判,但是这无疑会增加电脑计算时的工作量。
由此,引入了循环神经网络RNN,因为RNN可以一次往前或往后看任意多个的词。
0x01 RNN的结构
一个循环神经网络往往由输入层、一个隐藏层和一个输出层组成,如图:
这个网络在t时刻收到输入xtx_txt之后,隐藏层的值为sts_t ...
重新认识优化器
0x00 梯度下降法(Gradient Descent)
0x00 标准梯度下降法(GD)
假设要学习训练的模型参数为WWW,代价函数为J(W)J(W)J(W),则代价函数关于模型参数的偏导数即为相关梯度ΔJ(W)\Delta J(W)ΔJ(W),学习率为ηt\eta_tηt,WtW_tWt表示ttt时刻的模型参数,则使用梯度下降法更新后的参数Wt+1W_{t+1}Wt+1为:
Wt+1=Wt−ηtΔJ(Wt)W_{t+1}=W_t-\eta_t\Delta J(W_t)
Wt+1=Wt−ηtΔJ(Wt)
其基本的策略为在有限的视距内寻找最快的路径下山,因此每走一步都会沿着当前最陡的方向迈出下一步。
标准梯度下降法主要有两个缺点:
训练速度慢:每走一步都要要计算调整下一步的方向,下山的速度变慢。在应用于大型数据集中,每输入一个样本都要更新一次参数,且每次迭代都要遍历所有的样本。会使得训练过程及其缓慢,需要花费很长时间才能得到收敛解。
容易陷入局部最优解:由于是在有限的视距内寻找下山的方向。当落入鞍点(局部最优解),梯度为0,使得模型参数不再继续更新。
0x01 批量 ...
kNN
0x00 k-Nearest Neighbors Algorithm(kNN)
这里阐述一个最简单的用于分类问题的适用于监督学习环境下的机器学习算法——kNN算法。其主要具有高精确度、对异常值(outliers)不敏感的优点,以及需要大量的计算时间和占用大量的内存的缺点。而且其只适用于数值数据(Numeric values)。
这个算法的原理非常简单,首先给定一个训练集及其答案标签,此后对于每一个给出的测试数据,计算其与训练集中每一个数据的“距离”,然后取距离最近的k个数据作为当前训练样本的预测值。计算距离的过程实则就是我们高中数学中坐标系下计算两点之间距离的公式,对于一个二维坐标系下的两点(x1,y1)(x_1, y_1)(x1,y1)和(x2,y2)(x_2, y_2)(x2,y2)而言,其距离为:
d=(x1−x2)2+(y1−y2)d=\sqrt{(x_1-x_2)^2+(y_1-y_2)}
d=(x1−x2)2+(y1−y2)
这么说可能有点抽象,我们现在举例说明,假设我们要做一个程序对电影进行分类,区分出这部电影是爱情电影还是动作电影。我们现在统计出了6部 ...
神经网络
0x00 神经元
神经元和感知器本质上时一样的,只不过神经元内部用的激活函数不是阶跃函数而是sigmoid或者tanh这样的激活函数。sigmoid函数的定义如下:
sigmoid(x)=11+e−x\text{sigmoid}(x)=\frac{1}{1+e^{-x}}
sigmoid(x)=1+e−x1
这是一个非线性函数,其值域为(0, 1),其导数为:
y′=y(1−y)y'=y(1-y)
y′=y(1−y)
我们先来看一个神经网络:
左边红色的一层为输入层,用于接收输入的数据,最右边绿色的一层为输出层,外界从此获取预测值的输出。二者之间即为隐藏层,对外部而言是不可见的。在上图中,w41w_{41}w41表示从神经元1到神经元4的权值,a4a_4a4表示隐藏层神经元4的输出,其余的标号同理可得。由此我们可以有如下神经网络输出的形成过程:
a4=sigmoid(w41x1+w42x2+w43x3+b4)a_4=\text{sigmoid}(w_{41}x_1+w_{42}x_2+w_{43}x_3+b_4)
a4=sigmoid(w41x1+w42x2 ...
感知器
0x00 感知器
感知器这个玩意其实蛮有意思,他能用来学习实现其他函数。一个感知器有3个部分组成:
输入权值和偏置项:学过机器学习的你一看可能就知道,这就是那个w和b。一个感知器可以接收多个输入,每个输入上都有一个权值
激活函数:比如机器学习中常用的sigmoid函数,一般记作f
输出
感知器的输出可以使用一个通用的函数来计算,如下:
y=f(wx+b)y=f(wx+b)
y=f(wx+b)
看到这儿不要慌,下面我们通过一个简单的例子来简述感知器的训练过程。
0x01 感知器的训练
感知器的训练过程,其实就是设法通过不断地调整w和b的值,以求拟合我们输入和输出的数据。而调整的方式也很简单,如下:
wi←wi+Δwib←b+Δb\begin{align*}
w_i&\gets w_i+\Delta w_i \\
b&\gets b+\Delta b
\end{align*}
wib←wi+Δwi←b+Δb
其中:
Δwi=η(t−y)xiΔb=η(t−y)\begin{align*}
\Delta w_i&=\eta(t-y)x_i \\
\De ...
过拟合问题
0x00 过拟合问题
欠拟合根本原因在于特征维度过少、模型过于简单,拟合的函数无法满足训练集,导致误差较大。过拟合的根本原因则是在于特征维度过多、模型过于复杂、参数过多、训练的数据过少、噪声过多,导致拟合的函数完美预测训练集,在引入新的测试集时预测结果差。
产生过拟合的根本原因可以归咎于如下几点:
数据太少,无法描述问题的真实分布。比如投硬币,连续投了10次都是正面,这样的数据机器学习了以后是无法揭示投硬币的统计规律的。由此,当数据量无限大的时候,过拟合的问题即不存在。
观察值与真实值存在偏差。比如人脸识别系统,不同的人在不同的地方识别背景总是不一样的吧。如果你在选择训练集的时候,这批人脸的拍照背景都相同,那这个背景就可以看做是在样本选择时引入的一个随机误差了。再次使用相同背景拍照的人脸可能能正确识别出来,而换一个背景识别可能误差就会变得比较大。
数据有噪声。如果噪声过多,过拟合的情况下会尽量覆盖噪声点,这样会导致训练集的误差极小,但是换到测试集就会产生较大的误差。
过拟合实例,以上述的MNIST代码改造而成:
12345678910111213141516171819202122 ...