こんにちは。ABEJAでエンジニアをしている山下です。
こちらはABEJAアドベントカレンダー2024 20日目の記事です。
はじめに
最近、衛星データx LLMハッカソンというイベントに参加しました。
宇宙ビジネスの観点に染まりきっていないフレッシュなアイデアが多くあり、非常に刺激的なイベントでした。
その中でも特に感銘を受けた実装があり、自分でも試してみたいと考えました。
そこで今回、自分なりの解釈で再発明したいと思います!
今回開発する実装
LLMを使った実装二本立てになります。
- 衛星画像からある家をピックアップし、その家に住んでいる人物のプロフィールをLLMに妄想してもらう。
- プロフィールを出店計画のペルソナとして利用する。LLMにはプロフィールの人物になりきってもらい、出店した店にどれくらい行きたいと思うか、お気持ちを述べてもらう。
(株式会社Tech Funの鈴木様、チームの皆様、アイデア利用の許可ありがとうございました!!)
前提知識
まず、今回利用する衛星データの基本的な要素を解説します。
衛星データ(GISデータ)は大雑把に言うと画像のようなデータです。
大きく分けて ラスターデータ と ベクターデータ に分けられます。
ラスターデータ
ラスターデータはピクセル(グリッド)で構成されるデータです。
各ピクセルには色や値が割り当てられ、地表面の情報(例:標高、温度、植生など)を表現します。
- 画像形式(例:GeoTIFF、JPEG、PNGなど)
- 解像度(ピクセルサイズ)がデータの詳細度を決定する
ベクターデータ
ベクターデータは 点、線、面(ポリゴン) で構成されるデータです。
幾何学的な形状と属性情報を使って地物(例:道路、河川、土地の境界など)を表現します。
- データ形式はシェープファイル(Shapefile)、GeoJSON、KMLなどが一般的
- 座標系を使用して地理的な位置と形状を正確に記録できる
- スケールに依存しにくく、拡大・縮小しても情報が劣化しない
具体的には下記のようなデータを表現するときに使われます。
- 点:学校、交差点、観測地点などの特定の位置
- 線:道路、鉄道、河川の流れ
- 面(ポリゴン):建物の敷地、土地利用区画、行政区域
GISデータをLLMで利用するには
GISデータをLLMに渡すには工夫が必要になります。
汎用LLMは先述したGISデータを渡されることに対して最適化されていないため、LLMが解釈しやすい形式に変換することが必要です。
- png形式に変換する
- プロンプトとして渡せるような単純な値に直す
- 例: 降水量ラスターデータを江東区のベクターで括って、江東区の平均降水量にする
設計(How)
最初の方で述べたアイデアを元に具体的な実装の段階を考えると下記のようになりそうです。 GISデータをLLMに渡すための工夫を考えつつ設計しています。
- 建物の形状データ(foot print)を取得する
- 対象建物を中心にとらえた可視光データを作成し、LLMに渡しやすい形式にする
- 民家の付随情報をプロンプトに追加する
- 民家の住人プロフィールをLLMに生成させる
- 出力した住人データを元にLLMに出店シュミレーションをさせてみる
実際にやってみる
1. 建物の形状データ(foot print)を取得する
Open Street Mapなどから取得できると思いますが、今回は鮮度が高そうなGlobalMLBuildingFootprints というデータを利用しました。
日本国内の任意の緯度経度を指定して、半径1kmの建造物を取得してみます。
また、今回は民家を対象にしたかった為、foot printの面積を計算してフィルタリングしました。
geopandas
を使うと、ベクターデータの扱いがかなり楽になります。各地物の面積もGeoDataFrame.geometry.area
一瞬でdfのカラムに追加できます。
import geopandas as gpd # 建造物レイヤー読み込み geojson_path = "filtered_features.geojson" gdf = gpd.read_file(geojson_path) # CRSを平面座標系に変換 (EPSG:3857) gdf = gdf.to_crs(epsg=3857) # 面積を計算 gdf["area_m2"] = gdf.geometry.area # 面積でフィルタリング filtered_gdf = gdf[(gdf["area_m2"] <= 300) & (gdf["area_m2"] >= 50)] # 可視化のため地理座標系に戻す filtered_gdf = filtered_gdf.to_crs(epsg=4326)
2.対象建物を中心にとらえた可視光データを作成し、LLMに渡しやすい形式にする
民家レイヤーの各建造物の中心の緯度経度を取得して、対象の民家を中央にとらえた画像を取得します。
今回できるだけ解像度が高いデータが欲しかったので、Maps Static APIからmaptype=satellite
の画像を取得し利用しました。
maptype=satellite
ですがzoom_level次第では、衛星画像ではなく航空写真になってそうな雰囲気です。なのでzoom_levelを上げても高解像度の画像が取得できました。
対象の民家を示すため矩形を重ねた画像も作成しました。
Maps Static APIはpng形式で画像を取得できるのですが、緯度経度が扱えるGeotiff形式に一旦変換しベクターデータと重ね合わせたり等、前処理が色々必要でした。
また、矩形がずれてしまうケースが多くありました。LLMには家の状況を認識させたいので、二枚目の矩形は大雑把で良い感じがしますが、プロンプトで"矩形がずれる事があります"的な補足すると更に丁寧かなと思います。
3. 民家の付随情報をプロンプトに追加する
プロンプトに追加する為、民家周辺の情報を収集します。
周辺地域の店舗情報が欲しかったため、Open Street Mapから取得しました。
更にメタデータを充実させたかったのですが、データ集めや変換がしんどそうだったので今回は一旦断念しました…。
色々と調べたところ、経済系のGIS情報だと地域メッシュ統計/経済センサスがかなり良さそうでした。
500mメッシュでエリア内の事業所数、従事者数を取得できるので、その地域に住んでいる人の職業等を推定するのに役立ちそうです。
4. 民家の住人プロフィールをLLMに生成させる
1~3を組み合わせて、プロフィールを生成させます。 下記のようなスキーマで生成させてみました。
{ "is_house": true/false, "has_car": true/false, "buildings_age": 数値(1~50), "is_error": true/false, "response": "建物の状況に関するレポートと根拠", "residents": [ { "age": 整数, "gender": "woman" または "man", "job": "職業", "annual_income": 年収(推定値), "hobby": ["趣味1", "趣味2", ...], "story": "性格や背景に関する記述" }, { "age": 整数, "gender": "woman" または "man", "job": "職業", "annual_income": 年収(推定値), "hobby": ["趣味1", "趣味2", ...], "story": "性格や背景に関する記述" } // 二人とは限りません。必要に応じて複数人分を記載 ], "relationships": { "type": "家族" または "夫婦" または "ルームメイト" または "二世帯住宅" または "不明", "details": "関係性の具体的な推測や根拠" }, "household_analysis": { "total_income": "世帯の合計年収(推定値)", "spending_pattern": "消費行動の特徴(例: 子育て世帯、二世帯住宅、高齢世帯、独身世帯など)" }, }
結果がこちらです。一旦家や周囲の環境も捉えて推論してそうな雰囲気がありますね。
{ "is_house": true, "has_car": false, "buildings_age": 25, "is_error": false, "response": "この民家は、一般的な日本の住宅であると推測されます。周囲の環境を考えると、地域のコミュニティに溶け込んだ家族が住んでいる可能性が高いです。建物の外観からは、築25年ほどと判断し、外観のメンテナンスが行われている様子も見受けられますが、新しさを感じさせる部分もあります。庭や隣接するスペースの利用方法から、住人がたくさんの植物を育てていることが想像され、家庭菜園や花壇などを楽しむライフスタイルを持っているかもしれません。", "residents": [ { "age": 45, "gender": "woman", "job": "看護師", "annual_income": 5500000, "hobby": [ "ガーデニング", "料理", "ヨガ" ], "story": "健康志向で、休日は家族と過ごす時間を大切にしている。特にガーデニングに熱心で、四季折々の花や野菜を育てて家庭料理に活かしている。" }, { "age": 47, "gender": "man", "job": "ITエンジニア", "annual_income": 7000000, "hobby": [ "読書", "プログラミング", "釣り" ], "story": "静かな性格で、家では読書やプログラミングに没頭することが多いが、休日には家族と一緒に釣りに出かけることが多い。家族のために時間を大切にする。" }, { "age": 12, "gender": "girl", "job": "学生", "annual_income": 0, "hobby": [ "絵画", "友達と遊ぶ" ], "story": "学校での友達との関係を大切にし、絵を描くことが好き。将来はアーティストになりたいと夢見ている。" } ], "relationships": { "type": "家族", "details": "夫婦と子ども1人の家庭。双方ともに働いており、教育に力を入れている。" }, "household_analysis": { "total_income": "12500000", "spending_pattern": "教育費や家庭菜園のための資材購入が主な支出。家族旅行にも積極的で、地域行事にも参加する社会的な家庭。" }, "uncertainty": "画像は明確であり、住民の嗜好や家庭環境についての推測には自信があります。ただし、細部描写は実際とは異なる可能性があります。" }
とりあえず次の手順で必要となる100件分のプロフィールを作成しました。
5. 出力した住人データを元にLLMに出店シュミレーションをさせてみる
実際にある土地にコンビニを建てた時、どれくらい住民たちが喜んでくれるのか試してみました。
結果の可視化もしてみます。
出店店舗の位置には水色のマーカーが立っており、それ以外は民家のマーカーになります。
民家マーカーの色分け基準は行きたい気持ちスコアです。
- 赤: 30~50
- オレンジ: 50~70
- 緑: 70~90
- 深緑: 90~100
某コンビニと某大型家具店を出店してみました。
某コンビニ
某大型家具店
結果と考察
コンビニ.
コンビニの他に薬局でも試してみたのですが、似たような反響になりがちでした。
より近くに競合があると、そっちの方がいいと考えてくれるみたいです。
大型家具店.
大型家具店の場合は、多少遠くても行きたいと言っている人が多い印象でした。
データ上は家具店もあるのですが、有名な家具店の名前を入力したので、有名さに引っ張られている可能性はありそうです。
距離の影響.
どの条件にせよ、1km離れている程度でも行きたくないと言う人は多いです。
プロンプトには距離の基準を渡していたのですが、
車で行ける距離など渡せてなかったので、そこが効いてるかもしれません。
作ってみて感じたこと
よかった点
めっちゃ楽しい!!
シ⚪︎シティのようなシミュレーションゲームみがありました。実用性からは離れるかもしれませんが、ノイズを混ぜて変なプロフィールを作る等、更に楽しむ余地がありそうだと感じています。
可視光データが効いている
定性的な判断ですが、車の所有可否の判定ができているように感じました。これは出店シミュレーションをする上で重要な観点です。また、屋根の朽ち方を見て、築年数の推定をすることが出来ています。プロンプトを工夫すれば、住んでいる人の年齢の推定等、ステップバイステップに行えそうです。
改善点
生成したプロフィールが実際に住んでいる都市の人々と乖離がある
シミュレーションの舞台として地方都市を選択したのですが、年収や職業が実際に住んでいる人と乖離がありそうな結果になりました。地域メッシュ統計/経済センサス等、統計データを使ってその地域の職業分布や平均年収を出すことが出来れば、精緻なプロフィールを作ることができそうだと考えます。
シミュレーションの精度
現状では店舗からの距離のみで判断しがちになっている可能性があります。店舗に関する知識や趣味趣向をしっかりと渡さなければ、根拠のあるお気持ちが出てこないように思います。
渡したプロフィールを演じさせるようなプロンプトは改善の余地がありそうです。
まとめ
LLMに衛星データ渡す実装は一工夫必要でしたが、やりたいことやプロンプトに渡したいデータの内容を整理することで比較的スムーズに実装フェーズまで辿り着けました。
引き続き個人開発で深掘りしていけたらと思います!
We Are Hiring!
ABEJAは、テクノロジーの社会実装に取り組んでいます。 技術はもちろん、技術をどのようにして社会やビジネスに組み込んでいくかを考えるのが好きな方は、下記採用ページからエントリーください! (新卒の方やインターンシップのエントリーもお待ちしております!)
特に下記ポジションの募集を強化しています!ぜひ御覧ください!