kikikara commited on
Commit
f38d876
ยท
verified ยท
1 Parent(s): 408010c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +232 -5
app.py CHANGED
@@ -1,6 +1,233 @@
1
- # app.py (์ตœ์†Œ ๊ธฐ๋Šฅ ํ…Œ์ŠคํŠธ์šฉ)
2
- import streamlit as st
 
 
 
 
 
3
 
4
- st.title("Hugging Face Spaces ํ…Œ์ŠคํŠธ ์•ฑ")
5
- st.write("์ด ๋ฉ”์‹œ์ง€๊ฐ€ ๋ณด์ด๋ฉด Streamlit์ด ์ •์ƒ์ ์œผ๋กœ ์‹คํ–‰๋œ ๊ฒƒ์ž…๋‹ˆ๋‹ค!")
6
- st.balloons() # ์„ฑ๊ณต ์‹œ ํ’์„  ํšจ๊ณผ
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ import joblib
4
+ import torch
5
+ import numpy as np
6
+ import html
7
+ from transformers import AutoTokenizer, AutoModel, logging as hf_logging
8
 
9
+ # Hugging Face Transformers ๋กœ๊น… ๋ ˆ๋ฒจ ์„ค์ •
10
+ hf_logging.set_verbosity_error()
11
+
12
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ์„ค์ • โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
13
+ MODEL_NAME = "bert-base-uncased"
14
+ DEVICE = "cpu"
15
+ SAVE_DIR = "์ €์žฅ์ €์žฅ1" # ์ด ํด๋”๊ฐ€ app.py์™€ ๊ฐ™์€ ์œ„์น˜์— ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
16
+ LAYER_ID = 4
17
+ SEED = 0
18
+ CLF_NAME = "linear"
19
+
20
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ์ „์—ญ ๋ชจ๋ธ ๋กœ๋“œ (Gradio ์•ฑ ์‹œ์ž‘ ์‹œ ํ•œ ๋ฒˆ ์‹คํ–‰) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
21
+ # Streamlit์˜ @st.cache_resource ๋Œ€์‹ , ์•ฑ ์‹œ์ž‘ ์‹œ ๋กœ๋“œ๋˜๋„๋ก ์ „์—ญ ๋ณ€์ˆ˜๋กœ ๊ด€๋ฆฌ
22
+ TOKENIZER_GLOBAL = None
23
+ MODEL_GLOBAL = None
24
+ W_GLOBAL, MU_GLOBAL, W_P_GLOBAL, B_P_GLOBAL = None, None, None, None
25
+ CLASS_NAMES_GLOBAL = None
26
+ MODELS_LOADED_SUCCESSFULLY = False
27
+ MODEL_LOADING_ERROR_MESSAGE = ""
28
+
29
+ try:
30
+ print("Gradio App: ๋ชจ๋ธ ๋กœ๋”ฉ์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค...")
31
+ lda_file_path = os.path.join(SAVE_DIR, f"lda_layer{LAYER_ID}_seed{SEED}.pkl")
32
+ clf_file_path = os.path.join(SAVE_DIR, f"{CLF_NAME}_layer{LAYER_ID}_projlda_seed{SEED}.pkl")
33
+
34
+ if not os.path.isdir(SAVE_DIR):
35
+ raise FileNotFoundError(f"์˜ค๋ฅ˜: ๋ชจ๋ธ ์ €์žฅ ๋””๋ ‰ํ† ๋ฆฌ '{SAVE_DIR}'๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. '์ €์žฅ์ €์žฅ1' ํด๋”๋ฅผ ํ™•์ธํ•˜์„ธ์š”.")
36
+ if not os.path.exists(lda_file_path):
37
+ raise FileNotFoundError(f"์˜ค๋ฅ˜: LDA ๋ชจ๋ธ ํŒŒ์ผ '{lda_file_path}'๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
38
+ if not os.path.exists(clf_file_path):
39
+ raise FileNotFoundError(f"์˜ค๋ฅ˜: ๋ถ„๋ฅ˜๊ธฐ ๋ชจ๋ธ ํŒŒ์ผ '{clf_file_path}'๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
40
+
41
+ lda = joblib.load(lda_file_path)
42
+ clf = joblib.load(clf_file_path)
43
+
44
+ if hasattr(clf, "base_estimator"):
45
+ clf = clf.base_estimator
46
+
47
+ W_GLOBAL = torch.tensor(lda.scalings_, dtype=torch.float32, device=DEVICE)
48
+ MU_GLOBAL = torch.tensor(lda.xbar_, dtype=torch.float32, device=DEVICE)
49
+ W_P_GLOBAL = torch.tensor(clf.coef_, dtype=torch.float32, device=DEVICE)
50
+ B_P_GLOBAL = torch.tensor(clf.intercept_, dtype=torch.float32, device=DEVICE)
51
+
52
+ TOKENIZER_GLOBAL = AutoTokenizer.from_pretrained(MODEL_NAME, use_fast=True)
53
+ MODEL_GLOBAL = AutoModel.from_pretrained(
54
+ MODEL_NAME, output_hidden_states=True
55
+ ).to(DEVICE).eval()
56
+
57
+ if hasattr(lda, 'classes_'):
58
+ CLASS_NAMES_GLOBAL = lda.classes_
59
+ elif hasattr(clf, 'classes_'):
60
+ CLASS_NAMES_GLOBAL = clf.classes_
61
+
62
+ MODELS_LOADED_SUCCESSFULLY = True
63
+ print("Gradio App: ๋ชจ๋“  ๋ชจ๋ธ ๋ฐ ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์„ฑ๊ณต!")
64
+
65
+ except Exception as e:
66
+ MODEL_LOADING_ERROR_MESSAGE = f"๋ชจ๋ธ ๋กœ๋”ฉ ์ค‘ ์‹ฌ๊ฐํ•œ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}\n'์ €์žฅ์ €์žฅ1' ํด๋”์™€ ๋‚ด์šฉ๋ฌผ์„ ํ™•์ธํ•ด์ฃผ์„ธ์š”."
67
+ print(MODEL_LOADING_ERROR_MESSAGE)
68
+ # ์ด ์˜ค๋ฅ˜๋Š” Gradio UI๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž์—๊ฒŒ ์ „๋‹ฌ๋  ์ˆ˜ ์žˆ๋„๋ก ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
69
+
70
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ํ•ต์‹ฌ ๋ถ„์„ ํ•จ์ˆ˜ (Gradio ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ํ˜ธ์ถœ) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
71
+ def analyze_sentence_for_gradio(sentence_text, top_k_value):
72
+ if not MODELS_LOADED_SUCCESSFULLY:
73
+ # ๋ชจ๋ธ ๋กœ๋”ฉ ์‹คํŒจ ์‹œ Gradio ์ถœ๋ ฅ ํ˜•์‹์— ๋งž์ถฐ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€ ๋ฐ˜ํ™˜
74
+ error_html = f"<p style='color:red;'>์ดˆ๊ธฐํ™” ์˜ค๋ฅ˜: {html.escape(MODEL_LOADING_ERROR_MESSAGE)}</p>"
75
+ # Gradio Interface๋Š” ์ •์˜๋œ ๋ชจ๋“  ์ถœ๋ ฅ์— ๋Œ€ํ•ด ๊ฐ’์„ ๋ฐ›์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.
76
+ return error_html, "๋ชจ๋ธ ๋กœ๋”ฉ ์‹คํŒจ", "N/A", [] # HTML, ์˜ˆ์ธก๊ฒฐ๊ณผํ…์ŠคํŠธ, ์ƒ์„ธ๊ฒฐ๊ณผ(Label), TopK(DataFrame)
77
+
78
+ try:
79
+ # ์ „์—ญ์—์„œ ๋กœ๋“œ๋œ ๋ชจ๋ธ ์‚ฌ์šฉ
80
+ tokenizer = TOKENIZER_GLOBAL
81
+ model = MODEL_GLOBAL
82
+ W, mu, w_p, b_p = W_GLOBAL, MU_GLOBAL, W_P_GLOBAL, B_P_GLOBAL
83
+ class_names = CLASS_NAMES_GLOBAL
84
+
85
+ # 1) ํ† ํฐํ™”
86
+ enc = tokenizer(sentence_text, return_tensors="pt", truncation=True, max_length=510, padding=True)
87
+ input_ids = enc["input_ids"].to(DEVICE)
88
+ attn_mask = enc["attention_mask"].to(DEVICE)
89
+
90
+ if input_ids.shape[1] == 0:
91
+ return "<p style='color:orange;'>์ž…๋ ฅ ์˜ค๋ฅ˜: ์œ ํšจํ•œ ํ† ํฐ์ด ์—†์Šต๋‹ˆ๋‹ค.</p>", "์ž…๋ ฅ ์˜ค๋ฅ˜", "N/A", []
92
+
93
+ # 2) ์ž„๋ฒ ๋”ฉ ๋ฐ ๊ทธ๋ž˜๋””์–ธํŠธ ์„ค์ •
94
+ input_embeds = model.embeddings.word_embeddings(input_ids).clone().detach()
95
+ input_embeds.requires_grad_(True)
96
+
97
+ # 3) Forward pass
98
+ outputs = model(inputs_embeds=input_embeds, attention_mask=attn_mask, output_hidden_states=True)
99
+ cls_vec = outputs.hidden_states[LAYER_ID][:, 0, :]
100
+
101
+ # 4) LDA ํˆฌ์˜ ๋ฐ ๋ถ„๋ฅ˜
102
+ z_projected = (cls_vec - mu) @ W
103
+ logit_output = z_projected @ w_p.T + b_p
104
+ probs = torch.softmax(logit_output, dim=1)
105
+ pred_idx = torch.argmax(probs, dim=1).item()
106
+ pred_prob_val = probs[0, pred_idx].item()
107
+
108
+ # 5) Gradient ๊ณ„์‚ฐ
109
+ if input_embeds.grad is not None:
110
+ input_embeds.grad.zero_()
111
+ logit_output[0, pred_idx].backward()
112
+ if input_embeds.grad is None:
113
+ return "<p style='color:red;'>๋ถ„์„ ์˜ค๋ฅ˜: ๊ทธ๋ž˜๋””์–ธํŠธ ๊ณ„์‚ฐ ์‹คํŒจ.</p>", "๋ถ„์„ ์˜ค๋ฅ˜", "N/A", []
114
+ grads = input_embeds.grad.clone().detach()
115
+
116
+ # 6) ์ค‘์š”๋„ ์ ์ˆ˜ ๊ณ„์‚ฐ
117
+ scores = (grads * input_embeds.detach()).norm(dim=2).squeeze(0)
118
+ scores_np = scores.cpu().numpy()
119
+ valid_scores = scores_np[np.isfinite(scores_np)]
120
+ if len(valid_scores) > 0 and valid_scores.max() > 0:
121
+ scores_np = scores_np / (valid_scores.max() + 1e-9)
122
+ else:
123
+ scores_np = np.zeros_like(scores_np)
124
+
125
+ # 7) HTML ์ƒ์„ฑ
126
+ tokens = tokenizer.convert_ids_to_tokens(input_ids[0], skip_special_tokens=False)
127
+ html_tokens_list = []
128
+ cls_token_id, sep_token_id, pad_token_id = tokenizer.cls_token_id, tokenizer.sep_token_id, tokenizer.pad_token_id
129
+
130
+ for i, tok_str in enumerate(tokens):
131
+ if input_ids[0, i] == pad_token_id: continue
132
+ clean_tok_str = tok_str.replace("##", "") if "##" not in tok_str else tok_str[2:]
133
+ if input_ids[0, i] == cls_token_id or input_ids[0, i] == sep_token_id:
134
+ html_tokens_list.append(f"<span style='font-weight:bold;'>{html.escape(clean_tok_str)}</span>")
135
+ else:
136
+ score_val = scores_np[i] if i < len(scores_np) else 0
137
+ color = f"rgba(255, 0, 0, {max(0, min(1, score_val)):.2f})"
138
+ html_tokens_list.append(f"<span style='background-color:{color}; padding: 1px 2px; margin: 1px; border-radius: 3px; display:inline-block;'>{html.escape(clean_tok_str)}</span>")
139
+
140
+ html_output_str = " ".join(html_tokens_list).replace(" ##", "")
141
+
142
+ # Top-K ํ† ํฐ (DataFrame์šฉ ๋ฆฌ์ŠคํŠธ์˜ ๋ฆฌ์ŠคํŠธ)
143
+ top_tokens_for_df = []
144
+ valid_indices = [idx for idx, token_id in enumerate(input_ids[0].tolist())
145
+ if token_id not in [cls_token_id, sep_token_id, pad_token_id] and idx < len(scores_np)]
146
+ sorted_valid_indices = sorted(valid_indices, key=lambda idx: -scores_np[idx])
147
+ for token_idx in sorted_valid_indices[:top_k_value]:
148
+ top_tokens_for_df.append([tokens[token_idx], f"{scores_np[token_idx]:.3f}"])
149
+
150
+ # ์˜ˆ์ธก ํด๋ž˜์Šค ๋ ˆ์ด๋ธ”
151
+ predicted_class_label_str = str(pred_idx)
152
+ if class_names is not None and 0 <= pred_idx < len(class_names):
153
+ predicted_class_label_str = str(class_names[pred_idx])
154
+
155
+ prediction_summary_text = f"ํด๋ž˜์Šค: {predicted_class_label_str}\nํ™•๋ฅ : {pred_prob_val:.3f}"
156
+ prediction_details_for_label = {"์˜ˆ์ธก ํด๋ž˜์Šค": predicted_class_label_str, "ํ™•๋ฅ ": f"{pred_prob_val:.3f}"}
157
+
158
+
159
+ return html_output_str, prediction_summary_text, prediction_details_for_label, top_tokens_for_df
160
+
161
+ except Exception as e:
162
+ import traceback
163
+ tb_str = traceback.format_exc()
164
+ error_html = f"<p style='color:red;'>๋ถ„์„ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {html.escape(str(e))}</p><pre>{html.escape(tb_str)}</pre>"
165
+ print(f"Analyze_sentence_for_gradio error: {e}\n{tb_str}")
166
+ return error_html, "๋ถ„์„ ์‹คํŒจ", {"์˜ค๋ฅ˜": str(e)}, []
167
+
168
+
169
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Gradio ์ธํ„ฐํŽ˜์ด์Šค ์ •์˜ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
170
+ # ์ž…๋ ฅ ์ปดํฌ๋„ŒํŠธ
171
+ input_sentence = gr.Textbox(lines=3, label="๋ถ„์„ํ•  ์˜์–ด ๋ฌธ์žฅ", placeholder="์—ฌ๊ธฐ์— ์˜์–ด ๋ฌธ์žฅ์„ ์ž…๋ ฅํ•˜์„ธ์š”...")
172
+ input_top_k = gr.Slider(minimum=1, maximum=10, value=5, step=1, label="ํ‘œ์‹œํ•  Top-K ์ค‘์š” ํ† ํฐ ์ˆ˜")
173
+
174
+ # ์ถœ๋ ฅ ์ปดํฌ๋„ŒํŠธ
175
+ output_html_visualization = gr.HTML(label="ํ† ํฐ ์ค‘์š”๋„ ์‹œ๊ฐํ™”")
176
+ output_prediction_summary = gr.Textbox(label="์˜ˆ์ธก ์š”์•ฝ", lines=2) # ๊ฐ„๋‹จํ•œ ํ…์ŠคํŠธ ์š”์•ฝ์šฉ
177
+ output_prediction_details = gr.Label(label="์˜ˆ์ธก ์ƒ์„ธ") # Label์€ ๋”•์…”๋„ˆ๋ฆฌ๋ฅผ ์ž˜ ๋ณด์—ฌ์คŒ
178
+ output_top_tokens_df = gr.DataFrame(headers=["Token", "Score"], label="Top-K ์ค‘์š” ํ† ํฐ", row_count=(1,"dynamic"), col_count=(2,"fixed"))
179
+
180
+
181
+ # Gradio Blocks๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ ˆ์ด์•„์›ƒ ๊ตฌ์„ฑ (์„ ํƒ ์‚ฌํ•ญ, Interface๋ณด๋‹ค ์œ ์—ฐํ•จ)
182
+ with gr.Blocks(title="๋ฌธ์žฅ ํ† ํฐ ์ค‘์š”๋„ ๋ถ„์„๊ธฐ (Gradio)", theme=gr.themes.Soft()) as demo:
183
+ gr.Markdown("# ๐Ÿ“ ๋ฌธ์žฅ ํ† ํฐ ์ค‘์š”๋„ ๋ถ„์„๊ธฐ (Gradio)")
184
+ gr.Markdown("BERT์™€ LDA๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋ฌธ์žฅ ๋‚ด ๊ฐ ํ† ํฐ์˜ ์ค‘์š”๋„๋ฅผ ์‹œ๊ฐํ™”ํ•ฉ๋‹ˆ๋‹ค.")
185
+
186
+ with gr.Row():
187
+ with gr.Column(scale=2):
188
+ input_sentence.render()
189
+ input_top_k.render()
190
+ submit_button = gr.Button("๋ถ„์„ ์‹คํ–‰ํ•˜๊ธฐ ๐Ÿš€", variant="primary")
191
+ with gr.Column(scale=3):
192
+ output_prediction_summary.render()
193
+ output_prediction_details.render()
194
+
195
+ output_html_visualization.render()
196
+ output_top_tokens_df.render()
197
+
198
+ gr.Markdown("---")
199
+ gr.Markdown("<p style='text-align: center; color: grey;'>BERT ๊ธฐ๋ฐ˜ ๋ฌธ์žฅ ๋ถ„์„ ๋ฐ๋ชจ (Gradio)</p>")
200
+
201
+ # ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ํ•จ์ˆ˜ ์—ฐ๊ฒฐ
202
+ submit_button.click(
203
+ fn=analyze_sentence_for_gradio,
204
+ inputs=[input_sentence, input_top_k],
205
+ outputs=[output_html_visualization, output_prediction_summary, output_prediction_details, output_top_tokens_df]
206
+ )
207
+
208
+ # ์˜ˆ์ œ ์ถ”๊ฐ€
209
+ gr.Examples(
210
+ examples=[
211
+ ["This is a great movie and I really enjoyed it!", 5],
212
+ ["The weather is quite gloomy today.", 3],
213
+ ["I am not sure if this is the right way to do it, but let's try.", 4]
214
+ ],
215
+ inputs=[input_sentence, input_top_k],
216
+ outputs=[output_html_visualization, output_prediction_summary, output_prediction_details, output_top_tokens_df], # ์˜ˆ์ œ ์‹คํ–‰ ์‹œ์—๋„ ๋ชจ๋“  ์ถœ๋ ฅ ์ปดํฌ๋„ŒํŠธ ํ•„์š”
217
+ fn=analyze_sentence_for_gradio, # ์˜ˆ์ œ ์‹คํ–‰ ์‹œ์—๋„ ๋™์ผ ํ•จ์ˆ˜ ์‚ฌ์šฉ
218
+ cache_examples=False # ๋ชจ๋ธ์ด ์žˆ๋Š” ๊ฒฝ์šฐ True๋กœ ํ•˜๋ฉด ์˜ˆ์ œ ๋กœ๋”ฉ์ด ๋นจ๋ผ์งˆ ์ˆ˜ ์žˆ์œผ๋‚˜, ๋””๋ฒ„๊น… ์ค‘์—๋Š” False ๊ถŒ์žฅ
219
+ )
220
+
221
+ # Gradio ์•ฑ ์‹คํ–‰ (Hugging Face Spaces์—์„œ๋Š” ์ด ๋ถ€๋ถ„์ด ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌ๋จ)
222
+ # ๋กœ์ปฌ์—์„œ ํ…Œ์ŠคํŠธ ์‹œ: demo.launch()
223
+ if __name__ == "__main__":
224
+ if not MODELS_LOADED_SUCCESSFULLY:
225
+ print("*"*80)
226
+ print("๊ฒฝ๊ณ : ๋ชจ๋ธ ๋กœ๋”ฉ์— ์‹คํŒจํ•˜์—ฌ Gradio ์•ฑ์ด ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.")
227
+ print(f"์˜ค๋ฅ˜ ๋‚ด์šฉ: {MODEL_LOADING_ERROR_MESSAGE}")
228
+ print("Gradio UI๋Š” ํ‘œ์‹œ๋˜์ง€๋งŒ, '๋ถ„์„ ์‹คํ–‰ํ•˜๊ธฐ' ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €์„ ๋•Œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.")
229
+ print("`์ €์žฅ์ €์žฅ1` ํด๋” ๋ฐ ๋‚ด๋ถ€ ํŒŒ์ผ๋“ค์ด `app.py`์™€ ๋™์ผํ•œ ๋””๋ ‰ํ† ๋ฆฌ์— ์žˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.")
230
+ print("*"*80)
231
+ # Hugging Face Spaces๋Š” app.py๋ฅผ ์‹คํ–‰ํ•˜๊ณ  demo.launch()๋ฅผ ์ฐพ๊ฑฐ๋‚˜
232
+ # demo๋ผ๋Š” ์ด๋ฆ„์˜ launchable Blocks/Interface ๊ฐ์ฒด๋ฅผ ์ฐพ์Šต๋‹ˆ๋‹ค.
233
+ demo.launch()