1.个人理论理解
softmax regression 个人认为应该属于多分类,即有多个输出,然后选取概率最大的作为最终预测结果。
从w@x+b到概率的转化需要使用softmax函数: 该公式可以实现由数值大小到概率大小的转化。
对于多分类问题,损失函数应该使用交叉熵损失: 交叉熵损失表示预测标签和真实标签相差程度。因为预测值概率大多是小于1的,所以交叉熵损失是一个负数,但试想两种情况,对真实标签的预测正确概率分别为0.1和0.9两种情况,显然0.9的损失更小(损失值更接近0)。
2.数据加载
使用Fashion_Mnist数据集,该数据集分两部分,训练集和测试集,训练集只用来训练参数,测试集用来验证。训练集和测试集标签是被正确标记的,之所以区分训练集和测试集是为了防止在过拟合(在训练集上表现良好但是在测试集上表现并没有很好)产生的情况下对已经训练过的数据进行预测,在实际应用中我们观察模型的优劣性是观察其对没有出现过的事件进行预测的正确率来评价的,而对已经出现过的数据进行预测显然是没有实际意义的。在训练集上的损失成为训练损失,在测试集上的损失成为泛化损失。
训练集共60000个数据,测试集共10000个数据。每次训练60000个数据显然是不可行的,因此我们定义了batch_size,即每次拿出一部分数据进行训练,直到训练集中所有数据均被取过一遍后再进行下一个60000个数据的训练(和上一个6000个数据是一样的只是进行了顺序打乱处理),测试集同理。batch_size通常取2的指数次方大小,这是因为计算机更擅长计算2的指数次方大小的数据
#数据加载
def load_data(batch_size):
#ToTensor将PIL类型转换为tensor.float32类型,且每位除255,使其各元素值落于0-1之间
#且A*B*C的张量会转换为C*A*B
trans = transforms.ToTensor()
#训练集
train_picture = torchvision.datasets.FashionMNIST(root='../data',train=True,
transform=trans,download=True)
#测试集
test_picture = torchvision.datasets.FashionMNIST(root='../data', train=False,
transform=trans,download=True)
#data.DataLoader,将训练数据分为多组,每次抛出一组数据,直到所有数据抛出
#shuffle参数会在每个epoch前打乱数据,num_workers为线程数,即使用多少个线程提取这些数据
#数据返回为多组数据,不够一组按一组计算,每组数据有batch_size张图片和batch_size个对应标签
return (data.DataLoader(train_picture,batch_size,shuffle=True,num_workers=0),
data.DataLoader(test_picture,batch_size,shuffle=False,num_workers=0))
该函数实际意义不大,只是为了更加深入了解Fashion_Mnist数据。之后想要使用Fashion_Mnist数据集可以使用train_pic, test_pic = d2l.load_data_fashion_mnist(batch_size)即可。
3.文本标签转换
#标签值对应的文本标签
def get_labels(labels):
#返回文本标签
text_labels = ['t-shirt', 'trouser', 'pullover', 'dress', 'coat',
'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot']
return [text_labels[i] for i in labels]
softmax regression的最终输出为索引值,为便于观察,我们将索引值转换为文本标签
4.softmax函数
#softmax函数:e^xi/sum(e^x)
def softmax(X):
e_X = torch.exp(X)
e_X_sum = e_X.sum(1,keepdim=True) #sum结果为列向量
return e_X/e_X_sum #利用广播原理,要保证两张量维度一致
softmax:
张量运算需要注意维度一致,而sum操作若不指明会进行降维,所以要使用keepdim参数
5.定义网络
#定义网络y_hat=XW+b
def net(X,W,b):
#X初始为四维矩阵[个数,通道数,行,列],因此需要将X展开为多个向量组合,每列代表改组的一张图片
y_hat = torch.matmul(X.reshape(-1,W.shape[0]),W)+b
return softmax(y_hat)
使用线性回归计算个标签的输出大小,返回值需要进行softmax运算将个标签输出大小转换为概率大小
对于一张灰度图片,其可以看作是一个二维张量,我们将每张灰度图片转换为一个行向量(即一行表示一张图片)来处理线性回归运算并寻找线性关系
6.定义损失函数(交叉熵损失)
#定义交叉熵H(X)=-sum( log(q(xi)) )
#q(xi)是对真实标签的预测值,因此交叉熵计算值为真实类别的概率取对数再取负
def cross(y_hat,y):
#eg:
#y = torch.tensor([0, 2])
#y_hat = torch.tensor([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])
#print(y_hat[[0, 1], y])
#输出为:tensor([0.1000, 0.5000])
#此处range(len(y_hat))=[0,1]索引出0.1和0.5 将-ln(0.1)+-ln(0.5)赋值给cross_entropy
#利用y加入的二维索引来导出真实类别的概率再取对数
loss = -torch.log(y_hat[range(len(y_hat)),y])
return loss
y_hat [ range ( len ( y_hat ) ) , y ]:二维索引检索真实标签的预测概率,结果是一个批量大小的张量
7.定义梯度下降函数更新参数
#优化函数
def bgd(W,b,lr,batch_size):
with torch.no_grad():
#因为loss是batch_size个数据损失和,所以loss梯度也是和的梯度,因此要除batch_size
W -= lr * W.grad/batch_size
b -= lr * b.grad/batch_size
W.grad.zero_()
b.grad.zero_()
return W,b
我们使用的是平均损失来更新参数,所以梯度需要除批量大小
8.定义正确率函数,观察正确率
#优劣指标,正确率
def accuracy(y_hat,y):
if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
y_hat = y_hat.argmax(axis=1) #获取每行最大元素的索引
#将预测类别与真实y元素进行比较,test是一个比较y_hat与y是否相等的bool类型false和true
#false是0,true是1,然后再求和得到分类正确的个数
test = (y_hat == y)
return test.sum()/len(y)
输入的数据经过网络后得到各标签的概率值,显然我们应选取概率最大的那个作为预测值,即使用argmax函数选则概率最大的数值的索引。因为我们规定每行表示一张图片,所以对这张图片进行计算的最终结果也应该是一行概率值,使用argmax(axis=1)后可以选取该行所有标签最大的预测概率的索引。
9.定义初始预测正确率函数
#初始正确率
def init_acc(train_pic,init_W,init_b):
for X, y in train_pic:
y_hat = net(X, init_W, init_b)
acc = accuracy(y_hat, y) # 预测正确率
print('init accuracy is :',acc)
初始正确率理应处于0.1附近
10.定义训练函数
#训练函数
def train(train_pic,train_W,train_b,epoch,batch_size):
init_acc(train_pic,train_W,train_b) #初始预测正确率
for i in range(epoch):
for X, y in train_pic:
y_hat = net(X,train_W,train_b) #softmax(X*W+b)
loss = cross(y_hat, y) #交叉熵损失
loss.sum().backward()
lr = 0.1 # 训练步长
train_W,train_b = bgd(train_W,train_b,lr,batch_size) #更新参数
acc = accuracy(y_hat,y) #查看预测正确率
print('this times is '+str(i+1)+', accuracy is :',acc)
print('loss sum is :',loss.sum())
return train_W,train_b
每个epoch有60000个训练数据,批量训练中在本次epoch中我们每次取batch_size张图片进行训练,直到所有数据均被取一边后开始下一个epoch的训练
经过epoch次的训练得到较为可观的参数集,通过函数返回值进行返回训练后的参数
11.定义预测函数
使用经过训练后得到的参数集来对新数据预测,通过上述定义过的正确率函数来评价预测结果的优劣。将数字标签转换为文本标签可以更直观地观察。
#预测
def prediction(test_pic,final_W,final_b):
for picture,lable in test_pic:
y_hat = net(picture,final_W,final_b)
pro = y_hat.argmax(axis=1) #以每行最大概率的索引作为预测值
print('\n')
print('预测标签',get_labels(pro)) #索引转文本
print('真实标签',get_labels(lable))
acc = accuracy(y_hat, lable)
print('test_pic accuracy is :',acc)
return 0
12.softmax回归
#softmax回归
def softmax_regression():
# 定义批量大小,加载数据,初始化参数
batch_size = 256
train_pic,test_pic = load_data(batch_size)
# 输入28*28的图片
num_input = 784
# 输出10个类别
num_output = 10
# W,b均初始化为全0
init_W = torch.normal(0,0.1,size=(num_input,num_output),requires_grad=True)
init_b = torch.normal(0,0.1,size=(1,num_output),requires_grad=True)
epoch = 100
# 训练后得到最终W和最终b
final_W,final_b = train(train_pic,init_W,init_b,epoch,batch_size)
prediction(test_pic,final_W,final_b)
return
13.拟合并预测
#拟合并预测
softmax_regression()
14.运行结果
10个epoch后正确率在0.85附近
对测试集进行预测,正确率也可以在0.85附近