'''
softmax回归从零开始实现
'''
import torch
import torchvision
import numpy as np
import sys
sys.path.append('..')
import d2lzh_pytorch as d2l
'''
获取数据
'''
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
print(train_iter, test_iter)
'''
初始化模型参数
参数选择原因
每个样本输入的是长和宽(均为28),所以模型的输入向量长度就是28*28=784
一共有十个图像类别,所以单层神经网络有10个输出
因此softmax的权重跟偏差数分别为784*10 和 1*10
'''
num_inputs = 784
num_output = 10
w = torch.tensor(np.random.normal(0, 0.01, (num_inputs, num_output)), dtype=torch.float)
b = torch.zeros(num_output, dtype=torch.float)
w.requires_grad_(requires_grad=True)
b.requires_grad_(requires_grad=True)
'''
实现softma运算
tensor 对多维度操作
我们需要实现对其中的一行或者一列进行求和,并在结果中保留行(dim=0)or列(dim=1)这两个维度(keepdim=True)
求和不影响原来的张量
X = torch.tensor([[1,2,3],[4,5,6]])
print(X.sum(dim=0, keepdim=True))
print(X)
tensor([[5, 7, 9]])
tensor([[1, 2, 3],
[4, 5, 6]])
'''
def softmax(X):
X_exp = X.exp()
partition = X_exp.sum(dim=1, keepdim=True)
'''
这个函数先对每个元素进行指数运算(exp),
然后对exp矩阵同行元素进行求和,
最后领矩阵每行各元素与该行元素之和相除
得到矩阵每行元素和为1,即每行代表了该样本在不用类目下的预测概率
:param X:
:return: 返回的矩阵是一个预测矩阵,任何一个元素代表一个样本在格式输出类别的预测概率
'''
return X_exp / partition
'''
定义模型
'''
def net(x):
return softmax(torch.mm(x.view((-1, num_inputs)),w)+b)
'''
定义损失函数
softmax回归使用交叉熵损失函数,为了得到标签的预测概率使用gather函数
torch.gather(input, dim, index, out=None)
维度dim按照索引列表index从input中选取指定元素
y_hat = torch.tensor([[0.1, 0.3, 0.6],[0.3, 0.2, 0.5]])
print(y_hat)
y = torch.LongTensor([0, 2])
print(y_hat.gather(1, y.view(-1, 1)))
tensor([[0.1000, 0.3000, 0.6000],
[0.3000, 0.2000, 0.5000]])
tensor([[0.1000],
[0.5000]])
gather函数会按照index的torch的形状进行选取input里面的元素
dim=1 则按照行进行选择
即本例子选择的是第一行的第一个(索引为0的元素),第二行的第三个(索引为2的元素)
y_hat = torch.tensor([[0.1, 0.3, 0.6],[0.3, 0.2, 0.5]])
y = torch.LongTensor([[0, 0, 1],[1, 0, 1]])
print(y_hat.gather(0, y))
tensor([[0.1000, 0.3000, 0.6000],
[0.3000, 0.2000, 0.5000]])
tensor([[0.1000, 0.3000, 0.5000],
[0.3000, 0.3000, 0.5000]])
dim为0的 按列进行选择
所以得到的结果应该是
第一列的第一个 第二列的第一个 第三列的第二个
第一列的第二个 第二列的第一个 第三列的第二个
# 使用小技巧 y.view(-1, 1) 会将y转换成总长度除以1得到的行数,1为列的张量
# 举例说明
# y = torch.arange(15)
# y.view(5,3)
# y.view(-1,3)
# y.view(5,-1)
# 以上三个都是一样的效果
'''
def cross_entropy(y_hat, y):
return - torch.log(y_hat.gather(1,y.view(-1,1)))
'''
计算分类准确率 (分类预测正确的数量占总预测数量的比)
使用y_hat.argmax(dim=1)函数 返回的是矩阵y_hat每行最大的索引,
且返回结果的形状跟y_hat形状一样
'''
def accuracy(y_hat, y):
'''
:param y_hat: 预测值
:param y: 真实值
:return:
'''
return (y_hat.argmax(dim=1) == y).float().mean().item()
'''
0.5
通例子可以看出来
预测值中第一行预测值为0.6 索引为 2
实际值中正确值为0.1 索引为0
预测值中第一行预测值为0.5 索引为 2
实际值中正确值为0.5 索引为2
'''
def evaluate_accuracy(data_iter, net):
acc_sum, n = 0.0, 0
for x, y in data_iter:
acc_sum += (net(x).argmax(dim=1) == y).float().sum().item()
n += y.shape[0]
return acc_sum/n
print(evaluate_accuracy(test_iter, net))
'''
训练模型
使用小批量随机梯度下降来优化模型的损失函数
'''
def train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, params=None, lr=None, optimizer=None):
for epoch in range(num_epochs):
train_l_sum, train_acc_sum, n = 0.0, 0.0, 0
for X, y in train_iter:
y_hat = net(X)
l = loss(y_hat, y).sum()
if optimizer is not None:
optimizer.zero_grad()
elif params is not None and params[0].grad is not None:
for param in params:
param.grad.data.zero_()
l.backward()
if optimizer is None:
d2l.sgd(params, lr, batch_size)
else:
optimizer.step()
train_l_sum += l.item()
train_acc_sum += (y_hat.argmax(dim=1) == y).sum().item()
n += y.shape[0]
test_acc = evaluate_accuracy(test_iter, net)
print(f'epoch {epoch + 1}, loss {train_l_sum / n}, train acc {train_acc_sum / n}, test acc {test_acc}')
num_epochs, lr = 5, 0.1
train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, batch_size, [w, b], lr)