A quick review on my phone, pardon the typing and brevity...
Firstly, I'd be tempted to provide a probability rather than a Boolean prediction. Given that Bayesian statistics only provides probabilities inherently, it seems both more honest and more useful.
It's a bit odd having all the code in a single module other than lib.rs. Doesn't really hurt, but why?
Why have Model as well as Classifier types? It looks like your code could be clearer if a Model were eliminated.
Why eliminate Unicode characters?
Why is get word list a method? It would be cleaner as a function, or a class method without the &self argument.
Why not use a struct rather than an array for your counters? Then your code will require less remembering that 0 is ham and 1 is spam or vice versa.
On line 147ish, using contains key followed by get_mut with unwrap is an antipattern. Instead just use get_mut with if let Some([ham_count, spam_count]). That avoids the extra check and avoids the reader worrying about the unwrap. And saves a few lines of code.
Lines 175 and 182 use the product method. ratings.iter().map(|x| 1.0 - x). product ()
Your Model::new() method looks a bit strange to me... You're passing in a boolean flag which will switch between two completely separate implementations, what about breaking it into two constructors (Model::empty() and Model::from_pre_trained())?
The constructor using pre-trained data should also return a Result instead of unwrap()-ing. That way callers can handle file or parsing errors gracefully instead of crashing.
It can also return serde_json::from_reader(json_file) directly instead of storing the Model in a temporary deserialized variable then creating a new Model which just copies all deserialized's fields across.
Likewise, Model::save() should return a Result (hint: see the docs on ?) instead of unwrapping.
The Classified::new() constructor should probably accept an existing Model instead of invoking the Model::new() constructor internally. That way it doesn't matter where I get my Model from (google "dependency injection").
The Classified::get_word_list() method can drop the return and trailing ;.
It feels weird accepting an is_spam boolean inside Classifier::train() because you're essentially doing the same thing as in Model::new()'s with its create_new parameter. Maybe you want to split these into two methods (Classifier::train_spam() and Classifier::train_ham())?
Classifier::score()... wow that's a lot of math. Can you split it up and give each method useful names so we can understand what it's trying to accomplish?
I agree with @zenoxygen in that L147 should be replaced with if let Some([ham_count, spam_count]) = self.model.token_table.get_mut(&h). When reading Rust code unwrap()s jump out as a possible panic!() site and even though it's safe here, it adds unnecessary cognitive load (i.e. I'm lazy and you made me think ).
I see a lot of C-ish code, a lot of returns, too many muts and too many types written out (e.g. let mut alt_product: f32 = 1.0 should be let mut product = 1.0).
A lot of your loops can be rewritten in a more functional style, e.g. let product: f32 = ratings.iter().product(); (ironically you need the type hint here )
Your method is_spam doesn't need &mut self (because score doesn't need &mut self either).
There are too many unwraps. Better/proper error handling is prefered (e.g. with the snafu crate).
The regex in get_word_list should better be wrapped in a lazy_static! macro (see regex docs for reasons).
Your token_table should not contain an array with two u32, but instead a naming struct, e.g:
You don't need the summarized fields anymore (you can leave them of course for caching), but you have to be careful, that your data is always in sync with each other. That's somewhat error prone. Better provide a "helper method", that gives you the spam and ham total count by adding all of them from your hashmap.
Publicly-exposed types should implement common traits if they can, e.g. it would be nice to #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] for Classifier (and in turn, Model).
{Model,Classifier}::new() and {Model::Classifier}::save() would be better off taking an io::Read and an io::Write, respectively. You can't be sure your users want to use files on disk. Maybe they want to send the serialized model through HTTP or something. Honestly I wouldn't even put these methods on Classifier at all. Rather, use dependency inversion, and let the constructor of Classifier take a Model as its argument, and expose the Model type publicly.
get_word_list(): it's not idiomatic in Rust to name non-getter functions get_xyz(). You're doing a somewhat heavy computation in that method; call it something like load_word_list().
In the same function, it's unnecessary to recreate the regular expression every time you call it. Create it once in new(), put it in the Classifier instance, and then use it through self in load_word_list().
The score() function similarly re-loads the classifier upon every call. Consider using lazy_static! instead.
Looking at the documentation for Classifier, there's no reason to list the type of every argument. It's already written in the signature of the function. In the case below, you're even skipping important type information such as the fact that it takes a mutable reference to the file.