This commit is contained in:
Johannes Merl 2024-03-10 11:43:22 +01:00
parent 6435f4aca2
commit f79355e4c1
4 changed files with 92 additions and 28 deletions

View File

@ -20,7 +20,7 @@
-- In order to use it for a certain problem, basically, you have to make your -- In order to use it for a certain problem, basically, you have to make your
-- solution type an instance of 'Individual' and then simply call the 'run' -- solution type an instance of 'Individual' and then simply call the 'run'
-- function. -- function.
module GA (Environment, new, population, mutate, crossover1, crossover, nX, Evaluator, fitness, calc, Individual, GA.run, Tournament (..), N, R, Population, steps, bests, runTests) where module GA (Environment, new, population, mutate, crossover1, crossover, nX, Fitness, getR, Evaluator, fitness,fitness', calc, Individual, GA.run, Tournament (..), N, R, Population, steps, bests, runTests) where
import Control.Arrow hiding (first, second) import Control.Arrow hiding (first, second)
import Data.List.NonEmpty ((<|)) import Data.List.NonEmpty ((<|))
@ -35,7 +35,6 @@ import System.Random.MWC (create, createSystemRandom)
import Test.QuickCheck hiding (sample, shuffle) import Test.QuickCheck hiding (sample, shuffle)
import Test.QuickCheck.Instances () import Test.QuickCheck.Instances ()
import Test.QuickCheck.Monadic import Test.QuickCheck.Monadic
import Debug.Trace as DB
-- TODO there should be a few 'shuffle's here -- TODO there should be a few 'shuffle's here
@ -84,13 +83,16 @@ class (Pretty e, Individual i) => Environment i e | e -> i where
-- | -- |
-- An Evaluator that Individuals of type i can be evaluated by -- An Evaluator that Individuals of type i can be evaluated by
-- It stores all information required to evaluate an individuals fitness -- It stores all information required to evaluate an individuals fitness
class (Individual i) => Evaluator i e where class (Individual i, Fitness r) => Evaluator i e r | i -> e r where
-- | -- |
-- An individual's fitness. Higher values are considered “better”. -- An individual's fitness. Higher values are considered “better”.
-- --
-- We explicitely allow fitness values to be have any sign (see, for example, -- We explicitely allow fitness values to be have any sign (see, for example,
-- 'proportionate1'). -- 'proportionate1').
fitness :: e -> i -> R fitness :: e -> i -> R
fitness env i = getR ( fitness' env i)
fitness' :: e -> i -> r
-- TODO kinda hacky?!? -- TODO kinda hacky?!?
calc :: e -> Population i -> IO e calc :: e -> Population i -> IO e
@ -99,6 +101,12 @@ class (Individual i) => Evaluator i e where
class (Pretty i, Ord i) => Individual i class (Pretty i, Ord i) => Individual i
class (Show i) => Fitness i where
getR :: i -> R
instance Fitness Double where
getR d = d
-- | -- |
-- Populations are just basic non-empty lists. -- Populations are just basic non-empty lists.
type Population i = NonEmpty i type Population i = NonEmpty i
@ -150,18 +158,18 @@ bestsBy' k f pop
-- | -- |
-- The @k@ worst individuals in the population (and the rest of the population). -- The @k@ worst individuals in the population (and the rest of the population).
worst :: (Individual i, Evaluator i e) => e -> N -> Population i -> (NonEmpty i, [i]) worst :: (Individual i, Evaluator i e r) => e -> N -> Population i -> (NonEmpty i, [i])
worst e k = bestsBy k (negate . fitness e) worst e k = bestsBy k (negate . fitness e)
-- | -- |
-- The @k@ best individuals in the population (and the rest of the population). -- The @k@ best individuals in the population (and the rest of the population).
bests :: (Individual i, Evaluator i e) => e -> N -> Population i -> (NonEmpty i, [i]) bests :: (Individual i, Evaluator i e r) => e -> N -> Population i -> (NonEmpty i, [i])
bests e k = bestsBy k (fitness e) bests e k = bestsBy k (fitness e)
-- TODO add top x percent parent selection (select n guys, sort by fitness first) -- TODO add top x percent parent selection (select n guys, sort by fitness first)
reproduce :: reproduce ::
(Individual i, Environment i env, Evaluator i eval, SelectionType s) => (Individual i, Environment i env, Evaluator i eval r, SelectionType s) =>
eval -> eval ->
env -> env ->
-- | Mechanism for selecting parents -- | Mechanism for selecting parents
@ -177,7 +185,7 @@ reproduce eval env selectT nParents pop = do
return pop' return pop'
selectBest :: selectBest ::
(Individual i, Evaluator i eval) => (Individual i, Evaluator i eval r) =>
eval -> eval ->
-- | Elitism ratio @pElite@ -- | Elitism ratio @pElite@
R -> R ->
@ -198,7 +206,7 @@ selectBest eval pElite pop nPop = do
else return $ elitists <> (fst $ bests eval (nPop - length elitists) (NE.fromList rest)) else return $ elitists <> (fst $ bests eval (nPop - length elitists) (NE.fromList rest))
run :: run ::
(Individual i, Evaluator i eval, Environment i env, SelectionType s) => (Individual i, Evaluator i eval r, Environment i env, SelectionType s) =>
eval -> eval ->
env -> env ->
-- | Mechanism for selecting parents -- | Mechanism for selecting parents
@ -210,7 +218,7 @@ run ::
-- | Population size -- | Population size
N -> N ->
Termination i -> Termination i ->
Producer (Int, R) IO (Population i) Producer (Int, r) IO (Population i)
run eval env selectionType nParents pElite nPop term = do run eval env selectionType nParents pElite nPop term = do
mwc <- liftIO createSystemRandom mwc <- liftIO createSystemRandom
let smpl = ((sampleFrom mwc) :: RVar a -> IO a) let smpl = ((sampleFrom mwc) :: RVar a -> IO a)
@ -227,7 +235,7 @@ run eval env selectionType nParents pElite nPop term = do
withKids <- liftIO $ smpl $ reproduce eval env selectionType nParents pop withKids <- liftIO $ smpl $ reproduce eval env selectionType nParents pop
eval <- liftIO $ calc eval withKids eval <- liftIO $ calc eval withKids
resPop <- liftIO $ smpl $ selectBest eval pElite withKids nPop resPop <- liftIO $ smpl $ selectBest eval pElite withKids nPop
let fBest = fitness eval $ NE.head $ fst $ bests eval 1 resPop let fBest = fitness' eval $ NE.head $ fst $ bests eval 1 resPop
Pipes.yield (count, fBest) Pipes.yield (count, fBest)
res <- runIter eval (count + 1) resPop smpl res <- runIter eval (count + 1) resPop smpl
return res) return res)
@ -240,7 +248,7 @@ run eval env selectionType nParents pElite nPop term = do
data Tournament = Tournament N data Tournament = Tournament N
class SelectionType t where class SelectionType t where
select :: (Individual i, Evaluator i e) => t -> N -> Population i -> e -> RVar (NonEmpty i) select :: (Individual i, Evaluator i e r) => t -> N -> Population i -> e -> RVar (NonEmpty i)
-- type Selection m i = N -> Population i -> m (NonEmpty i) -- type Selection m i = N -> Population i -> m (NonEmpty i)
@ -250,7 +258,7 @@ instance SelectionType Tournament where
-- | -- |
-- Selects one individual from the population using tournament selection. -- Selects one individual from the population using tournament selection.
tournament1 :: tournament1 ::
(Individual i, Evaluator i e) => (Individual i, Evaluator i e r) =>
e -> e ->
-- | Tournament size -- | Tournament size
N -> N ->
@ -321,7 +329,7 @@ instance Environment Integer IntTestEnviroment where
data NoData = NoData deriving (Eq) data NoData = NoData deriving (Eq)
instance Evaluator Integer NoData where instance Evaluator Integer NoData Double where
fitness _ = fromIntegral . negate fitness _ = fromIntegral . negate
prop_children_asManyAsParents :: prop_children_asManyAsParents ::

