Addressing Performance Discrepancies In Pixel-Reasoner On V_star Benchmark

by StackCamp Team 75 views

Introduction

Thank you for your diligent work on Pixel-Reasoner. During evaluation using the HuggingFace Space demo code, a significant discrepancy was observed between the reported results in the paper and the actual performance. Specifically, Pixel Reasoner achieved an AVG-63.77 score on the V_star benchmark, which differs considerably from the paper's claims. This discrepancy raises questions about the consistency of the inference process, particularly regarding image cropping. In this article, we delve into the observed issues, the potential causes, and a proposed solution to improve the model's performance. Our discussion will focus on the critical aspects of image processing and inference, aiming to bridge the gap between the expected and the actual results. By addressing these discrepancies, we hope to contribute to a more accurate and reliable application of Pixel-Reasoner in visual reasoning tasks.

Detailed Observation of the Discrepancy

Upon closer inspection of the inference process, it was noted that the authors primarily retain the rendered image crop, as highlighted in Line 243 of the app.py file. This observation prompts a critical question: Is this practice consistent with the examples presented in the original paper? The concern is that limiting the model's input to only the cropped image might hinder its ability to contextualize and reason effectively about the entire scene. The original paper likely showcased results obtained using a broader visual context, which could explain the performance gap. To validate this hypothesis, a modified inference code was developed to incorporate a more comprehensive visual input, potentially leading to more accurate model outputs. This modification aims to provide the model with the necessary context to perform optimally, aligning the evaluation conditions more closely with those in the original research.

The Importance of Context in Visual Reasoning

In visual reasoning, context plays a crucial role in understanding the relationships between different objects and elements within an image. When a model is limited to only a cropped portion of the image, it may miss out on critical contextual cues that are necessary for accurate inference. For instance, the relative position of objects, the overall scene layout, and the presence of other relevant elements can all contribute to a more complete understanding of the visual information. By providing the model with a broader view of the image, we enable it to leverage these contextual cues and make more informed decisions. This approach is particularly important in complex visual reasoning tasks where the relationships between different parts of the scene are essential for arriving at the correct answer. Therefore, ensuring that the model has access to sufficient context is a key factor in achieving high performance on visual reasoning benchmarks.

Analyzing the Original Inference Process

The original inference process, as implemented in Line 243 of app.py, appears to prioritize the cropped image over the full image. While cropping can be a useful technique for focusing the model's attention on specific regions of interest, it should not come at the expense of losing the broader context. The decision to retain only the cropped image during inference suggests a potential oversight in the design of the evaluation pipeline. It is possible that this approach was adopted to reduce computational overhead or to simplify the inference process. However, the observed performance discrepancy indicates that the trade-off may not be justified. A more effective strategy would involve incorporating both the cropped image and the full image into the model's input, allowing it to leverage both local details and global context. This would likely lead to a more robust and accurate visual reasoning system.

Modified Inference Code and Its Rationale

The modified inference code addresses the context limitation by ensuring that the model has access to both the cropped and the full image. This approach aims to replicate the conditions under which the original paper's results were likely obtained, providing a fairer evaluation of the model's capabilities. The code includes several key functions that facilitate image processing and tool execution, ensuring that the model receives the necessary inputs for effective reasoning. By incorporating the full image alongside the cropped region, the model can better contextualize the information and make more accurate predictions.

Key Components of the Modified Code

The modified code includes several important functions that contribute to its improved performance. The zoom function is used to crop the image based on the bounding box coordinates, allowing the model to focus on specific regions of interest. The execute_tool function manages the execution of various tools, such as cropping and frame selection, ensuring that the model can interact with the visual data effectively. The parse_last_tool function extracts the parameters for the tool calls from the model's output, enabling the system to dynamically adapt to the model's reasoning process. Finally, the model_inference function orchestrates the entire inference process, handling image loading, preprocessing, and model execution. This function also incorporates the crucial step of providing the model with both the cropped and full images, ensuring that it has the necessary context for accurate reasoning.

Detailed Code Explanation

The provided code snippet demonstrates the core components of the modified inference process. The zoom function takes an image and bounding box coordinates as input and returns a cropped version of the image. This function is crucial for focusing the model's attention on specific regions of interest. The execute_tool function is responsible for applying various tools to the input images, such as cropping and frame selection. This function ensures that the model can interact with the visual data in a flexible and dynamic manner. The parse_last_tool function is used to extract the parameters for the tool calls from the model's output. This allows the system to dynamically adapt to the model's reasoning process, ensuring that the correct tools are applied at the appropriate times. The model_inference function is the heart of the inference process. It takes the user's input text and files, preprocesses them, and feeds them to the model. This function also incorporates the crucial step of providing the model with both the cropped and full images, ensuring that it has the necessary context for accurate reasoning. The code also includes a loop that iterates over the test questions, performs inference, and evaluates the model's performance. This allows for a comprehensive assessment of the model's capabilities on the V_star benchmark.

