Skip to content

Commit 09a4f99

Browse files
authored
Merge pull request #143 from lab-v2/bruno-branch
Assignment 1: Image Classifier Tutorial
2 parents 58b2a80 + ee6cb9d commit 09a4f99

11 files changed

Lines changed: 283 additions & 2 deletions
84.2 KB
Loading
76 KB
Loading
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
Image Classifier to PyReason Tutorial
2+
=====================================
3+
4+
This tutorial shows how to use an image classifier as input to PyReason,
5+
then reason over those predictions using logical rules.
6+
To learn more about the LLM used for this tutorial go to: https://huggingface.co/google/vit-base-patch16-224
7+
8+
We will walk through four steps:
9+
10+
Step 1: Load the model
11+
12+
Step 2: Define allowed labels and rules
13+
14+
Step 3: Convert model output into facts
15+
16+
Step 4: Run reasoning and inspect output
17+
18+
The accompanying runnable script is in ``examples/image_classifier_ex.py``.
19+
20+
.. warning::
21+
Only use labels that exist in the classifier's label space.
22+
If you add labels that the model does not support, those labels will
23+
never be produced as high-confidence facts, and your downstream logic can
24+
become misleading.
25+
26+
.. warning::
27+
The model used in this example (``google/vit-base-patch16-224``) follows
28+
ImageNet-style labels. Before adding new species/classes to
29+
``allowed_labels``, verify they exist in ``model.config.id2label``.
30+
31+
32+
Overview
33+
--------
34+
35+
The pipeline in this example is:
36+
37+
1. Load a pretrained vision model from Hugging Face.
38+
2. Read local ``.jpeg`` images from ``examples/images``.
39+
3. Convert classifier outputs into PyReason facts.
40+
4. Add domain rules (fish, shark, bird, eagle, can_fly).
41+
5. Run ``reason()`` and inspect node/edge rule traces.
42+
43+
44+
Step 1: Load the model
45+
----------------------
46+
47+
We load a pretrained image classifier and processor:
48+
49+
.. code:: python
50+
51+
from transformers import AutoImageProcessor, AutoModelForImageClassification
52+
53+
model_name = "google/vit-base-patch16-224"
54+
processor = AutoImageProcessor.from_pretrained(model_name)
55+
model = AutoModelForImageClassification.from_pretrained(model_name)
56+
57+
58+
Step 2: Define allowed labels and rules
59+
---------------------------------------
60+
61+
The model can output many classes, but we restrict reasoning to a selected set
62+
that fits our scenario.
63+
64+
.. code:: python
65+
66+
allowed_labels = [
67+
'goldfish',
68+
'tiger shark',
69+
'hammerhead',
70+
'great white shark',
71+
'tench',
72+
'flamingo',
73+
'bald eagle'
74+
]
75+
76+
Rules then map species-level predictions into abstract concepts:
77+
78+
- ``is_fish(x)``
79+
- ``is_shark(x)``
80+
- ``is_bird(x)``
81+
- ``is_eagle(x)``
82+
- ``can_fly(x)``
83+
- ``likes_to_eat(y, x)``
84+
85+
.. code:: python
86+
87+
add_rule(Rule("is_fish(x) <-0 goldfish(x)", "is_fish_rule"))
88+
add_rule(Rule("is_fish(x) <-0 tench(x)", "is_fish_rule"))
89+
90+
add_rule(Rule("is_shark(x) <-0 tigershark(x)", "is_shark_rule"))
91+
add_rule(Rule("is_shark(x) <-0 hammerhead(x)", "is_shark_rule"))
92+
add_rule(Rule("is_shark(x) <-0 greatwhiteshark(x)", "is_shark_rule"))
93+
add_rule(Rule("is_scary(x) <-0 is_shark(x)", "is_scary_rule"))
94+
95+
add_rule(Rule("is_flamingo(x) <-0 flamingo(x)", "is_flamingo_rule"))
96+
add_rule(Rule("is_bird(x) <-0 flamingo(x)", "is_bird_rule"))
97+
add_rule(Rule("is_eagle(x) <-0 baldeagle(x)", "is_eagle_rule"))
98+
add_rule(Rule("is_bird(x) <-0 baldeagle(x)", "is_bird_rule"))
99+
add_rule(Rule("can_fly(x) <-0 is_bird(x)", "can_fly_rule"))
100+
101+
add_rule(Rule("likes_to_eat(y,x) <-0 is_shark(y), is_fish(x)", "likes_to_eat_rule", infer_edges=True))
102+
add_rule(Rule("likes_to_eat(y,x) <-0 is_flamingo(y), is_fish(x)", "likes_to_eat_rule", infer_edges=True))
103+
104+
105+
Step 3: Convert model output into facts
106+
---------------------------------------
107+
108+
Each image is processed by ``HuggingFaceLogicIntegratedClassifier`` and turned
109+
into PyReason facts with bounds.
110+
111+
.. code:: python
112+
113+
classifier = HuggingFaceLogicIntegratedClassifier(
114+
model,
115+
allowed_labels,
116+
identifier=classifier_name,
117+
interface_options=interface_options,
118+
limit_classes=True
119+
)
120+
121+
logits, probabilities, classifier_facts = classifier(inputs)
122+
123+
for fact in classifier_facts:
124+
add_fact(fact)
125+
126+
Examples of generated facts:
127+
128+
.. code:: text
129+
130+
goldfish(fish_1) : [1.0,1.0] | start: 0 -> end: 0
131+
tigershark(shark_1) : [1.0,1.0] | start: 0 -> end: 0
132+
flamingo(Flamingo_1) : [1.0,1.0] | start: 0 -> end: 0
133+
baldeagle(eagle_1) : [1.0,1.0] | start: 0 -> end: 0
134+
135+
136+
Step 4: Run reasoning and inspect output
137+
----------------------------------------
138+
139+
After facts and rules are loaded, run PyReason:
140+
141+
.. code:: python
142+
143+
Settings.atom_trace = True
144+
interpretation = reason()
145+
trace = get_rule_trace(interpretation)
146+
print(trace[0]) # node trace
147+
print(trace[1]) # edge trace
148+
149+
This prints which rules fired on which nodes and edges.
150+
151+
152+
Expected output: classifier (by image group)
153+
--------------------------------------------
154+
155+
For each photo the script prints one block: filename, then one line per label
156+
in ``allowed_labels``. The label that matches what the model thinks the photo
157+
is gets a tight bound ``[1.0, 1.0]``. The other labels still print, but with
158+
``[0.0, 1.0]``. That is the model not picking that class for this image. It is
159+
not the same as ``[0.0, 0.0]`` on both ends.
160+
161+
Fish image trace
162+
^^^^^^^^^^^
163+
164+
.. code:: text
165+
166+
Processing Image: fish_1.jpeg
167+
=== Fish Classifier Output ===
168+
169+
Generated Classifier Facts:
170+
goldfish(fish_1) : [1.0,1.0] | start: 0 -> end: 0
171+
tench(fish_1) : [0.0,1.0] | start: 0 -> end: 0
172+
flamingo(fish_1) : [0.0,1.0] | start: 0 -> end: 0
173+
tigershark(fish_1) : [0.0,1.0] | start: 0 -> end: 0
174+
hammerhead(fish_1) : [0.0,1.0] | start: 0 -> end: 0
175+
baldeagle(fish_1) : [0.0,1.0] | start: 0 -> end: 0
176+
greatwhiteshark(fish_1) : [0.0,1.0] | start: 0 -> end: 0
177+
Done processing image fish_1.jpeg
178+
179+
After classification: what PyReason does next
180+
--------------------------------------------
181+
182+
Reasoning runs at timestep ``0`` and finishes in three fixed-point passes (you
183+
will see ``Fixed Point iterations: 3`` in the log).
184+
185+
**Pass 0.** Facts on the graph. The trace rows with ``Fixed-Point-Operation``
186+
``0`` are the classifier facts: each image node gets its winning species as a
187+
predicate (``goldfish``, ``tigershark``, ``flamingo``, ``baldeagle``, etc.).
188+
``Occurred Due To`` points at the fact name that fired.
189+
190+
**Pass 1.** Group the animals. The rules lift those facts into ``is_fish``,
191+
``is_shark``, ``is_flamingo``, ``is_eagle``, and ``is_bird``. A bald eagle ends
192+
up with both ``is_eagle`` and ``is_bird`` because two different rules match.
193+
194+
**Pass 2.** Extra facts and edges. From ``is_shark`` you get ``is_scary``. From
195+
``is_bird`` you get ``can_fly`` for flamingos and eagles. The ``likes_to_eat``
196+
edges show up here too (sharks and flamingos both use rules named ``likes_to_eat``
197+
toward fish nodes).
198+
199+
Full node and edge traces for this example are saved under
200+
``examples/csv_outputs/image_classifier_rule_trace_nodes.csv`` and
201+
``examples/csv_outputs/image_classifier_rule_trace_edges.csv``.
202+
203+
.. warning::
204+
Node and edge traces are printed as pandas tables. A narrow terminal may hide
205+
columns in the middle and show ``...``. Widen the terminal or print with
206+
``to_string()`` if you need every column.
207+
208+
209+
Sample end of run
210+
^^^^^^^^^^^^^^^^^
211+
212+
.. image:: image_classifier_Output_fish1.png
213+
214+
.. image:: image_classifier_Output_Table.png
215+
216+
217+
Common Pitfalls
218+
---------------
219+
220+
1. If you see many ``[0.0,1.0]`` facts and very few ``[1.0,1.0]`` facts,
221+
your image may not match the selected allowed labels well.
222+
223+
2. If a class never appears, confirm:
224+
225+
- the class exists in ``model.config.id2label``
226+
- the class spelling in ``allowed_labels`` matches model labels
227+
- your image set actually contains that visual concept clearly
228+

