I'm sure it’s news to all of you out there in the tech world, but apparently AI is the wave of the future. I know... big surprise! To give you a perspective on my history, I have been in tech for 25+ years primarily focused on the financial industry with many years of experience in the Microsoft stack, but the last few years have been spent around AWS, Go, and Postgres. My purpose in this post is to try to garner some insight in machine learning, python, and conversational bots. I am new to these subjects, but I want to understand more about creating and using a model.
Included in this post I will:
Create a small set of data to train the model
Build a very simple model that can make a prediction
Use this model in a Microsoft Bot
Since data is king when it comes to AI and machine learning, and I happen to be an avid golfer, I decided to use some public data I found on the web to train a simple AI model. This model is used to make a prediction as to what club a golfer should use based upon gender, handicap, and distance to the flag.
Data:
As there can be no prediction without data, I went in search of some data I could use to try to train a simple AI model. I stripped data from a few places and combined them. Here are a couple of the sites:
Below you can see some of the data that I consolidated from these sites. This data is all about averages and if I had actual numbers from tens of thousands of golfers, we could obviously create a better set of training data. In the gender data, a 1 is male and 0 is female, and handicap = (1 is low, 2 is mid, and 3 is high). The weapon column is just the id of the club. For example, in row 1 and 3 below, the Driver is where weapon = 1. The club column is the description of the weapon id column. The model seems to learn better and throw fewer warnings when using an id column versus the words Driver or 3 wood.
To create a more detailed prediction, there are 2 obvious ways we could improve our training data.
Gather tens of thousands of rows of data for specific golfers and the distance they hit a given club.
Add more data elements that could be used to improve the prediction such as temperature, elevation, into/with the wind, fairway/rough lie, and uphill/downhill shot. Each of these will make the club usage change for any given shot.
Sample data:
Model:
Over the past few weeks I have watched several videos about models and data. I've really enjoyed Jupyter Notebooks where the instructional text and the code are mixed. I would highly recommend checking them out. I will be using a Jupyter notebook to build and test my model locally. You can setup and host a Jupyter Notebook on Azure. I may tackle that in a future blog.
Here are some excerpts from the Jupyter Notebook I created to train the model and test it. The most important bits of code are included here.
With the above code run, we have a saved and trained model that we can use where needed.
Predictions:
As we now have a small model, how should we use it? Ideally with a model like this, it would be used within the scope of some sort of golf app. Since I am not writing a complete app, I decided to try my hand at building a VERY simple Microsoft conversational bot using Python.
I followed this article from Microsoft to build my project. I selected the Python option when building the code.
Following this tutorial will get you a bot that will repeat what you entered in a response. For my purposes, when a conversation starts, I want the bot to prompt the user for 3 data points: Gender, Handicap, and Distance to Pin (yards). Below are the highlights of what I added to the project to get it to work as I expected.
In method on_message_activity, which is called when there is an action that needs handling, the code checks to see if we received values, or prompts the user for input:
async def on_message_activity(self, turn_context: TurnContext):
# Check if the message is a command to start the data collection
if turn_context.activity.value:
# If the activity has a value property, it means it's coming from an adaptive card
await self.handle_adaptive_card_submission(turn_context)
await self.prompt_user_for_data(turn_context)
else:
await self.prompt_user_for_data(turn_context)
When there is a new user, this code prompts the user for the shot data. This happens when a member enters the conversation.
async def on_members_added_activity(
self,
members_added: ChannelAccount,
turn_context: TurnContext
):
for member_added in members_added:
if member_added.id != turn_context.activity.recipient.id:
await turn_context.send_activity("What are the details for your next golf shot?")
await self.prompt_user_for_data(turn_context)
To prompt the users for the shot data, I built some JSON used to construct a card. If you want to build a card, this site is really good: https://adaptivecards.io/designer/
Card code:
async def prompt_user_for_data(self, turn_context):
# JSON structure for the user input card
card_json = '''
{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.3",
"body": [
{
"type": "Input.ChoiceSet",
"id": "genderVal",
"label": "Gender",
"choices": [
{ "title": "Male", "value": "1" },
{ "title": "Female", "value": "0" },
{ "title": "Other", "value": "other" }
]
},
{
"type": "Input.ChoiceSet",
"id": "handicapVal",
"label": "Handicap",
"choices": [
{ "title": "Low", "value": "1" },
{ "title": "Mid", "value": "2" },
{ "title": "High", "value": "3" }
]
},
{
"type": "Input.Text",
"label": "Distance",
"style": "text",
"id": "distanceVal",
"placeholder": "Enter distance to flag"
}
],
"actions": [
{
"type": "Action.Submit",
"title": "Submit",
"data": {
"id": "1234567890"
}
}
]
}
'''
# Create a Card[GU1] with the specified JSON
card_attachment = self.create_adaptive_card_attachment(card_json)
# Create a message with the Card attachment
message = MessageFactory.attachment(card_attachment)
# Send the message to the user
await turn_context.send_activity(message)
def create_adaptive_card_attachment(self, card_json):
# Create an adaptive card attachment
return {
"contentType": "application/vnd.microsoft.card.adaptive",
"content": json.loads(card_json)
}
Finally, here's the code we need to load the model, call the predict method, and then return the shot prediction.
async def handle_adaptive_card_submission(self, turn_context):
# Extract data submitted from the adaptive card
data = turn_context.activity.value
# Access individual fields
gender = data.get("genderVal", "Not specified")
handicap = data.get("handicapVal", "Not specified")
distance = data.get("distanceVal", "Not specified")
#test for not specified at some point
loaded_model = load('golf_model.joblib')
shot = [
{'gender': gender, 'handicap': handicap, 'distance': distance},
]
#Create a 2-dimensional NumPy array
predict_data_arr = np.array([[entry['gender'], entry['handicap'], entry['distance']] for entry in shot])
prediction = loaded_model.predict(predict_data_arr)
#await turn_context.send_activity(f"You said '{ turn_context.activity.text }'")
club = ""
for i,p in enumerate(prediction):
club = str(p)
clubDesc = await self.getClubDesc(club)
# Process or store the data as needed
response_message = f"For this shot:\r\n Gender: {decodeGender(gender)}\r\n Handicap: {decodeHandicap(handicap)}\r\n Distance: {distance}\r\nUse: " + clubDesc
# Send a response to the user
await turn_context.send_activity(response_message)
async def getClubDesc(self, weaponId):
tempRow = df.loc[df['weapon'] == int(weaponId)]
temp = tempRow.iloc[0]
return temp['club']
def decodeGender(case_value):
if case_value == '1':
return 'Male'
elif case_value == '0':
return 'Female'
else:
return 'Other'
def decodeHandicap(case_value):
if case_value == '1':
return 'Low'
elif case_value == '2':
return 'Mid'
else:
return 'High'
I am using the Bot Framework Emulator to test it. Here is how it looks:
Summary:
On a personal note regarding how these predictions work for me, I am at the worse (higher) end of the low handicap range or better (lower) end of the mid handicap range. The club predictions would seem to suit my game where I live at a high altitude when I use a low handicap for the prediction. If I were at a lower altitude, I would be better suited to input the mid handicap range when I need to choose a club. This is because the ball generally flies less at sea level where the air is more dense. I suppose I better get to adding some elevation data to the model!
While this example model and bot are VERY simple, they are a good primer for how to build a model and one possible way to use it. I used a bot in Python to help me learn more about this language, but this model could be wrapped into a REST API and called from anywhere using whatever setup you want.
In review, I have:
Compiled the data needed to train the model
Built a Jupyter Notebook to train and test my model
Built a bot that will use the trained model to predict the club that should be used
I hope the above ideas and code might help you a little along the AI and model learning path. It sure has helped me.
If you have any questions on this or other topics, please feel free to contact me: stan@myndcorepartners.com
About the author:
I am a solution architect with 20+ years of experience designing, coding, testing, and implementing large scale financial services applications. Over my career I've concentrated on Microsoft core development tools, but over the last few years have also enjoyed working with AWS, Go, and Postgres.
Comments