Skip to content

Commit 4f8384e

Browse files
authored
Merge pull request #172 from cardmagic/docs/cli-workflow
docs: show the two commands working together
2 parents f9f33cc + a585814 commit 4f8384e

2 files changed

Lines changed: 168 additions & 1 deletion

File tree

README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,23 @@ space, as in `machine learning:0.35`.
118118
Run `keywords --help` for the full option list. A usage error exits 2 and any
119119
other error exits 1, so scripts can tell the two apart.
120120

121-
[keywords reference →](docs/keywords.md) · [CLI Guide →](https://rubyclassifier.com/docs/guides/cli/basics)
121+
Run the two commands side by side to read a label together with the terms that
122+
make the text distinctive:
123+
124+
```bash
125+
classifier -f reviews-model.json -p "Broken on arrival, awful quality"
126+
# => positive:0.12 negative:0.88
127+
128+
keywords -m reviews.json -n 5 "Broken on arrival, awful quality"
129+
# => awful:0.52 arrival:0.52 broken:0.52 quality:0.44
130+
```
131+
132+
They keep separate models in separate formats, so `classifier` takes `-f` and
133+
`keywords` takes `-m`, and neither reads the other's file. The terms are
134+
context, not an explanation of the label: TF-IDF measures how well a term
135+
separates a document from its corpus, not how much it favors a category.
136+
137+
[Using both commands →](docs/cli.md#using-both-commands-together) · [keywords reference →](docs/keywords.md) · [CLI Guide →](https://rubyclassifier.com/docs/guides/cli/basics)
122138

123139
### Claude Code Plugin
124140

docs/cli.md

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,157 @@ $ classifier -m lsi related article.txt
135135
| `-v`, `--version` | Print the gem version |
136136
| `-h`, `--help` | Print the full usage |
137137

138+
## Using both commands together
139+
140+
`classifier` and `keywords` answer different questions about the same text.
141+
`classifier` gives a category. `keywords` names the terms that make the text
142+
distinctive against its corpus. Run them side by side to read a label together
143+
with what the document is about.
144+
145+
Fit a vocabulary from the same corpus you train on, and the two views line up:
146+
147+
```console
148+
$ keywords fit -m reviews.json reviews/good/*.txt reviews/bad/*.txt
149+
$ keywords info -m reviews.json
150+
Documents: 8
151+
Vocabulary: 37
152+
Min DF: 1
153+
Max DF: 1.0
154+
```
155+
156+
`keywords info` reports the corpus before you commit to training. A vocabulary
157+
of 37 terms over 8 documents says the corpus is far too small, and no
158+
classifier fixes that.
159+
160+
Then train, and read the two answers together:
161+
162+
```console
163+
$ classifier -f reviews-model.json train positive reviews/good/*.txt
164+
$ classifier -f reviews-model.json train negative reviews/bad/*.txt
165+
166+
$ classifier -f reviews-model.json -p "Broken on arrival, awful quality and useless customer service"
167+
positive:0.07 negative:0.93
168+
169+
$ keywords -m reviews.json -n 5 "Broken on arrival, awful quality and useless customer service"
170+
useless:0.4 awful:0.4 arrival:0.4 broken:0.4 service:0.34
171+
```
172+
173+
The first line is the verdict. The second is the document in shorthand, which
174+
tells you what the classifier was reading when a result surprises you.
175+
176+
**The second line is not an explanation of the first.** The two commands hold
177+
separate models. `keywords` reports TF-IDF weight, which measures how well a
178+
term separates this document from the rest of the corpus. It never sees the
179+
classifier, and it does not know which category a term favors. A term can top
180+
the list and carry no weight in the decision.
181+
182+
Read it as context, not as attribution. When a label looks wrong, the terms
183+
tell you whether the document says what you assumed, which is usually the real
184+
problem. For the weights a model actually holds, use
185+
`Classifier::LogisticRegression#weights` from Ruby, which returns the learned
186+
weight per term and per category:
187+
188+
```ruby
189+
classifier.weights("positive", limit: 5)
190+
```
191+
192+
No command line flag reports per-term weights for a Bayes model.
193+
194+
### Find the words your corpus wastes on itself
195+
196+
A term that appears in nearly every document tells a classifier nothing, and
197+
every corpus grows its own. A review corpus repeats `delivery`, a support
198+
corpus repeats `ticket`. These are stopwords that no general stopword list
199+
knows about, because they are specific to your data.
200+
201+
`keywords` finds them, because `--max-df` drops a term that appears in more
202+
than the given ratio of documents. Compare the vocabulary size before and
203+
after:
204+
205+
```console
206+
$ keywords fit -m default.json good.txt bad.txt
207+
$ keywords info -m default.json
208+
Documents: 12
209+
Vocabulary: 42
210+
211+
$ keywords fit -m pruned.json --max-df 0.5 good.txt bad.txt
212+
$ keywords info -m pruned.json
213+
Documents: 12
214+
Vocabulary: 41
215+
```
216+
217+
One term went. Score a document under each model to see which:
218+
219+
```console
220+
$ keywords -m default.json -n 3 "delivery was awful and broken"
221+
broken:0.69 awful:0.69 delivery:0.24
222+
223+
$ keywords -m pruned.json -n 3 "delivery was awful and broken"
224+
broken:0.71 awful:0.71
225+
```
226+
227+
`delivery` sat in all 12 documents and still drew weight. Dropping it sharpens
228+
every term that carries real signal.
229+
230+
Watch the vocabulary count as you tune, because these bounds cut fast:
231+
232+
```console
233+
$ keywords fit -m tight.json --min-df 2 good.txt bad.txt
234+
$ keywords info -m tight.json
235+
Documents: 12
236+
Vocabulary: 7
237+
```
238+
239+
`--min-df 2` took 42 terms down to 7. That is no longer a vocabulary, it is a
240+
handful of words. Move one bound at a time and read `keywords info` after each
241+
change.
242+
243+
### Two commands, two models
244+
245+
The models are separate files in separate formats, and neither command reads
246+
the other's:
247+
248+
```console
249+
$ classifier -f reviews.json "broken awful"
250+
Error: Unknown classifier type in model: tfidf
251+
252+
$ keywords -m reviews-model.json "broken awful"
253+
Error: Invalid vectorizer type: bayes
254+
```
255+
256+
Note the flags differ too. `classifier` takes `-f`, and `keywords` takes `-m`.
257+
258+
| | `classifier` | `keywords` |
259+
|:--|:--|:--|
260+
| Answers | Which category | Which terms matter |
261+
| Model flag | `-f` | `-m` |
262+
| Default model | `./classifier.json` | `./keywords.json` |
263+
| Builds a model with | `train` | `fit` |
264+
| Pre-trained models | Yes, through `-r` | No |
265+
266+
### Do not pipe one into the other
267+
268+
`keywords` prints `term:score` pairs, which is not text to classify. Feeding
269+
its output to `classifier` throws away the rest of the document and weakens the
270+
result:
271+
272+
```console
273+
$ classifier -f reviews-model.json -p "$LONG_REVIEW"
274+
positive:0.08 negative:0.92 # the whole review
275+
276+
$ keywords -m reviews.json -n 4 "$LONG_REVIEW"
277+
arrived:0.6 refund:0.3 useless:0.3 build:0.3
278+
279+
$ classifier -f reviews-model.json -p "arrived refund useless build"
280+
positive:0.21 negative:0.79 # weaker, from the top terms alone
281+
```
282+
283+
Confidence drops from 0.92 to 0.79. TF-IDF ranks a term by how much it
284+
distinguishes one document from the rest of the corpus, which is not the same
285+
as how much it signals a category. Here it puts `arrived` first, a neutral
286+
word about delivery. Classify the full text, and read `keywords` alongside it
287+
for context rather than for attribution.
288+
138289
## Install without Ruby
139290

140291
Homebrew installs the command line tools on their own:

0 commit comments

Comments
 (0)