docs/source/tutorials/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Contents
1616
./custom_thresholds.rst
1717
./infer_edges.rst
1818
./annotation_function.rst
19+
./image_classifier_reasoning.rst
1920
./temporal_classifier_tutorial.rst
2021
./cybersecurity_inconsistency.rst
2122
./load_rules_facts_from_file.rst
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Time,Fixed-Point-Operation,Edge,Label,Old Bound,New Bound,Occurred Due To,Consistent,Triggered By,Inconsistency Message,Clause-1,Clause-2
2+
0,2,"('shark_1', 'fish_1')",likes_to_eat,"[0.0,1.0]","[1.0,1.0]",likes_to_eat_rule,True,Rule,,['shark_1'],['fish_1']
3+
0,2,"('shark_1', 'fish_2')",likes_to_eat,"[0.0,1.0]","[1.0,1.0]",likes_to_eat_rule,True,Rule,,['shark_1'],['fish_2']
4+
0,2,"('shark_2', 'fish_1')",likes_to_eat,"[0.0,1.0]","[1.0,1.0]",likes_to_eat_rule,True,Rule,,['shark_2'],['fish_1']
5+
0,2,"('shark_2', 'fish_2')",likes_to_eat,"[0.0,1.0]","[1.0,1.0]",likes_to_eat_rule,True,Rule,,['shark_2'],['fish_2']
6+
0,2,"('shark_3', 'fish_1')",likes_to_eat,"[0.0,1.0]","[1.0,1.0]",likes_to_eat_rule,True,Rule,,['shark_3'],['fish_1']
7+
0,2,"('shark_3', 'fish_2')",likes_to_eat,"[0.0,1.0]","[1.0,1.0]",likes_to_eat_rule,True,Rule,,['shark_3'],['fish_2']
8+
0,2,"('Flamingo_1', 'fish_1')",likes_to_eat,"[0.0,1.0]","[1.0,1.0]",likes_to_eat_rule,True,Rule,,['Flamingo_1'],['fish_1']
9+
0,2,"('Flamingo_1', 'fish_2')",likes_to_eat,"[0.0,1.0]","[1.0,1.0]",likes_to_eat_rule,True,Rule,,['Flamingo_1'],['fish_2']
10+
0,2,"('Flamingo_2', 'fish_1')",likes_to_eat,"[0.0,1.0]","[1.0,1.0]",likes_to_eat_rule,True,Rule,,['Flamingo_2'],['fish_1']
11+
0,2,"('Flamingo_2', 'fish_2')",likes_to_eat,"[0.0,1.0]","[1.0,1.0]",likes_to_eat_rule,True,Rule,,['Flamingo_2'],['fish_2']
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
Time,Fixed-Point-Operation,Node,Label,Old Bound,New Bound,Occurred Due To,Consistent,Triggered By,Inconsistency Message,Clause-1
2+
0,0,fish_1,goldfish,"[0.0,1.0]","[1.0,1.0]",fish_1-goldfish-fact,True,Fact,,
3+
0,0,shark_1,tigershark,"[0.0,1.0]","[1.0,1.0]",shark_1-tiger shark-fact,True,Fact,,
4+
0,0,Flamingo_1,flamingo,"[0.0,1.0]","[1.0,1.0]",Flamingo_1-flamingo-fact,True,Fact,,
5+
0,0,eagle_2,baldeagle,"[0.0,1.0]","[1.0,1.0]",eagle_2-bald eagle-fact,True,Fact,,
6+
0,0,eagle_1,baldeagle,"[0.0,1.0]","[1.0,1.0]",eagle_1-bald eagle-fact,True,Fact,,
7+
0,0,Flamingo_2,flamingo,"[0.0,1.0]","[1.0,1.0]",Flamingo_2-flamingo-fact,True,Fact,,
8+
0,0,shark_2,tigershark,"[0.0,1.0]","[1.0,1.0]",shark_2-tiger shark-fact,True,Fact,,
9+
0,0,fish_2,goldfish,"[0.0,1.0]","[1.0,1.0]",fish_2-goldfish-fact,True,Fact,,
10+
0,0,shark_3,hammerhead,"[0.0,1.0]","[1.0,1.0]",shark_3-hammerhead-fact,True,Fact,,
11+
0,1,fish_1,is_fish,"[0.0,1.0]","[1.0,1.0]",is_fish_rule,True,Rule,,['fish_1']
12+
0,1,fish_2,is_fish,"[0.0,1.0]","[1.0,1.0]",is_fish_rule,True,Rule,,['fish_2']
13+
0,1,shark_1,is_shark,"[0.0,1.0]","[1.0,1.0]",is_shark_rule,True,Rule,,['shark_1']
14+
0,1,shark_2,is_shark,"[0.0,1.0]","[1.0,1.0]",is_shark_rule,True,Rule,,['shark_2']
15+
0,1,shark_3,is_shark,"[0.0,1.0]","[1.0,1.0]",is_shark_rule,True,Rule,,['shark_3']
16+
0,1,Flamingo_1,is_flamingo,"[0.0,1.0]","[1.0,1.0]",is_flamingo_rule,True,Rule,,['Flamingo_1']
17+
0,1,Flamingo_2,is_flamingo,"[0.0,1.0]","[1.0,1.0]",is_flamingo_rule,True,Rule,,['Flamingo_2']
18+
0,1,Flamingo_1,is_bird,"[0.0,1.0]","[1.0,1.0]",is_bird_rule,True,Rule,,['Flamingo_1']
19+
0,1,Flamingo_2,is_bird,"[0.0,1.0]","[1.0,1.0]",is_bird_rule,True,Rule,,['Flamingo_2']
20+
0,1,eagle_2,is_eagle,"[0.0,1.0]","[1.0,1.0]",is_eagle_rule,True,Rule,,['eagle_2']
21+
0,1,eagle_1,is_eagle,"[0.0,1.0]","[1.0,1.0]",is_eagle_rule,True,Rule,,['eagle_1']
22+
0,1,eagle_2,is_bird,"[0.0,1.0]","[1.0,1.0]",is_bird_rule,True,Rule,,['eagle_2']
23+
0,1,eagle_1,is_bird,"[0.0,1.0]","[1.0,1.0]",is_bird_rule,True,Rule,,['eagle_1']
24+
0,2,shark_1,is_scary,"[0.0,1.0]","[1.0,1.0]",is_scary_rule,True,Rule,,['shark_1']
25+
0,2,shark_2,is_scary,"[0.0,1.0]","[1.0,1.0]",is_scary_rule,True,Rule,,['shark_2']
26+
0,2,shark_3,is_scary,"[0.0,1.0]","[1.0,1.0]",is_scary_rule,True,Rule,,['shark_3']
27+
0,2,Flamingo_1,can_fly,"[0.0,1.0]","[1.0,1.0]",can_fly_rule,True,Rule,,['Flamingo_1']
28+
0,2,Flamingo_2,can_fly,"[0.0,1.0]","[1.0,1.0]",can_fly_rule,True,Rule,,['Flamingo_2']
29+
0,2,eagle_2,can_fly,"[0.0,1.0]","[1.0,1.0]",can_fly_rule,True,Rule,,['eagle_2']
30+
0,2,eagle_1,can_fly,"[0.0,1.0]","[1.0,1.0]",can_fly_rule,True,Rule,,['eagle_1']

