建议点赞收藏关注!持续更新至pytorch大部分内容更完。
本文已达到10w字,故按模块拆开,详见目录导航。
损失函数
损失函数loss,代价函数cost,目标函数objective
交叉熵=信息熵+相对熵
(1)熵用来描述一个事件的不确定性,不确定性越大则熵越大.
(2)信息熵是自信息的期望,自信息用于衡量单个输出单个事件的不确定性.
(3)相对熵又叫做KL散度,用于衡量两个分布之间的差异,即两个分布之间的距离,但不是距离的函数,不具备对称性。
下图公式中都是,P是真实的分布,Q是模型输出的分布,Q需要逼近、拟合P的分布.
(4)交叉熵用于衡量两个分布之间的相似度.
(5)P是真实的分布,即训练集中样本的分布。由于训练集是固定的,概率分布也是固定的,H§是个常数.故优化交叉熵等价于优化相对熵。
上图是一个伯努利分布,可以发现当概率是0.5时熵最大(为0.69),0.69这个数字经常会碰到:模型训练坏了、模型的训练第一个iteration,此时不具备判断能力,因为对于任何输出判别的概率都是1/2。
创建损失函数 (共18个)
nn.CrossEntropyLoss
nn.CrossEntropyLoss(weight=None, ignore_index=- 100, reduction='mean' )
'''
size_average=None, reduce=None,这俩个参数已废弃
weight:各类别loss设置权值
ignore_index:忽略某个类别而不计算它的loss
reduction:计算模式,可为none/sum/mean
none 逐个元素计算这么多个loss
sum 所有元素求和,返回标量
mean 加权平均,返回标量
'''
交叉熵这里的不是按照书中公式所计算的,这里其实是nn.LogSoftmax()与nn.NLLLoss()结合进行计算的。它采用softmax对数据进行了归一化,把数据值归一化到概率输出的形式。交叉熵损失函数常常用于分类任务中。交叉熵是衡量两个概率分布之间的差异,交叉熵值越低,表示两个概率分布越近。
运用softmax可以将输出值转换归一化到0~1之间作为概率。
首先上图 这里x是输出的一个概率值,class是一个类别值。
第二式中的(分式)就是softmax的公式,对比第一式即交叉熵公式,可知 这个xi是已经取到的,所以p(xi)=1,并且第二式只是计算其中一个的交叉熵,故也没有N项求和符号。
当调用这个方法时传入了weight参数,则计算公式为第三式
# fake data
#二分类任务,输出神经元2个,batchsize是3,即三个样本:[1,2] [1,3] [1,3]
inputs = torch.tensor([[1, 2], [1, 3], [1, 3]], dtype=torch.float)
#dtype必须是long,有多少个样本,tensor(1D)就有多长。而且这里的类别label也是从0开始标号的
target = torch.tensor([0, 1, 1], dtype=torch.long)
# ----------------------------------- CrossEntropy loss: reduction -----------------------------------
flag = 0
# flag = 1
if flag:
# def loss function
loss_f_none = nn.CrossEntropyLoss(weight=None, reduction='none')
loss_f_sum = nn.CrossEntropyLoss(weight=None, reduction='sum')
loss_f_mean = nn.CrossEntropyLoss(weight=None, reduction='mean')
# forward
loss_none = loss_f_none(inputs, target)
loss_sum = loss_f_sum(inputs, target)
loss_mean = loss_f_mean(inputs, target)
# view
print("Cross Entropy Loss:\n ", loss_none, loss_sum, loss_mean)
#输出为[1.3133, 0.1269, 0.1269] 1.5671 0.5224
# --------------------------------- compute by hand
flag = 0
# flag = 1
if flag:
idx = 0
input_1 = inputs.detach().numpy()[idx] # [1, 2]两个神经元
target_1 = target.numpy()[idx] # [0]
# 第一项
x_class = input_1[target_1]
# 第二项 map指对其每一个神经元进行运算
sigma_exp_x = np.sum(list(map(np.exp, input_1)))
log_sigma_exp_x = np.log(sigma_exp_x)
# 输出loss
loss_1 = -x_class + log_sigma_exp_x
print("第一个样本loss为: ", loss_1)
# ----------------------------------- weight -----------------------------------
flag = 0
# flag = 1
if flag:
# def loss function
'''
有多少个类别weights这个向量就要设置多长,
每一个类别都必须设置weight,
如果不想关注这个weight,就将其设置为1就不会变的。
如果类别是mean,则每个weight数值多大不重要,重要在于他们之间的比例。
结果可以看出来,原先得到的loss基础上,如果数据label==0则用weight[0]=1倍,故与之前相等。而label==1的后两个数据的loss变成了之前的2倍(因为weight==2)
在sum模式下,weight==2也代表label==1的这一个数据就占两份1在分母中,在本例中为 三个数据loss之和/(1+2+2)
'''
weights = torch.tensor([1, 2], dtype=torch.float)
# weights = torch.tensor([0.7, 0.3], dtype=torch.float)
loss_f_none_w = nn.CrossEntropyLoss(weight=weights, reduction='none')
loss_f_sum = nn.CrossEntropyLoss(weight=weights, reduction='sum')
loss_f_mean = nn.CrossEntropyLoss(weight=weights, reduction='mean')
# forward
loss_none_w = loss_f_none_w(inputs, target)
loss_sum = loss_f_sum(inputs, target)
loss_mean = loss_f_mean(inputs, target)
# view
print("\nweights: ", weights)
print(loss_none_w, loss_sum, loss_mean)
#权值为[1,2],则输出[1.3133, 0.2539, 0.2539] 1.8210 0.3642
# target=[0,1,1]所以对应0的需要乘以权值1,对应1的需要乘以权值2.
# 1.3133是1.3133*1=1.3133,0.2530是0.1269*2=0.2539,0.2530是0.1269*2=0.2539.
#0.3642是1.8210/(1+2+2)=0.3642,分母是权值的份数(5).
# --------------------------------- compute by hand :手动计算
flag = 0
# flag = 1
if flag:
weights = torch.tensor([1, 2], dtype=torch.float)
#weights_all=5 计算sum公式中除掉的分母,份数
weights_all = np.sum(list(map(lambda x: weights.numpy()[x], target.numpy()))) # [0, 1, 1] ---> # [1 2 2]
mean = 0
loss_sep = loss_none.detach().numpy()
for i in range(target.shape[0]):
#输出每一个样本在这平均值loss中贡献的数目
x_class = target.numpy()[i]
tmp = loss_sep[i] * (weights.numpy()[x_class] / weights_all)
mean += tmp
print(mean)
nn.NLLLoss
nn.NLLLoss(weight=None, size_average=None, ignore_index=- 100, reduce=None, reduction='mean')
'''
实现负对数似然函数中的负号功能,简单来说就是取负号
'''
# fake data
#二分类任务,输出神经元2个,batchsize是3,即三个样本:[1,2] [1,3] [1,3]
inputs = torch.tensor([[1, 2], [1, 3], [1, 3]], dtype=torch.float)
#dtype必须是long,有多少个样本,tensor(1D)就有多长。
target = torch.tensor([0, 1, 1], dtype=torch.long)
# ----------------------------------- 2 NLLLoss -----------------------------------
flag = 0
# flag = 1
if flag:
weights = torch.tensor([1, 1], dtype=torch.float)
loss_f_none_w = nn.NLLLoss(weight=weights, reduction='none')
loss_f_sum = nn.NLLLoss(weight=weights, reduction='sum')
loss_f_mean = nn.NLLLoss(weight=weights, reduction='mean')
# forward
loss_none_w = loss_f_none_w(inputs, target)
loss_sum = loss_f_sum(inputs, target)
loss_mean = loss_f_mean(inputs, target)
# view
print("NLL Loss", loss_none_w, loss_sum, loss_mean)
#输出[-1,-3,-3] -7 -2.3333
# target[0, 1, 1]
#-1是因为第一个样本[1,2]是第0类,因此只对第一个神经元输出操作:-1*1。
#-3是因为第二个样本[1,3]是第1类,因此只对第二个神经元输出操作:-1*3。
#-3是因为第三个样本[1,3]是第1类,因此只对第二个神经元输出操作:-1*3。
#求mean时的分母是权重weight的和。
BCE Loss
xn是模型输出的概率取值,yn是标签(二分类中对应0或1)。
nn.BCELoss(weight=None, size_average=None, reduce=None, reduction='mean')
'''
二分类交叉熵损失函数
注意:输入值取值在[0,1],利用sigmoid函数可实现
'''
# ----------------------------------- 3 BCE Loss -----------------------------------
flag = 0
# flag = 1
if flag:
#同样是二分类,每个样本有两个神经元
inputs = torch.tensor([[1, 2], [2, 2], [3, 4], [4, 5]], dtype=torch.float) #4个样本
target = torch.tensor([[1, 0], [1, 0], [0, 1], [0, 1]], dtype=torch.float) #float类型,每个神经元一一对应去计算loss,而不是之前是整个向量去计算loss
target_bce = target
#一定要加上sigmoid,将输入值取值在[0,1]区间
inputs = torch.sigmoid(inputs)
weights = torch.tensor([1, 1], dtype=torch.float)
loss_f_none_w = nn.BCELoss(weight=weights, reduction='none')
loss_f_sum = nn.BCELoss(weight=weights, reduction='sum')
loss_f_mean = nn.BCELoss(weight=weights, reduction='mean')
# forward
loss_none_w = loss_f_none_w(inputs, target_bce)
loss_sum = loss_f_sum(inputs, target_bce)
loss_mean = loss_f_mean(inputs, target_bce)
# view
print("\nweights: ", weights)
print("BCE Loss", loss_none_w, loss_sum, loss_mean)
#输出[[0.3133, 2.1269], [0.1269, 2.1269], [3.0486, 0.0181], [4.0181, 0.0067]] 11.7856 1.4732
#每个神经元一一对应去计算loss,因此loss个数是2*4=8个
# --------------------------------- compute by hand
flag = 0
# flag = 1
if flag:
idx = 0
x_i = inputs.detach().numpy()[idx, idx]
y_i = target.numpy()[idx, idx] #
# loss
# l_i = -[ y_i * np.log(x_i) + (1-y_i) * np.log(1-y_i) ] # np.log(0) = nan
l_i = -y_i * np.log(x_i) if y_i else -(1-y_i) * np.log(1-x_i)
# 输出loss
print("BCE inputs: ", inputs)
print("第一个loss为: ", l_i) #0.3133
BCEwithLogitsLoss
σ是sigmoid函数
nn.BCEWithLogitsLoss(weight=None, size_average=None, reduce=None, reduction='mean', pos_weight=None)
'''
结合sigmoid与二分类交叉熵
注意:网络最后不要加sigmoid函数,再加这个值就不准确了
这个损失函数存在的原因:有的时候不希望模型里有sigmoid在最后一层。
多了一个参数pos_weight 正样本权值
(1)pos_weight用于均衡正负样本,正样本的loss需要乘以pos_weight。
比如正样本100个,负样本300个,此时可以将pos_weight设置为3,就可以实现正负样本均衡。
(2)pos_weight里是一个tensor列表,需要和标签个数相同,比如现在有一个多标签分类,类别有200个,那么 pos_weight 就是为每个类别赋予的权重值,长度为200
(3)如果现在是二分类,只需要将正样本loss的权重写上即可,比如我们有正负两类样本,正样本数量为100个,负样本为400个,我们想要对正负样本的loss进行加权处理,将正样本的loss权重放大4倍,通过这样的方式缓解样本不均衡问题:
'''
criterion = nn.BCEWithLogitsLoss(pos_weight=torch.tensor([4]))
# ----------------------------------- 4 BCE with Logis Loss -----------------------------------
# flag = 0
flag = 1
if flag:
inputs = torch.tensor([[1, 2], [2, 2], [3, 4], [4, 5]], dtype=torch.float)
target = torch.tensor([[1, 0], [1, 0], [0, 1], [0, 1]], dtype=torch.float)
target_bce = target
# inputs = torch.sigmoid(inputs) 不能加sigmoid!!!
weights = torch.tensor([1, 1], dtype=torch.float)
loss_f_none_w = nn.BCEWithLogitsLoss(weight=weights, reduction='none')
loss_f_sum = nn.BCEWithLogitsLoss(weight=weights, reduction='sum')
loss_f_mean = nn.BCEWithLogitsLoss(weight=weights, reduction='mean')
# forward
loss_none_w = loss_f_none_w(inputs, target_bce)
loss_sum = loss_f_sum(inputs, target_bce)
loss_mean = loss_f_mean(inputs, target_bce)
# view
print("\nweights: ", weights)
print(loss_none_w, loss_sum, loss_mean)
#输出[[0.3133, 2.1269], [0.1269, 2.1269], [3.0486, 0.0181], [4.0181, 0.0067]] 11.7856 1.4732
# --------------------------------- pos weight
# flag = 0
flag = 1
if flag:
inputs = torch.tensor([[1, 2], [2, 2], [3, 4], [4, 5]], dtype=torch.float)
target = torch.tensor([[1, 0], [1, 0], [0, 1], [0, 1]], dtype=torch.float)
target_bce = target
# itarget
# inputs = torch.sigmoid(inputs)
weights = torch.tensor([1], dtype=torch.float)
#pos_w = torch.tensor([1], dtype=torch.float) #设置为1时,loss输出与原先一致。
pos_w = torch.tensor([3], dtype=torch.float) # 3
#设置为3时会对正样本的loss乘以3,即标签是1对应的loss。
#输出[[0.3133*3, 2.1269], [0.1269*3, 2.1269], [3.0486, 0.0181*3], [4.0181, 0.0067*3]]
loss_f_none_w = nn.BCEWithLogitsLoss(weight=weights, reduction='none', pos_weight=pos_w)
loss_f_sum = nn.BCEWithLogitsLoss(weight=weights, reduction='sum', pos_weight=pos_w)
loss_f_mean = nn.BCEWithLogitsLoss(weight=weights, reduction='mean', pos_weight=pos_w)
# forward
loss_none_w = loss_f_none_w(inputs, target_bce)
loss_sum = loss_f_sum(inputs, target_bce)
loss_mean = loss_f_mean(inputs, target_bce)
# view
print("\npos_weights: ", pos_w)
print(loss_none_w, loss_sum, loss_mean)
nn.L1Loss
计算Inputs与target之差的绝对值
nn.MSELoss
计算差的平方
# ------------------------------------------------- 5 L1 loss ----------------------------------------------
flag = 0
# flag = 1
if flag:
inputs = torch.ones((2, 2)) #2*2,数值为1
target = torch.ones((2, 2)) * 3 #2*2,数值为3
loss_f = nn.L1Loss(reduction='none')
loss = loss_f(inputs, target)
print("input:{}\ntarget:{}\nL1 loss:{}".format(inputs, target, loss))
#输出为[[2,2],[2,2]]
# ------------------------------------------------- 6 MSE loss ----------------------------------------------
loss_f_mse = nn.MSELoss(reduction='none')
loss_mse = loss_f_mse(inputs, target)
print("MSE loss:{}".format(loss_mse))
#输出为[[4,4],[4,4]]
nn.SmoothL1Loss
平滑的L1 loss,看图就知道为什么要这样设置函数了,可以减轻离群点带来的影响。
# ------------------------------------------------- 7 Smooth L1 loss ----------------------------------------------
flag = 0
# flag = 1
if flag:
inputs = torch.linspace(-3, 3, steps=500) #-3到3区间取500个数据点
target = torch.zeros_like(inputs) #数值为0,维度与inputs一致
loss_f = nn.SmoothL1Loss(reduction='none')
loss_smooth = loss_f(inputs, target)
loss_l1 = np.abs(inputs.numpy()-target.numpy())
plt.plot(inputs.numpy(), loss_smooth.numpy(), label='Smooth L1 Loss')
plt.plot(inputs.numpy(), loss_l1, label='L1 loss')
plt.xlabel('x_i - y_i')
plt.ylabel('loss value')
plt.legend()
plt.grid()
plt.show()#画出的图像就是上图所示
nn.PoissonNLLLoss
泊松分布的负对数似然损失函数
nn.PoissonNLLLoss(log_input=True, full=False, size_average=None, eps=1e-08, reduce=None, reduction='mean')
'''
log_input:输入是否是对数形式,决定计算公式使用上图哪一个
full:计算所有loss,默认为False
eps:修正项,避免log(input)为nan(即避免输入取0时)
'''
# ------------------------------------------------- 8 Poisson NLL Loss ----------------------------------------------
flag = 0
# flag = 1
if flag:
inputs = torch.randn((2, 2)) #2*2,标准正态分布
target = torch.randn((2, 2)) #2*2,标准正态分布
loss_f = nn.PoissonNLLLoss(log_input=True, full=False, reduction='none')
loss = loss_f(inputs, target)
print("input:{}\ntarget:{}\nPoisson NLL loss:{}".format(inputs, target, loss))
# --------------------------------- compute by hand
flag = 0
# flag = 1
if flag:
idx = 0
loss_1 = torch.exp(inputs[idx, idx]) - target[idx, idx]*inputs[idx, idx] #计算第一个元素,即inputs[0,0]
print("第一个元素loss:", loss_1)
nn.KLDivLoss
KL散度衡量两个分布的相似性或者“距离”
P:样本在真实分布的概率 即标签
Q:尽可能拟合逼近P的分布,即模型输出的分布
Q(xi)指其中xi样本的概率
第二个公式ln 是pytorch 的这个计算公式,xn是输入的数据,yn是标签
有一些区别,区别在于xn而非logQ(xi)即log(xn),这样就是下面提到的需要提前计算输入的log-probabilities
nn.KLDivLoss(size_average=None, reduce=None, reduction='mean', log_target=False)
'''
计算KLD即KL散度,相对熵
需提前将输入计算log-probablities,如通过nn.logsoftmax(),softmax生成probabilities 然后对其log
batchmean:batchsize维度求平均值,也就是说求平均值除以的分母不再是元素个数,而是batchsize
'''
# ------------------------------------------------- 9 KL Divergence Loss ----------------------------------------------
flag = 0
# flag = 1
if flag:
# 3个神经元,第一批样本中第一个神经元输出为0.5,第二个0.3,第三个0.2
# 第二批样本中第一个0.2,第二个0.3,第三个0.5
inputs = torch.tensor([[0.5, 0.3, 0.2], [0.2, 0.3, 0.5]]) #2*3,2批样本,3个神经元 已经是概率值了不需要再softmax
inputs_log = torch.log(inputs)
target = torch.tensor([[0.9, 0.05, 0.05], [0.1, 0.7, 0.2]], dtype=torch.float)
loss_f_none = nn.KLDivLoss(reduction='none')
loss_f_mean = nn.KLDivLoss(reduction='mean')
loss_f_bs_mean = nn.KLDivLoss(reduction='batchmean')
loss_none = loss_f_none(inputs, target)
loss_mean = loss_f_mean(inputs, target) #所有元素相加/6,6是所有元素的个数
loss_bs_mean = loss_f_bs_mean(inputs, target) #所有元素相加/2,2是batchsize一共是两批次,即两批样本
print("loss_none:\n{}\nloss_mean:\n{}\nloss_bs_mean:\n{}".format(loss_none, loss_mean, loss_bs_mean))
# --------------------------------- compute by hand
flag = 0
# flag = 1
if flag:
idx = 0
loss_1 = target[idx, idx] * (torch.log(target[idx, idx]) - inputs[idx, idx])
print("第一个元素loss:", loss_1)
nn.MarginRankingLoss
计算公式如上,y=-1 或 1
当y=-1时,希望x2>x1,那么就不产生loss
当y= 1 时,希望x1>x2,那么就不产生loss
nn.MarginRankingLoss(margin=0.0, size_average=None, reduce=None, reduction='mean')
'''
计算两个向量之间相似度,用于排序任务
该方法计算两组数据之间的差异,返回n*n的一个loss矩阵
margin:边界值,x1与x2之间的差异值
'''
# ---------------------------------------------- 10 Margin Ranking Loss --------------------------------------------
flag = 0
# flag = 1
if flag:
x1 = torch.tensor([[1], [2], [3]], dtype=torch.float) #3*1
x2 = torch.tensor([[2], [2], [2]], dtype=torch.float) #3*1
target = torch.tensor([1, 1, -1], dtype=torch.float) #向量,也是1*3
loss_f_none = nn.MarginRankingLoss(margin=0, reduction='none')
loss = loss_f_none(x1, x2, target)
print(loss) #输出为[[1,1,0], [0,0,0], [0,0,1]]
# loss中第一行第一列的元素1是用x1的第1个元素[1]与x2的第1个元素[2]比较,此时y=1,loss=1
# loss中第一行第二列的元素1是用x1的第1个元素[2]与x2的第2个元素[2]比较,此时y=1,loss=1
# loss中第一行第三列的元素0是用x1的第1个元素[3]与x2的第3个元素[2]比较,此时y=-1,loss=0
# loss中第二行第一列的元素0是用x1的第2个元素[2]与x2的第1个元素[2]比较,此时y=1,loss=0,当两个值相等时,不论y是多少,loss=0
# 后面计算同理,y的值与x2是第几个有关,比如x2取第1个值,则y取第一个值;x2取第3个值,则y取第三个值。
nn.MultiLabelMarginLoss
1)分母是神经元的个数。
2)x[y[j]]-x[i]=标签所在的神经元的值-不是标签所在的神经元的值。
目的是为了使标签所在的神经元的输出比非标签所在的神经元的输出越来越大,这样loss才会等于0。
nn.MultiLabelMarginLoss(size_average=None, reduce=None, reduction='mean')
'''
多标签边界损失函数,注意是多标签 不是多分类!
比如 4分类任务,样本x属于0类和3类,则标签为[0,3,-1-1] 而非[1,0,0,1]
'''
# ---------------------------------------------- 11 Multi Label Margin Loss -----------------------------------------
flag = 0
# flag = 1
if flag:
x = torch.tensor([[0.1, 0.2, 0.4, 0.8]]) #1*4,1是一批样本,四分类,分别对应第0类、第1类、第2类、第3类
y = torch.tensor([[0, 3, -1, -1]], dtype=torch.long) #标签,设置第0类第3类,不足的地方用-1填充占位,注意类型必须是long
loss_f = nn.MultiLabelMarginLoss(reduction='none')
loss = loss_f(x, y)
print(loss) # 输出0.8500
# --------------------------------- compute by hand
flag = 0
# flag = 1
if flag:
#标签所在的神经元是第0类和第3类,所以要计算第0类神经元的loss和第3类神经元的loss,需要各自减去不是标签的神经元
x = x[0] #取出第1批样本
#第0类神经元需要减去第1类、第2类神经元
item_1 = (1-(x[0] - x[1])) + (1 - (x[0] - x[2])) # [0]
#第3类神经元需要减去第1类、第2类神经元
item_2 = (1-(x[3] - x[1])) + (1 - (x[3] - x[2])) # [3]
loss_h = (item_1 + item_2) / x.shape[0] #x.shape[0]=4
print(loss_h)
nn.SoftMarginLoss
分母是样本数(nelement() 统计 tensor (张量) 中元素的个数)。
y可取1 或-1
nn.SoftMarginLoss(size_average=None, reduce=None, reduction='mean')
'''
计算二分类的logistic 损失
'''
# ---------------------------------------------- 12 SoftMargin Loss -----------------------------------------
flag = 0
# flag = 1
if flag:
inputs = torch.tensor([[0.3, 0.7], [0.5, 0.5]]) # 2*2
#2个样本,每个样本里2个神经元
target = torch.tensor([[-1, 1], [1, -1]], dtype=torch.float) # 2*2
loss_f = nn.SoftMarginLoss(reduction='none')
loss = loss_f(inputs, target)
print("SoftMargin: ", loss) # 2*2
# --------------------------------- compute by hand
flag = 0
# flag = 1
if flag:
idx = 0
inputs_i = inputs[idx, idx]
target_i = target[idx, idx]
loss_h = np.log(1 + np.exp(-target_i * inputs_i))
print(loss_h)
nn.MultiLabelSoftMarginLoss
(1)C是样本个数,取平均。i是神经元个数。
(2)标签y[i]只能是0,1。
(3)当i是标签神经元时,y[i]=1,只需要计算前一项,因为后一项1-y[i]=0。反之,当i不是标签神经元时,y[i]=0,只需要计算后一项,因为前一项为0。
nn.MultiLabelSoftMarginLoss(weight=None, size_average=None, reduce=None, reduction='mean')
'''
softMarginLoss多标签版本
weight:各类别loss设置权值
'''
# ---------------------------------------------- 13 MultiLabel SoftMargin Loss -----------------------------------------
flag = 0
# flag = 1
if flag:
inputs = torch.tensor([[0.3, 0.7, 0.8]]) #三分类任务
target = torch.tensor([[0, 1, 1]], dtype=torch.float)#即属于1类也属于2类,不属于0类
loss_f = nn.MultiLabelSoftMarginLoss(reduction='none')
loss = loss_f(inputs, target)
print("MultiLabel SoftMargin: ", loss) #输出0.5429
# --------------------------------- compute by hand
flag = 0
# flag = 1
if flag:
#第1个元素的标签为0,只需要计算后一项。
i_0 = torch.log(torch.exp(-inputs[0, 0]) / (1 + torch.exp(-inputs[0, 0])))
#第2个元素的标签为1,只需要计算前一项。
i_1 = torch.log(1 / (1 + torch.exp(-inputs[0, 1])))
#第3个元素的标签为1,只需要计算前一项。
i_2 = torch.log(1 / (1 + torch.exp(-inputs[0, 2])))
loss_h = (i_0 + i_1 + i_2) / -3
print(loss_h) #输出0.5429
nn.MultiMarginLoss
x[y]是标签所在神经元的输出值,x[i]是非标签所在神经元的输出值。
nn.MultiMarginLoss(p=1, margin=1.0, weight=None, size_average=None, reduce=None, reduction='mean')
'''
计算多分类折页损失
p可选1或2
margin 边界值
weight 各类别设置loss权值
'''
# ---------------------------------------------- 14 Multi Margin Loss -----------------------------------------
flag = 0
# flag = 1
if flag:
x = torch.tensor([[0.1, 0.2, 0.7], [0.2, 0.5, 0.3]])
#在本损失函数的公式设定中:
#第一个标签1对应x中第一个样本第1个元素(从第0个元素开始数)0.2
#第二个标签2对应x中第二个样本第2个元素(从第0个元素开始数)0.3
y = torch.tensor([1, 2], dtype=torch.long)
loss_f = nn.MultiMarginLoss(reduction='none')
loss = loss_f(x, y)
print("Multi Margin Loss: ", loss) #输出[0.8000, 0.7000]
# --------------------------------- compute by hand
flag = 0
# flag = 1
if flag:
x = x[0] #取出第一个样本 ,所以后面减去的神经元不能是x[1]
margin = 1
i_0 = margin - (x[1] - x[0])
# i_1 = margin - (x[1] - x[1])
i_2 = margin - (x[1] - x[2])
loss_h = (i_0 + i_2) / x.shape[0] #shape类别数=3
print(loss_h) #输出0.8000
nn.TripletMarginLoss
三元组定义为 希望通过学习之后使得,anchor与positive的距离比anchor到negative的距离小;这里放在人脸识别中则anchor是当前样本,positive 这个样本真实对应的人脸,negative其他人脸
第二个公式指的是用p范数,默认用2范数
nn.TripletMarginLoss(margin=1.0, p=2.0, eps=1e-06, swap=False, size_average=None, reduce=None, reduction='mean')
'''
计算三元组损失,常用于人脸验证
p:范数的阶,默认2
margin边界值
'''
# ---------------------------------------------- 15 Triplet Margin Loss -----------------------------------------
flag = 0
# flag = 1
if flag:
anchor = torch.tensor([[1.]])
pos = torch.tensor([[2.]])
neg = torch.tensor([[0.5]])
loss_f = nn.TripletMarginLoss(margin=1.0, p=1)
loss = loss_f(anchor, pos, neg)
print("Triplet Margin Loss", loss) #(2-1)-(1-0.5)+1 = 1.5
#输出1.5000
# --------------------------------- compute by hand
flag = 0
# flag = 1
if flag:
margin = 1
a, p, n = anchor[0], pos[0], neg[0]
d_ap = torch.abs(a-p)
d_an = torch.abs(a-n)
loss = d_ap - d_an + margin
print(loss)
nn.HingeEmbeddingLoss
▲表示margin。
nn.HingeEmbeddingLoss(margin=1.0, size_average=None, reduce=None, reduction='mean')
'''
计算两个输入的相似性,常用于非线性embedding和半监督学习
输入x应为两个输入之差的绝对值
'''
# ---------------------------------------------- 16 Hinge Embedding Loss -----------------------------------------
flag = 0
# flag = 1
if flag:
inputs = torch.tensor([[1., 0.8, 0.5]])
target = torch.tensor([[1, 1, -1]])
loss_f = nn.HingeEmbeddingLoss(margin=1, reduction='none')
loss = loss_f(inputs, target)
print("Hinge Embedding Loss", loss)
#输出[1.0000, 0.8000, 0.5000]
# --------------------------------- compute by hand
flag = 0
# flag = 1
if flag:
margin = 1.
loss = max(0, margin - inputs.numpy()[0, 2])
print(loss)
nn.CosineEmbeddingLoss
nn.CosineEmbeddingLoss(margin=0.0, size_average=None, reduce=None, reduction='mean')
'''
利用余弦相似度计算两个输入相似性,利用cos是关注方向上的差异
margin 取值范围[-1,1] 推荐为[0,0.5]
'''
# ---------------------------------------------- 17 Cosine Embedding Loss -----------------------------------------
flag = 0
# flag = 1
if flag:
x1 = torch.tensor([[0.3, 0.5, 0.7], [0.3, 0.5, 0.7]])
x2 = torch.tensor([[0.1, 0.3, 0.5], [0.1, 0.3, 0.5]])
target = torch.tensor([[1, -1]], dtype=torch.float)
loss_f = nn.CosineEmbeddingLoss(margin=0., reduction='none')
loss = loss_f(x1, x2, target)
print("Cosine Embedding Loss", loss)
# 输出[0.0167, 0.9833]
# --------------------------------- compute by hand
flag = 0
# flag = 1
if flag:
margin = 0.
def cosine(a, b):
numerator = torch.dot(a, b) # 分子是点积
denominator = torch.norm(a, 2) * torch.norm(b, 2) #分母是模长相乘
return float(numerator/denominator)
l_1 = 1 - (cosine(x1[0], x2[0]))
l_2 = max(0, cosine(x1[1], x2[1]))
print(l_1, l_2)
# 输出0.0167 0.9833
nn.CTCLoss
原理后续补充。
nn.CTCLoss(blank=0, reduction='mean', zero_infinity=False)
'''
计算CTC损失,解决时序类数据的分类 connectionist temporal classification
blank:blank label
zero_infinity:无穷大的值或梯度置0
例如OCR中输入输出不确定的情况,怎么计算分类,就要用到这个
'''
# ---------------------------------------------- 18 CTC Loss -----------------------------------------
# flag = 0
flag = 1
if flag:
T = 50 # Input sequence length
C = 20 # Number of classes (including blank)
N = 16 # Batch size
S = 30 # Target sequence length of longest target in batch
S_min = 10 # Minimum target length, for demonstration purposes
# Initialize random batch of input vectors, for *size = (T,N,C)
inputs = torch.randn(T, N, C).log_softmax(2).detach().requires_grad_()
# Initialize random batch of targets (0 = blank, 1:C = classes)
target = torch.randint(low=1, high=C, size=(N, S), dtype=torch.long)
input_lengths = torch.full(size=(N,), fill_value=T, dtype=torch.long)
target_lengths = torch.randint(low=S_min, high=S, size=(N,), dtype=torch.long)
ctc_loss = nn.CTCLoss()
loss = ctc_loss(inputs, target, input_lengths, target_lengths)
print("CTC loss: ", loss) #输出7.5385
设置损失函数超参数
选择损失函数
优化器(共10个)
由损失函数得到loss,经由模型参数自动求导得到梯度,优化器根据优化策略根据梯度更新模型中可学习参数的值,使得loss往下降的方向走,即使模型输出接近真实标签。
梯度:是一个向量,方向为方向导数取得最大值的方向。故沿着梯度方向是增长最快的,那么沿着梯度负方向是下降最快的。
学习率:控制学习更新步伐,防止梯度爆炸,【通常设置学习率为0.01】
动量momentum:结合当前梯度和上一次更新信息,用于当前更新,可以看成一个惯性,减少迭代次数,加速迭代收敛。利用指数加权平均,时间序列中离得越近的参数参考性越大,权重越大。
指数加权平均:v_t=p*v_t-1 +(1-p)*θt
beta类似于记忆周期,beta越小,记忆周期越短。比如beta=0.8,到第20天左右就记不住了,而beta=0.98可以记忆到80天左右。
上述图中蓝色图像明显先达到了minimum,但又不断迂回缓冲,这是因为 momentum较大,而且受到之前迭代的大梯度值的影响。
合适的动量之一,可以发现蓝色早于黄色先收敛到最小值。
# -*- coding:utf-8 -*-
"""
@file name : momentum.py
@brief : 梯度下降的动量 momentum
"""
import torch
import numpy as np
import torch.optim as optim
import matplotlib.pyplot as plt
torch.manual_seed(1)
def exp_w_func(beta, time_list):
return [(1 - beta) * np.power(beta, exp) for exp in time_list]
# np.power(beta, exp) 即 beta**exp
beta = 0.9
num_point = 100
time_list = np.arange(num_point).tolist() #创建time list
# ------------------------------ exponential weight ------------------------------
flag = 0
# flag = 1
if flag:
weights = exp_w_func(beta, time_list)
plt.plot(time_list, weights, '-ro', label="Beta: {}\ny = B^t * (1-B)".format(beta))
plt.xlabel("time")
plt.ylabel("weight")
plt.legend()
plt.title("exponentially weighted average")
plt.show()
print(np.sum(weights))
# ------------------------------ multi weights ------------------------------
flag = 0
# flag = 1
if flag:
beta_list = [0.98, 0.95, 0.9, 0.8]
w_list = [exp_w_func(beta, time_list) for beta in beta_list]
for i, w in enumerate(w_list):
plt.plot(time_list, w, label="Beta: {}".format(beta_list[i]))
plt.xlabel("time")
plt.ylabel("weight")
plt.legend()
plt.show()
# ------------------------------ SGD momentum ------------------------------
# flag = 0
flag = 1
if flag:
def func(x):
return torch.pow(2*x, 2) # y = (2x)^2 = 4*x^2 dy/dx = 8x
iteration = 100
m = 0.9 # .9 .63 可以修改不同的momentum去看效果图
lr_list = [0.01, 0.03]
momentum_list = list()
loss_rec = [[] for l in range(len(lr_list))]
iter_rec = list()
for i, lr in enumerate(lr_list):
x = torch.tensor([2.], requires_grad=True)
momentum = 0. if lr == 0.03 else m
momentum_list.append(momentum)
optimizer = optim.SGD([x], lr=lr, momentum=momentum)
for iter in range(iteration):
y = func(x)
y.backward()
optimizer.step()
optimizer.zero_grad()
loss_rec[i].append(y.item())
for i, loss_r in enumerate(loss_rec):
plt.plot(range(len(loss_r)), loss_r, label="LR: {} M:{}".format(lr_list[i], momentum_list[i]))
plt.legend()
plt.xlabel('Iterations')
plt.ylabel('Loss value')
plt.show()
class Optimizer(object):
def __init__(self,params,defaults):
self.defaults=defaults
self.state=defaultdict(dict)
self.param_groups=[]
...
param_groups=[{'params':param_groups}]
'''
defaults:优化器超参数
state:参数缓存,如momentum缓存
param_groups:管理的参数组,是一个list,其中是dict键值对,value才是参数内容
_step_count:记录更新次数,学习率调整中使用
'''
优化器最基本的5个方法
zero_grad()#清空所管理参数的梯度
#pytorch中张量梯度不会自动清零,每次反向传播时计算的梯度是叠加的
step() #执行一次更新,反向传播后根据优化方法更新参数
add_param_group()#添加参数组在优化器中,可以不同阶段设置不同参数组(如设置不同的学习率)
state_dict()#获取优化器当前状态信息字典
load_state_dict()#加载状态信息字典 可以用于重新从一个state_dict状态开始训练
# -*- coding: utf-8 -*-
"""
# @file name : optimizer_methods.py
# @brief : optimizer's methods
"""
import os
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
import torch
import torch.optim as optim
from tools.common_tools import set_seed
set_seed(1) # 设置随机种子
weight = torch.randn((2, 2), requires_grad=True) # 创建2*2的weight
weight.grad = torch.ones((2, 2)) # 权值梯度设为1
optimizer = optim.SGD([weight], lr=0.1)
# ----------------------------------- step -----------------------------------
flag = 0
# flag = 1
if flag:
print("weight before step:{}".format(weight.data))
#输出[[0.6614, 0.2669], [0.0617, 0.6213]]
optimizer.step()
print("weight after step:{}".format(weight.data))
#输出[[0.5614, 0.1669], [-0.0383, 0.5213]]
#0.5614 = 0.6614 - weight.grad (1) * lr (0.1)=0.6614-0.1,其余同理减去0.1
# ----------------------------------- zero_grad -----------------------------------
flag = 0
# flag = 1
if flag:
print("weight before step:{}".format(weight.data))
optimizer.step() # 修改lr=1 0.1观察结果
print("weight after step:{}".format(weight.data))
print("weight in optimizer:{}\nweight in weight:{}\n".format(id(optimizer.param_groups[0]['params'][0]), id(weight))) #输出一致
#优化器中管理参数的内存地址和weight的内存地址一致,节省内存空间
print("weight.grad is {}\n".format(weight.grad)) #输出[[1., 1.], [1., 1.]]
optimizer.zero_grad()
print("after optimizer.zero_grad(), weight.grad is\n{}".format(weight.grad)) #输出[[0., 0.], [0., 0.]]
# ----------------------------------- add_param_group -----------------------------------
flag = 0
# flag = 1
if flag:
print("optimizer.param_groups is\n{}".format(optimizer.param_groups))
w2 = torch.randn((3, 3), requires_grad=True)
optimizer.add_param_group({"params": w2, 'lr': 0.0001}) #添加一组超参数
print("optimizer.param_groups is\n{}".format(optimizer.param_groups)) #有两组超参数
# ----------------------------------- state_dict -----------------------------------
flag = 0
# flag = 1
if flag:
optimizer = optim.SGD([weight], lr=0.1, momentum=0.9)
opt_state_dict = optimizer.state_dict()
print("state_dict before step:\n", opt_state_dict)
for i in range(10):
optimizer.step()
print("state_dict after step:\n", optimizer.state_dict())
#打印可以发现state中的key就是这个param的id地址
torch.save(optimizer.state_dict(), os.path.join(BASE_DIR, "optimizer_state_dict.pkl"))#保存这个状态
# -----------------------------------load state_dict -----------------------------------
flag = 0
# flag = 1
if flag:
#使用场景:比如说之前已经训练了100次,接着它的结果继续训练
optimizer = optim.SGD([weight], lr=0.1, momentum=0.9)
state_dict = torch.load(os.path.join(BASE_DIR, "optimizer_state_dict.pkl"))
print("state_dict before load state:\n", optimizer.state_dict())
optimizer.load_state_dict(state_dict)
print("state_dict after load state:\n", optimizer.state_dict())
optim.SGD 随机梯度下降法
注意事项:
pytroch中使用SGD十分需要注意的是,更新公式与其他框架略有不同!
pytorch中是这样的:
v=ρ∗v+g
p=p−lr∗v = p - lr∗ρ∗v - lr∗g
其他框架:
v=ρ∗v+lr∗g
p=p−v = p - ρ∗v - lr∗g
ρ是动量,v是速率,g是梯度,p是参数,其实差别就是在ρ∗v这一项,pytorch中将此项也乘了一个学习率。
torch.optim.SGD(params, lr=<required parameter>, momentum=0, dampening=0, weight_decay=0, nesterov=False)
'''
params(必须参数): 这是一个包含了需要优化的参数(张量)的迭代器,例如模型的参数 model.parameters()。
lr(必须参数): 学习率(learning rate)。它是一个正数,控制每次参数更新的步长。较小的学习率会导致收敛较慢,较大的学习率可能导致震荡或无法收敛。
momentum(默认值为 0): 动量(momentum)是一个用于加速 SGD 收敛的参数。它引入了上一步梯度的指数加权平均。通常设置在 0 到 1 之间。当 momentum 大于 0 时,算法在更新时会考虑之前的梯度,有助于加速收敛。
weight_decay(默认值为 0): 权重衰减,也称为 L2 正则化项。它用于控制参数的幅度,以防止过拟合。通常设置为一个小的正数。
nesterov(默认值为 False): 是否采用NAG。Nesterov 动量。当设置为 True 时,采用 Nesterov 动量更新规则。Nesterov 动量在梯度更新之前先进行一次预测,然后在计算梯度更新时使用这个预测。
'''
torch.optim.ASGD 随机平均梯度下降
torch.optim.ASGD(params, lr=0.01, lambd=0.0001, alpha=0.75, t0=1000000.0, weight_decay=0)
'''
功能:
ASGD也成为SAG,均表示随机平均梯度下降(Averaged Stochastic Gradient Descent),简单地说ASGD就是用空间换时间的一种SGD,详细可参看论文:http://riejohnson.com/rie/stograd_nips.pdf
参数:
params(iterable)- 参数组(参数组的概念请查看 3.1 优化器基类:Optimizer),优化器要优化的那些参数。
lr(float)- 初始学习率,可按需随着训练过程不断调整学习率。
lambd(float)- 衰减项,默认值1e-4。
alpha(float)- power for eta update ,默认值0.75。
t0(float)- point at which to start averaging,默认值1e6。
weight_decay(float)- 权值衰减系数,也就是L2正则项的系数。
'''
torch.optim.Rprop 弹性反向传播
torch.optim.Rprop(params, lr=0.01, etas=(0.5, 1.2), step_sizes=(1e-06, 50))
'''
功能:
实现Rprop优化方法(弹性反向传播),优化方法原文《Martin Riedmiller und Heinrich Braun: Rprop - A Fast Adaptive Learning Algorithm. Proceedings of the International Symposium on Computer and Information Science VII, 1992》
该优化方法适用于full-batch,不适用于mini-batch,因而在min-batch大行其道的时代里,很少见到。
'''
torch.optim.Adagrad 自适应学习率梯度下降法
torch.optim.Adagrad(params, lr=0.01, lr_decay=0, weight_decay=0, initial_accumulator_value=0)
'''
功能:
实现Adagrad优化方法(Adaptive Gradient),Adagrad是一种自适应优化方法,是自适应的为各个参数分配不同的学习率。这个学习率的变化,会受到梯度的大小和迭代次数的影响。梯度越大,学习率越小;梯度越小,学习率越大。缺点是训练后期,学习率过小,因为Adagrad累加之前所有的梯度平方作为分母。
详细公式请阅读:Adaptive Subgradient Methods for Online Learning and Stochastic Optimization
John Duchi, Elad Hazan, Yoram Singer; 12(Jul):2121−2159, 2011.(http://www.jmlr.org/papers/volume12/duchi11a/duchi11a.pdf)
'''
torch.optim.Adadelta:Adagrad的改进
torch.optim.Adadelta(params, lr=1.0, rho=0.9, eps=1e-06, weight_decay=0)
'''
功能:
实现Adadelta优化方法。Adadelta是Adagrad的改进。Adadelta分母中采用距离当前时间点比较近的累计项,这可以避免在训练后期,学习率过小。
详细公式请阅读:https://arxiv.org/pdf/1212.5701.pdf
'''
torch.optim.RMSprop:Adagrad的改进
torch.optim.RMSprop(params, lr=0.01, alpha=0.99, eps=1e-08, weight_decay=0, momentum=0, centered=False)
'''
功能:
实现RMSprop优化方法(Hinton提出),RMS是均方根(root meam square)的意思。RMSprop和Adadelta一样,也是对Adagrad的一种改进。RMSprop采用均方根作为分母,可缓解Adagrad学习率下降较快的问题。并且引入均方根,可以减少摆动,详细了解可读:http://www.cs.toronto.edu/~tijmen/csc321/slides/lecture_slides_lec6.pdf
'''
torch.optim.Adam(AMSGrad):RMSprop结合Momentum
torch.optim.Adam(params, lr=0.001, betas=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=False)
'''
功能:
实现Adam(Adaptive Moment Estimation))优化方法。Adam是一种自适应学习率的优化方法,Adam利用梯度的一阶矩估计和二阶矩估计动态的调整学习率。吴老师课上说过,Adam是结合了Momentum和RMSprop,并进行了偏差修正。
参数:
amsgrad- 是否采用AMSGrad优化方法,asmgrad优化方法是针对Adam的改进,通过添加额外的约束,使学习率始终为正值。(AMSGrad,ICLR-2018 Best-Pper之一,《On the convergence of Adam and Beyond》)。
详细了解Adam可阅读,Adam: A Method for Stochastic Optimization(https://arxiv.org/abs/1412.6980)。
'''
torch.optim.Adamax: Adam增加学习率上限
torch.optim.Adamax(params, lr=0.002, betas=(0.9, 0.999), eps=1e-08, weight_decay=0)
'''
功能:
实现Adamax优化方法。Adamax是对Adam增加了一个学习率上限的概念,所以也称之为Adamax。
详细了解可阅读,Adam: A Method for Stochastic Optimization(https://arxiv.org/abs/1412.6980)(没错,就是Adam论文中提出了Adamax)。
'''
torch.optim.SparseAdam:稀疏版Adam
torch.optim.SparseAdam(params, lr=0.001, betas=(0.9, 0.999), eps=1e-08)
'''
功能:
针对稀疏张量的一种“阉割版”Adam优化方法。
only moments that show up in the gradient get updated, and only those portions of the gradient get applied to the parameters
'''
torch.optim.LBFGS:BFGS的改进
torch.optim.LBFGS(params, lr=1, max_iter=20, max_eval=None, tolerance_grad=1e-05, tolerance_change=1e-09, history_size=100, line_search_fn=None)
'''
功能:
实现L-BFGS(Limited-memory Broyden–Fletcher–Goldfarb–Shanno)优化方法。L-BFGS属于拟牛顿算法。L-BFGS是对BFGS的改进,特点就是节省内存。
使用注意事项:
1.This optimizer doesn’t support per-parameter options and parameter groups (there can be only one).
Right now all parameters have to be on a single device. This will be improved in the future.(2018-10-07)
'''
优化算法
对于几种优化算法,具体选择原则:
经验:先Adam(自适应,训练更快,但精度不一定比SGD高),SGD(最多)
梯度下降法
当学习率learning rate==1时 :w_i+1=w_i -g(w_i)
w_i指第i次迭代时的参数,g(w_i)指的是它所对应的梯度。
下面的代码展示了如何发生梯度爆炸。
所以这就是引入学习率的重要性,此时LR可以控制更新步伐
梯度下降公式扩展成 w_i+1=w_i -LR*g(w_i)【通常设置学习率为0.01】
当LR设置成0.2时,明显发现loss是逐渐下降的。
# -*- coding:utf-8 -*-
"""
@file name : learning_rate.py
@brief : 梯度下降的学习率演示
"""
import torch
import numpy as np
import matplotlib.pyplot as plt
torch.manual_seed(1)
def func(x_t):
"""
y = (2x)^2 = 4*x^2 dy/dx = 8x
"""
return torch.pow(2*x_t, 2)
# init
x = torch.tensor([2.], requires_grad=True)
# ------------------------------ plot data ------------------------------
flag = 0
# flag = 1
if flag:
#画出func所示的笛卡尔坐标系x-y 曲线图
x_t = torch.linspace(-3, 3, 100)
y = func(x_t)
plt.plot(x_t.numpy(), y.numpy(), label="y = 4*x^2")
plt.grid()
plt.xlabel("x")
plt.ylabel("y")
plt.legend()
plt.show()
# ------------------------------ gradient descent ------------------------------
flag = 0
# flag = 1
if flag:
iter_rec, loss_rec, x_rec = list(), list(), list()
lr = 0.01 # /1. /.5 /.2 /.1 /.125
max_iteration = 20 # /1. 4 /.5 4 /.2 20 200
for i in range(max_iteration):
y = func(x)
y.backward()#求取到x的梯度
print("Iter:{}, X:{:8}, X.grad:{:8}, loss:{:10}".format(
i, x.detach().numpy()[0], x.grad.detach().numpy()[0], y.item()))
x_rec.append(x.item())
x.data.sub_(lr * x.grad)
# x -= lr * x.grad 数学表达式意义: x = x - lr * x.grad
# x是一个张量0.5 0.2 0.1 0.125
x.grad.zero_()#清0 防止累加
iter_rec.append(i)
loss_rec.append(y)
plt.subplot(121).plot(iter_rec, loss_rec, '-ro')
plt.xlabel("Iteration")
plt.ylabel("Loss value")
x_t = torch.linspace(-3, 3, 100)
y = func(x_t)
plt.subplot(122).plot(x_t.numpy(), y.numpy(), label="y = 4*x^2")
plt.grid()
y_rec = [func(torch.tensor(i)).item() for i in x_rec]
plt.subplot(122).plot(x_rec, y_rec, '-ro')
plt.legend()
plt.show()
#造成梯度爆炸
# ------------------------------ multi learning rate ------------------------------
# flag = 0
flag = 1
if flag:
iteration = 100
num_lr = 10
lr_min, lr_max = 0.01, 0.2 # .5 .3 .2
lr_list = np.linspace(lr_min, lr_max, num=num_lr).tolist()
loss_rec = [[] for l in range(len(lr_list))]
iter_rec = list()
for i, lr in enumerate(lr_list):
x = torch.tensor([2.], requires_grad=True)
for iter in range(iteration):
y = func(x)
y.backward()
x.data.sub_(lr * x.grad) # x.data -= x.grad
x.grad.zero_()
loss_rec[i].append(y.item())
for i, loss_r in enumerate(loss_rec):
plt.plot(range(len(loss_r)), loss_r, label="LR: {}".format(lr_list[i]))
plt.legend()
plt.xlabel('Iterations')
plt.ylabel('Loss value')
plt.show()