项目背景
传统的户型图设计需要建筑师或设计师花费大量时间手工绘制和调整,使用ai自动生成户型图可以减少大量的人力物力。
创新点
目前的相关工作中采用的大多是传统的求解器,比如lp solve,SCIP,OR-Tools等,它们通常作为一个单独的后处理阶段。而cvxpylayer是一个强大的Python库,它将凸优化问题无缝集成到深度学习框架中。简单地说,CVXPYLayers能够用来解决传统深度学习难以处理的复杂问题。
此外,关于网络部分,现有的一些工作使用的是GAN、贝叶斯等网络生成布局,而本项目考虑使用GNN图神经网络生成布局。
调研
3月-4月:关于cvpxylayer的调研
首先建模出用户输入和输出,从用户输入的气泡图中应该可以知道所有房间类型、不同房间之间是否相邻,因此用户的输入可以建模为如下形式:
ori_edges = {"01":'b',
"05":'r',
"03":'f',
"07":'r',
"02":'l',
"29":'b',
"45":'r',
"16":'b',
"18":'r'
}
其中上下左右的邻接关系假设由GNN根据房间类型预测得出。0123...9表示房间类型,r,l,f,b表示房间之间的邻接关系。
完成建模后,要根据房间类型和它们之间的邻接关系设计约束和目标函数,使用cvpxylayer求解每个房间的位置。
经过调研之后,我发现cvxpylayer是基于DPP设计的:
最开始我将约束划分为三个部分:
- 每个房间的长宽约束
- 矩形之间相邻的约束
- 不相邻矩形之间的不重叠约束
目标函数设计为:
min(所有房间矩形中右上角坐标的最大值),这样设计可以使户型图尽可能少得占用面积。
前两个约束都很好表示,能够表示为线性约束,满足DPP规则。但是第三个约束就比较困难了,因为我们事先不知道每两个矩形之间的相对位置关系,无法使用线性的方法来表示这一约束,我也曾考虑过直接计算每两个矩形之间的重叠面积并使它们最小,但是这种方法并不满足DPP规则。因此,唯一的办法就是根据已知的矩形之间的邻接关系,设计一个基于规则的方法,得到每两个矩形之间的相对位置关系,再将其表示为线性的约束。
那么如何设计一个基于规则的方法呢?
我先做出一个假设,根据任意两个节点之间的最短路径可以判断这两个节点的位置关系:
比如:
假设从1号节点到6号节点的最短路径是rbr(右下右),那么可以判断6号节点在1号节点的右下方;
再如,假设10号节点到5号节点的最短路径是lf(左上),那么可以判断5号节点在10号节点的左上方。
当然这种方法要求原始的气泡图中要有一个中心节点(可以看作是实际户型图中的客厅),并且要有较多的节点与中心节点相连。如果原始气泡图中只有少量的节点与中心节点相连(但是实际的户型图一般都会有较多的房间与客厅相连),很难通过基于规则的方法计算出每两个矩形的位置关系,因为总会出现很多冲突情况使得求解器无解。
正例:
反例:
依据这个假设,我可以通过弗洛伊德算法计算每两个矩形之间的最短路径,进而得到所有房间之间的位置关系,再在cvpxylayer中添加不重叠的线性约束,即可得到最优解(每个矩形的左下角和右上角坐标),再将最优解可视化为户型图,部分成果如下:
4月-5月:关于图神经网络的调研
因为这是我第一次正式接触一个深度学习项目,也是我第一次接触GNN,所以经过调研之后,我选择使用相对容易入门的dgl库进行开发。从开始的熟悉远程服务器的Ubuntu操作系统,到pytorch环境搭建,再到安装合适版本的dgl库,就花费了不少时间。待环境搭建好并完成测试时,已经4月上旬了。紧接着我开始考虑模型应该怎么搭建,我原本想要找到一个类似的,能够根据节点属性预测边的类别的GNN模型,然而经过大量调研之后,我发现并没有这样一个合适的demo可以供我参考,于是我只能根据自己的想法搭建一个模型(这对于刚刚入门深度学习的我来说是十分困难的)。
在阅读了大量的DGL相关资料之后,我渐渐知道了该怎样自己搭建一个模型。
首先要确定在dgl中,如何表示输入的图数据。可以先看一下dgl是怎么定义一个图的:
import dgl
import numpy as np
def build_karate_club_graph():
# All 78 edges are stored in two numpy arrays. One for source endpoints
# while the other for destination endpoints.
src = np.array([1, 2, 2, 3, 3, 3, 4, 5, 6, 6, 6, 7, 7, 7, 7, 8, 8, 9, 10, 10,
10, 11, 12, 12, 13, 13, 13, 13, 16, 16, 17, 17, 19, 19, 21, 21,
25, 25, 27, 27, 27, 28, 29, 29, 30, 30, 31, 31, 31, 31, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 32, 33, 33, 33, 33, 33, 33, 33,
33, 33, 33, 33, 33, 33, 33, 33, 33, 33])
dst = np.array([0, 0, 1, 0, 1, 2, 0, 0, 0, 4, 5, 0, 1, 2, 3, 0, 2, 2, 0, 4,
5, 0, 0, 3, 0, 1, 2, 3, 5, 6, 0, 1, 0, 1, 0, 1, 23, 24, 2, 23,
24, 2, 23, 26, 1, 8, 0, 24, 25, 28, 2, 8, 14, 15, 18, 20, 22, 23,
29, 30, 31, 8, 9, 13, 14, 15, 18, 19, 20, 22, 23, 26, 27, 28, 29, 30,
31, 32])
# Edges are directional in DGL; Make them bi-directional.
u = np.concatenate([src, dst])
print(u)
v = np.concatenate([dst, src])
# Construct a DGLGraph
return dgl.DGLGraph((u, v))
G = build_karate_club_graph()
embed = nn.Embedding(34, 5) # 34 nodes with embedding dim equal to 5
G.ndata['feat'] = embed.weight
以上是dgl官方文档的定义方法。可以看到,dgl使用源节点和目标节点的对应关系来表示一个图,使用G.ndata['feat']来表示节点特征,使用G.edata['feat']表示边特征。
而在我的项目中,已知了图的拓扑结构和节点类别,需要根据这些信息预测每条边的类别。于是我将节点类别作为节点的特征G.ndata['feat'],使用SAGEConv进行消息传递(dgl的SAGEConv一层传递一轮)。完成消息传递后,对于每条边,将它两端的节点特征拼接作为边的特征,再将边的特征输入到mlp多层感知机中做一个4分类任务(上下左右四种关系)。以上便是我设计模型的过程。
完成模型的设计之后,更重要的一步是获取数据集。在我的导师的指导下,我找到了Graph2Plan: Learning Floorplan Generation from Layout Graphs这篇论文,它对80000张真实户型图RPLAN数据集进行了处理,得到了每张户型图对应的气泡图,获取数据集的项目链接:
于是我使用这个项目获取了我所需要的80000张数据集。
(这里有个困难的地方在于,项目中调用了matlab代码,因此我需要在乌班图服务器中安装对应版本的matlab,才能把项目跑起来,而远程服务器的matlab下载界面在我电脑上无法显示,我只能在自己电脑上下载之后,再传到服务器上。总之,这个环境配置过程也耗费了很长时间)。
得到数据集之后,就可以对数据集进行清洗,用于训练了。需要先对数据集进行padding操作,我设计了两种方案:
方案1:
对边数量不足15的图(graph)补全,填充内容是已有的边。
eg. old: src = [0123456542]
dst = [5236102153]
edge = [0123203102]
new : src = [0123456542 01234]
dst = [5236102153 52361]
edge = [0123203102 01232]
方案2:
对边数量不足15的图(graph)补全,填充内容是4(表示相同的节点的重叠关系,此时有5种边)。
eg. old: src = [0123456542]
dst = [5236102153]
edge = [0123203102]
new : src = [0123456542 00000]
dst = [5236102153 00000]
edge = [0123203102 44444]
在实际训练过程中,方案二的准确率明显高于方案一,我觉得可能是因为大量的相同边[0000][0000][4444]更容易学习到,而真实的边更加复杂,模型难以学习到它们。
完成上述步骤之后,就可以开始训练了,但是结果似乎并不理想,我目前依然在调试网络,希望能够得到好的结果。
项目链接: