首先祝大家新年快乐!在此遥祝大家在 2020 年里身体健康、万事如意!
2019 年复盘
年终岁尾大家都在复盘,小时候,就是在前些年我也没有听过复盘这个词,对于我是新词。我也像利用春节小长假,来对今年自己学习进行一次复盘。上半年自己有一些迷茫,学习也是东一块(rust)西一块(golang)没有目标,人到中年自己,在事业上缺少安全感。下半年终于确定了自己目标—机器学习,这是一条艰苦漫长的路途。也曾因为看不清前方曾经试图放弃,也曾一点点进步而欣喜若狂,这些都是旅途的一幅幅画面,都是一次一次经历,学习如此人生如此,我们一定要享受今天过程。
春节期间对于自己学习过进行复盘以达到温故而知新效果,同时把一些含糊概念弄懂弄透。
今年主要做 2 刷,巩固自己学习过知识,在原有基础上进一步深入,通过实践来巩固自己学习过知识。不求取得多的进步,只是想继续享受学习机器学习的过程。
我们基于 python2 来用 tensorflow 来实现一个对数几率概率(他们都叫逻辑回归,其实都是不对的,可以叫 logistic 回归)模型来解决二分类问题。
准备数据集这里选择用 cifar-10-batches 数据集,这个数据集一共 10 类别图片,其中 5 batch 的训练数据集,每一个 batch 数据集有 10000 个样本(张图片),每一个样本为 3072 像素,一个样本对应一个标签用数字表示一个类别。
import tensorflow as tf
import os
import cPickle
import numpy as np
import matplotlib.pyplot as plt
分析数据集
CIFAR_DIR = "./data"
print(os.listdir(CIFAR_DIR))
打印一下文件夹写都有哪些文件
['data_batch_1', 'readme.html', 'batches.meta', 'data_batch_2', 'data_batch_5', 'test_batch', 'data_batch_4', 'data_batch_3']
首先介绍一数据集,这里data_batch 从 1 到 5 存放是训练集图片,每一个batch 存放10000 张图片,test_batch 作为测试集使用,其中也存放了 10000 张图片。这些数据集都是 python 版文件所有用 pickle 数据格式来保存,这里引入 pickle 包用来读取文件中内容。文件使用了 numpy 支持数据格式来存储图片,所以这里还需要引入 numpy 来保存数据。
接下来输出一下文件夹内内容,看文件夹下都有什么文件。
with open(os.path.join(CIFAR_DIR,"data_batch_1"),'rb') as f:
data = pk.load(f,encoding='iso-8859-1')
print(type(data))
print(data.keys())
print(type(data['data']))
print(type(data['labels']))
print(type(data['filenames']))
print(type(data['batch_label']))
print(data['data'].shape)
print(data['data'][0:2])
print(data['labels'][0:2])
print(data['filenames'][0:2])
print(data['batch_label'])
<class 'dict'>
dict_keys(['batch_label', 'labels', 'data', 'filenames'])
<class 'numpy.ndarray'>
<class 'list'>
<class 'list'>
<class 'str'>
(10000, 3072)
[[ 59 43 50 ... 140 84 72]
[154 126 105 ... 139 142 144]]
[6, 9]
['leptodactylus_pentadactylus_s_000004.png', 'camion_s_000148.png']
training batch 1 of 5
在 python 语言中提供了 with 语句,with 后面为一个表达式,表达式返回的是一个上下文管理器对象,理解就是表达式的返回结果。使用 as 可以将这个结果赋值给某个变量。这里将读取文件作为对象 f 来使用,还有一个好处就是我们不用手动close 文件,with 语句会帮助我们做收尾清理工作。现在我们主要是看一看文件结构用过输出 data 类型我们知道这是 dict 类型数据,所谓 dict 就是由键和值组成的,通过 keys()输出 dict 中都有哪些键
<class 'dict'>
dict_keys(['batch_label', 'labels', 'data', 'filenames'])
然后我们分别通过键取到对应值,通过 type 输出一下各个数据的类型都是什么
<class 'numpy.ndarray'>
<class 'list'>
<class 'list'>
<class 'str'>
- data 数据是 (10000, 3072) 矩阵,10000 代表有 10000 个样本,3072 每张图片像素值 32 * 32 = 1024 * 3 =3072,我们通常是通过 3 维矩阵定义图片,图片大小是 32 * 32 所以 1024 而表示图片是由 1024 个像素点组成,而每个像素点又是RGB 3 个通道组成,所以就是 1024 * 3 得出 3072
- labels 我们数据集中图片分别属于 10 类物体,我们对每一个样本打上一个标签表示样本属于哪个类别,6 和 9 代表不同类别。
- batch_label 的值为 training batch 1 of 5 表示这是 5 个数据集中第一个数据集
- filenames 表示每张图对应名字
预览图片
我们在图片样本数据集中抽取一张图片数据,然后将其显示出来便于直观观察。
img_arr = data['data'][100]
print(img_arr)
img_arr = img_arr.reshape((3,32,32)) #32 32 3
img_arr = img_arr.transpose((1,2,0))
import matplotlib.pyplot as plt
%matplotlib inline
from matplotlib.pyplot import imshow
imshow(img_arr)
这里用到两个 numpy 的函数跟大家说明一下,
org_arr = np.array([1,2,3,4,5,6,7,8,9])
tar_arr = org_arr.reshape((3,3))
tar_arr
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
通过 reshape 方法可以改变矩阵的结构,我们这里一维矩阵变为符合图片格式三维矩阵,因为在 cifar 图片数据中是按 RR-GG-BB 先R通道颜色数据信息,然后是绿色通道数据信息,最后是蓝色通道数据信息这样排列的下来,所以 3 维度别为 RGB ,3 * 1024 然后继续分解 1024 按每 32 进行拆分 3 * 32 * 32
trans_arr = tar_arr.transpose((1,0))
trans_arr
array([[1, 4, 7],
[2, 5, 8],
[3, 6, 9]])
通过 transpose 对行数组结构进行调整以达到我们想要的数据结构。
def load_data(filename):
""" 读取数据 """
with open(filename,'rb') as f:
data = cPickle.load(f)
return data['data'],data['labels']
接下来我们要做这么一件事,就是提供数据,看似简单却是在机器学习中分层重要的一件事,我们训练模型好坏一切都是从这里开始,好的开始注定有好的结果。我们按批次提供数据,对于训练数据集我们提供调整顺序增强数据泛化能力。
class CifarData:
# filename 表示输入文件,need_shuffle 表示对于训练数据集
# 是否需要洗牌,将数据打乱来得到更多更具有泛化能力的数据
def __init__(self,filenames,need_shuffle):
# 定义数据集和标签集
all_data = []
all_labels = []
# 读入数据
for filename in filenames:
# 使用上面读取文件数据的函数来加载文件的数据
data,labels = load_data(filename)
"""
因为我们样本数和标签数是一样的,而且对应位置相同,所以可以通过 zip 函数
将样本数和标签数进行合并
"""
for item, label in zip(data,labels):
if label in [0,1]:#因为是二分类问题我们需要两个类别就够了
all_data.append(item)
all_labels.append(label)
self._data = np.vstack(all_data)
self._data = self._data/127.5 - 1
self._labels = np.hstack(all_labels)
self._num_examples = self._data.shape[0]
self._need_shuffle = need_shuffle
# 定义遍历数据起始位置,表示遍历到数据集某个位置
self._indicator = 0
if self._need_shuffle:
self._shuffle_data()
def _shuffle_data(self):
# 对于从 0 到 _num_examples 从小到大数据进行混排,也就是打乱顺序得到序号
p = np.random.permutation(self._num_examples)
self._data = self._data[p]
self._labels = self._labels[p]
# 每一次返回 batch_size 样本,也就是从数据集取出 batch_size 个样本
def next_batch(self,batch_size):
""" return batch_size examples as a batch. """
end_indicator = self._indicator + batch_size
if end_indicator > self._num_examples:
# 如果当前没有住够数据时候如果可以洗牌
if self._need_shuffle:
self._shuffle_data()
self._indicator = 0
end_indicator = batch_size
else:
raise Exception("have no more examples")
if end_indicator > self._num_examples:
raise Exception("batch size is larager than all examples")
batch_data = self._data[self._indicator:end_indicator]
batch_labels = self._labels[self._indicator:end_indicator]
self._indicator = end_indicator
return batch_data, batch_labels
self._data = self._data/127.5 - 1
这里对数据进行了处理将原有介于 0 到 255 的数据变为 -1 到 1 间数据,这个对数据处理对训练结果产生很大影响。
这里有几处用到 numpy 提供对于数组的处理特别给大家说明一下。
train_filenames = [os.path.join(CIFAR_DIR,'data_batch_%d' % i) for i in range(1,6)]
test_data_filenames = [os.path.join(CIFAR_DIR,'test_batch')]
train_data = CifarData(train_filenames,True)
test_data = CifarData(test_data_filenames,False)
定义变量占位符
也就是我们定义计算图模型,看一看样本是什么模型
今天主要是实战,所以只是简单回顾一下logistic 回归模型,说到 logistic 我们还是先说一下他与不同回归不同的是他是用来做分类问题而不是预测问题。线性模型之所以能够做分类问题全都是得益于 sigmoid 函数
然后将我们线性模型 y = wx + b 带入到 sigmoid 函数来实现分类问题。
这里样本是 3072 的数据
在 tensorflow 中我们通常定义占位符,在计算图占位以备随后赋值
x = tf.placeholder(tf.float32,[None,3072])
y = tf.placeholder(tf.int64,[None])
x 和 y 占位符分别用于接收随后输入的数据。
定义模型
然后接下来定义图计算模型,定义参数 w 和 b ,y = wx + b 在 tensorflow 定义变量有两种方式 tf.Variable 和 tf.get_variable()
# 定义(权重)参数 w 3072 * 1
w = tf.get_variable('w',[x.get_shape()[-1],1],initializer=tf.random_normal_initializer(0,1))
b = tf.get_variable('b',[1],initializer=tf.constant_initializer(0.0))
# y = wx + b
# [None,3072] * [3072,1] = [None,1]
y_hat = tf.matmul(x,w) + b
- 通过 get_variable 来定义变量 w ,然后定义参数形状这里定义为 [3072x1] 因为我们有 3072 特征值,也就是每一个
- 像素值,最后是初始化变量,参数初始化对模型训练也是有一定程度影响,这里采用均值为 0 方差为 1 正态分布来初始化 w
- 定义偏置(1,)这里 b 是一维,通过 tensorflow 广播我们可以扩展,初始化为 0
定义损失函数
# 通过 sigmoid 函数[None,1]$$ f(z) = \frac{1}{1+e^{-(wx+b)}}$$
p_y_1 = tf.nn.sigmoid(y_hat)
# 因为 p_y_1 是 [None,1] 的数据,所以讲对于 y 进行变换 ,将 [None] 形状数据转为 [None,1] 的数据
y_reshaped = tf.reshape(y,(-1,1))
# 因为原有标签是 int 现在需要转换为 float 型,因为 tensorflow 对数据类型比较敏感,这个可能原有其背后 C++
y_reshaped_float = tf.cast(y_reshaped,tf.float32)
# 定义损失函数,这里我们用的是最小二乘法 $$\frac{1}{N}({\hat_{y} - y})^2$$
loss = tf.reduce_mean(tf.square(y_reshaped_float - p_y_1))
上面代码定义损失函数,也就是目标函数,需要我们通过不断迭代优化参数以让 loss 函数最小。这是优化做的,在下面代码中看到优化器使用 AdamOptimizer。
模型优化和评估
predict = p_y_1 > 0.5
#因为 y_reshaped 表示分类整数,我们需要将 boolean 型的 predict 进行转换。通过
correct_prediction = tf.equal(tf.cast(predict,tf.int64),y_reshaped)
accuracy = tf.reduce_mean(tf.cast(correct_prediction,tf.float64))
with tf.name_scope('train_op'):
train_op =tf.train.AdamOptimizer(1e-3).minimize(loss)
# 先初始化变量
accuracy 用于评估模型精度,通过对比估计值和真实值来获取判断对的数量来作为评估模型准确度标准(精度),这里值得介绍 tensorflow 中 equal 方法使用。
A = [[1,3,4,5,6]]
B = [[1,3,4,3,2]]
with tf.Session() as sess:
print(sess.run(tf.equal(A, B)))
输出为
[[ True True True False False]]
with tf.name_scope('train_op'):
这里用到命名空间来表示函数的作用域,
初始参数
init = tf.global_variables_initializer()
batch_size = 20
train_steps = 10000
test_steps = 100
这里我们需要调用 global_variables_initializer 对定义变量进行初始化。然后 batch_size 每次训练样本数量,train_steps 训练迭代次数,test_steps 进行测试次数
with tf.Session() as sess:
sess.run(init)
for i in range(train_steps):
batch_data,batch_labels = train_data.next_batch(batch_size)
loss_val, acc_val, _ = sess.run([loss, accuracy, train_op],feed_dict={x: batch_data, y: batch_labels})
if (i+1) % 500 == 0:
print '[Train] Step: %d, loss %4.5f, acc: %4.5f' % (i+1, loss_val,acc_val)
if (i+1) % 5000 == 0:
test_data = CifarData(test_data_filenames,False)
for j in range(test_steps):
test_batch_data,test_batch_labels = test_data.next_batch(batch_size)
all_test_acc_val = []
test_acc_val = sess.run([accuracy],{x: test_batch_data,y:test_batch_labels})
all_test_acc_val.append(test_acc_val)
test_acc = np.mean(all_test_acc_val)
print '[Test ] Steps: %d ,acc: %4.5f' % (i+1,test_acc)
在 tensorflow 中,我们定义好模型,还需要定义一个Session(会话)来运行我们模型。tensorflow 的内核使用更加高效的 C++ 作为后台,以支撑密集计算。tensorflow 把前台(即python程序)与后台程序之间的连接称为"会话(Session)",所以我们计算
run(
fetches,
feed_dict=None
options=None,
run_metadata=None
)
最终调试模型后输出
[Test ] Steps: 5000 ,acc: 0.80000
[Train] Step: 5500, loss 0.18075, acc: 0.80000
[Train] Step: 6000, loss 0.09055, acc: 0.90000
[Train] Step: 6500, loss 0.29896, acc: 0.70000
[Train] Step: 7000, loss 0.29990, acc: 0.70000
[Train] Step: 7500, loss 0.15007, acc: 0.85000
[Train] Step: 8000, loss 0.11396, acc: 0.85000
[Train] Step: 8500, loss 0.27588, acc: 0.65000
[Train] Step: 9000, loss 0.05072, acc: 0.95000
[Train] Step: 9500, loss 0.14737, acc: 0.85000
[Train] Step: 10000, loss 0.20000, acc: 0.80000
[Test ] Steps: 10000 ,acc: 0.80000
从数据上来看我们训练结果并不明显而且并不稳定,这都是我们实际工作要解决的问题。
最后希望大家关注我们微信公众号
有疑问加站长微信联系(非本文作者)