# LAB 15 · 语言指代小练习:目标、参照与关系 按问题继续 RGB规则提框、错误关键词基线、目标/参照/关系与干预 对应课堂(2 页) 可先阅读已有结果,再决定是否运行。重算时请核对各节记录的数据、参数、环境与日期。 **学习路径:** [一句话指向哪个对象](/course/study/grounding-interface.html) 这是[CLS 11](https://deepnote.com/project/38755e4e-23f6-4f5f-8c68-8a2e4114b1c1/notebook/95169c3e298c4c819373bb4534f3dff9)的短机制练习:在“蓝杯左边的杯子”中,blue修饰参照物,目标却可能是红杯。先看[P63](/course/pages/l3-r2-p63.html)和[带坐标的分解](/course/blocks/grounding-target-reference-relation.html)。 依次读关键词失败、RGB候选框、目标/参照/关系和四个干预输出。颜色阈值与有限词表是明确规则,没有训练GLIP或GroundingDINO;规则练习的价值是每一步都能对应到像素、坐标和输出。 完成时给出原图、遮挡、颜色交换和换关系四种情况下的目标及原因,并解释何时应返回无唯一目标。代码按原顺序可独立运行,2026-09-22历史结果在[干预证据页](/course/blocks/grounding-pixel-interventions.html)。学习式开放定位仅在末尾保留一手入口,不当成已完成实验。 ![像素干预与目标输出](/assets/display/969fa088b392b7fb8a846fb3.webp) 红色框线标目标角色,蓝色框线标参照物角色,框线颜色与物体填充颜色不同。图和输出来自2026-09-22保存运行,当前整理没有重跑。若要研究学习式开放定位,最后一节保留官方模型入口,但该模型的结果不能由当前规则实验代替。 [此段独立阅读](/course/notebook-blocks/4f3041dad81141a18a042e43789736d0.html) ## 1. 让错误暴露,而不是把错误当正确示范 先完整运行旧规则。`red cup` 和 `blue cup` 可凭颜色命中;`cup left of blue cup` 却因为字符串含 blue 返回 right-blue。这里不是概率不够高,而是程序根本没有关系变量。最后的断言保留这一错误输出,是复现失败的断言,不是验收正确性。 本册依赖 NumPy、Pillow、Matplotlib;每格包含所需定义,按顺序在全新进程运行,无需 PyTorch、网络或别册数据。 [此段独立阅读](/course/notebook-blocks/8f2a75d5421a4f9386da35d111cf6d81.html)展开完整实现代码 ``` # Detached readonly runs write only to an isolated ephemeral directory. import os, tempfile _LAB_WORKDIR = tempfile.mkdtemp(prefix="lecture03-lab15-") os.chdir(_LAB_WORKDIR) print("ephemeral workdir:", _LAB_WORKDIR) from pathlib import Path import json, hashlib, platform, sys from datetime import datetime, timezone import numpy as np from PIL import Image, ImageDraw import matplotlib if 'get_ipython' not in globals(): matplotlib.use('Agg') import matplotlib.pyplot as plt from matplotlib.patches import Rectangle OUT=Path('outputs/lab15');OUT.mkdir(parents=True,exist_ok=True) PALETTE={'red':(214,55,55),'blue':(40,105,218),'brown':(146,95,53)} boxes=['left-red','right-blue','book'] def keyword_match(phrase): if 'red' in phrase and 'left of' not in phrase:return boxes[0] if 'blue' in phrase:return boxes[1] return boxes[0] phrases=['red cup','blue cup','cup left of blue cup'] for p in phrases:print('historical keyword baseline:',p,'->',keyword_match(p)) assert keyword_match(phrases[-1])=='right-blue' # This is the demonstrated ERROR. ``` [此段独立阅读](/course/notebook-blocks/56ecbeb770fc4539a3f66c6cf658820a.html) ## 2. 同一个底图上做干预 画布为 160×96 RGB 像素;左红杯、右蓝杯和棕色书的形状固定。我们保存三个真正的 PNG:原图、覆盖左杯的灰色遮挡图、仅交换红蓝像素值的图。遮挡不是在结果字典里删除 target,而是改变图像;颜色交换也不重新随机画一张场景。 代码断言干预区域之外的像素完全相等。颜色交换使蓝杯的身份从右侧移到左侧;它不改变轮廓、尺寸或背景。再在交换后的同一张图上将 left-of 改为 right-of,才构成只修改语言关系的第四个条件。 [此段独立阅读](/course/notebook-blocks/f54c13d8a9cf4b2680078719d51b539c.html)展开完整实现代码 ``` def scene(): img=Image.new('RGB',(160,96),(245,243,236));d=ImageDraw.Draw(img) def cup(x,color): d.rounded_rectangle((x+18,31,x+34,52),radius=7,outline=color,width=5) d.rectangle((x,24,x+25,57),fill=color) d.ellipse((x,19,x+25,29),fill=color) cup(15,PALETTE['red']);cup(109,PALETTE['blue']) d.rectangle((60,65,89,79),fill=PALETTE['brown']) return np.array(img) base=scene();occluded=base.copy();occlusion_mask=np.zeros(base.shape[:2],bool);occlusion_mask[16:62,11:53]=True occluded[occlusion_mask]=(115,118,123) swapped=base.copy();redmask=np.all(base==PALETTE['red'],axis=-1);bluemask=np.all(base==PALETTE['blue'],axis=-1) swapped[redmask]=PALETTE['blue'];swapped[bluemask]=PALETTE['red'] assert np.array_equal(base[~occlusion_mask],occluded[~occlusion_mask]) assert np.array_equal(base[~(redmask|bluemask)],swapped[~(redmask|bluemask)]) for label,arr in [('base',base),('target-occluded',occluded),('colors-exchanged',swapped)]:Image.fromarray(arr).save(OUT/('lab15-'+label+'.png')) ``` [此段独立阅读](/course/notebook-blocks/bbf14f2650fc44e3ad84f34d49cdb8ff.html) ## 3. 正确分解目标、参照物和关系 像素规则先按预定义 RGB 原型距离阈值产生颜色 mask,再计算四连通区域和半开坐标框 $[x_1,y_1,x_2,y_2)$。原图左杯框为 `[15,19,50,58]`,中心 x=32.5;蓝色参照杯框为 `[109,19,144,58]`,中心 x=126.5。`left-of` 在此具体定义为中心横坐标严格小于,不暗示“整个框都在左侧”等其他关系语义。 解析 `cup left of blue cup` 得到 target category=cup、reference color=blue、relation=left。先找唯一蓝杯,再从其他 cup 候选中筛选满足关系者;书虽也位于蓝杯左边,但类别不是 cup。若参照物缺失或存在多个匹配,或者没有唯一可见目标,程序明确返回 `None`,不把蓝杯顶上去。 这里的视觉候选器也是规则:棕色被指定为 book,红/蓝被指定为 cup。它适合受控场景,不会识别任意颜色的真实杯子;可以观察到像素依赖,但不能把它写成“学会了视觉概念”。 [此段独立阅读](/course/notebook-blocks/18bfe17db1da4cbebc970b56a164ebbb.html)展开完整实现代码 ``` def connected_components(mask): seen=np.zeros(mask.shape,bool);components=[] for y,x in zip(*np.where(mask)): if seen[y,x]:continue stack=[(int(y),int(x))];seen[y,x]=True;points=[] while stack: yy,xx=stack.pop();points.append((yy,xx)) for dy,dx in [(1,0),(-1,0),(0,1),(0,-1)]: ny,nx=yy+dy,xx+dx if 0<=ny=30: yy,xx=np.array(points).T components.append([int(xx.min()),int(yy.min()),int(xx.max()+1),int(yy.max()+1)]) return components def pixel_candidates(rgb): candidates=[] for color,value in PALETTE.items(): distance=np.linalg.norm(rgb.astype(float)-np.array(value),axis=-1) for box in connected_components(distance<20): candidates.append({'color':color,'category':'book' if color=='brown' else 'cup','box':box,'center_x':(box[0]+box[2])/2}) return candidates def ground(rgb,phrase): objects=pixel_candidates(rgb) if ' left of ' in phrase or ' right of ' in phrase: relation='left' if ' left of ' in phrase else 'right' target_noun,reference_phrase=phrase.split(' '+relation+' of ') ref_color=reference_phrase.split()[0] refs=[o for o in objects if o['category']=='cup' and o['color']==ref_color] if len(refs)!=1:return {'status':'reference missing/ambiguous','target':None,'reference':None,'candidates':objects} ref=refs[0] possible=[o for o in objects if o['category']==target_noun and o is not ref and ((o['center_x']ref['center_x']))] return {'status':'unique target' if len(possible)==1 else 'no unique visible target','target':possible[0] if len(possible)==1 else None,'reference':ref,'relation':relation,'candidates':objects} color,noun=phrase.split();found=[o for o in objects if o['color']==color and o['category']==noun] return {'status':'unique target' if len(found)==1 else 'no unique visible target','target':found[0] if len(found)==1 else None,'reference':None,'candidates':objects} cases=[('base',base,'cup left of blue cup'),('target occluded',occluded,'cup left of blue cup'),('colors exchanged',swapped,'cup left of blue cup'),('relation changed',swapped,'cup right of blue cup')] results=[] for condition,pixels,phrase in cases: row={'condition':condition,'phrase':phrase,**ground(pixels,phrase)};results.append(row) print(condition,phrase,'->',row['status'],'target',row['target'],'reference',row['reference']) assert results[0]['target']['color']=='red' assert results[0]['reference']['color']=='blue' assert results[0]['target']['box']==[15,19,50,58] assert results[1]['target'] is None assert results[2]['target'] is None assert results[3]['target']['color']=='red' and results[3]['target']['box'][0]==109 ``` [此段独立阅读](/course/notebook-blocks/3be3345fe62d47d99d64a1e4491c6f03.html) ## 4. 根据实际输出解释每次条件变化 | 条件 | 参照物中心 x | 目标输出 | 原因 | | --- | --- | --- | --- | | 原图,left-of blue cup | 126.5 | 左红杯 `[15,19,50,58]` | cup 且 32.5 < 126.5 | | 同短语,遮住左杯 | 126.5 | 无唯一可见目标 | 目标像素不再可见 | | 交换红蓝颜色,仍问 left-of | 32.5 | 无唯一可见目标 | 蓝参照物已在最左 | | 同交换图,改问 right-of | 32.5 | 右红杯 `[109,19,144,58]` | cup 且 126.5 > 32.5 | ![两项干预与原图的像素差异图](/assets/display/991b6f1e9793fd586df0f08e.webp) 遮挡图只能证明本规则没有看见目标,不证明物理世界中杯子已不存在,更不是遮挡后的完整形状恢复。若希望系统根据上下文推断被遮物,需要另行定义任务、数据与评价。本册不把“不知道”替换为看似确定的框。 可改条件:把书也改成蓝色,会暴露“颜色等于类别”的规则假设;把两个蓝杯都画出来,会触发参照物歧义。先预测每一处应该返回框还是拒答,再修改图像观察。 [此段独立阅读](/course/notebook-blocks/125c5bb8ba834aa8940b9d4ec03b9325.html)展开完整实现代码 ``` fig,axes=plt.subplots(2,2,figsize=(11,7)) for ax,(condition,pixels,phrase),row in zip(axes.flat,cases,results): ax.imshow(pixels) for role,color in [('target','#c93636'),('reference','#2a5ed0')]: obj=row.get(role) if obj: x1,y1,x2,y2=obj['box'];ax.add_patch(Rectangle((x1,y1),x2-x1,y2-y1,fill=False,ec=color,lw=2.5));ax.text(x1,y1-3,role,color=color,fontsize=10,weight='bold') ax.set(xlim=(0,160),ylim=(96,0),xlabel='x (pixels)',ylabel='y (pixels)') ax.set_title(condition+'\n'+phrase+'\n'+row['status'],fontsize=11) fig.suptitle('LAB15: image pixels -> candidate boxes -> explicit relation rules',fontsize=14) fig.tight_layout();fig.savefig(OUT/'lab15-grounding-interventions.png',dpi=160);plt.show();plt.close(fig) fig,axes=plt.subplots(1,2,figsize=(8,3)) for ax,edited,title in [(axes[0],occluded,'Only target region covered'),(axes[1],swapped,'Only cup colors exchanged')]: changed=np.any(base!=edited,axis=-1);ax.imshow(changed,cmap='gray',vmin=0,vmax=1);ax.set_title(title+'\nchanged pixels: '+str(int(changed.sum())),fontsize=10);ax.axis('off') fig.tight_layout();fig.savefig(OUT/'lab15-pixel-differences.png',dpi=160);plt.show();plt.close(fig) report={'notebook_id':'7dc83786d067465aa76264475db2c62d','execution':'local fresh Python process; deterministic rules; no neural pretrained inference','created_utc':datetime.now(timezone.utc).isoformat(),'environment':{'python':sys.version,'numpy':np.__version__,'platform':platform.platform()},'coordinate_convention':'xyxy half-open pixels; relation compares horizontal box centers; category cup only','baseline_error':{'phrase':phrases[-1],'keyword_output':'right-blue','correct_target':'left red cup','failure':'blue modifies the reference, not the target'},'results':results,'pixel_changes':{'occlusion':int(np.any(base!=occluded,axis=-1).sum()),'color_exchange':int(np.any(base!=swapped,axis=-1).sum())},'pretrained_grounding_status':'not run; official GroundingDINO implementation and evidence linked in notebook','input_sha256':{p.name:hashlib.sha256(p.read_bytes()).hexdigest() for p in sorted(OUT.glob('lab15-*.png')) if p.name in ['lab15-base.png','lab15-target-occluded.png','lab15-colors-exchanged.png']}} (OUT/'lab15-results.json').write_text(json.dumps(report,indent=2));print('saved',OUT) ``` [此段独立阅读](/course/notebook-blocks/e73e270ed0de4f8cb1e775057ca0b1bf.html) ## 5. 真实 grounding 模型的证据入口与本册边界 [Grounding DINO 官方仓库](https://github.com/IDEA-Research/GroundingDINO) 提供模型结构、预训练 checkpoint、图像+文本推理代码、阈值与可视化;[论文作者原文](https://arxiv.org/abs/2303.05499) 给出方法与评价。这是已核验的一手实现入口,本册没有运行该权重,不将官方示例写成本次成绩,也不假设它必然正确解析任意空间关系。 官方 Python 示例的核心路径是 `load_model → load_image → predict → annotate`。`predict` 接收真正图像和文本,默认输出候选框及词级匹配分数;box threshold 和 text threshold 会影响保留框与短语。若后续运行,应固定 `GroundingDINO_SwinT_OGC.py`、`groundingdino_swint_ogc.pth`、输入 PNG、文本、两个阈值和设备,保存原始 boxes/logits/phrases 及模型权重哈希,再在本册的同图干预上比较。原图、遮挡图与颜色交换图已经生成在 `outputs/lab15/`,无需另找输入。 **本次已完成:** 关键词失败复现、可见像素候选、关系角色与坐标、四个真实条件输出、干预区域不变量检查。**本次未完成:** 预训练 grounding 权重下载与推理、开放词汇或真实照片评测。`lab15-results.json` 保留这个状态,不把外部入口当成已运行模型。 [此段独立阅读](/course/notebook-blocks/22efa2ceff4046f99dd3adcb0bf68d7f.html)