MNIST
THE MNIST DATABASE of handwritten digits
BP 神经网络
模型训练
import os
import numpy as np
import torch
import torch.utils.data # 数据读取包
import matplotlib.pyplot as plt
from time import time
from torchvision import datasets, transforms
from torch import nn, optim
cd=os.path.dirname(__file__) # 定义当前工作目录为代码所在目录
# 定义图片的处理方式
transf=transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.5,), (0.5,)),])
# 下载并加载训练数据集
train_set = datasets.MNIST(cd+'/train_set', # 下载至代码文件夹中
download=not os.path.exists(cd+'/train_set'), # 不重复下载
train=True, # 定义为训练集
transform=transf # 图片的处理
)
# 构建数据集的DataLoader对象
train_loader = torch.utils.data.DataLoader(train_set, batch_size=64, shuffle=True) # 定义训练集,64张图片一组,打乱次序
dataiter = iter(train_loader) # 获取一个训练集的迭代器
# print(images.shape) # torch.Size([64, 1, 28, 28]) 64张图片一组,每张图片一个颜色通道,尺寸为28x28
# print(labels.shape) # torch.Size([64])
# plt.imshow(images[0].numpy().squeeze(), cmap='gray_r')
# 以灰度图格式展示第一张图片,使用squeeze()函数将images[0]转换为二维矩阵(去掉多余的维度)
class NeuralNetwork(nn.Module):
def __init__(self):
super().__init__()
# 使用nn.Sequential创建前向传播的序列
self.model=nn.Sequential(nn.Linear(28*28, 128), # 输入层有128个神经元,接受所有像素的输入
nn.ReLU(), # 使用ReLU函数做激活函数
nn.Linear(128,64), # 隐藏层有64个神经元
nn.ReLU(),
nn.Linear(64,10), # 输出层有10个神经元
nn.LogSoftmax(dim=1)
)
def forward(self,x):
# 定义前向传播,x:图片数据(硬性规定)
# x-shape为(64, 1, 28, 28),将其转化为(64,784)
x=x.view(x.shape[0], -1)
#前向传播
x=self.model(x)
return x
model=NeuralNetwork()
# 使用负对数似然损失函数定义神经网络的损失函数
C=nn.NLLLoss()
# 定义优化器,使用随机梯度下降法,学习率手动设置,momentum默认为0.9(用于防止过拟合)
r=float(input("请输入学习率:"))
Optimizer=optim.SGD(model.parameters(), lr=r, momentum=0.9)
time0=time() # 记录当前时间
epochs=15 # 训练15轮
for i in range(93):
images, labels = next(dataiter) # 迭代训练集元素
for e in range(epochs):
running_loss=0 # 本轮损失
for images, labels in train_loader:
# 前向传播
output = model(images)
# 计算损失
loss = C(output,labels)
# print(output)
# print(labels)
# k=input()
# 反向传播
loss.backward()
# 更新权重
Optimizer.step()
# 清空梯度
Optimizer.zero_grad()
# 累加损失
running_loss += loss.item()
else:
# 一轮结束后打印本轮的损失函数
print("Set {} with Epoch {} - Training loss: {}".format(i+1,e+1, running_loss/len(train_loader)))
# 打印总训练时间
print("\nTraining Time (in minutes) =",(time()-time0)/60)
torch.save(model, cd+"/BPM") # 存储模型文件至当前文件夹下的"BPM"文件
模型测试
import os
import numpy as np
import torch
import torchvision
import torch.utils.data # 数据读取包
import matplotlib.pyplot as plt
from time import time
from torchvision import datasets, transforms
from torch import nn, optim
from sklearn import metrics # sklearn的包名是scikit-learn,装sklearn是没用的
# 定义神经网络
class NeuralNetwork(nn.Module):
def __init__(self):
super().__init__()
# 使用nn.Sequential创建前向传播的序列
self.model=nn.Sequential(nn.Linear(28*28, 128), # 输入层有128个神经元,接受所有像素的输入
nn.ReLU(), # 使用ReLU函数做激活函数
nn.Linear(128,64), # 隐藏层有64个神经元
nn.ReLU(),
nn.Linear(64,10), # 输出层有10个神经元
nn.LogSoftmax(dim=1)
)
def forward(self,x):
# 定义前向传播,x:图片数据(硬性规定)
# x-shape为(64, 1, 28, 28),将其转化为(64,784)
x=x.view(x.shape[0], -1)
#前向传播
x=self.model(x)
return x
# 定义图片的处理方式
transf=transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.5,), (0.5,)),])
# 下载并加载测试数据集
test_set = datasets.MNIST('test_set', # 下载
download=not os.path.exists('test_set'), # 不重复
train=False, # 测试集
transform=transf # 图片处理
)
print("Information for test_set.")
print(test_set)
# 构建数据集的DataLoader对象
test_loader = torch.utils.data.DataLoader(test_set, shuffle=True) # 定义测试集
checkiter = iter(test_loader) # 获取一个测试集的迭代器
model=torch.load(os.path.dirname(__file__)+"/BPM") # 载入已经训练好的模型
torch.no_grad() # 测试无梯度
# F1指标分析
data=[] # 输出
lab=[] # 真实值
pred=[]
for img_, lab_ in test_loader:
tmp=model(img_)
data.append(tmp.detach().numpy())
lab.append(lab_.detach().numpy())
_, pre=torch.max(tmp.data, 1)
pred.append(pre.detach().numpy()[0])
data=np.array(data)
d_shape=data.shape
data=data.reshape(d_shape[0],d_shape[2])
lab=np.array(lab).reshape(d_shape[0])
pred=np.array(pred)
rep=metrics.classification_report(lab,pred)
print(rep)
# ROC曲线
sizeS=d_shape[0]
for i in range(10):
score_=[]
for j in range(sizeS):
score_.append(data[j][i])
fpr, tpr, thresholds=metrics.roc_curve(lab, np.array(score_), pos_label=i)
auc=metrics.auc(fpr, tpr)
plt.plot(fpr, tpr, lw=1, label="Number "+str(i)+", Auc={:.3f}".format(auc))
plt.plot([0, 1], [0, 1], color="navy", lw=1, linestyle="--")
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel("False Positive Rate")
plt.ylabel("True Positive Rate")
plt.title("ROC for each type of number")
plt.legend()
plt.show()
F1-Score的
手动分析
TP_=np.zeros(10) # 真阳性,预测数字为n且实际数字也为n的个数
FP_=np.zeros(10) # 假阳性,预测数字为n但实际数字不为n的个数(存在FPn中)
FN_=np.zeros(10) # 假阴性,预测数字不为n但实际数字为n的个数
TN_=np.zeros(10) # 真阴性,预测数字不为n且实际数字也不为n的个数
# 记录以目标数字为真实值的测试样例标签以及对应的输出值,用于绘制ROC
Roc_Label=[[],[],[],[],[],[],[],[],[],[]] # 标签
Roc_Output=[[],[],[],[],[],[],[],[],[],[]] # 输出值
for (imgs, labels) in test_loader:
pred=model(imgs)
shape=pred.shape
for j in range(shape[0]):
cnt+=1 # 统计总的测试次数
ans=labels[j] # 实际数字
flag=pred[j][0]
res=0 # 预测数字
for k in range(shape[1]):
if(pred[j][k]>flag): # 搜索概率最大的
flag=pred[j][k]
res=k
if(res!=ans): # 将一次测试拆成十份分别判断各类别的四性质,但总的比例不改变
for ii in range(10):
if(ii==res):
FP_[res]+=1 # 预测数字的假阳
elif(ii==ans):
FN_[ans]+=1 # 实际数字的假阴
else: # 其他数字的真阴
TN_[ii]+=1
else:
for ii in range(10):
if(ii!=res): # 真阴
TN_[ii]+=1
else: #真阳
TP_[ii]+=1
Roc_Label[ans].append(res)
Roc_Output[ans].append(pred[j][ans].item()) # 真实值的对应概率,pred提取出dim=0的tensor类型的变量,用item()将其转化为数值
# 四性质
TP=TP_.sum() # 真阳
FP=FP_.sum() # 假阳
FN=FN_.sum() # 假阴
TN=TN_.sum() # 真阴
# 三指标+F1-Score
pec=100 # 百分
acu=((TP+TN)/(TP+TN+FP+FN)) # 准确率
pre=TP/(TP+FP) # 精确率
rec=TP/(TP+FN) # 召回率
F1=(2*pre*rec)/(pre+rec) # F1
# ROC处理
tpr=[[]]*10 # 获取10*1的列表
fpr=[[]]*10
auc=[[]]*10
for i in range(10): # 按十个类别分别绘制ROC曲线并叠加
labels=np.array(Roc_Label[i]) # 取出标签
op=np.array(Roc_Output[i]) # 取出输出值
fpr_tmp, tpr_tmp, theresholds = metrics.roc_curve(labels, op, pos_label=i)
tpr[i]=tpr_tmp # 真阳率
fpr[i]=fpr_tmp # 真阴率
auc[i]=metrics.auc(fpr_tmp, tpr_tmp) # 计算auc
F1-Score可以综合类别统计,也可以做二元统计。这里采取了宏平均,即各类别评分权值,也可根据支持率做宏加权处理。
同时ROC做了错误处理,只取了各类别只有TP而没有TN的情况,这会使得绘制的ROC曲线的FPR(即横坐标)的数量急剧减少
train-images.idx3-ubyte 文件格式
00 00 08 03
03表示数据维度为3维(60000x28x28)
00 00 EA 60
EA60 H为60000 D,表明有6w组图片数据
00 00 00 1C
x2 表示图片尺寸为28x28=784 Bytes,每个像素值在0-255之间,用一个字节表示
(784*60000+16=47040016 D=2CDC610 H,恰好对应文件的字节数)
随后就是第一组图片数据。
输出层设计-Softmax
使用神经网络进行分类任务时,分类的类别数目即为输出层的神经元个数。输出函数使用softmax函数:约束各个输出点的值在(0,1)之间且和为1。输出结果表示该类别的概率,取概率最大的结点所代表的类别为预测类别
对于手写数字识别,输出层将有10个神经元,分别代表数字0-9,如某个案例的标签为9,则训练目标就是使得0-8号神经元的输出尽可能接近0,而9号神经元的输出尽可能接近1。
softmax函数会放大数值间的差距,但也存在溢出的风险,因此计算指数时可以先将输出值减去最大值,来规避溢出的风险。
参考:[
]
Pytorch
指标
F1指标:https://zhuanlan.zhihu.com/p/62180318
ROC曲线必须按类别单独绘制,它只服务于二分类器。(顺便学学matplotlib.pyplot的可视化)。使用sklearn提取TPR与FPR时需要的两组数据为预测序列与(真实输出数字的概率,而非预测结果的概率)输出值,将其视为二值分类器,则序列的范围要取在全体序列上。最开始我取了只有TP而没有TN的情况,也就是只取了真实值为该类别的序列。
参考:[
ROC1,
ROC2,
]
Numpy小技巧
-
tp_num=(labels==1).sum()
:统计labels中1出现的次数。labels==1
返回一个逻辑数组,求和即可。 -
output_sort=np.flipud(np.argsort(outPut)) # argsort获取从小到大排序的索引数组,flipud将其逆序,即变为从大到小的索引序列。 labels=labels[output_sort] # 利用数组索引按输出值从大到小同时排序标签与输出值 outPut=outPut[output_sort]
参考:numpy排序
AdaBoost
模型训练
import numpy as np
import os
from sklearn.datasets import fetch_openml,load_iris
from sklearn.ensemble import AdaBoostClassifier
import joblib # joblib在高版本的sklearn中已经被移除
import time
# 获取mnist数据集
mnist=fetch_openml('mnist_784', version=1, cache=True) #默认需要已安装pandas包
mnist.target=mnist.target.astype(np.int8) # 标签类型转换为int8
mnist_train_data=np.array(mnist.data[:60000]) # 从pandas数据集转化为数组
mnist_train_target=np.array(mnist.target[:60000]) # 前6万个设置为训练数据集
mnist_test_data=np.array(mnist.data[60000:]) # 后1万个为测试数据集
mnist_test_target=np.array(mnist.target[60000:])
# Adaboost
# 定义分类器
model=AdaBoostClassifier(
base_estimator=None, # 基分类器的种类默认为决策树模型
n_estimators=500, # 基分类器个数
learning_rate=0.1, # 学习率
algorithm='SAMME.R', # boosting算法默认选用SAMME.R,对样本按错分率划分
random_state=None # 随机数种子
)
time0=time.time()
# 训练模型
model=model.fit(mnist_train_data,mnist_train_target) # 输入为数据与标签的ndarray,返回仍是同类分类器
# 计时模型训练时间
time1=time.time()
timed=(time1-time0)/60
print("It costs time:"+"%0.2f"%timed+" Minutes") # 类c++的printf写法
# 保存模型
cd=os.path.dirname(__file__)
modelName="AdaB"
joblib.dump(model, cd+"/"+modelName)
print("模型准确率:{:2.1f}".format(model.score(mnist_test_data,mnist_test_target)*100))
模型测试
import os
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from sklearn.datasets import fetch_openml
from sklearn.ensemble import AdaBoostClassifier
import sklearn.metrics as mtr
import joblib
cd=os.path.dirname(__file__)+"/"
# 读取模型
modelName="AdaB"
model=joblib.load(cd+modelName)
# 读取数据
print("Reading datas...")
mnist=fetch_openml(
"mnist_784",
version=1,
cache=True
)
mnist_data=np.array(mnist.data)
mnist_tar=np.array(mnist.target).astype(np.int8) # 一定要转换类型,默认为字符,影响预测
test_data=mnist_data[60000:]
test_tar=mnist_tar[60000:]
# 图表预设
# https://my.oschina.net/swuly302/blog/94805 matplotlib RC自定义配置,美化plot图表
mpl.rc("axes", labelsize=14) # x轴y轴字体大小
mpl.rc("xtick", labelsize=12) # x轴刻度标签字体大小
mpl.rc("ytick", labelsize=12)
mpl.rcParams['font.sans-serif']=[u'SimHei'] # 衬线
mpl.rcParams['axes.unicode_minus']=False # 减号使用连字符号
# 展示图片
def plot_digit(data):
image=data.reshape(28, 28)
plt.imshow(image, cmap=mpl.cm.binary, # 黑底白字
interpolation="nearest") # 处理图片
plt.axis("off") # 无轴线
plt.show() # 展示图片
# some_digit=mnist_train_data[36000]
# plot_digit(some_digit)
# 查看已经训练的模型信息
def someInformation(mdl):
print(len(mdl.estimators_)) # 查看分类器的个数
print(mdl.estimator_weights_) # 查看分类器权重
print(mdl.estimator_errors_) # 查看分类器错分率
print(mdl.feature_importances_) # 特征重要性
print("Predicting...")
# 模型评估
pec=100
acu=model.score(test_data,test_tar)
print("模型准确率:{:.2f}%".format(acu*pec))
# F1
pred=model.predict(test_data)
rep=mtr.classification_report(test_tar, pred) # 获取数据分析报告
print(rep)
# 按类别绘制ROC曲线
score=model.predict_proba(test_data)
sizeS=10000
for i in range(10):
score_=[]
for j in range(sizeS):
score_.append(score[j][i]) # 提取目标输出
fpr_, tpr_, thresholds=mtr.roc_curve(pred, np.array(score_), pos_label=i)
auc=mtr.auc(fpr_, tpr_)
plt.plot(fpr_, tpr_, lw=1, label="Number "+str(i)+", Auc={:.3f}".format(auc))
plt.plot([0, 1], [0, 1], color="navy", lw=1, linestyle="--")
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel("False Positive Rate")
plt.ylabel("True Positive Rate")
plt.title("ROC for each type of number")
plt.legend()
plt.show()
sklearn
所读取的Mnist集中的标签数据 .target
默认为字符串类型,需要将其转换为数值类型后再用作模型预测,否则会出现预测准确率始终为0的情况。
转化类型:
mnist.target=mnist.target.astype(np.int8)
参考:[
]
Python编码
编码总是让人头疼的问题,各种解码编码存储格式真分不清。
u'abc'
将字符串abc用unicode的形式存储,unicode是"统一码
",它包括了ASCII码,一般使用UTF-8(16,32)进行编码。
不同的编码方式对字符串的处理准则不同,最后编码出的结果就不同。解码 encode
会将字符串转换为unicode对象,编码 decode
会将unicode对象转换为普通字符串。
在使用 u'abc'
时,python会预先用utf-8编码识别该字符并将其转换成unicode对象,相当于提前做了编码标记,哪怕代码文件的编码格式发生了变化,解析该对象时所用的编码仍是固定的。
神奇的Python语法
-
k=sorted([(a, c) for a, c in enumerate(b)]) # -> [(0, [1, 2]), (1, [3, 4]), (2, [5, 6])] k1=np.array(k) # -> array([[0, list([1, 2])], # [1, list([3, 4])], # [2, list([5, 6])]], dtype=object)
-
time.time()
获取程序运行至当前位置所花费的时间