Amaia Salvador helped organize this week’s Women in Computer Vision workshop held in conjunction with ECCV 20.Read More
Amazon Scholar wins “highest honor” in knowledge discovery and data mining
Thorsten Joachims answers 3 questions about the work that earned him the award.Read More
Amazon researchers win best-paper award at KDD
Research involves making recommender systems more aware of changing trends and more scalable.Read More
Why AI may be healthcare’s prescription for transformation
Watch the KDD 2020 talk by Taha Kass-Hout, director of machine learning, AWS Health AI.Read More
ECCV: Where does computer vision go from here?
Amazon Scholar Thomas Brox sees promise in unsupervised learning, generative models, and integrating machine learning and geometry.Read More
Rewriting the past: Assessing the field through the lens of language generation
ACL 2020 keynote presentation given by Amazon Scholar and Columbia University professor Kathleen McKeown.Read More
Matthias Bethge: Amazon Scholar and self-proclaimed protopian
His research focuses on how computers can learn the same way as humans.Read More
Building a customized recommender system in Amazon SageMaker
Recommender systems help you tailor customer experiences on online platforms. Amazon Personalize is an artificial intelligence and machine learning service that specializes in developing recommender system solutions. It automatically examines the data, performs feature and algorithm selection, optimizes the model based on your data, and deploys and hosts the model for real-time recommendation inference. However, if you don’t have explicit user rating data or need to access trained models’ weights, you may need to build your recommender system from scratch. In this post, I show you how to train and deploy a customized recommender system in TensorFlow 2.0, using a Neural Collaborative Filtering (NCF) (He et al., 2017) model on Amazon SageMaker.
Understanding Neural Collaborative Filtering
A recommender system is a set of tools that helps provide users with a personalized experience by predicting user preference amongst a large number of options. Matrix factorization (MF) is a well-known approach to solving such a problem. Conventional MF solutions exploit explicit feedback in a linear fashion; explicit feedback consists of direct user preferences, such as ratings for movies on a five-star scale or binary preference on a product (like or not like). However, explicit feedback isn’t always present in datasets. NCF solves the absence of explicit feedback by only using implicit feedback, which is derived from user activity, such as clicks and views. In addition, NCF utilizes multi-layer perceptron to introduce non-linearity into the solution.
Architecture overview
An NCF model contains two intrinsic sets of network layers: embedding and NCF layers. You use these layers to build a neural matrix factorization solution with two separate network architectures, generalized matrix factorization (GMF) and multi-layer perceptron (MLP), whose outputs are then concatenated as input for the final output layer. The following diagram from the original paper illustrates this architecture.
In this post, I walk you through building and deploying the NCF solution using the following steps:
- Prepare data for model training and testing.
- Code the NCF network in TensorFlow 2.0.
- Perform model training using Script Mode and deploy the trained model using Amazon SageMaker hosting services as an endpoint.
- Make a recommendation inference via the model endpoint.
You can find the complete code sample in the GitHub repo.
Preparing the data
For this post, I use the MovieLens dataset. MovieLens is a movie rating dataset provided by GroupLens, a research lab at the University of Minnesota.
First, I run the following code to download the dataset into the ml-latest-small
directory:
%%bash
# delete the data directory if it already exists
rm -r ml-latest-small
# download movielens small dataset
curl -O http://files.grouplens.org/datasets/movielens/ml-latest-small.zip
# unzip into data directory
unzip ml-latest-small.zip
rm ml-latest-small.zip
I only use rating.csv
, which contains explicit feedback data, as a proxy dataset to demonstrate the NCF solution. To fit this solution to your data, you need to determine the meaning of a user liking an item.
To perform a training and testing split, I take the latest 10 items each user rated as the testing set and keep the rest as the training set:
def train_test_split(df, holdout_num):
""" perform training testing split
@param df: dataframe
@param holdhout_num: number of items to be held out per user as testing items
@return df_train: training data
@return df_test testing data
"""
# first sort the data by time
df = df.sort_values(['userId', 'timestamp'], ascending=[True, False])
# perform deep copy to avoid modification on the original dataframe
df_train = df.copy(deep=True)
df_test = df.copy(deep=True)
# get test set
df_test = df_test.groupby(['userId']).head(holdout_num).reset_index()
# get train set
df_train = df_train.merge(
df_test[['userId', 'movieId']].assign(remove=1),
how='left'
).query('remove != 1').drop('remove', 1).reset_index(drop=True)
# sanity check to make sure we're not duplicating/losing data
assert len(df) == len(df_train) + len(df_test)
return df_train, df_test
Because we’re performing a binary classification task and treating the positive label as if the user liked an item, we need to randomly sample the movies each user hasn’t rated and treat them as negative labels. This is called negative sampling. The following function implements the process:
def negative_sampling(user_ids, movie_ids, items, n_neg):
"""This function creates n_neg negative labels for every positive label
@param user_ids: list of user ids
@param movie_ids: list of movie ids
@param items: unique list of movie ids
@param n_neg: number of negative labels to sample
@return df_neg: negative sample dataframe
"""
neg = []
ui_pairs = zip(user_ids, movie_ids)
records = set(ui_pairs)
# for every positive label case
for (u, i) in records:
# generate n_neg negative labels
for _ in range(n_neg):
j = np.random.choice(items)
# resample if the movie already exists for that user
While (u, j) in records:
j = np.random.choice(items)
neg.append([u, j, 0])
# convert to pandas dataframe for concatenation later
df_neg = pd.DataFrame(neg, columns=['userId', 'movieId', 'rating'])
return df_neg
You can use the following code to perform the training and testing splits, negative sampling, and store the processed data in Amazon Simple Storage Service (Amazon S3):
import os
import boto3
import sagemaker
import numpy as np
import pandas as pd
# read rating data
fpath = './ml-latest-small/ratings.csv'
df = pd.read_csv(fpath)
# perform train test split
df_train, df_test = train_test_split(df, 10)
# create 5 negative samples per positive label for training set
neg_train = negative_sampling(
user_ids=df_train.userId.values,
movie_ids=df_train.movieId.values,
items=df.movieId.unique(),
n_neg=5
)
# create final training and testing sets
df_train = df_train[['userId', 'movieId']].assign(rating=1)
df_train = pd.concat([df_train, neg_train], ignore_index=True)
df_test = df_test[['userId', 'movieId']].assign(rating=1)
# save data locally first
dest = 'ml-latest-small/s3'
!mkdir {dest}
train_path = os.path.join(dest, 'train.npy')
test_path = os.path.join(dest, 'test.npy')
np.save(train_path, df_train.values)
np.save(test_path, df_test.values)
# store data in the default S3 bucket
sagemaker_session = sagemaker.Session()
bucket_name = sm_session.default_bucket()
print("the default bucket name is", bucket_name)
# upload to the default s3 bucket
sagemaker_session.upload_data(train_path, key_prefix='data')
sagemaker_session.upload_data(test_path, key_prefix='data')
I use the Amazon SageMaker session’s default bucket to store processed data. The format of the default bucket name is sagemaker-{region}–{aws-account-id}.
Coding the NCF network
In this section, I implement GMF and MLP separately. These two components’ input are both user and item embeddings. To define the embedding layers, we enter the following code:
def _get_user_embedding_layers(inputs, emb_dim):
""" create user embeddings """
user_gmf_emb = tf.keras.layers.Dense(emb_dim, activation='relu')(inputs)
user_mlp_emb = tf.keras.layers.Dense(emb_dim, activation='relu')(inputs)
return user_gmf_emb, user_mlp_emb
def _get_item_embedding_layers(inputs, emb_dim):
""" create item embeddings """
item_gmf_emb = tf.keras.layers.Dense(emb_dim, activation='relu')(inputs)
item_mlp_emb = tf.keras.layers.Dense(emb_dim, activation='relu')(inputs)
return item_gmf_emb, item_mlp_emb
To implement GMF, we multiply the user and item embeddings:
def _gmf(user_emb, item_emb):
""" general matrix factorization branch """
gmf_mat = tf.keras.layers.Multiply()([user_emb, item_emb])
return gmf_mat
The authors of Neural Collaborative Filtering show that a four-layer MLP with 64-dimensional user and item latent factor performed the best across different experiments, so we implement this structure as our MLP:
def _mlp(user_emb, item_emb, dropout_rate):
""" multi-layer perceptron branch """
def add_layer(dim, input_layer, dropout_rate):
hidden_layer = tf.keras.layers.Dense(dim, activation='relu')(input_layer)
if dropout_rate:
dropout_layer = tf.keras.layers.Dropout(dropout_rate)(hidden_layer)
return dropout_layer
return hidden_layer
concat_layer = tf.keras.layers.Concatenate()([user_emb, item_emb])
dropout_l1 = tf.keras.layers.Dropout(dropout_rate)(concat_layer)
dense_layer_1 = add_layer(64, dropout_l1, dropout_rate)
dense_layer_2 = add_layer(32, dense_layer_1, dropout_rate)
dense_layer_3 = add_layer(16, dense_layer_2, None)
dense_layer_4 = add_layer(8, dense_layer_3, None)
return dense_layer_4
Lastly, to produce the final prediction, we concatenate the output of GMF and MLP as the following:
def _neuCF(gmf, mlp, dropout_rate):
""" final output layer """
concat_layer = tf.keras.layers.Concatenate()([gmf, mlp])
output_layer = tf.keras.layers.Dense(1, activation='sigmoid')(concat_layer)
return output_layer
To build the entire solution in one step, we create a graph building function:
def build_graph(user_dim, item_dim, dropout_rate=0.25):
""" neural collaborative filtering model
@param user_dim: one hot encoded user dimension
@param item_dim: one hot encoded item dimension
@param dropout_rate: drop out rate for dropout layers
@return model: neural collaborative filtering model graph
"""
user_input = tf.keras.Input(shape=(user_dim))
item_input = tf.keras.Input(shape=(item_dim))
# create embedding layers
user_gmf_emb, user_mlp_emb = _get_user_embedding_layers(user_input, 32)
item_gmf_emb, item_mlp_emb = _get_item_embedding_layers(item_input, 32)
# general matrix factorization
gmf = _gmf(user_gmf_emb, item_gmf_emb)
# multi layer perceptron
mlp = _mlp(user_mlp_emb, item_mlp_emb, dropout_rate)
# output
output = _neuCF(gmf, mlp, dropout_rate)
# create the model
model = tf.keras.Model(inputs=[user_input, item_input], outputs=output)
return model
I use the Keras plot_model
utility to verify the network architecture I just built is correct:
# build graph
ncf_model = build_graph(n_user, n_item)
# visualize and save to a local png file
tf.keras.utils.plot_model(ncf_model, to_file="neural_collaborative_filtering_model.png")
The output architecture should look like the following diagram.
Training and deploying the model
For instructions on deploying a model you trained using an instance on Amazon SageMaker, see Deploy trained Keras or TensorFlow models using Amazon SageMaker. For this post, I deploy this model using Script Mode.
We first need to create a Python script that contains the model training code. I compiled the model architecture code presented previously and added additional code required in the ncf.py
, which you can use directly. I also implemented a function for you to load training data; to load testing data, the function is the same except the file name is changed to reflect the testing data destination. See the following code:
def _load_training_data(base_dir):
""" load training data """
df_train = np.load(os.path.join(base_dir, 'train.npy'))
user_train, item_train, y_train = np.split(np.transpose(df_train).flatten(), 3)
return user_train, item_train, y_train
After downloading the training script and storing it in the same directory as the model training notebook, we can initialize a TensorFlow estimator with the following code:
ncf_estimator = TensorFlow(
entry_point='ncf.py',
role=role,
train_instance_count=1,
train_instance_type='ml.c5.2xlarge',
framework_version='2.1.0',
py_version='py3',
distributions={'parameter_server': {'enabled': True}},
hyperparameters={'epochs': 3, 'batch_size': 256, 'n_user': n_user, 'n_item': n_item}
)
We fit the estimator to our training data to start the training job:
# specify the location of the training data
training_data_uri = os.path.join(f's3://{bucket_name}', 'data')
# kick off the training job
ncf_estimator.fit(training_data_uri)
When you see the output in the following screenshot, your model training job has started.
When the model training process is complete, we can deploy the model as an endpoint. See the following code:
predictor = ncf_estimator.deploy(initial_instance_count=1,
instance_type='ml.c5.xlarge',
endpoint_type='tensorflow-serving')
Performing model inference
To make inference using the endpoint on the testing set, we can invoke the model in the same way by using TensorFlow Serving:
# Define a function to read testing data
def _load_testing_data(base_dir):
""" load testing data """
df_test = np.load(os.path.join(base_dir, 'test.npy'))
user_test, item_test, y_test = np.split(np.transpose(df_test).flatten(), 3)
return user_test, item_test, y_test
# read testing data from local
user_test, item_test, test_labels = _load_testing_data('./ml-latest-small/s3/')
# one-hot encode the testing data for model input
with tf.Session() as tf_sess:
test_user_data = tf_sess.run(tf.one_hot(user_test, depth=n_user)).tolist()
test_item_data = tf_sess.run(tf.one_hot(item_test, depth=n_item)).tolist()
# make batch prediction
batch_size = 100
y_pred = []
for idx in range(0, len(test_user_data), batch_size):
# reformat test samples into tensorflow serving acceptable format
input_vals = {
"instances": [
{'input_1': u, 'input_2': i}
for (u, i) in zip(test_user_data[idx:idx+batch_size], test_item_data[idx:idx+batch_size])
]}
# invoke model endpoint to make inference
pred = predictor.predict(input_vals)
# store predictions
y_pred.extend([i[0] for i in pred['predictions']])
The model output is a set of probabilities, ranging from 0 to 1, for each user-item pair that we specify for inference. To make final binary predictions, such as like or not like, we need to apply a threshold. For demonstration purposes, I use 0.5 as a threshold; if the predicted probability is equal or greater than 0.5, we say the model predicts the user will like the item, and vice versa.
# combine user id, movie id, and prediction in one dataframe
pred_df = pd.DataFrame([
user_test,
item_test,
(np.array(y_pred) >= 0.5).astype(int)],
).T
# assign column names to the dataframe
pred_df.columns = ['userId', 'movieId', 'prediction']
Finally, we can get a list of model predictions on whether a user will like a movie, as shown in the following screenshot.
Conclusion
Designing a recommender system can be a challenging task that sometimes requires model customization. In this post, I showed you how to implement, deploy, and invoke an NCF model from scratch in Amazon SageMaker. This work can serve as a foundation for you to start building more customized solutions with your own datasets.
For more information about using built-in Amazon SageMaker algorithms and Amazon Personalize to build recommender system solutions, see the following:
- Omnichannel personalization with Amazon Personalize
- Creating a recommendation engine using Amazon Personalize
- Extending Amazon SageMaker factorization machines algorithms to predict top x recommendations
- Build a movie recommender with factorization machines on Amazon SageMaker
To further customize the Neural Collaborative Filtering network, Deep Matrix Factorization (Xue et al., 2017) model can be an option. For more information, see Build a Recommendation Engine on AWS Today.
About the Author
Taihua (Ray) Li is a data scientist with AWS Professional Services. He holds a M.S. in Predictive Analytics degree from DePaul University and has several years of experience building artificial intelligence powered applications for non-profit and enterprise organizations. At AWS, Ray helps customers to unlock business potentials and to drive actionable outcomes with machine learning. Outside of work, he enjoys fitness classes, biking, and traveling.
Alexa & Friends features Daniel Marcu, Director at Alexa AI
Watch the recording of Marcu’s live interview with Alexa evangelist Jeff Blankenburg.Read More
Relevance tuning with Amazon Kendra
Amazon Kendra is a highly accurate and easy-to-use enterprise search service powered by machine learning (ML). As your users begin to perform searches using Kendra, you can fine-tune which search results they receive. For example, you might want to prioritize results from certain data sources that are more actively curated and therefore more authoritative. Or if your users frequently search for documents like quarterly reports, you may want to display the more recent quarterly reports first.
Relevance tuning allows you to change how Amazon Kendra processes the importance of certain fields or attributes in search results. In this post, we walk through how you can manually tune your index to achieve the best results.
It’s important to understand the three main response types of Amazon Kendra: matching to FAQs, reading comprehension to extract suggested answers, and document ranking. Relevance tuning impacts document ranking. Additionally, relevance tuning is just one of many factors that impact search results for your users. You can’t change specific results, but you can influence how much weight Amazon Kendra applies to certain fields or attributes.
Faceting
Because you’re tuning based on fields, you need to have those fields faceted in your index. For example, if you want to boost the signal of the author
field, you need to make the author
field a searchable facet in your index. For more information about adding facetable fields to your index, see Creating custom document attributes.
Performing relevance tuning
You can perform relevance tuning in several different ways, such as on the AWS Management Console through the Amazon Kendra search console or with the Amazon Kendra API. You can also use several different types of fields when tuning:
- Date fields – Boost more recent results
- Number fields – Amplify content based on number fields, such as total view counts
- String fields – Elevate results based on string fields, for example those that are tagged as coming from a more authoritative data source
Prerequisites
This post requires you to complete the following prerequisites: set up your environment, upload the example dataset, and create an index.
Setting up your environment
Ensure you have the AWS CLI installed. Open a terminal window and create a new working directory. From that directory, download the following files:
- The sample dataset, available from:
s3://aws-ml-blog/artifacts/kendra-relevance-tuning/ml-blogs.tar.gz
- The Python script to create your index, available from:
s3://aws-ml-blog/artifacts/kendra-relevance-tuning/create-index.py
The following screenshot shows how to download the dataset and the Python script.
Uploading the dataset
For this use case, we use a dataset that is a selection of posts from the AWS Machine Learning Blog. If you want to use your own dataset, make sure you have a variety of metadata. You should ideally have varying string fields and date fields. In the example dataset, the different fields include:
- Author name – Author of the post
- Content type – Blog posts and whitepapers
- Topic and subtopic – The main topic is
Machine Learning
and subtopics includeComputer Vision
andML at the Edge
- Content language – English, Japanese, and French
- Number of citations in scientific journals – These are randomly fabricated numbers for this post
To get started, create two Amazon Simple Storage Service (Amazon S3) buckets. Make sure to create them in the same Region as your index. Our index has two data sources.
Within the ml-blogs.tar.gz tarball there are two directories. Extract the tarball and sync the contents of the first directory, ‘bucket1’ to your first S3 bucket. Then sync the contents of the second directory, ‘bucket2’, to your second S3 bucket.
The following screenshot shows how to download the dataset and upload it to your S3 buckets.
Creating the index
Using your preferred code editor, open the Python script ‘create-index.py’ that you downloaded previously. You will need to set your bucket name variables to the names of the Amazon S3 buckets you created earlier. Make sure you uncomment those lines.
Once this is done, run the script by typing python create-index.py
. This does the following:
- Creates an AWS Identity and Access Management (IAM) role to allow your Amazon Kendra index to read data from Amazon S3 and write logs to Amazon CloudWatch Logs
- Creates an Amazon Kendra index
- Adds two Amazon S3 data sources to your index
- Adds new facets to your index, which allows you to search based on the different fields in the dataset
- Initiates a data source sync job
Working with relevance tuning
Now that our data is properly indexed and our metadata is facetable, we can test different settings to understand how relevance tuning affects search results. In the following examples, we will boost based on several different attributes. These include the data source, document type, freshness, and popularity.
Boosting your authoritative data sources
The first kind of tuning we look at is based on data sources. Perhaps you have one data source that is well maintained and curated, and another with information that is less accurate and dated. You want to prioritize the results from the first data source so your users get the most relevant results when they perform searches.
When we created our index, we created two data sources. One contains all our blog posts—this is our primary data source. The other contains only a single file, which we’re treating as our legacy data source.
Our index creation script set the field _data_source_id
to be facetable, searchable, and displayable. This is an essential step in boosting particular data sources.
The following screenshot shows the index fields of our Amazon Kendra index.
- On the Amazon Kendra search console, search for
Textract
.
Your results should reference posts about Amazon Textract, a service that can automatically extract text and data from scanned documents.
The following screenshot shows the results of a search for ‘Textract’.
Also in the results should be a file called Test_File.txt
. This is a file from our secondary, less well-curated data source. Make a note of where this result appears in your search results. We want to de-prioritize this result and boost the results from our primary source.
- Choose Tuning to open the Relevance tuning
- Under Text fields, expand data source.
- Drag the slider for your first data source to the right to boost the results from this source. For this post, we start by setting it to 8.
- Perform another search for
Textract
.
You should find that the file from the second data source has moved down the search rankings.
- Drag the slider all the way to the right, so that the boost is set to 10, and perform the search again.
You should find that the result from the secondary data source has disappeared from the first page of search results.
The following screenshot shows the relevance tuning panel with data source field boost applied to one data source, and the search results excluding the results from our secondary data source.
Although we used this approach with S3 buckets as our data sources, you can use it to prioritize any data source available in Amazon Kendra. You can boost the results from your Amazon S3 data lake and de-prioritize the results from your Microsoft SharePoint system, or vice-versa.
Boosting certain document types
In this use case, we boost the results of our whitepapers over the results from the AWS Machine Learning Blog. We first establish a baseline search result.
- Open the Amazon Kendra search console and search for
What is machine learning?
Although the top result is a suggested answer from a whitepaper, the next results are likely from blog posts.
The following screenshot shows the results of a search for ‘What is machine learning?’
How do we influence Amazon Kendra to push whitepapers towards the top of its search results?
First, we want to tune the search results based on the content Type
field.
- Open the Relevance tuning panel on the Amazon Kendra console.
- Under Custom fields, expand Type.
- Drag the Type field boost slider all the way to the right to set the relevancy of this field to 10.
We also want to boost the importance of a particular Type
value, namely Whitepapers
.
- Expand Advanced boosting and choose Add value.
- Whitepapers are indicated in our metadata by the field
“Type”: “Whitepaper”
, so enter a value ofWhitepaper
and set the value to 10. - Choose Save.
The following screenshot shows the relevance tuning panel with type field boost applied to the ‘Whitepaper’ document type.
Wait for up to 10 seconds before you rerun your search. The top results should all be whitepapers, and blog post results should appear further down the list.
The following screenshot shows the results of a search for ‘What is machine learning?’ with type field boost applied.
- Return your Type field boost settings back to their normal values.
Boosting based on document freshness
You might have a large archive of documents spanning multiple decades, but the more recent answers are more useful. For example, if your users ask, “Where is the IT helpdesk?” you want to make sure they’re given the most up-to-date answer. To achieve this, you can give a freshness boost based on date attributes.
In this use case, we boost the search results to include more recent posts.
- On the Amazon Kendra search console, search for
medical
.
The first result is De-identify medical images with the help of Amazon Comprehend Medical and Amazon Rekognition, published March 19, 2019.
The following screenshot shows the results of a search for ‘medical’.
- Open the Relevance tuning panel again.
- On the Date tab, open Custom fields.
- Adjust the Freshness boost of PublishDate to 10.
- Search again for
medical
.
This time the first result is Enhancing speech-to-text accuracy of COVID-19-related terms with Amazon Transcribe Medical, published May 15, 2020.
The following screenshot shows the results of a search for ‘medical’ with freshness boost applied.
You can also expand Advanced boosting to boost results from a particular period of time. For example, if you release quarterly business results, you might want to set the sensitivity range to the last 3 months. This boosts documents released in the last quarter so users are more likely to find them.
The following screenshot shows the section of the relevance tuning panel related to freshness boost, showing the Sensitivity slider to capture range of sensitivity.
Boosting based on document popularity
The final scenario is tuning based on numerical values. In this use case, we assigned a random number to each post to represent the number of citations they received in scientific journals. (It’s important to reiterate that these are just random numbers, not actual citation numbers!) We want the most frequently cited posts to surface.
- Run a search for
keras
, which is the name of a popular library for ML.
You might see a suggested answer from Amazon Kendra, but the top results (and their synthetic citation numbers) are likely to include:
- Amazon SageMaker Keras – 81 citations
- Deploy trained Keras or TensorFlow models using Amazon SageMaker – 57 citations
- Train Deep Learning Models on GPUs using Amazon EC2 Spot Instances – 68 citations
- On the Relevance tuning panel, on the Numeric tab, pull the slider for Citations all the way to 10.
- Select Ascending to boost the results that have more citations.
The following screenshot shows the relevance tuning panel with numeric boost applied to the Citations custom field.
- Search for
keras
again and see which results appear.
At the top of the search results are:
- Train and deploy Keras models with TensorFlow and Apache MXNet on Amazon SageMaker – 1,197 citations
- Using TensorFlow eager execution with Amazon SageMaker script mode – 2,434 citations
Amazon Kendra prioritized the results with more citations.
Conclusion
This post demonstrated how to use relevance tuning to adjust your users’ Amazon Kendra search results. We used a small and somewhat synthetic dataset to give you an idea of how relevance tuning works. Real datasets have a lot more complexity, so it’s important to work with your users to understand which types of search results they want prioritized. With relevance tuning, you can get the most value out of enterprise search with Amazon Kendra! For more information about Amazon Kendra, see AWS re:Invent 2019 – Keynote with Andy Jassy on YouTube, Amazon Kendra FAQs, and What is Amazon Kendra?
Thanks to Tapodipta Ghosh for providing the sample dataset and technical review. This post couldn’t have been written without his assistance.
About the Author
James Kingsmill is a Solution Architect in the Australian Public Sector team. He has a longstanding interest in helping public sector customers achieve their transformation, automation, and security goals. In his spare time, you can find him canyoning in the Blue Mountains near Sydney.