Generative AI for Actuarial Modeling

Practical Examples

1 - Creating Additional Data

In this example, we've asked the AI (ChatGPT) to create a set amount of additional data based on a provided csv file of model points. In the screenshots below, the purple icon denotes the user input and the orange icon is what the AI has sent back.

Chatgpt example

The AI has verified what the user would like to accomplish and comes back with a question about a column in the supplied file.

Chatgpt example

After the user prompts the AI to continue and create the file, the work is done and a link to access the new file is provided by the AI.

Chatgpt example

At this point the user has asked the AI to revise the provided file with a tweak to the face amount, the AI then provides a new file to reflect those changes.

Chatgpt example

The user has asked the AI to create multiple new versions of the previously generated file, this time with some parameters around the number of rows. The AI has come back with a question to clarify one of the columns in the csv file that was provided.

Chatgpt example

Once again the AI has a bit of confusion around the "SUM_ASSURED" column and has asked the user for more clarification.

Chatgpt example

The user has prompted the AI to ignore the column that was giving it trouble and at this point the AI is able to generate the additional 3 versions of the file.

2 - Creating Actuarial Modeling Function Documentation

In this example, we used AI (ChatGPT via API) to create documentation of the functions available for modeling on the Montoux platform (functions were coded in Go). The documentation needed to include function description, required and optional inputs, type of the returned value, and examples.

