evanskim113 commited on
Commit
76f8627
ยท
verified ยท
1 Parent(s): 20b964c

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +49 -64
src/streamlit_app.py CHANGED
@@ -1,5 +1,5 @@
1
  # =============================================================
2
- # โšฝ LightGBM 3-Class ์˜ˆ์ธก + ์œ ์‚ฌ ๊ฒฝ๊ธฐ ๋ถ„ํฌ (Full Integration, Mobile-Fix)
3
  # =============================================================
4
  import streamlit as st
5
  import pandas as pd
@@ -9,23 +9,20 @@ import joblib
9
  # ===============================
10
  # ์•ฑ ๊ธฐ๋ณธ ์„ค์ • + ๋ชจ๋ฐ”์ผ ํ‘œ์‹œ ๋ฒ„๊ทธ ๋Œ€์‘ CSS/JS
11
  # ===============================
12
- st.set_page_config(page_title="โšฝ LightGBM ์˜ˆ์ธก + ์œ ์‚ฌ ๊ฒฝ๊ธฐ ํƒ์ƒ‰๊ธฐ", layout="wide")
13
 
14
  # ๐Ÿ”ง ๋ชจ๋ฐ”์ผ์—์„œ ํ…์ŠคํŠธ/๋ฉ”ํŠธ๋ฆญ์ด ์ž˜๋ฆฌ์ง€ ์•Š๋„๋ก overflow/zoom/๊ธ€๊ผด ๊ณ ์ • + ์ดˆ๊ธฐ ๋ฆฌ์‚ฌ์ด์ฆˆ ํŠธ๋ฆฌ๊ฑฐ