View File

@ -391,11 +391,25 @@ data LamdaExecutionEnv = LamdaExecutionEnv
trainingData :: ([(Float, Float, Float, Float)], [IrisClass]), trainingData :: ([(Float, Float, Float, Float)], [IrisClass]),
exTargetType :: TypeRep, exTargetType :: TypeRep,
-- todo: kindaHacky -- todo: kindaHacky
results :: Map TypeRequester R results :: Map TypeRequester FittnesRes
} }
instance Evaluator TypeRequester LamdaExecutionEnv where data FittnesRes = FittnesRes
fitness env tr = (results env) Map.! tr { total :: R,
fitnessTotal :: R,
fitnessGeoMean :: R,
fitnessMean :: R,
accuracy :: Int,
biasDist :: R,
biasSize :: R
}
deriving (Show)
instance Fitness FittnesRes where
getR = total
instance Evaluator TypeRequester LamdaExecutionEnv FittnesRes where
fitness' env tr = (results env) Map.! tr
calc env pop = do calc env pop = do
let toAdd = NE.filter (\k -> not (Map.member k (results env))) pop let toAdd = NE.filter (\k -> not (Map.member k (results env))) pop
@ -417,22 +431,64 @@ loadTrainingData lee@LamdaExecutionEnv {trainingData = (_ : _, _ : _)} = return
loadTrainingData lee@LamdaExecutionEnv {trainingData = (_ : _, [])} = return undefined loadTrainingData lee@LamdaExecutionEnv {trainingData = (_ : _, [])} = return undefined
loadTrainingData lee@LamdaExecutionEnv {trainingData = ([], _ : _)} = return undefined loadTrainingData lee@LamdaExecutionEnv {trainingData = ([], _ : _)} = return undefined
evalResults :: LamdaExecutionEnv -> [TypeRequester] -> Hint.InterpreterT IO [(TypeRequester, R)] evalResults :: LamdaExecutionEnv -> [TypeRequester] -> Hint.InterpreterT IO [(TypeRequester, FittnesRes)]
evalResults ex trs = mapM (evalResult ex) trs evalResults ex trs = mapM (evalResult ex) trs
evalResult :: LamdaExecutionEnv -> TypeRequester -> Hint.InterpreterT IO (TypeRequester, R) evalResult :: LamdaExecutionEnv -> TypeRequester -> Hint.InterpreterT IO (TypeRequester, FittnesRes)
evalResult ex tr = do evalResult ex tr = do
Hint.setImports $ (map T.unpack (imports ex)) ++ ["Protolude"] Hint.setImports $ (map T.unpack (imports ex)) ++ ["Protolude"]
Hint.unsafeSetGhcOption "-O2" Hint.unsafeSetGhcOption "-O2"
result <- Hint.interpret (T.unpack (toLambdaExpressionS tr)) (Hint.as :: Float -> Float -> Float -> Float -> IrisClass) result <- Hint.interpret (T.unpack (toLambdaExpressionS tr)) (Hint.as :: Float -> Float -> Float -> Float -> IrisClass)
let res = map (\(a,b,c,d) -> result a b c d) (fst (trainingData ex)) let res = map (\(a, b, c, d) -> result a b c d) (fst (trainingData ex))
let resAndTarget = (zip (snd (trainingData ex)) res) let resAndTarget = (zip (snd (trainingData ex)) res)
let acc = (foldr (\ts s -> if ((fst ts) == (snd ts)) then s + 1 else s) 0 resAndTarget) :: R let acc = (foldr (\ts s -> if ((fst ts) == (snd ts)) then s + 1 else s) 0 resAndTarget) :: Int
let biasWellDistributed = (foldr (*) 1 (map (\ty -> (foldr (\ts s -> if ((snd ts) == ty) then s + 1 else s) 1 resAndTarget)) ([minBound..maxBound] :: [IrisClass]):: [R])) ** (1/3) let biasWellDistributed = (foldr (*) 1 (map (\ty -> (foldr (\ts s -> if ((snd ts) == ty) then s + 1 else s) 1 resAndTarget)) ([minBound .. maxBound] :: [IrisClass]) :: [R])) ** (1 / 3) -- 1 (schlecht) bis 51 (gut)
let biasSmall = exp ( - (fromIntegral (countTrsR tr))) let biasSmall = exp (-(fromIntegral (countTrsR tr))) -- 0 (schlecht) bis 1 (gut)
let score = acc + (biasWellDistributed/5.1) + (biasSmall) let fitness' = mean [meanOfAccuricyPerClass resAndTarget, geomeanOfDistributionAccuracy resAndTarget]
return (tr, score) let score = fitness' + (biasSmall - 1)
return
( tr,
FittnesRes
{ total = score,
fitnessTotal = fitness',
fitnessMean = meanOfAccuricyPerClass resAndTarget,
fitnessGeoMean = geomeanOfDistributionAccuracy resAndTarget,
accuracy = acc,
biasDist = biasWellDistributed,
biasSize = biasSmall
}
)
if' :: Bool -> a -> a -> a if' :: Bool -> a -> a -> a
if' True e _ = e if' True e _ = e
if' False _ e = e if' False _ e = e
meanOfAccuricyPerClass :: (Enum r, Bounded r, Eq r) => [(r, r)] -> R
meanOfAccuricyPerClass results = mean $ map (accuracyInClass results) [minBound .. maxBound]
geomeanOfAccuricyPerClass :: (Enum r, Bounded r, Eq r) => [(r, r)] -> R
geomeanOfAccuricyPerClass results = geomean $ map (accuracyInClass results) [minBound .. maxBound]
geomeanOfDistributionAccuracy :: (Enum r, Bounded r, Eq r) => [(r, r)] -> R
geomeanOfDistributionAccuracy results = geomean $ map (distributionAccuracyForClass results) [minBound .. maxBound]
distributionAccuracyForClass :: (Eq r) => [(r, r)] -> r -> R
distributionAccuracyForClass results clas = (1 - (min 1 (fromIntegral (abs ((length (inResClass results clas)) - (length (inClass results clas)))) / fromIntegral (length (inClass results clas))))) * 100
mean :: (Show f, Floating f) => [f] -> f
mean values = (sum values) * (1 / (fromIntegral (length values)))
geomean :: (Show f, Floating f) => [f] -> f
geomean values = (product values) ** (1 / (fromIntegral (length values)))
accuracyInClass :: (Eq r) => [(r, r)] -> r -> R
accuracyInClass results clas = ((accuracy'(inResClass results clas)) * 100) / fromIntegral (length (inClass results clas))
inClass :: (Eq r) => [(r, r)] -> r -> [(r, r)]
inClass results clas = (filter ((clas ==) . fst) results)
inResClass :: (Eq r) => [(r, r)] -> r -> [(r, r)]
inResClass results clas = (filter ((clas ==) . snd) results)
accuracy' :: (Eq r) => [(r, r)] -> R
accuracy' results = fromIntegral $ length (filter (\(target, res) -> (res == target)) results)

View File

@ -51,7 +51,7 @@ main =
hSetBuffering stdout NoBuffering hSetBuffering stdout NoBuffering
let env = irisLE let env = irisLE
let selType = Tournament 3 let selType = Tournament 3
let run' = run irisLEE env selType 40 (5 / 100) (populationSize opts) (steps (iterations opts)) :: Producer (Int, R) IO (Population TypeRequester) let run' = run irisLEE env selType 40 (5 / 100) (populationSize opts) (steps (iterations opts))
pop' <- pop' <-
runEffect (for run' logCsv) runEffect (for run' logCsv)
@ -60,7 +60,7 @@ main =
mapM_ (format irisLE) res mapM_ (format irisLE) res
where where
format irisL s = do format irisL s = do
let f = fitness irisL s let f = fitness' irisL s
putErrText $ show f <> "\n" <> pretty s putErrText $ show f <> "\n" <> pretty s
logCsv = putText . csv logCsv = putText . csv
csv (t, f) = show t <> " " <> show f csv (t, f) = show t <> " " <> show f

View File

@ -159,8 +159,8 @@ prioOf' p (Just _) Nothing = lowestPriority p + 2
prioOf' p Nothing (Just _) = lowestPriority p + 2 prioOf' p Nothing (Just _) = lowestPriority p + 2
prioOf' p (Just s) (Just t) = prioOf p s t prioOf' p (Just s) (Just t) = prioOf p s t
instance Evaluator Assignment Priorities where instance Evaluator Assignment Priorities R where
fitness prio assment = fitness' prio assment =
negate . sum $ fromIntegral . uncurry (prioOf' prio) <$> assment negate . sum $ fromIntegral . uncurry (prioOf' prio) <$> assment
-- | -- |