HenryM commited on
Commit
a4ca52d
·
verified ·
1 Parent(s): 80d9e70

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +79 -109
app.py CHANGED
@@ -5,57 +5,69 @@ import torch
5
  from transformers import Qwen2_5_VLForConditionalGeneration, AutoProcessor
6
  from qwen_vl_utils import process_vision_info
7
  from peft import PeftModel
8
- from pathlib import Path
9
 
10
  system_prompt = (
11
  "A conversation between User and Assistant. The user asks a question, and the Assistant solves it. "
12
- "The assistant es un experto sobre Colombia. Primero razona en mente y luego da la respuesta. "
13
  "El razonamiento y la respuesta van en <think></think> y <answer></answer>."
14
  )
15
 
16
- MODEL_ID = 'Qwen/Qwen2.5-VL-3B-Instruct'
17
- ADAPTER_ID = 'Factral/qwen2.5vl-3b-colombia-finetuned'
18
 
19
  processor = AutoProcessor.from_pretrained(MODEL_ID)
20
 
21
- # Carga del modelo base
22
- has_gpu = torch.cuda.is_available()
23
  attn_impl = "flash_attention_2" if has_gpu else "eager"
24
- model = Qwen2_5_VLForConditionalGeneration.from_pretrained(
25
  MODEL_ID,
26
  torch_dtype=torch.bfloat16,
27
  attn_implementation=attn_impl,
28
- device_map="auto"
29
  )
30
-
31
- # Carga y fusión del adaptador PEFT
32
- model = PeftModel.from_pretrained(model, ADAPTER_ID)
33
- model = model.merge_and_unload()
34
- model.eval()
35
- device = torch.device("cuda" if has_gpu else "cpu")
36
- model.to(device)
37
 
38
  example_imgs = [
39
- ("6.png", "Shakira"),
40
  ("163.png", "Tienda esquinera"),
41
  ("img_71_2.png", "Comida colombiana"),
42
- ("img_98.png", "Oso de anteojos"),
43
  ]
44
 
45
- def cargar_imagen(imagen_path: str) -> Image.Image:
46
- return Image.open(imagen_path)
47
-
48
- with gr.Blocks(theme='lone17/kotaemon') as demo:
49
- demo.css = """
50
- #galeria-scroll {
51
- max-height: 320px;
52
- overflow-y: auto;
53
- border: 1px solid #ccc;
54
- padding: 8px;
55
- border-radius: 8px;
56
- }
57
- """
58
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  gr.Markdown(
60
  """
61
  <h1>🇨🇴
@@ -65,153 +77,111 @@ with gr.Blocks(theme='lone17/kotaemon') as demo:
65
  """
66
  )
67
 
 
68
  with gr.Row():
69
- # Columna izquierda
70
- with gr.Column(scale=1):
71
  gr.Markdown(
72
  """
73
  #### 📌 Motivación del proyecto
74
- El objetivo de **BacanoResponder** es permitir a los usuarios en Colombia interactuar con imágenes de su entorno y recibir información contextualizada.
75
  <br/>
76
-
77
  #### 🌟 Impacto
78
- Ofrecemos respuestas específicas sobre objetos, lugares o costumbres colombianas, beneficiando a estudiantes, turistas y a cualquier persona interesada en nuestras tradiciones.
79
 
80
  #### 👥 Equipo
81
  • Fabian Perez
82
  • Henry Mantilla
83
  • Andrea Parra
84
  • Juan Calderón
85
- Semillero de Investigación del que hacemos parte [SemilleroCV](https://semillerocv.github.io/)
86
  """
87
  )