15
  st.markdown(
16
  """
17
  <style>
18
- /* ์ „์—ญ์ ์œผ๋กœ overflow ์ˆจ๊น€์„ ํ•ด์ œ */
19
  .stApp, .block-container, [data-testid="stVerticalBlock"], [data-testid="column"] {
20
  overflow: visible !important;
21
  }
22
- /* ๋ชจ๋ฐ”์ผ์—์„œ ์—ฌ๋ฐฑ/์Šค์ผ€์ผ ๋ณด์ • */
23
  @media (max-width: 768px) {
24
  .block-container {
25
  padding-left: 1rem !important;
26
  padding-right: 1rem !important;
27
  }
28
- /* ํ™•๋ฅ  ํ‘œ์‹œ์— ์“ฐ๋Š” ํ…์ŠคํŠธ ๋ฐ•์Šค ๊ณตํ†ต ์Šคํƒ€์ผ */
29
  .prob-pill {
30
  font-size: 1.1rem;
31
  color: #ffffff;
@@ -43,7 +40,6 @@ st.markdown(
43
  }
44
  </style>
45
  <script>
46
- // ์ฒซ ๋ Œ๋” ์งํ›„ ๊ฐ•์ œ ๋ฆฌ์‚ฌ์ด์ฆˆ๋กœ ๋ ˆ์ด์•„์›ƒ ์žฌ๊ณ„์‚ฐ ์œ ๋„ (๋ชจ๋ฐ”์ผ ํ‘œ์‹œ ๋ˆ„๋ฝ ๋ฐฉ์ง€)
47
  window.addEventListener('load', () => {
48
  setTimeout(() => { window.dispatchEvent(new Event('resize')); }, 150);
49
  });
@@ -52,16 +48,16 @@ st.markdown(
52
  unsafe_allow_html=True,
53
  )
54
 
55
- st.title("โšฝ LightGBM ์˜ˆ์ธก + ์œ ์‚ฌ ๊ฒฝ๊ธฐ ๋ถ„ํฌ + ์œ ์‚ฌ ๊ฒฝ๊ธฐ ํƒ์ƒ‰๊ธฐ")
56
 
57
  EQ_DECIMALS = 2
58
  def eq(a, b, decimals=EQ_DECIMALS):
59
  return np.round(a, decimals) == np.round(b, decimals)
60
 
61
  # ===============================
62
- # Feature ๋ชฉ๋ก
63
  # ===============================
64
- expected_cols_base59 = [
65
  'norm_win','norm_draw','norm_lose','mean_odds','std_odds','cv_odds',
66
  'p_win','p_draw','p_lose','overround','entropy','spread','spread_draw',
67
  'odds_ratio_wd','odds_ratio_wl','odds_ratio_dl','draw_prob_ratio','draw_ratio',
@@ -71,9 +67,7 @@ expected_cols_base59 = [
71
  'draw_margin','fav_ratio','draw_skew','log_spread','draw_entropy_component','dominance_score',
72
  'hmean_odds','hstd_odds','hcv_odds','hentropy','hspread','hspread_draw',
73
  'hp_win','hp_draw','hp_lose','hp_win_norm','hp_draw_norm','hp_lose_norm','hoverround',
74
- 'diff_win_prob','diff_draw_prob','diff_lose_prob','diff_draw_odds'
75
- ]
76
- expected_cols_handicap65 = expected_cols_base59 + [
77
  'base_win_odds','base_draw_odds','base_lose_odds',
78
  'base_overround_ex','base_entropy_ex','base_spread_ex'
79
  ]
@@ -84,9 +78,7 @@ expected_cols_handicap65 = expected_cols_base59 + [
84
  def build_feature_dict(win, draw, lose, hwin, hdraw, hlose):
85
  d = {}
86
  denom = (win+draw+lose)
87
- d['norm_win'] = win/denom
88
- d['norm_draw'] = draw/denom
89
- d['norm_lose'] = lose/denom
90
  d['mean_odds'] = np.mean([win,draw,lose])
91
  d['std_odds'] = np.std([win,draw,lose])
92
  d['cv_odds'] = d['std_odds']/d['mean_odds'] if d['mean_odds']>0 else 0
@@ -98,37 +90,35 @@ def build_feature_dict(win, draw, lose, hwin, hdraw, hlose):
98
  d['spread'] = max(win,draw,lose)-min(win,draw,lose)
99
  d['spread_draw'] = abs(draw-(win+lose)/2)
100
  d['odds_ratio_wd'],d['odds_ratio_wl'],d['odds_ratio_dl']=win/draw,win/lose,draw/lose
101
- d['draw_prob_ratio'] = d['p_draw']/max(d['p_win'],d['p_lose'])
102
- d['draw_ratio'] = draw/min(win,lose)
103
- d['draw_prob_gap'] = abs(d['p_draw']-(d['p_win']+d['p_lose'])/2)
104
- d['fav_gap'] = abs(win-lose)
105
- d['fav_draw_gap'] = abs(draw-min(win,lose))
106
- d['fav_diff'] = abs(win-lose)
107
- d['draw_gap_mean'] = abs(draw-d['mean_odds'])
108
- d['rank_win'],d['rank_draw'],d['rank_lose'] = pd.Series([win,draw,lose]).rank().tolist()
109
- d['ev_win'],d['ev_draw'],d['ev_lose'] = win*d['p_win_norm'],draw*d['p_draw_norm'],lose*d['p_lose_norm']
110
- d['draw_vs_avg'] = draw/d['mean_odds']
111
- d['draw_vs_max'] = draw/max(win,draw,lose)
112
- d['cv_spread'] = d['spread']/d['mean_odds']
113
- d['cv_draw_gap'] = d['fav_draw_gap']/d['mean_odds']
114
- d['draw_margin'] = abs(draw-(win+lose)/2)
115
- d['fav_ratio'] = min(win,lose)/max(win,lose)
116
- d['draw_skew'] = (draw-win)-(lose-draw)
117
- d['log_spread'] = np.log(max(win,draw,lose))-np.log(min(win,draw,lose))
118
- d['draw_entropy_component'] = -d['p_draw_norm']*np.log(d['p_draw_norm'])
119
- d['dominance_score'] = max(d['p_win_norm'],d['p_lose_norm'])-d['p_draw_norm']
120
-
121
- d['hmean_odds'] = np.mean([hwin,hdraw,hlose])
122
- d['hstd_odds'] = np.std([hwin,hdraw,hlose])
123
- d['hcv_odds'] = d['hstd_odds']/d['hmean_odds'] if d['hmean_odds']>0 else 0
124
- p_h = 1/np.array([hwin,hdraw,hlose])
125
- p_hn = p_h/p_h.sum()
126
- d['hp_win'],d['hp_draw'],d['hp_lose'] = p_h
127
- d['hp_win_norm'],d['hp_draw_norm'],d['hp_lose_norm'] = p_hn
128
- d['hoverround'] = p_h.sum()
129
- d['hentropy'] = -np.sum(p_hn*np.log(p_hn))
130
- d['hspread'] = max(hwin,hdraw,hlose)-min(hwin,hdraw,hlose)
131
- d['hspread_draw'] = abs(hdraw-(hwin+hlose)/2)
132
  d['diff_win_prob']=d['p_win_norm']-d['hp_win_norm']
133
  d['diff_draw_prob']=d['p_draw_norm']-d['hp_draw_norm']
134
  d['diff_lose_prob']=d['p_lose_norm']-d['hp_lose_norm']
@@ -137,29 +127,30 @@ def build_feature_dict(win, draw, lose, hwin, hdraw, hlose):
137
  d['base_overround_ex'],d['base_entropy_ex'],d['base_spread_ex']=p_tot,d['entropy'],d['spread']
138
  return d
139
 
140
- def build_feature_frames(win,draw,lose,hwin,hdraw,hlose):
141
- d = build_feature_dict(win,draw,lose,hwin,hdraw,hlose)
142
- df_all = pd.DataFrame([d])
143
- return df_all[expected_cols_base59], df_all[expected_cols_handicap65]
144
 
145
  # ===============================
146
- # ๋ชจ๋ธ ๋กœ๋“œ
147
  # ===============================
148
  @st.cache_resource
149
  def load_models():
150
- base = joblib.load("lgbm_model_base_65.pkl")
151
- hand = joblib.load("lgbm_model_handicap_65.pkl")
152
  enc = joblib.load("label_encoder_handicap.pkl")
153
  return base, hand, enc
 
154
  model_base, model_hand, encoder_hand = load_models()
155
 
156
  # ===============================
157
  # ์˜ˆ์ธก
158
  # ===============================
159
  def predict_all(win, draw, lose, hwin, hdraw, hlose):
160
- df_base, df_hand = build_feature_frames(win, draw, lose, hwin, hdraw, hlose)
161
- probs_base = model_base.predict_proba(df_base)[0]
162
- probs_hand = model_hand.predict_proba(df_hand)[0]
163
  return dict(zip(["์Šน","๋ฌด","ํŒจ"], probs_base)), dict(zip(["ํ•ธ๋”” ์Šน","ํ•ธ๋”” ๋ฌด","ํ•ธ๋”” ํŒจ"], probs_hand))
164
 
165
  # ===============================
@@ -257,23 +248,17 @@ st.divider()
257
 
258
  # ===============================
259
  # ๐Ÿ”ข ํ™•๋ฅ  ํ‘œ์‹œ (๋ชจ๋ฐ”์ผ ์™„์ „ ํ˜ธํ™˜)
260
- # - columns ์‚ฌ์šฉ ์•ˆ ํ•จ
261
- # - markdown/HTML๋งŒ ์‚ฌ์šฉ
262
- # - ํŽ˜์ด์ง€ ํ•˜๋‹จ์—์„œ ๋ Œ๋” (๋ชจ๋ฐ”์ผ ๋ Œ๋” ํƒ€์ด๋ฐ ์•ˆ์ •)
263
  # ===============================
264
  base_probs, hand_probs = predict_all(win, draw, lose, hwin, hdraw, hlose)
265
 
266
- # ์„ ํƒ์ ์œผ๋กœ ๋ชจ๋ฐ”์ผ ๊ฐ•์ œ ํ˜ธํ™˜ ๋ชจ๋“œ(ํ…์ŠคํŠธ๋งŒ) ํ† ๊ธ€
267
- mobile_text_mode = st.toggle("๐Ÿ“ฑ ๋ชจ๋ฐ”์ผ ๊ฐ•์ œ ํ˜ธํ™˜ ๋ชจ๋“œ(ํ…์ŠคํŠธ๋งŒ)", value=True, help="๋ชจ๋ฐ”์ผ์—์„œ ๋ˆ„๋ฝ๋  ๊ฒฝ์šฐ ์ผœ๋‘์„ธ์š”")
268
 
269
  st.markdown('<div class="prob-section">', unsafe_allow_html=True)
270
  st.markdown("### โšฝ ๊ธฐ๋ณธ ์Šน/๋ฌด/ํŒจ ํ™•๋ฅ ")
271
  if mobile_text_mode:
272
- # ํ…์ŠคํŠธ๋งŒ (๊ฐ€์žฅ ์•ˆ์ „)
273
  for k, emoji in zip(["์Šน","๋ฌด","ํŒจ"], ["๐ŸŸข","๐ŸŸก","๐Ÿ”ด"]):
274
  st.markdown(f"{emoji} **{k}** : {base_probs[k]*100:.2f}%")
275
  else:
276
- # pill ์Šคํƒ€์ผ (๋ชจ๋ฐ”์ผ์—์„œ๋„ ์ž˜ ๋ณด์ด๋„๋ก ์ปค์Šคํ…€)
277
  html = "".join([f'<span class="prob-pill"><b>{k}</b>: {base_probs[k]*100:.2f}%</span>'
278
  for k in ["์Šน","๋ฌด","ํŒจ"]])
279
  st.markdown(html, unsafe_allow_html=True)
@@ -288,4 +273,4 @@ else:
288
  st.markdown(html, unsafe_allow_html=True)
289
  st.markdown('</div>', unsafe_allow_html=True)
290
 
291
- st.caption("โ“’ LightGBM 3-Class Softmax Models | ๊ธฐ๋ณธ: 59ํ”ผ์ฒ˜, ํ•ธ๋””: 65ํ”ผ์ฒ˜")
 
1
  # =============================================================
2
+ # โšฝ XGBoost 3-Class ์˜ˆ์ธก + ์œ ์‚ฌ ๊ฒฝ๊ธฐ ๋ถ„ํฌ (Full Integration, Mobile-Fix)
3
  # =============================================================
4
  import streamlit as st
5
  import pandas as pd
 
9
  # ===============================
10
  # ์•ฑ ๊ธฐ๋ณธ ์„ค์ • + ๋ชจ๋ฐ”์ผ ํ‘œ์‹œ ๋ฒ„๊ทธ ๋Œ€์‘ CSS/JS
11
  # ===============================
12
+ st.set_page_config(page_title="โšฝ XGBoost ์˜ˆ์ธก + ์œ ์‚ฌ ๊ฒฝ๊ธฐ ํƒ์ƒ‰๊ธฐ", layout="wide")
13
 
14
  # ๐Ÿ”ง ๋ชจ๋ฐ”์ผ์—์„œ ํ…์ŠคํŠธ/๋ฉ”ํŠธ๋ฆญ์ด ์ž˜๋ฆฌ์ง€ ์•Š๋„๋ก overflow/zoom/๊ธ€๊ผด ๊ณ ์ • + ์ดˆ๊ธฐ ๋ฆฌ์‚ฌ์ด์ฆˆ ํŠธ๋ฆฌ๊ฑฐ
15
  st.markdown(
16
  """
17
  <style>
 
18
  .stApp, .block-container, [data-testid="stVerticalBlock"], [data-testid="column"] {
19
  overflow: visible !important;
20
  }
 
21
  @media (max-width: 768px) {
22
  .block-container {
23
  padding-left: 1rem !important;
24
  padding-right: 1rem !important;
25
  }
 
26
  .prob-pill {
27
  font-size: 1.1rem;
28
  color: #ffffff;
 
40
  }
41
  </style>
42
  <script>
 
43
  window.addEventListener('load', () => {
44
  setTimeout(() => { window.dispatchEvent(new Event('resize')); }, 150);
45
  });
 
48
  unsafe_allow_html=True,
49
  )
50
 
51
+ st.title("โšฝ XGBoost ์˜ˆ์ธก + ์œ ์‚ฌ ๊ฒฝ๊ธฐ ๋ถ„ํฌ + ์œ ์‚ฌ ๊ฒฝ๊ธฐ ํƒ์ƒ‰๊ธฐ")
52
 
53
  EQ_DECIMALS = 2
54
  def eq(a, b, decimals=EQ_DECIMALS):
55
  return np.round(a, decimals) == np.round(b, decimals)
56
 
57
  # ===============================
58
+ # Feature ๋ชฉ๋ก (65ํ”ผ์ฒ˜ ํ†ตํ•ฉ)
59
  # ===============================
60
+ expected_cols_65 = [
61
  'norm_win','norm_draw','norm_lose','mean_odds','std_odds','cv_odds',
62
  'p_win','p_draw','p_lose','overround','entropy','spread','spread_draw',
63
  'odds_ratio_wd','odds_ratio_wl','odds_ratio_dl','draw_prob_ratio','draw_ratio',
 
67
  'draw_margin','fav_ratio','draw_skew','log_spread','draw_entropy_component','dominance_score',
68
  'hmean_odds','hstd_odds','hcv_odds','hentropy','hspread','hspread_draw',
69
  'hp_win','hp_draw','hp_lose','hp_win_norm','hp_draw_norm','hp_lose_norm','hoverround',
70
+ 'diff_win_prob','diff_draw_prob','diff_lose_prob','diff_draw_odds',
 
 
71
  'base_win_odds','base_draw_odds','base_lose_odds',
72
  'base_overround_ex','base_entropy_ex','base_spread_ex'
73
  ]
 
78
  def build_feature_dict(win, draw, lose, hwin, hdraw, hlose):
79
  d = {}
80
  denom = (win+draw+lose)
81
+ d['norm_win'], d['norm_draw'], d['norm_lose'] = win/denom, draw/denom, lose/denom
 
 
82
  d['mean_odds'] = np.mean([win,draw,lose])
83
  d['std_odds'] = np.std([win,draw,lose])
84
  d['cv_odds'] = d['std_odds']/d['mean_odds'] if d['mean_odds']>0 else 0
 
90
  d['spread'] = max(win,draw,lose)-min(win,draw,lose)
91
  d['spread_draw'] = abs(draw-(win+lose)/2)
92
  d['odds_ratio_wd'],d['odds_ratio_wl'],d['odds_ratio_dl']=win/draw,win/lose,draw/lose
93
+ d['draw_prob_ratio']=d['p_draw']/max(d['p_win'],d['p_lose'])
94
+ d['draw_ratio']=draw/min(win,lose)
95
+ d['draw_prob_gap']=abs(d['p_draw']-(d['p_win']+d['p_lose'])/2)
96
+ d['fav_gap']=abs(win-lose)
97
+ d['fav_draw_gap']=abs(draw-min(win,lose))
98
+ d['fav_diff']=abs(win-lose)
99
+ d['draw_gap_mean']=abs(draw-d['mean_odds'])
100
+ d['rank_win'],d['rank_draw'],d['rank_lose']=pd.Series([win,draw,lose]).rank().tolist()
101
+ d['ev_win'],d['ev_draw'],d['ev_lose']=win*d['p_win_norm'],draw*d['p_draw_norm'],lose*d['p_lose_norm']
102
+ d['draw_vs_avg']=draw/d['mean_odds']
103
+ d['draw_vs_max']=draw/max(win,draw,lose)
104
+ d['cv_spread']=d['spread']/d['mean_odds']
105
+ d['cv_draw_gap']=d['fav_draw_gap']/d['mean_odds']
106
+ d['draw_margin']=abs(draw-(win+lose)/2)
107
+ d['fav_ratio']=min(win,lose)/max(win,lose)
108
+ d['draw_skew']=(draw-win)-(lose-draw)
109
+ d['log_spread']=np.log(max(win,draw,lose))-np.log(min(win,draw,lose))
110
+ d['draw_entropy_component']=-d['p_draw_norm']*np.log(d['p_draw_norm'])
111
+ d['dominance_score']=max(d['p_win_norm'],d['p_lose_norm'])-d['p_draw_norm']
112
+ d['hmean_odds']=np.mean([hwin,hdraw,hlose])
113
+ d['hstd_odds']=np.std([hwin,hdraw,hlose])
114
+ d['hcv_odds']=d['hstd_odds']/d['hmean_odds'] if d['hmean_odds']>0 else 0
115
+ p_h=1/np.array([hwin,hdraw,hlose]); p_hn=p_h/p_h.sum()
116
+ d['hp_win'],d['hp_draw'],d['hp_lose']=p_h
117
+ d['hp_win_norm'],d['hp_draw_norm'],d['hp_lose_norm']=p_hn
118
+ d['hoverround']=p_h.sum()
119
+ d['hentropy']=-np.sum(p_hn*np.log(p_hn))
120
+ d['hspread']=max(hwin,hdraw,hlose)-min(hwin,hdraw,hlose)
121
+ d['hspread_draw']=abs(hdraw-(hwin+hlose)/2)
 
 
122
  d['diff_win_prob']=d['p_win_norm']-d['hp_win_norm']
123
  d['diff_draw_prob']=d['p_draw_norm']-d['hp_draw_norm']
124
  d['diff_lose_prob']=d['p_lose_norm']-d['hp_lose_norm']
 
127
  d['base_overround_ex'],d['base_entropy_ex'],d['base_spread_ex']=p_tot,d['entropy'],d['spread']
128
  return d
129
 
130
+ def build_feature_frame(win, draw, lose, hwin, hdraw, hlose):
131
+ d = build_feature_dict(win, draw, lose, hwin, hdraw, hlose)
132
+ df = pd.DataFrame([d])
133
+ return df[expected_cols_65]
134
 
135
  # ===============================
136
+ # ๋ชจ๋ธ ๋กœ๋“œ (XGBoost)
137
  # ===============================
138
  @st.cache_resource
139
  def load_models():
140
+ base = joblib.load("xgb_model_wdl_softmax.pkl")
141
+ hand = joblib.load("xgb_model_handicap_65f.pkl")
142
  enc = joblib.load("label_encoder_handicap.pkl")
143
  return base, hand, enc
144
+
145
  model_base, model_hand, encoder_hand = load_models()
146
 
147
  # ===============================
148
  # ์˜ˆ์ธก
149
  # ===============================
150
  def predict_all(win, draw, lose, hwin, hdraw, hlose):
151
+ df_feat = build_feature_frame(win, draw, lose, hwin, hdraw, hlose)
152
+ probs_base = model_base.predict_proba(df_feat.values)[0]
153
+ probs_hand = model_hand.predict_proba(df_feat.values)[0]
154
  return dict(zip(["์Šน","๋ฌด","ํŒจ"], probs_base)), dict(zip(["ํ•ธ๋”” ์Šน","ํ•ธ๋”” ๋ฌด","ํ•ธ๋”” ํŒจ"], probs_hand))
155
 
156
  # ===============================
 
248
 
249
  # ===============================
250
  # ๐Ÿ”ข ํ™•๋ฅ  ํ‘œ์‹œ (๋ชจ๋ฐ”์ผ ์™„์ „ ํ˜ธํ™˜)
 
 
 
251
  # ===============================
252
  base_probs, hand_probs = predict_all(win, draw, lose, hwin, hdraw, hlose)
253
 
254
+ mobile_text_mode = st.toggle("๐Ÿ“ฑ ๋ชจ๋ฐ”์ผ ๊ฐ•์ œ ํ˜ธํ™˜ ๋ชจ๋“œ(ํ…์ŠคํŠธ๋งŒ)", value=True)
 
255
 
256
  st.markdown('<div class="prob-section">', unsafe_allow_html=True)
257
  st.markdown("### โšฝ ๊ธฐ๋ณธ ์Šน/๋ฌด/ํŒจ ํ™•๋ฅ ")
258
  if mobile_text_mode:
 
259
  for k, emoji in zip(["์Šน","๋ฌด","ํŒจ"], ["๐ŸŸข","๐ŸŸก","๐Ÿ”ด"]):
260
  st.markdown(f"{emoji} **{k}** : {base_probs[k]*100:.2f}%")
261
  else:
 
262
  html = "".join([f'<span class="prob-pill"><b>{k}</b>: {base_probs[k]*100:.2f}%</span>'
263
  for k in ["์Šน","๋ฌด","ํŒจ"]])
264
  st.markdown(html, unsafe_allow_html=True)
 
273
  st.markdown(html, unsafe_allow_html=True)
274
  st.markdown('</div>', unsafe_allow_html=True)
275
 
276
+ st.caption("โ“’ XGBoost 3-Class Softmax Models | ๊ธฐ๋ณธยทํ•ธ๋”” 65ํ”ผ์ฒ˜ ํ†ตํ•ฉํ˜•")