序列化与反序列化
0x00 序列化与反序列化 其实这应该是一对很简单的概念,但是有一些资料用了一些很高大上的语句和词汇,整得初学者往往很头大。简单来说,序列化的主要用途主要就是为了传递或保存整个对象。对象存储在内存中的,在传递过程中需要将其以某种形式表现出来,先举个简单的例子,比如将一个对象序列化为一个 JSON 字符串的形式。 12345public class Person {private String name; // ="Qun" private int age; // =22 // 省略 getter, setter} 序列成 JSON 字符串就成为了: 1234 {"name":"Qun", "age":22} 这个 JSON 字符串的形式在网络上传递就很方便了,也可以将其保存成一个 JSON 文件做持久化存储,这其实就是序列化的意义。同样,我也可以通过这个 JSON 字符串来还原回一个 Person 对象,这也就是反序列化的意义。 注意:如果一...
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\\[1em] 100\\[1em] 50 \end {matrix} \...
循环神经网络
0x00 循环神经网络 循环神经网络(Recurrent Neural Network)适用于学习和处理成序列的信息,其最早在自然语言处理领域被应用,因其可以对语言进行建模。这是个很有意思的话题,来看一个例子: 我昨天上学迟到了,老师批评了____。 我们让电脑预测空白处应填什么词,初期的算法,语言模型处理主要使用 N-Gram 算法,N 是一个数字,它的含义是假设一个词出现的频率,只与前面 N 个词有关。比如对于上述的例子使用 3-Gram 算法,即假设空白处所推测的值只与词组『批评了』相关,于是电脑就会在词库中搜索,词组『批评了』后面最有可能出现的词是什么,然后进行填充。但是这样是远远不够的,因为我们真正要填的词『我』,在整句话的开头,词组『批评了』的大前方。这个时候,我们可以通过提升 N 的值,来做更让电脑作出精确的预判,但是这无疑会增加电脑计算时的工作量。 由此,引入了循环神经网络 RNN,因为 RNN 可以一次往前或往后看任意多个的词。 0x01 RNN 的结构 一个循环神经网络往往由输入层、一个隐藏层和一个输出层组成,如图: 这个网络在 t 时刻收到输入 xtx_txt之后,隐藏层的值为 st...
重新认识优化器
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) 这么说可能有点抽象,我们现在举例说明,假设我们要做一个程序对电影进行分类,区分出这部电影是爱情电影还是动作电影。我们现在统计出...
神经网络
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+w42...
感知器
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 \\[1em] 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...
