Class: Mjai::Manue::DangerEstimator
- Inherits:
-
Object
- Object
- Mjai::Manue::DangerEstimator
- Defined in:
- lib/mjai/manue/danger_estimator.rb
Defined Under Namespace
Classes: DecisionNode, DecisionTree, Scene, StoredKyoku, StoredScene
Instance Attribute Summary collapse
-
#min_gap ⇒ Object
Returns the value of attribute min_gap.
-
#verbose ⇒ Object
Returns the value of attribute verbose.
Class Method Summary collapse
- .bool_array_to_bit_vector(bool_array) ⇒ Object
- .feature_vector_to_str(feature_vector) ⇒ Object
- .get_feature_value(feature_vector, feature_name) ⇒ Object
Instance Method Summary collapse
- #aggregate_probabilities(criteria) ⇒ Object
- #calculate_probabilities(features_path, criteria) ⇒ Object
- #calculate_single_probabilities(features_path) ⇒ Object
- #create_kyoku_probs_map(features_path, criteria) ⇒ Object
- #extract_features_from_file(input_path, listener) ⇒ Object
- #extract_features_from_files(input_paths, output_path, listener = nil) ⇒ Object
- #generate_decision_tree(features_path, base_criterion = {}, base_node = nil, root = nil) ⇒ Object
-
#initialize ⇒ DangerEstimator
constructor
A new instance of DangerEstimator.
- #match?(feature_vector, positive_mask, negative_mask) ⇒ Boolean
- #node_to_hash(node) ⇒ Object
- #render_decision_tree(node, label, indent = 0) ⇒ Object
- #update_metrics_for_kyoku(stored_kyoku, criterion_masks) ⇒ Object
Constructor Details
#initialize ⇒ DangerEstimator
Returns a new instance of DangerEstimator.
444 445 446 |
# File 'lib/mjai/manue/danger_estimator.rb', line 444 def initialize() @min_gap = 0.0 end |
Instance Attribute Details
#min_gap ⇒ Object
Returns the value of attribute min_gap.
449 450 451 |
# File 'lib/mjai/manue/danger_estimator.rb', line 449 def min_gap @min_gap end |
#verbose ⇒ Object
Returns the value of attribute verbose.
448 449 450 |
# File 'lib/mjai/manue/danger_estimator.rb', line 448 def verbose @verbose end |
Class Method Details
.bool_array_to_bit_vector(bool_array) ⇒ Object
732 733 734 735 736 737 738 739 |
# File 'lib/mjai/manue/danger_estimator.rb', line 732 def self.bool_array_to_bit_vector(bool_array) vector = 0 bool_array.reverse_each() do |value| vector <<= 1 vector |= 1 if value end return vector end |
.feature_vector_to_str(feature_vector) ⇒ Object
741 742 743 744 |
# File 'lib/mjai/manue/danger_estimator.rb', line 741 def self.feature_vector_to_str(feature_vector) return (0...Scene.feature_names.size).select(){ |i| feature_vector[i] != 0 }. map(){ |i| Scene.feature_names[i] }.join(" ") end |
.get_feature_value(feature_vector, feature_name) ⇒ Object
746 747 748 |
# File 'lib/mjai/manue/danger_estimator.rb', line 746 def self.get_feature_value(feature_vector, feature_name) return feature_vector[Scene.feature_names.index(feature_name)] != 0 end |
Instance Method Details
#aggregate_probabilities(criteria) ⇒ Object
678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 |
# File 'lib/mjai/manue/danger_estimator.rb', line 678 def aggregate_probabilities(criteria) result = {} for criterion in criteria kyoku_probs = @kyoku_probs_map[criterion.object_id] next if !kyoku_probs result[criterion] = node = DecisionNode.new( kyoku_probs.inject(:+) / kyoku_probs.size, ConfidenceInterval.calculate(kyoku_probs, :min => 0.0, :max => 1.0), kyoku_probs.size) print("%p\n %.2f [%.2f, %.2f] (%d samples)\n\n" % [criterion, node.average_prob * 100.0, node.conf_interval[0] * 100.0, node.conf_interval[1] * 100.0, node.num_samples]) end return result end |
#calculate_probabilities(features_path, criteria) ⇒ Object
628 629 630 631 |
# File 'lib/mjai/manue/danger_estimator.rb', line 628 def calculate_probabilities(features_path, criteria) create_kyoku_probs_map(features_path, criteria) return aggregate_probabilities(criteria) end |
#calculate_single_probabilities(features_path) ⇒ Object
550 551 552 553 |
# File 'lib/mjai/manue/danger_estimator.rb', line 550 def calculate_single_probabilities(features_path) criteria = Scene.feature_names.map(){ |s| [{s => false}, {s => true}] }.flatten() calculate_probabilities(features_path, criteria) end |
#create_kyoku_probs_map(features_path, criteria) ⇒ Object
633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 |
# File 'lib/mjai/manue/danger_estimator.rb', line 633 def create_kyoku_probs_map(features_path, criteria) require "with_progress" @kyoku_probs_map = {} criterion_masks = {} for criterion in criteria positive_ary = [false] * Scene.feature_names.size negative_ary = [true] * Scene.feature_names.size for name, value in criterion index = Scene.feature_names.index(name) raise("no such feature: %p" % name) if !index if value positive_ary[index] = true else negative_ary[index] = false end end criterion_masks[criterion] = [ DangerEstimator.bool_array_to_bit_vector(positive_ary), DangerEstimator.bool_array_to_bit_vector(negative_ary), ] end open(features_path, "rb") do |f| = Marshal.load(f) if [:feature_names] != Scene.feature_names raise("feature set has been changed") end f.with_progress() do begin while true stored_kyokus = Marshal.load(f) for stored_kyoku in stored_kyokus update_metrics_for_kyoku(stored_kyoku, criterion_masks) end end rescue EOFError end end end end |
#extract_features_from_file(input_path, listener) ⇒ Object
471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 |
# File 'lib/mjai/manue/danger_estimator.rb', line 471 def extract_features_from_file(input_path, listener) begin stored_kyoku = nil reacher = nil waited = nil prereach_sutehais = nil skip = false archive = Archive.load(input_path) archive.each_action() do |action| archive.dump_action(action) if self.verbose case action.type when :start_kyoku stored_kyoku = StoredKyoku.new([]) reacher = nil skip = false when :end_kyoku next if skip raise("should not happen") if !stored_kyoku @stored_kyokus.push(stored_kyoku) stored_kyoku = nil when :reach_accepted if ["ASAPIN", "(≧▽≦)"].include?(action.actor.name) || reacher skip = true end next if skip reacher = action.actor waited = TenpaiAnalysis.new(action.actor.tehais).waited_pais prereach_sutehais = reacher.sutehais.dup() when :dahai next if skip || !reacher || action.actor.reach? scene = Scene.new({ :game => archive, :me => action.actor, :dapai => action.pai, :reacher => reacher, :prereach_sutehais => prereach_sutehais, }) stored_scene = StoredScene.new([]) #p [:candidates, action.actor, reacher, scene.candidates.join(" ")] puts("reacher: %d" % reacher.id) if self.verbose candidates = [] for pai in scene.candidates hit = waited.include?(pai) feature_vector = scene.feature_vector(pai) stored_scene.candidates.push([feature_vector, hit]) candidates.push({ :pai => pai, :hit => hit, :feature_vector => feature_vector, }) if self.verbose puts("candidate %s: hit=%d, %s" % [ pai, hit ? 1 : 0, DangerEstimator.feature_vector_to_str(feature_vector)]) end end stored_kyoku.scenes.push(stored_scene) if listener listener.on_dahai({ :game => archive, :action => action, :reacher => reacher, :candidates => candidates, }) end end end rescue Exception $stderr.puts("at #{input_path}") raise() end end |
#extract_features_from_files(input_paths, output_path, listener = nil) ⇒ Object
451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 |
# File 'lib/mjai/manue/danger_estimator.rb', line 451 def extract_features_from_files(input_paths, output_path, listener = nil) require "with_progress" $stderr.puts("%d files." % input_paths.size) open(output_path, "wb") do |f| = { :feature_names => Scene.feature_names, } Marshal.dump(, f) @stored_kyokus = [] input_paths.enum_for(:each_with_progress).each_with_index() do |path, i| if i % 100 == 0 && i > 0 Marshal.dump(@stored_kyokus, f) @stored_kyokus.clear() end extract_features_from_file(path, listener) end Marshal.dump(@stored_kyokus, f) end end |
#generate_decision_tree(features_path, base_criterion = {}, base_node = nil, root = nil) ⇒ Object
555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 |
# File 'lib/mjai/manue/danger_estimator.rb', line 555 def generate_decision_tree(features_path, base_criterion = {}, base_node = nil, root = nil) p [:generate_decision_tree, base_criterion] targets = {} criteria = [] criteria.push(base_criterion) if !base_node for name in Scene.feature_names next if base_criterion.has_key?(name) negative_criterion = base_criterion.merge({name => false}) positive_criterion = base_criterion.merge({name => true}) targets[name] = [negative_criterion, positive_criterion] criteria.push(negative_criterion, positive_criterion) end node_map = calculate_probabilities(features_path, criteria) base_node = node_map[base_criterion] if !base_node root = base_node if !root gaps = {} for name, (negative_criterion, positive_criterion) in targets negative = node_map[negative_criterion] positive = node_map[positive_criterion] next if !positive || !negative if positive.average_prob >= negative.average_prob gap = positive.conf_interval[0] - negative.conf_interval[1] else gap = negative.conf_interval[0] - positive.conf_interval[1] end p [name, gap] gaps[name] = gap if gap > @min_gap end max_name = gaps.keys.max_by(){ |s| gaps[s] } p [:max_name, max_name] if max_name (negative_criterion, positive_criterion) = targets[max_name] base_node.feature_name = max_name base_node.negative = node_map[negative_criterion] base_node.positive = node_map[positive_criterion] render_decision_tree(root, "all") generate_decision_tree(features_path, negative_criterion, base_node.negative, root) generate_decision_tree(features_path, positive_criterion, base_node.positive, root) end return base_node end |
#match?(feature_vector, positive_mask, negative_mask) ⇒ Boolean
727 728 729 730 |
# File 'lib/mjai/manue/danger_estimator.rb', line 727 def match?(feature_vector, positive_mask, negative_mask) return (feature_vector & positive_mask) == positive_mask && (feature_vector | negative_mask) == negative_mask end |
#node_to_hash(node) ⇒ Object
613 614 615 616 617 618 619 620 621 622 623 624 625 626 |
# File 'lib/mjai/manue/danger_estimator.rb', line 613 def node_to_hash(node) if node return { "average_prob" => node.average_prob, "conf_interval" => node.conf_interval, "num_samples" => node.num_samples, "feature_name" => node.feature_name, "negative" => node_to_hash(node.negative), "positive" => node_to_hash(node.positive), } else return nil end end |
#render_decision_tree(node, label, indent = 0) ⇒ Object
597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 |
# File 'lib/mjai/manue/danger_estimator.rb', line 597 def render_decision_tree(node, label, indent = 0) puts("%s%s : %.2f [%.2f, %.2f] (%d samples)" % [" " * indent, label, node.average_prob * 100.0, node.conf_interval[0] * 100.0, node.conf_interval[1] * 100.0, node.num_samples]) if node.feature_name for value, child in [[false, node.negative], [true, node.positive]]. sort_by(){ |v, c| c.average_prob } render_decision_tree(child, "%s = %p" % [node.feature_name, value], indent + 1) end end end |
#update_metrics_for_kyoku(stored_kyoku, criterion_masks) ⇒ Object
697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 |
# File 'lib/mjai/manue/danger_estimator.rb', line 697 def update_metrics_for_kyoku(stored_kyoku, criterion_masks) scene_prob_sums = Hash.new(0.0) scene_counts = Hash.new(0) for stored_scene in stored_kyoku.scenes pai_freqs = {} for feature_vector, hit in stored_scene.candidates for criterion, (positive_mask, negative_mask) in criterion_masks if match?(feature_vector, positive_mask, negative_mask) # Uses object_id as key for efficiency. pai_freqs[criterion.object_id] ||= Hash.new(0) pai_freqs[criterion.object_id][hit] += 1 end end #p [pai, hit, feature_vector] end for criterion_id, freqs in pai_freqs scene_prob = freqs[true].to_f() / (freqs[false] + freqs[true]) #p [:scene_prob, criterion, scene_prob] scene_prob_sums[criterion_id] += scene_prob scene_counts[criterion_id] += 1 end end for criterion_id, count in scene_counts kyoku_prob = scene_prob_sums[criterion_id] / count #p [:kyoku_prob, criterion, kyoku_prob] @kyoku_probs_map[criterion_id] ||= [] @kyoku_probs_map[criterion_id].push(kyoku_prob) end end |