会打代码的扫地王大爷

CS Uncle


  • Home

  • Categories

  • Archives

  • Tags

  • About

基于tensorflow的MNIST手写数字识别(三)--神经网络篇

Posted on 2017-06-13 | In MachineLearning

想想还是要说点什么

    抱歉啊,第三篇姗姗来迟,确实是因为我懒,而不是忙什么的,所以这次再加点料,以表示我的歉意。废话不多说,我就直接开始讲了。

加入神经网络的意义

  •     前面也讲到了,使用普通的训练方法,也可以进行识别,但是识别的精度不够高,因此我们需要对其进行提升,其实MNIST官方提供了很多的组合方法以及测试精度,并做成了表格供我们选用,谷歌官方为了保证教学的简单性,所以用了最简单的卷积神经网络来提升这个的识别精度,原理是通过强化它的特征(比如轮廓等),其实我也刚学,所以能看懂就说明它确实比较简单。
    •     我的代码都是在0.7版本的tensorflow上实现的,建议看一下前两篇文章先。

流程和步骤

    其实流程跟前面的差不多,只是在softmax前进行了卷积神经网络的操作,所也就不仔细提出了,这里只说卷积神经网络的部分。
如第一篇文章所说,我们的卷积神经网络的,过程是卷积->池化->全连接.

1
2
3
4
5
6
7
8
9
10
11
# 卷积函数
# convolution
def conv2d(x, W):
return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')
#这里tensorflow自己带了conv2d函数做卷积,然而我们自定义了个函数,用于指定步长为1,边缘处理为直接复制过来



# pooling
def max_pool_2x2(x):
return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')

tf.nn.conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None, name=None)

Computes a 2-D convolution given 4-D input and filter tensors.

Given an input tensor of shape [batch, in_height, in_width, in_channels] and a filter / kernel tensor of shape [filter_height, filter_width, in_channels, out_channels], this op performs the following:

Flattens the filter to a 2-D matrix with shape [filter_height filter_width in_channels, output_channels].

Extracts image patches from the the input tensor to form a virtual tensor of shape [batch, out_height, out_width, filter_height filter_width in_channels].

For each patch, right-multiplies the filter matrix and the image patch vector.
In detail,

output[b, i, j, k] =
sum_{di, dj, q} input[b, strides[1] i + di, strides[2] j + dj, q] *
filter[di, dj, q, k]

Must have strides[0] = strides[3] = 1. For the most common case of the same horizontal and vertices strides, strides = [1, stride, stride, 1].

Args:

input: A Tensor. Must be one of the following types: float32, float64.

filter: A Tensor. Must have the same type as input.

strides: A list of ints. 1-D of length 4. The stride of the sliding window for each dimension of input.

padding: A string from: “SAME”, “VALID”. The type of padding algorithm to use.

use_cudnn_on_gpu: An optional bool. Defaults to True.

name: A name for the operation (optional).

Returns:

A Tensor. Has the same type as input.

####

tf.nn.max_pool(value, ksize, strides, padding, name=None)

Performs the max pooling on the input.

Args:

value: A 4-D Tensor with shape [batch, height, width, channels] and type float32, float64, qint8, quint8, qint32.

ksize: A list of ints that has length >= 4. The size of the window for each dimension of the input tensor.

strides: A list of ints that has length >= 4. The stride of the sliding window for each dimension of the input tensor.

padding: A string, either ‘VALID’ or ‘SAME’. The padding algorithm.

name: Optional name for the operation.

Returns:

A Tensor with the same type as value. The max pooled output tensor.

1
2
3
4
5
6
7
8
9
10
初始化权重和偏置值矩阵,值是空的,需要后期训练。

def weight_variable(shape):
initial = tf.truncated_normal(shape, stddev=0.1)
return tf.Variable(initial)

def bias_variable(shape):
initial = tf.constant(0.1, shape = shape)
# print(tf.Variable(initial).eval())
return tf.Variable(initial)
1
2
3
4
5
6
#这是做了两次卷积和池化
h_conv1 = tf.nn.relu(conv2d(x_image, w_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)

h_conv2 = tf.nn.relu(conv2d(h_pool1, w_conv2) + b_conv2)
h_pool2 = max_pool_2x2(h_conv2)
1
2
3
这里是做了全连接,还用了relu激活函数(RELU在下面会提到)
h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, w_fc1) + b_fc1)
1
2
#为了防止过拟合化,这里用dropout来关闭一些连接(DROP下面会提到)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

然后得到的结果再跟之前的一样,使用softmax等方法训练即可得到参数。

RELU激活函数