88
-
89
- # Columna derecha
90
- with gr.Column(scale=1):
91
  gr.Markdown(
92
  """
93
  #### 🚀 Ideas futuras
94
- - 📈 Escalar significativamente el dataset
95
- - 🎤 Añadir preguntas por voz en dialectos regionales
96
- - 🌐 Traducción automática para usuarios internacionales
97
- - 🗺️ Más dialectos y costumbres (Amazonía, Caribe, etc.)
98
- - 🔄 Retroalimentación comunitaria para fine-tuning continuo
99
- - 🗺️ Mapas con coordenadas y rutas turísticas
100
-
101
  #### 🤖 Modelos utilizados
102
  - *Qwen2.5-VL-3B-Instruct*
103
  - Dataset: [QuestionAnswer-ImgsColombia](https://huggingface.co/datasets/4nd/QuestionAnswer-ImgsColombia)
104
  """
105
  )
106
 
107
-
108
-
109
  with gr.Row(equal_height=True):
110
- # Columna izquierda
111
  with gr.Column(scale=1):
112
  pregunta = gr.Textbox(
113
  label="❓ Pregunta sobre tu imagen",
114
  placeholder="¿Qué muestra esta imagen?",
115
- lines=2
116
  )
117
 
118
- # Asignamos elem_id al Gallery directamente
119
  galeria = gr.Gallery(
120
  label="📁 Elige una imagen de ejemplo",
121
  value=[img for img, _ in example_imgs],
122
- columns=2,
123
- height=60%, # el CSS controla altura
124
  allow_preview=True,
125
  show_label=True,
126
- elem_id="galeria-scroll"
127
  )
128
 
129
- # Columna derecha
130
  with gr.Column(scale=1):
131
  imagen_mostrada = gr.Image(
132
  label="🖼 Imagen seleccionada o subida",
133
  type="numpy",
134
- height=256
135
  )
136
-
137
  respuesta = gr.Textbox(
138
  label="🧠 Respuesta",
139
  interactive=False,
140
- lines=4
141
  )
142
-
143
  btn_procesar = gr.Button("🔍 Procesar")
144
 
145
  def seleccionar_imagen(evt: gr.SelectData):
146
- idx = evt.index
147
- img_path = example_imgs[idx][0]
148
- pil = cargar_imagen(img_path)
149
- return np.array(pil)
150
-
151
  galeria.select(fn=seleccionar_imagen, inputs=None, outputs=imagen_mostrada)
152
 
153
  def responder(img, pregunta_text):
154
  if img is None or pregunta_text.strip() == "":
155
  return "Por favor sube una imagen y escribe una pregunta."
156
 
157
- # Convertir array numpy a PIL si es necesario
158
  if isinstance(img, np.ndarray):
159
- img_pil = Image.fromarray(img.astype('uint8'))
160
- else:
161
- img_pil = img # ya es PIL
162
 
163
  messages = [
164
- {
165
- "role": "system",
166
- "content": [{"type": "text", "text": system_prompt}],
167
- },
168
- {
169
- "role": "user",
170
- "content": [
171
- {"type": "text", "text": pregunta_text},
172
- {"type": "image", "image": img_pil},
173
- ],
174
- }
175
  ]
176
-
177
- text = processor.apply_chat_template(
178
- messages,
179
- tokenize=False,
180
- add_generation_prompt=True
181
- )
182
  image_inputs, video_inputs = process_vision_info(messages)
183
-
184
  inputs = processor(
185
  text=[text],
186
  images=image_inputs,
187
  videos=video_inputs,
188
  padding=True,
189
  return_tensors="pt",
190
- )
191
- inputs = inputs.to(device)
192
 
193
  with torch.no_grad():
194
- generated_ids = model.generate(
195
- **inputs,
196
- max_new_tokens=512,
197
- top_p=1.0,
198
- do_sample=True,
199
- temperature=0.9
200
- )
201
 
202
- trimmed = [
203
- out_ids[len(in_ids):]
204
- for in_ids, out_ids in zip(inputs.input_ids, generated_ids)
205
- ]
206
- respuesta_text = processor.batch_decode(
207
- trimmed,
208
- skip_special_tokens=True,
209
- clean_up_tokenization_spaces=False
210
- )
211
-
212
- return respuesta_text[0]
213
-
214
- btn_procesar.click(fn=responder, inputs=[imagen_mostrada, pregunta], outputs=respuesta)
215
 
