def test_oracle_maxutil_classifier_is_stable(self):
        env = lending.DelayedImpactEnv()

        agent_params = classifier_agents.ScoringAgentParams(
            feature_keys=["applicant_features"],
            group_key="group",
            default_action_fn=(lambda: 1),
            burnin=1,
            threshold_policy=threshold_policies.ThresholdPolicy.
            SINGLE_THRESHOLD,
            convert_one_hot_to_integer=True,
            cost_matrix=params.CostMatrix(fn=0,
                                          fp=-1,
                                          tp=env.initial_params.interest_rate,
                                          tn=0),
        )

        agent = oracle_lending_agent.OracleThresholdAgent(
            action_space=env.action_space,
            observation_space=env.observation_space,
            reward_fn=rewards.BinarizedScalarDeltaReward("bank_cash"),
            params=agent_params,
            env=env,
        )

        test_util.run_test_simulation(env=env, agent=agent)
        # Drop 0 threshold associated with burn-in.
        first_nonzero_threshold = None
        for thresh in agent.global_threshold_history:
            if thresh > 0:
                if first_nonzero_threshold is None:
                    first_nonzero_threshold = thresh
                self.assertAlmostEqual(first_nonzero_threshold, thresh)
        # Make sure there is at least one non-zero threshold.
        self.assertIsNotNone(first_nonzero_threshold)
Exemple #2
0
 def test_final_credit_distribution_metric_can_interact_with_lending(self):
     env = lending.DelayedImpactEnv()
     env.set_scalar_reward(rewards.NullReward())
     # Use step=-1 to get the final credit distribution.
     final_distribution = lending_metrics.CreditDistribution(env, step=-1)
     initial_distribution = lending_metrics.CreditDistribution(env, step=0)
     test_util.run_test_simulation(
         env=env, metric=[final_distribution, initial_distribution])
Exemple #3
0
    def test_cumulative_count(self):
        env = lending.DelayedImpactEnv()
        metric = lending_metrics.CumulativeLoans(env)

        env.seed(100)
        _ = env.reset()
        for _ in range(10):
            env.step(np.asarray(1))

        result = metric.measure(env)
        self.assertEqual(result.shape, (2, 10))

        # On the first step, the combined number of loans given out should be 1.
        self.assertEqual(result[:, 0].sum(), 1)

        # On the last step, the combined number of loans given out should be 10.
        self.assertEqual(result[:, -1].sum(), 10)
Exemple #4
0
    def test_no_loans_to_group_zero(self):
        env = lending.DelayedImpactEnv()
        metric = lending_metrics.CumulativeLoans(env)

        env.seed(100)
        obs = env.reset()
        for _ in range(10):
            # action is 0 for group 0 and 1 for group 1.
            action = np.argmax(obs["group"])
            obs, _, _, _ = env.step(action)

        result = metric.measure(env)
        self.assertEqual(result.shape, (2, 10))

        # Group 0 gets no loans.
        self.assertEqual(result[0, -1], 0)

        # Group 1 gets at least 1 loan.
        self.assertGreater(result[1, -1], 0)
    def test_higher_credit_scores_default_less(self):
        env = lending.DelayedImpactEnv()
        high_scores = []
        low_scores = []
        rng = np.random.RandomState()
        rng.seed(100)
        for _ in range(1000):
            applicant = env.initial_params.applicant_distribution.sample(rng)
            if np.argmax(applicant.features) > 4:
                high_scores.append(applicant)
            else:
                low_scores.append(applicant)

        self.assertNotEmpty(high_scores)
        self.assertNotEmpty(low_scores)

        self.assertLess(
            np.mean([applicant.will_default for applicant in high_scores]),
            np.mean([applicant.will_default for applicant in low_scores]),
        )
