CoolFace
Apppublic

MEG529/BuildingEnergy

sourceHugging Faceupdated 5mo agoView on Hugging Face
0likes
app.py322 linesDownload Raw Back to root
1# -*- coding: utf-8 -*-2"""3建築能耗指標 (EPI) 預測系統 — Hugging Face Spaces 部署版4"""5 6import pandas as pd7import numpy as np8import os9from collections import OrderedDict10import gradio as gr11 12from sklearn.linear_model import LinearRegression13from sklearn.ensemble import RandomForestRegressor14from lightgbm import LGBMRegressor15from catboost import CatBoostRegressor16import xgboost as xgb17 18# ==========================================19# 區塊 1: 載入資料20# ==========================================21path_bdg = '.'22 23electricity = pd.read_csv(os.path.join(path_bdg, '模擬結果.csv'), encoding='big5')24builddata   = pd.read_csv(os.path.join(path_bdg, '外殼參數.csv'), encoding='big5')25HVACdata    = pd.read_csv(os.path.join(path_bdg, '空調參數.csv'), encoding='big5')26 27electricity = electricity.iloc[:, 1:]28builddata   = builddata.iloc[:, 1:]29HVACdata    = HVACdata.iloc[:, 1:]30 31allsingle_meter = pd.concat([electricity, builddata, HVACdata], axis=1)32 33# ==========================================34# 區塊 2: 特徵篩選(相關係數 > 0.1)35# ==========================================36target_columns  = electricity.columns.tolist()37feature_columns = [col for col in allsingle_meter.columns if col not in target_columns]38corr_matrix     = allsingle_meter.corr()39corr_subset     = corr_matrix.loc[feature_columns, target_columns]40 41res_df = corr_subset.unstack().reset_index()42res_df.columns = ['目標變數', '特徵名稱', '相關係數']43res_df = res_df[res_df['相關係數'].abs() > 0.1].sort_values(by='目標變數')44 45target_features_map = res_df.groupby('目標變數')['特徵名稱'].apply(list).to_dict()46all_unique_features = sorted(list(set().union(*target_features_map.values())))47 48# ==========================================49# 區塊 3: 特徵顯示設定(標籤、單位、分組、選項名稱)50# ==========================================51 52# 原始欄位名 -> (顯示名稱, 單位)53FEATURE_LABEL_MAP = {54    '樓板面積':                     ('樓板面積',                   'm²'),55    '地點':                         ('地點',                       ''),56    '方位':                         ('方位',                       '度'),57    '樓層數':                       ('樓層數',                     ''),58    '體型係數(V/F)':                ('體型係數(V/F)',               'm³/m²'),59    '樓高':                         ('樓高',                       'm'),60    '窗牆比 (WWR)':                 ('窗牆比 (WWR)',                ''),61    '遮陽比':                       ('遮陽比',                     ''),62    'SHGC':                         ('SHGC',                       ''),63    '窗戶U值':                      ('窗戶U值',                    'W/m²·K'),64    '外牆U值':                      ('外牆U值',                    'W/m²·K'),65    '屋頂U值':                      ('屋頂U值',                    'W/m²·K'),66    '設備密度':                     ('設備密度',                   'W/m²'),67    '照明密度':                     ('照明密度',                   'W/m²'),68    '人員密度':                     ('人員密度',                   'person/m²'),69    '活動量':                       ('活動量',                     'met'),70    '間隙風':                       ('間隙風',                     'L/s-m²'),71    '室外新風量':                   ('室外新風量',                 'L/s·m²'),72    '冷房設定值':                   ('冷房設定值',                 '°C'),73    '主機類型':                     ('主機類型',                   ''),74    '主機 COP':                     ('主機 COP',                   'kW/kW'),75    '風機效率ATF':                  ('風機效率ATF',                'W·s/L'),76    '冰水泵輸送效率WTF':            ('冰水泵輸送效率WTF',          'W·s/L'),77    '區域泵輸送效率WTF':            ('區域泵輸送效率WTF',          'W·s/L'),78    '冷卻水泵輸送效率WTF':          ('冷卻水泵輸送效率WTF',        'W·s/L'),79    '冷卻水泵輸送效率WTF.1':        ('冷卻水塔效率',               'L/s·kW'),80    '安全係數':                     ('安全係數',                   ''),81    '空氣側變風量系統':             ('空氣側變風量系統',           ''),82    '冰水VWV':                      ('冰水VWV',                    ''),83    '全熱交換器':                   ('全熱交換器',                 ''),84    'CO2濃度控制':                  ('CO2濃度控制',                ''),85    '冷卻水塔濕球溫度接近控制':     ('冷卻水塔濕球溫度接近控制',   ''),86    '冷凍水溫度重設':               ('冷凍水溫度重設',             ''),87    '主機變頻':                     ('主機變頻',                   ''),88}89 90# 原始代碼(數字) -> 顯示名稱(依實際資料中的唯一值設定)91FEATURE_CHOICES_MAP = {92    '地點':                     {1: '臺北', 2: '臺中', 3: '高雄'},93    '主機類型':                 {1: '離心式', 3: '往復式'},94    '主機變頻':                 {0: '無', 1: '主機變頻'},95    '空氣側變風量系統':         {0: '無', 1: '空氣側變風量系統'},96    '冰水VWV':                  {0: '無',97                                 1: '使用三通閥冰水系統',98                                 2: '一次變頻冰水系統',99                                 3: '一次定頻/二次變頻冰水系統(含二次以上)'},100    '全熱交換器':               {0: '無',101                                 1: '無外氣旁通全熱交換器系統',102                                 2: '外氣旁通全熱交換器系統'},103    'CO2濃度控制':              {0: '無', 1: 'CO2濃度控制'},104    '冷卻水塔濕球溫度接近控制': {0: '無',105                                 1: '定頻+出水溫度控制',106                                 2: '定頻+濕球溫度控制',107                                 3: '出水溫度變頻控制',108                                 4: '濕球溫度及水溫變頻控制'},109    '冷凍水溫度重設':           {0: '無', 1: '冷凍水溫度重設'},110}111 112# 分組定義(按截圖順序)113FEATURE_GROUPS = [114    ('建築幾何', ['樓板面積', '地點', '方位', '樓層數', '體型係數(V/F)', '樓高', '窗牆比 (WWR)', '遮陽比']),115    ('外殼',     ['SHGC', '窗戶U值', '外牆U值', '屋頂U值']),116    ('內部負載', ['設備密度', '照明密度', '人員密度', '活動量', '間隙風']),117    ('操作',     ['室外新風量', '冷房設定值']),118    ('HVAC 系統',['主機類型', '主機 COP', '風機效率ATF', '冰水泵輸送效率WTF',119                  '區域泵輸送效率WTF', '冷卻水泵輸送效率WTF', '冷卻水泵輸送效率WTF.1']),120    ('節能措施', ['安全係數', '空氣側變風量系統', '冰水VWV', '全熱交換器',121                  'CO2濃度控制', '冷卻水塔濕球溫度接近控制', '冷凍水溫度重設', '主機變頻']),122]123 124def get_label(feature):125    display, unit = FEATURE_LABEL_MAP.get(feature, (feature, ''))126    return f"{display}(單位: {unit})" if unit else display127 128# ==========================================129# 區塊 4: 訓練模型(啟動時執行一次)130# ==========================================131selected_model_config = {132    'EPIG':  'CatBoost',133    'EPIL':  'LinearRegression',134    'EPIS':  'CatBoost',135    'EPICG': 'LinearRegression',136    'EPIAT': 'LGBM',137    'EPIWT': 'CatBoost',138}139 140def get_model_instance(model_key):141    if model_key == 'LinearRegression': return LinearRegression()142    if model_key == 'RandomForest':     return RandomForestRegressor(random_state=42)143    if model_key == 'LGBM':            return LGBMRegressor(random_state=42, verbose=-1)144    if model_key == 'XGBoost':         return xgb.XGBRegressor(random_state=42)145    if model_key == 'CatBoost':        return CatBoostRegressor(random_state=42, verbose=0, allow_writing_files=False)146    raise ValueError(f"未知模型: {model_key}")147 148print("正在訓練模型,請稍候...")149trained_models = {}150for target, model_key in selected_model_config.items():151    features = target_features_map[target]152    model    = get_model_instance(model_key)153    model.fit(allsingle_meter.iloc[:500][features], electricity.iloc[:500][target])154    trained_models[target] = model155    print(f"  ✅ {target} ({model_key}) 訓練完成")156print("所有模型訓練完成!")157 158# ==========================================159# 區塊 5: 顯示名稱 -> 原始代碼(預測時反查)160# ==========================================161feature_label_to_code = {}162for feat, mapping in FEATURE_CHOICES_MAP.items():163    feature_label_to_code[feat] = {v: k for k, v in mapping.items()}164 165# 依分組收集有進入 all_unique_features 的特徵,保留順序166grouped_features = []167seen = set()168for group, feats in FEATURE_GROUPS:169    for f in feats:170        if f in all_unique_features and f not in seen:171            grouped_features.append((group, f))172            seen.add(f)173for f in all_unique_features:174    if f not in seen:175        grouped_features.append(('其他', f))176 177# ==========================================178# 區塊 6: 預測函數179# ==========================================180def predict(*args):181    user_inputs = {}182    for idx, (group, feature) in enumerate(grouped_features):183        raw = args[idx]184        if feature in feature_label_to_code:185            user_inputs[feature] = feature_label_to_code[feature].get(raw, raw)186        else:187            try:188                user_inputs[feature] = float(raw)189            except Exception:190                user_inputs[feature] = raw191 192    # 下標設定:EPI名稱 -> (前綴, 下標文字)193    EPI_DISPLAY = {194        "EPIG":  ("EPI", "G"),195        "EPIL":  ("EPI", "L"),196        "EPIS":  ("EPI", "S"),197        "EPICG": ("EPI", "CG"),198        "EPIAT": ("EPI", "AT"),199        "EPIWT": ("EPI", "WT"),200    }201    EPI_UNITS = {202        "EPIG":  "kWh/m²·yr",203        "EPIL":  "kWh/m²·yr",204        "EPIS":  "kWh/kWh",205        "EPICG": "kWh/kWh",206        "EPIAT": "kWh/kWh",207        "EPIWT": "kWh/kWh",208    }209    rows = []210    for target, model in trained_models.items():211        prefix, sub = EPI_DISPLAY.get(target, (target, ""))212        unit = EPI_UNITS.get(target, "")213        label_html = f"{prefix}<sub>{sub}</sub>" if sub else prefix214        try:215            needed   = target_features_map[target]216            X_df     = pd.DataFrame([{f: user_inputs[f] for f in needed}])217            pred_val = model.predict(X_df)[0]218            rows.append(219                f"<div class='epi-row'>"220                f"<span class='epi-name'>📊 {label_html}</span>"221                f"<span class='epi-model'>({selected_model_config[target]})</span>"222                f"<span class='epi-value'>{pred_val:.2f} <span class='epi-unit'>{unit}</span></span>"223                f"</div>"224            )225        except Exception as e:226            rows.append(f"<div class='epi-row epi-error'>❌ {label_html}:預測失敗({e})</div>")227    return "<div class='epi-results'>" + "".join(rows) + "</div>"228 229 230# ==========================================231# 區塊 7: 自訂 CSS232# ==========================================233custom_css = """234body { font-family: 'Noto Sans TC', sans-serif; font-size: 1.15em; }235#title-md h2 {236    text-align: center;237    color: #2e7d32;238    font-size: 1.9em;239    font-weight: 700;240    padding: 8px 0 2px 0;241}242.section-header p { color: #1b5e20; font-weight: 700; font-size: 1.2em; margin: 2px 0 4px 0; }243.group-box {244    border: 1.5px solid #a5d6a7;245    border-radius: 10px;246    padding: 10px 16px 4px 16px;247    margin-bottom: 8px;248    background: #f1f8e9;249}250.group-box label { font-size: 1.1em !important; }251.group-box input, .group-box select { font-size: 1.1em !important; }252.gradio-slider { margin-bottom: 4px !important; }253.gradio-dropdown { margin-bottom: 4px !important; }254.predict-btn { background-color: #2e7d32 !important; color: white !important; font-size: 1.15em !important; }255.result-area textarea { font-size: 1.15em !important; line-height: 1.5 !important; }256.epi-results { padding: 8px 4px; }257.epi-row { display: flex; align-items: baseline; gap: 10px; padding: 6px 8px; border-bottom: 1px solid #c8e6c9; font-size: 1.1em; }258.epi-row:last-child { border-bottom: none; }259.epi-name { font-weight: 700; color: #1b5e20; min-width: 90px; }260.epi-model { color: #777; font-size: 0.9em; min-width: 130px; }261.epi-value { font-size: 1.15em; font-weight: 600; color: #2e7d32; }262.epi-unit { font-size: 0.85em; color: #555; font-weight: 400; }263.epi-error { color: #c62828; }264footer { display: none !important; }265"""266 267# ==========================================268# 區塊 8: 組裝 Gradio Blocks269# ==========================================270groups_dict = OrderedDict()271for group, feature in grouped_features:272    groups_dict.setdefault(group, []).append(feature)273 274with gr.Blocks(css=custom_css, title="建築能效快速評估平台-FCU系統") as demo:275    gr.Markdown("## 建築能效快速評估平台-FCU系統", elem_id="title-md")276    gr.Markdown("### 輸入參數", elem_classes=["section-header"])277 278    all_comp_ordered = []279 280    for group_name, feats in groups_dict.items():281        with gr.Group(elem_classes=["group-box"]):282            gr.Markdown(f"**{group_name}**")283            pairs = [feats[i:i+2] for i in range(0, len(feats), 2)]284            for pair in pairs:285                with gr.Row():286                    for feature in pair:287                        label   = get_label(feature)288                        data    = allsingle_meter[feature]289                        mapping = FEATURE_CHOICES_MAP.get(feature)290 291                        if mapping:292                            labels_list = list(mapping.values())293                            comp = gr.Dropdown(choices=labels_list,294                                               value=labels_list[0],295                                               label=label)296                        elif pd.api.types.is_string_dtype(data) or data.nunique() <= 10:297                            choices = [str(x) for x in sorted(data.unique().tolist())]298                            comp = gr.Dropdown(choices=choices,299                                               value=choices[0],300                                               label=label)301                        else:302                            mn  = float(data.min())303                            mx  = float(data.max())304                            dp  = max((len(str(float(v)).rstrip("0").split(".")[1])305                                       if "." in str(float(v)) else 0)306                                      for v in data.unique())307                            stp = 1 if dp == 0 else round(10 ** -dp, dp)308                            mv  = round(float(data.mean()), dp)309                            comp = gr.Slider(minimum=mn, maximum=mx,310                                             step=stp, value=mv, label=label)311                        all_comp_ordered.append(comp)312 313    gr.Markdown("---")314    gr.Markdown("### 模型預測結果")315    predict_btn = gr.Button("🔍 進行預測", elem_classes=["predict-btn"])316    result_box  = gr.HTML(value="<div class='epi-results' style='color:#888;padding:8px;'>請輸入參數後按下「進行預測」</div>",317                          elem_classes=["result-area"])318 319    predict_btn.click(fn=predict, inputs=all_comp_ordered, outputs=result_box)320 321demo.launch()322