All 3d Algorithm AWS Data Deep Learning Design diffusers DRL Generative AI Graph IFC k8s Landbook LBDeveloper LLM Machine Learning Optimization Parametricism PoC rendering Research threejs Wave Function Collapse
미분가능한 환경을 찾아서

미분가능한 환경을 찾아서

Abstract 건축 알고리즘, 혹은 패러메트릭 디자인을 수행한다는 것은 파라미터에 따른 결과를 만들어내는 알고리즘이라 생각을 고정하기 쉽습니다. 하지만 ga 나 es 같은 heuristic optimizer 에서는 그 알고리즘 자체가 파라미터를 변경하고자 하는 환경으로, 더욱이 모델이 학습의 기준으로 사용하기 위한 환경으로 사용되며, 강화 학습이 될 경우, 환경은 모델의 방향성을 판단하는 기준 그 자체가 됩니다.

이 때, 환경은 어떤 특성을 지녀야 하는가에 대한 탐구의 일환으로, 미분가능한 환경의 의의를 찾고 결과를 테스트해봤습니다. gradient 단절이 확실한 환경과 그렇지 않은 환경 두 환경을 설정하고 실제로 미분 가능한 환경일 때 사용이 가능한 gradient descent 및 AdamW 방법을 포함해, ga 를 비교해 미분 가능한 환경의 의의를 확인할 수 있었습니다. Problem 건축 알고리즘 관련 환경을 작성한다 - 혹은 패러메트릭 디자인 환경을 작성한다 - 는 것은 직관적으로는 다음과 같은 형식을 가지고 있습니다. 원의 위치와 반지름을 파라미터로 원을 생성한다. loss 는 원의 넓이와 반지름 5인 경우의 원의 넓이(25)와의 차이의 10분의 1로 정의한다. 위처럼 로직이 단순할 떄는 파라미터와 결과 사이의 직관성이 크게 떨어지는 일은 많지 않을 것입니다. 그 때의 radius - loss 그래프와 같이 표현하면 다음과 같습니다. (x >= 0 인 경우) 하지만 알고리즘이 점점 복잡해질수록 이 환경은 블랙박스가 되어가고, 사용자는 물론 이를 개발한 사람 조차도 파라미터에 따른 결과의 경향성 조차 파악하기 어려워집니다. 이 때 문제가 발생합니다. 강화학습의 모델까지 생각할 것도 없이, ga와 같은 최적화 과정에서도 파라미터 변경에 따른 결과의 “측정”은 필요합니다. 하지만 파라미터의 차이가 환경에 미칠 영향의 랜덤성이 너무 커 파라미터 변경에 따른 결과 역시 지나치게 랜덤해집니다. 위의 그림은 파라미터 하나의 경우에만의 예시인데도 문제가 바로 보입니다. 하지만 건축 알고리즘 관련 엔진에서는 당연히 파라미터를 한두개 사용하는 것으로 끝낼 수 있는 경우는 존재하지 않습니다. 그러면 다음과 같은 경향을 확인할 수 있는 부드러운 곡면은 물론이고, 최적화 모듈은 경향성을 파악하는 것이 사실상 불가능에 가깝다는 것입니다. continuous 한 [parameter - loss] 없이 continuous 한 엔진은 없습니다. 따라서, 어떠한 환경의 기능 및 파라미터를 추가할 때, 그 파라미터와 파라미터에 대응하는 환경이 만드는 score (혹은 loss 의 음수) 가 미분 가능한 관계가 될 수 있도록 의식적으로 추가해보는 것으로 시작해보았습니다. Premises 패러메트릭 디자인 알고리즘을 포함해, 이 포스트에서 말하는 환경은 파라미터를 입력해 결과 geometry 관련 데이터 및 loss까지 계산할 수 있는 모듈을 의미한다. 미분 가능함 혹은 파라미터의 연결성을 명시적으로 보장하기 위해, starting parameter 에서 loss 계산까지의 모든 과정은 torch 의 tensor 연산으로만 이루어진다. Environment Details 1. 사용할 파라미터와 및 loss 등 환경 정의 파라미터 정의 - 총 16개 환경 1 - 4개의 사각형의 x, y position ratio (x_ratio, y_ratio) 는 각 사각형의 width 와 height 를 기준으로 하며, (x, y) 위치와 일대일 대응하는 것을 위함입니다. 환경 2 - 4개의 사각형의 interpolation, offset ratio offset은 해당하는 width 혹은 height 의 0. 5를 기준으로 작동해, (interpolation, offset ratio)는 이를 통해 생성한 (x, y) 위치와 일대일 대응하는 것을 위함입니다. 즉, gradient 단절이 확실히 존재합니다. Environment 2 의 4개 사분면 분리를 위한 0. 5 사용 - 공통 - 4개의 사각형의 x, y size ratio - position 이 정해진 후 남은 가능 거리 중에서 비율을 통해 x_size 와 y_size 를 결정합니다. - 즉, 두 환경 모두 동일한 2D 평면의 모든 점 (x, y) ∈ ℝ² 에 대해, 이를 생성할 수 있는 파라미터가 일대일 대응으로 존재해, 두 환경은 동일한 탐색 영역을 바라보고 있습니다. loss 정의 (각 loss 는 추가로 계수가 곱해져 최종 loss에 더해진다. ) L1: 각 사각형의 넓이의 분산 (각 사각형이 서로 비슷한 면적을 가지도록) $$ \displaystyle L1 = \frac{1}{n} \sum_{i=1}^{n} (Area(Rectangle_i) - \mu)^2 $$ L2: 각 사각형의 서로 겹치는 넓이의 합 (각 사각형이 서로 겹치지 않도록) $$ \displaystyle L2 = \sum_{i=1}^{n} \sum_{j=i+1}^{n} Area(Rectangle_i \cap Rectangle_j) $$ L3: 각 사각형의 aspect ratio (1. 5에서 벗어나는 정도) $$ \displaystyle L3 = \sum_{i=1}^{n} \left| \frac{\max(w_i, h_i)}{\min(w_i, h_i)} - 1. 5 \right| $$ L4: 각 사각형의 면적의 합 (면적이 커져야 한다. ) $$ \displaystyle L4 = \sum_{i=1}^{n} Area(Rectangle_i) $$ 원시적인 Gradient Descent 사용을 통한 미분 가능 정도 확인 # 학습 모델의 가중치를 변경하는 optimizer 가 아니라, # 결과 생성에 사용하고자 하는 parameter 를 직접 update하는 differentialbe programming 입니다. optimizer = torch. optim. SGD([parameters], lr=learning_rate) parameter 는 0 ~ 1 범위를 사용하도록 sigmoid 가 사용되었습니다. back propagation 된 grad 를 parameter 에 단순 반영하는 방법으로, 양쪽의 환경에서 간단하게 테스트해보았습니다. 환경 1이 더 상대적으로 더 일관적인 경향성을 보이고, 최종 loss 값도 더 작으며, 실제 도형 배치 역시 의도에 가까운 결과를 보여주었습니다. (다만, Loss 는 현재 완전하다기보다 테스트의 경향을 파악하기 위함으로, 실제 도형 배치 이미지 자체보다 Loss 숫자 자체에 더 중점을 두겠습니다. ) 즉, 두 환경 모두 바라보고 있는 탐색 영역은 동일하지만 환경 1이 더 미분가능한 환경에 가깝다고 볼 수 있습니다. - 환경 1 Loss 1 Loss 2 Loss 3 Loss 4 - 환경 2 Loss 1 Loss 2 Loss 3 Loss 4 이제 이 두 환경을 이용해 ga 모듈 및 AdamW optimizer 를 이용한 최적화를 진행해보겠습니다. 1. AdamW Optimizer 를 이용한 최적화 Test results optimizer = torch. optim. AdamW([parameters], lr=learning_rate) - 환경 1 Loss 1 Loss 2 Loss 3 Loss 4 - 환경 2 Loss 1 Loss 2 Loss 3 Loss 4 2. Genetic Algorithm 를 이용한 최적화 Test results ga 에서는 최종적인 결과는 유사하게 나오고 있습니다. 충분한 양의 연산이 주어지면, 탐색 영역 자체는 동일하기 때문에 거의 동일한 점수로 수렴하는 것을 확인할 수 있습니다. 하지만 연산을 매번 충분히 주어지는 것은 불가능 하는 경우가 많습니다. 최대한 답을 향해 빨리 나아가는 것 역시 중요합니다. 환경 1이 확실히 초기 안정성이 더 좋은 것 역시 확인이 가능합니다. 두 환경의 초기 그래프 차이 더욱이 ga 에서는 generation 당 100개의 Population 을 사용했습니다. 즉, 200 번의 연산만 수행했던 위 두 케이스와 다르게 20000번의 연산을 수행한 결과입니다. ga 를 사용하는 것이 최종적인 성능을 보장하는 방법 중에 하나일 수는 있지만, 적어도 이 셋중에는 효율적인 방식이라 보기 어렵습니다. (단 2 generation 만에 200번의 연산) - 환경 1 Loss 1 Loss 2 Loss 3 Loss 4 - 환경 2 Loss 1 Loss 2 Loss 3 Loss 4 Conclusion 미분 가능한 환경, 즉 파라미터의 변화에 따른 환경이나 결과의 변화가 연속적일 수록 최적화 과정에서 더 좋은 결과를 얻을 수 있음을 확인할 수 있었습니다. 이는 단순한 경사하강이나 AdamW 뿐만 아니라, 심지어 ga 와 같은 최적화 알고리즘에서도 같은 경향을 보였습니다. 모든 파라미터에 적용하는 것이 불가능할 때도 있지만, 이를 의식해 개발할 때 추가하고자 하는 파라미터에 가능하다면 최대한 환경이 미분 가능한 형태로 추가하는 것이 의미가 있음을 재확인할 수 있었습니다. 비슷한 점수를 얻기 위해 ga 에서는 훨씬 많은 수의 실행 횟수가 필요했습니다. ga 보다 효율적인 방법이 더 많을 수 있다는 것을 시사합니다. 미분 가능한 환경과 방법 혹은 또 다른 방법으로 전체 연산 횟수 또한 줄일 수 있을 것으로 기대합니다. Let’s Use Differentiable Environment! . .


Quantifying Architectural Design

Quantifying Architectural Design

1.

Measurement: Why It Matters 공간이나 건축에서 ‘측정하고 계산한다’는 말은 단순히 도면 위에 치수를 적어두는 수준을 넘어선다. 예를 들어, 건축가는 건물 벽의 두께나 층고를 재는 일뿐 아니라, 사람들이 그 공간에서 얼마나 오래 머무는지, 어떤 방향으로 주로 이동하는지까지도 살핀다. 결국 측정이라는 과정은, 디자인 문제를 종합적으로 이해하기 위한 중요한 단서가 된다. 물론 디자인의 모든 것을 숫자로만 파악할 수 있는 것은 아니다. 하지만 제임스 빈센트가 강조했듯이, 측정이라는 행위 자체가 우리가 놓치던 세부를 더 깊이 들여다보게 만든다. 예컨대 “도시가 커질수록 혁신성이 올라간다”는 말이, 정량적 통계로 확인될 때 그 의미가 더욱 명확해진다는 뜻이다. 다시 말해, 디자인에서의 측정이란 감에 의존하던 의사 결정을 좀 더 분명한 근거 위에 올려놓는 데 기여한다. 2. The History of Measurement Leonardo da Vinci’s Vitruvian Man 옛 시절을 떠올려 보자. 고대 이집트에서는 팔꿈치 끝부터 손가락 끝까지의 길이를 기준으로 건물을 지었고, 중국은 ‘척’이라는 단위를 도입해 만리장성 같은 대규모 공사를 보다 정확히 진행할 수 있었다. 르네상스 시대로 건너가면, 레오나르도 다빈치의 비트루비안 인간을 통해 예술과 수학의 결합이 두드러졌고, 갈릴레오 갈릴레이가 “측정할 수 없는 것은 측정 가능하게 하라”고 말했던 일화에서 보듯, 자연 현상을 수치로 정밀하게 규정하는 시도가 자리 잡았다. 그리고 지금의 디지털 시대에 이르면, 나노미터 단위의 계측이 가능해지고, 컴퓨터와 AI 기술이 방대한 데이터를 순식간에 분석해 준다. 옛 시절 예술가들이 감에 의존해 섬세한 작업을 했다면, 이제는 3D 프린터로 1mm 오차도 없는 구조물을 만드는 시대가 됐다. 도시나 건축에서 측정과 계산이 얼마나 중요한지를 보여주는 예라고 할 수 있다. 3. The Meaning of Measurement 영국 물리학자인 윌리엄 톰슨(케빈 경)은 “무언가를 숫자로 표현할 수 있다면, 어느 정도 이해했다는 의미이고, 그렇지 못하면 아직 미흡한 것”이라고 지적했다. 이런 맥락에서 보면, 측정은 곧 이해의 폭과 깊이를 넓혀주는 도구이자, 우리가 얻은 지식에 신뢰도를 부여하는 근거가 된다. 건축만 보더라도, 구조적 안전을 위해 하중을 계산하고, 재료 물성을 파악하는 일이 우선시된다. 에너지 절감을 목표로 일조량을 예측하거나 소음을 수치화하는 등의 과정은 설계 단계에서 최적의 해법을 찾는 데 도움이 된다. 도시에 관한 연구에서도, 물리학자 제프리 웨스트가 저서 스케일에서 지적했듯이, 인구가 늘어남에 따라 특허나 혁신이 증가하는 비율이 단순한 추세가 아니라 실제 통계로 확인될 때, 도시 디자인은 더 뚜렷한 방향성을 갖게 된다. 하지만 무엇을 측정하고 어떤 지표를 믿느냐에 따라 결과 해석이 크게 달라진다. 맥나마라의 오류처럼, 엉뚱한 지표를 맹신하면 본래 달성해야 할 목표가 흐려지게 된다. 특히 도시나 건축은 인간의 삶과 맞닿아 있는 영역인 만큼, 숫자로 환원하기 어려운 가치—이를테면 공간이 주는 정서적 만족감이나 장소성—도 다뤄야 한다. 바츠라프 스밀이 “숫자가 실제로 말해주는 바를 알아야 한다”고 경고한 것도 같은 이유다. 숫자만 보지 말고, 그 뒤에 깔린 맥락을 함께 들여다보라는 의미다. 4. The “Unmeasurable” 디자인에서 수치화하기 어려운 부분의 대표적인 예로는 미적 감각이 있다. 아름다움, 분위기, 사용자 만족, 사회적 연결감 등은 한계가 뚜렷하다. 다만, Birkhoff의 미적 척도미적 값 M = 질서도(O) / 복잡도(C)처럼 질서와 복잡도의 비율을 통해 시도해보려는 연구가 있기는 하다. 또, 설문 조사나 피드백 데이터를 토대로 만족도를 수치화하려는 방법도 나오지만, 여전히 질적 맥락과 현장 경험은 온전히 숫자로 담기 힘들다. Informational Aesthetics Measures 디자인 칼럼니스트 질리언 테드가 “숫자에만 의존하면 정작 주변 맥락을 놓칠 수 있다”는 경고를 남긴 것도 같은 맥락이다. 결국 ‘숫자와 인간’을 잇는 통찰이 필요하다는 뜻이다. 한편, 건축이론가 K. Michael Hays는 고대 피타고라스·플라톤 철학의 비례 개념에서 비롯된 건축이 이후 디지털·알고리즘 설계까지 이어져 왔음을 지적하면서도, 건축적 경험을 완전히 수치로 표현하는 것은 불가능하다고 본다. 면적이나 비용 등은 환산되더라도, “왜 이 공간이 이렇게 배치돼야 하느냐”에 대한 답은 단순 숫자만으로는 충분치 않다는 것이다. 결국 디자이너가 숫자와 함께 ‘숫자로 묘사되지 않는 요소’를 고민해야 비로소 의미 있는 공간이 나온다. 5. Algorithmic Methods to Measure, Analyze, and Compute Space 최근에는 건축·도시 설계에 그래프 이론과 시뮬레이션, 최적화 기법을 접목해 공간 분석을 정교화하는 사례가 증가했다. 예컨대 건축 평면을 노드와 엣지로 구성해 방과 방 사이의 깊이나 접근성을 계산하거나, 유전 알고리즘으로 병원 평면이나 사무실 배치를 자동화하려는 시도도 있다. 또, 도시 차원의 교통 흐름이나 보행자 동선을 시뮬레이션해 최적 해법을 찾는 연구가 활발하다. 이처럼 알고리즘과 계산을 통해 공간 성능을 수치화하면, 디자이너는 그 수치를 바탕으로 더 합리적이고 창의적인 결정을 내릴 수 있다. Spatial Networks 건축 평면이나 도시 거리망을 노드와 엣지로 표현하여, 각 공간(또는 교차로) 간의 연결성을 평가한다. 예를 들어, 방과 방을 연결하는 문을 엣지로 삼으면 방들의 접근성을 최단경로 알고리즘(Dijkstra, A* 등)으로 계산할 수 있다. Spatial networks Justified Permeability Graph (JPG) 건축물 내부 공간의 깊이(depth)를 측정할 때, 주 출입구 등 특정 공간을 루트로 하여 층위적으로 배치한 정당화 그래프를 만든다. 루트로부터 얼마나 많은 단계를 거쳐야 도달하는지(=방의 깊이)를 보면 개방적·중심적 공간과 고립된 공간을 파악할 수 있다. Justified Permeability Graph (JPG) Visibility Graph Analysis (VGA) 공간을 촘촘히 분할하고, 서로 가시한 점들끼리 연결하여 시야적 연결망을 만든다. 이를 통해 어디가 가장 시야적으로 개방된 지점인지, 사람들이 많이 머무를 가능성이 높은 구역은 어디인지 예측 가능하다. Axial Line Analysis 실내나 도시를 사람이 실제로 이동할 수 있는 가장 긴 직선(축선)들로 나눈 뒤, 축선끼리 만나는 교차점을 엣지로 연결한 그래프를 만든다. 이때 통합도(Integration), 평균 심도(Mean Depth) 등의 지표를 산출해, 동선의 중심성이나 사적 공간 여부를 추정할 수 있다. Genetic Algorithm (GA) 건축 평면 배치나 도시 용도 배분 등에서, 여러 요구조건(면적, 인접성, 일조 등)을 적합도 함수로 설정하여 무작위 해를 진화시키는 방식이다. 세대를 거듭하며 최적 평면 혹은 배치안에 가까워진다. Reinforcement Learning (RL) 보행자나 차량을 에이전트로 삼고, 원하는 목표(빠른 이동, 충돌 최소화 등)에 대한 보상 함수를 두어 스스로 최적 경로를 학습하게 한다. 대형 건물이나 도시 교차로에서 군중 흐름이나 교통 흐름을 시뮬레이션하는 데 유용하다. 6. Using LLM Multimodality for Quantification 인공지능이 발전하면서, LLM(대형 언어 모델)이 건축 도면이나 디자인 이미지를 분석해 정성적 요소까지 어느 정도 수치화하려는 노력도 보인다. 예컨대 건축 디자인 이미지를 입력하고, 텍스트로 된 요구사항을 대조한 뒤 ‘디자인 적합성’을 점수로 제시하는 식이다. 건축 도면끼리 비교해 건축법 준수 여부나 창문 활용 비율을 자동으로 파악해주는 프로그램도 시범 운영되고 있다. 이 과정을 통해 동선 길이, 채광 면적, 시설 접근성 등을 정리한 후, 디자이너가 “이 디자인이 제일 적절해 보인다”는 판단을 내릴 수 있게 도와주는 것이다. 7. A Real-World Example: Automating Office Layout Automating Office Layout 사무실 평면 설계를 자동화하는 과정은 크게 세 단계로 나눌 수 있다. 먼저, LLM(Large Language Model)이 사용자 요구사항을 분석하고, 이를 컴퓨터가 이해할 수 있는 형식으로 바꿔준다. 다음으로, 파라메트릭 모델과 최적화 알고리즘이 수십·수백 가지 평면 시안을 만들어 각종 지표(접근성·에너지 효율·동선 길이 등)로 평가한다. 마지막으로, LLM이 그 결과를 자연어로 요약·해석해 제안한다. 이때 LLM은 GPT-4 등과 같이 대규모 텍스트 데이터로 학습된 모델을 뜻하며, 사용자의 “50인 규모 사무실에 회의실 2개, 로비 조망은 충분해야 함” 같은 지시를 받아 건축 알고리즘 툴이 이해할 수 있는 스크립트를 자동 생성하거나, 코드 수정 방안을 안내하기도 한다. Zoning diagram for office layout by architect 이 과정을 예로 들어 보자. 과거였다면, 건축가는 일일이 버블 다이어그램을 그리며 “부서를 어디에 둘지, 창문은 누가 우선 사용하게 할지”를 반복적으로 조정했다. 하지만 LLM으로 “로비에 조망을 최대한 확보하고, 협업이 잦은 부서는 서로 5m 이내로 붙여달라”라는 식의 요구사항을 파라메트릭 모델로 직결할 수 있다. 파라메트릭 모델은 기둥이나 벽, 창문 위치 같은 기본 정보를 토대로 rectangle decomposition, bin-packing 알고리즘 등을 활용해 다양한 평면 설계안을 만들어 낸다. 그 안들이 각종 지표(인접성, 협업 부서 간 거리, 창문 활용 비율 등)로 자동 평가되면, 최적화 알고리즘은 점수가 높은 상위안을 추려낸다. LLM-based office layout generation 이때 사람이 일일이 숫자 지표를 들여다보려 하면, ‘협업 부서 간 거리 평균 5m vs. 목표치 3~4m’ 같은 상세 데이터가 쏟아져 복잡해지기 쉽다. 여기서 LLM이 다시 등장한다. 최적화 결과를 받아 “이 설계안은 협업 공간 비율이 25%를 잘 충족하지만, 창문 활용도가 예정보다 높으니 에너지 효율이 조금 떨어질 수 있다” 같은 식으로, 전문가가 아닌 이도 이해하기 쉬운 문장으로 요약해준다. 예컨대 아래와 같이 브리핑할 수도 있다. “협업이 중요한 부서 간 거리는 평균 5m로, 사용자가 희망했던 3~4m 범위보다 다소 큰 편입니다. ” “에너지 사용량은 이 규모 표준 사무실 대비 10% 가량 낮게 추정됩니다. ” 만약 새로운 자료가 추가되면 LLM이 RAG(Retrieval Augmented Generation)을 이용해 문서를 재확인하고, 파라메트릭 모델에 “긴급 비상계단을 추가해야 한다”거나 “방음 성능을 더 강화해야 한다” 같은 추가 제약을 반영하라고 지시할 수도 있다. 이렇게 사람과 AI가 끊임없이 상호작용하면서, 설계자는 훨씬 짧은 시간에 다양한 대안을 탐색하고, ‘데이터에 근거한 직관’을 살려 최종 결정을 내릴 수 있게 된다. results of office layout generation 본질적으로, 측정이란 “공간을 어떻게 바라볼지”를 결정하는 도구다. 오피스라는 한정된 공간에서, 누가 어떤 우선순위를 갖고 그 공간을 점유할지를 정하는 일은 복잡한 문제이지만, LLM과 파라메트릭 모델, 최적화 알고리즘이 결합하면 그 복잡성이 한결 가벼워진다. 최종적으로 디자이너나 의사결정자가 가지는 자유도는 줄어드는 게 아니라, 오히려 “잡다한 반복 업무”에서 벗어나 프로젝트 전반의 창의적·감성적 부분에 더 집중할 수 있게 된다. 이것이 바로, ‘측정과 계산’을 바탕으로 한 사무실 레이아웃 추천 시스템이 가져다주는 효과다. 8. Conclusion 결국 측정은 “보이지 않던 것을 드러내는” 강력한 도구다. 정확한 숫자를 통해 문제를 분명히 인식하면, 직관과 감각에만 의존할 때보다 더 객관적으로 해법을 찾을 수 있다. 인공지능 시대에는 LLM과 알고리즘이 이런 측정·계산 과정을 한층 확장해, 평소 수치화하기 어려웠던 영역을 어느 정도 정량화할 수 있게 되었다. 오피스 배치 자동화 사례에서 보듯, 추상적 요구조차 LLM 덕분에 정량 파라미터가 되고, 파라메트릭 모델과 최적화 알고리즘이 수많은 대안을 생성·평가해 합리적이면서도 창의적인 결과를 제안한다. . .


AI and Architectural Design: Present and Future

AI and Architectural Design: Present and Future

Introduction 인공지능은 반복 업무부터 창의적 해결까지 폭넓게 적용되고 있다. 건축 분야도 예외가 아니다. 한때 CAD로 도면을 그리는 정도였던 전산화가, 이제 생성적 디자인과 최적화 알고리즘을 통해 건축가·엔지니어 업무 전반을 바꾸고 있다.

