自从tensorflow更新到2.0以后,他变得和常规的python非常相似,那我们就简单列举一些常用的数据类型。
首先,对于tensorflow来说,他有标量scaler;向量vector[…];矩阵matrix[[],[]],他们都可以被称为是一个tensor:张量
其实,看到这些,我们或许有些疑惑,这些东西在numpy中,不都有差不多吗?确实,tensorflow可以说是numpy的针对于深度学习的增强版,它包含了很多深度学习常用的函数,并且能够支持GPU加速,这将会大大缩短计算时间。
一些需要注意的区别:
在tensor中最常使用的方法就是描述这个张量的维度,因此对于他来说[a, b, c],分别对应的是这个张量在这三个维度的大小,例如a代表了某一个属性的所有值,将其想象为坐标轴,因该就能够理解。
在深度学习中,尤其是涉及到图像处理的过程中,对于图片我们也许会进行一些处理,例如最简单的神将网络我们就将像素点无差别的输入到输入层,如果是卷积神经网络,我们就会使用到卷积核,对原始的图片进行卷积处理,这个时候就保持原来图片的维度就行,或者说我们觉得正常的样本不够,需要增加一点不正常的,例如将图像反转。
因此,首先对于第一种情况,我们就会使用到tf.reshape(data, [new shape]),需要注意的是,变换前后应保持相同,如果不想自己计算可以用-1代替,但只能使用一次。
对于第二种问题就需要使用到tf.transpose(data, [indices]),这里的参数就是各个维度的排列顺序,应该要注意的是,样本有多少维度,这个参数的shape就有多大。
在数据处理的过程中我们也许会想用一个新的维度,加在原有的样本中,为的是能够有个相对更有区分度的维度帮助算法更好地实现功能,又或者说两个维度的相关性太大,就取出一个不再使用,因此我们就需要使用到:
我们看到如果我们想扩大样本维度,就需要先扩大,但扩大后的维度里面是里面是空的,不理解为何要用它,不如直接用tile函数对于维度直接复制也能扩维。
当然,tensor中有一个自动补齐维度的函数:tf.broadcast_to(data, [new shape]),这个方法在做运算的时候是自动的,当然也是有条件的,将维度右对齐,只有等于一的情况下才能自动补齐否则报错。它补齐后的效果就是,按某一维度扩大几倍,就将该维度复制几遍。
2.0版本的tensorflow,他的一些基础的数学运算就和python一样,常规的没有变化,但有两个常用的运算符:
这里有一个有意思的现象,就是为什么会有reduce这个东西?
我们可以想象,求某一维度,最大最小或者平均,都会将该维度减小到1,有种缩维的意思,所以就有这个关键词reduce。
tf.split(data, axis= , num_or_size_splits= )
,该函数能够按我们想要的比例,对于指定的维度对数据进行切割。数据合并似乎不常用到,可能是对于生成batch后想要扩大batch?tf.concat([data1, data2], axis= )
就能够按指定维度将两组数据合并起来。tf.math.top_k(data, k)
,该函数就能做到取出前k个最大的数并返回索引。至于k取什么,就需要参考具体项目,基于查全率与查准率,进行设定。tf.pad(data, [[1, 1], [1, 2]])
,我们看到其中的参数,大方括号中的每一个小方括号代表每一个维度,小方括号中的两个数分别代表是否在该维度两侧扩容,以及需要外扩多少。这个东西有点不知道具体有什么用,就是如果说用作激活函数,那也有专门的激活函数的接口可以使用,对于向量的等比缩放或许会有一点用,因为如果只是按照一个具体的学习率去更新,会出现,某些维度的梯度相对巨大,某些又相对巨小,如果采用固定值,可能使小的更更更小,极端情况下或许会出现梯度消失的情况。又或者说,梯度都巨大,乘上学习率也巨大,就会造成更新过度的情况。所以一个相对有用的范数裁剪函数为:tf.clip_by.global_norn(grad, k)
,其中k为整体的模值,它会返回新的梯度以及总的模值。
当我们拿到一个具体的任务的时候,我们首先需要做如下几个步骤:
我们就复习一遍最简单的手写数字识别。
首先,我们要做的是获得这个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就已经帮我们打包好了各个接口供我们直接使用。
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个权重参数,对于现在,尤其是图像信息,往往含有非常巨大的信息量,如果在那么经过卷积,在经过全连接层,这个权重参数可想而知,不仅拖慢运算速度,还占用大量的存储资源。