blob: 9430352933ab506946b9258c56830b74e8d02cb6 [file]
# Copyright 2018 The Chromium Authors. All rights reserved.
# Use of this source code is govered by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd
import json
import logging
import re
import settings
import cloudstorage
from googleapiclient import discovery
from googleapiclient import errors
from oauth2client.client import GoogleCredentials
from features import generate_dataset
from framework import sql
from framework import jsonfeed
from framework import framework_helpers
from services import ml_helpers
from tracker import tracker_bizobj
class ComponentPredict(jsonfeed.JsonFeed):
"""Serves suggestions for components by getting a prediction from the default
component model given the provided text.
"""
top_words = {}
def InitializeTopWords(self, trainer_name):
"""Gets list of top words from GCS to be used as features for component
prediction.
"""
credentials = GoogleCredentials.get_application_default()
storage = discovery.build('storage', 'v1', credentials=credentials)
objects = storage.objects()
request = objects.get_media(bucket=settings.component_ml_bucket,
object=trainer_name + '/topwords.txt')
response = request.execute()
top_list = response.split()
# This turns the top words list into a dictionary for faster feature
# generation.
for i in range(len(top_list)):
self.top_words[top_list[i]] = i
logging.info('Length of top words list: %s', len(self.top_words))
def Predict(self, instance, ml_engine, model_name, trainer_name):
"""High-level method that takes an input and returns the model's prediction.
Args:
instance (list): A single sample on which to make a prediction
(output from GenerateFeaturesRaw).
ml_engine: An ML Engine API instance.
model_name (str): The hosted model to call.
trainer_name (str): The name of the trainer that generated this
model. Used to fetch component ID to index mapping.
Returns:
A dict: {'components': []}. The list will either be empty or contain
one ComponentDef representing the top predicted component.
"""
components = []
best_score_index = self.GetPrediction(instance, ml_engine, model_name)
config = self.services.config.GetProjectConfig(
self.mr.cnxn, self.mr.project_id)
# Add dummy component for local testing.
if settings.dev_mode:
if len(config.component_defs):
component = config.component_defs[0]
else:
component = None
else:
component_id = self.GetComponentID(trainer_name, best_score_index)
component = tracker_bizobj.FindComponentDefByID(component_id, config)
if component:
components.append(component.path)
return {'components': components}
@framework_helpers.retry(3)
def GetPrediction(self, instance, ml_engine, model_name):
"""Gets component prediction from default model based on provided text.
Args:
instance: The dict object returned from ml_helpers.GenerateFeaturesRaw
containing the features generated from the provided text.
ml_engine: An ML Engine instance for making predictions.
model_name: The full path to the model's location.
Returns:
The index of the component with the highest score. ML engine's predict
api returns a dict of the format
{'predictions': [{'classes': ['0', '1', ...], 'scores': [.00234, ...]}]}
where each class has a score at the same index. Classes are sequential,
so the index of the highest score also happens to be the component's
index.
"""
body = {'instances': [{'inputs': instance['word_features']}]}
request = ml_engine.projects().predict(name=model_name, body=body)
response = request.execute()
logging.info('ML Engine API response: %r' % response)
prediction_dict = response['predictions'][0]
scores = prediction_dict['scores']
return scores.index(max(scores))
def GetComponentID(self, trainer_name, index):
"""Gets the actual component ID from the provided index by getting the
mapping of indexes to IDs, which is stored in the bucket where the model's
trainer is stored.
Args:
trainer_name: The name of the trainer that generated the current default
model.
index: The index of the component we want to get the ID of.
Returns:
The component ID of the provided component, determined by the index/ID
mapping.
"""
mapping_path = '/%s/%s/component_index.json' % (
settings.component_ml_bucket, trainer_name)
logging.info('Mapping path full name: %r', mapping_path)
# TODO(carapew): Memcache the index mapping file.
gcs_file = cloudstorage.open(mapping_path, mode='r')
logging.info('Index component mapping opened')
component_index = json.load(gcs_file)
component_id = component_index[str(index)]
gcs_file.close()
# Component IDs are stored in GCS as strings but represented in the app
# as longs.
if component_id:
component_id = long(component_id)
return component_id
def HandleRequest(self, mr):
text = mr.request.POST.items()[0][1]
logging.info('text: %r', text)
clean_text = generate_dataset.CleanText(text)
ml_engine = ml_helpers.setup_ml_engine()
model_name = 'projects/%s/models/%s' % (
settings.classifier_project_id, settings.component_model_name)
model_request = ml_engine.projects().models().get(name=model_name)
model_response = model_request.execute()
version_name = model_response['defaultVersion']['name']
# Gets the timestamp number from the folder containing the model's trainer
# in order to get the correct files for mappings and features.
trainer_name = 'component_trainer_' + re.search('v_(\d+)',
version_name).group(1)
# TODO(carapew): Use memcache to get top words rather than storing as a
# variable of ComponentPredict.
if self.top_words == {}:
self.InitializeTopWords(trainer_name)
instance = ml_helpers.GenerateFeaturesRaw(
[clean_text],
settings.component_features,
self.top_words
)
prediction = self.Predict(instance, ml_engine, model_name, trainer_name)
logging.info('prediction: %r', str(prediction))
return prediction