激活函数有很多种,最常用的是以下三种

Sigmoid

将数据映射到0-1范围内

公式如下

这里写图片描述

####函数图像如下
函数图像

Tanh

将数据映射到-1-1的范围内

公式如下

这里写图片描述

函数图像如下
这里写图片描述

RELU

小于0的值就变成0,大于0的等于它本身

函数图像

这里写图片描述

具体的参考这个http://blog.csdn.net/u012526120/article/details/49149317

###dropout的作用

  • 以前学习数学我们常用到一种方法,叫做待定系数法,就是给定2次函数上的几个点,然后求得2次函数的参数。

  • 一样的道理,我们这里用格式训练集训练,最后训练得到参数,其实就是在求得一个模型(函数),使得它能跟原始数据的曲线进行拟合(说白了,就是假装原始数据都在我们计算出来的函数上)

  • 但是这样不行啊,因为我们还需要对未知数据进行预测啊,如果原始的数据点都在(或者大多数都在)函数上了(这就是过拟合),那会被很多训练数据误导的,所以其实只要一个大致的趋势函数就可以了

  • 所以Dropout函数就是用来,减少某些点的全连接(可以理解为把一些点去掉了),来防止过拟合

具体的看这个http://www.cnblogs.com/tornadomeet/p/3258122.html

代码

  • 水完了,看代码吧,注释上有写一些变量的维度,大家可以一步步地看过去,计算过去
  • https://github.com/wlmnzf/tensorflow-train/blob/master/mnist/cnn_mnist.py

基于tensorflow的MNIST手写数字识别(二)--入门篇

Posted on 2017-06-13 | In MachineLearning

一、本文的意义

    因为谷歌官方其实已经写了MNIST入门和深入两篇教程了,那我写这些文章又是为什么呢,只是抄袭?那倒并不是,更准确的说应该是笔记吧,然后用更通俗的语言来解释,并且补充更多,官方文章中没有详细展开的一些知识点,不过建议与官方文章结合着阅读。

    另外是代码部分的改动,官方的demo只提供了验证精确度,我将它改造成了能输入并预测输出结果的代码也就是说是一个从准备待测图片到最终是别的一个完整demo

中文版本:MNIST机器学习入门
http://wiki.jikexueyuan.com/project/tensorflow-zh/tutorials/mnist_beginners.html

    需要识别的图片放到test_num里,然后运行mnist_softmax.py就好了

    demo截图如下,会将放进去的图片预测,然后输出结果,代码说明请看github的readme(最底下)

这里写图片描述

二、MNIST简介

官网:http://yann.lecun.com/exdb/mnist/

    这个MNIST数据库是一个手写数字的数据库,它提供了六万的训练集和一万的测试集。
它的图片是被规范处理过的,是一张被放在中间部位的28px*28px的灰度图

总共4个文件:\
train-images-idx3-ubyte: training set images \
train-labels-idx1-ubyte: training set labels \
t10k-images-idx3-ubyte: test set images \
t10k-labels-idx1-ubyte: test set labels\

    图片都被转成二进制放到了文件里面,
所以,每一个文件头部几个字节都记录着这些图片的信息,然后才是储存的图片信息

TRAINING SET LABEL FILE (train-labels-idx1-ubyte):

[type] [value] [description]
1
2
3
4
5
6
0000     32 bit integer  0x00000801(2049) magic number (MSB first) 
0004 32 bit integer 60000 number of items
0008 unsigned byte ?? label
0009 unsigned byte ?? label
........
xxxx unsigned byte ?? label

The labels values are 0 to 9.

TRAINING SET IMAGE FILE (train-images-idx3-ubyte):

[type] [value] [description]
1
2
3
4
5
6
7
8
0000     32 bit integer  0x00000803(2051) magic number 
0004 32 bit integer 60000 number of images
0008 32 bit integer 28 number of rows
0012 32 bit integer 28 number of columns
0016 unsigned byte ?? pixel
0017 unsigned byte ?? pixel
........
xxxx unsigned byte ?? pixel

每个像素被转成了0-255,0代表着白色,255代表着黑色。

TEST SET LABEL FILE (t10k-labels-idx1-ubyte):

[type] [value] [description]
1
2
3
4
5
6
0000     32 bit integer  0x00000801(2049) magic number (MSB first) 
0004 32 bit integer 10000 number of items
0008 unsigned byte ?? label
0009 unsigned byte ?? label
........
xxxx unsigned byte ?? label

The labels values are 0 to 9.

TEST SET IMAGE FILE (t10k-images-idx3-ubyte):

