{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "# 서울 아파트, 1년 뒤 올랐을까\n### 당신의 감 vs 당신이 만든 인공지능 두뇌\n\n**Connect AI LAB · 인공지능 두뇌 만들기**\n\n---\n\n국토교통부 실거래가 공개시스템에 올라온 **실제 거래**입니다. 2024년에 거래된 서울 아파트 단지 3,418개(단지·면적대 기준)를 모았고, 2025년(8월까지) 거래가가 **5% 넘게 올랐는지**를 맞힙니다.\n\n| | |\n|---|---|\n| **가르칠 명단** 3,000개 | 정답(올랐나)이 있습니다. 이걸로 두뇌를 가르칩니다 |\n| **시험 명단** 418개 | 정답이 **없습니다**. 두뇌가 답안지를 씁니다 |\n| **답안지.csv** | 순위표에 올리면 점수가 나옵니다 |\n\n시험 명단 418개 중 절반쯤이 올랐습니다. 그래서 **아무렇게나 찍으면 50%** 입니다. 평당가 하나만 넣은 두뇌가 73%, 다 넣으면 75% 근처입니다.\n사람 감으로 먼저 열 개 찍어 보고 시작하세요. 두뇌가 이기는지 보는 게 오늘의 재미입니다.\n\n> 이 실습은 투자 조언이 아닙니다. 2024년 데이터로 2025년을 맞히는 **과거 문제**이고, 목적은 두뇌가 무엇을 배우고 무엇을 못 배우는지 보는 것입니다.\n\n### 오늘 하는 일 여섯 단계\n\n```\n 1 · 명단 내려받기          셀 하나로 자동\n 2 · 글자를 숫자로          구 → 입지 등급, 빈칸 채우기\n 3 · 어떤 열을 넣을까       표에서 숫자 덩어리 꺼내기        ← 포인트 ①\n 4 · 두뇌 만들기            칸 하나짜리 가장 얇은 두뇌       ← 포인트 ②\n 5 · 가르치기               3,000개로 w 와 b 를 정한다        ← 포인트 ③\n 6 · 답안지 만들기 · 제출   418개의 답을 파일로 → 순위표\n```\n\n> 순위표 · https://www.aicitybuilders.com/contest"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "---\n# 1 · 명단 내려받기\n\n| 줄 | 하는 일 |\n|---|---|\n| `import …` | `pd` 는 표, `np` 는 숫자 계산, `keras` 는 두뇌 |\n| `!wget -q -O …` | 인터넷에서 파일을 받아 이름을 붙여 저장 |\n| `pd.read_csv(…)` | csv 파일을 읽어 **표**로 |\n| `.head()` | 앞 다섯 줄 보기 |"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "import numpy as np                          # 숫자 계산\nimport pandas as pd                         # 표 다루기\nimport keras                                # 두뇌 만들기\n\n!wget -q -O 가르칠명단.csv \"https://www.aicitybuilders.com/apt/seoul_apt_train.csv\"\n!wget -q -O 시험명단.csv \"https://www.aicitybuilders.com/apt/seoul_apt_test.csv\"\n\n가르칠명단 = pd.read_csv('가르칠명단.csv')   # 3,000개, 정답(올랐나) 있음\n시험명단   = pd.read_csv('시험명단.csv')     # 418개, 정답 없음\n\nprint(f'가르칠 명단 {len(가르칠명단)}개 · 시험 명단 {len(시험명단)}개')\n가르칠명단.head()"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "### 명단에 있는 열\n\n| 열 | 뜻 | 모양 |\n|---|---|---|\n| 번호 · 이름 | 단지 번호, 「구 단지명」 | |\n| 구 · 동 | 어디에 있나 | 글자 |\n| 면적 | 전용면적 (㎡). 84 면 흔히 말하는 34평 | 숫자 |\n| 층 | 2024년 거래들의 가운데 층 | 숫자 |\n| 연식 | 2024년 기준 지은 지 몇 년 | 숫자 |\n| 거래수 | 2024년에 몇 번 거래됐나 | 숫자 |\n| 거래가 | 2024년 거래가 가운데 값 (만원) | 숫자 |\n| 평당가 | 거래가 ÷ 평수 (만원) | 숫자 |\n| 직전변동 | 2024년 상반기 → 하반기 얼마나 올랐나 (%) | 숫자, **573개 빈칸** (반기 거래가 없던 단지) |\n| 동평당가 · 구평당가 | 그 동네·그 구의 2024년 평당가 가운데 값 | 숫자 |\n| 올랐나 | 2025년 거래가가 5% 넘게 올랐으면 1 | **가르칠 명단에만** |"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "---\n# 2 · 글자를 숫자로\n\n두뇌에는 숫자만 들어갑니다. 구를 0~24 번호로 바꾸면 「노원구(8)가 강남구(0)보다 크다」는 엉뚱한 뜻이 되니, 대신 **뜻이 있는 숫자**로 바꿉니다. 강남 3구는 2, 한강 가까운 구는 1, 나머지는 0. 이걸 「입지」라고 부르겠습니다.\n\n| 줄 | 하는 일 | 왜 |\n|---|---|---|\n| `입지등급 = {...}` | 구 이름 → 0 · 1 · 2 | 글자를 뜻 있는 숫자로 |\n| `.map(입지등급).fillna(0)` | 표에 적용, 목록에 없으면 0 | |\n| `['직전변동'].fillna(0)` | 반기 거래가 없어 빈칸이면 0 (변동 없음) | 빈칸은 계산이 안 됩니다 |"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "입지등급 = {'강남구': 2, '서초구': 2, '송파구': 2,\n           '용산구': 1, '성동구': 1, '마포구': 1, '광진구': 1, '동작구': 1, '강동구': 1, '양천구': 1, '영등포구': 1}\n\nfor 명단 in [가르칠명단, 시험명단]:                                  # 두 명단에 똑같이\n    명단['입지']     = 명단['구'].map(입지등급).fillna(0)               # 구 → 0 / 1 / 2\n    명단['직전변동'] = 명단['직전변동'].fillna(0)                        # 반기 거래 없으면 0\n\nprint('남은 빈칸 수 — 숫자 열은 전부 0 이어야 합니다')\nprint(가르칠명단.drop(columns=['이름', '구', '동']).isna().sum().sum(), '·', 시험명단.drop(columns=['이름', '구', '동']).isna().sum().sum())\n가르칠명단[['이름', '입지', '평당가', '직전변동', '올랐나']].head()"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "---\n# 3 · 어떤 열을 두뇌에 넣을까  ← 포인트 ①\n\n처음에는 평당가 · 연식 · 면적 세 개만 씁니다.\n\n| 줄 | 하는 일 |\n|---|---|\n| `쓸열 = [...]` | 두뇌에 넣을 열 목록. 여기만 바꾸면 아래는 따라옵니다 |\n| `.values` | 표 → 숫자 덩어리 |\n| 표준화 | 평당가는 수천, 연식은 수십이라 크기가 다릅니다. (값 − 평균) ÷ 퍼짐 |"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "쓸열 = ['평당가', '연식', '면적']      # ← 넣어 보세요: '입지', '동평당가', '구평당가', '거래가', '거래수', '직전변동', '층'\n\n입력     = 가르칠명단[쓸열].values.astype('float32')\n정답     = 가르칠명단['올랐나'].values.astype('float32')\n시험입력 = 시험명단[쓸열].values.astype('float32')\n\n평균, 퍼짐 = 입력.mean(axis=0), 입력.std(axis=0) + 1e-6   # 가르칠 명단 기준\n입력     = (입력 - 평균) / 퍼짐\n시험입력 = (시험입력 - 평균) / 퍼짐\n\nprint('두뇌에 넣는 열 :', 쓸열)\nprint('입력 모양     :', 입력.shape, ' ← (단지 수, 한 단지당 숫자 개수)')"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "---\n# 4 · 두뇌 만들기  ← 포인트 ②\n\n| 줄 | 하는 일 |\n|---|---|\n| `keras.Input(shape=(len(쓸열),))` | 들어오는 숫자 개수 |\n| `Dense(1, activation='sigmoid')` | 칸 하나. w 곱하고 b 더해 **오를 확률**로 |\n| `compile(loss=…, optimizer=…)` | 틀린 정도를 재는 자, w·b 를 고치는 방법 |\n\n미리 말씀드리면, 이 명단은 **층을 쌓아도 잘 안 오릅니다.** 실제 시장 데이터라 규칙이 「비싼 곳이 더 올랐다」 한 줄에 가깝고, 나머지는 잡음입니다. 층을 쌓으면 잡음까지 외워서 오히려 떨어질 수 있습니다. 직접 확인해 보세요."
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "오름두뇌 = keras.Sequential([\n    keras.Input(shape=(len(쓸열),)),\n                                                     # ← 여기에  keras.layers.Dense(16, activation='relu'),  를 넣으면 층이 하나 늘어납니다\n    keras.layers.Dense(1, activation='sigmoid'),     # 칸 하나. 오를 확률\n])\n오름두뇌.compile(loss='binary_crossentropy', optimizer=keras.optimizers.Adam(0.003), metrics=['accuracy'])\n오름두뇌.summary()"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "---\n# 5 · 가르치기  ← 포인트 ③\n\n| 줄 | 하는 일 |\n|---|---|\n| `.fit(입력, 정답, epochs=100, validation_split=0.2)` | 3,000개를 100번 되풀이. 20% 는 떼어 두고 **처음 보는 문제**로 |\n| `기록.history[...]` | 마지막 연습 점수와 처음 보는 문제 점수 |\n\n연습 점수만 오르고 처음 보는 문제 점수가 안 오르면 외우는 중입니다. epochs 를 줄이세요."
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "기록 = 오름두뇌.fit(입력, 정답, epochs=100, batch_size=32, validation_split=0.2, verbose=0)   # ← 100 을 바꿔 보세요\n\n연습점수 = 기록.history['accuracy'][-1] * 100\n검증점수 = 기록.history['val_accuracy'][-1] * 100\nprint(f'연습 점수 {연습점수:.1f}%  ·  처음 보는 문제 점수 {검증점수:.1f}%')"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "### 댓글에 올리기 (대회 안 해도 됩니다)\n\n아래 셀이 **댓글에 붙여 넣을 한 줄**을 만들어 줍니다. 유튜브 댓글에 그대로 올려 주세요. 남들이 뭘 넣어서 몇 %가 나왔는지 서로 보는 게 이 실습의 절반입니다."
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "층수 = len(오름두뇌.layers) - 1                       # 마지막 칸 빼고 끼운 층 개수\nprint('📋 댓글에 올릴 한 줄 ↓')\nprint(f'처음 보는 문제 {검증점수:.1f}% · 쓸열 {\"+\".join(쓸열)} · 층 {층수}개 · epochs {len(기록.history[\"loss\"])} · 연습 {연습점수:.1f}%')"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "---\n# 6 · 답안지 만들기 · 제출\n\n| 줄 | 하는 일 |\n|---|---|\n| `.predict(시험입력)` | 418개 단지의 오를 확률 |\n| `>= 0.5` | 0.5 이상이면 1(오른다), 아니면 0 |\n| `to_csv` · `files.download` | 답안지.csv 저장 → 내 컴퓨터로 |"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "예측 = (오름두뇌.predict(시험입력, verbose=0).flatten() >= 0.5).astype(int)\n\n답안지 = pd.DataFrame({'번호': 시험명단['번호'], '이름': 시험명단['이름'], '예측': 예측})\n답안지.to_csv('답안지.csv', index=False)\nprint(f'답안지.csv 저장 — {len(답안지)}개 중 {int(예측.sum())}개를 「오른다」로 적었습니다')\n\nfrom google.colab import files\nfiles.download('답안지.csv')\n답안지.head()"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "### 제출 전 확인"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "확인 = pd.read_csv('답안지.csv')\nprint('✓' if len(확인) == 418 else '✗', f'418개 (지금 {len(확인)}개)')\nprint('✓' if set(확인['예측'].unique()) <= {0, 1} else '✗', '예측이 0 과 1 뿐')\nprint('✓' if 확인['번호'].min() == 3001 and 확인['번호'].max() == 3418 else '✗', '번호 3001 ~ 3418')\nprint()\nprint(f'오른다 {int(확인[\"예측\"].sum())}개 · 안 오른다 {int((확인[\"예측\"] == 0).sum())}개   (참고: 실제로는 절반쯤 올랐습니다)')"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "### 순위표에 올리기 (진짜 점수가 궁금하면)\n\nhttps://www.aicitybuilders.com/contest?c=apt 에 로그인하고 **답안지.csv** 를 올리면 시험 명단 418개 기준 진짜 점수가 나옵니다. 댓글에는 그 점수를 올려도 됩니다."
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "name": "python3"
  },
  "language_info": {
   "name": "python"
  },
  "colab": {
   "name": "서울아파트_1년뒤_올랐을까.ipynb"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}