深度学习 19、DNN

你挡我一时,挡不了我一世

发布日期: 2019-06-05 13:46:30 浏览量: 545
评分:
star star star star star star star star star star
*转载请注明来自write-bug.com

前文链接:https://write-bug.com/article/2540.html

深度学习

在前几节介绍的模型都属于单点学习模型,比如朴素贝叶斯的模型为概率,lr与softmax的模型为w,b权重,聚类kmeans的模型为中心向量点,还有后面的决策树的树模型等等都属于单点学习模型,都有各自的用途,而这里的深度学习来说,属于机器学习的一部分,可用不同的网络结构来解决各个领域的问题,模型为深度学习网络。

通过前几节的学习,不知道大家有没有对单点学习的模型有感触,所谓模型是什么?

我们从已有的例子(训练集)中发现输入x与输出y的关系,这个过程是学习(即通过有限的例子发现输入与输出之间的关系),而我们使用的function就是我们的模型,通过模型预测我们从未见过的未知信息得到输出y,通过激活函数(常见:relu,sigmoid,tanh,swish等)对输出y做非线性变换,压缩值域,而输出y与真实label的差距就是误差error,通过损失函数再求取参数偏导,梯度下降降低误差,从而优化模型,我们前面见过的损失函数是mse和交叉熵

而DNN的模型就是网络结构,在说如何选取更好的网络结构之前,我们先介绍下神经网络:

人类大脑的神经网络如上图具象出来就是无数个神经元,而神经元分为几个结构:

  • 树突:接收信号源

  • 神经元:核心计算单元

  • 轴突:传输数据到下一个神经元

通过一个个神经元的传输,生成结果,比如说条件反射:缩手

而在我们模型中,这个网络怎么表示呢?

  • 感知机:最最简单的神经网络,只有一个神经元

  • 树突:样本特征a1

  • 轴突:权重w1

树突与轴突,也就是样本特征和权重相乘,有些类似lr的wx,共同决策了神经元的输入

  • 神经元:对信号进一步处理,比如lr中wx的∑,并加入偏置b,默认为1

  • 传递函数(激活函数):对f(s)做压缩非线性变换

假设一个二分类问题:

  • 输入

  • 树突P1:颜色

  • 树突P2:形状

  • 香蕉(黄色,弯形):(-1,-1)

  • 苹果(红色,圆形):(1,1)

相当于坐标轴的两个点,我们需要一个分类面把两个点分割开。

  • P1颜色的取值为1,-1

  • P2形状的取值为1,-1

  • 初始化w1=w2=1,b=0

则有:

  1. s=p1w1+p2w2=2>0
  2. s=p2 2w2 2+p2 2w2 2=-2 < 0

这个初始化的参数可以区分,但是初始化有随机性,换一组参数就不能区分了

所以感知机的训练方法,目的就是训练权重和偏置

  1. w=w+ep
  2. b=b+e

e(误差)=t-a=期望-实际

如,假设w1=1,w2=-1,b=0

  • 苹果s=0

  • 期望为1,e=1-0=1

修正:

  1. w1=1+1*1=2
  2. w2=-1+1*1=0
  3. b=0+1=1
  4. s=3>0为苹果

但是,y=wx+b只能解决线性可分的问题,对于异或问题,也就是四点对立面,需要一个曲线做分割,而这就意味着需要多层神经元和信号源共同决策分割面,也就形成了复杂的神经网络,而目标:同样是那么多边的权重。

通过不同层间的结果作为下一层的输入,再对输入中间结果进一步处理,以此类推形成足够的深度,对于大部分问题来说都可以提到一个提升效果的作用。

感知机只有从输出层具有激活函数,即功能神经元,这里的隐层和输出层都是具有激活函数的功能神经元。

层与层全连接,同层神经元不连接,不跨层连接的网络又称为多层前馈神经网络。

前馈:网络拓扑结构上不存在环和回路

我们通过pytorch实现演示:

二分类问题:

假数据准备:

  1. # make fake data
  2. #正态分布随机产生
  3. n_data = torch.ones(100, 2)
  4. x0 = torch.normal(2*n_data, 1) # class0 x data (tensor), shape=(100, 2)
  5. y0 = torch.zeros(100) # class0 y data (tensor), shape=(100, 1)
  6. x1 = torch.normal(-2*n_data, 1) # class1 x data (tensor), shape=(100, 2)
  7. y1 = torch.ones(100) # class1 y data (tensor), shape=(100, 1)
  8. #拼接数据
  9. x = torch.cat((x0, x1), 0).type(torch.FloatTensor) # shape (200, 2) FloatTensor = 32-bit floating
  10. y = torch.cat((y0, y1), ).type(torch.LongTensor) # shape (200,) LongTensor = 64-bit integer
  11. # tensor类型 :张量:高维特征,不可更新
  12. #variable类型:可变参数,更新w,b从而更新xy
  13. # torch can only train on Variable, so convert them to Variable
  14. x, y = Variable(x), Variable(y)

设计构建神经网络:class net 继承torch.nn.model

  1. 200\*2 2\*10 10\*2

参数:n_fea=2,n_hidden=10,n_output=2

输入层2

初始化hidden层10

  1. self.hidden=torch.nn.linear(n_fea,n_hidden)

输出层2

  1. self.out=torch.nn.linear(n_hidden,n_output)
  1. class Net(torch.nn.Module):
  2. def __init__(self, n_feature, n_hidden, n_output):
  3. super(Net, self).__init__()
  4. self.hidden = torch.nn.Linear(n_feature, n_hidden) # hidden layer
  5. self.out = torch.nn.Linear(n_hidden, n_output) # output layer
  6. #激活函数,在隐藏层后加relu非线性变换
  7. def forward(self, x):
  8. x = F.relu(self.hidden(x)) # activation function for hidden layer
  9. x = self.out(x)
  10. return x #输出的预测值x
  11. net = Net(n_feature=2, n_hidden=10, n_output=2) # define the network
  12. print(net)

参数梯度下降sgd:

  1. optimizer = torch.optim.sgd(net.parameters(),lr=0.02)

损失函数:交叉熵:负对数似然函数,并计算softmax

  1. loss_func = torch.nn.CrossEntropyLoss()

参数初始化:

  1. optimizer.zero_grad()

反向传播:

  1. loss.backward()

计算梯度:

  1. optimizer.step()
  1. for t in range(100):
  2. out = net(x) # input x and predict based on x
  3. loss = loss_func(out, y) # must be (1. nn output, 2. target), the target label is NOT one-hotted
  4. optimizer.zero_grad() # clear gradients for next train
  5. loss.backward() # backpropagation, compute gradients
  6. optimizer.step() # apply gradients
  7. if t % 2 == 0:
  8. # plot and show learning process
  9. plt.cla()
  10. prediction = torch.max(F.softmax(out), 1)[1]
  11. pred_y = prediction.data.numpy().squeeze()
  12. target_y = y.data.numpy()
  13. plt.scatter(x.data.numpy()[:, 0], x.data.numpy()[:, 1], c=pred_y, s=100, lw=0, cmap='RdYlGn')
  14. accuracy = sum(pred_y == target_y)/200.
  15. plt.text(1.5, -4, 'Accuracy=%.2f' % accuracy, fontdict={'size': 20, 'color': 'red'})
  16. plt.pause(0.1)
  17. plt.ioff()
  18. plt.show()

交叉熵问题:

二次代价:

a 是 神经元的输出,其中 a = σ(z), z = wx + b

C求导:w,b求偏导:

受到激活函数影响

交叉熵函数:

不用对激活函数导数影响,不管是对w求偏导还是对b求偏导,都和e有关,而和激活函数的导数无关,比如说sigmoid函数,只有中间下降最快,两边的下降逐渐缓慢,最后斜率逼近于直线

反向传播算法:errorBP

可应用于大部分类型神经网络

一般说BP算法时,多指多层前馈神经网络

上面的数据为了演示,造的假数据形成200*2维的矩阵,一列label

那么如何对接我们的真实数据呢?

在我们前几节的数据中大都是这种形式,label feature_id:score