1
2
3
4
5
6
7
8
9
[offset] [type]          [value]          [description] 
0000 32 bit integer 0x00000803(2051) magic number
0004 32 bit integer 10000 number of images
0008 32 bit integer 28 number of rows
0012 32 bit integer 28 number of columns
0016 unsigned byte ?? pixel
0017 unsigned byte ?? pixel
........
xxxx unsigned byte ?? pixel

每个像素被转成了0-255,0代表着白色,255代表着黑色。

三、tensorflow手写数字识别的大致步骤

  1. 将要识别的图片转为灰度图,并且转化为28*28矩阵(单通道,每个像素范围0-255,0为黑色,255为白色,这一点与MNIST中的正好相反)
  2. 将28*28的矩阵转换成1维矩阵(也就是把第2,3,4,5….行矩阵纷纷接入到第一行的后面)
  3. 用一个1*10的向量代表标签,也就是这个数字到底是几,举个例子e数字1对应的矩阵就是[0,1,0,0,0,0,0,0,0,0]
  4. softmax回归预测图片是哪个数字的概率
  5. 用交叉熵和梯度下降法训练参数

四、过程讲解

4.1 准备要识别的图片

    这个部分其实是比较重要的,因为如果处理不得当可能并不一定会有很好的结果,所以按照mnist的标准规范需要将待测图片转为28×28且文字居中的灰度图(其实彩色的也可以,不过就是最后代码需要改一下),目前介绍两种获得待测图片的方法:

  1. 自己用ps或者真的手写一些数字
  2. 将MNIST数据库中的二进制转化成图片,然后用来做测试
    ps:图片解析  点击进入
    
4.2 将待测图片转换为矩阵

    如图所示,根据黑色部分的浓淡将其转化成微一个浮点数的数组,(白色0,黑色1)

    看到这里,如果你跟我一样不熟悉python,是不是开始方了,没事,其实python很厉害,自带的PIL图片库一句话就可以搞定

1
img=array(Image.open(filename))         //打开然后就被numpy转化了

    如果是彩色的图片,则需要先将它这样子转换一下(我当初并不知道可以转化,傻不垃圾地自己写了一个转化,所以python还是好好学习啊)

1
Lim  = img=array(im.convert("L"))

4.3将矩阵转化为一维矩阵,以及标签的介绍

    转化为一维的矩阵其实并不难,用python的reshape就能搞定,还是要讲一下标签的表示方法,这个曾经令队友疑惑不久,直到我把这个数组打印出来

4.3.1标签的来历–有监督学习 和 无监督学习

监督学习:利用一组已知类别的样本调整分类器的参数,使其达到所要求性能的过程,也称为监督训练或有教师学习

     举个例子,MNIST自带了训练图片和训练标签,每张图片都有一个对应的标签,比如这张图片是1,标签也就是1,用他们训练程序,之后程序也就能识别测试集中的图片了,比如给定一张2的图片,它能预测出他是2

无监督学习:其中很重要的一类叫聚类

     举个例子,如果MNIST中只有训练图片,没有标签,我们的程序能够根据图片的不同特征,将他们分类,但是并不知道他们具体是几,这个其实就是“聚类”

4.3.2 标签的表示

    在这里标签的表示方式有些特殊,它也是使用了一个一维数组,而不是单纯的数字,上面也说了,他是一个一位数组,0表示方法[1,0,0,0,0,0,0,0,0,0],1表示[0,1,0,0,0,0,0,0,0,0],………, 主要原因其实是这样的,因为softmax回归处理后会生成一个1*10的数组,数组[0,0]的数字表示预测的这张图片是0的概率,[0,1]则表示这张图片表示是1的概率……以此类推,这个数组表示的就是这张图片是哪个数字的概率(已经归一化),因此,实际上,概率最大的那个数字就是我们所预测的值。两者对应来看,标准的标签就是表示图片对应数字的概率为100%,而表示其它数字的概率为0,举个例子,0表示[1,0,0,0,0,0,0,0,0,0],可以理解为它表示0的概率为1,而表示别的数字的概率为0.

4.4 softmax回归

    这是一个分类器,可以认为是Logistic回归的扩展,Logistic大家应该都听说过,就是生物学上的S型曲线,它只能分两类,用0和1表示,这个用来表示答题对错之类只有两种状态的问题时足够了,但是像这里的MNIST要把它分成10类,就必须用softmax来进行分类了。

    P(y=0)=p0,P(y=1)=p1,p(y=2)=p2……P(y=9)=p9.这些表示预测为数字i的概率,(跟上面标签的格式正好对应起来了),它们的和为1,即 ∑(pi)=1。

     tensorflow实现了这个函数,我们直接调用这个softmax函数即可,对于原理,可以参考下面的引文,这里只说一下我们这个MNIST demo要用softmax做什么。