이 글은 인공지능이 건축 설계에 어떻게 혁신을 가져오는지, 그리고 전문가들이 어떤 기회와 과제에 직면하는지 살펴보려 한다. Background of AI in Architectural Design Rule-Based Approaches and Shape Grammar 처음에는 사람이 만든 규칙에 따라 설계안을 자동 생성하는 연구가 있었다. 형태 문법(Shape Grammar)은 1970년대 이후 발전해, 적은 규칙으로도 특정 건축가의 양식을 재현할 수 있음을 보여줬다. 예를 들어 르네상스 건축가 팔라디오의 빌라 평면을 간단한 기하학 규칙으로 구현한 사례가 있다. 다만 사람이 정의한 범위 안에서만 작동하기 때문에 복잡한 건축 문제 모두를 해결하기는 어려웠다. Koning's and Eisenberg's compositional forms for Wright's prairie-style houses. Genetic Algorithms and Deep Reinforcement Learning 자연계 진화를 모델링한 유전 알고리즘은 무작위 해답을 평가하고, 우수 해법을 교배·돌연변이해 점진적으로 개선한다. 복잡한 문제를 탐색하는 데 유리하지만, 변수가 많으면 계산 시간이 길어진다. 심층 강화 학습은 바둑 AI 알파고로 유명해진 기법이다. 시행착오로부터 보상을 최대화하는 정책을 스스로 학습한다. 건축 설계에서도 대지 형상, 법규, 형태 등 맥락 정보를 바탕으로 의사결정 규칙을 만들어갈 수 있다. Generative Design and the Design Process Automating Repetitive Tasks and Proposing Alternatives 생성적 디자인은 반복 업무를 자동화하고, 다양한 설계 대안을 동시에 만들어낸다. 기존에는 시간이 부족해 몇 가지 아이디어만 검토했지만, 인공지능을 쓰면 수십~수백 가지 안을 실험할 수 있다. 일조량·공사비·편의성. 용적률 등 여러 요소를 복합 평가하는 것도 가능하다. The Changing Role of the Architect 건축가는 인공지능 설계를 위한 목표·제약 조건을 정하고, 그 결과물을 큐레이션한다. 단순 도면이나 계산 업무에서 벗어나, 어떻게 AI가 작동할지 환경을 세팅하고, 상위안을 골라 미학적 감각을 더하는 쪽에 집중하게 된다. The Role of Architects in the AI Era 1966년 영국 건축가 세드릭 프라이스가 던진 말이 있다. “Technology is the answer… but what was the question?” 이 말은 오늘날 더 중요한 화두가 되었다. 문제를 ‘어떻게 풀까’보다는 ‘어떤 문제를 풀지’가 핵심이라는 뜻이다. 디자인 프로세스에는 문제 정의 단계와 해결 단계가 있는데, 인공지능 발전으로 해결 단계는 많이 주목받았다. 그러나 건축 설계에서 중요한 일은 무엇을 문제로 삼을지, 그 문제를 어떻게 정의할지에 달려 있다. Defining the Problem and Constructing a State Space 도메인 지식과 컴퓨터적 사고를 모두 이해하는 사람이 문제를 잘 정의한다. 문제 설정이 적확하면 복잡한 AI 없이도 간단한 알고리즘으로도 충분한 결과를 낼 수 있다. 여기서 건축가는 하나의 독창적 결과물을 직접 만들기보다, 요소 간 상호작용을 설계해 상태공간을 구성하는 역할을 한다. 반면 인공지능은 이미 존재하는 무수한 가능성 중 최적의 조합을 발견한다. Palladio’s Villas and Shape Grammar 이런 관점을 뒷받침하는 대표적 사례로, 루돌프 비트코버(Rudolf Wittkower)가 르네상스 건축가 안드레아 팔라디오(Palladio)의 빌라 평면에서 찾아낸 규칙과 패턴을 들 수 있다. 비트코버는 팔라디오 빌라에 일관되게 나타나는 기하학적 원리를 분석했는데, 그중 두드러진 것이 “나인 스퀘어 그리드(nine square grid)”로 대표되는 기본 틀이었다. 팔라디오의 빌라는 겉보기에는 모두 독창적이지만, 중앙에 홀을 두고 좌우대칭으로 방을 배치하고, 방의 가로세로 비율을 3:4나 2:3 같은 음악적 음정비로 맞추는 등 특정 규칙을 공유했다. 팔라디오가 자주 썼던 방의 비례나 치수 목록은 모두 다분히 의도적이었고, 이를 통해 건축가가 조화로운 형태를 체계적으로 구현했음을 알 수 있다. Landbook and LBDeveloper 스페이스워크(Spacewalk)는 인공지능 기반 건축 설계 기술을 개발하고 있다. 2018년 공개된 랜드북은 AI 부동산 개발 플랫폼이다. 지번만 입력하면 해당 토지의 시세, 법적 개발 한계, 개발 후 예상 수익률을 바로 보여준다. LBDeveloper는 가로주택정비사업용 전문 솔루션이다. 노후 주택지의 개발 요건 검토와 사업성 분석을 자동화한다. 주소 입력만으로 개발 가능 여부와 기대 수익을 확인할 수 있다. AI-Based Design Workflow LBDeveloper는 법규로부터 허용 영역을 계산하고, 평수 조합을 바탕으로 건물 블록을 생성한다. 그리드와 축을 다양하게 적용해 블록을 배치한 다음, 블록 사이 간격을 최적화해 충돌을 피한다. 마지막으로 건물 바닥 면적 비율(BCR)과 용적률(FAR) 같은 지표로 배치 효율을 평가해 최적안을 고른다. 전체 조합 수는 (축 옵션 수) × (그리드 옵션 수) × (층수 옵션 수) × (열 shift 옵션 수 × 행 shift 옵션 수) × (회전 옵션 수) × (추가 블록 옵션 수) 로 계산한다. 예를 들어, 4 × 100 × 31 × (10×10) × 37 × (추가 옵션은 활성 블록 개수에 따라 다름) 정도로 평가할 수 있다. 실제로는 각 단계에서 전수 조사하여 최적의 값을 선택하는 방식이므로, 모든 조합을 동시에 탐색하지 않고 각 요소별로 최적화를 진행한다. Future Prospects 디자인에는 무수히 많은 대안이 있다. 여러 제약으로 인해 완벽한 최적 해를 찾기 어렵다. 최적화 알고리즘도 탐색 범위나 규정 한계로 인해 늘 만족스러운 결과를 보장하지 못한다. 이 한계를 보완하는 도구가 LLM이다. LLM은 방대한 데이터를 학습해 새로운 아이디어를 제안하고, 수많은 대안 중 좋은 해답을 골라준다. 복잡한 규칙이 얽힌 건축 분야에서는 더 효과적이다. LLMs for Design Evaluation and Decision LLM은 미리 생성된 설계안을 다각도로 평가하고, 사용자 요구사항이나 법규에 맞춰 어느 정도로 합리적인지를 판단한다. 단순한 법적 준수 여부뿐 아니라, 건물 배치나 조형 요소가 미적으로 균형 잡혀 있는지, 공간적 편의성이 충족되는지도 같이 살핀다. 설계 후보가 많을수록 이런 지원이 중요해진다. 디자이너는 그동안 무수히 많은 안을 직접 검토해야 했지만, LLM의 조언을 통해 빠르게 적합한 안을 좁혀갈 수 있다. 무엇보다 LLM은 “텍스트” 형태의 질의를 기반으로 작동하기 때문에, 특정 평가 기준이나 미적 감각을 언어로 풀어내기만 하면 된다. 예를 들어 “인접 대지의 건물 사이에 충분한 간격이 있는지 확인해줘” 같은 문장을 넣으면, LLM은 해당 조건을 재차 분석하고 결과를 요약한다. 이 과정에서 LLM은 공간적 아이디어와 기능적 요구 사항을 동시에 고려한다. 설계자가 제시한 목표치와 실제 배치 간 격차를 파악하고, 필요한 개선 방향을 추천해주기도 한다. 결국 LLM은 디자이너가 더 깊이 있는 고민을 할 수 있도록 돕는다. 건축가는 무수한 설계안 중 어느 것이 “실제 실행 가능한지, 또 법·규정상 허점이 없는지, 미적·기능적 균형이 적절한지”를 빠르게 확인하며, 창의적인 과정에 에너지를 쏟게 된다. 이는 사람이 놓칠 수 있는 사소한 실수를 줄이고, 아이디어를 폭넓게 탐색하도록 독려한다. 이렇게 인간 전문가와 LLM이 협력해 설계안을 평가하고 선택하는 방식은 앞으로 점차 확산될 것으로 본다. . .


Rendering Multiple Geometries

Rendering Multiple Geometries

Introduction 이 글에서는 여러 개의 geometry 를 렌더링하고자 할 때 최적화 및 렌더링 부하를 줄이는 방법을 소개하고자 합니다. Abstract 1. three js 에서 도형에 해당하는 객체를 만드는 가장 기본적인 방법은 mesh 에 해당하는 geometry 와 material 을 통해 하나의 mesh 를 생성하는 것입니다.

2. 이때 지나친 drawcall 의 개수 때문에 너무 느려서, geometry 들을 material 단위로 merge 해 mesh 개수를 줄이는 과정을 통해 1차 최적화 시도를 행했습니다. 3. 추가적으로, 기준 geometry에서 move or rotate 및 scale 등의 변환을 통해 생성 가능한 geometry 는 group 이 아닌 instancedMesh 를 통해 메모리 사용량 개선과 geometry 생성시의 부하 감소를 시도했습니다. 1. Basic Mesh Creation 먼저 아파트 하나를 렌더링한다고 가정해보겠습니다. 아파트를 형성하는 도형에는 많은 종류가 있지만, 여기서는 덩어리 블록을 예시로 들어보겠습니다. 아래처럼 하나의 블록을 렌더링하는 경우, 큰 고민이 필요하지 않습니다. . . . const [geometry, material] = [new THREE. BoxGeometry(20, 10, 3), new THREE. MeshBasicMaterial({ color: 0x808080, transparent: true, opacity: 0. 2 })]; const cube = new THREE. Mesh(geometry, material); scene. add(cube); . . . Draw Calls: 0 Mesh Creation Time: 0 하지만 아파트 전체를 렌더링하고자 할 때, 창문과 벽 등 한 세대의 geometry 는 100개가 넘는 경우가 적지 않고, 이는 즉 100세대를 렌더하고자 할 경우 geometry 의 개수는 다섯자리 수를 쉽게 넘을 수 있다는 의미입니다. 이 때 각 geometry 하나 당 mesh 한개를 생성해 렌더링하는 경우입니다. 아래는 10,000개의 도형을 렌더링한 결과입니다. 각 geometry는 베이스 도형을 clone 및 translate 하여 생성했습니다. // 아래의 코드가 5,000번 실행됩니다. scene 에 add되는 mesh 의 개수는 10,000개입니다. . . . const geometry_1 = base_geometry_1. clone(). translate(i * x_interval, j * y_interval, k * z_interval); const geometry_2 = base_geometry_2. clone(). translate(i * x_interval, j * y_interval, k * z_interval + 3); const cube_1 = new THREE. Mesh(geometry_1, material_1) ; const cube_2 = new THREE. Mesh(geometry_2, material_2); scene. add(cube_1); scene. add(cube_2); . . . Draw Calls: 0 Mesh Creation Time: 0 2. Merge Geometries 이제 렌더링 최적화를 위해 대표적으로 사용되는 방법 중 하나인 mesh 개수 감소 방법을 말씀드리겠습니다. 그래픽스 쪽에는 drawcall 이라는 개념이 있다고 합니다. CPU는 장면에서 렌더링해야 할 도형을 찾아내고, 이를 GPU에 렌더링하도록 요청하는데, 이 요청 횟수를 draw call이라고 합니다. 이 때의 기준은 기본적으로 렌더링해야하는 서로다른 material 을 가진 서로 다른 mesh 의 렌더링을 요청하는 횟수로 이해할 수 있습니다. 여기서 다중 작업에 특화되어 있지 않은 CPU가 지속적으로 동시에 많은 호출을 처리해야 하면서 병목 현상이 발생할 수 있습니다. 이미지 출처: https://joong-sunny. github. io/graphics/graphics/#%EF%B8%8Fdrawcall 마우스로 scene 을 조작해보시면서 이 케이스와 직전 케이스의 drawcall의 차이를 확인해보실 수 있습니다. 위의 케이스에서는 설정되어있는 카메라의 max distance나 화면 밖으로 도형이 나가는 경우 등의 이유로 항상 10,000번이 call 되지는 않지만 대부분의 경우 상당한 양의 call 이 발생하는 것을 확인할 수 있습니다. 여기에서는 material 을 두개 사용했기 때문에, 서로 다른 material 을 가진 geometry 들을 merge 하는 방법을 사용했습니다. 따라서 확인하실 수 있는 것 처럼 여기에서의 draw call 은 최대 2로 고정되는 것을 확인하실 수 있습니다. // 아래의 코드가 1번 실행됩니다. scene 에 add되는 mesh 의 개수는 2개입니다. . . . const mesh_1 = new THREE. Mesh(BufferGeometryUtils. mergeBufferGeometries(all_geometries_1), material); const mesh_2 = new THREE. Mesh(BufferGeometryUtils. mergeBufferGeometries(all_geometries_2), material_2); scene. add(mesh_1); scene. add(mesh_2); . . . Draw Calls: 0 Mesh Creation Time: 0 렌더링 부하가 개선되었음을 직접적으로 확인할 수 있습니다. 3. InstancedMesh 위에서 geometry 들을 merge 하는 것으로 draw call 관점에서의 부하를 감소시킬 수 있는 방법을 확인했습니다. 이 도형들은 clone 및 translate 만 사용된 도형들로 이루여져 있어 모태가되는 형태가 동일합니다. 이는 건물이나 나무와 같은 객체를 렌더링 할 떄에도 유사한 경우가 발생합니다. 층별로 형태가 다를 필요가 없다거나, 파츠들을 복사해 층의 벽들로 이루는 등의 경우, 나무 종류를 많이 사용하지 않고 하나의 나무를 크기나 방향만 바꿔 사용하는 경우 등 입니다. 같은 형태의 평면이 여러개의 동으로 생성된 경우 또한 물론 적용 가능합니다. 이 경우에는 geometry 객체 생성 횟수도 줄일 수 있고 메모리 및 시간에 효율적인 instancedMesh 를 사용하여 추가적으로 최적화를 시도할 수 있습니다. 그래픽스에 Instancing 이라는 개념이 있습니다. Instancing은 유사한 geometry를 여러 번 렌더링할 때, 데이터를 한 번만 GPU로 보내고 각 인스턴스의 변환 정보를 GPU에 추가적으로 전달하는 것으로 geometry 생성 또한 한번만 하면 되는 장점이 있습니다. Unity와 같은 게임 엔진에서도 이 개념을 활용하여 GPU instancing 이라는 기능을 통해 성능 최적화를 이루고 있습니다. 유사한 형태의 도형을 여러개 렌더링하는 경우 (유니티 GPU Instancing) 이미지 출처: https://unity3d. college/2017/04/25/unity-gpu-instancing/ three js 에도 이에 해당하는 InstancedMesh라는 기능이 있습니다. draw call은 위에서 말씀드린 geomtries merge 케이스와 동일하지만, 모든 geometry 를 각각 생성해야 할 필요가 없기에 메모리 및 렌더링 시간 면에서 큰 이점이 있습니다. 아래의 다이어그램은 1, 2, 3번 과정에서 gpu 에 전달되는 mesh 를 간략화 한 그림입니다. 아래의 예시는 translation 만 사용되었지만, 이 외에도 rotate, scale 등의 변환 역시 사용할 수 있습니다. mesh 를 따로 생성하는 것을 줄였던 것에 더해 geometry 까지 따로 생성하는 것을 줄이며, creation time 차이가 상당한 것을 확인할 수 있습니다. // instancedMesh 의 마지막 arguement에는 사용할 도형의 개수를 추가해줘야 합니다. const mesh_1 = new THREE. InstancedMesh(base_geometry_1, material_1, x_range * y_range * z_range); const mesh_2 = new THREE. InstancedMesh(base_geometry_2, material_2, x_range * y_range * z_range); let current_total_index = 0; for (let i = 0; i Draw Calls: 0 Mesh Creation Time: 0 물론 translate(이동), rotate(회전), scale(크기변환) 변환만으로는 모든 경우의 수를 커버할 수 없기에, 그럴 경우 merged geometry 방법도 혼용해야 하는 경우가 많고, 적용하고 있습니다. 마치며 three js 렌더링을 최적화하는 데는 이 밖에도 다양한 방법이 있지만, 이번 글에서는 우선 geometry 개수에 따른 렌더링 부담을 줄이는 방법에 대해 말씀드렸습니다. 이 밖에도 겪었던 메모리 누수나 다른 원인에서 있었던 부하 문제 등을 수정했던 경험을 추후 공유드리겠습니다. 감사합니다. . .


Zone Subdivision With LLM - Expanded Self Feedback Cycle

Zone Subdivision With LLM - Expanded Self Feedback Cycle

Introduction 이 글에서는 반복적인 사이클을 통해 결과의 품질을 향상시키기 위해 대규모 언어 모델(LLM)을 피드백 루프에서 활용하는 방법을 살펴봅니다. LLM이 제공하는 초기 직관적 결과를 개선하는 것이 목표이며, 이는 LLM 내부의 피드백 메커니즘을 넘어서 LLM 외부의 최적화를 포함한 피드백 사이클을 통해 이루어집니다.

Concept LLM은 자체적으로도 피드백을 통해 결과를 개선할 수 있으며, 이는 널리 활용되고 있습니다. 그러나 사용자의 단일 요청만으로 LLM이 제공하는 직관적인 결과가 항상 완전하다고 기대하기는 어렵습니다. 따라서, LLM이 제공한 초기 결과를 다시 요청하여 피드백을 주고받음으로써 답변의 질을 향상시키는 것을 목표로 합니다. 이는 LLM 내부의 피드백에만 의존하는 것을 넘어, LLM과 알고리즘을 포함한 더 큰 사이클에서의 피드백을 통해 결과를 지속적으로 개선할 수 있는지를 확인하는 과정입니다. Self-feedback outside LLM API In This Work: Self-feedback inside LLM API examples: LLM을 사용하는 의의 사용자의 모호한 니즈와 알고리즘의 구체적 기준 사이의 연결 매개체 사이클의 결과를 이해하고, 다음 사이클에 그 결과를 반영해 답변을 개선해가는 과정 Premises Two Main Components: LLM: 직관적 선택에 사용 사용자의 비교적 모호한 의사를 LLM 은 직관적이고 개략적인 결정들을 만들 수 있습니다. 사용자 - LLM - optimizer 순으로의 구체화 순서 과정에서, 사용자 - optimizer 사이의 갭을 매꿔주는 존재로 사용됩니다. Heuristic Optimizer (GA): - 상대적으로 구체적인 최적화에 사용 Use Cycles: 사이클의 실행을 반복함으로, 의도에 가까운 결과에 스스로 접근하고자 하는 구조를 만들고자 합니다. Details Parameter Conversion: LLM 과 structured out 을 사용하면, 직관적으로 선택할 내용들을 명확한 파라미터 입력으로 변환해 알고리즘 입력의 input 으로 사용할 수 있습니다. Zone grid 및 grid size 에 따른 각 patch 사이즈를 조절하기 위해, 추가적인 subdivision 개수를 얼마나 받아야 할지 재 요청하며 조정 number_additional_subdivision_x: int number_additional_subdivision_y: int 각 용도에 대해 boundary 에 가깝게 위치하는 것을 우선할지 prompt 를 바탕으로 답변을 요구합니다. place_rest_close_to_boundary: bool place_office_close_to_boundary: bool place_lobby_close_to_boundary: bool place_coworking_close_to_boundary: bool 각 용도가 서로 옆 patch 와 다른 용도이길 원하는 비율에 대해 물어봅니다. percentage_of_mixture: number 각 용도가 몇퍼센트 정도 차지해야 할 지 물어봅니다. office: number coworking: number lobby: number rest: number Optimization Based on LLM Responses: llm 에게서 돌아온 답변을 토대로 이를 최적화 기준으로 사용하고, 이는 사용자가 가지고 있던 생각들을 구체적인 기준으로 변환하는 과정입니다. Incorporate Optimization Results into Next Cycle: 최적화를 통해 나온 결과는 다시 llm 에게 다음 사이클의 질문을 할 때 참고사항으로 삽입해줍니다. 이를 통해 [llm answer - optimize results with the answer] 사이클을 복수 횟수 돌리는 것의 의의를 강화하고 결과를 개선시킵니다. Cycle Structure: 한 사이클 1차 [llm answer - optimize results with the answer] zone 용도를 Optimizer 하기 전, 입력받은 prompt를 기준으로 subdivision 기준을 LLM에게 물어봅니다. LLM 은 subdivision 의 개략적인 결정을 해주고, optimize 는 구체적인 결과를 생성합니다. 2차 [llm answer - optimize results with the answer] 입력받은 prompt 를 기준으로, zone 들의 config 관련 직관적인 대답을 LLM에게 요구합니다. LLM 은 zone placement 관련된 직관적인 결정을 해주고, optimize 는 이를 구체적인 결과로 생성합니다. 두번째 사이클 이후 두번째 사이클 이후에서는, 직전의 LLM 이 돌려줬던 답변과 함께 실제 optimize 결과를 알려주며 답변의 개선을 직접적으로 요구합니다. 사이클을 반복하며 LLM 결과는 업데이트 되고, 이에 따른 optimize 결과도 개선되어갑니다. Test results case 1 Prompt: 대공간에서 다같이 한 목표를 위해 가운데 쪽에 office 공간들이 모여있어. 그밖의 공간들은 boundary 에 가까운 곳에 배치하고싶어. GIFs: case 2 Prompt: 한 팀에는 약 5명 내외로 사일로 한 팀들을 목표로 하고있어. 때문에 허느 한 용도가 몰려있지 않은 섞인 공간들을 원해. GIFs: case 3 Prompt: office 가 채광을 받기 쉽도록 boundary 에 가까운 곳에 배치를 우선하고, 다른 공간들은 안쪽에 배치하고싶어. GIFs: Conclusion llm의 response raw data 만으로 사용자에게 전달할 데이터가 완성되기 어려운 api에 대해, Self Feedback 을 llm 이 아닌 llm 요청 및 최적화와 후처리까지 합친 범위로 확장함으로 최종 결과의 개선을 도모하고자 하는 작업입니다. cycle 이 반복될수록 의도된 결과에 가까워지는 것을 확인할 수 있었으며, 직관적인 초기값을 llm 에게 의지하는 작업에서 초기값의 불완전할 수 있는 가능성을 감소시킬 수 있습니다. . .


Landbook Diffusion Pipeline

Landbook Diffusion Pipeline

Introduction Landbook은 중소규모 토지 투자자를 위한 신축 개발의 모든 단계를 지원하는 서비스입니다. Landbook의 AI 건축가 서비스는 각 지역의 대지 크기, 용도지역, 건축 규제 등을 고려하여 건물주에게 다양한 건축 설계안을 제공합니다. Landbook's AI architect service 본 프로젝트는 diffusers와 같은 생성형 이미지 모델을 활용하여 Landbook AI 건축가 서비스의 최종 결과물을 렌더링하는 파이프라인을 개발하는 것입니다.

