06_1_神经网络与全连接层_数据加载&测试(张量)实战
数据集加载
Outline
这次主要讲解一些小型常用的经典数据集的加载步骤
-
keras.datasets
-
tf.data.Dataset.from_tensor_slices
将数据加载进Tensor-
一些操作
-
shuffle
-
map 转换
-
batch 多个样本组成一个batch
-
repeat
-
-
we will talk input pipeline later(大型的数据集,根据自己的需求,一项一项支持多线程的加载方式)
keras.datasets
- 波士顿的房价,提供一些指定策略的因子,区域、面积、地铁、小区居民来源……通过考虑一些因子,制定价格
- 手写数字mnist
- 小型的图片识别数据集 cifar10/100
- 对数据处理的入门数据集,类似于淘宝好评的数据集,电影的评语,会有标注好评还是差评,做用户评语的分析
MNIST
28*28,1
70K/60K(training)/10K(test)
MNIST加载案例:
import os
os.environ['TF_CPP_MIN_LOG_LEVEL']='2'
import tensorflow as tf
from tensorflow import keras
#返回numpy的格式,4个对象
# 前2个是tuple,train,60K;后2个是tuple,test,10K
(x,y),(x_test,y_test) = keras.datasets.mnist.load_data()
print(x.shape) #(60000, 28, 28)
#0~9的编码,都后面需要onehot encoding
print(y.shape) #(60000,)
#这里可以调用min,因为x还是numpy类型,因此是numpy的api
#像素点的灰度值,通常会除以255,使得数据scale(缩放)/归一到[0,1]或[-1,1]或[-0.5,0.5],有利于优化
print(x.min(),x.max(),x.mean())
#0 255 33.318421449829934
#(10000, 28, 28) (10000,)
print(x_test.shape,y_test.shape)
#[5 0 4 1] dtype=uint8
print(y[:4])
#onehot encoding,depth可以转换为多长的向量,然后会把对应index的值变为1
y_onehot = tf.one_hot(y,depth=10)
#[[0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]
#[1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]], shape=(2, 10), dtype=float32)
print(y_onehot[:2])
CIFAR10/100
小型图片识别的数据集。10和100是指类别。100是对于每一个大类再细分分为10类,共100类。因此是同一个数据集(图片)。
CIFAR10加载:
import os
os.environ['TF_CPP_MIN_LOG_LEVEL']='2'
import tensorflow as tf
from tensorflow import keras
#或cifar100
#50K train,10K test
(x,y),(x_test,y_test) = keras.datasets.cifar10.load_data()
#(50000, 32, 32, 3) (50000, 1) (10000, 32, 32, 3) (10000, 1)
print(x.shape,y.shape,x_test.shape,y_test.shape)
#现在x,y是numpy格式,之后需要转换为Tensor格式,之后需要iter(迭代)
#0 255
print(x.min(),x.max())
#[[6]
# [9]
# [9]
# [4]]
print(y[:4])
tf.data.Dataset
Dataset是专门用来数据集迭代的类,只需要numpy类型数据通过dataset提供的from_tensor_slices()
接口,直接转换为Dataset类的对象。
先拿到numpy的数据,转换到Dataset里去,通过db就可以进行迭代,it = iter(db)
,it就是一个迭代器,再对迭代器做迭代-next(it)
,可以得到数据里的每一项内容,第三部分是得到了一张了image。
x-》x_Tensor,直接for x in x_Tensor: 这样也可以进行读取操作,为什么一定用Dataset?
- x只能一张一张image的拿,不能读取一个batch,还没有做图片的预处理;Dataset可以完成这些内容,它还支持多线程
实际情况,我们要image和label同时拿取,Dataset可以一次接收2个数据,迭代的时候会一次返回两个这样的样本。[32,32,3]和[1]
.shuffle
为什么?因为neuro network(NN)的能力非常强,记忆能力非常强,0,1,2,3,4,5,6……按顺序学习可能会不利于。
所以需要shuffle打散,把图片的顺序打散,但是也要注意x和y的相对位置不能打散。当然也可以利用idx功能,在这里直接利用shuffle。
10000只是buffer,对功能不是很影响。
.map
数据预处理的功能
for x,y in (X,Y):
x,y->Tensor
->32位
->one-hot
预处理函数preprocess,它是对于每一个sample。我们只需要把它map到db后,它就能对于dataset中的每一个sample做一个预处理。
经过处理后image[32,32,3];label[1,10],1表示batch,10表示onehot过
.batch
db2.batch(32)
我们看到y是[32,1,10],希望把1 squize(压扁)掉。如果[60K,1]把1 squize掉,这里就会变成[32,10]
这些操作后,for x,y in db3:
结果是,x[b,32,32,3] y[b,10]
还有1个问题,如果是for没有问题,但是如果使用while,最后1次的x,y=next(it),会引发错误。
StopIteration
当对数据集完成了一次迭代,就会发生异常StopIteration。
取完60K后,如果下次还想循环地再从头取数据集:可以catch StopIteration,再构造一次db的iterator,再一次next(db_iter)。
.repeat()
如果你想好了只需要数据集迭代2次或n次,只需要repeat(2),它可以很好地帮助你完成。这样for x,y in db4: 它会迭代2次。
如果想永远迭代的话,db4 = db3.repeat()
,不需要填写参数。
For example
比较完整的流程:
onehot->Dataset->预处理->打散、batch……
import os
os.environ['TF_CPP_MIN_LOG_LEVEL']='2'
import tensorflow as tf
from tensorflow.keras import datasets
def prepare_mnist_features_and_labels(x,y):
#数据类型的转换,除以255,scale(缩放)到0~1的范围内
x = tf.cast(x,tf.float32) / 32
y = tf.cast(y,tf.int64)
return x,y
def mnist_dataset():
#fashion_mnist类似mnist,大小也是28*28的灰度图片,实例不再是0~9,是衣服、帽子……
#60K 10K
(x,y),(x_val,y_val) = datasets.fashion_mnist.load_data()
#进行onehot
y = tf.one_hot(y,depth=10)
y_val = tf.one_hot(y_val,depth=10)
#转换到Dataset中去
ds = tf.data.Dataset.from_tensor_slices((x,y))
#预处理
ds = ds.map(prepare_mnist_features_and_labels)
#打散,100的batch,这里没有repeat
ds = ds.shuffle(60000).batch(100)
ds_val = tf.data.Dataset.from_tensor_slices((x_val,y_val))
ds_val = ds_val.map(prepare_mnist_features_and_labels)
ds_val = ds_val.shuffle(10000).batch(100)
return ds,ds_val
测试(张量)实战
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import datasets
# 加载数据集,自动从网上下载
# x:[60k,28,28],[10k,28,28] y:[60k],[10k]
(x, y), (x_test,y_test) = datasets.mnist.load_data()
# 转化成Tensor
# 原来x的范围为[0,255],y的范围为[0,9],255是灰度值,习惯把数据转换为0~1的数值适合优化
# x: [0,255] => [0,1.]
# y: [0,9]
x = tf.convert_to_tensor(x, dtype=tf.float32) / 255.
y = tf.convert_to_tensor(y, dtype=tf.int32)
x_test = tf.convert_to_tensor(x_test, dtype=tf.float32) / 255.
y_test = tf.convert_to_tensor(y_test, dtype=tf.int32)
print(x.shape, y.shape, x.dtype, y.dtype)
# 看x和y的最小值和最大值
print(tf.reduce_min(x), tf.reduce_max(x))
print(tf.reduce_min(y), tf.reduce_max(y))
# 设置batch,希望一次能取128张照片
train_db = tf.data.Dataset.from_tensor_slices((x, y)).batch(128)
test_db = tf.data.Dataset.from_tensor_slices((x_test,y_test)).batch(128)
# train_iter可以对train_db做next方法
train_iter = iter(train_db)
# 一直取,直到得到一个最终的元素。这里是一个sample例子
# x:(128, 28, 28) y:(128,)
sample = next(train_iter)
print('batch: ', sample[0].shape, sample[1].shape)
# 创建权值
# 层是降维的过程,一开始是[b,784] => 降维[b,256] => 降维[b,128] => 降维[b,10]
# w的shape要满足运算规则,因此为[dim_in,dim_out],初始化为截断的正态分布
# b的shape为[dim_out],初始化为0
w1 = tf.Variable(tf.random.truncated_normal([784, 256], stddev=0.1))
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]))
lr = 1e-3 # 10^-3
for epoch in range(100): # 对整个数据集迭代10次,iterate db for 10,表示对数据集迭代多少次
# 对一次数据集的所有图片做一个循环
# for (x, y) in train_db:
for step, (x, y) in enumerate(train_db):
# for every batch,表示对当前的数据集,你迭代的数据集,迭代哪个进度。比如说batch为128,一共有60k图片,一共取多少次,step表示当前进度的表示条
# x:[128,28,28]
# y:[128]
# 维度变换 [b,28,28] => [b,28*28]
x = tf.reshape(x, [-1, 28 * 28])
# 使得梯度信息被记录下来
with tf.GradientTape() as tape: # 默认只会跟踪tf.Variable类型
# 在这里我们希望x:[b,28*28]
# h1 = x@w1 + b1
# [b,784]@[784,256] + [256] => [b,256]+[256] 加号会自动做broadcast/自己做broadcast
# b是batch,在这里b为128,也就是x.shape[0]
# => [b,256] + [b,256] => [b,256]
h1 = x @ w1 + tf.broadcast_to(b1, [x.shape[0], 256])
# 非线性转化
h1 = tf.nn.relu(h1)
# [b,256] => [b,128]
h2 = h1 @ w2 + b2
h2 = tf.nn.relu(h2)
# [b,128] => [b,10]
out = h2 @ w3 + b3
# compute loss
# 输出out:[b,10]
# 真实结果y:[b],因此要将y进行one-hot encoding
# =>[b,10]
y_onehot = tf.one_hot(y, depth=10)
# mse(均方误差) = mean(sum(y-out)^2) y-out的平方和,之后平均
# y-out 还是shape为[b,10]
loss = tf.square(y_onehot - out)
# 均值mean:得到scalar
# 相当于 loss / b / 10。/b是求出每一个batch上的均值;/10是求每个instance上每一个点的均值
loss = tf.reduce_mean(loss)
# 需要自动求导,需要把前向计算的过程,也就是需要求梯度的过程,把它包在GradientTape
# compute gradients
grads = tape.gradient(loss, [w1, b1, w2, b2, w3, b3])
# print(grads)
# 本来optimizer可以一次w1 = w1 - lr * w1_grad,在这里进行手写
# 在这里相减后,w1又从tf.Variable变回了tf.Tensor,这里使用assign_sub进行原地更新,保持数据类型(Variable类型)不变
w1.assign_sub(lr * grads[0])
# w1 = w1 - 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])
# print(isinstance(b3,tf.Variable))
# print(isinstance(b3,tf.Tensor))
if step % 100 == 0:
print(epoch, step, 'loss:', float(loss))
# 100 loss: nan 梯度爆炸,解决方案:给6个参数一个范围值,stddev=0.1
# test/evaluation 测试/评估集
total_correct, total_num = 0, 0
#使用测试集
#使用当前的w1,b1,w2,b2,w3,b3
for step,(x,y) in enumerate(test_db):
# x的shape:[b,28,28] => [b,28*28]
x = tf.reshape(x,[-1,28*28])
# [b, 784] => [b,256] => [b,128] => [b,10]
h1 = tf.nn.relu(x@w1+b1)
h2 = tf.nn.relu(h1@w2+b2)
out = h2@w3+b3
# out是属于实数的范围,out: [b,10] ~ R
# 概率需要转化到0~1的范围,prob: [b,10] ~ [0,1]
# softmax实现1对1的映射,缩放映射到0~1的范围,并且保持概率之和为1
prob = tf.nn.softmax(out,axis=1)
#这样还是会得到[b,10]的shape
# 选取概率最大的那个值所在的索引,并且会消掉1维度,[b,10] => [b]
pred = tf.argmax(prob,axis=1)
# pred是int64不能和int32的y比较,所以需要转化为int32
pred = tf.cast(pred,dtype=tf.int32)
# 这里y: [b],不需要转换成onehot;但是训练的时候需要,因为要计算mse(Mean Square Error)
#equal做比较,返回boolean的Tensor,然后利用cast转化为int32
correct = tf.cast(tf.equal(pred,y),dtype=tf.int32)
correct = tf.reduce_sum(correct)
#累加一次batch中正确的次数,最后获得总的正确的个数
total_correct+=int(correct)
#x.shape[0]等于batch
#最后获得总的测试样本的数量
total_num += x.shape[0]
#准确率
acc = total_correct / total_num
print('test acc:',acc)
结果: