ClaireLee2429 Claude Opus 4.6 commited on
Commit
e5203dd
·
1 Parent(s): 1705254

Add inference script with post-processing and update README

Browse files

The inference.py script loads the fine-tuned model and generates
recipes with automatic cleanup of generation artifacts (trailing
comments, empty bullets, malformed lines, truncated text).

Co-Authored-By: Claude Opus 4.6 <[email protected]>

Files changed (3) hide show
  1. README.md +40 -27
  2. inference.py +213 -0
  3. inference_results.txt +194 -0
README.md CHANGED
@@ -93,45 +93,58 @@ After a full run:
93
  - `./processed_data/val/` — tokenized validation split (Arrow format)
94
  - `./processed_data/lora_adapter/` — trained LoRA adapter weights
95
 
96
- ## Testing the Trained Model
97
 
98
- ### Load from local adapter
99
 
100
- ```python
101
- from peft import PeftModel
102
- from transformers import AutoModelForCausalLM, AutoTokenizer
103
- import torch
104
 
105
- # Load base model + LoRA adapter
106
- base_model = AutoModelForCausalLM.from_pretrained("google/gemma-2b")
107
- model = PeftModel.from_pretrained(base_model, "./processed_data/lora_adapter")
108
- tokenizer = AutoTokenizer.from_pretrained("./processed_data/lora_adapter")
109
- model.eval()
110
 
111
- # Generate a recipe
112
- prompt = "Recipe for chocolate chip cookies:\n"
113
- inputs = tokenizer(prompt, return_tensors="pt")
114
- with torch.no_grad():
115
- outputs = model.generate(
116
- **inputs,
117
- max_new_tokens=256,
118
- temperature=0.7,
119
- top_p=0.9,
120
- do_sample=True,
121
- repetition_penalty=1.2,
122
- )
123
- print(tokenizer.decode(outputs[0], skip_special_tokens=True))
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  ```
125
 
126
- ### Load from HuggingFace Hub
127
 
128
  ```python
129
  from peft import PeftModel
130
  from transformers import AutoModelForCausalLM, AutoTokenizer
131
 
 
132
  base_model = AutoModelForCausalLM.from_pretrained("google/gemma-2b")
133
- model = PeftModel.from_pretrained(base_model, "ClaireLee2429/gemma-2b-recipes-lora")
134
- tokenizer = AutoTokenizer.from_pretrained("ClaireLee2429/gemma-2b-recipes-lora")
 
 
 
 
135
  ```
136
 
137
  ### Sample output
 
93
  - `./processed_data/val/` — tokenized validation split (Arrow format)
94
  - `./processed_data/lora_adapter/` — trained LoRA adapter weights
95
 
96
+ ## Inference
97
 
98
+ A standalone inference script with built-in post-processing is provided. It removes common generation artifacts (trailing comments, empty bullets, malformed lines, truncated text).
99
 
100
+ ### Quick start
 
 
 
101
 
102
+ ```bash
103
+ python inference.py --prompt "Recipe for chocolate chip cookies:"
104
+ ```
 
 
105
 
106
+ ### Options
107
+
108
+ | Flag | Default | Description |
109
+ |---|---|---|
110
+ | `--prompt` | `"Recipe for chocolate chip cookies:"` | Prompt for recipe generation |
111
+ | `--adapter` | `./processed_data/lora_adapter` | Path to LoRA adapter (local or HuggingFace Hub ID) |
112
+ | `--model` | `google/gemma-2b` | Base model name |
113
+ | `--max-tokens` | `256` | Maximum new tokens to generate |
114
+ | `--temperature` | `0.7` | Sampling temperature |
115
+ | `--raw` | off | Show raw output without post-processing |
116
+ | `--save` | none | Save output to file |
117
+
118
+ ### Examples
119
+
120
+ ```bash
121
+ # Generate with post-processing (default)
122
+ python inference.py --prompt "Recipe for pasta carbonara:"
123
+
124
+ # Compare raw vs cleaned output
125
+ python inference.py --prompt "Recipe for tomato soup:" --raw
126
+
127
+ # Save to file
128
+ python inference.py --prompt "Recipe for banana bread:" --save output.txt
129
+
130
+ # Use adapter from HuggingFace Hub
131
+ python inference.py --adapter ClaireLee2429/gemma-2b-recipes-lora --prompt "Recipe for chicken stir fry:"
132
  ```