(注:每一个神经元都可以接收来自网络中其他神经元的一个或多个输入信号,神经元与神经元之间都对应着连接权值,所有的输入加权和决定该神经元是处于激活还是抑制状态。感知器网络的输出只能取值0或1,不具备可导性。而基于敏感度的训练算法要求其输出函数必须处处可导,于是引入了常见的S型可导函数,即在每个神经元的输出之前先经过S型激活函数的处理。)

4.5 交叉熵

    通俗一点就是,方差大家都知道吧,用它可以衡量预测值和实际值的相差程度,交叉熵其实也是一样的作用,那为什么不用方差呢,因为看sigmoid函数的图像就会发现,它的两侧几乎就是平的,导致它的方差在大部分情况下很小,这样在训练参数的时候收敛地就会很慢,交叉熵就是用来解决这个问题的,它的公式是 ,其中,y 是我们预测的概率分布, y’ 是实际的分布。

4.6 梯度下降

    上面那步也说了,有个交叉熵,根据大伙对方差的理解,值越小,自然就越好,因此我们也要训练使得交叉熵最小的参数,这里梯度下降法就派上用场了,这个解释见上一篇系列文章吧,什么叫训练参数呢,可以想象一下,我们先用实际的值在二位坐标上画一条线,然后我们希望我们预测出来的那些值要尽可能地贴近这条线,我们假设生成我们这条线的公式ax+ax^2+bx^3+…..,我们需要生成这些系数,要求得这些系数,我们就需要各种点代入,然后才能求出,所以其实训练参数跟求参数是个类似的过程。

4.7 预测

    训练结束以后我们就可以用这个模型去预测新的图片了,就像我们已经求出来了方程,以后只要随意输入一个x,就能求出对应的y。

5 代码

https://github.com/wlmnzf/tensorflow-train/tree/master/mnist

6 参考文章

http://blog.csdn.net/acdreamers/article/details/44663305 softmax回归

http://wiki.jikexueyuan.com/project/tensorflow-zh/tutorials/mnist_beginners.html MNIST学习入门

http://blog.csdn.net/u012162613/article/details/44239919 交叉熵代价函数

基于tensorflow的MNIST手写数字识别(一)--白话卷积神经网络模型

Posted on 2017-06-13 | In MachineLearning

一、卷积神经网络模型知识要点

  1. 卷积
  2. 池化
  3. 全连接
  4. 梯度下降法
  5. softmax

    本次就是用最简单的方法给大家讲解这些概念,因为具体的各种论文网上都有,连推导都有,所以本文主要就是给大家做个铺垫,如有错误请指正,相互学习共同进步。

二、卷积神经网络讲解

##### 2.1卷积神经网络作用

     大家应该知道大名鼎鼎的傅里叶变换,即一个波形,可以有不同的正弦函数和余弦函数进行叠加完成,卷积神经网络也是一样,可以认为一张图片是由各种不同特征的图片叠加而成的,所以它的作用是用来提取特定的特征,举个例子,比如给定一张图片,然后我只想提取它的轮廓,于是就需要卷积神经网络。

2.2卷积神经网络模型

    如图是大名鼎鼎的LeNet-5(识别数字的卷积网络),效果和论文在此,这里拿出来只是为了说明一下卷积神经网络的模型,就像图中那样,经过多次,卷积,池化(又叫子采样),然后全连接,就完工了。

2.3 卷积

2.3.1 卷积的原理

    其实卷积很好理解,左侧绿色的部分的55矩阵其实一般就是我们输入的图片的灰度值(可以想象成一张5px5px的黑白照片,然后把黑白照片上的每一个点转化成矩阵上的每一个元素),然后上面的黄色部分矩阵就是我们的过滤器,用来提取特征,(其实应该叫滤波器或者卷积核),让卷积核在输入矩阵上进行从左到右,从上到下滑动,然后每一次滑动,两个矩阵对应位置的元素相乘然后求和,就是右边那个矩阵的一个元素。

2.3.2 滑动的步长-stride

    上面那张图片从左到右,每次滑动的时候只移动一格,但是其实它一次滑动多格,这就是步长

2.3.3 卷积的边界处理-padding

    如上图所示,卷积后的矩阵只有3*3,比原来的图片要小了,因为边界没有了,所以要考虑这个边界的问题,网上说卷积的边界处理有两种方式:

  1. 丢掉边界,也就是就按右边那个缩小的矩阵来。
  2. 复制边界,也就是把左边的最外层原封不动地复制过去

    但是在看matlab代码和tensorflow代码的时候发现并不是那么简单的事情。