定义一个数组

  1. output =[None]*3 维护3tensor数据:

  1. max_fid = 123
  2. num_epochs = 10
  3. data_path = '/root/7_codes/pytorch_test/data/a8a'
  4. all_sample_size = 0
  5. sample_buffer = []
  6. with open(data_path, 'r') as fd:
  7. for line in fd:
  8. all_sample_size += 1
  9. sample_buffer.append(line.strip())
  10. output = [None] *3
  11. #id
  12. ids_buffer = torch.LongTensor(all_sample_size, max_fid)
  13. ids_buffer.fill_(1)
  14. output[0] = ids_buffer
  15. #weight
  16. weights_buffer = torch.zeros(all_sample_size, max_fid)
  17. output[1] = weights_buffer
  18. #label
  19. label_buffer = torch.LongTensor(all_sample_size)
  20. output[2] = label_buffer
  21. random.shuffle(sample_buffer)
  22. sample_id = 0
  23. for line in sample_buffer:
  24. it = line.strip().split(' ')
  25. label = int(it[0])
  26. f_id = []
  27. f_w = []
  28. for f_s in it[1:]:
  29. f, s = f_s.strip().split(":")
  30. f_id.append(int(f))
  31. f_w.append(float(s))
  32. #解析数据后填入数组
  33. f_id_len = len(f_id)
  34. #适当拓展数组大小
  35. output[0][sample_id, 0:f_id_len] = torch.LongTensor(f_id)
  36. output[1][sample_id, 0:f_id_len] = torch.FloatTensor(f_w)
  37. output[2][sample_id] = label
  38. sample_id += 1
  39. fid, fweight, label = tuple(output)
  40. fid, fweight, label = map(Variable, [fid, fweight, label])

接下来是设计网络结构:

  1. def emb_sum(embeds, weights):
  2. emb_sum = (embeds * weights.unsqueeze(2).expand_as(embeds)).sum(1)
  3. return emb_sum
  4. #输入结构为22696条数据*124维特征*32维特征向量:可以将其想象成立方体的三维条边
  5. #想要把id的特征向量和对应的score相乘并相加,需要把score也就是权重weight拓展成和id同样的维度,即22696 *124 *32 ,而sum(1)就是把124维相加成1维,最后形成22696×32维的平面矩阵,即每个样本后有个32维的向量存在,相当于把feature_id向量化
  6. class Net(torch.nn.Module):
  7. def __init__(self, n_embed, n_feature, n_hidden, n_output):
  8. super(Net, self).__init__()
  9. self.embedding = nn.Embedding(max_fid + 1, n_feature) #124特征*32隐层向量
  10. self.hidden = torch.nn.Linear(n_feature, n_hidden) # hidden layer
  11. self.out = torch.nn.Linear(n_hidden, n_output) # output layer
  12. def forward(self, fea_ids, fea_weights):
  13. embeds = self.embedding(fea_ids)
  14. embeds = emb_sum(embeds, fea_weights)#对特征进一步处理,融合idweight_score进行相容
  15. embeds = nn.functional.tanh(embeds)#非线性变换
  16. hidden = self.hidden(embeds)
  17. output = self.out(hidden)
  18. return output
  19. net = Net(n_embed = 32, n_feature= 32, n_hidden=10, n_output=2) # define the network
  20. print(net)

以上,我们对接了真实的数据类型,那这个demo有没有什么问题呢?

1.是否支持大规模数据,进行快速训练?mini-batch

我们之前学BGD、SGD、MGD梯度下降的训练方法,在上面就运用了sgd的方法,不管是BGD还是SGD都是对所有样本一次性遍历一次,如果想提升,大致相当于MGD的方法:

把所有样本分批处理,每批次有多少个样本(batch),循环所有样本循环多少轮(epoch)。

