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)
	#这个就是纯输出预测了

这段代码就是无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])

对比之下,我们可以很明显的看到,相对第一个,这一段明显的篇幅更小,而且运行时间也远小于第一段程序,因此,我们接下来就详细的学习学习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()

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)

这样很实用也很简单,增加点通用性,因为当我们的为数据集起的名字改变时,这段程序就不再适用,虽然改起来也不麻烦(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)

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

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

但光有这句还不够,他只是将数据划分为了不同的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)

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

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

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

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

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

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

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

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

四、深度学习

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

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

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值