Deeplearning 笔记:MNIST数据集初体验

2.Numpy

2.17 dtype, astype

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
>>> a = np.array([-1, 1])	     
>>> b = np.array([-2, 2])
>>> a.dtype #dtype查看数据类型
dtype('int32')
>>> b.dtype
dtype('int32')
>>> a.astype(np.int16) #astype转换数据类型 参数np.int16与直接'int16'一样的结果
array([-1, 1], dtype=int16)
>>> a.astype('int16') #astype当参数为signed符号整数时,转换结果没有发生变化
array([-1, 1], dtype=int16)
>>> b.astype('int16')
array([-2, 2], dtype=int16)
>>> a.astype('int8')
array([-1, 1], dtype=int8)
>>> b.astype('int8')
array([-2, 2], dtype=int8)
>>> a.astype('uint8') #astype当参数为unsigned无符号整数时,-1则为对应数据类型uint8所表示的最大值255,-2则对应的减去1
array([255, 1], dtype=uint8)
>>> b.astype('uint8')
array([254, 2], dtype=uint8)
>>> a #astype数据类型转换并不会改变原始数据的值,除非再次赋值给同一个变量如:a = a.astype('int8')
array([-1, 1])
>>> b
array([-2, 2])
>>> a.dtype
dtype('int32')
>>> b.dtype
dtype('int32')
>>>
>>> a = np.array([-1, 1, 2, 3])
>>> b = np.array([-2, 4, 5, 6])
>>> a.shape
(4,)
>>> b.shape
(4,)
>>> a.dtype
dtype('int32')
>>> b.dtype
dtype('int32')
>>> a.dtype = 'int16' # 对dtype进行定义则改变原来的数据类型,用新的数据类型来表示(其改变原理还没有搞清楚)
>>> a.shape #int32是4个字节的宽度,int16是2个字节的宽度,所以从原来的4个元素变成8个元素
(8,)
>>> a
array([-1, -1, 1, 0, 2, 0, 3, 0], dtype=int16) #为什么会变成这些元素目前还没有搞清楚
>>> a = np.array([-1, 1, 2, 3])
>>> a.dtype = 'int64' #64位是8个字节,相应的元素从32位的4个元素变成2个元素
>>> a.shape
(2,)
>>> a
array([ 8589934591, 12884901890], dtype=int64) #为什么会变成这些元素目前还没有搞清楚
>>> b = np.array([-2, 4, 5, 6]) #dtype下面为什么用不同的数据类型符号整数和无符号整数结果是一样?目前还不清楚,估计跟数字大小有关系
>>> b.dtype = 'int64'
>>> b
array([21474836478, 25769803781], dtype=int64)
>>> b = np.array([-2, 4, 5, 6])
>>> b.dtype = 'uint64' #跟int64是相同的数组
>>> b
array([21474836478, 25769803781], dtype=uint64)
>>> a = np.array([-1, 1])
>>> a.dtype = 'int16'
>>> a
array([-1, -1, 1, 0], dtype=int16) #与uint16是不同的数组,实际上等于转换成uint16的值
>>> a = np.array([-1, 1])
>>> a.dtype = 'uint16'
>>> a
array([65535, 65535, 1, 0], dtype=uint16)

另外如果从浮点数用astype转换成整数或者无符号整数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> a = np.array([-1.23])
>>> a.dtype
dtype('float64')
>>> a.astype('int64') #去掉了小数部分
array([-1], dtype=int64)
>>> a.astype('uint64') #同上面一样-1转换为对应uint64表示的最大值
array([18446744073709551615], dtype=uint64)
>>> a.astype('int32') #符号整数可以为负,所以没有变化
array([-1])
>>> a.astype('uint32') #同理2的(32-1)次方
array([4294967295], dtype=uint32)
>>> a.astype(np.uint32)
array([4294967295], dtype=uint32)

3. enumerate(), Python的内置函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。

enumerate(sequence, [start=0])

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>> list1 = ["这", "是", "一个", "测试"]
>>> for index, item in enumerate(list1, 1):
print(index, item)

1
2
3 一个
4 测试
>>> for index, item in enumerate(list1):
print(index, item)

0
1
2 一个
3 测试
>>> for index, item in enumerate(list1, -1):
print(index, item)

-1
0
1 一个
2 测试
>>>

enumerate用于统计文件行数

1
2
3
4
5
6
# method 1
count = len(open(filepath, 'r').readlines())
# method 2
count = -1
for index, line in enumerate(open(filepath, 'r')):
count += 1

4. Deep Learning From Scratch

4.1 Mnist数据集初体验

经过一段时间的Numpy的学习,上一周又回到书本上,把之前学过的数据集的内容又看了一遍。第一遍的时候,不知所云,主要是因为Numpy知识的缺乏,第二遍后,终于理解了书中代码的内容。下面是书中源码的备注和自己的理解。