examples/image_classifier_ex.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,13 @@
1616
from pyreason.scripts.facts.fact import Fact
1717
from pyreason.scripts.learning.utils.model_interface import ModelInterfaceOptions
1818
from pyreason.scripts.rules.rule import Rule
19-
from pyreason.pyreason import _Settings as Settings, reason, reset_settings, get_rule_trace, add_fact, add_rule, load_graph, save_rule_trace
19+
from pyreason.pyreason import _Settings as Settings, reason, reset_settings, get_rule_trace, add_fact, add_rule, load_graph
2020

21+
#import pandas as pd
22+
#If you want to see the full table, you can uncomment the following lines and run the script.
23+
#pd.set_option("display.max_columns", None) # show all columns
24+
#pd.set_option("display.width", 200) # wider line before wrapping
25+
#pd.set_option("display.max_colwidth", None) # don't truncate cell text
2126

2227
# Step 1: Load a pre-trained model and image processor from Hugging Face
2328
model_name = "google/vit-base-patch16-224" # Vision Transformer model
@@ -31,7 +36,7 @@
3136
image_dir = Path(__file__).resolve().parent.parent / "examples" / "images"
3237
image_paths = list(Path(image_dir).glob("*.jpeg")) # Get all .jpeg files in the directory
3338
image_list = []
34-
allowed_labels = ['goldfish', 'tiger shark', 'hammerhead', 'great white shark', 'tench']
39+
allowed_labels = ['goldfish', 'tiger shark', 'hammerhead', 'great white shark', 'tench', 'flamingo', 'bald eagle']
3540