3D 모델링 데이터를 입력으로 받아 실제 건물과 매우 유사한 사실적인 이미지를 생성함으로써, 건축축주가 자신의 설계안이 실제로 지어졌을 때 어떤 모습일지 시각화하고 검토할 수 있게 합니다. 기존의 3D 렌더링과 달리, AI 기반 생성 모델을 활용하여 실제 건물의 텍스처와 주변 환경과의 조화를 모두 고려한 고품질 시각화를 제공하는 것을 목표로 합니다. Pipeline Overview 아래의 직관적인 파이프라인은 최종 결과물이 건축 설계를 정확하게 표현할 뿐만 아니라, 건물주가 실제 건물의 모습을 더 잘 이해할 수 있도록 사실적인 시각화를 제공합니다. 파이프라인은 다음과 같은 단계로 구성됩니다: 2D Plans Generation: 건물 설계의 기초가 되는 2D 평면도를 생성하는 것으로 프로세스가 시작됩니다. 3D Building Generation: 2D 도면을 적절한 치수와 구조를 가진 3D 건물 모델로 변환합니다. Three. js Plot: 3D 모델을 Three. js에 플롯하여 시각화 및 조작이 가능하도록 합니다. Camera Adjustment: 가장 적절한 지점점에서 건물을 포착하기 위해 시야각과 카메라 위치를 신중하게 조정합니다. Scale Figures: 장면에 규모 참조와 맥락을 제공하기 위해 사람 형상, 나무, 차량을 추가합니다. Masking: 건물과 환경의 다른 부분들을 구별되는 색상으로 마스킹하여 재료와 표면을 정의합니다. Canny Edge Detection: 명확한 건물 윤곽과 세부사항을 생성하기 위해 엣지 검출을 적용합니다. Highlighting: 중요한 건축적 특징과 엣지를 하이라이팅 및 해칭을 통해 강조합니다. Base Image Generation: 적절한 음영과 텍스처가 있는 기본 이미지를 생성합니다. Inpainting & Refining: 사실적인 텍스처와 세부사항을 추가하기 위해 여러 번의 인페인팅과 정제 과정을 수행합니다. Pipeline Diagram Camera Position Estimation 카메라 위치 추정은 가장 효과적인 시점에서 건물을 포착하는 데 있어 매우 중요한 단계입니다. 알고리즘은 건물의 크기, 대지 배치, 도로 위치를 고려하여 적절한 카메라 위치를 결정합니다. Road-Based Positioning 건물 대지에 인접한 가장 넓은 도로를 식별합니다 도로의 중심점을 카메라 배치의 기준점으로 사용합니다 거리 레벨의 시점에서 건물이 보이도록 합니다 Vector Calculation 가장 넓은 도로와 정렬된 수평 벡터를 생성합니다 (X 벡터) 수평 벡터를 90도 회전하여 수직 벡터를 생성합니다 (Y 벡터) 이러한 벡터들은 카메라의 시점 방향을 결정하는 기준이 됩니다 Height Determination and Distance Calculation 두 가지 기준을 사용하여 최적의 카메라 높이를 계산합니다 적절한 건물 커버리지를 보장하기 위해 이 기준들 중 최대값을 선택합니다 삼각함수를 사용하여 카메라와 건물 사이의 이상적인 거리를 계산합니다 \[ \tan(\theta) = \frac{h}{d}, \quad d = \frac{h}{\tan(\theta)} \] 여기서 \(d\)는 카메라와 widestRoadCentroid 사이의 거리, \(h\)는 카메라의 높이, \(\theta = \frac{\text{fov}}{2} \times \frac{\pi}{180}\)입니다 Camera Position Estimation Diagram const estimateCameraPosition = ( data: BuildingStateInfo, buildingHeightEstimated: number, fov: number, ) => { const parcelPolygon = data. plotOutline // Obtain the widest road object. // The widest road object is computed by widthRaw + edgeLength let widestWidth = -Infinity; let widestRoad = undefined; data. roadWidths. forEach((road) => { if (widestWidth Scale Figures Scale figures는 디퓨전 모델이 더 사실적인 건축 시각화를 이해하고 생성하는 데 필수적인 맥락적 요소입니다. 사람 형상, 나무, 차량을 장면에 포함시킴으로써, 모델이 생성해야 하는 건축물의 공간적 관계와 규모를 이해하는 데 도움이 되는 중요한 참조점을 제공합니다. Scale Figures 이러한 맥락적 요소들은 또한 모델이 적절한 조명, 그림자, 대기 효과를 생성하는 데 도움을 줍니다. 모델이 장면에서 사람 형상이나 나무를 볼 때, 최종 렌더링에 있어야야 할 조명 효과와 환경적 상호작용의 규모를 더 잘 해석할 수 있습니다. 이는 더욱 설득력 있고 자연스럽게 통합된 건축 시각화를 만드는 데 도움이 됩니다. 우리의 파이프라인에서는 디퓨전 프로세스가 시작되기 전에 이러한 스케일 요소들이 배치됩니다. 모델은 이러한 참조를 사용하여 건물의 의도된 크기와 비율을 더 잘 이해하며, 이는 생성된 이미지의 품질과 정확도를 크게 향상시킵니다. 특히 사람 형상은 디퓨전 모델에 스케일 참조를 제공하여 생성 과정 전반에 걸쳐 일관되고 현실적인 비율을 유지하는 데 중요한 역할을 합니다. Landbook AI Architect result w/ and w/o scale figures Material Masking three. js에서 제공하는 ShaderMaterial은 건물과 환경의 재질을 마스킹하는 데 사용됩니다. ShaderMaterial은 커스텀 셰이더로 렌더링되는 재질입니다. 셰이더는 GPU에서 실행되는 GLSL로 작성된 작은 프로그램입니다. ShaderMaterial은 사용자가 커스텀 셰이더를 작성할 수 있게 해주므로, 서로 다른 건축 요소에 대해 특정 색상을 정의하여 특수한 마스킹 재질을 만들 수 있습니다. 이러한 마스킹 재질은 3D 모델을 구분된 부분으로 분할하여 디퓨전 모델이 각각을 별도로 처리할 수 있도록 도와줍니다. Material Masking const createMaskMaterial = (color: number) => { return new THREE. ShaderMaterial({ uniforms: { color: { value: new THREE. Color(color) } }, vertexShader: ` void main() { gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1. 0); } `, fragmentShader: ` uniform vec3 color; void main() { gl_FragColor = vec4(color, 1. 0); } ` }); }; export const glassMaskMaterial = createMaskMaterial(0xff0000); // red export const glassPanesMaskMaterial = createMaskMaterial(0x00ffff); // cyan export const columnsMaskMaterial = createMaskMaterial(0xff00ff); // magenta export const wallMaskMaterial = createMaskMaterial(0x0000ff); // blue export const surroundingBuildingsMaskMaterial = createMaskMaterial(0xffff00); // yellow export const surroundingParcelsMaskMaterial = createMaskMaterial(0xff8e00); // orange export const roadMaskMaterial = createMaskMaterial(0x000000); // black export const siteMaskMaterial = createMaskMaterial(0x00ff00); // green export const railMaskMaterial = createMaskMaterial(0xbc00bc); // purple export const carMaskMaterial = createMaskMaterial(0xbcbcbc); // gray export const treeMaskMaterial = createMaskMaterial(0xc38e4d); // brown export const personMaskMaterial = createMaskMaterial(0x00a800); // darkgreen export const pathMaskMaterial = createMaskMaterial(0xc0e8f6); // skyblue export const parkingLineMaskMaterial = createMaskMaterial(0x000080) // darkblue EndpointHandler 🤗 우리 파이프라인의 디퓨전 프로세스는 사실적인 건축 이미지를를 생성하기 위해 HuggingFace Diffusers 라이브러리의 여러 모델들을 활용합니다. 이 프로세스는 초기 생성, 타겟팅된 인페인팅, 최종 정제의 세 가지 주요 단계로 구성됩니다. 파이프라인은 canny image 에 대해 학습된 ControlNet 모델을 사용하는 StableDiffusionXLControlNetPipeline으로 시작됩니다. Canny edge detection, highlighting the main building, hatching some parts 이 단계에서는 3D 모델의 엣지 정보를 가져와 base image를 생성합니다. ControlNet은 프롬프트의 도움을 받아 생성된 base image가 건물 디자인의 정확한 기하학적 윤곽을 따르도록 보장합니다: self. prompt_positive_base = ", ". join( [ "", "[Bold Boundary of given canny Image is A Main Building outline]", "[Rich Street Trees]", "[Pedestrians]", "[Pedestrian path with hatch pattern paving stone]", "[Driving Cars on the asphalt roads]", "At noon", "[No Clouds at the sky]", "First floor parking lot", "glass with simple mullions", "BLENDED DIVERSE ARCHITECTURAL MATERIALS", "Korean city context", "REALISTIC MATERIAL TEXTURE", "PROPER PERSPECTIVE VIEW", "PROPER ARCHITECTURAL SCALE", "8k uhd", "masterpiece", "[Columns placed at the corner of the main building]" "best quality", "ultra detailed", "professional lighting", "Raw photo", "Fujifilm XT3", "high quality", ] ) 초기 생성 이후, 파이프라인은 StableDiffusionXLInpaintPipeline을 사용하여 일련의 타겟팅된 인페인팅 작업을 수행합니다. 인페인팅 프로세스는 다양한 건축 요소들을 처리하기 위해 특정한 순서를 따릅니다. 각 인페인팅 단계는 적절한 재질 텍스처와 건축 디테일이 각 요소에 대해 생성되도록 하기 위해 정교하게 작성된 프롬프트와 마스크를 사용합니다. 각 인페인팅 단계 이후에는 base image와 병합되어 새로운 base image를 생성합니다. 아스팔트 텍스처가 있는 도로 표면 주변 필지와 보행자 도로 하늘을 포함한 배경 요소 적절한 건축 디테일이 포함된 주변 건물 ( . . . ) Masked images 마지막 단계에서는 StableDiffusionXLImg2ImgPipeline을 사용하여 전체 이미지를 정제하고, 렌더링 이미지의 일관성과 사실성을 향상시킵니다. 이 정제 과정은 더 나은 해상도와 디테일 향상을 통해 전반적인 이미지 품질을 개선하는 데 중점을 둡니다. 더 자연스럽고 사실적인 효과를 만들기 위해 조명과 그림자를 조정하고, 건물의 다양한 표면에 걸쳐 일관된 재질 표현을 보장하며, 설계의 정확성을 유지하기 위해 건축 디테일을 미세 조정합니다. 이러한 정제 과정들이 함께 작용하여 건축적으로 정확하면서도 시각적으로 매력적인 최종 이미지지를 만들어냅니다. Results 위에서 설명한 다단계 디퓨전 파이프라인을 적용한 결과, 일관된 재질, 조명, 건축 디테일을 갖춘 고품질 건축 렌더링을 생성하는 데 있어 우리의 접근 방식이 효과적임을 보여주는 다음과 같은 결과를 얻을 수 있습니다. Future Works 현재 파이프라인이 사실적인 건축 렌더링 이미지를 성공적으로 생성하고 있지만, 다음과 같은 개선 및 향후 개발이 필요한 영역들이 있습니다: Material Diversity Enhancement: 주변 건물 외관의 다양한 텍스처와 재질을 처리하고, 더 사실적인 환경 맥락을 만들기 위한 재질 상호작용과 풍화 효과를 적용해볼 수 있습니다. Sky Condition Variation: 향후 개발에서는 다양한 시간대, 날씨 효과, 구름 패턴, 그리고 동적인 대기 조건을 지원하여 더 다양한 시각화 시나리오를 제공할 수 있습니다. Road Detail Improvements: 파이프라인을 개선하여 다양한 포장 유형, 도로 표시, 표면 마모 패턴, 주변 요소와의 더 나은 통합을 포함한 더 상세한 도로 표면을 생성할 수 있습니다. . .


Floor Plan generation with Voronoi Diagram

Floor Plan generation with Voronoi Diagram

Introduction 본 프로젝트는 논문 리뷰 및 Free-form Floor Plan Design using Differentiable Voronoi Diagram 논문을 구현하는 것입니다. 딥러닝이나 경사하강법 기반의 최적화 접근법에서는 그래디언트를 계산하기 위해 텐서만을 사용하지만, 이는 기하학적으로 직관적이지 않습니다 따라서 이 프로젝트에서는 Pytorch와 Shapely를 사용하여 텐서 연산과 기하학적 연산을 통합합니다.

본 논문과 이 프로젝트의 가장 큰 차이점은 autograd의 사용 여부입니다. 논문에서는 그래디언트 흐름을 연결하기 위해 Differentiable Voronoi Diagram을 사용했지만, 여기서는 그래디언트를 직접 근사하기 위해 수치 미분 (Numerical Differentiation) 방식을 채택했습니다. Floor plan generation with voronoi diagram 먼저 수치 미분 (Numerical Differentiation)에 대해서 알아보겠습니다. Numerical Differentiation 수치 미분 (Numerical differentiation)은 finite perturbation differences를 사용하여 도함수를 근사하는 방법입니다. 원 논문에서 사용된 미분 가능한 보로노이 다이어그램을 통한 자동 미분과 달리, 이 방식은 여러 인접한 지점에서 함수를 평가하여 도함수를 계산합니다. 수치 미분에는 세 가지 기본적인 방법이 있습니다. 이 프로젝트에서는 그래디언트를 계산하기 위해 중앙 차분법을 사용했습니다. Basic methods for the numerical differentiation Central difference method: \[ \begin{align*} \,\\ f'(x) &= \lim_{h \, \rightarrow \, 0} \, \frac{1}{2} \cdot \left( \frac{f(x + h) - f(h)}{h} - \frac{f(x - h) - f(h)}{h} \right) \\\,\\ &= \lim_{h \, \rightarrow \, 0} \, \frac{1}{2} \cdot \frac{f(x + h) - f(x - h)}{h} \\\,\\ &= \lim_{h \, \rightarrow \, 0} \, \frac{f(x + h) - f(x - h)}{2h} \,\\ \end{align*} \] 수식에서 \(h \,(\text{또는 } dx)\)는 근사의 정확도를 결정하는 perturbation differences 값입니다. \(h\)가 0에 가까워질수록 수치적 근사는 실제 도함수에 더 가까워집니다. 하지만 실제로는 계산상의 한계와 부동소수점 정밀도 때문에 무한히 작은 값을 사용할 수 없습니다. 적절한 스텝 크기를 선택하는 것이 중요합니다. 너무 큰 값은 부정확한 근사를 초래하고, 반대로 너무 작은 값은 반올림 오차로 인한 수치적 불안정성을 야기할 수 있습니다. 안정적인 섭동값은 일반적으로 \(h = 10^{-4}\)에서 \(h = 10^{-6}\) 사이의 범위를 가집니다. 이 구현에서는 섭동값으로 \(h = 10^{-6}\)을 사용했습니다. Expression of Loss functions 원 논문에서 최적화에 사용되는 주요 손실 함수는 네 가지 부분으로 구성됩니다. 아래 내용은 논문에서 발췌된 내용입니다: Wall loss: Unconstrained Voronoi diagram 은 일반적으로 벽면 방향에 원치 않는 변동을 만들어내기 때문에, 벽면의 복잡도를 제어하기 위한 맞춤형 손실 함수를 설계했습니다. Cubic Stylization에서 영감을 받아, 벽면 길이의 \(\mathcal{L}_1\) 노름을 정규화했습니다. \(L_1\) 노름은 \(v_x + v_y\) (x의 노름 + y의 노름)로 정의되며, 따라서 벡터 \(\mathbb{v}_j - \mathbb{v}_i\)가 수직 또는 수평일 때 \(\mathcal{L}_{\text{wall}}\)이 최소값을 가집니다. \[ \,\\ \mathcal{L}_{\text{wall}} = w_{\text{wall}} \sum_{(v_i, v_j) \, \in \, \mathcal{E}} ||\, \mathbb{v}_i - \mathbb{v}_j \,||_{L1} \,\\ \] 여기서 \(\mathcal{E}\)는 인접한 두 방 사이의 보로노이 셀 경계선 집합을 나타내며, \(\mathbb{v}_i\)와 \(\mathbb{v}_j\)는 해당 경계선에 속한 보로노이 꼭짓점들을 나타냅니다. Area loss: 각 방의 면적은 사용자가 지정합니다. 현재 방 면적과 사용자가 지정한 목표 면적 간의 제곱 차이를 최소화합니다. 여기서 \(\bar{A}_r\)은 방 \(r\)의 목표 면적을 나타냅니다. \[ \,\\ \mathcal{L}_{\text{area}} = w_{\text{area}} \sum_{r=1}^{\#Room} ||\, A_r(\mathcal{V}) - \bar{A}_r \,||^2 \,\\ \] Lloyd loss: 사이트 밀도를 조절하기 위해 Lloyd's algorithm에서 영감을 받은 손실 함수를 설계했습니다. 여기서 \(\mathbb{c}_i\)는 \(i\)번째 보로노이 셀의 중심점을 나타냅니다. 이는 외부 사이트들을 \(\Omega\) 내부로 끌어들이는 데 유용합니다. \[ \,\\ \mathcal{L}_{\text{Lloyd}} = w_{\text{Lloyd}} \sum_{i=1}^N ||\, \mathbb{s}_i - \mathbb{c}_i \,||^2 \,\\ \] Topology loss: 각 방이 하나의 연결된 영역이 되도록 하고, 방들 간의 지정된 연결이 이루어지도록 위상 손실을 설계했습니다. 각 사이트 \(\mathbb{s}_i\)에 대한 목표 위치 \(\mathbb{t}_i\)를 설정하여 원하는 위상을 만족하도록 사이트를 이동시킵니다. \[ \,\\ \mathcal{L}_{\text{topo}} = w_{\text{topo}} \sum_{i=1}^N ||\, \mathbb{s}_i - \mathbb{t}_i \,||^2 \,\\ \] 목표 위치 \(\mathbb{t}_i\)는 같은 그룹 내에서 가장 가까운 사이트로 자동 계산됩니다. 각 방에 대해, 먼저 해당 방에 속한 사이트들을 인접한 사이트들의 그룹으로 묶습니다. 만약 여러 그룹이 존재한다면, 즉 방이 분리된 영역들로 나뉘어 있다면, 해당 그룹에서 가장 가까운 사이트를 사이트 \(\mathbb{t}_i\)의 목표 위치로 설정합니다. Implementation of loss functions 서론에서 언급했듯이, 순전파를 위한 위의 손실 함수들을 구현하기 위해 아래와 같이 Shapely와 Pytorch를 사용했습니다. 전체 손실은 위에서 설명한 손실들의 가중합으로 정의되며, 이를 사용하여 보로노이 다이어그램이 평면도를 생성합니다. \[ \,\\ \begin{align*} \mathcal{S}^{*} &= \arg \min_{\mathcal{S}} \mathcal{L}(\mathcal{S}, \mathcal{V}(\mathcal{S})) \\ \mathcal{L} &= \mathcal{L}_{\text{wall}} + \mathcal{L}_{\text{area}} + \mathcal{L}_{\text{fix}} + \mathcal{L}_{\text{topo}} + \mathcal{L}_{\text{Lloyd}} \end{align*} \,\\ \] class FloorPlanLoss(torch. autograd. Function): @staticmethod def compute_wall_loss(rooms_group: List[List[geometry. Polygon]], w_wall: float = 1. 0): loss_wall = 0. 0 for room_group in rooms_group: room_union = ops. unary_union(room_group) if isinstance(room_union, geometry. MultiPolygon): room_union = list(room_union. geoms) else: room_union = [room_union] for room in room_union: t1 = torch. tensor(room. exterior. coords[:-1]) t2 = torch. roll(t1, shifts=-1, dims=0) loss_wall += torch. abs(t1 - t2). sum(). item() for interior in room. interiors: t1 = torch. tensor(interior. coords[:-1]) t2 = torch. roll(t1, shifts=-1, dims=0) loss_wall += torch. abs(t1 - t2). sum(). item() loss_wall = torch. tensor(loss_wall) loss_wall *= w_wall return loss_wall @staticmethod def compute_area_loss( cells: List[geometry. Polygon], target_areas: List[float], room_indices: List[int], w_area: float = 1. 0, ): current_areas = [0. 0] * len(target_areas) for cell, room_index in zip(cells, room_indices): current_areas[room_index] += cell. area current_areas = torch. tensor(current_areas) target_areas = torch. tensor(target_areas) area_difference = torch. abs(current_areas - target_areas) loss_area = torch. sum(area_difference) loss_area **= 2 loss_area *= w_area return loss_area @staticmethod def compute_lloyd_loss(cells: List[geometry. Polygon], sites: torch. Tensor, w_lloyd: float = 1. 0): valids = [(site. tolist(), cell) for site, cell in zip(sites, cells) if not cell. is_empty] valid_centroids = torch. tensor([cell. centroid. coords[0] for _, cell in valids]) valid_sites = torch. tensor([site for site, _ in valids]) loss_lloyd = torch. norm(valid_centroids - valid_sites, dim=1). sum() loss_lloyd **= 2 loss_lloyd *= w_lloyd return loss_lloyd @staticmethod def compute_topology_loss(rooms_group: List[List[geometry. Polygon]], w_topo: float = 1. 0): loss_topo = 0. 0 for room_group in rooms_group: room_union = ops. unary_union(room_group) if isinstance(room_union, geometry. MultiPolygon): largest_room, *_ = sorted(room_union. geoms, key=lambda r: r. area, reverse=True) loss_topo += len(room_union. geoms) for room in room_group: if not room. intersects(largest_room) and not room. is_empty: loss_topo += largest_room. centroid. distance(room) loss_topo = torch. tensor(loss_topo) loss_topo **= 2 loss_topo *= w_topo return loss_topo ( . . . ) @staticmethod def forward( ctx: FunctionCtx, sites: torch. Tensor, boundary_polygon: geometry. Polygon, target_areas: List[float], room_indices: List[int], w_wall: float, w_area: float, w_lloyd: float, w_topo: float, w_bb: float, w_cell: float, save: bool = True, ) -> torch. Tensor: cells = [] walls = [] sites_multipoint = geometry. MultiPoint([tuple(point) for point in sites. detach(). numpy()]) raw_cells = list(shapely. voronoi_polygons(sites_multipoint, extend_to=boundary_polygon). geoms) for cell in raw_cells: intersected_cell = cell. intersection(boundary_polygon) intersected_cell_iter = [intersected_cell] if isinstance(intersected_cell, geometry. MultiPolygon): intersected_cell_iter = list(intersected_cell. geoms) for intersected_cell in intersected_cell_iter: exterior_coords = torch. tensor(intersected_cell. exterior. coords[:-1]) exterior_coords_shifted = torch. roll(exterior_coords, shifts=-1, dims=0) walls. extend((exterior_coords - exterior_coords_shifted). tolist()) cells. append(intersected_cell) cells_sorted = [] raw_cells_sorted = [] for site_point in sites_multipoint. geoms: for ci, (cell, raw_cell) in enumerate(zip(cells, raw_cells)): if raw_cell. contains(site_point): cells_sorted. append(cell) cells. pop(ci) raw_cells_sorted. append(raw_cell) raw_cells. pop(ci) break rooms_group = [[] for _ in torch. tensor(room_indices). unique()] for cell, room_index in zip(cells_sorted, room_indices): rooms_group[room_index]. append(cell) loss_wall = torch. tensor(0. 0) if w_wall > 0: loss_wall = FloorPlanLoss. compute_wall_loss(rooms_group, w_wall=w_wall) loss_area = torch. tensor(0. 0) if w_area > 0: loss_area = FloorPlanLoss. compute_area_loss(cells_sorted, target_areas, room_indices, w_area=w_area) loss_lloyd = torch. tensor(0. 0) if w_lloyd > 0: loss_lloyd = FloorPlanLoss. compute_lloyd_loss(cells_sorted, sites, w_lloyd=w_lloyd) loss_topo = torch. tensor(0. 0) if w_topo > 0: loss_topo = FloorPlanLoss. compute_topology_loss(rooms_group, w_topo=w_topo) loss_bb = torch. tensor(0. 0) if w_bb > 0: loss_bb = FloorPlanLoss. compute_bb_loss(rooms_group, w_bb=w_bb) loss_cell_area = torch. tensor(0. 0) if w_cell > 0: loss_cell_area = FloorPlanLoss. compute_cell_area_loss(cells_sorted, w_cell=w_cell) if save: ctx. save_for_backward(sites) ctx. room_indices = room_indices ctx. target_areas = target_areas ctx. boundary_polygon = boundary_polygon ctx. w_wall = w_wall ctx. w_area = w_area ctx. w_lloyd = w_lloyd ctx. w_topo = w_topo ctx. w_bb = w_bb ctx. w_cell = w_cell loss = loss_wall + loss_area + loss_lloyd + loss_topo + loss_bb + loss_cell_area return loss, [loss_wall, loss_area, loss_lloyd, loss_topo, loss_bb, loss_cell_area] 손실 함수들을 Shapely를 이용해 직관적인 파이썬 코드로 구현하는 과정에서, 원본 논문과는 다소 차이가 있습니다. Backward with numerical differentiation 수치 미분은 계산 성능 측면에서 효율적이지 않습니다. 이는 도함수를 근사하기 위해 여러 인접 지점에서 함수를 반복적으로 계산해야 하기 때문입니다. backward 메서드에서 볼 수 있듯이, 계산 성능은 주어진 사이트의 개수에 따라 크게 영향을 받습니다. 따라서 역전파 성능을 개선하기 위해 파이썬의 내장 멀티프로세싱 모듈을 사용했습니다. @staticmethod def _backward_one(args): ( sites, i, j, epsilon, boundary_polygon, target_areas, room_indices, w_wall, w_area, w_lloyd, w_topo, w_bb, w_cell, ) = args perturbed_sites_pos = sites. clone() perturbed_sites_neg = sites. clone() perturbed_sites_pos[i, j] += epsilon perturbed_sites_neg[i, j] -= epsilon loss_pos, _ = FloorPlanLoss. forward( None, perturbed_sites_pos, boundary_polygon, target_areas, room_indices, w_wall, w_area, w_lloyd, w_topo, w_bb, w_cell, save=False, ) loss_neg, _ = FloorPlanLoss. forward( None, perturbed_sites_neg, boundary_polygon, target_areas, room_indices, w_wall, w_area, w_lloyd, w_topo, w_bb, w_cell, save=False, ) return i, j, (loss_pos - loss_neg) / (2 * epsilon) @runtime_calculator @staticmethod def backward(ctx: FunctionCtx, _: torch. Tensor, __): sites = ctx. saved_tensors[0] room_indices = ctx. room_indices target_areas = ctx. target_areas boundary_polygon = ctx. boundary_polygon w_wall = ctx. w_wall w_area = ctx. w_area w_lloyd = ctx. w_lloyd w_topo = ctx. w_topo w_bb = ctx. w_bb w_cell = ctx. w_cell epsilon = 1e-6 grads = torch. zeros_like(sites) multiprocessing_args = [ ( sites, i, j, epsilon, boundary_polygon, target_areas, room_indices, w_wall, w_area, w_lloyd, w_topo, w_bb, w_cell, ) for i in range(sites. size(0)) for j in range(sites. size(1)) ] with multiprocessing. Pool(processes=multiprocessing. cpu_count()) as pool: results = pool. map(FloorPlanLoss. _backward_one, multiprocessing_args) for i, j, grad in results: grads[i, j] = grad return grads, None, None, None, None, None, None, None, None, None, None Initializing parameters 최적화 문제에서는 초기 매개변수가 최종 결과에 큰 영향을 미칩니다. 먼저, 보로노이 다이어그램의 사이트들이 주어진 평면도의 중심에 생성되도록 초기화했습니다: Random Sites Generation: 균일 분포를 사용하여 초기 무작위 사이트들을 생성합니다. Moving to Center of Boundary: 모든 사이트를 평면도 경계의 중심으로 이동시킵니다. Outside Sites Adjustment: 경계 밖으로 벗어난 사이트들을 안쪽으로 이동시켜 조정합니다. Voronoi Diagram: 사이트들을 사용하여 보로노이 다이어그램을 생성합니다. Process of parameters initialization 두 번째로, 각 사이트별 셀 인덱스를 할당하기 위해 KMeans 클러스터링 알고리즘을 사용했습니다. 거리 기반 KMeans 알고리즘은 사이트들의 공간적 근접성을 기준으로 그룹화하며, 이는 인접한 셀들로부터 방이 형성되도록 보장하는 데 도움이 됩니다. 사이트들을 사전에 클러스터링함으로써, 이미 공간적으로 연결된 초기 방 배치를 생성할 수 있었고, 이는 최적화 과정에서 방이 분리된 영역으로 나뉠 가능성을 줄여줍니다. 이러한 접근 방식을 사용하면 최적화가 더 안정적으로 수렴합니다. 예시를 보여드리겠습니다: Floor plan generation on 300 iterations From the left, optimization without KMeans · optimization with KMeans 위 그림에서 볼 수 있듯이, KMeans를 사용하면 손실이 더 부드럽게 흐르고 더 빠르게 수렴합니다. KMeans를 사용하지 않으면, 최적화 과정에서 방들이 분리되는 불안정한 동작을 보입니다. 반면에, 초기 방 할당에 KMeans를 사용하면 최적화 과정 전반에 걸쳐 공간적 일관성이 유지되어 다음과 같은 장점이 있습니다: 목표 방 면적으로 더 빠른 수렴 더 안정적인 벽체 정렬 방이 분리된 영역으로 나뉠 가능성 감소 이러한 최적화 안정성의 향상은 특히 여러 개의 방과 특정 면적 요구사항이 있는 복잡한 평면도에서 중요합니다. Experiments 마지막으로, 800회 반복으로 최적화된 실험 결과들을 첨부하며 이 글을 마무리하겠습니다. 실험에 사용된 경계들은 원 논문과 저장소에서 가져왔습니다. 전체 코드는 이 프로젝트의 저장소를 참고해 주시기 바랍니다. Future works Set entrance: 논문에서는 평면도의 출입구를 설정하기 위해 \(\mathcal{L}_{\text{fix}}\) 손실 함수 사용. Graph-based contraint: 논문에서는 방들 간의 인접성을 설정하고 보장하기 위해 그래프 기반 제약을 사용. Improve computational performance: 코드 실행 속도 최적화 (사용 언어 변경 또는 미분 가능한 보로노이 다이어그램 구현). Handle deadspaces: 실현 불가능한 평면도를 제외하기 위해 Deadspace에 대한 손실 함수 \(\mathcal{L}_{\text{deadspace}}\) 설정. Following boundary axis: 전역 X, Y축 대신 주어진 경계의 축을 따라 벽면 정렬 (\(\mathcal{L}_{\text{wall}}\) 대체). . .


The Potential for Architectural Space Variation Through the WFC Algorithm

The Potential for Architectural Space Variation Through the WFC Algorithm

The Potential for Architectural Space Variation Through the WFC Algorithm 현대 건축에서는 기존의 건축 어휘나 스타일을 “복사-붙여넣기”가 아니라, 창의적으로 재해석하고 변주하려는 시도가 늘고 있다.

그중 하나가 ‘Wave Function Collapse(WFC)’ 알고리즘을 활용하는 방법이다. 본 연구는 WFC 알고리즘을 건축 영역에 도입해, 공간 패턴의 변주를 어떻게 자동 생성할 수 있는지를 탐구한다. 특히 네덜란드 건축가 알도 반 아이크의 ‘Sonsbeek 조각 파빌리온’을 예시로 삼아, 해당 건물의 공간 어휘를 기계가 학습하도록 한 뒤 여러 평면 대안을 만들어보았다. 이 과정에서 드러난 알고리즘적 접근의 의의와 한계를 함께 논의하고자 한다. Understanding the Principles of the WFC Algorithm WFC(Wave Function Collapse) 알고리즘은 게임 개발이나 그래픽 쪽에서 자주 쓰이는 절차적 생성 기법으로 알려져 있다. 핵심은, 하나의 예시 패턴을 입력받아 그와 유사한 결과물을 만들어낸다는 점이다. 이를 건축 쪽으로 설명해보면, 우리가 어떤 건축물의 평면 패턴이나 공간적 룰을 “예시”로 제시하면, 컴퓨터가 그걸 배워서 흡사한 분위기이면서도 새로운 평면안을 자동으로 찍어내는 방식이라 할 수 있겠다. Superposition & Collapse WFC는 초기 단계에서 모든 셀이 “뭐든 될 수 있는” 불확정 상태(중첩)로 시작한다. 그러다 가장 불확실성이 낮은(선택지가 적은) 셀을 골라서, 그 셀에 들어갈 타일(또는 공간 모듈)을 하나로 확정한다(콜랩스). Constraint Propagation(제약 전파) 어떤 셀을 확정시키면, 그 주변 이웃에게 “내가 이 상태니까 너는 이 상태가 안 된다” 하는 식으로 영향을 준다. 이 과정을 반복하며 전체가 모순 없이 조합되도록 만든다. Backtracking(되돌아가기) 가끔 “아무 모듈도 들어갈 수 없는 막다른 골목”에 부딪히면, 알고리즘은 과거 결정 중 몇 단계를 되돌려 다른 대안을 시도한다. 결국 WFC는 건축으로 치면 “퍼즐 조각”을 하나씩 맞춰나가되, 충돌이 나면 되돌아가는 식으로 전체를 완성하는 알고리즘이라 이해할 수 있다. Aldo van Eyck Amsterdam Orphanage / Aldo van Eyck 알도 반 아이크(Aldo van Eyck)는 20세기 중반 네덜란드 건축계에서 큰 족적을 남긴 인물이다. 그는 인간 중심적이고 사회적 상호작용이 살아 있는 공간을 강조했다. 흔히 CIAM 이후의 ‘구조주의 건축’ 흐름에서, 건축을 인간 삶의 무대라 보며, 지극히 일상적이고 심리적인 요소까지 설계에 담아내려고 했다. 가령 그는 고아원 건물을 설계하면서, 주택 단위를 줄 맞춰 놓는 대신 작은 마당과 복도, 열린 공간을 곳곳에 배치해 마치 작은 도시처럼 만들었다. 거대한 외부 도시와 내부 건축이 자연스럽게 연결되도록 했으며, 도시에는 주택의 아늑함을, 주택에는 도시의 연속성을 담아내려고 했던 것이다. 이처럼 반 아이크는 단순히 깔끔한 모던 디자인을 넘어, 사람이 어울려 살아가는 공간의 리듬과 흐름을 건축에 녹이고자 했다. The Sonsbeek Sculpture Pavilion: Spatial Elements and Patterns Sonsbeek Sculpture Pavilion / Aldo van Eyck 그의 대표작 중 하나가 Sonsbeek 조각 파빌리온이다. 1966년 아른험(Arnhem)의 공원에서 열린 조각 전시회를 위해 만든 임시 전시관이었는데, “외부에서 보면 폐쇄적이지만, 내부로 들어가면 복잡하고 다채로운 전시 공간이 펼쳐지는” 방식으로 설계했다. 한마디로 “작은 도시를 공원 한가운데에” 만들어놓은 느낌을 주는 셈이다. 파빌리온은 평행으로 놓인 여러 개의 벽을 중심으로 구성되며, 사이사이에 반원형의 포켓 공간이나 복도가 얽혀 있다. 이렇게 “반복과 변주”로 만들어진 평면이 자연 속에서 “은밀하지만 사람을 끌어들이는” 특유의 경험을 제공한다. 본 연구에서는 이 파빌리온의 벽, 반원형 모듈, 복도 등을 퍼즐 조각처럼 추출하고, “벽+곡선+복도”라는 모듈 조합이 어떻게 이어질 수 있는지를 일종의 규칙(제약)으로 정의했다. WFC 알고리즘에 이 규칙 묶음을 입력하면, 원본 파빌리온의 분위기를 유지하면서도 다른 평면 변형을 만들어낼 수 있다. Analysis of the Sonsbeek Sculpture Pavilion Applying the WFC Algorithm: Generating Floor Plan Variations Information stored in each module Connection example 50 sample modules 본격적으로 WFC를 적용해봤다. 먼저 Sonsbeek 파빌리온을 격자 그리드 형태로 단순화했고, 거기에 들어갈 수 있는 모듈(벽, 포켓, 복도 등) 50여 개를 정의했다. 이때 “벽 옆에는 복도만 올 수 있다” “포켓은 특정 간격에만 배치 가능” 같은 세밀한 규칙들이 곧 WFC의 제약 조건이 된다. 2-module 4-module 6-module 12-module 알고리즘은 모든 셀이 모든 모듈 후보를 가진 상태로 시작해, 가장 확정하기 쉬운(엔트로피가 낮은) 셀부터 하나를 결정한다. 그 결정을 기반으로 인접 셀들의 가능성이 조정(제약 전파)되고, 다시 확정할 셀을 골라 결정한다. 이렇게 반복하면, 마치 반 아이크가 평행 벽과 곡선 모듈을 자연스럽게 짜 맞췄던 것과 유사한 느낌으로 평면이 자라난다. Process of WFC algorithm Aldo van Eyck mutation with WFC 그 결과, 원본과 꽤 흡사해 보이면서도 다른 변주안이 다수 생성됐다. 어떤 안은 포켓 공간이 더 많아서 더 미로처럼 느껴지기도 하고, 또 다른 안은 복도가 단순화돼 한층 정돈된 공간이 되기도 했다. 다만, 때론 백트래킹을 여러 번 해도 충돌을 피하지 못해 동선이 단절되거나 중복된 벽이 생기는 실패 사례도 나왔다. 이를 통해 “규칙 설계를 더 치밀하게 해야 한다”는 교훈을 얻었는데, “욕실 옆에 주방이 오면 안 된다” 같은 일상적 건축 규칙이 더욱 엄밀해야 한다는 이야기다. Overall process Significance and Limitations of the Algorithmic Approach 이렇듯 WFC 알고리즘은, 하나의 건축적 패턴(예: Sonsbeek 파빌리온)을 재료 삼아 다채로운 평면안을 뽑아내기에 좋다. 디자이너 입장에선 모든 변주를 손으로 그리지 않아도 되니, 단시간에 풍부한 아이디어 풀이가 마련된다는 장점이 있다. 또한 입력 예시가 가진 미적·공간적 논리를 어느 정도 유지해주므로, “기존 건축가의 어법”을 또 다른 공간에서도 활용하는 가능성을 열어준다. 하지만 건축은 수많은 질적 요소와 기능이 얽힌 복합 예술이기에, 숫자 규칙이나 모듈만으로 모든 걸 표현하기는 어렵다는 점도 한계로 지적할 수 있다. 예컨대 “이 공간이 주는 사람 사이의 심리적 거리감”이나 “건물에 깃든 역사와 도시 맥락”은 단순한 모듈 배치 규칙만으로 형상화하기 어렵다. 결국 인간 디자이너가 알고리즘 결과물을 바라보며, “이건 괜찮다, 저건 전시 동선이 너무 구불구불해 실제론 불편하겠다” 같은 판단을 내려야 한다. 게다가 구조 안전, 일조, 방음 등 현실적 문제도 알고리즘이 전부 해결해주진 않는다. 그런 점에서 WFC는 개념 스케치 단계나 형태 실험의 보조 도구로 유용한 편이다. Conclusion 알도 반 아이크가 보여준 인간 중심의 반복·변주 설계 기법을, WFC 알고리즘이 일정 부분 흉내 낼 수 있다는 사실은 꽤 흥미롭다. 즉, 하나의 건축적 문법(어휘)에서 시작해, 새로운 평면 시나리오를 다수 자동 생성해보는 시나리오가 가능하다는 뜻이다. 이를테면 앞으로 “자신이 좋아하는 건축가 A의 아파트 단지”를 입력 예시로 학습하면, 비슷한 어휘로 변주된 새로운 아파트 평면을 술술 만들어낼 수도 있다. 건축가 입장에선 이 수많은 변주안을 훑어보면서, 기존엔 떠올리지 못했던 예기치 않은 배치를 발견할 수도 있다. 반 아이크의 디자인 방법에 WFC를 접목한 시도는, 향후 건축 설계 프로세스에서 사례 기반의 생성 알고리즘이 어떻게 활용될 수 있는지를 보여주는 하나의 예시이라 할 수 있다. 예를 들어, 건축가가 자신의 대표작을 입력하면 그 변주를 통해 다음 프로젝트에 아이디어를 얻거나, 선배 건축가들의 작품을 학습한 알고리즘이 디자인 제안을 해주는 등의 응용을 상상해볼 수 있다. 나아가 개별 건축가의 스타일뿐만 아니라 특정 건축 유형의 보편적 패턴을 학습시켜 새로운 대안을 모색하는 등, 건축 지식의 축적과 재창조를 가속화하는 도구로 발전할 것으로 기대한다. Example Outputs References The Wavefunction Collapse Algorithm explained very clearly | Robert Heaton Infinite procedurally generated city with the Wave Function Collapse algorithm | Marian’s Blog Aldo van Eyck pavilion – Kröller-Müller Museum Labyrinth and Life - Luis Fernández-Galiano | Arquitectura Viva Aldo van Eyck > Sculpture Pavilion, Sonsbeek Exhibition | HIC Wave Function Collapse & Plan Adjacencies — Sam Aston . .


Polygon Segmentation

Polygon Segmentation

Objective 건축 설계의 초기 단계에서는 건물이 배치될 방향에 대한 축이라는 개념이 있습니다. 이는 건물의 기능성, 심미성, 환경 조건을 고려한 최적의 배치를 결정하는 데 중요한 역할을 합니다.

이 프로젝트의 목표는 주어진 2D 다각형을 몇 개의 축으로 분할해야 하는지와 어떻게 분할할지를 결정할 수 있는 모델을 개발하는 것입니다.     Segmented lands by a human architect 위 그림들은 건축가가 임의로 설정한 가상의 분할선입니다. 그림에서 아파트들은 분할된 다각형을 기반으로 배치되어 있습니다. 건축가들은 주어진 2D 다각형을 몇 개의 축으로 분할해야 하는지와 어떻게 분할할지를 직관적으로 알고 있지만, 이러한 직관을 컴퓨터에게 설명하는 것은 어렵습니다. 이를 달성하기 위해, 저는 딥러닝과 그래프 이론을 결합하여 사용할 것입니다. 그래프에서 다각형의 각 점은 노드가 되고 점들 간의 연결은 엣지가 됩니다. 이 개념을 바탕으로, 주어진 다각형을 최적으로 분할하는 방법을 학습하는 GNN 기반 모델을 구현할 것입니다. A simple understanding of graph and GNN 데이터를 생성하기 전에, 그래프와 그래프 신경망에 대해 이해해 보겠습니다. 기본적으로 그래프는 \( G = (V, E) \)로 정의됩니다. 이 표현에서 \( V \)는 정점(노드)의 집합이고 \( E \)는 엣지의 집합입니다. 그래프는 주로 Adjacency Matrix로 표현됩니다. 점의 개수가 \( n \)일 때, Adjacency Matrix \( A \)의 크기는 \( n \times n \)입니다. 머신러닝에서 그래프를 다룰 때는 점들의 특성을 나타내는 Feature Matrix 표현됩니다. 특성의 개수가 \( f \)일 때, Feature Matrix \( X \)의 차원은 \( n \times f \)입니다. Understanding of the graph expression In this figure, \( n = 4 \), \( f = 3 \) \( A = n \times n = 4 \times 4 \) \( X = n \times f = 4 \times 3 \). 그래프는 다양한 분야에서 데이터를 표현하는 데 사용되며, 다음과 같은 이유로 딥러닝 모델에 기하학을 가르칠 때 유용합니다: Representing Complex Structures Simply: 그래프는 복잡한 구조와 관계를 효과적으로 표현하여 다양한 형태의 기하학적 데이터를 모델링하는 데 적합합니다. Flexibility: 그래프 데이터구조는 다양한 수의 노드와 엣지를 수용할 수 있어, 다양한 형태와 크기의 객체를 다룰 때 유리합니다. Graph Neural Network(GNN)은 그래프 구조에서 동작하도록 설계된 신경망의 한 종류입니다. 벡터나 행렬과 같은 고정된 크기의 입력에서 작동하는 전통적인 신경망과 달리, GNN은 크기와 연결성이 가변적인 그래프 구조의 데이터를 처리할 수 있습니다. 이러한 특성으로 인해 GNN은 소셜 네트워크, Geometry 등과 같이 관계가 중요한 작업에 특히 적합합니다. GNN은 주로 연결과 이웃 노드들의 상태를 사용하여 각 노드의 상태를 업데이트(학습)합니다(Message Passing). 그런 다음 최종 상태를 기반으로 예측이 이루어집니다. 이 최종 상태는 일반적으로 Node Embedding이라고 합니다(또는 노드의 원시 특성이 다른 표현으로 변환되기 때문에 Encoding이라고도 할 수 있습니다). Message Passing에는 다양한 방법이 있지만, 이 작업은 기하학을 다루게 될 것이므로 Spatial Convolutionan Network를 기반으로 한 모델에 집중해 보겠습니다. 이 방법은 중요한 기하학적 및 공간적 구조를 가진 데이터를 표현하는 데 적합한 것으로 알려져 있습니다. 공간 합성곱 네트워크를 사용하는 GNN은 각 노드가 이웃 노드들의 정보를 통합할 수 있게 하여 지역적 특성을 더 정확하게 이해할 수 있게 합니다. 이를 통해 모델은 기하학의 복잡한 형태와 특성을 더 잘 이해할 수 있습니다. Convolution operations From the left, 2D Convolution · Graph Convolution Spatial Convolutional Network (SCN)의 아이디어는 Convolutional Neural Network (CNN)와 유사합니다. 이미지는 grid-shaped graph로 변환될 수 있습니다. CNN은 필터를 사용하여 중심 픽셀의 주변 픽셀들을 결합하여 이미지를 처리합니다. SCN은 이 아이디어를 그래프 구조로 확장하여, 주변 픽셀 대신 연결된 이웃 노드들의 특성을 결합합니다. 구체적으로, CNN은 필터가 각 픽셀의 주변 영역을 고려하여 특성을 추출하는 규칙적인 격자 구조에서 이미지를 처리하는 데 유용합니다. 반면에, SCN은 일반적인 그래프 구조에서 작동하며, 각 노드의 특성을 연결된 이웃들의 특성과 결합하여 Embedding을 생성합니다. Data preparation 위에서 그래프와 GNN에 대해 간단히 살펴보았으니, 이제 데이터를 준비해 보겠습니다! 우리 주변에는 이미 많은 원시 다각형이 있으며, 그것이 바로 토지입니다. 먼저, vworld에서 원시 다각형을 수집해 보겠습니다. 아래는 제가 vworld에서 수집한 모든 원시 다각형의 일부입니다. Somewhere in Seoul. shp 이제 raw polygons을 수집했으니, feature matrix에 포함될 polygon의 특성들을 정의해 보겠습니다. 다음과 같습니다: X coordinate (normalized) Y coordinate (normalized) Inner angle of each vertex (normalized) Incoming edge length (normalized) Outgoing edge length (normalized) Concavity or convexity of each vertex Minimum rotated rectangle ratio Minimum rotated rectangle aspect ratio Polygon area 그런 다음, 이러한 데이터를 graph 형태로 변환해야 합니다. 다음은 수작업 라벨링의 예시입니다: Land polygon in Gangbukgu, Seoul From the left, raw polygon & labeled link · adjacency matrix · feature matrix 하지만 수많은 raw polygons을 수작업으로 라벨링하는 것은 불가능하기 때문에, 이 데이터를 자동으로 생성해야 합니다. 알고리즘으로 완전 자동화할 수 있다면 좋겠지만, 그것이 가능하다면 딥러닝이 필요하지 않았을 것입니다 🤔. 그래서, 수작업을 조금이라도 줄일 수 있는 간단한 알고리즘을 개발했습니다. 이 알고리즘은 다각형의 triangulations에서 영감을 받았습니다. 이 알고리즘의 과정은 다음과 같습니다: Triangulate a polygon: Triangulation edge들을 추출합니다. Generate Splitter Combinations: 반복 처리를 위한 모든 가능한 분할선 조합을 생성합니다. Iterate Over Combinations: 최적의 분할된 다각형을 찾기 위해 모든 가능한 분할선 조합을 반복합니다. Check Split Validity: 모든 분할이 유효한지 확인합니다. Compute Scores and evaluation: 최적의 분할을 저장하기 위한 점수를 계산합니다. 다섯 번째 단계에서 사용되는 점수들(even_area_score, ombr_ratio_score, slope_similarity_score)은 다음과 같이 계산되며, 각 점수는 가중 합으로 집계되어 가장 낮은 점수를 가진 segmentation을 얻습니다. \[ even\_area\_score = \frac{(A_1 - \sum_{i=2}^{n} A_i)}{A_{polygon}} \times {w_1} \] \[ ombr\_ratio\_score = |(1 - \frac{A_{split1}}{A_{ombr1}}) - \sum_{i=2}^{n} (1 - \frac{A_{spliti}}{A_{ombri}})| \times {w_2} \] \[ avg\_slope\_similarity_i = \frac{\sum_{j=1}^{k_i} |\text{slope}_j - \text{slope}_{\text{main}}|}{k_i} \] \[ slope\_similarity\_score = \frac{\sum_{i=1}^{n} avg\_slope\_similarity_i}{n} \times {w_3} \] \[ score = even\_area\_score + ombr\_ratio\_score + slope\_similarity\_score \] 이 알고리즘의 전체 코드는 여기에서 확인할 수 있으며, 알고리즘의 핵심 부분은 다음과 같습니다. def segment_polygon( self, polygon: Polygon, number_to_split: int, segment_threshold_length: float, even_area_weight: float, ombr_ratio_weight: float, slope_similarity_weight: float, return_splitters_only: bool = True, ): """Segment a given polygon Args: polygon (Polygon): polygon to segment number_to_split (int): number of splits to segment segment_threshold_length (float): segment threshold length even_area_weight (float): even area weight ombr_ratio_weight (float): ombr ratio weight slope_similarity_weight (float): slope similarity weight return_splitters_only (bool, optional): return splitters only. Defaults to True. Returns: Tuple[List[Polygon], List[LineString], List[LineString]]: splits, triangulations edges, splitters """ _, triangulations_edges = self. triangulate_polygon( polygon=polygon, segment_threshold_length=segment_threshold_length, ) splitters_selceted = None splits_selected = None splits_score = None for splitters in list(itertools. combinations(triangulations_edges, number_to_split - 1)): exterior_with_splitters = ops. unary_union(list(splitters) + self. explode_polygon(polygon)) exterior_with_splitters = shapely. set_precision( exterior_with_splitters, self. TOLERANCE_LARGE, mode="valid_output" ) exterior_with_splitters = ops. unary_union(exterior_with_splitters) splits = list(ops. polygonize(exterior_with_splitters)) if len(splits) != number_to_split: continue if any(split. area score_sum: splits_score = score_sum splits_selected = splits splitters_selceted = splitters if return_splitters_only: return splitters_selceted return splits_selected, triangulations_edges, splitters_selceted 하지만 이 알고리즘은 완벽하지 않으며, 몇 가지 문제가 있습니다. 이 알고리즘은 점수 계산에 weight를 사용하기 때문에 이에 민감할 수 있습니다. 아래 그림들을 보면, 좋은 예시와 나쁜 예시를 각가 보실 수 있습니다. Results of the algorithm From the left, triangulations · segmentations · oriented bounding boxes for segmentations 이러한 문제들은 위에서 소개한 알고리즘으로 처리할 수 없기 때문에, 먼저 알고리즘을 사용하여 원시 다각형 데이터를 처리한 다음 다음과 같이 수동으로 라벨링했습니다. 이제 증강된 원본을 포함하여 약 40000개의 데이터를 보유하고 있습니다. 전체 데이터셋은 여기에서 확인할 수 있습니다. Some data for training Model for link prediction 이제 Pytorch Geometric을 사용하여 graph 모델을 만들고 graph 데이터를 학습시켜 보겠습니다. Pytorch Geometric은 PyTorch를 기반으로 graph neural network를 쉽게 작성하고 학습할 수 있게 해주는 라이브러리입니다. 모델에 부여해야 할 역할은 다각형을 분할할 선들을 생성하는 것입니다. 이는 GNN에서 주로 사용되는 link prediction이라는 작업으로 해석될 수 있습니다. Link prediction 모델은 일반적으로 encoder-decoder 구조를 사용합니다. Encoder는 노드의 특성을 추출하는 벡터 표현인 node embedding을 생성합니다. Decoder는 이러한 embedding을 사용하여 노드 쌍이 연결될 확률을 예측합니다. 추론시에는 모델이 모든 가능한 노드 쌍을 decoder에 입력합니다. 그런 다음 각 쌍이 연결될 확률을 계산합니다. 특정 임계값보다 높은 확률을 가진 쌍만 유지되며, 이는 연결 가능성이 높음을 나타냅니다. 일반적으로 노드 쌍의 유사성을 기반으로 link를 예측하기 위해 내적과 같은 간단한 연산이 사용됩니다. 하지만 저는 이 접근 방식이 geometry 데이터를 사용하는 작업에는 적합하지 않다고 생각하여 decoder를 추가로 학습시켰습니다. 아래는 모델의 encode와 decode 메서드입니다. 전체 모델 코드는 여기에서 확인할 수 있습니다. class PolygonSegmenter(nn. Module): def __init__( self, conv_type: str, in_channels: int, hidden_channels: int, out_channels: int, encoder_activation: nn. Module, decoder_activation: nn. Module, predictor_activation: nn. Module, use_skip_connection: bool = Configuration. USE_SKIP_CONNECTION, ): ( . . . ) def encode(self, data: Batch) -> torch. Tensor: """Encode the features of polygon graphs Args: data (Batch): graph batch Returns: torch. Tensor: Encoded features """ encoded = self. encoder(data. x, data. edge_index, edge_weight=data. edge_weight) return encoded def decode(self, data: Batch, encoded: torch. Tensor, edge_label_index: torch. Tensor) -> torch. Tensor: """Decode the encoded features of the nodes to predict whether the edges are connected Args: data (Batch): graph batch encoded (torch. Tensor): Encoded features edge_label_index (torch. Tensor): indices labels Returns: torch. Tensor: whether the edges are connected """ # Merge raw features and encoded features to inject geometric informations if self. use_skip_connection: encoded = torch. cat([data. x, encoded], dim=1) decoded = self. decoder(torch. cat([encoded[edge_label_index[0]], encoded[edge_label_index[1]]], dim=1)). squeeze() return decoded Encoding 과정은 원본 feature matrix \( F \)를 잠재적으로 다른 차원을 가진 새로운 행렬 \( E \)로 변환합니다. 다음은 encode 함수 전후의 feature matrix 표현입니다. \( n \)은 노드의 개수 \( m \)은 feature의 개수 \( c \)는 channel의 개수 \( F \in \mathbb{R}^{n \times m} \)는 encode 함수 이전의 feature matrix \( E \in \mathbb{R}^{n \times c} \)는 encode 함수 이후의 feature matrix \[ F = \begin{bmatrix} f_{11} & f_{12} & \cdots & f_{1m} \\ f_{21} & f_{22} & \cdots & f_{2m} \\ \vdots & \vdots & \ddots & \vdots \\ f_{n1} & f_{n2} & \cdots & f_{nm} \end{bmatrix} \] \[ E = \begin{bmatrix} e_{11} & e_{12} & \cdots & e_{1c} \\ e_{21} & e_{22} & \cdots & e_{2c} \\ \vdots & \vdots & \ddots & \vdots \\ e_{n1} & e_{n2} & \cdots & e_{nc} \end{bmatrix} \] decode 메서드에서는 노드의 encoded feature와 raw feature를 사용하여 노드들 간의 link를 예측합니다. 이 skip connection은 raw feature와 encoded feature를 병합하여 geometry 정보를 주입합니다. Skip connection을 사용하면 원본 특성을 보존하고 low-level과 high-level 정보를 결합하여 전체적인 모델 성능을 향상시킬 수 있습니다. feedforward 과정에서 decoder는 연결된 레이블과 연결되지 않아야 할 레이블로 학습됩니다. 이는 negative sampling이라고 하는 기법으로, link prediction 작업에서 모델 성능을 향상시키는 데 사용됩니다. 연결되지 않아야 할 예시를 제공함으로써, negative sampling은 모델이 실제 link와 non-link를 더 잘 구분할 수 있게 도와주어 미래 또는 누락된 link를 예측하는 정확도를 향상시킵니다. 대부분의 네트워크에서 실제 link는 non-link보다 훨씬 적기 때문에, 모델이 link가 없다고 예측하는 방향으로 편향될 수 있습니다. Negative sampling은 negative example을 제어된 방식으로 선택할 수 있게 하여 training data의 균형을 맞추고 학습 과정을 향상시킵니다. def forward(self, data: Batch) -> Tuple[torch. Tensor, torch. Tensor, torch. Tensor]: """Forward method of the models, segmenter and predictor Args: data (Batch): graph batch Returns: Tuple[torch. Tensor, torch. Tensor, torch. Tensor]: whether the edges are connected, predicted k and target k """ # Encode the features of polygon graphs encoded = self. encode(data) ( . . . ) # Sample negative edges negative_edge_index = negative_sampling( edge_index=data. edge_label_index_only, num_nodes=data. num_nodes, num_neg_samples=int(data. edge_label_index_only. shape[1] * Configuration. NEGATIVE_SAMPLE_MULTIPLIER), method="sparse", ) # Decode the encoded features of the nodes to predict whether the edges are connected decoded = self. decode(data, encoded, torch. hstack([data. edge_label_index_only, negative_edge_index]). int()) return decoded, k_predictions, k_targets 지금까지 encoder와 decoder를 정의했습니다. encoder와 decoder는 batch 단위로 node들을 사용하기 때문에 각각의 graph를 개별적으로 인식할 수 없는 것으로 보입니다. graph가 입력되었을 때 몇 개의 segmentation으로 나눌지를 모델이 학습하기를 원했기 때문에, segmenter 클래스 내에서 k를 별도로 학습하기 위한 predictor를 정의했습니다. segmenter는 위에서 설명한 encoder와 decoder 과정을 통해 topk를 사용하여 segmentation을 생성합니다. 그런 다음 생성된 link들을 연결 강도 순으로 정렬하고, predictor가 몇 개의 link를 사용할지 결정합니다. Inference process From the top, topk segmentations · segmentation selected by predictor Training and evaluating 이제 모델을 학습시킬 차례입니다. 모델은 500 epoch 동안 학습되었으며, 이 과정에서 training loss와 validation loss를 기록하여 학습 진행 상황과 수렴도를 모니터링했습니다. 그래프에서 보여지듯이: 상단의 그래프에 따르면, training loss와 validation loss 모두 초기에 빠르게 감소한 다음 일반화됩니다. 모든 평가 지표(accuracy, F1 score, recall, AUROC)가 초기에 빠른 향상을 보인 후 안정화되어 좋은 일반화를 보입니다. Losses and metrics for 500 epochs 모든 지표가 좋아 보이지만, 이러한 지표들을 100% 신뢰할 수 있는지에 대한 의문이 있습니다. 이는 negative sampling의 영향 때문일 수 있습니다. 일부 test data의 시각화를 기반으로 볼 때, 모델이 분할이 필요하지 않은 다각형을 정확하게 예측한다는 것이 분명합니다. 이는 모델이 negative sample에 대해 overfitting되었을 수 있음을 시사합니다. Negative sample에 대한 높은 성능이 positive case를 정확하게 식별하는 모델의 능력을 정확히 반영하지 못할 수 있습니다. Evaluate some test data qualitatively IoU loss에서 영감을 받아, segmentation 품질을 평가하고 신뢰할 수 있는 평가 지표를 만들기 위해 GeometricLoss를 정의했습니다. GeometricLoss는 graph 내에서 예측된 geometric 구조와 ground-truth geometric 구조 간의 차이를 정량화하는 것을 목표로 합니다. Geometric loss 계산의 과정은 다음과 같습니다: Polygon Creation from Node Features: 각 graph의 좌표를 나타내는 node feature들이 다각형으로 변환됩니다. Connecting Predicted Edges: 이 edge들은 다각형의 예측된 segmentation을 나타냅니다. Connecting Ground-Truth Edges: 이 edge들은 다각형의 올바른 segmentation을 나타냅니다. Creating Buffered Unions: 예측된 edge와 ground-truth edge가 결합된 후 buffer 처리됩니다. Calculating Intersection: 이 교차 영역은 예측된 geometric 구조와 실제 geometric 구조 사이의 공통 영역을 나타냅니다. Computing Geometric Loss: 교차 영역이 ground-truth 영역으로 정규화된 후 음수화됩니다. An example for the geometric loss calculation From the left, loss: -0. 000478528 · loss: -0. 999999994 Geometric loss는 모델의 evaluation metric으로만 사용됩니다. 따라서 BCE loss에 추가되지 않았습니다. 이는 모델의 training batch가 graph가 아닌 node를 기반으로 하기 때문입니다. 따라서 매 epoch마다 모든 graph에 대해 이 loss를 계산하면 처리 속도가 크게 저하될 것입니다. 그래서 batch당 4 sample에 대해서만 이 loss를 계산하여 모델 평가 지표로만 사용했습니다. 결과는 다음과 같습니다. Geometric losses for 500 epochs From the left, train geometric loss · validation geometric loss GeometricLoss 클래스는 다음과 같이 정의되며 코드는 여기에서 확인할 수 있습니다. Limitations and future works GNN은 node embedding 기반 모델이므로 그래프의 기하학적 형상보다는 개별 노드의 특성을 인식하는 것으로 보입니다. GNN은 학습 중에 형태를 일반화하는 능력이 뛰어나지만, 의도한 label에 정확하게 overfit시키는 것은 어려웠습니다. Comparison with CNNs: CNN은 지역적 패턴을 포착하고 이를 더 추상적인 표현으로 결합하는 데 뛰어나며, 이는 다각형을 다루는 작업에 유리할 수 있습니다. Imbalanced labels for predictor: predictor는 0, 1, 2 중 하나를 선택하도록 학습되지만, 2에 대한 데이터의 수가 매우 적습니다. Exploring reliable metrics: geometry 작업에 더 적합한 지표들을 탐색합니다. Overfitting: positive label을 완전히 overfit할 수 있는 방법을 연구합니다. References https://pytorch-geometric. readthedocs. io/en/latest/ https://en. wikipedia. org/wiki/Graph_theory/ https://mathworld. wolfram. com/AdjacencyMatrix. html/ https://process-mining. tistory. com/164 https://blog. naver. com/gyrbsdl18/222556439520 https://medium. com/watcha/gnn-%EC%86%8C%EA%B0%9C-%EA%B8%B0%EC%B4%88%EB%B6%80%ED%84%B0-%EB%85%BC%EB%AC%B8%EA%B9%8C%EC%A7%80-96567b783479/ . .


Spacewalk × Boundless

Spacewalk × Boundless

Boundless 스페이스워크의 자회사인 경계없는작업실은 이름 그대로 다양한 분야의 전문가들이 협력하여 경계를 허무는 혁신을 추구합니다. 대한민국 최초의 인공지능 건축 설계 서비스인 랜드북은 경계없는작업실의 사내 기술 연구소에서 탄생하여 기술과 디자인을 아우르는 새로운 가능성을 제시했습니다.

또한, 경계없는작업실은 그린램프라이브러리와 임팩트웨이브의 초기 주요 투자자로 참여했으며, 최근에는 예산과 기한을 중시하는 건설 관리의 범주를 넘어 설계와 건설을 통합하는 프로세스를 구축하고 있습니다. 경계없는작업실은 그린램프라이브러리와 임팩트웨이브의 초기 주요 투자자로 참여했으며, 최근에는 예산과 기한을 중시하는 건설 관리의 범주를 넘어 설계와 건설을 통합하는 프로세스를 구축하고 있습니다. Projects designed by Boundless Finding Wave Point’s Balance with Parametric Design ‘Wave Point‘는 제주도의 해안선에서 영감을 받은 곡선미와 층층이 쌓인 형태를 특징으로 하는 디자인으로, 해양 환경의 아름다움을 반영합니다. ‘Wave’라는 이름에서 연상되는 인상적인 외관에 ‘Point’라는 개념을 추가함으로써, 이곳이 단순한 교통의 허브를 넘어 커뮤니티의 중심지로서 기능할 것임을 강조합니다. ‘Wave Point: Socar Station Jeju’는 제주도의 자연을 현대적이고 역동적인 건축물로 재해석한 곳이며, 여유롭고 걱정없는 여행의 시작점으로 자리 잡을 것입니다. Objectives and Requirements Wave Point: Socar Station Jeju 프로젝트는 예산 내에서 공사비를 맞추기 위해 면적 조정 등을 진행 중이며, 이 과정에서 건축가와 발주자 모두 최적의 디자인 밸런스를 찾기를 원합니다. 디자인 조정은 여러 파라미터를 통해 이루어지며, 현재는 건축가는 오랜시간을 걸려서 소수의 대안을 만들고 그 대안 가운데에서 최적안을 찾고 있습니다. 스페이스워크 기술팀이 파라미터 기반 디자인 툴을 경계없는작업실 건축가들에게 제공함으로써, 건축가는 보다 합리적인 결정을 내리고 발주자를 설득하는 데에도 유리할 것으로 보입니다. 이 프로젝트에서는 파라메트릭 모델링링의 장점을 활용하여 디자인 프로세스를 혁신하고자 합니다. 건물의 핵심 요소인 층별 원형 평면의 반경과 중심점, 층고, 그리고 외피의 곡률 등을 변수로 설정하여 다양한 디자인 변형을 시도할 수 있습니다. 이러한 접근 방식은 기존의 수작업 설계 방식과 비교하여 몇 가지 중요한 이점을 제공합니다. 첫째, 디자인 탐색 과정이 대폭 효율화됩니다. 건축가들은 실시간으로 다양한 디자인 옵션을 생성하고 검토할 수 있어, 창의적인 해결책을 더 빠르게 도출할 수 있습니다. 둘째, 설계 변경 시 발생하는 건축물의 면적, 구조, 외관 등의 변화를 즉각적으로 확인할 수 있어 더욱 정확한 의사결정이 가능합니다. 셋째, 발주자와의 협의 과정에서 다양한 대안을 실시간으로 제시하고 비교할 수 있어, 더욱 효과적인 커뮤니케이션이 가능해집니다. 특히 이 프로젝트에서는 2층 바닥판 크기, 기둥 위치, 코어의 위치 및 볼륨을 고정 요소로 설정하여 구조적 안정성과 기능성을 확보하면서도, 나머지 요소들의 유동적인 조정을 통해 최적의 디자인 솔루션을 찾아갈 계획입니다. 이러한 파라메트릭 접근은 예술성과 기능성, 경제성의 균형을 찾는 데 큰 도움이 될 것으로 기대됩니다. Parameters and Constraint Wave Point 프로젝트의 파라메트릭 디자인은 건축물의 형태와 기능을 결정짓는 주요 변수들을 체계적으로 제어합니다. 이러한 변수들은 건물의 미학적 가치와 실용성, 그리고 경제성을 모두 고려하여 설정되었습니다. 각 파라미터는 상호 연관성을 가지며, 이들의 조합을 통해 최적의 디자인 솔루션을 도출할 수 있습니다. 파라메트릭 디자인 시스템은 크게 고정 요소와 가변 요소로 구분됩니다. 고정 요소는 구조적 안정성과 기본 기능을 보장하며, 가변 요소는 디자인의 유연성과 최적화 가능성을 제공합니다. 이러한 요소들의 체계적인 조절을 통해 예산 제약 내에서 최적의 디자인을 찾아갈 수 있습니다. Parameters 층별 원들의 반경: 각 층의 평면 크기를 결정하는 핵심 요소로, 사용 면적과 외관 형태에 직접적인 영향을 미칩니다. 중심점: 각 층 평면의 위치를 결정하며, 전체적인 매스의 움직임과 역동성을 만들어냅니다. 층별 층고: 공간의 실질적 활용도와 외부에서 보이는 건물의 비례감을 결정합니다. Tapered면의 곡률(종횡비): 건물 외피의 기울기와 곡면을 형성하여 전체적인 조형미를 완성합니다. Parameters scanning Constraint 구조적 제약: 2층 바닥판(지름 38m), 기둥 위치, 코어의 위치 및 볼륨 Structural constraint 3D Parameteric Model View Wave Point의 파라메트릭 모델은 설계 요소들이 유기적으로 연결된 통합 시스템으로 구성되어 있습니다. 이 모델은 앞서 설명한 모든 파라미터와 제약 조건들을 포함하며, 각 요소들의 변화가 전체 디자인에 미치는 영향을 실시간으로 확인할 수 있습니다. 이러한 통합적 접근을 통해 디자인 변경 시 발생하는 영향을 즉각적으로 분석하고, 최적의 해결안을 도출할 수 있습니다. Socar Station Jeju Parametric Model Variation designed by Parametric System 파라메트릭 디자인 시스템을 통해 다양한 디자인 변형을 탐색하고 평가할 수 있습니다. 각 변형은 앞서 정의된 파라미터들의 조합을 통해 생성되며, 다음과 같은 관점에서 평가될 수 있습니다: 외피 디자인의 심미성 구조적 안정성 시공 용이성 경제성 이러한 다각적 평가를 통해, 프로젝트의 목표와 제약 조건을 모두 만족시키는 최적의 디자인 안을 도출할 수 있습니다. 파라메트릭 시스템은 수백 가지의 디자인 변형을 신속하게 생성하고 분석할 수 있어, 건축가가 더 나은 의사결정을 내릴 수 있도록 돕습니다. . .


Infinite synthesis

Infinite synthesis

Introduction Deep Generative AI는 훈련 데이터와 유사한 새로운 데이터를 생성하는 인공지능 분야로, 텍스트 생성 및 이미지 생성뿐만 아니라 디자인 산업의 3D 모델 생성에도 영향을 미치고 있습니다.

건축 디자인 분야에서, 특히 초기 디자인 단계에서 Generative AI는 다양한 디자인 옵션을 검토하는 데 유용한 도구로 활용될 수 있습니다.   Physical model examination by humans Shou Sugi Ban / BYTR Architects 이 프로젝트는 잠재 벡터와 함께 딥 서명 거리 함수 모델(DeepSDF)을 활용하여, 훈련된 데이터와 유사한 무한한 수의 skyscrapers를 합성할 수 있는 알고리즘을 구축하는 것을 목표로 합니다. 이러한 벡터는 고차원 잠재 공간 내에 매핑되어 잠재적 skyscrapers를 합성하는 DNA 역할을 합니다. 건물의 형태를 표현할 수 있는 잠재 벡터를 조작하는 시스템을 통해 건축 디자이너는 다양한 디자인 옵션을 신속하게 생성하고 검토할 수 있습니다. 두 개 이상의 잠재 벡터 간의 조작(interpolation, and arithmetic operations)을 통해 모델은 가상으로 무한한 디자인 옵션을 제공합니다. 이 방법은 새로운 디자인 프로세스를 제공할 뿐만 아니라 기존의 디자인 방법론으로는 도달할 수 없었던 새로운 건축 형태를 탐구할 수 있게 해줍니다. Understanding Signed Distance Functions Wikipedia에서 Signed Distance Functions (SDFs)는 다음과 같이 정의됩니다: 수학과 그 응용분야에서 signed distance function(또는 oriented distance function)은 메트릭 공간에서 집합 \(\Omega\)의 경계까지의 한 점 \(x\)로부터의 수직 거리를 나타내며, 부호는 \(x\)가 \(\Omega\)의 내부에 있는지 여부에 따라 결정됩니다. 이 함수는 \(\Omega\) 내부의 점 \(x\)에서 양수 값을 가지며, signed distance function이 0이 되는 \(\Omega\)의 경계에 가까워질수록 값이 감소하고, \(\Omega\) 외부에서는 음수 값을 가집니다. 하지만 때로는 반대의 규칙(즉, \(\Omega\) 내부에서 음수, 외부에서 양수)이 적용되기도 합니다. SDF representation applied to the Stanford Bunny (a) 부호 결정 방법: 점이 표면에 있을 경우 SDF = 0 (b) signed distance field의 2D 단면 (c) SDF = 0에서 복원된 3D 표면 SDFs의 핵심은 복잡한 기하학적 형태를 표현하는 단순함과 강력함에 있습니다. 꼭지점, 모서리, 면을 사용하여 형태를 정의하는 전통적인 mesh 표현과 달리, SDFs를 사용하면 연속적인 표면을 가진 3D mesh 모델을 구축할 수 있으며, 이는 단지 3D 격자 형태의 XYZ와 해당하는 SDF 값만을 필요로 합니다. SDF = 0에서 복원된 OMA의 CCTV 본사 건물의 다음 예시를 살펴보겠습니다. 초기에 SDF 값을 얻기 위해, CCTV 본사 모델 주변의 전체 공간이 regular grid에서 샘플링됩니다 (이 예시에서 resolution은 격자점의 수를 나타냅니다. 즉, resolution 8은 8x8x8 격자를 의미합니다). 각 격자점에서 SDF는 모델의 가장 가까운 표면으로부터의 점의 거리를 나타내는 값을 제공합니다. 모델 내부에서 이 값들은 음수(또는 규칙에 따라 양수)이며, 외부에서는 양수(또는 음수)입니다. 아래 그림에서 볼 수 있듯이, 더 많은 격자점은 더 상세하고 정확한 3D 모델을 만들어냅니다. 예시에서 사용된 격자점의 수는 각각 8x8x8(=512), 16x16x16(=4096), 32x32x32(=32768), 64x64x64(=262144), 128x128x128(=2097152)입니다. 격자점과 SDF 값을 사용하여 mesh를 복원하기 위해서는 Marching Cubes 알고리즘을 사용해야 합니다. Recovered CCTV headquarters from the SDFs 상단 3개, 원본 3D 모델 · resolution 8 · resolution 16 하단 3개, resolution 32 · resolution 64 · resolution 128 점이 모델의 내부인지 외부인지를 결정하는 각 sign이 필요하기 때문에, SDF 값에서 복원하는 mesh는 완전히 닫힌 watertight mesh여야 합니다. 아래 코드를 사용하여 SDF 값과 각 격자 resolution에서 복원된 mesh를 검사했습니다. check_watertight 매개변수가 True로 설정되어 있어, 코드는 mesh가 watertight인지 확인하고, 완전히 닫혀있지 않은 경우 pcu를 사용하여 watertight mesh로 변환합니다. mesh = DataCreatorHelper. load_mesh( path=r"deepSDF\data\raw-skyscrapers\cctv_headquarter. obj", normalize=True, map_z_to_y=True, check_watertight=True, translate_mode=DataCreatorHelper. CENTER_WITHOUT_Z ) for resolution in [8, 16, 32, 64, 128]: coords, grid_size_axis = ReconstructorHelper. get_volume_coords(resolution=resolution) sdf, *_ = pcu. signed_distance_to_mesh( np. array(coords. cpu(), dtype=np. float32), mesh. vertices. astype(np. float32, order='F'), mesh. faces. astype(np. int32) ) recovered_mesh = ReconstructorHelper. extract_mesh( grid_size_axis, torch. tensor(sdf), ) Data preparation and processing DeepSDF 모델을 훈련시키기 위한 데이터 준비의 첫 번째 단계는 skyscrapers의 3D 모델을 수집하는 것입니다. 저는 무료 3D 모델을 다운로드하기 위해 3dwarehouse를 사용했습니다. 3dwarehouse로부터 그림에 있는 모델들을 다운로드했습니다. 왼쪽부터, CCTV 본사 · 마하나콘 · 허스트 타워 · 중국은행 · 엠파이어 스테이트 빌딩 · 트랜스아메리카 피라미드 · 더 샤드 · 거킨 런던 · 타이페이 101 · 상하이 월드 파이낸셜 센터 · 원 월드 트레이드 센터 · 롯데 타워 · 킹덤 센터 · 차이나 준 · 버즈 알 아랍입니다. 제가 수집한 15개의 원본 데이터는 이 링크에 있습니다. Skyscrapers 다음 단계는 (1) 정규화하여 모든 데이터를 regular grid 볼륨 내에 맞추고, (2) 변환하여 일관된 형식으로 만드는 것을 포함합니다. (1) 정규화: 일반적으로 기하학적 데이터를 학습에 사용할 때는 각각의 개별 객체에 대해 0과 1 사이의 값으로 정규화하고, 모델의 중심점을 원점(0, 0)으로 이동하여 정규화합니다. 즉, 모델의 가장 먼 점을 1로 설정합니다. 이러한 일반적인 정규화 방법을 사용하면 skyscrapers들의 상대적인 높이가 반영되지 않습니다. 따라서 이 프로젝트에서는 모든 skyscrapers 데이터 중 가장 높은 모델의 높이를 1로 설정하여 정규화했습니다. 정규화된 skyscrapers들 왼쪽부터, 가장 낮은 건물(거킨 런던) · 가장 높은 건물(원 월드 트레이드 센터) (2) 변환: DeepSDF 모델의 feed-forward network는 다음과 같은 구조를 가집니다. 다이어그램에서 "FC"로 표시된 8개의 fully connected layer로 구성되어 있습니다. 아래 그림에서 볼 수 있듯이, latent vector를 제외한 입력 X의 차원은 (x, y, z) 3으로 구성됩니다. DeepSDF 모델을 위한 feed-forward network 데이터 샘플 \( X \)는 \( (x, y, z) \)와 해당하는 레이블 \( s \)로 다음과 같이 구성됩니다: \( X := \{(x, s) : SDF(x) = s\} \) 추가적으로, 각 샘플에 latent vector를 할당하기 위한 클래스 번호가 필요합니다. 서론 부분에서 언급했듯이, latent vector는 건물의 형태를 표현하는 DNA 역할을 합니다. class SDFdataset(Dataset, Configuration): def __init__(self, data_path: str = Configuration. SAVE_DATA_PATH): self. sdf_dataset, self. cls_nums, self. cls_dict = self. _get_sdf_dataset(data_path=data_path) def __len__(self) -> int: return len(self. sdf_dataset) def __getitem__(self, index: int) -> Tuple[torch. Tensor]: xyz = self. sdf_dataset[index, :3] sdf = self. sdf_dataset[index, 3] cls = self. sdf_dataset[index, 4]. long() return xyz. to(self. DEVICE), sdf. to(self. DEVICE), cls. to(self. DEVICE) Implementing and training of DeepSDF model 위의 (2) 변환 부분에서 볼 수 있듯이, DeepSDF 모델의 feed-forward network는 다음과 같습니다. class SDFdecoder(nn. Module, Configuration): def __init__(self, cls_nums: int, latent_size: int = Configuration. LATENT_SIZE): super(). __init__() self. main_1 = nn. Sequential( nn. Linear(latent_size + 3, 512), nn. ReLU(True), nn. Linear(512, 512), nn. ReLU(True), nn. Linear(512, 512), nn. ReLU(True), nn. Linear(512, 512), nn. ReLU(True), nn. Linear(512, 512), ) self. main_2 = nn. Sequential( nn. Linear(latent_size + 3 + 512, 512), nn. ReLU(True), nn. Linear(512, 512), nn. ReLU(True), nn. Linear(512, 512), nn. ReLU(True), nn. Linear(512, 1), nn. Tanh(), ) self. latent_codes = nn. Parameter(torch. FloatTensor(cls_nums, latent_size)) self. latent_codes. to(self. DEVICE) self. to(self. DEVICE) def forward(self, i, xyz, cxyz_1=None): if cxyz_1 is None: cxyz_1 = torch. cat((self. latent_codes[i], xyz), dim=1) x1 = self. main_1(cxyz_1) # skip connection cxyz_2 = torch. cat((x1, cxyz_1), dim=1) x2 = self. main_2(cxyz_2) return x2 SDFdecoder 클래스는 다음과 같은 입력 인자를 가집니다: cls_nums는 skyscrapers의 수 latent_size는 latent vector의 차원 이 프로젝트에서는 cls_nums와 latent_size를 각각 15와 128로 사용했습니다. 따라서 latent vector를 위해 초기화된 인스턴스 변수(self. latent_codes)의 크기는 torch. Size([15, 128])입니다. 순전파에 사용된 skip connection 기법은 네트워크가 학습한 고수준 특징과 저수준 정보(XYZ 좌표)를 결합하여 SDF를 표현하는 복잡한 함수를 학습할 수 있게 합니다. 이제 학습 과정을 살펴보겠습니다. 모델은 150 에폭 동안 훈련되었으며, 전체 데이터(점의 수)는 64x64x64x15(=3932160)입니다. 이는 8:2 비율로 나뉘어 학습과 평가 과정에 사용되었습니다. 에폭당 평균 1000초가 소요되었습니다. 각 에폭 루프의 끝에서, 모델을 정성적으로 평가하기 위해 skyscrapers을 재구성하는 코드를 추가했습니다. 150 epochs 동안의 훈련 과정 위에서부터, 모델이 생성한 skyscrapers · 손실값 150 epochs 동안 모델을 훈련시킨 후, latent vector를 가진 15개의 skyscrapers에 대해 regular grid 점들의 각 점에서 SDF 값을 예측하여 3D 모델을 재구성했습니다. 아래 그림에서 왼쪽 열의 건물들은 모델에 의해 재구성된 것이고, 오른쪽 열의 건물들은 원본 3D 모델입니다. 모델이 생성한 skyscrapers와 원본 데이터 비교 모델이 정확한 디테일까지 재구성하지는 못하지만, 원본 skyscrapers과 유사한 건물을 적절하게 생성하는 것으로 보입니다. 이 모델 재구성 작업에서는 격자점 resolution으로 384x384x384(=56623104)를 사용했습니다. Synthesizing skyscrapers infinitely 마지막으로, skyscrapers들을 보간하거나 산술 연산을 사용하여 합성해보겠습니다. 다음 코드를 통해 초기 15개 건물의 latent vector를 합성하는 것으로 시작하여 다양한 형태의 무한한 데이터를 생성할 수 있습니다. 이번에는 이들을 합성하기 위해 128x128x128(=2097152) 격자점 resolution을 사용했습니다. def infinite_synthesis( sdf_decoder: SDFdecoder, save_dir: str, synthesis_count: int = np. inf, resolution: int = 128, map_z_to_y: bool = True, check_watertight: bool = True, ): synthesizer = Synthesizer() synthesized_latent_codes_npz = "infinite_synthesized_latent_codes. npz" synthesized_latent_codes_path = os. path. join(save_dir, synthesized_latent_codes_npz) os. makedirs(save_dir, exist_ok=True) synthesized_latent_codes = { "data": [ { "name": i, "index": i, "synthesis_type": "initial", "latent_code": list(latent_code. detach(). cpu(). numpy()), } for i, latent_code in enumerate(sdf_decoder. latent_codes) ] } if os. path. exists(synthesized_latent_codes_path): synthesized_latent_codes = { "data": list(np. load(synthesized_latent_codes_path, allow_pickle=True)["synthesized_data"]) } while len(synthesized_latent_codes["data"]) 초기 15개의 skyscrapers를 사용해서 450개의 skyscrapers 합성 첫 번째 행의 사각형으로 표시된 것이 초기 15개의 skyscrapers Tracking synthesized data skyscrapers을 합성하는 데 사용된 위의 함수가 데이터를 기록하기 때문에, 우리는 이 데이터를 사용하여 합성된 skyscrapers들의 부모를 확인할 수 있습니다. 이 과정은 그래프 기반 분석을 사용하여 주어진 합성 디자인에서 그 기원까지 역추적하는 것을 포함합니다. 이를 통해 우리는 특정 디자인이 어떻게 도출되었는지와 원본 모델이 합성 결과에 미치는 영향을 이해할 수 있습니다. 따라서 합성된 skyscrapers들을 추적하기 위해 BFS를 사용했습니다. 아래 그림들은 이러한 함수들의 적용을 보여주며, 초기 디자인에서 시작하여 다양한 합성 단계를 거쳐 복잡한 구조물로 완성되는 합성된 skyscrapers들의 추적과 시각화를 보여줍니다. 이는 합성된 skyscrapers들 내의 복잡한 관계와 의존성을 보여줍니다. Tracking synthesized skyscrapers Limitations and future works 이 프로젝트는 skyscrapers 디자인을 합성하는 Deep Generative AI의 잠재력을 보여주지만, 한계점도 있습니다. 따라서 다음과 같은 점들이 개선되어야 합니다: 디자인 평가: 모델이 skyscrapers 디자인을 합성할 수 있지만, 현재는 이러한 디자인의 품질을 자동으로 평가하는 기능이 구현되어 있지 않습니다. 세부 표현: 현재 모델의 주요 한계점 중 하나는 skyscrapers 모델의 복잡한 세부 사항을 정확하게 포착하지 못한다는 것입니다. 컴퓨팅 자원: 모델 훈련 과정, 특히 상세한 합성을 위한 높은 해상도에서는 상당한 컴퓨팅 성능과 시간이 필요합니다. 대화형 디자인 도구: 건축가가 직접 latent vector를 조작하거나 제약 조건과 선호도를 지정할 수 있는 대화형 도구를 개발하면 실제 디자인 응용에서 이 기술을 더 실용적이고 매력적으로 만들 수 있습니다. References https://en. wikipedia. org/wiki/Signed_distance_function https://github. com/fwilliams/point-cloud-utils https://scikit-image. org/docs/stable/auto_examples/edges/plot_marching_cubes. html https://xoft. tistory. com/47 https://velog. io/@qtly_u/Skip-Connection https://arxiv. org/pdf/1901. 05103. pdf https://github. com/facebookresearch/DeepSDF https://github. com/maurock/DeepSDF . .


Latent masses

Latent masses

Objectives Generative Adversarial Networks (GANs)는 예술 창작부터 딥페이크 비디오 생성에 이르기까지 수많은 분야에서 전례 없는 발전을 이루어냈습니다.

하지만 GANs의 잠재력은 2D 공간에만 국한되지 않습니다. 3D GANs의 개발과 응용은 특히 디자인 분야에서 새로운 가능성을 열었습니다. 이 프로젝트는 다음과 같은 목표를 가지고 디자인 분야에서의 3D GANs의 가능성을 탐구합니다: GANs의 기본 개념과 3D 확장에 대한 이해 실습을 통한 3D GANs의 능력과 특성 이해 제품 디자인, 건축 모델링, 가상 환경 제작에서 3D GANs의 활용 방안 검토 latent space를 시각화하고 조작하여 새롭고 혁신적인 디자인 생성 현재 3D GAN 모델의 한계점과 개선 가능한 영역 이해 Interpolation in latent space By the way, what is GANs(Generative Adversarial Networks)? 🧬 Generative Adversarial Networks, 줄여서 GANs는 주어진 데이터셋과 유사한 새로운 데이터를 생성하도록 설계된 인공지능 알고리즘입니다. GAN의 구조는 두 가지 주요 구성요소로 이루어져 있습니다: 1. Generator (생성자) 생성자의 역할은 가짜 데이터를 만드는 것입니다 latent space로부터 무작위 노이즈를 입력받아 데이터 샘플을 출력합니다 생성자의 주요 목표는 실제 데이터와 구분할 수 없는 데이터를 생성하는 것입니다 2. Discriminator (판별자) 판별자는 이진 분류기로 작동합니다 실제 데이터와 가짜 데이터를 구분하는 것이 목표입니다 판별자는 실제 데이터 샘플과 생성자가 만든 가짜 데이터를 모두 받아 이를 '진짜' 또는 '가짜'로 올바르게 분류하는 것이 임무입니다 아래 다이어그램은 이 과정을 보여주며, 생성자의 출력이 판별자에 의해 평가되어 loss가 발생하고 이를 통해 두 부분이 모두 개선되는 과정을 보여줍니다. Generative adversarial networks concept diagram 3D shape representations for the generative adversarial networks 1. Point cloud (포인트 클라우드) 포인트 클라우드는 공간상의 데이터 포인트 집합입니다. 3D 형상 표현에서 포인트 클라우드는 일반적으로 객체의 외부 표면을 표현하는데 사용되며 각 포인트는 (x, y, z) 좌표를 가집니다 특정 위상이나 그리드에 제한되지 않고 어떤 3D 형상도 표현할 수 있습니다 (유연성이 좋음) 포인트들이 서로 연결되어 있지 않아 표면이나 다른 특징을 추출하기 위해서는 추가 처리가 필요합니다 (연결성 부족) Shape representation for point cloud 2. Voxel Voxel은 2D 픽셀의 3D 버전입니다. Voxel 표현 방식은 3D 공간을 규칙적인 그리드로 나누며, 각 셀(또는 voxel)은 채워져 있거나 비어있을 수 있습니다 convolution과 같은 연산을 voxel 그리드에 쉽게 적용할 수 있습니다 (단순성) 세밀한 디테일을 표현하기 위해서는 매우 높은 해상도의 그리드가 필요하며, 이는 계산 비용이 많이 들 수 있습니다 (해상도 제한) 3. Mesh 3D mesh는 공간에서 3D 객체의 형태를 정의하는 정점, 모서리, 면으로 구성됩니다. 가장 일반적인 mesh 유형은 삼각형 mesh로, 삼각형을 사용하여 형태를 표현합니다 단순하거나 복잡한 기하학적 형태 모두 표현 가능합니다 (좋은 표현력) 점들이 어떻게 연결되어 있는지에 대한 정보를 제공하며, 이는 많은 응용 분야에서 유용합니다 (연속적인 표면 표현에 적합) subdivision이나 단순화와 같은 mesh 작업은 계산 비용이 많이 들 수 있습니다 (복잡성) Shape representation for mesh Simple implementation: A single sphere GAN 먼저, point cloud 데이터로 단일 구체를 생성하는 GAN 학습의 실제 구현을 진행해보겠습니다. 신경망을 구현하기 전에, Rhino로 모델링한 하나의 구체 형태의 point cloud를 파일에서 불러오는 것부터 시작합니다. 일반적으로, 정규화는 훈련 데이터가 다양한 크기의 유사한 객체로 구성되어 있거나 절대적인 크기가 중요하지 않은 경우에 특히 유용합니다. 우리의 단일 구체 데이터셋에서는 절대적인 크기가 중요하지 않습니다. 따라서 정규화를 진행하겠습니다. 구체는 numpy를 사용하여 다음과 같이 쉽게 정규화할 수 있습니다: class Normalize: def __call__(self, pointcloud): assert len(pointcloud. shape) == 2 norm_pointcloud = pointcloud - np. mean(pointcloud, axis=0) norm_pointcloud /= np. max(np. linalg. norm(norm_pointcloud, axis=1)) return norm_pointcloud 서로 다른 3D 모델들이 각각 다른 수의 정점을 가지고 있을 때, 각 모델에서 일정한 수의 포인트를 샘플링하는 것은 입력 크기를 균일하게 유지할 수 있게 합니다. 이는 일관된 입력 크기를 필요로 하는 신경망에 데이터를 공급할 때 매우 중요합니다. PointSampler와 관련된 코드는 다음 링크를 참조해 주시기 바랍니다. 포인트 클라우드로 표현된 구체 왼쪽부터, 원본 구체 · 무작위 샘플링된 구체 · 정규화 및 무작위 샘플링된 구체 이제 데이터 전처리가 완료되었고 모델 학습을 위한 준비가 되었습니다. 다음과 같이 간단한 생성자와 판별자로 구성된 모델을 구축하고 학습해보겠습니다: class Generator(nn. Module): def __init__(self, input_dim=3, output_dim=3, hidden_dim=128): super(Generator, self). __init__() self. fc1 = nn. Linear(input_dim, hidden_dim) self. fc2 = nn. Linear(hidden_dim, hidden_dim) self. fc3 = nn. Linear(hidden_dim, hidden_dim) self. fc4 = nn. Linear(hidden_dim, output_dim) def forward(self, x): x = torch. relu(self. fc1(x)) x = torch. relu(self. fc2(x)) x = torch. relu(self. fc3(x)) x = torch. tanh(self. fc4(x)) return x class Discriminator(nn. Module): def __init__(self, input_dim=3, hidden_dim=128): super(Discriminator, self). __init__() self. fc1 = nn. Linear(input_dim, hidden_dim) self. fc2 = nn. Linear(hidden_dim, hidden_dim) self. fc3 = nn. Linear(hidden_dim, hidden_dim) self. fc4 = nn. Linear(hidden_dim, 1) def forward(self, x): x = torch. relu(self. fc1(x)) x = torch. relu(self. fc2(x)) x = torch. relu(self. fc3(x)) x = torch. sigmoid(self. fc4(x)) return x 생성자, 판별자, 데이터, 학습 과정 등에 대한 자세한 내용이 포함된 전체 코드는 다음 링크에서 확인할 수 있습니다. 또한, matplotlib을 사용하여 시각화한 학습 과정은 아래에서 확인할 수 있습니다. loss 상태 그래프를 살펴보면, 약 2700 epoch 부근에서 구체가 생성되기 시작하는 것을 확인할 수 있습니다. 이 시점 이후로는 loss 값의 진동이 멈추고 수렴하는 그래프를 보여줍니다. 단일 구체 GAN의 학습 과정 왼쪽부터, loss 상태 · 생성된 포인트 클라우드 구체 Implementing MassGAN 🧱 위에서 우리는 기본 구현과 단일 구체 GAN을 통해 GANs에 대한 이해를 얻었습니다. 이제 이 이해를 바탕으로, 건축가들이 설계한 건물(Masses)로 모델을 학습시키고 가짜 Masses를 생성하는 생성자를 만들어보겠습니다. The procedure for the implementation of MassGAN follows the below processes: 데이터셋 준비 및 전처리 모델 구현 및 학습 생성자 평가 및 latent space 탐색 Preparation and preprocessing of the dataset 모델 학습을 위해 여러 유명 건축가들이 설계한 건물 모델들을 수집했습니다. 아래 그림은 제가 수집한 모델링 데이터의 실제 건물들을 보여줍니다. 복셀 형태의 건물들 왼쪽부터, RED7(MVRDV architects) · 79 and Park(BIG architects) · Mountain dwelling(BIG architects) 위에서 언급한 건물들은 복셀 형태의 구성이라는 공통적인 특징을 가지고 있습니다. 앞서 설명했듯이, 우리는 GANs와 관련된 세 가지 3D 형상 표현 방식에 대해 배웠습니다. 복셀 형태 표현의 주요 한계점은 고해상도 표현의 어려움에 있습니다. 하지만 건축 디자인 분야에서는 이러한 제약이 오히려 기회가 될 수 있습니다. 복셀 형태는 건축 분야에서 널리 사용되며, 이러한 형태의 고해상도 표현이 반드시 필요한 것은 아닙니다. 따라서, 우리는 적절한 해상도의 복셀 데이터를 사용하여 위와 같은 매스를 생성하는 생성 모델을 만들 것입니다. 먼저, 모델링 데이터를 사용하여 모델을 학습시키기 위해서는 데이터 구조를 . obj 형식에서 더 적합한 . binvox 형식으로 변환해야 합니다. . binvox 형식은 데이터를 이진 복셀 그리드 구조로 표현하며, 실제 있는 영역은 True (1), 비어있는 영역은 False (0)로 표시합니다. 아래에서 . binvox 형식으로 전처리된 예시를 살펴보겠습니다. 이진 복셀 그리드 표현 왼쪽부터, 주어진 구체 · 복셀화된 구체 · 이진 복셀 그리드(9번째 복셀 그리드) 이는 제 포스팅의 Voxelate 부분에서 이전에 언급되었습니다 위의 이진 복셀 그리드에서 볼 수 있듯이, 비어있는 영역은 0으로, 실제 있는 영역은 1로 표시됩니다. . binvox 형식으로의 전처리에 대한 모든 상세 코드는 다음 링크에서 확인할 수 있으며, 이를 활용하여 아래 6개의 모델을 32 x 32 x 32 해상도로 전처리했습니다. binvox를 활용하여 이진 복셀 그리드로 전처리된 데이터 위쪽부터 왼쪽으로, 79 and Park · Lego tower, RED7 아래쪽부터 왼쪽으로, Vancouver house · CCTV Headquarter, Mountain dwelling Implementation of models and training them 이제 데이터 수집과 전처리 과정을 완료했습니다. 이어서 생성자와 판별자의 구현을 시작할 준비가 되었습니다. 따라서 여러 3D 생성 모델이 구현된 GitHub 저장소들을 참고하여 gradient penalty가 적용된 DCGAN(WGAN)을 구현했습니다. 모델 정의에 대한 전체 코드는 다음 링크에서 확인할 수 있습니다: massGAN/model. py class Generator(nn. Module, Config): def __init__(self, z_dim, init_out_channels: int = None): super(). __init__() out_channels_0 = self. GENERATOR_INIT_OUT_CHANNELS if init_out_channels is None else init_out_channels out_channels_1 = int(out_channels_0 / 2) out_channels_2 = int(out_channels_1 / 2) self. main = nn. Sequential( nn. ConvTranspose3d(z_dim, out_channels_0, kernel_size=4, stride=1, padding=0, bias=False), nn. BatchNorm3d(out_channels_0), nn. ReLU(True), nn. ConvTranspose3d(out_channels_0, out_channels_1, kernel_size=4, stride=2, padding=1, bias=False), nn. BatchNorm3d(out_channels_1), nn. ReLU(True), nn. ConvTranspose3d(out_channels_1, out_channels_2, kernel_size=4, stride=2, padding=1, bias=False), nn. BatchNorm3d(out_channels_2), nn. ReLU(True), nn. ConvTranspose3d(out_channels_2, 1, kernel_size=4, stride=2, padding=1, bias=False), nn. Sigmoid() ) self. to(self. DEVICE) def forward(self, x): return self. main(x) class Discriminator(nn. Module, Config): def __init__(self, init_out_channels: int = None): super(). __init__() out_channels_0 = self. DISCRIMINATOR_INIT_OUT_CHANNELS if init_out_channels is None else init_out_channels out_channels_1 = out_channels_0 * 2 out_channels_2 = out_channels_1 * 2 self. main = nn. Sequential( nn. Conv3d(1, out_channels_0, kernel_size=4, stride=2, padding=1, bias=False), nn. LeakyReLU(0. 2, inplace=True), nn. Conv3d(out_channels_0, out_channels_1, kernel_size=4, stride=2, padding=1, bias=False), nn. BatchNorm3d(out_channels_1), nn. LeakyReLU(0. 2, inplace=True), nn. Conv3d(out_channels_1, out_channels_2, kernel_size=4, stride=2, padding=1, bias=False), nn. BatchNorm3d(out_channels_2), nn. LeakyReLU(0. 2, inplace=True), nn. Conv3d(out_channels_2, 1, kernel_size=4, stride=1, padding=0, bias=False), nn. Sigmoid() ) self. to(self. DEVICE) def forward(self, x): return self. main(x). view(-1, 1). squeeze(1) 또한 모델 학습, 평가, 저장을 포함한 모델 관리를 위해 MassganTrainer를 정의했습니다. 이 과정에서 학습 단계에서 발생하는 모든 문제를 모니터링했습니다. 기록된 결과는 아래와 같습니다: 0에서 20000 epoch까지 200 epoch마다 시각화된 학습 과정 위에서부터, loss 상태 · 모델 학습 중 생성된 매스들 이전에 학습했던 단일 구체 GAN과는 달리, MassGAN은 데이터의 복잡성으로 인해 loss 값이 하나의 지점으로 수렴하지 않습니다. 그럼에도 불구하고, 학습의 초기와 최종 단계를 비교해보면 loss 값이 낮은 범위 내에서 진동하는 것을 관찰할 수 있습니다. 더욱이, 모니터링된 가짜 매스들을 관찰해보면 점진적으로 실제 매스의 형태에 근접해가는 것을 확인할 수 있습니다. Evaluating generator, and exploration for the latent spaces 학습률, 배치 크기, 노이즈 차원 등과 같은 모델 학습을 위한 매개변수는 다음과 같이 사용되었습니다: class ModelConfig: """Configuration related to the GAN models """ DEVICE = "cpu" if torch. cuda. is_available(): DEVICE = "cuda" SEED = 777 GENERATOR_INIT_OUT_CHANNELS = 256 DISCRIMINATOR_INIT_OUT_CHANNELS = 64 EPOCHS = 20000 LEARNING_RATE = 0. 0001 BATCH_SIZE = 6 BATCH_SIZE_TO_EVALUATE = 6 Z_DIM = 128 BETAS = (0. 5, 0. 999) LAMBDA_1 = 10 LOG_INTERVAL = 200 이제 해당 ModelConfig로 학습된 모델을 불러와서 평가해보겠습니다. GAN에서는 loss 상태와 같은 정량적인 모델 평가도 중요하지만, 생성자가 생성한 데이터를 정성적으로 평가하는 것도 모델 평가에 효과적입니다. 다음 그림들은 evaluate 함수를 통해 MassGAN 모델이 생성한 매스들입니다. MassGAN 모델이 생성한 매스들 전반적으로 괜찮은 데이터를 생성하는 것으로 보입니다. 이어서, 생성자가 만든 매스들 중 일부를 선택하여 그들 사이의 잠재 공간에서의 보간을 관찰해보겠습니다. 잠재 공간에서의 보간 왼쪽부터, RED7 · 보간 · CCTV 본사 잠재 공간에서의 보간 왼쪽부터, 레고 타워 · 보간 · Mountain dwelling 잠재 공간에서의 보간 왼쪽부터, Vancouver house · 보간 · 레고 타워 References https://medium. com/hackernoon/latent-space-visualization-deep-learning-bits-2-bd09a46920df https://github. com/ChrisWu1997/SingleShapeGen https://github. com/znxlwm/pytorch-MNIST-CelebA-GAN-DCGAN https://developer-ping9. tistory. com/108 . .


Drafting with AI

Drafting with AI

Stable diffusion Stable Diffusion은 2022년에 출시된 딥러닝 기반의 text-to-image 모델입니다. 주로 텍스트 설명을 기반으로 상세한 이미지를 생성하는 데 사용되며, inpainting, outpainting, 텍스트 프롬프트로 안내되는 image-to-image 변환과 같은 다른 작업에도 적용될 수 있습니다. An image generated by Stable Diffusion based on the text prompt: "a photograph of an astronaut riding a horse" 이 AI를 사용하여 이미지를 생성하면 몇 줄의 텍스트만으로도 쉽고 빠르게 그럴듯한 렌더링 이미지를 얻을 수 있습니다.

이것이 건축 분야에서 어떻게 활용될 수 있는지 살펴보겠습니다. Application in the field of architecture 건축 설계 분야에서 자주 사용되는 여러 rendering engine이 있지만, 좋은 렌더링을 위해서는 정밀한 모델링이 필요합니다. 디자인 프로젝트 시작 단계에서는 매우 간단한 모델이나 스케치를 AI에 입력하고, prompt를 입력하여 우리가 구상하는 것에 대한 대략적인 아이디어를 얻을 수 있습니다. 먼저, 간단한 예시를 살펴보겠습니다. 아래 예시는 Rhino의 GhPython 환경에서 rhino viewport를 캡처한 다음 ControlNet of the stable diffusion API를 사용하여 원하는 방향으로 이미지를 렌더링하는 예시입니다. Demo for using stable diffusion in Rhino environment Prompt: Colorful basketball court, Long windows, Sunlight 먼저, rhino viewport를 캡처하기 위한 코드를 작성해야 합니다. 현재 *. gh 파일이 위치한 경로에 저장되도록 만들었습니다. def capture_activated_viewport( self, save_name="draft. png", return_size=False ): """ Capture and save the currently activated viewport to the location of the current *. gh file path """ save_path = os. path. join(CURRENT_DIR, save_name) viewport = Rhino. RhinoDoc. ActiveDoc. Views. ActiveView. CaptureToBitmap() viewport. Save(save_path, Imaging. ImageFormat. Png) if return_size: return save_path, viewport. Size return save_path 다음으로, stable-diffusion-webui repository를 clone한 후 local API server를 실행해야 합니다. local API server 실행에 필요한 설정은 이 AUTOMATIC1111/stable-diffusion-webui repository를 참조하세요. 모든 설정이 완료되면 API 호출을 통해 원하는 method를 요청할 수 있습니다. AUTOMATIC1111/stable-diffusion-webui/wiki/API의 API 가이드를 참조했습니다. Drafting Python은 HTTP를 사용하기 위한 urllib이라는 내장 모듈을 제공합니다. GhPython에서 사용된 API 요청 코드는 이 링크에서 확인할 수 있습니다. import os import json import Rhino import base64 import urllib2 import scriptcontext as sc import System. Drawing. Imaging as Imaging class D2R: """Convert Draft to Rendered using `stable diffusion webui` API""" def __init__( self, prompt, width=512, height=512, local_url="http://127. 0. 0. 1:7860" ): self. prompt = prompt self. width = width self. height = height self. local_url = local_url (. . . ) def render(self, image_path, seed=-1, steps=20, draft_size=None): payload = { "prompt": self. prompt, "negative_prompt": "", "resize_mode": 0, "denoising_strength": 0. 75, "mask_blur": 36, "inpainting_fill": 0, "inpaint_full_res": "true", "inpaint_full_res_padding": 72, "inpainting_mask_invert": 0, "initial_noise_multiplier": 1, "seed": seed, "sampler_name": "Euler a", "batch_size": 1, "steps": steps, "cfg_scale": 4, "width": self. width if draft_size is None else draft_size. Width, "height": self. height if draft_size is None else draft_size. Height, "restore_faces": "false", "tiling": "false", "alwayson_scripts": { "ControlNet": { "args": [ { "enabled": "true", "input_image": self. _get_decoded_image_to_base64(image_path), "module": self. module_pidinet_scribble, "model": self. model_scribble, "processor_res": 1024, }, ] } } } request = urllib2. Request( url=self. local_url + "/sdapi/v1/txt2img", data=json. dumps(payload), headers={'Content-Type': 'application/json'} ) try: response = urllib2. urlopen(request) response_data = response. read() rendered_save_path = os. path. join(CURRENT_DIR, "rendered. png") converted_save_path = os. path. join(CURRENT_DIR, "converted. png") response_data_jsonify = json. loads(response_data) used_seed = json. loads(response_data_jsonify["info"])["seed"] used_params = response_data_jsonify["parameters"] for ii, image in enumerate(response_data_jsonify["images"]): if ii == len(response_data_jsonify["images"]) - 1: self. _save_base64_to_png(image, converted_save_path) else: self. _save_base64_to_png(image, rendered_save_path) return ( rendered_save_path, converted_save_path, used_seed, used_params ) except urllib2. HTTPError as e: print("HTTP Error:", e. code, e. reason) response_data = e. read() print(response_data) return None if __name__ == "__main__": CURRENT_FILE = sc. doc. Path CURRENT_DIR = "\\". join(CURRENT_FILE. split("\\")[:-1]) prompt = ( """ Interior view with sunlight, Curtain wall with city view Colorful Sofas, Cushions on the sofas Transparent glass Table, Fabric stools, Some flower pots """ ) d2r = D2R(prompt=prompt) draft, draft_size = d2r. capture_activated_viewport(return_size=True) rendered, converted, seed, params = d2r. render( draft, seed=-1, steps=50, draft_size=draft_size ) Physical model From the left, Drfat view · Rendered view Prompt: Isometric view, River, Trees, 3D printed white model with illumination Interior view From the left, Drfat view · Rendered view Prompt: Interior view with sunlight, Curtain wall with city view, Colorful sofas, Cushions on the sofas, Transparent glass table, Fabric stools, Some flower pots Floor plan From the left, Drfat view · Rendered view Prompt: Top view, With sunlight and shadows, Some flower pots, Colorful furnitures, Conceptual image Skyscrappers in the city From the left, Drfat view · Rendered view Prompt: Skyscrapers in the city, Night scene with many stars in the sky, Neon sign has shine brightly, Milky way in the sky Contour From the left, Drfat view · Rendered view Prompt: Bird's eye view, Colorful master plan, Bright photograph, River Gabled houses From the left, Drfat view · Rendered view Prompt: Two points perspective, White clouds, Colorful houses, Sunlight Kitchen From the left, Drfat view · Rendered view Prompt: Front view, Kitchen with black color interior, Illuminations . .


K-Rooms clusters

K-Rooms clusters

K-Means clustering The K-Means clustering 알고리즘은 주어진 데이터를 K number로 군집화하는 알고리즘입니다. 그리고 각 cluster 간의 거리 차이를 최소화하는 방식으로 작동합니다. 이 알고리즘은 Unsupervised-Learning의 한 종류이며 레이블이 없는 입력 데이터에 label을 부여하는 역할을 합니다.

K-Means 알고리즘은 clustering 방법 중 partitioning 방법에 속합니다. Partitioning 방법은 주어진 데이터를 여러 개의 partition으로 나누는 분할 방식입니다. 예를 들어, n개의 데이터 객체가 입력된다고 가정해보겠습니다. 이때 partitioning 방법은 주어진 데이터를 N보다 작은 K개의 그룹으로 나누며, 이때 각 그룹은 cluster를 형성합니다. 즉, 하나의 데이터를 하나 이상의 데이터 객체로 분할하는 것입니다. K-means clustering, divided by 10 Implementation K-Means clustering의 작동 과정은 다음 5단계로 구성됩니다: K(cluster의 개수)를 선택하고 데이터 입력 cluster의 초기 centroid를 무작위로 설정 가장 가까운 centroid를 기준으로 각 cluster에 데이터 할당 cluster의 새로운 centroid를 재계산하고 4단계 재실행 centroid의 위치가 더 이상 업데이트되지 않으면 종료 위의 단계를 기반으로 K-Means 알고리즘을 구현해보겠습니다. 먼저, KMeans 객체를 정의합니다. 입력값으로 분할할 cluster의 개수(K), points cloud, 그리고 centroid 업데이트 반복 횟수인 iteration_count를 받습니다. class KMeans(PointHelper): def __init__(self, points=None, k=3, iteration_count=20, random_seed=0): """KMeansCluster simple implementation using Rhino Geometry Args: points (Rhino. Geometry. Point3d, optional): Points to classify. Defaults to None. if points is None, make random points k (int, optional): Number to classify. Defaults to 3. iteration_count (int, optional): Clusters candidates creation count. Defaults to 20. random_seed (int, optional): Random seed number to fix. Defaults to 0. """ PointHelper. __init__(self) self. points = points self. k = k self. iteration_count = iteration_count self. threshold = 0. 1 import random # pylint: disable=import-outside-toplevel self. random = random self. random. seed(random_seed) 다음으로, 주어진 points cloud에서 K개만큼 무작위로 선택하여 K개수만큼 cluster의 centroid를 초기화합니다. 초기 centroid 설정이 완료되면, 각 centroid와 주어진 points cloud 사이의 거리를 계산하고, 가장 가까운 거리에 있는 cluster에 데이터를 할당합니다. 이제 모든 cluster의 centroid를 업데이트해야 합니다. 이를 위해 초기 cluster들(points cloud clusters)의 centroid를 계산 해야 합니다. 마지막으로, 업데이트된 centroid와 이전 centroid 사이의 거리를 계산합니다. 이 거리가 더 이상 변화가 없다면 종료합니다. 그렇지 않다면, 위에서 설명한 주요 과정들을 반복합니다. def kmeans(self, points, k, threshold): """Clusters by each iteration Args: points (Rhino. Geometry. Point3d): Initialized given points k (int): Initialized given k threshold (float): Initialized threshold Returns: Tuple[List[List[Rhino. Geometry. Point3d]], List[List[int]]]: Clusters by each iteration, Indices by each iteration """ centroids = self. random. sample(points, k) while True: clusters = [[] for _ in centroids] indices = [[] for _ in centroids] for pi, point in enumerate(points): point_to_centroid_distance = [ point. DistanceTo(centroid) for centroid in centroids ] nearest_centroid_index = point_to_centroid_distance. index( min(point_to_centroid_distance) ) clusters[nearest_centroid_index]. append(point) indices[nearest_centroid_index]. append(pi) shift_distance = 0. 0 for ci, current_centroid in enumerate(centroids): if len(clusters[ci]) == 0: continue updated_centroid = self. get_centroid(clusters[ci]) shift_distance = max( updated_centroid. DistanceTo(current_centroid), shift_distance, ) centroids[ci] = updated_centroid if shift_distance 이제 위의 코드에서 K를 설정하여 point cluster들을 얻을 수 있습니다. 그리고 K-Means의 상세 코드는 이 링크에서 확인할 수 있습니다. Implemented K-means clustering, divided by 10 From the left, Given Points · Result clusters K-Rooms clusters K-Means 알고리즘은 위에서 설명한 것처럼 주어진 점들을 K개수만큼 군집화하는 데 사용됩니다. 이를 활용하면 주어진 건축 경계를 K개수만큼 분할할 수 있습니다. 이는 K개수의 방을 얻을 수 있다는 것을 의미합니다. 코드를 작성하기 전에 다음 순서를 설정합니다. 입력값으로 건물 외벽선(닫힌 선)과 분할할 방의 개수를 받습니다 oriented bounding box를 생성하고 grid를 생성합니다 grid 중심점들을 위에서 구현한 K-Means clustering 알고리즘에 삽입합니다 군집화된 grid 중심점들의 인덱스를 기반으로 grid들을 병합합니다 core에서 각 방까지의 최단 경로를 탐색하고 복도를 생성합니다 이제 K-Rooms cluster 알고리즘을 구현해보겠습니다. KRoomsClusters 클래스를 다음과 같이 정의하고 1단계에서 정의한 것을 입력값으로 받습니다. 그리고 KMeans 클래스를 상속받습니다. class KRoomsCluster(KMeans, PointHelper, LineHelper, ConstsCollection): """ To use the inherited moduels, refer the link below. https://github. com/PARKCHEOLHEE-lab/GhPythonUtils """ def __init__(self, floor, core, hall, target_area, axis=None): self. floor = floor self. core = core self. hall = hall self. sorted_hall_segments = self. get_sorted_segment(self. hall) self. target_area = target_area self. axis = axis KMeans. __init__(self) PointHelper. __init__(self) LineHelper. __init__(self) ConstsCollection. __init__(self) 다음으로, grid를 생성하기 위해 OBB(oriented bounding box)를 생성합니다. OBB는 grid의 xy축을 정의하기 위한 것입니다. (OBB 생성 알고리즘을 보려면 이 링크를 참조하세요) 그런 다음, grid와 grid centroid를 생성하고, grid의 중심점들과 K값을 넣어 clustering indices를 추출한 후, 같은 cluster 내에 존재하는 모든 grid들을 병합합니다. K-Rooms clustering key process, divided by 5 From the left, Grid and centroids creation · Divided rooms def _gen_grid(self): self. base_rectangles = [ self. get_2d_offset_polygon(seg, self. grid_size) for seg in self. sorted_hall_segments[:2] ] counts = [] planes = [] for ri, base_rectangle in enumerate(self. base_rectangles): x_vector = ( rg. AreaMassProperties. Compute(base_rectangle). Centroid - rg. AreaMassProperties. Compute(self. hall). Centroid ) y_vector = copy. copy(x_vector) y_transform = rg. Transform. Rotation( math. pi * 0. 5, rg. AreaMassProperties. Compute(base_rectangle). Centroid, ) y_vector. Transform(y_transform) base_rectangle. Translate( ( self. sorted_hall_segments[0]. PointAtStart - self. sorted_hall_segments[0]. PointAtEnd ) / 2 ) base_rectangle. Translate( self. get_normalized_vector(x_vector) * -self. grid_size / 2 ) anchor = rg. AreaMassProperties. Compute(base_rectangle). Centroid plane = rg. Plane( origin=anchor, xDirection=x_vector, yDirection=y_vector, ) x_proj = self. get_projected_point_on_curve( anchor, plane. XAxis, self. obb ) x_count = ( int(math. ceil(x_proj. DistanceTo(anchor) / self. grid_size)) + 1 ) y_projs = [ self. get_projected_point_on_curve( anchor, plane. YAxis, self. obb ), self. get_projected_point_on_curve( anchor, -plane. YAxis, self. obb ), ] y_count = [ int(math. ceil(y_proj. DistanceTo(anchor) / self. grid_size)) + 1 for y_proj in y_projs ] planes. append(plane) counts. append([x_count] + y_count) x_grid = [] for base_rectangle, count, plane in zip( self. base_rectangles, counts, planes ): xc, _, _ = count for x in range(xc): copied_rectangle = copy. copy(base_rectangle) vector = plane. XAxis * self. grid_size * x copied_rectangle. Translate(vector) x_grid. append(copied_rectangle) y_vectors = [planes[0]. YAxis, -planes[0]. YAxis] y_counts = counts[0][1:] all_grid = [] + x_grid for rectangle in x_grid: for y_count, y_vector in zip(y_counts, y_vectors): for yc in range(1, y_count): copied_rectangle = copy. copy(rectangle) vector = y_vector * self. grid_size * yc copied_rectangle. Translate(vector) all_grid. append(copied_rectangle) union_all_grid = rg. Curve. CreateBooleanUnion(all_grid, self. TOLERANCE) for y_count, y_vector in zip(y_counts, y_vectors): for yc in range(1, y_count): copied_hall = copy. copy(self. hall) copied_hall. Translate( ( self. sorted_hall_segments[0]. PointAtStart - self. sorted_hall_segments[0]. PointAtEnd ) / 2 ) vector = y_vector * self. grid_size * yc copied_hall. Translate(vector) all_grid. extend( rg. Curve. CreateBooleanDifference( copied_hall, union_all_grid ) ) self. grid = [] for grid in all_grid: for boundary in self. boundaries: tidied_grid = list( rg. Curve. CreateBooleanIntersection( boundary. boundary, grid, self. TOLERANCE ) ) self. grid. extend(tidied_grid) self. grid_centroids = [ rg. AreaMassProperties. Compute(g). Centroid for g in self. grid ] 마지막으로, 각 방을 복도로 연결하면 완성입니다! (최단 경로 알고리즘을 위해 Dijkstra 알고리즘을 구현했습니다. 구현 내용은 다음 링크에서 확인할 수 있습니다. 다음 기회에 이에 대한 포스트를 작성하도록 하겠습니다. ) Corridor creation Limitation of K-Rooms clusters 우리가 구현한 K-Rooms Clusters 알고리즘은 아직 불완전합니다. 위에서 보여진 이미지들은 K값이 작을 때 유효한 결과입니다. K값이 증가하고 방의 개수가 증가할 때, 건축적으로 적절한 형태가 도출되지 않는 문제가 발생합니다. 아래 예시와 같습니다: Improper shapes, divided by 13 From the left, K-Rooms · Result corridor 이 문제를 해결하기 위해 앞으로 해야 할 일은 적절한 형태를 정의하고 post-processing으로 이를 완화하는 로직을 만드는 것입니다. 그리고 K-Rooms clusters의 전체 코드는 이 링크에서 확인할 수 있습니다. . .


IFC format study

IFC format study

Objectives IFC(Industry Foundation Classes) 포맷의 구조와 특성을 분석하여 건설 정보 모델링에서의 활용 가능성을 평가하는 것이다. 구체적으로는: IFC 포맷의 데이터 구조와 저장 체계를 이해하고, 특히 건축 요소들 간의 관계성 표현 방식을 파악한다.

ifcopenshell-python과 blender-bim 등 주요 IFC 처리 도구들의 특성과 활용 방법을 분석한다. 기존 랜드북 렌더링 작업 프로세스와 비교하여 IFC 기반 워크플로우의 실무 적용 가능성과 한계점을 검토한다. 이를 통해 IFC 포맷이 제공하는 데이터 관리 및 상호운용성 측면의 이점을 파악하고, 향후 건설 정보 모델링 시스템 개발에 활용할 수 있는 인사이트를 도출하고자 한다. What is IFC? IFC (Industry Foundation Classes)는 건축, 건물 및 건설 산업 데이터를 설명하기 위한 CAD 데이터 교환 데이터 스키마이다. 이는 단일 공급업체 또는 공급업체 그룹에 의해 제어되지 않는 플랫폼 중립적인 개방형 데이터 스키마 사양이다. 소프트 웨어간 정보 공유를 위해서 만들어졌으며 국제표준기반으로 buildingSMART에 의해 개발되고 있다. https://en. wikipedia. org/wiki/Industry_Foundation_Classes https://m. blog. naver. com/PostView. naver?isHttpsRedirect=true&blogId=silvury14&logNo=10177489179 https://en. wikipedia. org/wiki/BuildingSMART How to Read IFC UI - blender console - blenderBIM (ifcopenshell - python) Loading and Manipulating IFC Files in Blender 예시 파일 https://github. com/myoualid/ifc-101-course/tree/main/episode-01/Resources 예시 파일 IFC 형식의 구조와 관계들 IFC의 spatial structure Project aggregates Site aggregates Facility: building( bridge, road, railway) aggregates Building storey ( 층 ) Contains Products (building elements) IFC의 spatial structure - 예시 파일 IFC의 spatial structure 다이어그램램 file = ifcopenshell. open(r"C:\Users\MAD_SCIENTIST_21. ifc") project = file. by_type("IfcProject")[0] site = project. IsDecomposedBy[0]. RelatedObjects[0] building = site. IsDecomposedBy[0]. RelatedObjects[0] building_storeys = building. IsDecomposedBy[0]. RelatedObjects for building_storey in building_storeys: print(building_storey) sous_sol = building_storeys[0] sous_sol. get_info() rel_contained_structure = sous_sol. ContainsElements[0] rel_contained_structure. RelatedElements for element in rel_contained_structure. RelatedElements: print(element) bleder python script로 다음 코드를 이용하여 출력한 요소들은 blender에서 우측 패널에 보이는 object들과 동일하다. (IFC의 spatial structure - 예시 파일 그림의 패널) 출력 결과 IFC Class Inheritance Structure and Properties 크게 rooted class, non-rooted class로 나뉜다. rooted class ifcroot라는 class로부터 상속 cardinality: 필수인지 아닌지 나머지는 optional인데 track item 잘 하기 위한 용도 3가지 subclass ifcObjectDefinition ifcPropertyDefinition ifcRelationship attribute inheritance 살펴펴보면, 어떤 부모로부터 어떤 attribute를 상속한 것인지 볼 수 있다. rooted class code object 좌클릭, → object property 누르면 ifc 속성들을 보고 수정할 수 있다. ifc properties ifc construction type 은 relation인데 클릭하면 같은 타입을 다 볼 수 있다. ifc construction type Attributes and Property Sets property set이 매우 중요한데 schema에 주어진 template 대로 이 부분이 없으면, 지정한 custom으로 add 된다. inherited pset이 있는데, wall type으로부터 온다(quantity set은 수치에 대한 속성). attribute와 property set code 위의 내용들을 ifcopenshell 코드로 접근해보자. rooted_entities = file. by_type("IfcRoot") ifc_building_element_entities = set() for entity in rooted_entities: if entity. is_a("IfcBuildingElement"): ifc_building_element_entities. add(entity. is_a()) my_wall = file. by_id("1K9fMEc5bCUxo4LlWWYA9b") my_wall. Description my_wall. OwnerHistory my_wall. Name my_wall. GlobalId my_wall. Description my_wall. Tag my_wall. PredefinedType my_wall. IsTypedBy[0]. RelatingType # similar construction type 보는 것과 유사한 기능. my_wall. IsTypedBy[0]. RelatedObjects my_wall. is_a() my_wall. is_a("IfcRoot") my_wall. is_a("IfcBuildingElement") my_wall. is_a("IfcProduct") my_wall. is_a("IfcWall") Property Sets and Quantity Sets 위에서 attribute들을 다뤘는데, 이걸로는 정보가 충분히 표현되지 않는다. IFC에서는 property set(Pset)과 quantity set(Qset)을 통해 더 많은 정보를 표현할 수 있다. Property Set (Pset) 객체의 추가적인 특성을 정의하는 속성들의 집합 표준 Pset: buildingSMART에서 정의한 표준 속성 집합 (예: Pset_WallCommon) Custom Pset: 사용자가 필요에 따라 정의할 수 있는 속성 집합 주요 속성 타입: Single Value: 문자열, 숫자, 불리언 등의 단일 값 Enumerated Value: 미리 정의된 값 목록에서 선택 Bounded Value: 상한값과 하한값이 있는 수치 List Value: 여러 값의 목록 Table Value: 2차원 데이터 구조 IfcWall의 isDefinedBy member에 있는 여러 객체 중 IfcRelDefinedsByProperties라는 객체가 있는데, 그 객체의 RelatingPropertyDefinition member가, IfcPropertySet을 인 것들을 대상으로 loop를 돈다. 그 pset의 HasProperties안에는 여러 IfcPropertySingleValue들이 있고, Name과 NominalValue등을 가지고 있다. 이걸 props에 dict로 저장 → props들은 psets에 저장. from blenderbim. bim. ifc import IfcStore file = IfcStore. file path = IfcStore. path my_wall. IsDefinedBy my_wall. IsDefinedBy[0]. RelatingPropertyDefinitio my_wall. IsDefinedBy[1]. RelatingPropertyDefinitio my_wall. IsDefinedBy[2]. RelatingPropertyDefinitio my_wall. IsDefinedBy[3]. RelatingPropertyDefinitio pset = my_wall. IsDefinedBy[3]. RelatingPropertyDefinition pset. HasProperties[0]. Name pset. HasProperties[0]. NominalValue. wrappedValue psets = {} if my_wall. IsDefinedBy: for relationship in my_wall. IsDefinedBy: if relationship. is_a("IfcRelDefinesByProperties") and relationship. RelatingPropertyDefinition. is_a("IfcPropertySet"): pset = relationship. RelatingPropertyDefinition props = {} for property in pset. HasProperties: if property. is_a("IfcPropertySingleValue"): props[property. Name] = property. NominalValue. wrappedValue psets[pset. Name] = props print(pset. Name + " was added!") psets 이걸 다 직접 해야하나? → util에 있음. 더 정교한 기능으로 구현되어 있다. import ifcopenshell. util. element ifcopenshell. util. element. get_psets(my_wall, psets_only=True) Quantity Set (Qset) 객체의 물리적 수량 정보를 포함하는 집합 주요 수량 타입: Length (길이) Area (면적) Volume (부피) Weight (무게) Count (개수) Time (시간) my_wall. IsDefinedBy[0]. RelatingPropertyDefinition. is_a("IfcQuantitySet") my_wall. IsDefinedBy[0]. RelatingPropertyDefinition. Quantities my_wall. IsDefinedBy[0]. RelatingPropertyDefinition. Quantities[0]. Name my_wall. IsDefinedBy[0]. RelatingPropertyDefinition. Quantities[0]. LengthValue my_wall. IsDefinedBy[0]. RelatingPropertyDefinition. Quantities[3]. AreaValue my_wall. IsDefinedBy[0]. RelatingPropertyDefinition. Quantities[3][3] qsets = {} if my_wall. IsDefinedBy: for relationship in my_wall. IsDefinedBy: if relationship. is_a("IfcRelDefinesByProperties") and relationship. RelatingPropertyDefinition. is_a("IfcQuantitySet"): qset = relationship. RelatingPropertyDefinition quantities = {} for quantity in qset. Quantities: if quantity. is_a("IfcPhysicalSimpleQuantity"): quantities[quantity. Name] = quantity[3] qsets[qset. Name] = quantities print(qset. Name + " was added!") qsets 수치단위 확인. 어떤 수치가 어떤 단위인지 명시되어 있다. 이 quantity들을 묶어서 export csv 등이 가능하다. project = file. by_type("IfcProject") project = project[0] for unit in project. UnitsInContext. Units: print(unit) BIM application with python streamlit을 활용한 web app front-end proto 개발 가이드. viewer 와 download csv, 통계 수치 등을 시각화 해준다. ifc viewer Prototyping import uuid import time import ifcopenshell import ifcopenshell. guid import json from ifc_builder import IfcBuilder O = 0. 0, 0. 0, 0. 0 X = 1. 0, 0. 0, 0. 0 Y = 0. 0, 1. 0, 0. 0 Z = 0. 0, 0. 0, 1. 0 create_guid = lambda: ifcopenshell. guid. compress(uuid. uuid1(). hex) # IFC template creation filename = "hello_wall. ifc" # https://standards. buildingsmart. org/IFC/DEV/IFC4_3/RC2/HTML/schema/ifcdatetimeresource/lexical/ifctimestamp. htm # NOTE: 초단위. timestamp = int(time. time()) timestring = time. strftime("%Y-%m-%dT%H:%M:%S", time. gmtime(timestamp)) creator = "CKC" organization = "SWK" application, application_version = "IfcOpenShell", "0. 7. 0" project_globalid, project_name = create_guid(), "Hello Wall" # A template IFC file to quickly populate entity instances for an IfcProject with its dependencies template = ( """ISO-10303-21; HEADER; FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1'); FILE_NAME('%(filename)s','%(timestring)s',('%(creator)s'),('%(organization)s'),'%(application)s','%(application)s',''); FILE_SCHEMA(('IFC4')); ENDSEC; DATA; #1=IFCPERSON($,$,'%(creator)s',$,$,$,$,$); #2=IFCORGANIZATION($,'%(organization)s',$,$,$); #3=IFCPERSONANDORGANIZATION(#1,#2,$); #4=IFCAPPLICATION(#2,'%(application_version)s','%(application)s',''); #5=IFCOWNERHISTORY(#3,#4,$,. ADDED. ,$,#3,#4,%(timestamp)s); #6=IFCDIRECTION((1. ,0. ,0. )); #7=IFCDIRECTION((0. ,0. ,1. )); #8=IFCCARTESIANPOINT((0. ,0. ,0. )); #9=IFCAXIS2PLACEMENT3D(#8,#7,#6); #10=IFCDIRECTION((0. ,1. ,0. )); #11=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1. E-05,#9,#10); #12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0); #13=IFCSIUNIT(\*,. LENGTHUNIT. ,$,. METRE. ); #14=IFCSIUNIT(\*,. AREAUNIT. ,$,. SQUARE_METRE. ); #15=IFCSIUNIT(\*,. VOLUMEUNIT. ,$,. CUBIC_METRE. ); #16=IFCSIUNIT(\*,. PLANEANGLEUNIT. ,$,. RADIAN. ); #17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0. 017453292519943295),#16); #18=IFCCONVERSIONBASEDUNIT(#12,. PLANEANGLEUNIT. ,'DEGREE',#17); #19=IFCUNITASSIGNMENT((#13,#14,#15,#18)); #20=IFCPROJECT('%(project_globalid)s',#5,'%(project_name)s',$,$,$,$,(#11),#19); ENDSEC; END-ISO-10303-21; """ % locals() ) def run(): print(type(template)) # Write the template to a temporary file # temp_handle, temp_filename = tempfile. mkstemp(suffix=". ifc", text=True) # print(temp_filename) # with open(temp_filename, "w", encoding="utf-8") as f: # f. write(template) # os. close(temp_handle) temp_filename = "temp. ifc" with open(temp_filename, "w", encoding="utf-8") as f: f. write(template) print(template) with open("result-bldg. json", "r", encoding="utf-8") as f: bldg_info = json. load(f) # Obtain references to instances defined in template ifc_file_template = ifcopenshell. open(temp_filename) # IFC hierarchy creation ifc_builder = IfcBuilder(bldg_info, ifc_file_template) # site 생성 site_placement, site = ifc_builder. add_site("Site") # project -> site ifc_builder. add_rel_aggregates("Project Container", ifc_builder. project, [site]) # building 생성 building_placement, building = ifc_builder. add_building("Building", relative_to=site_placement) # site -> building ifc_builder. add_rel_aggregates("Site Container", site, [building]) # storeys 생성 storey_placement_list = [] building_storey_list = [] for i in range(bldg_info["unit_info"]["general"]["floor_count"]): storey_placement, building_storey = ifc_builder. add_storey( f"Storey_{i+1}", building_placement, elevation=i * 6. 0 ) storey_placement_list. append(storey_placement) building_storey_list. append(building_storey) print(building_storey_list) # building -> storeys ifc_builder. add_rel_aggregates("Building Container", building, building_storey_list) ifc_walls = [] # 외벽과 창문 for i, (exterior_wall_polyline_each_floor, exterior_wall_area_each_floor, window_area_each_floor) in enumerate( zip( ifc_builder. ifc_preprocess. exterior_wall_polyline, ifc_builder. ifc_preprocess. exterior_wall_area, ifc_builder. ifc_preprocess. window_area, ) ): for ( exterior_wall_polyline_in_floor_polygon, exterior_wall_area_in_floor_polygon, window_area_in_floor_polygon, ) in zip( exterior_wall_polyline_each_floor, exterior_wall_area_each_floor, window_area_each_floor, ): for (exterioir_wall_polyline, exterior_wall_area, window_area_list) in zip( exterior_wall_polyline_in_floor_polygon, exterior_wall_area_in_floor_polygon, window_area_in_floor_polygon, ): print(storey_placement_list[i]) print(building_storey_list[i]) wall, windows = ifc_builder. add_exterior_wall( storey_placement_list[i], exterioir_wall_polyline, exterior_wall_area, window_area_list ) ifc_walls. append(wall) ifc_builder. add_rel_contained_in_spatial_structure( "Building Storey Container", building_storey_list[i], wall ) print(windows) for window in windows: ifc_builder. add_rel_contained_in_spatial_structure( "Building Storey Container", building_storey_list[i], window ) # 층 바닥 for i, floor_slab_polyline_each_floor in enumerate(ifc_builder. ifc_preprocess. floor_slab_polyline): ifc_floor_slabs_in_a_floor = [] for floor_slab_polyline_each_polygon in floor_slab_polyline_each_floor: floor_slab = ifc_builder. add_floor_slab(0. 2, storey_placement_list[i], floor_slab_polyline_each_polygon) # , building_storey_list[i] print(floor_slab) ifc_floor_slabs_in_a_floor. append(floor_slab) ifc_builder. add_rel_contained_in_spatial_structure( "Building Storey Container", building_storey_list[i], floor_slab, ) # 세대 내벽 ifc_interior_walls = [] for i, (interior_wall_polyline_each_floor, interior_wall_area_each_floor) in enumerate( zip( ifc_builder. ifc_preprocess. interior_wall_polyline, ifc_builder. ifc_preprocess. interior_wall_area, ) ): for ( interior_wall_polyline, interior_wall_area, ) in zip(interior_wall_polyline_each_floor, interior_wall_area_each_floor): print(storey_placement_list[i]) print(building_storey_list[i]) wall = ifc_builder. add_interior_wall( storey_placement_list[i], interior_wall_polyline, interior_wall_area, ) ifc_interior_walls. append(wall) ifc_builder. add_rel_contained_in_spatial_structure( "Building Storey Container", building_storey_list[i], wall ) # material 적용 방식중 맞는 방식 확인 필요. # ifc_builder. add_material_style(ifc_interior_walls, ifc_builder. wall_style) # ifc_builder. add_material_style(ifc_walls, ifc_builder. glass_style) for i, flight_info_each_floor in enumerate(ifc_builder. ifc_preprocess. flight_info): ifc_stair = ifc_builder. add_stair( flight_info_each_floor, storey_placement_list[i], ) ifc_builder. add_rel_contained_in_spatial_structure( "Building Storey Container", building_storey_list[i], ifc_stair ) print(filename) # Write the contents of the file to disk ifc_builder. build() if __name__ == "__main__": run() results Conclusion IFC 포맷은 예상했던 것보다 훨씬 더 다양한 요소들을 저장할 수 있도록 체계적인 구조를 갖추고 있다. 이러한 포괄적인 구조로 인해 문서화와 실제 사용법이 다소 복잡한 면이 있다. 특히 포함 관계나 연관 관계를 entity로 정의하는 구조적 특징은 매우 인상적이며, 향후 참고할만한 좋은 사례로 보임. ifcopenshell-python을 이용한 IFC 생성에 관한 인터넷 자료는 상대적으로 부족한 편이지만 대신 blender-bim이 주된 도구로 사용되고 있으며, 다른 프로그래밍 언어 바인딩이나 도구들에 대한 정보는 비교적 풍부합니다. 렌더링 작업 측면에서는 기존 방식과 비교했을 때 필요한 요소들을 직접 생성해야 하는 점에서 큰 차이가 없어 작업 효율성 향상을 기대하기는 어려워 보인다. 다만, 동일한 사전 처리 과정이 필요하더라도 이를 저장하고 제공하거나 관리할 수 있다는 점에서 장점이 있을 것으로 판단됨. References https://www. youtube. com/playlist?list=PLbFY94gzUJhGkxOUZknWupIiBnY5A0KUM https://standards. buildingsmart. org/IFC/RELEASE/IFC4/ADD1/HTML/schema/ifcproductextension/lexical/ifcbuilding. htm . .


Accelerating Reinforcement Batch Inference Speed

Accelerating Reinforcement Batch Inference Speed

스페이스워크는 인공지능. 데이터 기술로 최적의 토지 개발시나리오를 구현하는 프롭테크 기업입니다.

건축설계 AI는 강화학습 알고리즘을 기반으로 최적의 건축설계안을 만듭니다. 대표적인 제품으로 Landbook 이 있습니다. Landbook ㅡ Architectural AI 강화학습은 어떤 환경 안에서 정의된 에이전트가 현재의 상태를 인식하여, 선택 가능한 행동들 중 보상을 최대화하는 행동을 선택하는 방법입니다. 딥러닝을 활용한 강화학습에서 에이전트는 다양한 행동을 하고 그에 대한 보상을 얻는 방식으로 학습하게 됩니다. 품질좋은 설계안을 만들기 위해서는 이와 관련된 연구를 신속하고 빠르게 진행해야합니다. 하지만 건축설계 AI의 추론과정은 상당한 시간이 소요되어 개선이 필요했습니다. 본 포스팅에서는 건축설계 AI의 추론시간을 개선한 사례를 공유하겠습니다. Inference process of architectural AI 하나의 데이터배치에 대해서 추론작업은 다음과 같이 진행됩니다. 하나의 추론과정에서 에이전트와 환경이 서로 상호작용합니다. 에이전트가 현재의 상태를 기반으로 액션을 생성합니다. 환경은 에이전트의 액션을 바탕으로 새로운 상태를 에이전트에게 전달합니다. 위의 두 과정을 N번 반복하게 되면 하나의 추론이 끝나게 됩니다. Inference process of architectural AI AS-IS: Using Environment Package 기존방법은 환경을 파이썬 패키지로 사용합니다. 즉 에이전트와 환경은 같은 컴퓨팅자원에서 동작합니다. 환경연산의 경우 각 연산은 독립적으로 수행할 수 있습니다. 보통 다수의 코어를 가진 컴퓨팅자원을 사용하기 때문에 사용할 수 있는 코어의 수만큼 환경연산을 병렬처리 합니다. Parallel processing when using environment package 학습에 사용하는 컴퓨팅자원의 코어의 수는 직접적으로 학습속도에 영향을 주는 요소입니다. 예를 들어 배치사이즈가 $128$인 학습을 진행할 때 코어의 수가 $4$개인 경우와 $8$개인 경우를 비교해보겠습니다. 코어의 수가 $4$개인 경우 $128$개의 환경연산은 $4$개씩 동시에 이루어집니다. 그렇게 되면 한 코어에서 대략 $128 / 4 = 32$번 의 환경연산이 이루어지면 $128$개의 환경연산은 완료됩니다. Environment operations with 4-core parallel processing 반면에 코어의 수가 $8$라면 $128$개의 환경연산은 $8$개씩 동시에 이루어지면 $128/ 8 = 16$번 의 환경연산이 이루어지면 $128$개의 환경연산이 완료됩니다. 따라서 코어가 $4$개인 경우와 비교했을 때 환경연산시간은 2배정도 빠를 것으로 기대할 수 있습니다. Environment operations with 8-core parallel processing 하지만 특정 컴퓨팅자원의 코어의 수는 물리적으로 한정되어 있습니다. 즉 특정 수 이상으로는 코어를 늘릴 수 없습니다. 또한 학습 배치사이즈는 모델이 커지거나 연구방향에 따라서 커질 수 있습니다. 따라서환경연산이 학습을 진행하는 컴퓨팅자원에 의존적이라면 배치사이즈가 커짐에 따라서 학습속도도 느려질 것입니다. TO-BE: Operating Environment Servers 환경연산을 에이전트의 컴퓨팅자원에 독립적으로 만들기 위해서 환경을 패키지 형태가 아닌 서버형태로 변경하였습니다. 즉 에이전트 연산이 이루어지는 컴퓨팅자원과 환경연산이 이루어지는 컴퓨팅자원은 독립적 으로 운영될 수 있습니다. Parallel processing when using environment servers 환경연산을 서버형태로 하게 되면 환경연산을 동시에 처리할 수 있는 수는 제한이 없게됩니다. 기존과 비교하면 다음과 같습니다. 정리해보면 환경서버 형태는 현재 운영중인 환경서버의 수만큼 동시에 환경연산을 처리할 수 있게됩니다. 또한 AWS 자원을 사용하기 때문에 환경서버의 수는 특별히 제한받지 않습니다. Implementation 스페이스워크에서 어떻게 환경서버를 운영하는지 소개하겠습니다. 사용하는 기술스택은 다음과 같습니다. FastAPI: 환경서버 구현을 위한 프레임워크 AWS: 환경서버에 필요한 컴퓨팅자원 Kubernetes: 환경서버를 운영하기 위한 컨터네이너 오케스트레이션 도구 Newrelic: 환경서버 모니터링을 위한 도구 Implementing Environment Servers Using FastAPI 환경서버를 구현하기 위해서 FastAPI 프레임워크를 사용했습니다. FastAPI는 파이썬 기반 프레임워크로 다음과 같은 장점이 있습니다. 참고로 기존 환경패키지는 파이썬으로 구현되어 있습니다. 구현의 편의성을 위해서 파이썬 언어를 사용하여 환경서버를 구축하였습니다. 빠릅니다. 파이썬 프레임워크중에서는 가장 좋은 성능을 보여줍니다. 개발표준을 채택하고 있습니다. OpenAPI(Swagger UI)를 활용할 수 있습니다. 낮은 개발비용으로 개발할 수 있습니다. . 실제로 많은 기업에서 FastAPI에 대해서 상당히 좋은 인상을 받았다는 것을 알 수 있었습니다. FastAPI Reviews 다음과 같은 코드로 서버를 간단히 구현할 수 있습니다. 환경패키지를 임포트하여 환경서버에서 사용하는 방식으로 구현하였습니다. from fastapi import FastAPI, File, UploadFile import os # Environment package from swkgym import step import pickle from fastapi. responses import Response from fastapi. logger import logger as fastapi_logger import os import time import sys app = FastAPI( contact={ "name": "roundtable", "email": "roundtable@spacewalk. tech" } ) @app. on_event("startup") async def startup_event(): pass @app. post('/step') async def step(inputs: UploadFile = File(. . . ),): start = time. time() bytes = await inputs. read() inputs = pickle. loads(bytes) fastapi_logger. log(logging. INFO, "Input is Ready") # Environment computation states = step(inputs["state"], inputs["action"], inputs["p"], inputs["is_training"], inputs["args"]) fastapi_logger. log(logging. INFO, "Packing Input is Start") size_of_response_file = sys. getsizeof(states) fastapi_logger. log(logging. INFO, f"response states size is {size_of_response_file} bytes") fastapi_logger. log(logging. INFO, f"{inputs['p']} Step time: {time. time() - start}") return Response( content=pickle. dumps(states) ) Using AWS for Dynamic Allocation 위에서 언급되었듯이 환경서버는 AWS의 자원을 사용하고 있습니다. 건축AI 학습이 항상(24시간, 매일) 발생하는 이벤트가 아니기 때문에 온프라미스 장비를 구매하기에는 다소 불확실성이 큽니다. 따라서 컴퓨팅 자원을 동적으로 사용할 필요가 있었고 대표적인 클라우드 서비스인 AWS를 이용하였습니다. 참고로 AWS의 EKS는 Cluster Autoscaler 기능을 제공하고 있습니다. 즉 사용량이 많아진다면 EKS를 구성하는 노드(컴퓨팅 리소스)의 수도 늘어나게 됩니다. 실제로 내부에서 환경서버 사용 경향성을 추정해보면 다음과 같습니다. 환경서버는 건축설계 AI를 학습시킬 때활용될 수 있기 때문에 GPU 사용률을 근거로 추정하였습니다. FastAPI Reviews 요청이 상시로 구동하는 서비스와 다르게 규칙적으로 발생하지 않으며 요청이 한번 발생하면 그렇지 않을때와 비교했을 때의 차이가 매우 큰 편입니다. 따라서 동적인 자원할당이 합리적이라고 판단했습니다 Kubernetes(EKS) for Operating Environment Servers 환경서버를 안정적이고 운영비용을 낮추는 방향으로 관리하기 위해서 Kubernetes를 사용했습니다. Kubernetes에 대해서 알고 싶으신 분들은 Kubernetes의 공식문서[6]를 참고하시는 것을 추천드립니다. Kubernetes를 사용하면 기존의 사람이 하던 컨테이너 관리를 많은 부분 시스템으로 처리할 수 있습니다. 예를 들어 컨테이너가 다운되면 개발자가 직접 컨테이너를 다시 띄우기 보다는 시스템이 컨테이너 상태를 체크하고 이를 다시 복구하도록 하는 것입니다. 이를 자동화된 복구(Self-Healing)이라고 합니다. 또한 서비스 디스커버리와 로드밸런싱도 손쉽게 구현할 수 있습니다. 에이전트 모델이 환경서버에 접근할 수 있도록 환경서버를 외부로 노출시킬 수 있으며 환경서버에 대한 부하에 대해서 AWS Network Loadbalaner를 이용하여 부하를 분산합니다. 그리고 Kubernetes는 Autoscaling기능도 제공합니다. Autoscaling의 종류에는 Horizontal Pod Autoscaling과 Vertical Pod Autoscaling이 있습니다. Horizontal Pod Autoscaler같은 경우는 동일한 리소스를 가지는 Pod의 수를 늘리거나 줄이는 것이고 Vertical Pod Autoscaling은 Pod의 리소스를 늘리거나 줄이는 기능을 합니다. Kubernetes Horizontal Pod Autoscaler 앞서 언급했듯이 환경서버 요청이 일정하지 않게 발생하지 않습니다. 따라서 같은 수의 환경서버를 항상 운영하는 것은 비효율적입니다. 환경서버 요청이 있을 때 환경서버의 수를 늘리고 요청이 없을때 환경서버의 수를 줄여주는 것이 필요했고 이를 위해 Kubernets의 HPA를 사용했습니다. 이를 시각화해보면 아래와 같은 구성이 됩니다. Diagram when using HPA Newrelic Adapter for Defining Environment Server External Metrics 위에서 HPA를 사용하여 환경서버의 수를 조절한다고 했습니다. 그렇다면 무엇을 기준으로 환경서버의 수를 조절할 수 있을까요? 가장 기본적으로 Kubernetes는 파드의 리소스 사용량을 기준으로 스케일링 할 수 있습니다. type: Resource resource: name: cpu target: type: Utilization averageUtilization: 60 CPU Utilization-based scaling 하지만 환경서버 HPA 메트릭으로 파드의 리소스 사용량은 아쉬움이 있었습니다. 실제로 CPU 사용량을 바탕으로 스케일링하게되면 다음과 같은 문제가 발생할 수 있습니다. 하나의 요청에 대한 환경서버의 평균 및 최대 CPU Utilization은 90%라고 가정하겠습니다. averageUtilization 90%로 설정: Pod의 수가 늘어나지 않는다. 하나의 환경서버에 요청이 몰릴경우에도 Pod의 수는 늘어나지 않는다. 환경서버는 하나의 요청씩 처리하며 따라서 CPU Utilization은 평균적으로 90%로 유지된다. averageUtilization 90% 미만으로 설정: Pod의 수가 실제로 필요한 수보다 커진다. 배치사이즈가 128인 경우 환경서버의 수는 128개 이상으로 증가하여 averageUtilization 를 90%미만으로 떨어트린다. 보다 세밀한 스케일링을 위해서는 환경서버 파드 당 요청량이 필요했습니다. 예를 들어 적절한 환경서버 파드 당 요청량을 초당 4개로 설정한경우 초당 16개의 요청이 들어오면 환경서버 파드를 1개에서 4개로 스케일링 할 수 있습니다. 하지만 Kubernetes 자체적으로 Pod에 대한 요청량을 구할 수 있는 기능은 없습니다. 대신 커스텀 메트릭을 등록하여 이를 스케일링 매트릭으로 활용할 수 있습니다 [6]. Newrelic 혹은 Prometheus와 같은 Kubernetes에서 사용할 수 있는 모니터링 도구들은 쉽게 커스텀 매트릭을 등록할 수 있게합니다. 사내에서는 Newrelic을 활용하여 배포하고 있는 서비스에 모니터링을 하고 있습니다. 이에 HPA 매트릭으로 Newrelic을 활용하기로 했습니다. Newrelic은 강력한 모니터링 도구입니다 [7]. 그리고 Newrelic에서는 New Relic Metrics Adapter를 제공합니다 [8]. New Relic Metrics Adapter는 newrelic이 제공하는 다양한 metric을 Kubernetes Metric으로 등록합니다. Newrelic Metrics Adapter 설치는 다음과 같이 진행할 수 있습니다. 참고로 이미 Newrelic이 Kubernetes Cluster에 설치되어 있다면 New Relic Metrics Adapter를 따로 설치할 수 있습니다. 다음과 같은 Requirement를 모두 충족했다면 Helm으로 간단히 설치할 수 있습니다. Kubernetes 1. 16 or higher. The New Relic Kubernetes integration. New Relic’s user API key. 다른 External Metric을 등록하는 것이 없어야합니다. helm upgrade --install newrelic newrelic/nri-bundle \ --namespace newrelic --create-namespace --reuse-values \ --set metrics-adapter. enabled=true \ --set newrelic-k8s-metrics-adapter. personalAPIKey=YOUR_NEW_RELIC_PERSONAL_API_KEY \ --set newrelic-k8s-metrics-adapter. config. accountID=YOUR_NEW_RELIC_ACCOUNT_ID \ --set newrelic-k8s-metrics-adapter. config. externalMetrics. {external_metric_name}. query={NRQL query} Script for installing newrelic metric adapter 앞서 환경서버의 요청량 기준으로 환경서버를 스케일링하는 것이 필요하다고 했습니다. 이에 다음과 같은 External Metric을 정의했습니다. external_metric_name: env_servier_request_per_seconds NRQL query: FROM Metric SELECT average(k8s. pod. netRxBytesPerSecond) / {하나의 요청당 bytes} / uniqueCount(k8s. podName) SINCE 1 minute AGO WHERE k8s. deploymentName = 'env-service-deployment' 설치가 완료되면 다음과 같은 명령어로 External Metric이 제대로 작동하는지 확인할 수 있습니다. $ kubectl get --raw "/apis/external. metrics. k8s. io/v1beta1/namespaces/*/env_servier_request_per_seconds" >>> {"kind":"ExternalMetricValueList","apiVersion":"external. metrics. k8s. io/v1beta1","metadata":{},"items":[{"metricName":"env_servier_request_per_seconds","metricLabels":{},"timestamp":"2022-02-08T02:28:17Z","value":"0"}]} Registered Metric check 매트릭이 정상작동한 것을 확인했다면 HPA를 배포할 차례입니다. 다음과 같은 YAML로 HPA를 설정할 수 있습니다. apiVersion: autoscaling/v2beta2 kind: HorizontalPodAutoscaler metadata: name: env-service-autoscaling spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: env-service-deployment minReplicas: 1 maxReplicas: 64 metrics - type: External external: metric: name: env_servier_request_per_seconds selector: matchLabels: k8s. namespaceName: default target: type: Value value: "4" Autoscaler using External Metric 이제 HPA까지 모두 배포되었습니다. 전체적인 다이어그램을 그려보면 다음과 같습니다. Newrelic Metrics Adapter Experiments 환경서버 운영시 추론 속도 개선이 얼마나 될 수 있는지 살펴보겠습니다. 64 Batch Inference 1024 Batch Inference 에이전트 서버: c4. xlarge, 시간당 0. 227 USD 환경 서버: c4. xlarge/ 4 시간당 0. 061 USD AWS 비용계산 = (시간당 에이전트 서버비용 + 시간당 환경서버 비용 * 사용서버 개수) * 사용시간(초) / 3600 참고로 실험에서는 명확한 비교를 위해서 HPA를 적용하지 않았습니다. 참고로 환경패키지-CPU4와 환경서버 4개의 추론시간이 차이가 납니다. CPU를 이용한 추론의 경우 에이전트 모델과 환경패키지를 연산하는 부분이 CPU를 함께 사용합니다. 하지만 환경서버의 경우 에이전트 모델이 사용하는 CPU와 환경패키지를 사용하는 CPU가 분리되어 환경연산 처리 의한 지연현상이 없습니다. 64개의 배치추론에 대한 실험에서 AWS 비용이 세배정도 사용하면 시간을 절반정도 줄일 수 있었습니다. 또한 거의 유사한 비용으로 시간을 기존대비 60%정도 줄일 수 있었습니다. 1024개의 배치추론에 대해서 병렬처리 효과가 더 명확하게 보입니다. 약 7. 6배정도의 비용으로 속도를 4. 5배정도 향상시킬 수 있었습니다. Conclusion 이번 포스팅에서는 건축설계 AI의 배치추론을 가속화하는 방법에 대해서 다뤘습니다. 에이전트와 환경이 하나의 호스트에 종속되면 필연적으로 병렬처리할 수 있는 코어의 수가 제한됩니다. 문제를 해결하기 위해서 에이전트와 환경이 각각 독립적인 컴퓨팅 자원에서 운영될 수 있도록 환경을 서버형태로 운영하였습니다. 또한 환경서버의 수요가 일정하지 않으므로 온프라미스 자원을 구축하기보다는 클라우드 서비스를 이용하여 컴퓨팅 자원을 동적으로 활용했습니다. (Cluster Autoscaling) 그리고 kubernetes의 HPA를 활용하여 자동으로 환경서버의 수를 조절하도록 했습니다. 참고로 본 포스팅에서 다룬 배치추론 가속화를 건축설계 AI의 학습 속도를 높이거나 서비스의 속도를 높이는데 활용할 계획입니다. 스페이스워크에서는 다양한 기술스택을 적극적으로 도입하고 있습니다. 앞으로 다른 포스팅을 통해서 다양한 내부사례를 공유하겠습니다. . .


Improving AVM with Duplicate Data Integration

Improving AVM with Duplicate Data Integration

Topic 다세대 주택과 오피스텔에 대한 거래 데이터를 모두 사용할 때 오피스텔 데이터가 다세대 주택 데이터의 약 1/10 수준으로 클래스 불균형이 존재합니다.

전처리 없이 결합된 데이터를 사용할 때, 우리는 모델이 훈련 중에 오피스텔에 비해 다세대 주택 데이터에 상대적으로 더 적합함을 발견했고, 그 결과 오피스텔의 예측 가격이 더 높았습니다. 따라서 우리는 다세대 주택과 오피스텔에 대한 통합 모델을 개발할 때 데이터 불균형으로 인한 편향을 줄이는 방법을 실험하는 것을 목표로 합니다. Method 데이터 불균형 문제를 해결하기 위해 고려할 수 있는 첫 번째 접근 방식은 손실 함수를 사용자 정의하는 것입니다. 그러나 현재 사용 중인 AutoGluon 패키지에서는 손실 함수를 임의로 수정하기 어려웠고, 사용자 정의는 주관적인 방법으로서 한계가 있었습니다. 따라서 우리는 “데이터 중복을 손실 대신 사용하여 특정 데이터 포인트의 손실이 전체 손실 함수에 미치는 영향을 더 크게 만들 수 있다”는 가설을 테스트했습니다. 이 아이디어는 AutoGluon 패키지와 독립적이면서도 더 체계적이라는 기준을 충족합니다. Result & Analysis Changes by Area and Type 지역 및 유형별 변화를 플로팅할 때, 중복 데이터를 사용한 통합 모델은 일반적으로 기존 통합 모델에 비해 낮은 결과를 보여준다는 것을 확인할 수 있습니다. 하지만, 오피스텔의 경우 두 모델 간에 선형적 관계가 나타나는 반면, 공동주택의 경우 특별한 선형적 관계가 나타나지 않았으며, 두 모델 간의 차이가 비교적 큰 경우도 있습니다. 오피스텔의 경우 규모가 커질수록 두 모델 간 차이가 극단적으로 나타나는 사례(수직·수평 범위가 넓어지고 추세에서 벗어나는 지점이 많아짐)가 많았습니다. Figure 왼쪽: 다세대 주택 / 오른쪽: 오피스텔 X축 : 기존 통합 모델 / Y축 : 중복 데이터를 활용한 통합 모델 Change Amount Scatter Plot 지역별로 변화량에 차이가 있는지 확인하기 위해 지도에 변화를 매핑했습니다. 다세대 주택의 경우 강서구 지역에서 두 모델 간에 큰 차이가 있는 사례가 많고, 오피스텔의 경우 서대문구 지역에서 많은 차이가 관찰되었습니다. 추가 실험과 투자팀 QA를 통해 유의미한 차이가 있는 지역 간에 어떤 공통점이 있는지 파악하는 것이 필요합니다. Figure 왼쪽: 다세대 주택 / 오른쪽: 오피스텔 색상 : 변화량 . .


Understanding Form Through Algorithms

Understanding Form Through Algorithms

Introduction 현대 과학에서는 자연의 복잡한 패턴이 사실은 단순한 규칙의 반복과 상호작용에서 발생한다는 사실이 자주 발견된다.

이를 창발성(emergence)이라고 부르는데, 개별 요소의 행동은 단순해 보여도 그 요소들이 모여 체계적으로 연결될 때 예측하기 어려운 복잡한 패턴이나 구조가 나타나는 현상을 의미한다. 컴퓨터 알고리즘은 이러한 과정을 살펴보기에 매우 적합하다. 왜냐하면 규칙을 프로그래밍해 시뮬레이션을 돌려 보면, 형태가 어떻게 만들어지는지 과정을 단계별로 추적할 수 있기 때문이다. 이는 자연을 단순히 흉내 내거나 결과물을 찍어내는 차원을 넘어, 형태가 생성되는 논리를 분석하는 강력한 방법론으로 발전한다. Complexity Science and Algorithmic Models Computational Models vs. Observed Forms 복잡계 과학(Complexity science)에서는 자연현상을 단순화한 계산 모델을 규칙 기반으로 구축하고, 이를 컴퓨터 시뮬레이션으로 돌려 나온 결과물을 실제 자연에서 관찰되는 형태와 비교함으로써 이론을 검증한다. 이때 알고리즘은 자연의 물리·화학적 과정을 똑같이 재현할 필요는 없다. 중요한 것은 형태가 생성되는 절차(procedure)를 포착하고, 그 과정을 조금씩 수정해가며 반복 실험하는 능력이다. 파라미터 값을 바꾸고, 얻어진 결과가 자연의 실측 데이터와 얼마나 유사한지 비교하는 식으로 진행하면, 복잡한 형태가 어떤 과정을 통해 발생하는지를 점진적으로 알아갈 수 있다. 따라서 알고리즘은 자연 모사가 아니라 형태를 이해하고 분석하기 위한 탐구 수단으로도 기능한다. Flocking effect Boid cohesion D’Arcy Thompson and Alan Turing 규칙 기반으로 형태생성을 이해하려는 관점은 다르시 톰슨(D’Arcy Wentworth Thompson)의 On Growth and Form(1917)에서 체계적으로 드러났다. 톰슨은 다윈적 진화가 강조되던 당시 분위기에서 더 나아가, 물리적 힘이 생물의 형태에 미치는 영향에 주목했다. 해파리 종 모양이나 새 뼈 구조가 기하학적·물리학적 원리로 설명될 수 있고, 어류의 체형은 격자변환(grid transformation)으로 서로 변형 가능한 사례가 될 수 있음을 제시했다. 이후 1952년 수학자 앨런 튜링(Alan Turing)은 반응-확산 모델을 제안해, 극도로 단순해 보이는 화학 반응과 확산만으로도 동물의 얼룩, 줄무늬 같은 복잡한 패턴이 형성될 수 있음을 수리적으로 증명했다. 이는 “유전자가 직접 색깔을 ‘그리는(paint)’ 것이 아니라, 국소적 반응 규칙이 패턴을 저절로 조직화한다”는 사실을 보여준 획기적인 연구로, 복잡한 형태 발생이 국소 규칙의 상호작용으로부터 창발할 수 있음을 확인시켰다. Major Algorithms for Explaining Natural Patterns 과학자들은 이러한 원리를 바탕으로, 자연계에서 일어나는 형태 발생을 시뮬레이션하고 연구하기 위해 다양한 알고리즘적 모델을 고안했다. Fractal Geometry and Self-similarity: 자연에는 나뭇가지, 번개, 로마네스코 브로콜리처럼 프랙털 특성을 띠는 구조가 많다. 부분을 확대했을 때 전체와 유사한 형태가 반복되는 자기 유사성이다. 브누아 망델브로(Benoît Mandelbrot)의 프랙털 기하학은 단순한 재귀적 규칙을 반복 적용함으로써 해안선이나 산맥 윤곽 같은 ‘무한히 복잡해 보이는’ 패턴도 재현 가능함을 보여준다. Fractal patterns Cellular Automata: 콘웨이의 생명 게임(Game of Life) 등이 대표 예다. 격자 위 개별 셀이 국소적 규칙(이웃의 상태에 따라 생존·사멸 결정)만 지키며 진행되는데, 시간이 지날수록 복잡한 패턴이 출현한다. 이는 동물 무늬나 도시 성장 같은 자연·사회 현상을 단순 규칙으로 모사할 수 있음을 시사한다. 이때 알고리즘은 개별 규칙이 전체 복잡성을 어떻게 야기하는지 설명하는 핵심 도구가 된다. Cellular Automata Zebra Patterns Reaction-Diffusion Systems: 앨런 튜링이 제안한 이 모델은 두 가지 이상의 화학 물질이 상호 반응하고, 공간적으로 확산하는 과정에서 얼룩·줄무늬 같은 패턴이 만들어지는 원리를 설명한다. 한 물질이 활성, 다른 물질이 억제를 맡고, 이 둘의 확산 속도 차이가 균일했던 공간을 자연스레 분할한다. 실제 동물 피부의 얼룩무늬와 유사한 패턴도 충분히 재현할 수 있어, 생물학·화학·예술 전반에서 광범위하게 쓰인다. Diffusion limited aggregation Diffusion-Limited Aggregation (DLA): 무작위 운동을 하는 입자들이 군집 중심에 달라붙으며 결정체를 성장시키는 방식이다. 번개나 서리, 광물 결정이 나뭇가지형·프랙털형 구조를 띠는 이유를 보여준다. 무작위 운동이라는 단순 규칙이 여러 입자에 걸쳐 적용될 때, 전체적으로는 나뭇가지 구조가 창발한다. 이러한 알고리즘들은 자연의 외형을 복제하는 데만 머물지 않고, 형태가 단계별로 생성되는 논리를 파악하고 분석하는 수단으로 활용된다. . Algorithmic Explanations of Design Forms Applications in Design 자연의 형태를 설명하는 데 활용되던 알고리즘들은 디자인에서도 의의가 크다. 과거에는 “복잡하고 참신한 형태를 생성하는 도구” 정도로 주로 인식됐지만, 요즘에는 “왜 그 형태가 그렇게 생겼는가”를 분석하고 설명하는 방법으로도 쓰인다. 형태 생성 규칙을 명시적으로 표현하면, 디자인 결과물의 논리가 투명해진다. 반복 실험을 통해 “어떤 파라미터 변화가 어떤 형태 변화를 유발하는지” 즉각 확인할 수 있다. Richard Serra and Verb Lists 조각가 리처드 세라는 1960년대 말, ‘to roll, to crease, to fold, to cut…’ 같은 행동 동사를 리스트로 만들어 작업 과정에 적용했다. 각 동사는 ‘자르고, 접고, 구부리는’ 식의 단순 조작이지만, 이들이 순차로 누적되면 복잡한 조형물이 탄생한다. 알고리즘으로 보면, 각 동사가 “형태를 특정 방식으로 변환하는 규칙”에 해당한다. 만약 이를 컴퓨터로 시뮬레이션한다면, “1단계에서 무엇을 하고, 2단계에서 어떤 변환을 적용했기에 최종 결과가 이 형태가 되었는가?”라는 과정을 명료하게 시각화·기록할 수 있다. 즉, 세라의 조각은 예술적 영감만이 아니라 “연속된 변환 규칙들이 만든 결과물”로도 볼 수 있다. 알고리즘은 이 변환 과정을 명시화하여, 작품의 생성 메커니즘을 논리적으로 설명하는 틀을 제공한다. Parametric and Procedural Design 파라메트릭 디자인에서는 형상을 직접 고정하지 않고, 대신 파라미터와 규칙(프로시저)를 설정한다. 예컨대 기둥 간격, 곡률, 높이 등의 파라미터를 입력으로 주면, 알고리즘은 이 값들을 바탕으로 즉시 건축 형상을 재계산한다. 이는 형상 자체가 아니라 형상을 만드는 규칙을 중심에 두는 방식이다. 설계자는 “기둥 간격을 20% 넓히면 지붕 곡률이 어떻게 변하는지”를 즉각 파악할 수 있다. 복잡한 구조에서도 규칙과 파라미터만 알면 반복 실험과 최적화가 쉽다. 결과적으로 이 방식은 설계 의도를 구조화하고, “왜 이런 형태가 나왔는지”를 설명할 수 있는 논리를 명확하게 제시한다. Examples of Action Verbs From the left, to split, to fill action verbs Combining Creativity with Logic 알고리즘을 통해 디자인을 설명한다는 것은 직관이나 감각적 판단을 배제한다는 뜻이 아니다. 오히려 감각적 아이디어를 명시적 규칙으로 전환해, ‘만약 이 값을 이렇게 바꾼다면?’ 같은 가설을 빠르게 테스트함으로써 새로운 가능성을 열어준다. 이는 디자인 지식이 디자이너 개인의 머릿속에만 머무르지 않고, 협업과 검증이 쉬운 명시적 체계로 자리 잡는 효과를 낸다. Conclusion 알고리즘적 접근은 자연 현상을 연구할 때만 유효한 것이 아니라, 디자인을 포함한 창작 영역 전반에서 “형태가 어떻게 만들어지고, 왜 그렇게 생겼는지”를 해명하는 데 큰 도움을 준다. 자연에서는 간단한 국소 규칙이 서로 얽혀 창발성을 일으켜 복잡한 패턴을 만든다. 디자인에서도 파라미터와 규칙에 주목해, 형태 생성 과정을 투명하게 기록하고 분석할 수 있다. 알고리즘을 통해 우리는 결과물만 보지 않고 과정 자체를 다룬다. 이는 직관적 감각과 계산적 논리를 융합해, 창의적 확장과 검증 가능한 구조를 동시에 확보하는 길을 열어준다. 디자이너가 실험을 거듭하며 최적 해법을 찾듯이, 형태 생성 알고리즘도 다양한 조건을 시험해볼 수 있다. 결국 이런 알고리즘적 사고는 예술과 과학을 연결하고, 협업과 토론의 장을 넓혀주며, 형태가 가진 내재적 논리를 더욱 명료하게 밝혀줄 것이다. . .