To accomplish this, a Python library called “Langchain” (https://github.com/langchain-ai/langchain) was used to:

• Store knowledge of our Golang functions and actuarial modeling code
• Store knowledge of our Montoux Model Script (m2) files so it could use those as examples
• Store and use the a custom prompt (see below)
• Call the OpenAI API (GPT4)
• Let a user specify which function to document

This is an example of a custom prompt that was provided to the AI:


   (delimited by <hs></hs>) to answer the question:
   
   Explain the Answer to me in a way that actuarially trained people will understand. The purpose of the answer is to help people understand how to make functions that will be used in m2 files.
   
   I'm going to show you what output I would like using the following function called "Rank" as an example input you'll receive.
 
```
   func Rank(_value float64, _values []float64, descending bool) float64 {{
if len(_values) == 0 {{
panic("At least one value must be provided to Rank to compare against.")
}}
rank := 1.0
for i := range _values {{
if (descending && _value < _values[i]) || (!descending && _value > _values[i]) {{
rank += 1
}}
}}
return rank
   }}

```    

     Start the answer with a heading called "Description" that explains what the function does at a high level. Using the 'Rank' function as an example, here's what that would look like:    

```
   Rank determines the rank of a value within a set of values. The rank is determined based on whether the ranking is in ascending (default) or descending order. In the case of a tie the value provided is considered to be the lowest rank. The rank starts at 1.

```    

    Follow this with a section called "Parameters:" explaining what the parameters of the function are and what they mean.
   Parameters that start with '_' are positional parameters, otherwise they are named parameters.
   You can remove the '_' when explaining the parameters.
   Named parameters are optional and have a default value.
   Parameters that start with 'has' do not need to be included in this section.
   
   Using the 'Rank' function as an example, here's what that would look like:    

```
m2 Parameters:
   - value - optional, a numeric value whose rank needs to be determined.
   - values - optional, an array of numeric values against which `value` is compared to determine its rank.
   - descending - a parameter with a default, a True or False (default) flag that determines if the ranking should be performed in ascending or descending order. If the parameter is specified by user as True, the ranking is performed in descending order, otherwise in ascending order.    

```
   Follow this with a section called "Result:"  which explains the output of the function.
   Using the 'Rank' function as an example, here's what that would look like:    

```
   Result:
   A single whole number representing the rank of the requested values within the specified array

```  
   Follow this with a section called "Exceptions:"
   Exceptions lists the errors that can be thrown by the function, and which values must be set.    
   Using the 'Rank' function as an example, here's what that would look like:    

```
     Errors:
   - "At least one value must be provided to Rank to compare against." error is returned if no values
         are specified.

   ```    
    Follow this with a section called "Examples:"
   The examples must look like code found in .m2 files and results.
   Different examples should reflect the different ways in which parameters can be used. If you can, you need to suppress the has___ boolean parameters from the explanation. They're meant to be hidden and not exposed to users.

   The construct where you'll see them is something like this function signature:

   ```
   func LessThan(_values []float64, hasLimit bool, limit float64, limits []float64) []float64 {{
    ...function in here
   }}

   ```
   'limit' and 'limits' are both optional but one needs to be provided. The hasLimit boolean indicates to us at runtime if the limit was specified in the function or not. Since Go instantiates a float64 to a 0 value it's impossible to tell whether it wasn't passed or it was passed with a value of 0. The M2 interpreter therefore helps us out at runtime by setting the has parameter to true if it was there.
   
   All of this isn't meant to be exposed to users, so in the help docs we're leaving out the has parameter and simply saying one of limit or limits  needs to be provided. Also the example should therefore just be
   
   ```
   var result = LessThan(values, limit: 3)

   ```    
Another pattern you'll see is where a function has a named parameter that has an enum type. Normally denoted where a parameter is of type `types.MonthTiming` or `types.Frequency` or starting with `types.`
   
   ```
   func IRR(_series []float64, timing types.MonthTiming, frequency types.Frequency) float64 {{
       f, err := fin.IRR(_series, timing, frequency)
       if err != nil {{
           return math.NaN()
       }}
       return f
   }}

   ```
   When you see this you should include parameters as a named parameters in the example you answer with. or example you'll note how the timing and frequency parameters are specified below

```
   var irr = IRR(mos_profit_after_tax, timing: EndMonth, frequency: Monthly);

```
   Using the 'MonthlyForwardRatesFromSpotRate' function as an example, here's what that would an output would like:        

```
   Example with Annual spot rate frequency
   var spotRate = 0.05;
   var num_proj_months = 12;
   var result = MonthlyForwardRatesFromSpotRate(spotRate: spotRate, spotRateFreq: Annual, numberMonths: num_proj_months);    
    
    result = [0.004074, 0.004074, 0.004074, 0.004074, 0.004074, 0.004074, 0.004074, 0.004074, 0.004074, 0.004074, 0.004074, 0.004074]    Example with Monthly spot rate frequency
   
    var spotRate = 0.01;
   var numberMonths = 6;
   var result = MonthlyForwardRatesFromSpotRate(spotRate: spotRate, spotRateFreq: Monthly, numberMonths: num_proj_months);    

    result = [0.01, 0.01, 0.01, 0.01, 0.01, 0.01]    

    Example with Daily spot rate frequency
   var spotRate = 0.0002;
   var spotRateFreq = Daily;
   var numberMonths = 3;
   var result = MonthlyForwardRatesFromSpotRate(spotRate: spotRate, spotRateFreq: Daily, numberMonths: num_proj_months);    
    
    result = [0.006135, 0.006135, 0.006135]
   
```        
    Follow this with a section called "Notes:"
   This section contains useful information about the function including examples that would throw an error, or interesting behavior that is not obvious.

   
   In general please follow these rules when writing the answer:
       Do not use example code that looks like golang code.
       If the function also returns an error you don't need to mention that in your answer.
       Do not include error checking code.
       Don't include notes about functions that are called from within the function
       Do not use example code based on Excel formula.
       Don't refer to "Actuarially trained people".
       When talking about parameters in the function please use the following replacements when you output the answer:
   - float64 -> number
   - float -> number
   - int -> integer
   - int64 -> integer
   - symbol -> string
   ------
   <ctx>
   {context}
   </ctx>
   ------
   <hs>
   {history}
   </hs>
   ------
   {question}
   Answer:
   """

After using the Langchain Python library to accomplish the tasks mentioned above, the AI was able to produce the requested documentation. Below are examples of what the AI returned:

Function 1 - SnapToZero

```
Description:

SnapToZero function is used to snap values, within a specified tolerance, to zero. It applies this to every value in an array of numbers. The function takes two parameters: a list of numeric values and a tolerance value. Each value in the list is compared to the tolerance, and if it falls within the tolerance, it is snapped to zero.

Parameters:

- values - mandatory, an array of numeric values that are to be evaluated.
- tolerance - mandatory, the numeric value that determines the range within which numbers are snapped to zero.

Result:

An array of numeric values where values within the tolerance are snapped to zero.

Examples:

```
var values = [0.0001, 0.2, 0.0003, 0.4];
var tolerance = 0.001;
var result = SnapToZero(values, tolerance);

```
Result would be: [0, 0.2, 0, 0.4]

Notes:

The SnapToZero function can be useful in scenarios where minor deviations from zero (that could occur due to floating-point arithmetic precision, for instance) should be ignored. The tolerance parameter allows the user to define what constitutes a 'minor' deviation.

Function 2 - DistanceToMean

```
Description:

The 'DistanceToMean' function computes the distance of each value in a set of values (array) from the mean of the set. It returns an array where each element is the original value minus the mean of the original set of values.    

Parameters:

- values - optional, an array of numeric values from which the mean will be calculated and the distances of each individual value from this mean will be computed.    

Result:

An array of numeric values representing the distances of each original value from the mean of the set. The returned array has the same length as the input array 'values'.    

Errors:

No specific errors are thrown by this function.    

Examples:

```
var values = [1.0, 2.0, 3.0, 4.0, 5.0];
var result = DistanceToMean(values);    

result = [-2.0, -1.0, 0.0, 1.0, 2.0] // as the mean of the 'values' is 3.0

```    
Notes:
 
The function does not modify the original array of values. Instead, it creates a copy of the input array and then modifies this copy. Therefore the original array remains unchanged.

Function 3 - Increment

```
Description:
   
The Increment function generates an array of sequential integers. The length of the array is determined by the '_length' parameter and the starting value of the sequence is determined by the '_start' parameter. This sequence increases by an increment of 1 for each subsequent value.    

Parameters:
   
- length - an integer determining the length of the output array.
- start - an integer determining the starting value of the sequence.    

Result:

An array of integers, starting from 'start' and incrementing by 1 for 'length' number of times.    

Exceptions:
   
None. This function does not throw any exceptions.    

Examples:
   
```
   
var length = 5;
var start = 1;
var result = Increment(length: length, start: start);    

result = [1, 2, 3, 4, 5]    

var length = 3;
var start = 0;
var result = Increment(length: length, start: start);    

result = [0, 1, 2]
   

```
Notes:
   
The function does not check if the 'length' parameter is less than or equal to 0. If this is the case, it will return an empty array. Similarly, it does not check if 'start' is negative. If it is, the sequence will start from the negative value and increment up towards zero and beyond.

```

While the results that the AI produced were great, there was still a minor amount of editing that needed to be done. Below are the final results of the documentation process.

Function 1 - SnapToZero

SnapToZero

Function 2 - DistanceToMean

Chatgpt example

Function 3 - Incremenet

Chatgpt example

3 - Python to Go Conversion

In this example, we've asked the AI (ChatGPT) to convert a snippet of Python code to Go.

Chatgpt example

The user has made the request for the AI to convert the code. The AI then acknowledges the requests and asks the user to provide the code they would like translated.

Chatgpt example

The user responds with the Python code they would like converted into Go.

Chatgpt example

The AI then acknowledges the submission from the user and feeds back the corresponding Go code.

This is the code generated by the AI (code has been extracted from the screenshot for readability purposes.

Chatgpt example

The AI then explains some of it's methodology and includes a short disclaimer about how it generated some of the code.

4 - Model Comparison

In this example, we've asked the AI (ChatGPT) to compare two difference models and summarize the differences.

Chatgpt example

First, the user asks the AI to summarize the key difference between two actuarial models, the user then pastes the models into the dialog box after the request.

Chatgpt example

The AI then summarizes the key differences in the models, it also notes some of the other components that are handled similarly between the two models.

5 - VM-20 Reserves

In this example, we've asked the AI (ChatGPT) to generate some code as well as documentation for that code.

chatGPT example

The user has made the request for the AI to create python code to calculate VM-20 reserves for a term life assurance policy. The AI provides a high-level example of how that might be done.

def calculate_reserve(policy_term, interest_rate, mortality_rate, face_amount, premium):
   """
   Simplistic calculation for the reserve of a term life assurance policy.
   
   :param policy_term: The term of the policy in years
   :param interest_rate: Annual interest rate
   :param mortality_rate: Annual mortality rate
   :param face_amount: The face amount of the policy
   :param premium: Annual premium for the policy
   :return: Reserve value
   """

   
   reserve = 0
   discount_factor = 1
   
   for t in range(policy_term):
       discount_factor *= (1 + interest_rate)
       survival_probability = (1 - mortality_rate) ** t
       death_benefit = face_amount * mortality_rate * survival_probability
       premium_contribution = premium * survival_probability
       
       reserve += (death_benefit - premium_contribution) / discount_factor
       
   return reserve

# Example usage:
policy_term = 20
interest_rate = 0.03
mortality_rate = 0.005
face_amount = 100000
premium = 500

reserve = calculate_reserve(policy_term, interest_rate, mortality_rate, face_amount, premium)
print("Reserve for the term life assurance policy:", reserve)

This is the code generated by the AI (code has been extracted from the screenshot for readability purposes.

chatGPT example

After displaying the code, the AI gives a quick description of how the code functions and warns that it may be necessary to consult with a professional before using it.

generative ai example

The user has made the request for the AI to create python code to calculate VM-20 reserves for a universal life insurance policy. The AI provides a high-level example of how that might be done.

def calculate_iul_reserve(policy_years, interest_rates, mortality_rates, face_amount, premiums, charges, index_participation_rate, cap_rate):
   """
   Simplistic calculation for the reserve of an Indexed Universal Life insurance policy.
   
   :param policy_years: The number of years the policy is in force
   :param interest_rates: A list of annual interest rates for each year
   :param mortality_rates: A list of annual mortality rates for each year
   :param face_amount: The face amount of the policy
   :param premiums: A list of annual premiums for each year
   :param charges: A list of annual charges for each year
   :param index_participation_rate: The participation rate in the index
   :param cap_rate: The cap on the indexed interest rate
   :return: Reserve value
   """

   
   reserve = 0
   cash_value = 0
   discount_factor = 1
   
   for t in range(policy_years):
       survival_probability = (1 - mortality_rates[t]) ** t
       
       # Apply interest to cash value
       indexed_interest_rate = min(interest_rates[t] * index_participation_rate, cap_rate)
       cash_value *= (1 + indexed_interest_rate)
       
       # Add premiums and subtract charges
       cash_value += premiums[t] - charges[t]
       
       # Calculate death benefit
       death_benefit = (face_amount + cash_value) * mortality_rates[t] * survival_probability
       
       # Calculate the reserve for this year
       reserve += (death_benefit - premiums[t] * survival_probability) / discount_factor
       
       discount_factor *= (1 + interest_rates[t])
       
   return reserve

# Example usage
policy_years = 20
interest_rates = [0.03] * policy_years
mortality_rates = [0.005] * policy_years
face_amount = 100000
premiums = [500] * policy_years
charges = [100] * policy_years
index_participation_rate = 0.5
cap_rate = 0.04

reserve = calculate_iul_reserve(policy_years, interest_rates, mortality_rates, face_amount, premiums, charges, index_participation_rate, cap_rate)
print("Reserve for the Indexed Universal Life insurance policy:", reserve)

This is the code generated by the AI (code has been extracted from the screenshot for readability purposes.

generative ai example

The AI once again provides a code along with a warning that the code is highly simplified and recommends consulting with an actuary.

chatGPT Example

The user then asks the AI to generate some documentation that will allow the model to be coded in any language, the AI returns an outline of requirements for the model.

chatGPT Example

The user has asked the AI to create documentation for a model built in Golang that would allow the model to be coded by a developer in another language. The AI notes that there was no code provided.

chatGPT Example

The user submits the model that was originally coded in Golang for the AI to analyze.

chatGPT Example

The AI has analyzed the Golang code and returned documentation for the user that will allow the model to be coded in another language.

chatGPT Example

The user has asked the AI to run the python code. The AI has encountered some errors and outlines them for the user.

chatGPT Example

The user asks the AI to run the iul reserve model code, the AI remarks that it does not have the code to be run.

chatGPT Example

Now the user has provided the AI with the code it would like it to run.

chatGPT Example

The AI acknowledges the receipt of this code, informs the user which environment the code can be run in and provides the output of the code it received.

Reserve for the term life assurance policy: 7874.564052386748

This is the code generated by the AI (code has been extracted from the screenshot for readability purposes.

chatGPT Example

The AI then describes what the code models and gives the user that information, it once again includes a disclaimer.

chatGPT Example

The user has now asked the AI to rewrite the code and include documentation that explains what the code is doing.

def calculate_reserve(policy_term, interest_rate, mortality_rate, face_amount, premium):
   """
   Simplistic calculation for the reserve of a term life assurance policy.
   
   :param policy_term: The term of the policy in years
   :param interest_rate: Annual interest rate
   :param mortality_rate: Annual mortality rate
   :param face_amount: The face amount of the policy
   :param premium: Annual premium for the policy
   :return: Reserve value
   """

   
   reserve = 0 # Initialize the reserve value
   discount_factor = 1 # Initialize the discount factor
   
   # Loop through each year in the policy term
   for t in range(policy_term):
       discount_factor *= (1 + interest_rate) # Update the discount factor for the current year
       survival_probability = (1 - mortality_rate) ** t # Calculate the probability of survival to time t
       death_benefit = face_amount * mortality_rate * survival_probability # Calculate the death benefit for time t
       premium_contribution = premium * survival_probability # Calculate the premium contribution for time t
       
       # Update the reserve by adding the net present value of the death benefit minus premium contribution for time t
       reserve += (death_benefit - premium_contribution) / discount_factor
       
   return reserve # Return the final reserve value

# Example usage:
policy_term = 20
interest_rate = 0.03
mortality_rate = 0.005
face_amount = 100000
premium = 500

# Call the function with the given parameters and print the result
reserve = calculate_reserve(policy_term, interest_rate, mortality_rate, face_amount, premium)
print("Reserve for the term life assurance policy:", reserve)

This is the code generated by the AI (code has been extracted from the screenshot for readability purposes.

chatGPT Example

The AI has sent back the code and explained what the comments are to be used for.

6 - Generate Python Code for Model

In this example, we've asked the AI (ChatGPT) to generate Python code for a two-factor Hull-White model.

Chatgpt example

Here the AI receives the prompt and gives some information on the output it is going to give include defining variables and the equations the model is based on.

import numpy as np

class TwoFactorHullWhite:
   def __init__(self, kappa, alpha, sigma, eta, rho, r0, theta0, T, num_steps, num_paths):
       self.kappa = kappa
       self.alpha = alpha
       self.sigma = sigma
       self.eta = eta
       self.rho = rho
       self.r0 = r0
       self.theta0 = theta0
       self.T = T
       self.num_steps = num_steps
       self.num_paths = num_paths
       self.dt = self.T / self.num_steps    

def simulate(self):
       r = np.zeros((self.num_steps + 1, self.num_paths))
       theta = np.zeros((self.num_steps + 1, self.num_paths))        

        r[0] = self.r0
       theta[0] = self.theta0        

        for t in range(1, self.num_steps + 1):
           dW = np.sqrt(self.dt) * np.random.normal(size=self.num_paths)
           dV = np.sqrt(self.dt) * (self.rho * np.random.normal(size=self.num_paths) +
                                    np.sqrt(1 - self.rho**2) * np.random.normal(size=self.num_paths))            

            theta[t] = theta[t - 1] + self.alpha * (self.theta0 - theta[t - 1]) * self.dt + self.eta * dV
           r[t] = r[t - 1] + self.kappa * (theta[t - 1] - r[t - 1]) * self.dt + self.sigma * dW        

        return r, theta


# Example usage
hull_white = TwoFactorHullWhite(kappa=0.03, alpha=0.01, sigma=0.01, eta=0.01, rho=0.5, r0=0.05, theta0=0.05, T=1, num_steps=100, num_paths=1000)
r, theta = hull_white.simulate()

This is the code generated by the AI (code has been extracted from the screenshot for readability purposes.

Chatgpt example

After providing the code, the AI goes on to give more context to the model along with a disclaimer about the code that it generated.

Chatgpt example

Here the user has asked the AI to make some adjustments to the code, the AI responds explaining what changes it will make to the code to accomplish this request.

import numpy as np

class TwoFactorHullWhite:
   def __init__(self, kappa, alpha, sigma, eta, rho, r0, theta0, T, num_steps, num_paths):
       self.kappa = kappa
       self.alpha = alpha
       self.sigma = sigma
       self.eta = eta
       self.rho = rho
       self.r0 = r0
       self.theta0 = theta0
       self.T = T
       self.num_steps = num_steps
       self.num_paths = num_paths
       self.dt = self.T / self.num_steps    

    def simulate(self):
        r = np.zeros((self.num_steps + 1, self.num_paths))      

         r[0] = self.r0        

         for t in range(1, self.num_steps + 1):
           dW = np.sqrt(self.dt) * np.random.normal(size=self.num_paths)
           dV = np.sqrt(self.dt) * (self.rho * np.random.normal(size=self.num_paths) +
                                    np.sqrt(1 - self.rho**2) * np.random.normal(size=self.num_paths))            

            theta = self.theta0 + self.alpha * (self.theta0 - self.theta0) * self.dt + self.eta * dV
           r[t] = r[t - 1] + self.kappa * (theta - r[t - 1]) * self.dt + self.sigma * dW        


         return r


# Example usage
hull_white = TwoFactorHullWhite(kappa=0.03, alpha=0.01, sigma=0.01, eta=0.01, rho=0.5, r0=0.05, theta0=0.05, T=40, num_steps=40, num_paths=1000)
r = hull_white.simulate()

# Calculate spot rates
bond_price = 1
spot_rate = -np.log(bond_price) / np.arange(1, hull_white.num_steps + 1)

# Repeat spot rate for each path
spot_rate = np.repeat(spot_rate[:, np.newaxis], hull_white.num_paths, axis=1)

This is the code generated by the AI (code has been extracted from the screenshot for readability purposes.

Chatgpt example

The AI explains the additional object that was created in the code to accomplish the user's request. It again provides a disclaimer about the code that it produced.

Chatgpt example

The user has now asked the AI to add some additional code to produce the results in a csv file. The AI responds with the method it will use to accomplish this.

import csv

# Write short rates to a CSV file
with open('short_rates.csv', 'w', newline='') as csvfile:
   writer = csv.writer(csvfile)
   writer.writerow(['Time Step'] + [f'Scenario {i+1}' for i in range(hull_white.num_paths)])
   for t in range(hull_white.num_steps + 1):
       writer.writerow([t] + list(r[t]))

# Write spot rates to a CSV file
with open('spot_rates.csv', 'w', newline='') as csvfile:
   writer = csv.writer(csvfile)
   writer.writerow(['Time Step'] + [f'Scenario {i+1}' for i in range(hull_white.num_paths)])
   for t in range(1, hull_white.num_steps + 1):  # Start from 1 since spot_rate[0] is undefined
       writer.writerow([t] + list(spot_rate[t - 1]))

This is the code generated by the AI (code has been extracted from the screenshot for readability purposes.

Chatgpt example

The AI finishes by describing what the additional code will do and gives a note to add this code snippet to the code it had provided previously.