matlab中conv2这个“padding”参数可以设为三个值FULL,SAME,VALID

tensorflow中conv2d的”padding”参数可以设为两个值SAME,VALID

    它们对边界是这样处理的,对输入的矩阵,包裹n层0,然后再按照上面所说的卷积方法进行卷积,这个n怎么求呢,

FULL: edge_row = kernel_row - 1; edge_cols = kernel_cols - 1;

SAME: edge_row = (kernel_row - 1) / 2; edge_cols = (kernel_cols - 1) / 2;

VALID:edge_row = edge_cols = 0;

    edge_row就是边的行数,kernel_row就是卷积核的行数,所以上面讲的其实就是VALID模式

2.3.4 卷积与神经网络

    右下角就是卷积的数学公式,矩阵的对应元素相乘求和,然后加上一个偏置值

2.4 池化

    池化分为两种,一种是最大池化,在选中区域中找最大的值作为抽样后的值,另一种是平均值池化,把选中的区域中的平均值作为抽样后的值,这样做的,原因是为了后面全连接的时候减少连接数

2.5 全连接

    左边的是没有没有进行卷积的全连接,假设图片是10001000的,然后用1M的神经元去感知,最后需要10^12个权值作为参数,右边是经过卷积过的,每个圆点是一个神经元,因此只是用一个卷积核的话,其实只要10010^6,数量级就大大减少,而且因为提取的就是所需的特征,所以在加快训练速度的时候对结果并不会产生过大的影响,甚至更为精确。

2.6 梯度下降法


    可能很多人会问,那个卷积核是怎么得出来的呢,其实它是被各种训练集训练出来的,利用梯度下降法使得我们的参数到达最优解。

    梯度下降法可以这样子理解,假设我们正在下山,要使得下山的路径达到最短,于是我们每走一步之前就判断一下四面八方从哪个方向跨出这一步会最短,不过学过算法的人应该都知道,有个问题就是,我们当前走的这一步是当前位置最短的,但是真正从山上到山下最短路径可能并不路过这一步。也就是说这是个局部最优解,而不是全局最优解,我们得到的路径并不一定是最短的,但是也足够优秀,原因就是,得到最优解费时费力,性价比并不高。这一个知识点还是建议大家伙去看一下斯坦福Andrew Ng的《机器学习》,然后就能理解上面所说的权值参数要少的意义了。

2.7最后 softmax

    softmax是分类用的,说直白一点就是归一化,因为这个店最好跟例子结合起来,所以暂时不多说,感兴趣的可以去网上找,也可以关注后面的系列文章。

三、总结

    其实感觉讲的并不深入,因此还是希望各位能自己去仔细钻研一下,这里给各位一些基础吧,读起论文和数学公式来会更轻松一些。

四、参考

神经网络介绍
http://ufldl.stanford.edu/wiki/index.php/%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C

技术向:一文读懂卷积神经网络CNN
http://www.cnblogs.com/nsnow/p/4562308.html

深度学习(卷积神经网络)一些问题总结
http://blog.csdn.net/nan355655600/article/details/17690029

卷积神经网络(CNN)
http://ibillxia.github.io/blog/2013/04/06/Convolutional-Neural-Networks/

Deep Learning模型之:CNN卷积神经网络(一)深度解析CNN
http://www.cnblogs.com/nsnow/p/4562363.html

数据挖掘系列(10)——卷积神经网络算法的一个实现(转)
http://blog.sina.com.cn/s/blog_4ff49c7e0102vl5m.html

Matlab/DeepLearnToolbox
https://github.com/rasmusbergpalm/DeepLearnToolbox

Deep Learning论文笔记之(四)CNN卷积神经网络推导和实现
http://blog.csdn.net/zouxy09/article/details/9993371

Deep Learning论文笔记之(五)CNN卷积神经网络代码理解
http://blog.csdn.net/zouxy09/article/details/9993743

斯坦福 池化
http://ufldl.stanford.edu/wiki/index.php/%E6%B1%A0%E5%8C%96

CNN神经网络层次分析
http://blog.csdn.net/liulina603/article/details/44915905

深度学习笔记1(卷积神经网络)
http://blog.csdn.net/lu597203933/article/details/46575779

CNN公式推导
http://blog.csdn.net/lu597203933/article/details/46575871

前向型神经网络之BPNN(附源码)
http://blog.csdn.net/heyongluoyao8/article/details/48213345

