• tensorflow笔记



    一、tensorflow基础

    1.tensorflow的数据类型

    自从tensorflow更新到2.0以后,他变得和常规的python非常相似,那我们就简单列举一些常用的数据类型。

    首先,对于tensorflow来说,他有标量scaler;向量vector[…];矩阵matrix[[],[]],他们都可以被称为是一个tensor:张量

    其实,看到这些,我们或许有些疑惑,这些东西在numpy中,不都有差不多吗?确实,tensorflow可以说是numpy的针对于深度学习的增强版,它包含了很多深度学习常用的函数,并且能够支持GPU加速,这将会大大缩短计算时间。

    一些需要注意的区别:

    1. 设备选择:with tf.device(‘CPU/GPU’),一般默认情况下是选择GPU进行加速。
    2. 在tensorflow中,他只能对于张量进行运算,,因此一定要将数据类型转化为张量:tf.convert_to_tensor(data,dtype),他能够将numpy类型转化为tensor。如果说已经是tensor类型,我们需要将其数据类型转化就需要用到:tf.cast()。
    3. 我们需要注意的是tensor他不是一个数据,如果打印出来为nan(not a number),因此对于需要计算更替内容的,就需要使用到tf.variable(),才能够进行更替。
    4. 但是还有一个神奇的地方,就是计算返回出来以后,它就成为了一个张量,就不再能够参与计算了,因此我们就需要将其进行原地更替,保持它的variable类型:例如:assign_sub(),括号内为被减量,他出来的依旧是variable类型,能够继续参与计算更新。

    2.如何创建tensor

    1. 在第一章中,我们已经看到了有将numpy转化为tensor的函数.convert_to_tensor,这是最常用的一种方法,我们能够用相对更容易地numpy生成一个我们想要的数据,再将其转化为tensor。
    2. 和numpy一样,tensorflow中也有zeros这样的函数:tf.zeros([]),其中方括号内为要生成的tensor的各个维度大小。
    3. 对于生成全零矩阵,还有一种就是tf.zeros_like(data):该函数能够得到一个与data类型维度相同的全零矩阵。
    4. 如果说我们想生成一个我们指定数字填充的张量呢:tf.fill([], c),该函数能够将指定数字c填充到特定的维度中。
    5. 对于上面这些僵硬的张量生成,我们在深度学习中,更频繁的会用到生成随机量:tf.random.normal([], mean=, stdder=),该函数能够生成指定平均,方差的正态分布的随机数。
    6. 我们会注意到对于正太分布来说,他的头尾两部分分布趋势平缓,这样的话就可能造成梯度消失的可能性,因此,我们就需要用到:tf.random.truncated_normal([], mean=, stdder=)。这样子出来的随机数就分布于正态分布的中间部分,缓解梯度消失。
    7. 当我们对于离散数据处理的时候,我们可能希望再进行一次打乱,希望增加数据的无关性,于是我们用到:tf.random.shuffle(),该函数能够返回打散后数据对应的索引值。
    8. 返回了索引值之后我们就能够通过tf.gather(data, indices)来重组数据顺序。不过,直接gather只能是同一纬度上的截取,如果说我想要截取某一维度下的某个向量或者标量就需要使用到gather_nd,进行获取。
    9. 还有一种比较麻烦一点的是用布尔型,列出某一维度要取还是不取的内容:tf.boolen_mask(data, mask=[TRUE, FALSE…], axis=),其中mask的维度跟要取得那个维度相同。

    3.维度变化

    在tensor中最常使用的方法就是描述这个张量的维度,因此对于他来说[a, b, c],分别对应的是这个张量在这三个维度的大小,例如a代表了某一个属性的所有值,将其想象为坐标轴,因该就能够理解。

    在深度学习中,尤其是涉及到图像处理的过程中,对于图片我们也许会进行一些处理,例如最简单的神将网络我们就将像素点无差别的输入到输入层,如果是卷积神经网络,我们就会使用到卷积核,对原始的图片进行卷积处理,这个时候就保持原来图片的维度就行,或者说我们觉得正常的样本不够,需要增加一点不正常的,例如将图像反转。

    因此,首先对于第一种情况,我们就会使用到tf.reshape(data, [new shape]),需要注意的是,变换前后应保持相同,如果不想自己计算可以用-1代替,但只能使用一次。
    对于第二种问题就需要使用到tf.transpose(data, [indices]),这里的参数就是各个维度的排列顺序,应该要注意的是,样本有多少维度,这个参数的shape就有多大。

    在数据处理的过程中我们也许会想用一个新的维度,加在原有的样本中,为的是能够有个相对更有区分度的维度帮助算法更好地实现功能,又或者说两个维度的相关性太大,就取出一个不再使用,因此我们就需要使用到:

    1. tf.expend_dims(data, axis=),其中加的位置就在axis指定的维度之前(正反都是)。
    2. tf.squeeze(data, axis=),这个就相对来说较为简单,指定那个维度就删得了。

    我们看到如果我们想扩大样本维度,就需要先扩大,但扩大后的维度里面是里面是空的,不理解为何要用它,不如直接用tile函数对于维度直接复制也能扩维。

    当然,tensor中有一个自动补齐维度的函数:tf.broadcast_to(data, [new shape]),这个方法在做运算的时候是自动的,当然也是有条件的,将维度右对齐,只有等于一的情况下才能自动补齐否则报错。它补齐后的效果就是,按某一维度扩大几倍,就将该维度复制几遍。

    4.基础数学运算

    2.0版本的tensorflow,他的一些基础的数学运算就和python一样,常规的没有变化,但有两个常用的运算符:

    1. 矩阵乘法:@或者tf.matmul()
    2. 求和:reduce_sum
    3. 求平均:reduce_mean
    4. 求最大最小:reduce_max/min

    这里有一个有意思的现象,就是为什么会有reduce这个东西?
    我们可以想象,求某一维度,最大最小或者平均,都会将该维度减小到1,有种缩维的意思,所以就有这个关键词reduce。

    5.常用的数据操作

    1. 合并与分割
      在数据处理的过程中,我们常常需要将数据集划分为训练集与测试集,这就需要在shuffle之后,然后按比例切割:tf.split(data, axis= , num_or_size_splits= ),该函数能够按我们想要的比例,对于指定的维度对数据进行切割。数据合并似乎不常用到,可能是对于生成batch后想要扩大batch?tf.concat([data1, data2], axis= )就能够按指定维度将两组数据合并起来。
    2. 张量排序
      在神经网络中,对于分类问题,我们得到的输出就是各个类别的可能性大小,那个可能性最大的就是我们想要的答案,因此就用到tf.math.top_k(data, k),该函数就能做到取出前k个最大的数并返回索引。至于k取什么,就需要参考具体项目,基于查全率与查准率,进行设定。
    3. 复制填充
      我们使用到这个情况一般大多都是在进行卷积时,我们希望原图像与经过卷积核后整成的数据集保持同一个维度,因此,就可能需要我们人为给样本扩大一点维度,因为卷积过后得到的数据维度通常来说是小于等于原数据的,于是我们就要使用到:tf.pad(data, [[1, 1], [1, 2]]),我们看到其中的参数,大方括号中的每一个小方括号代表每一个维度,小方括号中的两个数分别代表是否在该维度两侧扩容,以及需要外扩多少。

    6.张量限幅

    这个东西有点不知道具体有什么用,就是如果说用作激活函数,那也有专门的激活函数的接口可以使用,对于向量的等比缩放或许会有一点用,因为如果只是按照一个具体的学习率去更新,会出现,某些维度的梯度相对巨大,某些又相对巨小,如果采用固定值,可能使小的更更更小,极端情况下或许会出现梯度消失的情况。又或者说,梯度都巨大,乘上学习率也巨大,就会造成更新过度的情况。所以一个相对有用的范数裁剪函数为:tf.clip_by.global_norn(grad, k),其中k为整体的模值,它会返回新的梯度以及总的模值。

    二、numpy与tensorflow对于神将网络的差别

    0.机器学习的一般流程

    当我们拿到一个具体的任务的时候,我们首先需要做如下几个步骤:

    1. 拿到数据,数据越多,质量越高,那么结果可能就会越好
    2. 处理数据。比如说:缺失值,异常值。再比如说离散数据的独热编码
    3. 根据任务要求以及数据特征选取适合的算法
    4. 根据loss函数调优

    1.基于numpy编写的神经网络

    我们就复习一遍最简单的手写数字识别。
    首先,我们要做的是获得这个mnist数据集,我们假装现在我们已经获得数据了,然后我们就可以开始进行下一步骤:代码

    # 首先我们需要导入一些常用的库
    import numpy
    import scipy.special
    import matplotlib.pyplot as pp
    
    #因为在编程中要么将函数写在开头,要么先声明,后面再构建,这里省事就先写函数了
    class nn:
    	def _init_(self, inputnodes, hiddennodes, outnodes, learningrate):
    		self.inodes = inputnodes
    		self.hnodes = hiddennodes
    		self.onodes = outnodes
    		self.lr = learningrate
    		#设置完基础的框架后还需要设定一个初始化权重,因为后续会迭代更新所以随机取就行
    		self.wih = numpy.random.normal(0, pow(self.hnodes, -0.5), (self.hnodes, self.inodes))
    		self.who = numpy.random.normal(0, pow(self.onodes, -0.5), (self.onodes, self.hnodes))
    		#最后就是需要用到的激活函数
    		self.activiation = lambda x:scipy.expict(x)
    		pass
    	# 设置完基本框架后,就可以加上训练与验证的功能了
    	def training(self, inputs, targets):
    		#在这里面我们需要把整个计算流程过一遍
    		#首先前向传播
    		input_data = numpy.array(inputs).T
    		target_data =numpy.array(targets).T
    		#其实,这个是数据转化,如果提前做了,也可以省去
    		#计算
    		hiden_input = numpy.dot(self.wih, input_data)
    		hiden_output = self.activation(hiden_input)
    		output_hiden = numpy.dot(self.who, hiden_output)
    		final_output = self.activation(output_hiden)
    		#计算误差
    		error = final_output - target_data
    		#反向传播
    		hiden_error = numpy.dot(self.who.T, error)
    		#梯度更新(这里的更新公式好似有点问题,但又能进行更新操作)
    		self.who += self.lr*numpy.dot((error*final_output*(1.0 - final_output)),hiden_outputs.T)
    		self.wih += self.lr*numpy.dot((hiden_error*hiden_output*(1.0 - hiden_output)),inputs.T)
    		pass
    	def query(self, inputs):
    		#这一部分就相对简单了只需要前向部分就行
    		input_data = numpy.array(inputs).T
    		#其实,这个是数据转化,如果提前做了,也可以省去
    		#计算
    		hiden_input = numpy.dot(self.wih, input_data)
    		hiden_output = self.activation(hiden_input)
    		output_hiden = numpy.dot(self.who, hiden_output)
    		final_output = self.activation(output_hiden)
    		return final_output
    
    
    #ok了,基础架构搭建完成,下面就进行补充内容
    i = 784
    h = 211
    o = 10
    l = 0.01
    neutralnet = nn(i, h, o, l)
    #加载数据集
    test_dataset = open('address', 'r')
    #保持只读状态,避免数据发生意外
    test_data_list = test_dataset.readlines()
    #也可以使用pandas直接read_csv,生成一个dataframe数据
    test_dataset.close()
    train_dataset = open('address', 'r')
    #保持只读状态,避免数据发生意外
    train_data_list = train_dataset.readlines()
    #也可以使用pandas直接read_csv,生成一个dataframe数据
    train_dataset.close()
    
    #读完数据后,就需要将数据导入函数 ,多训练几次能够提高正确率
    epoch = 5
    for i in range(epoch):
    	for step in train_data_list:
    		all_values = step.split(',')
    		#归一化
    		inputs = numpy.asarray(all_values[1:])/255.*0.99 + 0.01
    		targets = numpy.zeros(o) + 0.01
    		targets[int(all_values[0])] = 0.99      #书上说这么写是为了防止更新失败,但我感觉似乎问题不大
    		neutralnet.train(inputs, targets)
    		pass
    	pass
    #训练完就开始测试,和前面一样
    for step in train_data_list:
    	all_values = step.split(',')
    	#归一化
    	inputs = numpy.asarray(all_values[1:])/255.*0.99 + 0.01
    	out = neutralnet.query(inputs)
    	real_out = numpy.argmax(out)
    	print("the prediction is:", real_out)
    	#这个就是纯输出预测了
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89

    这段代码就是无GPU加速的手写数字识别,我们可以发现,对于这种小图像,最简单的三层神将网络已经能够获得足够高的准确率,但是非常的花费时间,占用很大的计算资源。并且,一些常规的例如求导计算,都需要我们事先计算完成,而且对于神经网络的架构需要我们自己去一点一点定义构建,而tensorflow就已经帮我们打包好了各个接口供我们直接使用。

    2.基于tensorflow编写的神经网络

    
    import tensorflow as tf
    from tensorflow import keras
    from tensorflow.keras import datasets
    import os
    # 这里还没有安装tensorflow
    
    os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
    # 只显示tensor报错的信息
    
    (x, y), _ = datasets.mnist.load_data()
    
    # 将其转化为tensor
    lr = 0.001
    x = tf.convert_to_tensor(x, dtype=tf.float32)/255.
    # 归一化
    y = tf.convert_to_tensor(y, dtype=tf.float32)
    # 之后在采用独热编码
    
    # 取batch
    train_db = tf.data.Dataset.from_tensor_slice((x, y)).batch(128)    # 将整个数据集依据128为单位划分为各个batch,但他实际上还是一整个
    # 通过迭代函数取batch
    train_iter = iter(train_db)
    sample = next(train_iter)
    # 在这里面sample就包含了128个样本和标签,tensor的数据类型会比较奇特
    print(sample[0].shape, sample[1].shape)
    
    # 划分完数据后,就可以创建一个最简单的多层神经网络
    # 1.初始化权重与偏执
    # 超级重要的一点,对于变量类型一定加个tf.variable进行封装
    w1 = tf.Variable(tf.random.truncated_normal([784, 256], stddev=0.1))     # 输入有784个元素,到中间层有256个神经元,数字随意自己换
    b1 = tf.Variable(tf.zeros([256]))
    w2 = tf.Variable(tf.random.truncated_normal([256, 128], stddev=0.1))
    b2 = tf.Variable(tf.zeros([128]))
    w3 = tf.Variable(tf.random.truncated_normal([128, 10], stddev=0.1))
    b3 = tf.Variable(tf.zeros([10]))
    
    
    for epoch in range(10):
        # 网络结构形成后就可以将数据带进去计算
        for step, (x, y) in enumerate(train_db):
            with tf.GradientTape() as tape:        # 要将参与梯度计算的内容包含在内
                # 因为输入节点是一维的,所以应当改变一下这个形状
                x = tf.reshape(x, [-1, 28*28])    # 这个-1 能够自己计算他这个维度应当是多少,但只能有一个
                # 然后就能够通过前向传播前往隐藏层
                h1 = x@w1 + b1
                # 经过激活函数常用relu
                h1 = tf.nn.relu(h1)
                h2 = h1@w2 + b2
                h2 = tf.nn.relu(h2)
                h3 = h2@w3 + b3
                # 最后一步一般不加
    
                # 计算误差
                y_onehot = tf.one_hot(y, depth=10)
                loss = tf.square(y_onehot-h3)
                loss = tf.reduce_mean(loss)
            # 梯度计算
            grads = tape.gradient(loss, [w1, b1, w2, b2, w3, b3])
            '''
            w1 = w1 - lr * grads[0]    # 索引和上面对应起来
            b1 = b1 - lr * grads[1]
            w2 = w2 - lr * grads[2]    # 索引和上面对应起来
            b2 = b2 - lr * grads[3]
            w3 = w3 - lr * grads[4]    # 索引和上面对应起来
            b3 = b3 - lr * grads[5]
            但这样子是会报错的,因为在tensorflow中,variable运算出来的结果是一个tensor,tensor做不到那么样子的计算
            '''
            w1.assign_sub(lr * grads[0])
            b1.assign_sub(lr * grads[1])
            w2.assign_sub(lr * grads[2])
            b2.assign_sub(lr * grads[3])
            w3.assign_sub(lr * grads[4])
            b3.assign_sub(lr * grads[5])
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75

    对比之下,我们可以很明显的看到,相对第一个,这一段明显的篇幅更小,而且运行时间也远小于第一段程序,因此,我们接下来就详细的学习学习tensorflow以及深度学习。

    三、深度学习第一例

    其实不管是深度学习怎么变化,他的根本就是神将网络,我们先从最简单的手写数字识别做起。

    首先我们先引入数据,与sklearn相同,tensorflow也内置了一些常用的数据集,因此我们用如下语句引入数据:

    import tensorflow as tf
    from tensorflow import keras
    from tensorflow.keras import datasets
    (x_train, y_train), (x_val, y_val) = datasets.mnist.load_data()
    
    • 1
    • 2
    • 3
    • 4

    datasets中的数据他已经给我们处理好了,但大多情况下,我们要做的最重要的一步就是进行数据清理分割,但正好,这里帮我们省去了这部分操作。

    其次,神将网络会对于处于0~1之间的数字更为敏感,至于原因,个人猜测是因为与激活函数有关,因为对于最常用的sigmoid函数,它有点类似于掐头去尾,只留下中间这一段随函数的变化较大,头尾可能会出现梯度消失。

    因此,我们先将得到的数据进行归一化处理。

    x_train = tf.convert_to_tensor(x, dtype=tf.float32)/255.
    y_tarin = tf.convert_to_tensor(y, dtype=tf.float32)
    x_val = tf.convert_to_tensor(x, dtype=tf.float32)/255.
    y_val = tf.convert_to_tensor(y, dtype=tf.float32)
    
    • 1
    • 2
    • 3
    • 4

    这样很实用也很简单,增加点通用性,因为当我们的为数据集起的名字改变时,这段程序就不再适用,虽然改起来也不麻烦(shihua)
    这就需要介绍tensorflow中比较实用的一个函数,map能够将得到的每个元素当作其中函数的输入:

    def data_preprocessing(xi, yi):
        x = tf.convert_to_tensor(xi, dtype=tf.float32) / 255.
        y = tf.convert_to_tensor(yi, dtype=tf.float32)
        return x, y
    x_train = x_train.map(data_preprocessing)
    y_train = y_train.map(data_preprocessing)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    接着,如果说样本量比较小,那就直接整个数据放进去算,但这也就失去了做深度神经网络的意义了,小样本的处理,传统方法将更有优势,但数据过大会导致计算的过于缓慢,因此引入了batch,他就是将巨大的数据切分成一个个batch,将batch轮回计算。

    train_db = tf.data.Dataset.from_tensor_slice((x, y)).batch(128)
    
    • 1

    但光有这句还不够,他只是将数据划分为了不同的batch,但还是一个整体,如果不信可以打印出来看看,我们可以使用迭代器将其取出:

    train_db = tf.data.Dataset.from_tensor_slice((x, y)).batch(128)    # 将整个数据集依据128为单位划分为各个batch,但他实际上还是一整个
    # 通过迭代函数取batch
    train_iter = iter(train_db)
    sample = next(train_iter)
    
    • 1
    • 2
    • 3
    • 4

    这段代码,他首先将原本巨大的数据集划分为以128为单位的小数据集,然后用这个划分好的数据集,生成一个以它为内容的迭代器,通过next()函数将他们依次返回。
    当然了,我们取数据肯定是需要用到for函数,有一个枚举函数可以用到:

    for step, (x, y) in enumerate(train_db):
    
    • 1

    这个枚举函数能够返回batch的索引,以及每个batch的内容。
    最后就是对于离散值的处理,通常情况下,我们使用独热编码进行处理:

    y_onehot = tf.one_hot(y, depth=10)
    
    • 1

    到此为止,数据处理的部分算是大致完成,接下来就是网络的生成,如果网络简单,就能够直接将计算过程显示地写在代码中,但如果想要更多更多,那么我们就需要用到:

    dense = tf.layers.dense(x_train, units=512, activation=tf.nn.relu)
    
    • 1

    他很有意思,就是单单的一层,如果要多层就需要多加几句这样的语句。具体操作看后面的详细代码。

    得了到此为止,还就只剩下最后的loss函数,目标就是最小化loss函数。常见的loss函数,也就是误差函数,有MSE或者RMSE,但对于这种离散型,感觉使用交叉熵误差更加合理。这是因为,交叉熵为我们提供了一种表达两种概率分布的差异的方法。

    四、深度学习

    深度学习最最开始就和具有很多层的多层神经网络相似,他被称为自编码器,他先生成一个只有一层隐层的左右对称的三层神经网络,然后对其训练,如BP,然后保留输入层的权重,丢弃输出层的权重,依次往复,不断将隐层减少,直到变成想要的维度,然后将其对称地再来一遍,还原到原始维度。

    这种方法通过encoding将一个原本为度很高的样本,减小到想要的维度,再将其还原到原来的输入。也就是输入是什么,我的输出也要尽量与其保持一致。

    换句话来说,这个过程就有点像是降维,只不过是通过一个神经网络让他自行寻找,最后中间的几个参数虽然看着比原来减少了很多维度,却包含着基本所有的信息。
    具体过程如下图所示:(图片来自百度百科)
    在这里插入图片描述

    也许我们会非常疑惑,又是压缩又是解压,这些操作究竟是为何呢。我们举一个最简单的例子,对于神经网络的全连接层,假设3*3的一个矩阵,也得有9个权重参数,对于现在,尤其是图像信息,往往含有非常巨大的信息量,如果在那么经过卷积,在经过全连接层,这个权重参数可想而知,不仅拖慢运算速度,还占用大量的存储资源。

  • 相关阅读:
    linux 锁-- atomic & per_cpu
    python实现灰色关联法(GRA)
    【开发教程3】开源蓝牙心率防水运动手环-开发环境搭建
    mybatis3:使用mybatis
    Maven开发环境搭建
    framework.jar如何导入到android studio中进行framework的开发+系统签名
    go语言的高级特性
    C/C++网络编程基础知识超详细讲解上部分(系统性学习day11)
    21天学习挑战赛——Python爬虫 selenium自动化操作浏览器
    多线程访问资源计数不正确问题分析
  • 原文地址:https://blog.csdn.net/qq_37967562/article/details/125463651