Exemple #6
0
    def test_measure_distribution_change_measurement(self):

        # The lower cluster has a 100% success rate and the upper cluster has a 0%
        # success rate. This causes applicants to move constantly between clusters.
        clusters = distributions.Mixture(
            components=[
                lending_params._credit_cluster_builder(
                    group_membership=[1, 0],
                    cluster_probs=[0.1, 0.9],
                    success_probs=[1.0, 0.0])(),
                lending_params._credit_cluster_builder(
                    group_membership=[0, 1],
                    cluster_probs=[0.8, 0.2],
                    success_probs=[1.0, 0.0])(),
            ],
            weights=(0.5, 0.5),
        )

        env = lending.DelayedImpactEnv(
            lending_params.DelayedImpactParams(
                applicant_distribution=clusters))
        initial_distribution = lending_metrics.CreditDistribution(env, 0)
        final_distribution = lending_metrics.CreditDistribution(env, -1)

        # Giving a loan should change the distribution.
        env.step(np.asarray(1))
        # Take another step to move current state into history. This step does not
        # change the distribution because the loan is rejected.
        env.step(np.asarray(0))

        self.assertEqual({
            "0": [0.1, 0.9],
            "1": [0.8, 0.2]
        }, initial_distribution.measure(env))
        self.assertNotEqual({
            "0": [0.1, 0.9],
            "1": [0.8, 0.2]
        }, final_distribution.measure(env))
Exemple #7
0
    def scenario_builder(self):
        """Returns an agent and environment pair."""
        env_params = lending_params.DelayedImpactParams(
            applicant_distribution=lending_params.two_group_credit_clusters(
                cluster_probabilities=self.cluster_probabilities,
                group_likelihoods=[self.group_0_prob, 1 - self.group_0_prob],
            ),
            bank_starting_cash=self.bank_starting_cash,
            interest_rate=self.interest_rate,
            cluster_shift_increment=self.cluster_shift_increment,
        )
        env = lending.DelayedImpactEnv(env_params)

        agent_params = classifier_agents.ScoringAgentParams(
            feature_keys=["applicant_features"],
            group_key="group",
            default_action_fn=(lambda: 1),
            burnin=self.burnin,
            convert_one_hot_to_integer=True,
            threshold_policy=self.threshold_policy,
            skip_retraining_fn=lambda action, observation: action == 0,
            cost_matrix=params.CostMatrix(fn=0,
                                          fp=-1,
                                          tp=env_params.interest_rate,
                                          tn=0),
        )

        agent = oracle_lending_agent.OracleThresholdAgent(
            action_space=env.action_space,
            reward_fn=rewards.BinarizedScalarDeltaReward(
                "bank_cash", baseline=env.initial_params.bank_starting_cash),
            observation_space=env.observation_space,
            params=agent_params,
            env=env,
        )
        agent.seed(100)
        return env, agent
    def test_oracle_lending_agent_interacts(self):
        env = lending.DelayedImpactEnv()

        agent_params = classifier_agents.ScoringAgentParams(
            feature_keys=["applicant_features"],
            group_key="group",
            default_action_fn=(lambda: 1),
            burnin=1,
            convert_one_hot_to_integer=True,
            cost_matrix=params.CostMatrix(fn=0,
                                          fp=-1,
                                          tp=env.initial_params.interest_rate,
                                          tn=0),
        )

        agent = oracle_lending_agent.OracleThresholdAgent(
            action_space=env.action_space,
            observation_space=env.observation_space,
            reward_fn=rewards.BinarizedScalarDeltaReward("bank_cash"),
            params=agent_params,
            env=env,
        )

        test_util.run_test_simulation(env=env, agent=agent)
    def test_credit_score_distributions_change(self):
        env = lending.DelayedImpactEnv()
        state = env._get_state()

        # Candidate is in group 0
        state.group = [1, 0]
        state.group_id = 0
        # Candidate will default.
        state.will_default = 1
        # Should move probability mass from clusters 3 to 2.
        state.applicant_features = [0] * 7
        state.applicant_features[3] = 1

        lending._CreditShift().update(state, lending.LoanDecision.ACCEPT)

        def get_cluster_probs(params):
            return params.applicant_distribution.components[0].weights

        self.assertLess(
            get_cluster_probs(state.params)[3],
            get_cluster_probs(env.initial_params)[3])
        self.assertGreater(
            get_cluster_probs(state.params)[2],
            get_cluster_probs(env.initial_params)[2])
 def test_delayed_impact_env_has_multinomial_observation_space(self):
     env = lending.DelayedImpactEnv()
     for _ in range(10):
         features = env.observation_space.sample()["applicant_features"]
         self.assertEqual(features.sum(), 1)
         self.assertSameElements(features, {0, 1})
 def test_render_fails_for_high_dimensional_environments(self):
     env = lending.DelayedImpactEnv()
     test_util.run_test_simulation(env=env)
     with self.assertRaises(NotImplementedError):
         env.render()