残差与误差的区别
http://wenku.baidu.com/link?url=DUDkyV1tnD_SEGzgcxb9AaFU5VUcP9ISNR8q39-fpCcq_LGUHY7ucx5vDwr-MCfU_ofr7yIQZ_UgTfiivTtaDOulW2DD3pGs07eYmiQv5P7

反向传导算法
http://deeplearning.stanford.edu/wiki/index.php/%E5%8F%8D%E5%90%91%E4%BC%A0%E5%AF%BC%E7%AE%97%E6%B3%95

图像卷积与滤波的一些知识点
http://blog.csdn.net/zouxy09/article/details/49080029

CNN卷积神经网络原理简介+代码详解
http://doc.okbase.net/u012162613/archive/126058.html

卷积神经网络(lenet)
http://deeplearning.net/tutorial/lenet.html

激活函数的作用
https://www.zhihu.com/question/22334626

神经网络入门第一部分
http://blog.sina.com.cn/s/blog_6a67b5c50100tspb.html

神经网络入门第二部分
http://blog.sina.com.cn/s/blog_6a67b5c50100tspe.html

卷积神经网络全面解析
http://www.moonshile.com/post/juan-ji-shen-jing-wang-luo-quan-mian-jie-xi

Deep learning:四十一(Dropout简单理解)
http://www.cnblogs.com/tornadomeet/p/3258122.html

DeepLearning (六) 学习笔记整理:神经网络以及卷积神经网络
http://www.07net01.com/2015/11/963741.html

深度卷积网络CNN与图像语义分割
http://blog.csdn.net/xiahouzuoxin/article/details/47789361

MATLAB conv2卷积的实现
http://blog.csdn.net/celerychen2009/article/details/38852105


William
会打代码的扫地王大爷
wlmnzf
中科院信工所
IIE
CAS
csuncle
丁洁的男朋友
丁洁的人
王立敏

自定义表单(完)

Posted on 2017-06-11 | In WebDesign

一、实现的功能

1、拖拽插入表单元素,从右边拖拽如左边的表单区

2、拖拽删除表单元素,从左边的表单区拖到右边的区域就会删除

3、拖拽交换位置,左边的表单元素上下拖拽可以交换位置

二、关键部分实现思路

2.1拖拽插入
请输入图片描述

1、在ondragstart的时候先区分是要拖拽请插入还是拖拽改变顺序(因为绑定的是同一个函数),插入记flag为1,改变顺序记flag为2,然后把正在拖拽的元素记录下来就算完成准备工作了

2、在ondragover中,因为要显示一根线来提示用户这个正在拖拽的表单元素会被放在哪里,因此就需要计算

这个黑色的就是浏览器页面,白色的是表单元素调用getBoundingClientRect()获取的最小覆盖矩形区,他会返回一个对象,其中top表示白色部分的距离(上边界到浏览器顶部的距离),bottom表示黄色线 距离。我们可以获取鼠标在浏览器页面上的坐标,然后判断鼠标正在哪个表单元素里。然后再对这个元素对半分,在粉色上面部分,则对其上边界标记为蓝色,否则就是下部分标记为蓝色。

3、ondrop中,根据上一步判断的位置,放置拖拽的元素,如果是新增,则克隆一份,再插入,是替换的话则直接插进去就可以了。

2.2拖拽删除

其实原理差不多,只不过在ondrop中绑定的函数中,是把正在拖拽的那个元素删掉就可以了。代码中有很多注释,这里就不一一讲解了。

3、代码

https://github.com/wlmnzf/javascript-train/tree/master/customForm

自定义表单(二)

Posted on 2017-06-11 | In WebDesign

一、瞎扯

最近在折腾人工智能,今天写了段tensorflow,用来分辨手写字体的图片,跑的时间有点久,所以就跑回来跟前端玩耍了,其实代码早就写好了,只是补上文章。

二、Html5原生拖拽介绍

Html5的很多特性十分激动人心,比如这里的拖拽功能,还有websockeet,从此网页聊天程序就能更轻松的编写出来,再有就是canvas,于是撼动了flash长久的统治地位,H5还开始进入手机APP领域,开始在制作APP的不归路上越走越远了。

H5的拖拽十分好用,玩过js拖拽的人知道,在那里,拖拽的效果什么的都需要自己实现,十分地麻烦和复杂,但是在H5中都予以了封装,连移动效果都有,相当不错,简化了开发,不过事实上,对于深入学习并不利,因此想要深入理解原理的小伙伴们建议去实现一下js版本的拖拽。