216
  if __name__ == "__main__":
217
  demo.launch()
 
 
5
  from transformers import Qwen2_5_VLForConditionalGeneration, AutoProcessor
6
  from qwen_vl_utils import process_vision_info
7
  from peft import PeftModel
 
8
 
9
  system_prompt = (
10
  "A conversation between User and Assistant. The user asks a question, and the Assistant solves it. "
11
+ "El assistant es un experto sobre Colombia. Primero razona en mente y luego da la respuesta. "
12
  "El razonamiento y la respuesta van en <think></think> y <answer></answer>."
13
  )
14
 
15
+ MODEL_ID = "Qwen/Qwen2.5-VL-3B-Instruct"
16
+ ADAPTER_ID = "Factral/qwen2.5vl-3b-colombia-finetuned"
17
 
18
  processor = AutoProcessor.from_pretrained(MODEL_ID)
19
 
20
+ has_gpu = torch.cuda.is_available()
 
21
  attn_impl = "flash_attention_2" if has_gpu else "eager"
22
+ model = Qwen2_5_VLForConditionalGeneration.from_pretrained(
23
  MODEL_ID,
24
  torch_dtype=torch.bfloat16,
25
  attn_implementation=attn_impl,
26
+ device_map="auto",
27
  )
28
+ model = PeftModel.from_pretrained(model, ADAPTER_ID).merge_and_unload()
29
+ model.eval().to(torch.device("cuda" if has_gpu else "cpu"))
 
 
 
 
 
30
 
31
  example_imgs = [
32
+ ("6.png", "Shakira"),
33
  ("163.png", "Tienda esquinera"),
34
  ("img_71_2.png", "Comida colombiana"),
35
+ ("img_98.png", "Oso de anteojos"),
36
  ]
37
 
38
+ def cargar_imagen(path: str) -> Image.Image:
39
+ return Image.open(path)
40
+
41
+ CSS_CUSTOM = """
42
+ /* Galería horizontal con miniaturas */
43
+ #galeria-scroll {
44
+ overflow-x: auto;
45
+ overflow-y: hidden;
46
+ padding: 4px;
47
+ scrollbar-width: thin;
48
+ }
49
+ #galeria-scroll .gallery { flex-wrap: nowrap !important; }
50
+ #galeria-scroll .gallery-item {
51
+ flex: 0 0 auto !important;
52
+ width: 90px !important;
53
+ height: 90px !important;
54
+ margin-right: 6px;
55
+ }
56
+ #galeria-scroll .gallery-item img { object-fit: cover; }
57
+
58
+ /* Texto blanco y sin halo azul al enfocar */
59
+ input, textarea { color: #fff !important; }
60
+ input::placeholder, textarea::placeholder { color: #ddd !important; }
61
+ label { color: #fff !important; }
62
+ input:focus, textarea:focus {
63
+ outline: none !important;
64
+ box-shadow: none !important;
65
+ border-color: #888 !important;
66
+ }
67
+ """
68
+
69
+ with gr.Blocks(theme="lone17/kotaemon", css=CSS_CUSTOM) as demo:
70
+ # título
71
  gr.Markdown(
72
  """
73
  <h1>🇨🇴
 
77
  """
78
  )
79
 
80
+ # motivación / ideas futuras en dos columnas
81
  with gr.Row():
82
+ with gr.Column():
 
83
  gr.Markdown(
84
  """
85
  #### 📌 Motivación del proyecto
86
+ BacanoResponder permite a los usuarios colombianos obtener información contextual de sus imágenes.
87
  <br/>
88
+
89
  #### 🌟 Impacto
90
+ Difunde cultura local y apoya a estudiantes, turistas y creadores de contenido.
91
 
92
  #### 👥 Equipo
93
  • Fabian Perez
94
  • Henry Mantilla
95
  • Andrea Parra
96
  • Juan Calderón
97
+ • [SemilleroCV](https://semillerocv.github.io/)
98
  """
99
  )