每训练一个batch产生一条log,参数可保存,之前是每epoch输出一次log,这种方法训练快,性能虽然赶不上全局跑出来的准确率,但仍然一直逼近,小步快跑,每次加载到内存的数据较小,性能方面就高。

  1. num_epochs=10
  2. batch_size =1000
  3. data_path = './data/a8a'
  4. max_fid = 123
  5. def read_and_shuffle(filepath,shuffle=True):
  6. lines=[]
  7. with open(filepath,’r’) as fd:
  8. for linr in fd:
  9. lines.append(line.strip())
  10. if shuffle:
  11. random.shuffle(lines)
  12. return lines
  13. class DataLoader(object):
  14. def __init__(self,data_path,batch_size,shuffle = True):
  15. self.batch_size = batch_size
  16. self.shuffle = shuffle
  17. if os.path.isdir(data_path):
  18. self.files = [
  19. join_path(data_path,i) for I in os.listdir(data_path)
  20. ]
  21. else:
  22. self.files = [data_path]
  23. def parse(self,line_ite):
  24. for line in line_ite:
  25. it = line.strip().split(‘ ’)
  26. label = int(it[0])
  27. f_id = []
  28. f_w = []
  29. for f_s in it[1:]:
  30. f,s = f_s.strip().split(‘:’)
  31. f_id.append(int(f))
  32. f_w.append(float(s))
  33. f_id_len = len(f_id)
  34. output = []
  35. output.append(f_id)
  36. output.append(f_w)
  37. output.append(f_id_len)
  38. output.append(label)
  39. yield output
  40. def to_tensor(self,batch_data,sample_size):
  41. output =[None]*3
  42. ids_buffer = torch.LongTensor(sample_size,max_fid)
  43. ids_buffer.fill_(1)
  44. output[0]=ids_buffer
  45. weights_buffer = torch.zeros(sample_size,max_fid)
  46. output[1]=weights_buffer
  47. label_buffer = torch.LongTensor(sample_size)
  48. output[2] = label_buffer
  49. for sample_id,sample in enumerate(batch_data):
  50. f_id,f_weight,f_id_len,label = sample
  51. output[0][sample_id,0:f_id_len] = torch.LongTensor(f_id)
  52. output[1][sample_id,0:f_id_len] = torch.FloatTensor(f_w)
  53. output[2][sample_id] = label
  54. return tuple(output)
  55. def batch(self):
  56. count = 0
  57. batch_buffer =[]
  58. for f in self.files:
  59. for parse_res in self.parse(read_and_shuffle(f,self.shuffle)):
  60. count+=1
  61. batch_buffer.append(parse_res)
  62. if count ==self.batch_size:
  63. t_size = len(batch_buffer)
  64. batch_tensor = self.to_tensor(batch_buffer,t_size)
  65. yield (batch_tensor,t_size)
  66. count = 0
  67. batch_buffer = []
  68. if batch_buffer:
  69. t_size = len(batch_buffer)
  70. batch_tensor = self.to_tensor(batch_buffer,t_size)
  71. yield (batch_tensor,t_size)
  72. for epoch in range(1,num_epochs+1):
  73. for (batch_id,data_tuple) in enumrate(dataloader.batch(),1):
  74. data = data_tuple[0]
  75. all_sample_size = data_tuple[1]
  76. fid,fweight,label =data
  77. fid,fweight,label=map(Variable,[fid,fweight,label]
  78. optimizer = torch.optim.SGD(net.parameters(), lr=0.02)
  79. loss_func = torch.nn.CrossEntropyLoss() # the target label is NOT an one-hotted
  80. out = net(fid, fweight)
  81. optimizer.zero_grad() # clear gradients for next train
  82. loss = loss_func(out, label)
  83. loss.backward() # backpropagation, compute gradients
  84. optimizer.step() # apply gradients
  85. if batch_id % 2 == 0:#每两批次打印一次
  86. # plot and show learning process
  87. prediction = torch.max(F.softmax(out), 1)[1]
  88. pred_y = prediction.data.numpy().squeeze()
  89. target_y = label.data.numpy()
  90. accuracy = sum(pred_y == target_y)/float(all_sample_size)
  91. print "epoch: %s, batch_id: %s, acc: %s" % (epoch, batch_id, accuracy)

2.怎么使用模型?

现在模型可以训练自己数据了,效果也提升了,那如何把模型的参数保存下来?

  1. print "epoch: %s, batch_id: %s, acc: %s" % (epoch, batch_id, accuracy)
  2. checkpoint = {
  3. 'model': net.state_dict(),#网络静态参数
  4. 'epoch': epoch,
  5. 'batch': batch_id,
  6. }
  7. model_name = 'epoch_%s_batch_id_%s_acc_%s.chkpt' % (epoch, batch_id, accuracy)
  8. model_path = join_path('./model', model_name)
  9. torch.save(checkpoint, model_path)

拆分上面大文件出各个参数:

  1. import os
  2. import sys
  3. import torch
  4. import torch.nn as nn
  5. import random
  6. from torch.autograd import Variable
  7. from os.path import join as join_path
  8. import torch.nn.functional as F
  9. model_path = sys.argv[1]
  10. checkpoint = torch.load(model_path)
  11. model_dict = checkpoint['model']
  12. for k, v in model_dict.items():
  13. print k
  14. model_sub_file_name = k
  15. model_sub_file_path = join_path('./dump', model_sub_file_name)
  16. f = open(model_sub_file_path, 'w')
  17. value = model_dict[k].cpu().numpy()
  18. value.dump(f)
  19. f.close()

模型怎么用?加载、预测

  1. import sys
  2. import numpy as np
  3. import math
  4. embedding_weight = np.load('dump/embedding.weight')
  5. hidden_weight = np.load('./dump/hidden.weight')
  6. hidden_bias = np.load('./dump/hidden.bias')
  7. out_weight = np.load('./dump/out.weight')
  8. out_bias = np.load('./dump/out.bias')
  9. emb_dim = embedding_weight.shape[1]
  10. def calc_score(fids, fweights):
  11. embedding = np.zeros(emb_dim)
  12. for id, weight in zip(fids, fweights):
  13. embedding += weight * embedding_weight[id]
  14. embedding_tanh = np.tanh(embedding)
  15. #对应相乘,第二维相加成10维向量
  16. hidden = np.sum(np.multiply(hidden_weight, embedding_tanh), axis=1) + hidden_bias
  17. out = np.sum(np.multiply(out_weight, hidden), axis=1) + out_bias
  18. return out
  19. def softmax(x):
  20. exp_x = np.exp(x)
  21. softmax_x = exp_x / np.sum(exp_x)
  22. return softmax_x
  23. for line in sys.stdin:
  24. ss = line.strip().split(' ')
  25. label = ss[0].strip()
  26. fids = []
  27. fweights = []
  28. for f_s in ss[1:]:
  29. f, s = f_s.strip().split(':')
  30. fids.append(int(f))
  31. fweights.append(float(s))
  32. pred_label = softmax(calc_score(fids, fweights))[1]
  33. print label, pred_label

3.怎么评估?auc

  1. cat auc.raw | sort -t$'\t' -k2g | awk -F'\t' '($1==-1){++x;a+=y}($1==1){++y}END{print 1.0 - a/(x*y)}'
  1. acc=0.827
  2. auc=0.842569
  3. acc=0.745
  4. auc=0.494206

轮数、acc都影响着auc,数字仅供参考

总结

以上,是以二分类为例,从头演示了一遍神经网络,大家可再找一些0-9手写图片分类任务体验一下,这里总结下,模型拥有参数,所以涉及参数初始化,有了参数后涉及正向传递,分为拟合和泛化两部分建立好模型后,学习过程就是确定参数的过程,使用的是梯度下降法,定义一个误差函数loss function 来衡量模型预测值与真实值(label)之间的差距,通过不断降低差距来使模型预测值逼近真实值,更新参数的反向传递是利用对应的误差值来求得梯度(batch、epoch、learning rate 、shuffle),评估标准也根据任务类型和具体要求可以使用更多的指标。

回归任务常用均方差MSE作为误差函数。评估可用均方差表示。

分类任务常用交叉熵(cross entropy)和softmax配合使用。评估可用准确率表示。

回归任务:

借用知乎yjango末尾的一句话:

但最重要的并非训练集的准确率,而是模型从未见过的测试集的准确率(泛化能力),正如高考,真正的目的是为了解出从未见过的高考题,而不是已经做过的练习题,学习的困难之处正是要用有限的样本训练出可以符合无限同类样本规律的模型。是有限同无限的对抗,你是否想过,为什么看了那么多的道理、听了那么多人生讲座,依然过不哈这一生,一个原因就在于演讲者向你展示的例子都是训练集的样本,他所归纳的方法可以很轻松的完美拟合这些样本,但未必对你将面临的新问题同样奏效,人的一生都在学习,都在通过观察有限的例子找出问题和答案的规律,中医是,玄学是,科学同样是,但科学,是当中最为可靠的一种。

上传的附件
最近文章
eject