code: """ Methods for doing logistic regression."""
  
  import numpy as np
  from utils import sigmoid
  
  def logistic_predict(weights, data):
      """
      Compute the probabilities predicted by the logistic classifier.
  
      Note: N is the number of examples and
            M is the number of features per example.
  
      Inputs:
          weights:    (M+1) x 1 vector of weights, where the last element
                      corresponds to the bias (intercepts).
          data:       N x M data matrix where each row corresponds
                      to one data point.
      Outputs:
          y:          :N x 1 vector of probabilities. This is the output of the classifier.
      """
  
      ones = np.array([[1] for i in range(data.shape[0])])
      n_data = np.c_[data, ones]
      y = n_data @ weights
      return sigmoid(y)
  
  def evaluate(targets, y):
      """
      Compute evaluation metrics.
      Inputs:
          targets : N x 1 vector of targets.
          y       : N x 1 vector of probabilities.
      Outputs:
          ce           : (scalar) Cross entropy. CE(p, q) = E_p[-log q]. Here we want to compute CE(targets, y)
          frac_correct : (scalar) Fraction of inputs classified correctly.
      """
      ce = -np.sum(targets * np.log(y) + (1-targets) * np.log(1-y))/len(targets)
  
      predictions = [1 if y_i > 0.5 else 0 for y_i in y]
      correct = [1 if predictions[i] == targets[i] else 0 for i in range(len(predictions))]
  
      frac_correct = sum(correct)/len(correct)
  
      return ce, frac_correct
  
  def logistic(weights, data, targets, hyperparameters):
      """
      Calculate negative log likelihood and its derivatives with respect to weights.
      Also return the predictions.
  
      Note: N is the number of examples and
            M is the number of features per example.
  
      Inputs:
          weights:    (M+1) x 1 vector of weights, where the last element
                      corresponds to bias (intercepts).
          data:       N x M data matrix where each row corresponds
                      to one data point.
          targets:    N x 1 vector of targets class probabilities.
          hyperparameters: The hyperparameters dictionary.
  
      Outputs:
          f:       The sum of the loss over all data points. This is the objective that we want to minimize.
          df:      (M+1) x 1 vector of derivative of f w.r.t. weights.
          y:       N x 1 vector of probabilities.
      """
  
      y = logistic_predict(weights, data)
      f, frac = evaluate(targets, y)
      d = data.T @ (y - targets)
      db = np.array([sum((y - targets))])
      zero = np.array([[0]])
      df = np.r_[d, db]
  
      return f, df, y
  
  
  def logistic_pen(weights, data, targets, hyperparameters):
      """
      Calculate negative log likelihood and its derivatives with respect to weights.
      Also return the predictions.
  
      Note: N is the number of examples and
            M is the number of features per example.
  
      Inputs:
          weights:    (M+1) x 1 vector of weights, where the last element
                      corresponds to bias (intercepts).
          data:       N x M data matrix where each row corresponds
                      to one data point.
          targets:    N x 1 vector of targets class probabilities.
          hyperparameters: The hyperparameters dictionary.
  
      Outputs:
          f:             The sum of the loss over all data points. This is the objective that we want to minimize.
          df:            (M+1) x 1 vector of derivative of f w.r.t. weights.
      """
  
      lambd = hyperparameters['weight_regularization']
      f, dwb, y = logistic(weights, data, targets, hyperparameters)
  
      regularizer = hyperparameters['weight_regularization'] * weights
      regularizer[-1] = 0 # do not penalize bias
      df = dwb + regularizer
  
      return f, df, y
  
apis: numpy.array numpy.log