3641
# Add Rules to the knowlege base
3742
add_rule(Rule("is_fish(x) <-0 goldfish(x)", "is_fish_rule"))
@@ -41,6 +46,12 @@
4146
add_rule(Rule("is_shark(x) <-0 greatwhiteshark(x)", "is_shark_rule"))
4247
add_rule(Rule("is_scary(x) <-0 is_shark(x)", "is_scary_rule"))
4348
add_rule(Rule("likes_to_eat(y,x) <-0 is_shark(y), is_fish(x)", "likes_to_eat_rule", infer_edges=True))
49+
add_rule(Rule("is_flamingo(x) <-0 flamingo(x)", "is_flamingo_rule"))
50+
add_rule(Rule("is_bird(x) <-0 flamingo(x)", "is_bird_rule"))
51+
add_rule(Rule("is_eagle(x) <-0 baldeagle(x)", "is_eagle_rule"))
52+
add_rule(Rule("is_bird(x) <-0 baldeagle(x)", "is_bird_rule"))
53+
add_rule(Rule("can_fly(x) <-0 is_bird(x)", "can_fly_rule"))
54+
add_rule(Rule("likes_to_eat(y,x) <-0 is_flamingo(y), is_fish(x)", "likes_to_eat_rule", infer_edges=True))
4455

4556
for image_path in image_paths:
4657
print(f"Processing Image: {image_path.name}")

examples/images/Flamingo_1.jpeg

5.41 MB
Loading

examples/images/Flamingo_2.jpeg

370 KB
Loading

examples/images/eagle_1.jpeg

4.8 KB
Loading

0 commit comments

Comments
 (0)