三、HTML拖拽实现

跟JS版本的原理一样,H5的拖拽也分为三个步骤,开始拖拽,拖拽时,拖拽后

前提:拖拽的元素要写上draggable=”true”的标签

1、拖拽元素的 ondragstart,里面写的代码表示开始拖拽的时候发生的事

2、拖放元素所处位置的ondragover,比如拖拽一个img到div上方额,就会触发div的这个事件

3、拖放元素所处位置的ondrop,里面写的代码表示放置后所触发的事件

很多人肯定会问,那该如何传递数据呢,这里H5也考虑到了,在这里可以通过dataTransfer来传递数据

四、dataTransfer的使用

这里借用W3CSCHOOL中的例子来说明,

functiondragStart(ev)

{

ev.dataTransfer.setData("Text",ev.target.id);

}

这里其实就是利用setData传递一个文本格式的参数(拖拽元素的id)

functiondrop(ev)

{

ev.preventDefault();

vardata=ev.dataTransfer.getData("Text");

ev.target.appendChild(document.getElementById(data));

}

这里则是通过getData来获取这个参数。大吃一惊了吧,方便到爆了。

因为这个html5拖拽很简单,因此本文其实着重想讲解一下这个dataTransfer。(其实也就是官方api上抄来的而已,哈哈哈哈,不要见怪,我也不敢瞎造啊)

五、dataTransfer API

Properties

DataTransfer.dropEffect

Gets the type of drag-and-drop operation currently selected or sets the operation to a new type. The value must be none copy link or move.

获取或者设置当前被选择元素的拖拽类型,它的值必须为none、copy、link、或者move

DataTransfer.effectAllowed

Provides all of the types of operations that are possible. Must be one ofnone,copy,copyLink,copyMove,link,linkMove,move,alloruninitialized.

提供所有可能的操作种类,必须是none,copy,copyLink,copyMove,link,linkMove,move,all或者uninitialized.中的一个。

DataTransfer.files

Contains a list of all the local files available on the data transfer. If the drag operation doesn’t involve dragging files, this property is an empty list.

包含一组可获取的本地文件列表,如果拖拽操作不包含文件,则这个文件列表将会是空的。这个属性超棒,很多拖拽上传功能就是这样子开发出来的

DataTransfer.items Read only

Gives aDataTransferItemListobject which is a list of all of the drag data.

只读,给定一个DataTransferItemList的对象,其中包含了一个所有拖拽数据的列表。

DataTransfer.types Read only

An array ofstringgiving the formats that were set in thedragstartevent.

只读,一组字符串数组,给定了在dragstart事件中设置的一组格式。

Methods

void dataTransfer.clearData([format]);

DataTransfer.clearData()

Remove the data associated with a given type. The type argument is optional. If the type is empty or not specified, the data associated with all types is removed. If data for the specified type does not exist, or the data transfer contains no data, this method will have no effect.

清除给定类别的数据,type这个参数是可选的,如果类别是空或者不明确,跟所有类别相关的数据都将清除掉,如果特定类别的数据不存在,或者dataTransfer不包含数据,则这个方法将没有任何效果。

DOM String dataTransfer.getData(format);

DataTransfer.getData()

Retrieves the data for a given type, or an empty string if data for that type does not exist or the data transfer contains no data.

取回给定类别的数据,如果给定类别的数据不存在或者dataTransfer不包含任何数据,则将返回一个空字符串。

DataTransfer.setData()

Set the data for a given type. If data for the type does not exist, it is added at the end, such that the last item in the types list will be the new format. If data for the type already exists, the existing data is replaced in the same position.

设置一个给定类别的数据,如果这个类别的数据不存在,则将被添加到末尾,因此这个类别的列表的最后一项将是一个新的格式,如果这个类别已经存在,则存在的数据将被取代为这个新的数据

void dataTransfer.setDragImage(img, xOffset, yOffset);

DataTransfer.setDragImage()

Set the image to be used for dragging if a custom one is desired.

设置拖拽的时候显示的图片(默认是拖拽元素的缩略图)

六、代码

https://github.com/wlmnzf/javascript-train/tree/master/customForm

七、感谢

1、MDN DataTransfer API

2.、W3CSCHOOL HTML5拖放

3、太兴奋的时候要听伤感的歌,感谢 网易云音乐 –《岛歌》

自定义表单(一)

Posted on 2017-06-11 | In WebDesign

一、瞎扯

