// // EyeDeeThreeLearner.m // Pennyworth // // Created by Chris Karr on 1/22/08. // Copyright 2008 Chris J. Karr. All rights reserved. // #import "DecisionTreeLearner.h" #define EXAMPLES_KEY @"Training Examples" #define FEATURE_KEY @"Feature" #define LEARNER_PATH [NSString stringWithFormat:@"%@/Library/Application Support/Pennyworth/Decision Tree (KEY).learner", NSHomeDirectory ()] NSInteger TreeSort (id one, id two, void * context) { if (([one isEqual:@"true"] || [one isEqual:@"false"]) && ([two isEqual:@"true"] || [two isEqual:@"false"])) return [two compare:one]; return [one compare:two]; } @implementation DecisionTreeLearner @synthesize examples; @synthesize decisionTree; - (NSString *) bestFeatureForExamples:(NSArray *) exampleArray { NSMutableSet * features = [NSMutableSet set]; for (NSDictionary * example in exampleArray) [features addObjectsFromArray:[example allKeys]]; [features removeObject:LABEL_KEY]; return [features anyObject]; } - (NSMutableDictionary *) treeForExamples:(NSArray *) exampleArray { NSMutableDictionary * node = [NSMutableDictionary dictionary]; NSString * last = nil; BOOL match = YES; for (NSDictionary * example in exampleArray) { if (last == nil) last = [example valueForKey:LABEL_KEY]; else if (![last isEqual:[example valueForKey:LABEL_KEY]]) match = NO; } if (match) [node setValue:last forKey:LABEL_KEY]; else { NSString * feature = [self bestFeatureForExamples:exampleArray]; NSMutableDictionary * matchingExamples = [NSMutableDictionary dictionary]; if (feature != nil) { [node setValue:feature forKey:FEATURE_SENSOR]; for (NSMutableDictionary * example in exampleArray) { NSMutableDictionary * thisExample = [NSMutableDictionary dictionaryWithDictionary:[example copy]]; NSObject * value = [thisExample valueForKey:feature]; if (value == nil) { [thisExample setValue:UNKNOWN_VALUE forKey:feature]; value = UNKNOWN_VALUE; } NSMutableArray * values = [NSMutableArray array]; if ([value isKindOfClass:[NSArray array]]) [values addObjectsFromArray:(NSArray *) value]; else [values addObject:value]; for (NSString * observation in values) { NSMutableArray * matches = [matchingExamples valueForKey:observation]; if (matches == nil) { matches = [NSMutableArray array]; [matchingExamples setValue:matches forKey:observation]; } [matches addObject:thisExample]; } [thisExample removeObjectForKey:feature]; } for (NSString * exampleKey in [matchingExamples allKeys]) [node setValue:[self treeForExamples:[matchingExamples valueForKey:exampleKey]] forKey:exampleKey]; } } return node; } - (CGFloat) prune:(NSMutableDictionary *) tree { NSLog (@"default implementation doesn't prune"); return 0; } - (DecisionTreeLearner *) init { if (self = [super init]) { [self load]; } return self; } - (void) load { BOOL isDir = NO; NSFileManager * fm = [NSFileManager defaultManager]; NSString * path = [LEARNER_PATH stringByReplacingOccurrencesOfString:@"KEY" withString:key]; [fm fileExistsAtPath:[path stringByDeletingLastPathComponent] isDirectory:&isDir]; if (!isDir) [fm createDirectoryAtPath:[path stringByDeletingLastPathComponent] attributes:nil]; if ([fm fileExistsAtPath:path isDirectory:&isDir]) { if (self.examples == nil) { NSData * data = [NSData dataWithContentsOfFile:path]; NSKeyedUnarchiver * unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; self.examples = [NSMutableArray arrayWithArray:[unarchiver decodeObjectForKey:EXAMPLES_KEY]]; [unarchiver release]; } } else self.examples = [NSMutableArray array]; self.decisionTree = [self treeForExamples:self.examples]; [self prune:self.decisionTree]; } /* - (void) awakeFromNib { NSMutableArray * testExamples = [NSMutableArray array]; [testExamples addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys: @"No", LABEL_KEY, @"Rain", @"Outlook", @"Mild", @"Temp", @"High", @"Hum", @"Strong", @"Wind", nil]]; [testExamples addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys: @"Yes", LABEL_KEY, @"Overcast", @"Outlook", @"Hot", @"Temp", @"Normal", @"Hum", @"Weak", @"Wind", nil]]; [testExamples addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys: @"Yes", LABEL_KEY, @"Overcast", @"Outlook", @"Mild", @"Temp", @"High", @"Hum", @"Strong", @"Wind", nil]]; [testExamples addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys: @"Yes", LABEL_KEY, @"Sunny", @"Outlook", @"Mild", @"Temp", @"Normal", @"Hum", @"Strong", @"Wind", nil]]; [testExamples addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys: @"Yes", LABEL_KEY, @"Rain", @"Outlook", @"Mild", @"Temp", @"Normal", @"Hum", @"Weak", @"Wind", nil]]; [testExamples addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys: @"Yes", LABEL_KEY, @"Sunny", @"Outlook", @"Cool", @"Temp", @"Normal", @"Hum", @"Weak", @"Wind", nil]]; [testExamples addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys: @"No", LABEL_KEY, @"Sunny", @"Outlook", @"Mild", @"Temp", @"High", @"Hum", @"Weak", @"Wind", nil]]; [testExamples addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys: @"Yes", LABEL_KEY, @"Overcast", @"Outlook", @"Cool", @"Temp", @"Normal", @"Hum", @"Strong", @"Wind", nil]]; [testExamples addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys: @"No", LABEL_KEY, @"Rain", @"Outlook", @"Cool", @"Temp", @"Normal", @"Hum", @"Strong", @"Wind", nil]]; [testExamples addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys: @"Yes", LABEL_KEY, @"Rain", @"Outlook", @"Cool", @"Temp", @"Normal", @"Hum", @"Weak", @"Wind", nil]]; [testExamples addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys: @"Yes", LABEL_KEY, @"Rain", @"Outlook", @"Mild", @"Temp", @"High", @"Hum", @"Weak", @"Wind", nil]]; [testExamples addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys: @"Yes", LABEL_KEY, @"Overcast", @"Outlook", @"Hot", @"Temp", @"High", @"Hum", @"Weak", @"Wind", nil]]; [testExamples addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys: @"No", LABEL_KEY, @"Sunny", @"Outlook", @"Hot", @"Temp", @"High", @"Hum", @"Strong", @"Wind", nil]]; [testExamples addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys: @"No", LABEL_KEY, @"Sunny", @"Outlook", @"Hot", @"Temp", @"High", @"Hum", @"Weak", @"Wind", nil]]; NSLog (@"tree = %@", [self treeForExamples:testExamples]); } */ - (NSXMLElement *) getXML:(NSDictionary *) tree { NSXMLElement * element = [[NSXMLElement alloc] initWithName:@"node"]; NSString * label = [tree valueForKey:LABEL_KEY]; // [element addAttribute:[NSXMLNode attributeWithName:@"count" stringValue:[[tree valueForKey:EXAMPLE_COUNT] description]]]; if (label != nil) [element addAttribute:[NSXMLNode attributeWithName:@"label" stringValue:label]]; else { NSDictionary * feature = [tree valueForKey:FEATURE_SENSOR]; [element addAttribute:[NSXMLNode attributeWithName:@"feature" stringValue:[feature valueForKey:FEATURE_NAME]]]; NSNumber * split = [feature valueForKey:SPLIT_VALUE]; if (split != nil) [element addAttribute:[NSXMLNode attributeWithName:@"split_value" stringValue:[split description]]]; for (NSString * k in [[tree allKeys] sortedArrayUsingFunction:TreeSort context:NULL]) { NSObject * value = [tree valueForKey:k]; if ([value isKindOfClass:[NSDictionary class]] && ![k isEqualToString:FEATURE_SENSOR]) { NSXMLElement * valueElement = [[NSXMLElement alloc] initWithName:@"value"]; [valueElement addAttribute:[NSXMLNode attributeWithName:@"observed" stringValue:k]]; [valueElement addChild:[self getXML:((NSDictionary *) value)]]; [element addChild:valueElement]; [valueElement release]; } } } return element; } - (void) saveXMLTree:(NSString *) path { NSXMLDocument * doc = [[NSXMLDocument alloc] init]; [doc setRootElement:[self getXML:self.decisionTree]]; [[doc rootElement] release]; [[doc XMLData] writeToFile:path atomically:YES]; [doc release]; } - (void) save { NSMutableData * data = [NSMutableData data]; NSKeyedArchiver * archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; [archiver encodeObject:self.examples forKey:EXAMPLES_KEY]; [archiver finishEncoding]; [archiver release]; NSString * path = [LEARNER_PATH stringByReplacingOccurrencesOfString:@"KEY" withString:key]; [data writeToFile:path atomically:YES]; // [self saveXMLTree:[[NSString stringWithFormat:@"~/Desktop/%@.xml", key, nil] stringByExpandingTildeInPath]]; } - (void) setKey:(NSString *) newKey { self.examples = nil; [super setKey:newKey]; [self load]; } - (void) reset { [super reset]; [self.examples removeAllObjects]; [self save]; [self load]; } - (NSNumber *) exampleCount { return [NSNumber numberWithInt:[examples count]]; } - (NSString *) labelForTree:(NSDictionary *) treeDict features:(NSArray *) features { NSString * label = [treeDict valueForKey:LABEL_KEY]; if (label != nil) return label; else { NSString * featureKey = [treeDict valueForKey:FEATURE_SENSOR]; NSMutableArray * labels = [NSMutableArray array]; NSMutableArray * matchingFeatures = [NSMutableArray array]; for (NSDictionary * feature in features) { if ([featureKey isEqual:[feature valueForKey:FEATURE_SENSOR]]) [matchingFeatures addObject:feature]; } NSMutableArray * newFeatures = [NSMutableArray arrayWithArray:features]; [newFeatures removeObjectsInArray:matchingFeatures]; for (NSDictionary * feature in matchingFeatures) { NSDictionary * child = [treeDict valueForKey:[feature valueForKey:FEATURE_OBSERVATION]]; if (child != nil) { NSString * featureLabel = [self labelForTree:child features:newFeatures]; if (featureLabel != nil) [labels addObject:featureLabel]; } } [labels sortUsingSelector:@selector(compare:)]; NSMutableDictionary * labelCounts = [NSMutableDictionary dictionary]; for (NSString * newLabel in labels) { NSNumber * count = [labelCounts valueForKey:newLabel]; if (count == nil) count = [NSNumber numberWithInt:1]; else count = [NSNumber numberWithInt:(1 + [count intValue])]; [labelCounts setValue:count forKey:newLabel]; } int currentCount = 0; for (NSString * featureKey in [labelCounts allKeys]) { NSNumber * labelCount = [labelCounts valueForKey:featureKey]; if ([labelCount intValue] > currentCount) { currentCount = [labelCount intValue]; label = featureKey; } } } return label; } - (NSString *) getLabelForExample:(NSArray *) features { return [self labelForTree:self.decisionTree features:features]; } - (void) addExample:(NSArray *) features forClass:(NSString *) label { NSMutableDictionary * example = [NSMutableDictionary dictionaryWithObject:label forKey:LABEL_KEY]; for (NSDictionary * feature in features) [example setValue:[feature valueForKey:FEATURE_OBSERVATION] forKey:[feature valueForKey:FEATURE_SENSOR]]; [self.examples addObject:example]; [self save]; [self load]; } - (void) removeLastExample:(NSNotification *) note { if ([self.examples count] > 0) { [self.examples removeLastObject]; [self save]; [self load]; } } @end