133
 
134
+ ### Using the model directly in Python
135
 
136
  ```python
137
  from peft import PeftModel
138
  from transformers import AutoModelForCausalLM, AutoTokenizer
139
 
140
+ # From local adapter
141
  base_model = AutoModelForCausalLM.from_pretrained("google/gemma-2b")
142
+ model = PeftModel.from_pretrained(base_model, "./processed_data/lora_adapter")
143
+ tokenizer = AutoTokenizer.from_pretrained("./processed_data/lora_adapter")
144
+
145
+ # Or from HuggingFace Hub
146
+ # model = PeftModel.from_pretrained(base_model, "ClaireLee2429/gemma-2b-recipes-lora")
147
+ # tokenizer = AutoTokenizer.from_pretrained("ClaireLee2429/gemma-2b-recipes-lora")
148
  ```
149
 
150
  ### Sample output
inference.py ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Standalone inference script for the fine-tuned recipe generation model.
3
+
4
+ Usage:
5
+ python inference.py --prompt "Recipe for chocolate chip cookies:"
6
+ python inference.py --prompt "Recipe for pasta carbonara:" --save output.txt
7
+ python inference.py --prompt "Recipe for banana bread:" --raw
8
+ python inference.py --adapter ClaireLee2429/gemma-2b-recipes-lora --prompt "Recipe for soup:"
9
+ """
10
+
11
+ import argparse
12
+ import re
13
+
14
+ import torch
15
+ from peft import PeftModel
16
+ from transformers import AutoModelForCausalLM, AutoTokenizer
17
+
18
+
19
+ def clean_recipe(text: str) -> str:
20
+ """Post-process generated recipe text to remove artifacts."""
21
+ lines = text.split("\n")
22
+ cleaned = []
23
+
24
+ for line in lines:
25
+ stripped = line.strip()
26
+
27
+ # Remove empty or malformed bullet lines (e.g., "- ", "- .", "- ,", "- AZ")
28
+ if re.match(r"^-\s*[.,;:]*\s*$", stripped):
29
+ continue
30
+
31
+ # Remove short junk bullets (single word/number fragments like "- AZ", "- 12-07-02.")
32
+ if re.match(r"^-\s+\S{1,10}$", stripped) and not re.match(r"^-\s+\d+", stripped):
33
+ # Allow numeric items like "- 1 cup" but skip junk like "- AZ"
34
+ words_after_dash = stripped[2:].strip()
35
+ if len(words_after_dash.split()) <= 1 and not any(
36
+ c.islower() for c in words_after_dash
37
+ ):
38
+ continue
39
+
40
+ # Stop at trailing commentary sections
41
+ if re.match(r"^-?\s*Notes?:", stripped, re.IGNORECASE):
42
+ break
43
+ if re.match(r"^-?\s*Recipe (from|by|submitted)", stripped, re.IGNORECASE):
44
+ break
45
+ if re.match(r"^-?\s*Source:", stripped, re.IGNORECASE):
46
+ break
47
+ if re.match(
48
+ r"^-\s+(I |My |This is |You can |That |He |She |We |It |Visit )",
49
+ stripped,
50
+ ):
51
+ break
52
+ if re.match(r"^-\s+Bon App", stripped):
53
+ break
54
+
55
+ cleaned.append(line)
56
+
57
+ # Remove duplicate consecutive lines
58
+ deduped = []
59
+ for line in cleaned:
60
+ if not deduped or line.strip() != deduped[-1].strip():
61
+ deduped.append(line)
62
+
63
+ # Trim trailing incomplete line (doesn't end with punctuation)
64
+ while deduped:
65
+ last = deduped[-1].strip()
66
+ if not last:
67
+ deduped.pop()
68
+ continue
69
+ if last and last[-1] not in ".!?)\":;":
70
+ deduped.pop()
71
+ else:
72
+ break
73
+
74
+ # Remove trailing blank lines
75
+ while deduped and not deduped[-1].strip():
76
+ deduped.pop()
77
+
78
+ return "\n".join(deduped)
79
+
80
+
81
+ def load_model(model_name: str, adapter_path: str):
82
+ """Load the base model with LoRA adapter."""
83
+ use_cuda = torch.cuda.is_available()
84
+ use_mps = torch.backends.mps.is_available()
85
+
86
+ dtype = torch.bfloat16 if use_cuda else torch.float32
87
+ if use_cuda:
88
+ device_map = "auto"
89
+ elif use_mps:
90
+ device_map = {"": "mps"}
91
+ else:
92
+ device_map = {"": "cpu"}
93
+
94
+ device_name = "CUDA" if use_cuda else ("MPS" if use_mps else "CPU")
95
+ print(f"Loading base model ({device_name})...")
96
+
97
+ base_model = AutoModelForCausalLM.from_pretrained(
98
+ model_name, torch_dtype=dtype, device_map=device_map
99
+ )
100
+
101
+ print(f"Loading LoRA adapter from {adapter_path}...")
102
+ model = PeftModel.from_pretrained(base_model, adapter_path)
103
+ model.eval()
104
+
105
+ tokenizer = AutoTokenizer.from_pretrained(adapter_path)
106
+
107
+ device = "cuda" if use_cuda else ("mps" if use_mps else "cpu")
108
+ return model, tokenizer, device
109
+
110
+
111
+ def generate_recipe(
112
+ model,
113
+ tokenizer,
114
+ device: str,
115
+ prompt: str,
116
+ max_new_tokens: int = 256,
117
+ temperature: float = 0.7,
118
+ raw: bool = False,
119
+ ) -> str:
120
+ """Generate a recipe from a prompt and optionally post-process."""
121
+ inputs = tokenizer(prompt, return_tensors="pt").to(device)
122
+
123
+ with torch.no_grad():
124
+ outputs = model.generate(
125
+ **inputs,
126
+ max_new_tokens=max_new_tokens,
127
+ temperature=temperature,
128
+ top_p=0.9,
129
+ do_sample=True,
130
+ repetition_penalty=1.2,
131
+ )
132
+
133
+ text = tokenizer.decode(outputs[0], skip_special_tokens=True)
134
+
135
+ if raw:
136
+ return text
137
+ return clean_recipe(text)
138
+
139
+
140
+ def main():
141
+ parser = argparse.ArgumentParser(description="Generate recipes with the fine-tuned model")
142
+ parser.add_argument(
143
+ "--prompt",
144
+ type=str,
145
+ default="Recipe for chocolate chip cookies:\n",
146
+ help="Prompt for recipe generation",
147
+ )
148
+ parser.add_argument(
149
+ "--adapter",
150
+ type=str,
151
+ default="./processed_data/lora_adapter",
152
+ help="Path to LoRA adapter (local or HuggingFace Hub ID)",
153
+ )
154
+ parser.add_argument(
155
+ "--model",
156
+ type=str,
157
+ default="google/gemma-2b",
158
+ help="Base model name",
159
+ )
160
+ parser.add_argument(
161
+ "--max-tokens",
162
+ type=int,
163
+ default=256,
164
+ help="Maximum new tokens to generate",
165
+ )
166
+ parser.add_argument(
167
+ "--temperature",
168
+ type=float,
169
+ default=0.7,
170
+ help="Sampling temperature",
171
+ )
172
+ parser.add_argument(
173
+ "--raw",
174
+ action="store_true",
175
+ help="Show raw output without post-processing",
176
+ )
177
+ parser.add_argument(
178
+ "--save",
179
+ type=str,
180
+ default=None,
181
+ help="Save output to file",
182
+ )
183
+
184
+ args = parser.parse_args()
185
+
186
+ # Ensure prompt ends with newline
187
+ prompt = args.prompt if args.prompt.endswith("\n") else args.prompt + "\n"
188
+
189
+ model, tokenizer, device = load_model(args.model, args.adapter)
190
+
191
+ print(f"\nPrompt: {prompt.strip()}")
192
+ print("-" * 40)
193
+
194
+ result = generate_recipe(
195
+ model,
196
+ tokenizer,
197
+ device,
198
+ prompt,
199
+ max_new_tokens=args.max_tokens,
200
+ temperature=args.temperature,
201
+ raw=args.raw,
202
+ )
203
+
204
+ print(result)
205
+
206
+ if args.save:
207
+ with open(args.save, "w") as f:
208
+ f.write(result + "\n")
209
+ print(f"\nSaved to {args.save}")
210
+
211
+
212
+ if __name__ == "__main__":
213
+ main()
inference_results.txt ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ============================================================
2
+ PROMPT: Recipe for chocolate chip cookies:
3
+ ------------------------------------------------------------
4
+ Recipe for chocolate chip cookies:
5
+ Ingredients:
6
+ - 1/2 cup butter
7
+ - 1/4 cup sugar
8
+ - 1/4 cup packed brown sugar
9
+ - 3/4 cup flour
10
+ - 1/2 teaspoon baking soda
11
+ - 1/2 teaspoon salt
12
+ - 1 egg
13
+ - 1 teaspoon vanilla
14
+ - 1/2 cup chocolate chips
15
+
16
+ Directions:
17
+ - In a medium bowl, cream together the butter and sugars.
18
+ - Add in the egg and vanilla, mixing until combined.
19
+ - In another bowl, whisk together the flour, baking soda and salt.
20
+ - Add to the creamed mixture alternately with the chocolate chips, ending with the dry ingredients.
21
+ - Stir in gently using a rubber spatula.
22
+ - Drop by rounded teaspoonfuls onto ungreased cookie sheets.
23
+ - Bake at 350 degrees F for 9 minutes.
24
+ - Cool on wire racks before serving.
25
+ - Yield: 2 dozen cookies.
26
+ - Notes:
27
+ - I use this recipe all the time.
28
+ - The only thing that's changed is adding raisins!
29
+ - It makes it more like a raisin cookie, but still delicious.
30
+ - Recipe may be doubled or tripled if desired.
31
+ - If you make them small
32
+
33
+ ============================================================
34
+ PROMPT: Recipe for pasta carbonara:
35
+ ------------------------------------------------------------
36
+ Recipe for pasta carbonara:
37
+ Ingredients:
38
+ - 100 g spaghetti
39
+ - 100 g smoked bacon or pancetta
40
+ - 250 g mushrooms
41
+ - 2 eggs
42
+ - 1 tablespoon olive oil
43
+ - 1 tablespoon white wine
44
+ - 1 teaspoon freshly grated parmesan cheese
45
+
46
+ Directions:
47
+ - Cook the pasta according to package instructions.
48
+ - Meanwhile, brown the bacon in a frying pan with some olive oil.
49
+ - Add the mushrooms and cook them until they are tender (about 10 minutes).
50
+ - Add the cooked pasta to the mushroom mixture along with the eggs and stir well.
51
+ - Stir in the wine and then sprinkle over the grated parmesan cheese.
52
+ - Season with salt and pepper and serve immediately.
53
+ - Enjoy!
54
+ - Notes:
55
+ - The recipe is based on a traditional Italian dish called pasta carbonara.
56
+ - It's usually made with bacon, but you can also use pancetta or even ham.
57
+ - I like to add some mushrooms too, but it's not necessary.
58
+ - If you don't have any fresh mushrooms, you can use frozen ones instead.
59
+ - Be sure to cook the pasta al dente (just shy of being fully cooked) so that it's still nice
60
+
61
+ ============================================================
62
+ PROMPT: Recipe for banana bread:
63
+ ------------------------------------------------------------
64
+ Recipe for banana bread:
65
+ Ingredients:
66
+ - 2 c. sugar
67
+ - 1/2 c. oil
68
+ - 3 eggs
69
+ - 1 c. mashed banana
70
+ - 3 c. flour
71
+ - 2 tsp. baking soda
72
+ - 1 tsp. cinnamon
73
+ - 1 tsp. salt
74
+ - 1 tsp. vanilla
75
+ - 1 c. chopped nuts
76
+
77
+ Directions:
78
+ - Mix together first four ingredients.
79
+ - Mix the next five ingredients and add to previous mixture.
80
+ - Stir well.
81
+ - Add nuts on top of batter.
82
+ - Bake at 400° for 30 minutes or until toothpick comes out clean.
83
+ - Makes about 9 to 8 loaves.
84
+ - Serves 12 to 6 servings.
85
+ - Recipe from my mother, who was born in 1898.
86
+ - This is a great recipe!
87
+ - My mother had no electric mixer; she used an old fashioned wooden spoon when mixing.
88
+ - It works fine!
89
+ - (This recipe can be halved.)
90
+ - I have also made this with applesauce instead of bananas.
91
+ - The loaf will rise more than if you use bananas.
92
+ - If you want a denser loaf, substitute butter for the oil
93
+
94
+ ============================================================
95
+ PROMPT: Recipe for chicken stir fry:
96
+ ------------------------------------------------------------
97
+ Recipe for chicken stir fry:
98
+ Ingredients:
99
+ - 1 lb. boneless chicken breasts, cut into bite size pieces
100
+ - 1/2 c. soy sauce
101
+ - 1/4 c. brown sugar
102
+ - 1 tsp. cornstarch
103
+ - 3/4 c. water
104
+ - 2 Tbsp. oil
105
+
106
+ Directions:
107
+ - Mix all ingredients and place in microwaveable dish.
108
+ - Cover with plastic wrap and cook on high for 15 minutes.
109
+ - Stir and continue cooking until desired doneness is reached.
110
+ - May also be cooked in a skillet over medium heat using the same method.
111
+ - Serves 4 to 6 people.
112
+ - Note: If you want it spicier, add more chili paste.
113
+ - For extra flavor, try adding some minced ginger.
114
+ - This can also be done in an oven at 350° for about 30 minutes or so.
115
+ - (Serves 4 to 8).
116
+ - Recipe by: Judy G., Mesa, Ariz.
117
+ - .
118
+ - ,
119
+ - AZ
120
+ - 12-07-02.
121
+ - .
122
+ - .
123
+ - .
124
+ - .
125
+ - .
126
+ - .
127
+ - .
128
+ - .
129
+ - .
130
+ -
131
+
132
+ ============================================================
133
+ PROMPT: Recipe for tomato soup:
134
+ ------------------------------------------------------------
135
+ Recipe for tomato soup:
136
+ Ingredients:
137
+ - 1 (28 ounce) can crushed tomatoes
138
+ - 1 (4 ounce) can tomato paste
139
+ - 1 tablespoon dried basil
140
+ - 1/2 tablespoon salt
141
+ - 1/2 tablespoon sugar
142
+ - 1/2 teaspoon garlic powder
143
+ - 1/4 teaspoon red pepper flakes
144
+
145
+ Directions:
146
+ - Combine all ingredients in a large pot and bring to a boil.
147
+ - Reduce heat, cover and simmer until thickened, about 1 hour.
148
+ - Serve over grilled cheese sandwiches or with tortilla chips.
149
+ - Enjoy!
150
+ - Recipe submitted by:
151
+ - Anonymous
152
+ - Source:
153
+ - Food.com
154
+ -
155
+ -
156
+ -
157
+ -
158
+ -
159
+ -
160
+ -
161
+ -
162
+ -
163
+ -
164
+ -
165
+ -
166
+ -
167
+ -
168
+ -
169
+ -
170
+ -
171
+ -
172
+ -
173
+ -
174
+ -
175
+ -
176
+ -
177
+ -
178
+ -
179
+ -
180
+ -
181
+ -
182
+ -
183
+ -
184
+ -
185
+ -
186
+ -
187
+ -
188
+ -
189
+ -
190
+ -
191
+ -
192
+ -
193
+ -
194
+