@@ -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
140291Homebrew installs the command line tools on their own:
0 commit comments