You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

vae.py 8.4 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. # -*- coding: utf-8 -*-
  2. # ---
  3. # jupyter:
  4. # jupytext_format_version: '1.2'
  5. # kernelspec:
  6. # display_name: Python 3
  7. # language: python
  8. # name: python3
  9. # language_info:
  10. # codemirror_mode:
  11. # name: ipython
  12. # version: 3
  13. # file_extension: .py
  14. # mimetype: text/x-python
  15. # name: python
  16. # nbconvert_exporter: python
  17. # pygments_lexer: ipython3
  18. # version: 3.5.2
  19. # ---
  20. # # 变分自动编码器
  21. # 变分编码器是自动编码器的升级版本,其结构跟自动编码器是类似的,也由编码器和解码器构成。
  22. #
  23. # 回忆一下,自动编码器有个问题,就是并不能任意生成图片,因为我们没有办法自己去构造隐藏向量,需要通过一张图片输入编码我们才知道得到的隐含向量是什么,这时我们就可以通过变分自动编码器来解决这个问题。
  24. #
  25. # 其实原理特别简单,只需要在编码过程给它增加一些限制,迫使其生成的隐含向量能够粗略的遵循一个标准正态分布,这就是其与一般的自动编码器最大的不同。
  26. #
  27. # 这样我们生成一张新图片就很简单了,我们只需要给它一个标准正态分布的随机隐含向量,这样通过解码器就能够生成我们想要的图片,而不需要给它一张原始图片先编码。
  28. #
  29. # 一般来讲,我们通过 encoder 得到的隐含向量并不是一个标准的正态分布,为了衡量两种分布的相似程度,我们使用 KL divergence,利用其来表示隐含向量与标准正态分布之间差异的 loss,另外一个 loss 仍然使用生成图片与原图片的均方误差来表示。
  30. #
  31. # KL divergence 的公式如下
  32. #
  33. # $$
  34. # D{KL} (P || Q) = \int_{-\infty}^{\infty} p(x) \log \frac{p(x)}{q(x)} dx
  35. # $$
  36. # ## 重参数
  37. # 为了避免计算 KL divergence 中的积分,我们使用重参数的技巧,不是每次产生一个隐含向量,而是生成两个向量,一个表示均值,一个表示标准差,这里我们默认编码之后的隐含向量服从一个正态分布的之后,就可以用一个标准正态分布先乘上标准差再加上均值来合成这个正态分布,最后 loss 就是希望这个生成的正态分布能够符合一个标准正态分布,也就是希望均值为 0,方差为 1
  38. #
  39. # 所以标准的变分自动编码器如下
  40. #
  41. # ![](https://ws4.sinaimg.cn/large/006tKfTcgy1fn15cq6n7pj30k007t0sv.jpg)
  42. # 所以最后我们可以将我们的 loss 定义为下面的函数,由均方误差和 KL divergence 求和得到一个总的 loss
  43. #
  44. # ```
  45. # def loss_function(recon_x, x, mu, logvar):
  46. # """
  47. # recon_x: generating images
  48. # x: origin images
  49. # mu: latent mean
  50. # logvar: latent log variance
  51. # """
  52. # MSE = reconstruction_function(recon_x, x)
  53. # # loss = 0.5 * sum(1 + log(sigma^2) - mu^2 - sigma^2)
  54. # KLD_element = mu.pow(2).add_(logvar.exp()).mul_(-1).add_(1).add_(logvar)
  55. # KLD = torch.sum(KLD_element).mul_(-0.5)
  56. # # KL divergence
  57. # return MSE + KLD
  58. # ```
  59. # 下面我们用 mnist 数据集来简单说明一下变分自动编码器
  60. # + {"ExecuteTime": {"start_time": "2018-01-01T10:41:05.215490Z", "end_time": "2018-01-01T10:41:05.738797Z"}}
  61. import os
  62. import torch
  63. from torch.autograd import Variable
  64. import torch.nn.functional as F
  65. from torch import nn
  66. from torch.utils.data import DataLoader
  67. from torchvision.datasets import MNIST
  68. from torchvision import transforms as tfs
  69. from torchvision.utils import save_image
  70. # + {"ExecuteTime": {"start_time": "2018-01-01T10:41:05.741302Z", "end_time": "2018-01-01T10:41:05.769643Z"}}
  71. im_tfs = tfs.Compose([
  72. tfs.ToTensor(),
  73. tfs.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5]) # 标准化
  74. ])
  75. train_set = MNIST('./mnist', transform=im_tfs)
  76. train_data = DataLoader(train_set, batch_size=128, shuffle=True)
  77. # + {"ExecuteTime": {"start_time": "2018-01-01T10:41:06.306479Z", "end_time": "2018-01-01T10:41:06.397118Z"}}
  78. class VAE(nn.Module):
  79. def __init__(self):
  80. super(VAE, self).__init__()
  81. self.fc1 = nn.Linear(784, 400)
  82. self.fc21 = nn.Linear(400, 20) # mean
  83. self.fc22 = nn.Linear(400, 20) # var
  84. self.fc3 = nn.Linear(20, 400)
  85. self.fc4 = nn.Linear(400, 784)
  86. def encode(self, x):
  87. h1 = F.relu(self.fc1(x))
  88. return self.fc21(h1), self.fc22(h1)
  89. def reparametrize(self, mu, logvar):
  90. std = logvar.mul(0.5).exp_()
  91. eps = torch.FloatTensor(std.size()).normal_()
  92. if torch.cuda.is_available():
  93. eps = Variable(eps.cuda())
  94. else:
  95. eps = Variable(eps)
  96. return eps.mul(std).add_(mu)
  97. def decode(self, z):
  98. h3 = F.relu(self.fc3(z))
  99. return F.tanh(self.fc4(h3))
  100. def forward(self, x):
  101. mu, logvar = self.encode(x) # 编码
  102. z = self.reparametrize(mu, logvar) # 重新参数化成正态分布
  103. return self.decode(z), mu, logvar # 解码,同时输出均值方差
  104. # + {"ExecuteTime": {"start_time": "2018-01-01T10:41:06.430817Z", "end_time": "2018-01-01T10:41:10.056600Z"}}
  105. net = VAE() # 实例化网络
  106. if torch.cuda.is_available():
  107. net = net.cuda()
  108. # + {"ExecuteTime": {"start_time": "2018-01-01T10:41:10.059597Z", "end_time": "2018-01-01T10:41:10.409900Z"}}
  109. x, _ = train_set[0]
  110. x = x.view(x.shape[0], -1)
  111. if torch.cuda.is_available():
  112. x = x.cuda()
  113. x = Variable(x)
  114. _, mu, var = net(x)
  115. # + {"ExecuteTime": {"start_time": "2018-01-01T10:41:29.749178Z", "end_time": "2018-01-01T10:41:29.753678Z"}}
  116. print(mu)
  117. # -
  118. # 可以看到,对于输入,网络可以输出隐含变量的均值和方差,这里的均值方差还没有训练
  119. #
  120. # 下面开始训练
  121. # + {"ExecuteTime": {"start_time": "2018-01-01T10:13:54.530108Z", "end_time": "2018-01-01T10:13:54.560436Z"}}
  122. reconstruction_function = nn.MSELoss(size_average=False)
  123. def loss_function(recon_x, x, mu, logvar):
  124. """
  125. recon_x: generating images
  126. x: origin images
  127. mu: latent mean
  128. logvar: latent log variance
  129. """
  130. MSE = reconstruction_function(recon_x, x)
  131. # loss = 0.5 * sum(1 + log(sigma^2) - mu^2 - sigma^2)
  132. KLD_element = mu.pow(2).add_(logvar.exp()).mul_(-1).add_(1).add_(logvar)
  133. KLD = torch.sum(KLD_element).mul_(-0.5)
  134. # KL divergence
  135. return MSE + KLD
  136. optimizer = torch.optim.Adam(net.parameters(), lr=1e-3)
  137. def to_img(x):
  138. '''
  139. 定义一个函数将最后的结果转换回图片
  140. '''
  141. x = 0.5 * (x + 1.)
  142. x = x.clamp(0, 1)
  143. x = x.view(x.shape[0], 1, 28, 28)
  144. return x
  145. # + {"ExecuteTime": {"start_time": "2018-01-01T10:13:54.562533Z", "end_time": "2018-01-01T10:35:01.115877Z"}}
  146. for e in range(100):
  147. for im, _ in train_data:
  148. im = im.view(im.shape[0], -1)
  149. im = Variable(im)
  150. if torch.cuda.is_available():
  151. im = im.cuda()
  152. recon_im, mu, logvar = net(im)
  153. loss = loss_function(recon_im, im, mu, logvar) / im.shape[0] # 将 loss 平均
  154. optimizer.zero_grad()
  155. loss.backward()
  156. optimizer.step()
  157. if (e + 1) % 20 == 0:
  158. print('epoch: {}, Loss: {:.4f}'.format(e + 1, loss.data[0]))
  159. save = to_img(recon_im.cpu().data)
  160. if not os.path.exists('./vae_img'):
  161. os.mkdir('./vae_img')
  162. save_image(save, './vae_img/image_{}.png'.format(e + 1))
  163. # -
  164. # 可以看看使用变分自动编码器得到的结果,可以发现效果比一般的编码器要好很多
  165. #
  166. # ![](https://ws1.sinaimg.cn/large/006tKfTcgy1fn1ag8832zj306q0a2gmz.jpg)
  167. #
  168. # 我们可以输出其中的均值看看
  169. # + {"ExecuteTime": {"start_time": "2018-01-01T10:40:36.463332Z", "end_time": "2018-01-01T10:40:36.481622Z"}}
  170. x, _ = train_set[0]
  171. x = x.view(x.shape[0], -1)
  172. if torch.cuda.is_available():
  173. x = x.cuda()
  174. x = Variable(x)
  175. _, mu, _ = net(x)
  176. # + {"ExecuteTime": {"start_time": "2018-01-01T10:40:37.485127Z", "end_time": "2018-01-01T10:40:37.490484Z"}}
  177. print(mu)
  178. # -
  179. # 变分自动编码器虽然比一般的自动编码器效果要好,而且也限制了其输出的编码 (code) 的概率分布,但是它仍然是通过直接计算生成图片和原始图片的均方误差来生成 loss,这个方式并不好,在下一章生成对抗网络中,我们会讲一讲这种方式计算 loss 的局限性,然后会介绍一种新的训练办法,就是通过生成对抗的训练方式来训练网络而不是直接比较两张图片的每个像素点的均方误差

机器学习越来越多应用到飞行器、机器人等领域,其目的是利用计算机实现类似人类的智能,从而实现装备的智能化与无人化。本课程旨在引导学生掌握机器学习的基本知识、典型方法与技术,通过具体的应用案例激发学生对该学科的兴趣,鼓励学生能够从人工智能的角度来分析、解决飞行器、机器人所面临的问题和挑战。本课程主要内容包括Python编程基础,机器学习模型,无监督学习、监督学习、深度学习基础知识与实现,并学习如何利用机器学习解决实际问题,从而全面提升自我的《综合能力》。