第一个源码:mnist.py 目的是下载训练和测试数据,并转化为数组,然后保存在本地pickle文件里面

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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# coding: utf-8
try:
import urllib.request
except ImportError:
raise ImportError('You should use Python 3.x')
import os.path
import gzip
import pickle
import os
import numpy as np

#数据下载地址
url_base = 'http://yann.lecun.com/exdb/mnist/'

#对应的文件类型和其被压缩后的文件名组成的字典,包含训练图像,训练标签,测试图像,测试标签
key_file = {
'train_img':'train-images-idx3-ubyte.gz',
'train_label':'train-labels-idx1-ubyte.gz',
'test_img':'t10k-images-idx3-ubyte.gz',
'test_label':'t10k-labels-idx1-ubyte.gz'
}

dataset_dir = os.path.dirname(os.path.abspath(__file__))#os.path.abspath(__file__)表示当前py文件的绝对路径,os.path.dirname表示其所在的文件夹
save_file = dataset_dir + "/mnist.pkl"

train_num = 60000
test_num = 10000
img_dim = (1, 28, 28)
img_size = 784

#下载压缩文件
def _download(file_name): #参数是压缩文件名
file_path = dataset_dir + "/" + file_name #文件的绝对路径

if os.path.exists(file_path): #如果该压缩文件已经存在则退出
return

print("Downloading " + file_name + " ... ")
urllib.request.urlretrieve(url_base + file_name, file_path) #下载远程文件到本地指定地址和文件名
print("Done")

#下载数据
def download_mnist():
for v in key_file.values(): #迭代出压缩文件名
_download(v) #下载压缩文件

#将标签压缩文件转化为数组(一维,uint8)
def _load_label(file_name):
file_path = dataset_dir + "/" + file_name

print("Converting " + file_name + " to NumPy Array ...")
with gzip.open(file_path, 'rb') as f:
labels = np.frombuffer(f.read(), np.uint8, offset=8) #注意这里offset=8,生成一维数组(label的个数, )
print("Done")

return labels

#将图片压缩文件转化为数组(二维,uint8)
def _load_img(file_name):
file_path = dataset_dir + "/" + file_name #压缩文件的绝对路径

print("Converting " + file_name + " to NumPy Array ...")
with gzip.open(file_path, 'rb') as f: #gzip模块用于压缩和解压缩文件
data = np.frombuffer(f.read(), np.uint8, offset=16) #frombuffer把提供的数据流转化为对应dtype类型,从offset位置开始读取的一维数组。offset为什么要设置为16呢?
data = data.reshape(-1, img_size) #-1表示根据系统设定,只要第一维是img_size。转化后0维为图片个数,1维为图片的像素值. (-1, 784)
print("Done")

return data

#转化成数组,文件类型和ta对应的数组组成的字典
def _convert_numpy():
dataset = {}
dataset['train_img'] = _load_img(key_file['train_img'])
dataset['train_label'] = _load_label(key_file['train_label'])
dataset['test_img'] = _load_img(key_file['test_img'])
dataset['test_label'] = _load_label(key_file['test_label'])

return dataset

#初始化数据,把文件下载到本地并读出里面的数据信息并保存在文件里
def init_mnist():
download_mnist() #下载数据,从网上地址直接下载压缩文件到本地
dataset = _convert_numpy() #文件类型和ta对应的数组组成的字典
print("Creating pickle file ...") #把字典数据保存在文件mnist.pkl里面
with open(save_file, 'wb') as f:
pickle.dump(dataset, f, -1)
print("Done!")

#把标签保存为仅正确解标签为1, 其余为0的数组。参数X为训练或者测试标签的一维数组
def _change_one_hot_label(X):
T = np.zeros((X.size, 10)) #生成一个二维数组,X的每个元素为一行10个0的数组,及X的第0个元素对应T的第0行数组(10个0组成),以此类推
for idx, row in enumerate(T):#遍历数组,
row[X[idx]] = 1 #X标签(一维数组)的第0个元素对应的值设为n,T第0行数组的第n个元素设置为1,以此类推。也就是说X标签是什么值,ta对应的那行数组(T里面的对应行)的标签(序列号)就为1
return T


def load_mnist(normalize=True, flatten=True, one_hot_label=False):
"""读入MNIST数据集

Parameters
----------
normalize : 将图像的像素值正规化为0.0~1.0
one_hot_label :
one_hot_label为True的情况下,标签作为one-hot数组返回
one-hot数组是指[0,0,1,0,0,0,0,0,0,0]这样的数组
flatten : 是否将图像展开为一维数组

Returns
-------
(训练图像, 训练标签), (测试图像, 测试标签)
"""
if not os.path.exists(save_file): #如果不存在save_file文件夹
init_mnist() #则初始化数据

with open(save_file, 'rb') as f:#导出pickle文件的数据,数据是文件类型和其对应的数组组成的字典
dataset = pickle.load(f)