100
+ with gr.Column():
 
 
101
  gr.Markdown(
102
  """
103
  #### 🚀 Ideas futuras
104
+ - 📈 Escalar el dataset
105
+ - 🎤 Soporte de voz en dialectos regionales
106
+ - 🌐 Traducción automática
107
+ - 🗺️ Más dialectos/costumbres
108
+ - 🔄 Retroalimentación continua
109
+ - 🗺️ Mapas turísticos
110
+
111
  #### 🤖 Modelos utilizados
112
  - *Qwen2.5-VL-3B-Instruct*
113
  - Dataset: [QuestionAnswer-ImgsColombia](https://huggingface.co/datasets/4nd/QuestionAnswer-ImgsColombia)
114
  """
115
  )
116
 
 
 
117
  with gr.Row(equal_height=True):
 
118
  with gr.Column(scale=1):
119
  pregunta = gr.Textbox(
120
  label="❓ Pregunta sobre tu imagen",
121
  placeholder="¿Qué muestra esta imagen?",
122
+ lines=2,
123
  )
124
 
 
125
  galeria = gr.Gallery(
126
  label="📁 Elige una imagen de ejemplo",
127
  value=[img for img, _ in example_imgs],
128
+ columns=999, # fuerza una sola fila
129
+ height="110px",
130
  allow_preview=True,
131
  show_label=True,
132
+ elem_id="galeria-scroll",
133
  )
134
 
 
135
  with gr.Column(scale=1):
136
  imagen_mostrada = gr.Image(
137
  label="🖼 Imagen seleccionada o subida",
138
  type="numpy",
139
+ height=256,
140
  )
 
141
  respuesta = gr.Textbox(
142
  label="🧠 Respuesta",
143
  interactive=False,
144
+ lines=4,
145
  )
 
146
  btn_procesar = gr.Button("🔍 Procesar")
147
 
148
  def seleccionar_imagen(evt: gr.SelectData):
149
+ path = example_imgs[evt.index][0]
150
+ return np.array(cargar_imagen(path))
 
 
 
151
  galeria.select(fn=seleccionar_imagen, inputs=None, outputs=imagen_mostrada)
152
 
153
  def responder(img, pregunta_text):
154
  if img is None or pregunta_text.strip() == "":
155
  return "Por favor sube una imagen y escribe una pregunta."
156
 
 
157
  if isinstance(img, np.ndarray):
158
+ img = Image.fromarray(img.astype("uint8"))
 
 
159
 
160
  messages = [
161
+ {"role": "system", "content": [{"type": "text", "text": system_prompt}]},
162
+ {"role": "user",
163
+ "content": [
164
+ {"type": "text", "text": pregunta_text},
165
+ {"type": "image", "image": img},
166
+ ]},
 
 
 
 
 
167
  ]
168
+ text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
 
 
 
 
 
169
  image_inputs, video_inputs = process_vision_info(messages)
 
170
  inputs = processor(
171
  text=[text],
172
  images=image_inputs,
173
  videos=video_inputs,
174
  padding=True,
175
  return_tensors="pt",
176
+ ).to(model.device)
 
177
 
178
  with torch.no_grad():
179
+ out_ids = model.generate(**inputs, max_new_tokens=512, top_p=1.0, do_sample=True, temperature=0.9)
180
+ trimmed = [o[len(i):] for i, o in zip(inputs.input_ids, out_ids)]
181
+ return processor.batch_decode(trimmed, skip_special_tokens=True)[0]
 
 
 
 
182
 
183
+ btn_procesar.click(responder, inputs=[imagen_mostrada, pregunta], outputs=respuesta)
 
 
 
 
 
 
 
 
 
 
 
 
184
 
185
  if __name__ == "__main__":
186
  demo.launch()
187
+