mantaflow-tensorflow tutorial

                      

                                                  tuorial_1

Overview

mantaflow框架是为开发人员和研究人员设计的,以便尽可能方便地测试和开发新算法。这意味着一些折衷:它并不是要成为最终用户的工具,因此,用户界面绝对是非常基本的,并且主要是为快速调试而设计的。同时,我们试图使代码最少且通用。因此,它并没有完全优化。当然,速度相当快,但是为了提高执行速度,我们一直没有牺牲牺牲代码的可读性(在很多地方,它还是不平凡的)。

 

网格和数组

耦合的最重要方面是在mantaflow网格和numpy数组之间交换数据。前者是mantaflow中所有流体求解器的核心数据类型,而张量流中的神经网络固有地使用numpy数组。虽然两者都只是简单的密集数组,但重要的区别是mantaflow使用“ grid(x,y,z)”访问网格,而numpy数组使用“ array [z,y,x]”。因此,mantaflow具有“字典”索引顺序,而numpy通常遵循存储步伐。在分配和索引数据时要记住这一点,这一点很重要。

文件numpyconvert.cpp中有几个python帮助程序函数,以便在两者之间复制数据。例如,您可以使用copyArrayToGridReal将numpy数组的内容复制到标量mantaflow网格。请注意,所有函数都需要具有正确大小的现有网格和数组,它们不会自行调整大小或分配内存。而且,它们假定numpy数组的连续C样式内存顺序。如果不确定,最好使用nd.copy进行复制。

相应地,存在用于mantaflow的其他本机数据类型的传递函数,例如copyGridToArrayVec3,用于将Vec3 mantaflow网格复制到numpy数组。对于这些功能,“网格”指的是mantaflow网格,而“数组”指的是numpy数据。

 

点和粒子

 

粒子数据对于许多mantaflow场景也很重要。为此,还有另一套函数可以在particle数据字段(“ pdata”)和numpy数组之间复制数据,例如copyArrayToPdataReal。如前所述,“数组”表示一个numpy数组。注意,您当然也可以沿着numpyconvert.cpp编写自己的数据传输函数,并且mantaflow PYTHON函数还支持将numpy数组作为参数。

在下面的代码片段中可以找到带有示例网格转换的完整代码示例。它首先创建一个64 x 128 x 192域,并分配一个标量(scale)和矢量3(vector-3)网格。然后,分配相应的numpy数组。请注意,现在交换了维度的顺序:mantaflow中的“ res,res * 2,res * 3”变成了numpy的“ res * 3,res * 2,res”,我们必须明确分配第四个维度来确定每个单元格数据的维数。然后,将numpy标量网格数据复制到mantaflow,并将mantaflow Vec3网格数据复制到numpy。太棒了–默认情况下,两个网格都存储零,因此我们来回复制了大量零🙂!

from manta import *
import numpy as np
res = 64
solver = Solver(name='manta', gridSize=vec3(res,res*2,res*3), dim=3)
scalar = solver.create(RealGrid) 
vel = solver.create(MACGrid)
npAr = np.zeros( (res*3, res*2, res, 1), dtype='f')
npVel = np.zeros( (res*3, res*2, res, 3), dtype='f')
copyArrayToGridReal( source=npAr, target=scalar )
copyGridToArrayVec3( source=vel, target=npVel )

 

在source / pluings / tfplugins.cpp中可以找到有关如何使用C ++代码处理numpy数组数据的示例。示例内核迭代器在标量mantaflow网格上进行迭代,并从其pData指针访问numpy数据(假设它包含浮点值的二维网格)。该示例代码仅将按因子缩放的numpy数据的单个条目添加到mantaflow网格的每个单元上。

 

KERNEL(bnd=0)
void knSimpleNumpyTest(Grid<Real>& grid, PyArrayContainer npAr, Real scale)
{
    const float* p = reinterpret_cast<float*>(npAr.pData);
    grid(i,j,k) += scale * p[j*grid.getSizeX()+i]; 
}

 

之后,可以从具有python绑定的函数或任何其他C ++函数中“照常”调用内核:

 

PYTHON() void simpleNumpyTest( Grid<Real>& grid, PyArrayContainer npAr, Real scale) {
    knSimpleNumpyTest(grid, npAr, scale);
}

 