#使图片数据正规化
if normalize: #如果这个参数为True, 那么把图片(二维,uint8)数据转化为float32, 并除以255,使其图片数据正规化为0.0-1.0的值
for key in ('train_img', 'test_img'):
dataset[key] = dataset[key].astype(np.float32)
dataset[key] /= 255.0

if one_hot_label:#把标签转化为one-hot数组
dataset['train_label'] = _change_one_hot_label(dataset['train_label'])
dataset['test_label'] = _change_one_hot_label(dataset['test_label'])

if not flatten: #如果flatten为False,图片数据则不转为(-1, 1, 28, 28),为True则数据结构不变为(-1, 784)
for key in ('train_img', 'test_img'):
dataset[key] = dataset[key].reshape(-1, 1, 28, 28)

return (dataset['train_img'], dataset['train_label']), (dataset['test_img'], dataset['test_label'])


if __name__ == '__main__':
init_mnist()

第二个源码:neuralnet_mnist.py 目的是利用训练好的数据,测试判断手写字的准确率

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
# coding: utf-8
import sys, os
sys.path.append(os.pardir) # 为了导入父目录的文件而进行的设定
import numpy as np
import pickle
from dataset.mnist import load_mnist
from common.functions import sigmoid, softmax


#从load_minist中得到所有训练和测试数据,正则化。这里只需要测试数据
def get_data():
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, flatten=True, one_hot_label=False)
return x_test, t_test

#从保存的训练数据中,导出每层的权重的偏置数据。一个字典变量
def init_network():
with open("sample_weight.pkl", 'rb') as f:
network = pickle.load(f)
return network

#从字典变量中导出对应键的值,并进行数据计算,最后得到x的概率值y。network是权重偏置数据,x是输入的图片数据
def predict(network, x):
W1, W2, W3 = network['W1'], network['W2'], network['W3']
b1, b2, b3 = network['b1'], network['b2'], network['b3']

a1 = np.dot(x, W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) + b2
z2 = sigmoid(a2)
a3 = np.dot(z2, W3) + b3
y = softmax(a3)

return y


x, t = get_data() #正则化的测试数据。标签是一维数据(标签个数10000, ),图片是二维数据(图片个数10000, 像素值784)
network = init_network() #从训练数据中得到权重偏置数据的字典变量
accuracy_cnt = 0
for i in range(len(x)): #len(x)=10000
y = predict(network, x[i]) #x[i]就是导出每张图片的像素数据,最终得到每个图片的概率值y
p= np.argmax(y) # 从10个神经元中获取概率最高的元素的索引,就是该测试结果的值
if p == t[i]: #t[i]是这个测试图片正确值,如果测试结果是正确的,那么就算测试正确一次
accuracy_cnt += 1

print("Accuracy:" + str(float(accuracy_cnt) / len(x))) #计算正确率

第三个源码:neuralnet_mnist_batch.py 目的是对数据进行批量处理

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
# coding: utf-8
import sys, os
sys.path.append(os.pardir) # 为了导入父目录的文件而进行的设定
import numpy as np
import pickle
from dataset.mnist import load_mnist
from common.functions import sigmoid, softmax


def get_data():
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, flatten=True, one_hot_label=False)
return x_test, t_test


def init_network():
with open("sample_weight.pkl", 'rb') as f:
network = pickle.load(f)
return network


def predict(network, x):
w1, w2, w3 = network['W1'], network['W2'], network['W3']
b1, b2, b3 = network['b1'], network['b2'], network['b3']

a1 = np.dot(x, w1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, w2) + b2
z2 = sigmoid(a2)
a3 = np.dot(z2, w3) + b3
y = softmax(a3)

return y


x, t = get_data()
network = init_network()

batch_size = 100 # 批数量
accuracy_cnt = 0

for i in range(0, len(x), batch_size):
x_batch = x[i:i+batch_size] #100个图片为一批,进行批处理shape是(100, 784)
y_batch = predict(network, x_batch) #每次生成一个二维数组shape是(100, 10)
p = np.argmax(y_batch, axis=1) #在轴上第一维上最大概率的索引组成一维数组,shape是(100,)
accuracy_cnt += np.sum(p == t[i:i+batch_size]) #np.sum(bool), 统计True的个数,也就是正确的个数

print("Accuracy:" + str(float(accuracy_cnt) / len(x)))

初体验以及遗留的问题:

  1. 如果没有Numpy基础理解这些源代码会比较难。
  2. 识别手写字是个很神奇的事情,也可以通过数组之间的运算来实现。
  3. 问题1:怎么设置frombuffer函数的offset参数,在源码中要设置为16或者8呢?
  4. 问题2:为什么MNIST的神经网络中隐藏层的神经元个数要设置成50和100,而不设置成其他?
  5. 问题3:怎么通过第一层每个神经元的像素值输入就可以判断数字是什么,这是什么原理?