I just started learning Haskell, and I must say it is unlike anything else. As my first not-entirely-trivial piece of code I tried to write a simple Bayes Classifier. It is in two parts, a classifier module and a cli.
What could be done to improve it? Is it entirely awful? Is it too imperative? Any and all feedback appreciated. Here are the two files:
The Main Classifier:
module BayesClassifier where
-- Text Classifier Using Bayes Formula
import Data.List
import Data.Char
type Category = String
newtype Classifier = Classifier { training :: [(Category, [String])] } deriving (Eq, Show)
-- Get a new classifer with no training
classifier :: Classifier
classifier = Classifier []
-- classifier probabilities
probabilityOfWordInCategory :: Classifier -> String -> Category -> Double
probabilityOfCategory :: Classifier -> Category -> Double
-- Adding + 1 for Laplacian Correction
probabilityOfWordInCategory (Classifier training) word category = let allInCategory = filter (\(cat, _) -> cat == category) training
allInCategoryContainingWord = filter (\(_, text) -> word `elem` text) allInCategory
in (fromIntegral $ length allInCategoryContainingWord + 1) / (fromIntegral $ length allInCategory + 1)
probabilityOfCategory (Classifier training) category = let allInCategory = filter (\(cat, _) -> cat == category) training
in (fromIntegral $ length allInCategory) / (fromIntegral $ length training)
-- Train a classifier
train :: Classifier -> String -> Category -> Classifier
train (Classifier training ) text category = Classifier $ (category, cleanInput $ text):training
-- Categorize text with a classifier
classify :: Classifier -> String -> Category
classify classifier text = fst $ head $ sortBy (\(_, a) (_, b) -> b `compare` a) $ probabilities classifier text
-- Get Probability for each Category
probabilities :: Classifier -> String -> [(Category, Double)]
probabilities classifier@(Classifier training) text = map (\cat -> (cat, probabilityForCategory classifier text cat)) $ nub $ map (\(cat, _) -> cat) training
-- Get Probability for a passage in a certain category
probabilityForCategory :: Classifier -> String -> Category -> Double
probabilityForCategory classifier text category = (+) (log $ probabilityOfCategory classifier category) (sum $ map (\word -> log $ probabilityOfWordInCategory classifier word category) $ cleanInput text)
-- Lowercase, Remove Punctuation
cleanInput :: String -> [String]
cleanInput text = filter (\w -> not (w `elem` stopWords)) $ words $ filter (`elem` ' ':['a'..'z']) $ map toLower text
where stopWords = ["a","about"....(More Stop Words)...."yourself","yourselves"]
and the Interface:
import System.IO
import BayesClassifier
interactionLoop myClassifier function = case function of
"start" ->
do
putStrLn "Enter an action [train|classify]"
action <- getLine
interactionLoop myClassifier action
"train" ->
do
putStr "Category: "
category <- getLine
putStr "Material: "
material <- getLine
interactionLoop (train myClassifier material category) "start"
"classify" ->
do
putStr "Material: "
material <- getLine
putStrLn $ classify myClassifier material
putStrLn . show $ probabilities myClassifier material
putStrLn "\n\n\n\n"
interactionLoop myClassifier "start"
_ ->
interactionLoop myClassifier "start"
main = do
hSetBuffering stdout NoBuffering
hSetBuffering stdin NoBuffering
interactionLoop classifier "start"