踩坑:矩阵乘法

    1. w = torch.Tensor([[2],
    2. [-3.4]]) # 定义了一个1*2维的张量
    3. features = torch.randn(2, 1000, dtype=torch.float32) # 定义了一个2*1000的张量
    4. labels = features*w # 虽然维度没错可以相乘,但会报错
    5. labels = torch.mm(features, w) # 需要调用mm函数完成矩阵乘法

    python list(tuple)函数用于将元组将元组等其他变量转变为list

    1. indices = list(range(num_examples)) # range返回的并不是list,该语句的含义是构建一个值从0开始的列表

    shuffle(list)函数用于将list中元素随机打乱

    1. random.shuffle(indices) # 打乱刚才得到的list

    LongTensor(list)指令用于构建长度较长的行向量

    1. # 构建一个LongTensor,它的值从indice数组中的第i个取到第i+batchsize个,它的长度大小=batch_size
    2. j = torch.LongTensor(indices[i: min(i + batch_size, num_examples)])

    torch.linspace(start, end, steps=100, out=None)方法返回一个1维张量,包含在区间start和end上均匀间隔的step个点,输出张量的长度由steps决定

    1. #生成0到10的4个数构成的等差数列
    2. a = torch.linspace(0,10,steps=4)
    3. print(a)
    4. #生成0到10的5个数构成的等差数列
    5. b = torch.linspace(0,10,steps=5)
    6. print(b)
    7. 结果:
    8. tensor([ 0.0000, 3.3333, 6.6667, 10.0000])
    9. tensor([ 0.0000, 2.5000, 5.0000, 7.5000, 10.0000])

    index_select(tensor, dim, index)可以从张量的某个维度的指定位置选取数据

    • dim:表示从第几维挑选数据,类型为int值
    • index:表示从第一个参数维度中的哪个位置挑选数据,类型为torch.Tensor类的实例 ```python a = torch.linspace(1, 12, steps=12).view(3, 4) print(a) b = torch.index_select(a, 0, torch.tensor([0, 2])) # 选择第0维,索引0和2位置的数据 print(b) print(a.index_select(0, torch.tensor([0, 2]))) # tensor对象也可调用该方法 c = torch.index_select(a, 1, torch.tensor([1, 3])) # 选择第1维,索引1和3位置的数据 print(c)

    结果: tensor([[ 1., 2., 3., 4.], [ 5., 6., 7., 8.], [ 9., 10., 11., 12.]]) tensor([[ 1., 2., 3., 4.], [ 9., 10., 11., 12.]]) tensor([[ 1., 2., 3., 4.], [ 9., 10., 11., 12.]]) tensor([[ 2., 4.], [ 6., 8.], [10., 12.]])

    1. yieldreturn
    2. - yield返回的是一个生成器对象,该对象可以迭代遍历和通过next()方法取出对象中的值。比较节约内存空间。保存的是生成数据的方式。可以达到随用随取的效果。
    3. - return直接结束该函数的运行,return 后面的代码块不会执行,返回该函数的执行结果。
    4. ```python
    5. def func():
    6. start_time = time.time()
    7. list = []
    8. for i in range(1, 100000000):
    9. list.append(i)
    10. end_time = time.time()
    11. cost_time = end_time - start_time
    12. print(cost_time)
    13. yield list
    14. func() # 随用随取,节省内存空间。
    15. next(func())
    16. def func():
    17. start_time = time.time()
    18. list = []
    19. for i in range(1, 100000000):
    20. list.append(i)
    21. end_time = time.time()
    22. cost_time = end_time - start_time
    23. print(cost_time)
    24. return list # 相同的代码
    25. func() # 过了几十秒终于跑完了
    1. def data_iter(batch_size, features, labels):
    2. num_examples = len(features)
    3. indices = list(range(num_examples))
    4. random.shuffle(indices)
    5. for i in range(0, num_examples, batch_size):
    6. j = torch.LongTensor(indices[i:min(i + batch_size, num_examples)])
    7. yield features.index_select(0, j), labels.index_select(0, j)
    8. # 通过迭代方式就完成了每次小批量的训练,每次10步
    9. for X, y in data_iter(10, features, labels):

    完整程序代码如下:

    1. import torch
    2. from IPython import display
    3. from matplotlib import pyplot as plt
    4. import random
    5. def use_svg_display():
    6. # 用矢量图显示
    7. display.set_matplotlib_formats('svg')
    8. def set_fig_size(fig_size=(3.5, 2.5)):
    9. use_svg_display()
    10. # 设置图的尺寸
    11. plt.rcParams['figure.figsize'] = fig_size
    12. def data_iter(batch_size, features, labels):
    13. num = len(features)
    14. indices = list(range(num))
    15. random.shuffle(indices)
    16. for i in range(0, num, batch_size):
    17. j = torch.LongTensor(indices[i:min(num, i + batch_size)]) # 最后一次可能不足一个batch
    18. yield features.index_select(0, j), labels.index_select(0, j)
    19. def linear_regression(w, x, b): # 这个跟教程中给的函数不一样,w与b交换了一下位置
    20. return torch.mm(x, w) + b
    21. def squared_loss(y_hat, y):
    22. # 注意这里返回的是向量, 另外, pytorch里的MSELoss并没有除以 2
    23. tmp = y.view(y_hat.size())
    24. return (y_hat - tmp) ** 2 / 2
    25. def sgd(params, lr, batch_size):
    26. for param in params:
    27. param.data -= lr * param.grad / batch_size # 注意这里更改param时用的param.data
    28. if __name__ == '__main__':
    29. num_inputs = 2
    30. num_examples = 1000
    31. w_true = torch.Tensor([[2],
    32. [-3.4]])
    33. b_true = 4.2
    34. features = torch.randn(num_examples, num_inputs, dtype=torch.float32)
    35. labels = torch.mm(features, w_true) + b_true
    36. labels += torch.normal(mean=0.0, std=0.01, size=labels.size())
    37. w = torch.normal(mean=0.0, std=0.01, size=[2, 1], dtype=torch.float32, requires_grad=True)
    38. b = torch.zeros(1, dtype=torch.float32, requires_grad=True)
    39. set_fig_size()
    40. plt.scatter(features[:, 1], labels, 1)
    41. plt.show()
    42. # 回归(学习)参数设定
    43. lr = 0.03
    44. num_epochs = 3
    45. net = linear_regression
    46. loss = squared_loss
    47. size = 10
    48. for epoch in range(3): # 训练模型一共需要num_epochs个迭代周期
    49. # 在每一个迭代周期中,会使用训练数据集中所有样本一次(假设样本数能够被批量大小整除) X和y分别是小批量样本的特征和标签
    50. for X, y in data_iter(size, features, labels):
    51. l = loss(net(w, X, b), y).sum() # l是有关小批量X和y的损失
    52. l.backward() # 小批量的损失对模型参数求梯度
    53. sgd([w, b], lr, size) # 使用小批量随机梯度下降迭代模型参数
    54. # 不要忘了梯度清零
    55. w.grad.data.zero_()
    56. b.grad.data.zero_()
    57. train_l = loss(net(w, features, b), labels)
    58. print('epoch %d, loss %f' % (epoch + 1, train_l.mean().item()))
    59. print(w_true, '\n', w) # 比较差异
    60. print(b_true, '\n', b)

    几个注意的地方:

    1. l = loss(net(w, X, b), y).sum()
    2. l.backward()

    由于pytorch只能使标量对其他变量求导,因此选取.sum()或.mean()转成标量后再都可以。在sgd函数处加上断点后发现选取.sum()函数后w与b的梯度普遍为两位数,而选取.mean()函数后w与b的梯度仅为一位数。其实原理很简单:
    线性回归(从零开始) - 图1
    这是sum形式的损失函数

    线性回归(从零开始) - 图2
    这是mean形式的损失函数

    用脚想想都知道,第一个对w和b求完导结果是第二个的bath_size倍。所以,使用.sum()函数w和b梯度更大。不过无所谓,反正参数学习的时候也要/batch_size,最后减的还是均值。

    所以说,选取mean函数后,sgd函数中学习参数时就无需/batch_size了,这样之后与原来的结果是一样的。

    还有,sgd([w, b], lr, size)里一开始忘记加上b了,折腾了很久时间。以后不能忘记偏置项参数也要进行更新。

    框架学习也没那么容易,一步一步来。