import json
from transformers import AutoProcessor, AutoModelForImageTextToText, TextIteratorStreamer
from transformers.image_utils import load_image
from threading import Thread
import torch
import pickle as pkl
import re 
from PIL import Image
import json
import os
from serve_constants import html_header, bibtext, learn_more_markdown, tos_markdown
from tqdm import tqdm

def zoom(image, bbox_2d, padding=(0.1,0.1)):
    """
    Crop the image based on the bounding box coordinates.
    """
    img_x, img_y = image.size
    padding_tr = (600.0/img_x,600.0/img_y)
    padding = (min(padding[0],padding_tr[0]),min(padding[1],padding_tr[1]))

    if bbox_2d[0] < 1 and bbox_2d[1] < 1 and bbox_2d[2] < 1 and bbox_2d[3] < 1:
        normalized_bbox_2d = (float(bbox_2d[0])-padding[0], float(bbox_2d[1])-padding[1], float(bbox_2d[2])+padding[0], float(bbox_2d[3])+padding[1])
    else:
        normalized_bbox_2d = (float(bbox_2d[0])/img_x-padding[0], float(bbox_2d[1])/img_y-padding[1], float(bbox_2d[2])/img_x+padding[0], float(bbox_2d[3])/img_y+padding[1])
    normalized_x1, normalized_y1, normalized_x2, normalized_y2 = normalized_bbox_2d
    normalized_x1 =min(max(0, normalized_x1), 1)
    normalized_y1 =min(max(0, normalized_y1), 1)
    normalized_x2 =min(max(0, normalized_x2), 1)
    normalized_y2 =min(max(0, normalized_y2), 1)
    cropped_img = image.crop((int(normalized_x1*img_x), int(normalized_y1*img_y), int(normalized_x2*img_x), int(normalized_y2*img_y)))
    w, h = cropped_img.size
    assert w > 28 and h > 28, f"Cropped image is too small: {w}x{h}"


    return cropped_img  