之前公司放我一个礼拜写了一个高度定制化,功能完善的自定义表单,然而因为去年9月份,硬盘,u盘,存储卡接连坏掉,代码就没了,不过也多亏这个,避免了日后的官司问题(开个玩笑,此处捂着嘴偷笑),最近一直在恢复之前写过的一些重要的代码,因此顺便更深入地折腾一下这个问题,写一些不会惹官司的demo(这个还真的得严肃对待=_=||)。

二、JS拖拽

2.1原理

拖拽的过程其实只有三个:按下,移动,放开

分别对应JS中的三个事件:

——需拖拽部分的onmousedown

记录下点击时的鼠标位置信息,以及此时点击的节点的位置信息。

绑定document的onmousemove和onmouseup事件。

——Document的onmousemove

记录当前鼠标的位置信息,然后计算出位移值,让刚才选中的节点的坐标的left和top分别加上位移值即可移动到当前位置。

——Document的onmouseup

取消document的onmousemove和onmouseup事件。

然后就完成了一次拖拽。

2.2 JS的setCapture和releaseCapture

//    if (target.setCapture)

//    {

//        target.setCapture();

//    }
//    else if (window.captureEvents)

//    {

//        window.captureEvents(Event.MOUSEMOVE | Event.MOUSEUP);

//    }

我看网上的很多拖拽教程都是用了这两个函数,但是去掉之后依然很正常,就去查询了一下资料,谷歌了一下,在MDN上找到了API:

element.setCapture(retargetToElement);

retargetToElement:

If true, all events are targeted directly to this element;if false, events can also fire at descendants of this element.

参数retargetToElement:如果为true,则所有的事件将直接导向这个元素,如果为false,则同时也将指向派生自这个元素的元素(子元素)

function:Callthismethod during the handling of a mousedown event to retarget all mouse events tothiselement until the mouse button is released or document.releaseCapture() is called.

功能:在mousedown绑定的函数中调用这个函数将重定向所有鼠标事件到这个元素直到鼠标被释放或者调用了document.releaseCapture()

因为在这里我绑定的是document的onmousemove和onmouseup,整个页面中的鼠标事件也能得到响应,拖动速度快了,鼠标超出了拖拽元素依然能得到响应,因此setCapture其实也不需要。

2.3 移动算法

原先采用的算法是

请输入图片描述

黑色部分是浏览器页面,白色部分是拖拽元素,棕色部分是点击的圆点,黄色部分是是鼠标点击处在浏览器上的坐标位置(X,Y),红色部分则是鼠标点击处在拖拽元素中的偏移量(x,y),因此实际上要设置的top和left值是(X-x,Y-y),这里稍后还得普及一下各种位置信息的区别以及获取方法,不过这种方法的效果并不是很理想,所以就还整了新的方法,在mousedown时记录鼠标位置信息,然后在mousemove中,记录鼠标位移的变化值,把值直接加在拖拽元素的坐标上面就好了。

2.4 区分offsetX layerX PageX clientX 值

offsetX:

IE特有(新版本浏览器 除了ff都支持),鼠标相比较于触发事件的元素的位置,以元素盒子模型的内容区域的左上角为参考点,如果有boder,可能出现负值。

layerX:

FF特有,鼠标相比较于当前坐标系的位置,即如果触发元素没有设置绝对定位或相对定位,以页面为参考点,如果有,将改变参考坐标系,从触发元素盒子模型的border区域的左上角为参考点,

也就是当触发元素设置了相对或者绝对定位后,layerX和offsetX就幸福地生活在一起^-^,几乎相等,唯一不同就是一个从border为参考点,一个以内容为参考点

PageX和clientX ,这个两个比较容易搞混,

PageX:鼠标在页面上的位置,从页面左上角开始,即是以页面为参考点,不随滑动条移动而变化

clientX:鼠标在页面上可视区域的位置,从浏览器可视区域左上角开始,即是以浏览器的可视窗口为参考点,随滑动条移动 而变化.

IE中没有PageX,解决办法:

PageY=clientY+scrollTop-clientTop;

页面上的位置=可视区域位置+页面滚动条切去高度-自身border高度

三、源码

https://github.com/wlmnzf/javascript-train/tree/master/customForm
四、感谢

1.MDN-Element.setCapture()

2.各个浏览器中鼠标位置的属性 offsetX layerX PageX clientX (自由拖动框,防止文字选中)

3.JavaScript实现最简单的拖拽效果

123
Limin Wang

Limin Wang

26 posts
17 categories
58 tags
GitHub Twitter Scholar Linkedin
Links
  • Junyangz
  • Dch's Blog
  • Myth Blog
© 2020 Limin Wang
Powered by Hexo
Hosted by Coding Pages
Theme - NexT.Pisces