并得出结论,您可以在下面找到python场景中所有来自source / plugin / numpyconvert.cpp的数据传输函数的列表:

 copyArrayToGridReal(numpyArray source, RealGrid target) 
 copyGridToArrayReal(RealGrid source, numpyArray target)
 copyArrayToGridLevelset(numpyArray source, LevelsetGrid target)
 copyGridToArrayLevelset(LevelsetGrid source, numpyArray target)
 copyArrayToGridVec3(numpyArray source, VecGrid target)
 copyGridToArrayVec3(VecGrid source, numpyArray target)
 copyArrayToGridMAC(numpyArray source, MACGrid target)
 copyGridToArrayMAC(MACGrid source, numpyArray target)

 copyArrayToPdataInt(PdataInt p, numpyArray n)
 copyPdataToArrayInt(numpyArray n, PdataInt p)
 copyArrayToPdataReal(PdataReal p, numpyArray n)
 copyPdataToArrayReal(numpyArray n, PdataReal p)
 copyArrayToPdataVec3(PdataVec3 p, numpyArray n)
 copyPdataToArrayVec3(numpyArray n, PdataVec3 p)

 

 

                                                  tutorial_2

因此,让我们从一个尽可能简单的示例开始:一个非常简单的mantaflow场景,该场景生成一些流量数据,一个简单的tensorflow设置,使用该数据训练一个简单的神经网络。可以在“ mantaflow / tensorflow / example0_simple”下的mantaflow中找到这样的设置。此示例演示了如何使用mantaflow生成流量数据集,然后使用烟雾密度数据集训练简单的自动编码器。自动编码器(AE)是一种特别方便的启动方式,因为AE只需学习数据集的减少的潜在空间表示,就应该从中尽可能准确地重建原始信号。如果您对AE的详细信息感兴趣,可以例如在Wikipedia(https://en.wikipedia.org/wiki/Autoencoder)和该页面的参考资料中找到更多信息。

请注意,每个mantaflow示例都附带一个简短的README.txt文件,该文件简要总结了运行示例的步骤。在下文中,我们将进一步详细介绍。还需要指出的是,带有“ manta_”前缀的python文件应该与mantaflow一起运行,而“ tf_”文件可以简单地在python中执行。由于mantaflow最初是作为独立模拟器使用的,因此目前无法作为python库使用。相反,mantaflow可执行文件在加载时提供了模拟器功能,因此这两种类型的python文件之间存在差异。

 

使用数据进行训练…

 

这个简单的示例仅包含两个python文件:用于生成流数据的“ manta_genSimSimple.py”和包含网络设置和训练代码的“ tf_simple.py”。自然地,数据生成是第一位的。此设置的核心是一个简单的烟雾模拟(主循环中的advectSemiLagrange,addBuoyancy和solvePressure调用)。我们不会在此处讨论烟雾模拟的详细信息,如果您对此部分感兴趣,请访问http://mantaflow.com/scenes.html上的mantaflow场景教程。除了烟雾模拟之外,manta_genSimSimple场景还初始化了几个随机密度源和两个方向随机的速度源。在“ for range inn(noiseN)”循环中创建密度源,并在下面初始化速度源。

所有mantaflow示例都将其数据存储在“基本目录”中,即basePath变量,默认情况下将其设置为“ ../data”。所有示例还假设它们是从python脚本所在的目录中运行的。因此,在使用“ ... / manta ./manta_genSimSimple.py”开始场景之前,请确保例如使用“ cd mantaflow / tensorflow / example0_simple”切换到正确的目录。请注意,此场景(以及其他示例中的场景)采用可选的命令行参数“ seed”,可用于确定性地创建随机数据集。默认情况下,在numpy中使用随机的初始种子,因此在不带任何其他参数的情况下调用时,场景将生成一个随机数据集,并将数据保存在“ ../data/simSimple_XXXX”中,其中XXXX被第一个替换免费的可用索引(从索引1000开始)。运行之后,您会在此目录中找到一系列“ density_”和“ vel _”的uni文件。 .uni文件是mantaflow自己的自定义格式,用于保存网格和粒子数据。这是一种相对简单的gzip压缩二进制格式,带有一些标题信息,是在mantaflow中读取和写入数据的最简单方法。

 

对于深度学习任务,通常需要尽可能多的数据。对于此mantaflow示例,您可以简单地运行生成脚本几次,例如十次,以生成相当数量的模拟数据集。由于该任务是在训练自动编码器,因此,数据中的差异较大实际上会使任务难度加大,因此建议不要生成更多数据(除非使网络更大)。之后,您应该找到ca。 “ ../data/”目录中的十个“ simSimple_”目录。如果存在这些数据,则可以开始使用数据训练第一个神经网络。

 

使网络学习

 

对于此设置,训练NN实际上非常简单:只需在python中运行“ tf_simple.py”脚本即可。请注意,常见的初始陷阱是安装了多个python版本(例如2.x和3.y),并调用了错误的版本。这些脚本应可用于2和3,但我们强烈建议在实践中仅使用一个。 tensorflow脚本本身值得一看。首先重要的一点是加载mantaflow数据,并准备进行训练。

加载仿真数据是此文件中的重要第一步。在这个简单的示例中,这是非常直接的方式。该脚本只查找索引范围为1000到2000的目录,然后使用uniio模块的“ readUni”功能(在“ ../tools”中)从每个目录中加载100个.uni密度文件。此函数以numpy数组形式返回header和网格数据。在下面的代码片段中,您可以找到此循环以及从uni文件header中检索x和y维的代码:

for sim in range(1000,2000):    
     if os.path.exists( "%s/simSimple_%04d" % (basePath, sim) ):      
     for i in range(0,100):            
          filename = "%s/simSimple_%04d/density_%04d.uni"            
          uniPath = filename % (basePath, sim, i)              
          header, content = uniio.readUni(uniPath)            
          h = header['dimX']
          w = header['dimY']

宽度和高度派上用场以重塑阵列。默认情况下,readUni函数始终返回形状为ZYXC的数组,其中ZYX是空间尺寸,C是每个单元格的通道。对于标量网格(最初是mantaflow中的RealGrid类型),我们将有一个单一值,即C = 1。因此,在这种情况下,readUni将返回形状为[1,64,64,1]的数组。接下来的几行确保数组具有正确的形状,并简化了书写图像。

值得更详细地解释。从理论上讲,我们可以在加载ZYXC数组后对其进行处理,但是对于纯2D示例,如果必须绕Z轴拖动,则不必要地使事情复杂化。因此,我们可以简单地对数组进行整形以舍弃Z轴,只要我们确保数组中只有一个2D切片,它就可以正常工作。

此外,在UI中显示网格时,mantaflow假定原点(0,0)在左下角。另一方面,对于图像输出,通常在(0,0)点位于右上角。因此,为了确保我们可以简单地将任何数组写出为图像,并获得类似于mantaflow的外观,我们可以使用一些python切片魔术来沿Y轴反转数组内容。虽然下面的代码中的[::]切片符号乍看起来可能很奇怪,但它是一个强大的工具,在许多numpy和tensorflow示例中都会遇到,因此值得习惯。例如,查看https://docs.scipy.org/doc/numpy/reference/arrays.indexing.html了解更多详细信息。简而言之,我们可以编写“ start:stop:step”来指定范围。如果忽略了其中一些,则numpy只需插入默认值。因此,“ ::-1”跨轴的整个长度,反步为-1。最后,将数组添加到densities变量,该变量只是数组对象的列表,以后将用于训练网络。

arr = arr[:, ::-1, :, :] 
arr = np.reshape(arr, [w, h, 1])
densities.append( arr )

 

接下来,我们将数据切分成至少一个完整的模拟作为验证数据集(如果有足够的话,将占所有数据的10%),其余的将用于训练。
 

loadNum = len(densities)valiSize = max(100, int(loadNum * 0.1)) 
valiData = densities[loadNum-valiSize:loadNum,:] 
densities = densities[0:loadNum-valiSize,:]

现在我们可以继续建立网络了。如上所述,我们使用的是简单的自动编码器。接下来的x代表输入,而y代表“基本事实”值,即我们希望网络尽可能准确地计算出的正确输出。因此,我们试图表示f(x)= y的函数f。我们首先要定义与密度网格大小相同的占位符,然后为批次大小添加另一个尺寸。有关使用tensorflow设置神经网络的详细信息,请查看官方资源,例如https://www.tensorflow.org/get_started/mnist/beginners上的MNIST示例。简而言之,我们将分配一个单独的大小为50的完全连接层,然后将大小为[64,64,1]的完整输出重新创建为y_pred。您可以在下图中找到网络体系结构的摘要。

 

请注意,对于完全连接的层fc1,我们直接包含相当数量的辍学率,速率为0.5。作为激活功能,我们在这里使用tanh,并且y_pred中的最终输出是网络的最终输出,因此我们不对其应用激活功能。 y_pred完成的所有操作都将其重塑为网格形式。

x = tf.placeholder(tf.float32, shape=[None, 64,64, 1])
y = tf.placeholder(tf.float32, shape=[None, 64,64, 1])
inSize = 64 * 64 * 1
xIn    = tf.reshape(x, shape=[-1, inSize ]) 

fc_1w  = tf.Variable(tf.random_normal([inSize, 50], stddev=0.01))
fc_1b  = tf.Variable(tf.random_normal([50], stddev=0.01))

fc1 = tf.add(tf.matmul(xIn, fc_1w), fc_1b)
fc1 = tf.nn.tanh(fc1)fc1 = tf.nn.dropout(fc1, 0.5)

fc_2w = tf.Variable(tf.random_normal([50, inSize], stddev=0.01))  
fc_2b = tf.Variable(tf.random_normal([inSize], stddev=0.01))

y_pred = tf.add(tf.matmul(fc1, fc_2w), fc_2b)
y_pred = tf.reshape( y_pred, shape=[-1, 64, 64, 1])

 

损失函数和优化器非常简单:一个简单的L2损失,以及流行的ADAM优化器,步长足够小:

 

cost = tf.nn.l2_loss(y - y_pred) 
opt  = tf.train.AdamOptimizer(0.0001).minimize(cost)

 

现在,我们已经准备好了所有部分来训练网络。我们可以设置一个tensorflow会话,并开始training epoch loop。请注意,虽然“epoch”通常表示训练数据完全通过,但我们在这里使用它来表示训练迭代。尽管试图使事情保持简单,但我们会重新整理批次的条目,以避免计算出的梯度出现偏差。通过生成随机索引到我们的训练数组中,并将选定的数组附加到当前批处理中,可以非常简单地完成此操作。在此脚本中,我们使用的批次固定大小为10。然后使用tensorflow的“ feed_dict”功能将该批次输入x中。

 

sess = tf.InteractiveSession()
sess.run(tf.global_variables_initializer())
for epoch in range(trainingEpochs): 
    c = (epoch * batchSize) % densities.shape[0] 
    batch = [] 
    for currNo in range(0, batchSize): 
        r = random.randint(0, loadNum-1)  
        batch.append( densities[r] ) 
    _ , currentCost = sess.run([opt, cost], feed_dict={x: batch, y: batch})

 

此处最有趣的调用是sess.run,它通过网络执行完整的前向和后向传递,以更新权重。请注意,将评估在第一个列表“ [opt,cost]”中传递的所有变量,并由sess.run返回它们各自的值。在此示例中,我们通过在“ _,currentCost”中使用下划线占位符来忽略优化器的返回值。我们仅存储成本(cost),以便在必要时可以跟踪训练进程。虽然以随机批次重复调用sess.run就足以训练网络,但通常重要的是评估网络对新数据的推广程度。在此示例中,我们每10个更新步骤就在验证数据上运行经过训练的网络

 

[valiCost,vout] = sess.run([cost, y_pred], feed_dict={x: valiData, y: valiData})

对于此sess.run调用,我们不需要评估optimizer(优化器),而只需检查根据ground truth(地面真实情况)y与预测y_pred之间的L2距离计算出的成本。另外,我们将生成的数组存储在变量vout中。这对于训练不是绝对必要的。相反,它只是简化了训练结束时将结果写入磁盘的过程。运行此示例时,您应该在屏幕上看到诸如以下的输出:

命令行输出应为您提供有关网络如何对训练和验证数据进行操作的粗略反馈。在此示例中,L2范数未标准化,w.r.t. 输入的数量,因此,更多的验证样本将导致更大的值。尽管如此,无论您使用哪种绝对值,在训练迭代过程中,两种成本都应缓慢降低。到达最后一次迭代后,此脚本使用scipy的toimage函数将整个验证数据集输出为png图像:

 

for i in range(len(valiData)):    
    scipy.misc.toimage( np.reshape(valiData[i], [64, 64]) , cmin=0.0, cmax=1.0).save("in_%d.png" % i)    
    scipy.misc.toimage( np.reshape(vout[i]    , [64, 64]) , cmin=0.0, cmax=1.0).save("out_%d.png" % i)

 

因此,脚本完成后,您应该找到ca. 100 in_XXX.png图像显示输入,100 out_XXX.png图像显示网络的相应重建。您可以在下面找到默认设置的示例。您会注意到,重构与输入的匹配并不十分紧密,但是您仍然应该能够大致看到输入的形状。另外,请记住,该网络的潜在空间(隐藏层的大小)仅为50!不到输入大小的2%(即64 * 64),对于我们的流量模拟中的混沌密度输入,网络仅从那50个标量中重建输入就做得很好。

 

 

总结

 

到此结束了“尽可能简单”的示例。您可以通过多种方法来改进和扩展此设置。例如,尝试使用不同数量的输入数据,不同的隐藏层大小,不同的优化程序/激活功能,或添加多个隐藏层。更高级的一点:您可以添加卷积层(在下一章中有更多介绍),或将自动编码器变成可变编码器,以改善潜在空间的形状,通常还可以改善重构。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值