def execute_tool(images, rawimages, args, toolname, is_video, function=None):
    if toolname=='select_frames':
        tgt = args['target_frames']
        if len(tgt)>8:
            message = f"You have selected {len(tgt)} frames in total. Think again which frames you need to check in details (no more than 8 frames)"
            # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
            ##### controlled modification
            if do_controlled_rectify and np.random.uniform()<0.75:
                if np.random.uniform()<0.25:
                    tgt = tgt[:len(tgt)//2]
                elif np.random.uniform()<0.25/0.75:
                    tgt = tgt[-len(tgt)//2:]
                elif np.random.uniform()<0.25/0.5:
                    tgt = tgt[::2]
                else:
                    tgt = np.random.choice(tgt, size=len(tgt)//2, replace=False)
                    tgt = sorted(tgt)
                selected_frames = function(images[0], tgt)
                message = tgt
            else: 
                selected_frames = []
            # selected_frames = function(images[0], [x-1 for x in tgt][::2]) # video is always in the first item
        elif max(tgt)>len(images[0]):
            message = f"There are {len(images[0])} frames numbered in range [1,{len(images[0])}]. Your selection is out of range."
            selected_frames = []
        else:
            message = ""
            candidates = images[0]
            if not isinstance(candidates, list):
                candidates = [candidates]
            selected_frames = function(candidates, [x-1 for x in tgt]) # video is always in the first item
        return selected_frames, message
    else:
        tgt = args['target_image']
        if is_video:
            if len(images)==1: # there is only 
                # we default the candidate images into video frames 
                video_frames = images[0]
                index = tgt - 1 
                assert index<len(video_frames), f"Incorrect `target_image`. You can only select frames in the given video within [1,{len(video_frames)}]"
                image_to_crop = video_frames[index]
            else: # there are zoomed images after the video; images = [[video], img, img, img]
                cand_images = images[1:]
                index = tgt -1
                assert index<len(cand_images), f"Incorrect `target_image`. You can only select a previous frame within [1,{len(cand_images)}]"
                image_to_crop = cand_images[index]
        else:
            index =  tgt-1 
            assert index<len(images), f"Incorrect `target_image`. You can only select previous images within [1,{len(images)}]"
            
            if index<len(rawimages):
                tmp = rawimages[index]
            else:
                tmp = images[index]
            image_to_crop = tmp
        if function is None: function = zoom
        cropped_image = function(image_to_crop, args['bbox_2d'])
    return cropped_image
    

def parse_last_tool(output_text):
    # print([output_text])
    return json.loads(output_text.split(tool_start)[-1].split(tool_end)[0])

def model_inference(input_dict):
    text = input_dict["text"]
    files = input_dict["files"]

    """
    Create chat history
    Example history value:
    [
        [('pixel.png',), None], 
        ['ignore this image. just say "hi" and nothing else', 'Hi!'], 
        ['just say "hi" and nothing else', 'Hi!']
    ]
    """
    all_images = []
    current_message_images = []
    sysprompt = "<|im_start|>system\nYou are a helpful assistant.\n\n# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>\n{\"type\": \"function\", \"function\": {\"name\": \"crop_image_normalized\", \"description\": \"Zoom in on the image based on the bounding box coordinates.\", \"parameters\": {\"type\": \"object\", \"properties\": {\"bbox_2d\": {\"type\": \"array\", \"description\": \"normalized coordinates for bounding box of the area you want to zoom in. Values within [0.0,1.0].\", \"items\": {\"type\": \"number\"}}, \"target_image\": {\"type\": \"number\", \"description\": \"The index of the image to crop. Index from 1 to the number of images. Choose 1 to operate on original image.\"}}, \"required\": [\"bbox_2d\", \"target_image\"]}}}\\n{\"type\": \"function\", \"function\": {\"name\": \"select_frames\", \"description\": \"Select frames from a video.\", \"parameters\": {\"type\": \"object\", \"properties\": {\"target_frames\": {\"type\": \"array\", \"description\": \"List of frame indices to select from the video (no more than 8 frames in total).\", \"items\": {\"type\": \"integer\", \"description\": \"Frame index from 1 to 16.\"}}}, \"required\": [\"target_frames\"]}}}\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{\"name\": <function-name>, \"arguments\": <args-json-object>}\n</tool_call>"
    messages = [{
        "role": "user",
        "content": sysprompt
    }]
    hint = "\n\nGuidelines: Understand the given visual information and the user query. Determine if it is beneficial to employ the given visual operations (tools). For a video, we can look closer by `select_frames`. For an image, we can look closer by `crop_image_normalized`. Reason with the visual information step by step, and put your final answer within \\boxed{}."
    
    imagelist = rawimagelist = current_message_images = [load_image(image) for image in files]
    all_images += current_message_images
    messages.append({
        "role": "user",
        "content": [
            *[{'type': 'image', 'image': image} for image in current_message_images],
            {"type": "text", "text": text+hint},
        ],
    })
    
    # print(messages)

    complete_assistant_response_for_gradio = []
    while True:
        """
        Generate and stream text
        """
        prompt = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
        inputs = processor(
            text=[prompt],
            images=all_images if all_images else None,
            return_tensors="pt",
            padding=True,
        ).to("cuda")
        # print(f"===> messages for generation")
        # print(messages)
        generated_ids = model.generate(**inputs, max_new_tokens=8192, temperature=0.1, top_p=0.95, top_k=50)
        generated_ids_trimmed = [out_ids[len(in_ids) :] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)]
        response = processor.batch_decode(generated_ids_trimmed, skip_special_tokens=False, clean_up_tokenization_spaces=False)[0]

        processed_segment = response.split("<|im_end|>", 1)[0] if "<|im_end|>" in response else response
        
        # Check for tool call in the *just generated* segment
        qatext_for_tool_check = processed_segment
        require_tool = tool_end in qatext_for_tool_check and tool_start in qatext_for_tool_check
        
        messages.append({  # I have reserved the previous reasoning trajectories.
            "role": "assistant",
            "content": [
                {"type": "text", "text": response.replace('<|im_end|>','')},
            ],
        })
                
        if require_tool:
            tool_params = parse_last_tool(qatext_for_tool_check) 
            tool_name = tool_params['name']
            tool_args = tool_params['arguments']
            video_flag = False
            raw_result = execute_tool(all_images, all_images, tool_args, tool_name, is_video=video_flag)
            proc_img = raw_result
            all_images += [proc_img]
            proc_img.save("tmp.png")
            
            new_piece = dict(role='user', content=[
                                    dict(type='text', text="\nHere is the cropped image (Image Size: {}x{}):".format(proc_img.size[0], proc_img.size[1])),
                                    dict(type='image', image=proc_img)
                                ]
            )
            messages.append(new_piece)
        else:
            return messages, response
    
    return None

cur_dir = os.path.dirname(os.path.abspath(__file__))
MODEL_ID = "/home/liyou/opensource_models/PixelReasoner-RL-v1"
processor = AutoProcessor.from_pretrained(MODEL_ID, trust_remote_code=True, max_pixels=512*28*28)
model = AutoModelForImageTextToText.from_pretrained(MODEL_ID, trust_remote_code=True, torch_dtype=torch.bfloat16).to("cuda").eval()
tool_start = '<tool_call>'
tool_end = '</tool_call>'

with open('/home/liyou/opensource_datasets/vstar_bench/test_questions.jsonl') as file:
    data = [json.loads(line) for line in file.readlines()]
    data = [obj for obj in data if 'direct' in obj['image']]
    
total, correct = len(data), 0
results = []
for obj in tqdm(data):
    examples = {
                    "text": obj['text'], 
                    "files": [
                        f"/home/liyou/opensource_datasets/vstar_bench/{obj['image']}"
                    ]
                }

    try:
        messages, response = model_inference(examples)
    except Exception as e:
        print(e)
        messages, response = [], ''
        
    filter_ans = response.split('\\boxed')[-1].split('<|im_end|>')[0]
    if obj['label'] in filter_ans:
        correct += 1
    print(response, obj['label'])
    messages.append({'correct': obj['label'] in filter_ans})
    results.append(json.loads(json.dumps(messages, default=str)))
    with open('/home/liyou/MLLM_Reasoning/Pixel-Reasoner/onestep_evaluation/v_star_direct.json', 'w') as file:
        json.dump(results, file, indent=4, ensure_ascii=False)

print(correct/total)

Addressing Potential Errors

The modified code includes comprehensive error handling to ensure robustness. For instance, assertions are used to validate the input parameters of the execute_tool function, preventing common issues such as out-of-range frame selections. Additionally, the model_inference function includes a try-except block to catch any exceptions that may occur during the inference process. This ensures that the evaluation can continue even if some questions result in errors. By addressing potential errors proactively, the modified code provides a more reliable and accurate evaluation of the model's performance.

Expected Outcomes and Further Research

By implementing the modified inference code, we anticipate a significant improvement in the Pixel-Reasoner's performance on the V_star benchmark. This improvement is expected due to the inclusion of both the cropped and full images in the model's input, which provides the necessary context for effective visual reasoning. However, this is just the first step in addressing the observed discrepancies. Further research is needed to fully understand the factors that influence the model's performance and to optimize its architecture and training process. This may involve exploring different image processing techniques, experimenting with various model configurations, and conducting a more detailed analysis of the model's reasoning process. By pursuing these avenues of research, we can continue to improve the Pixel-Reasoner's capabilities and ensure that it achieves its full potential.

Potential Impact on Visual Reasoning Models

The insights gained from this investigation can have a broader impact on the field of visual reasoning. By identifying the importance of contextual information in image understanding, we can inform the design of future models and evaluation pipelines. It is crucial to ensure that models have access to sufficient context to perform effectively, and that evaluation metrics accurately reflect their ability to reason about visual data. This may involve developing new evaluation benchmarks that explicitly test the model's ability to leverage contextual cues, as well as exploring novel model architectures that are better suited for capturing long-range dependencies in images. By focusing on these areas, we can drive progress in the field of visual reasoning and develop more capable and robust models.

Future Directions for Model Improvement

In addition to addressing the context limitation, there are several other potential avenues for improving the Pixel-Reasoner's performance. One promising direction is to explore the use of attention mechanisms, which allow the model to selectively focus on the most relevant parts of the image. This can help the model to better integrate local details and global context, leading to more accurate reasoning. Another area for investigation is the training process. By using more diverse and challenging training data, we can improve the model's generalization ability and robustness. Additionally, exploring different training objectives and regularization techniques can help to prevent overfitting and improve the model's performance on unseen data. By pursuing these various avenues of research, we can continue to push the boundaries of visual reasoning and develop models that are capable of solving increasingly complex tasks.

Conclusion

In conclusion, the observed discrepancy in Pixel-Reasoner's performance on the V_star benchmark highlights the critical role of context in visual reasoning. The modified inference code, which incorporates both cropped and full images, represents a significant step towards addressing this issue. By providing the model with a more complete view of the scene, we enable it to leverage contextual cues and make more accurate predictions. While this modification is expected to improve performance, further research is needed to fully optimize the model's capabilities. By continuing to explore different image processing techniques, model architectures, and training strategies, we can advance the field of visual reasoning and develop models that are capable of solving complex real-world problems. The insights gained from this investigation can inform the design of future models and evaluation pipelines, ensuring that they accurately reflect the ability of models to reason about visual data effectively. Thank you for your attention, and we look forward to your early reply and further discussion on this important topic.