U
    Uc                     @   s  d Z ddlmZ ddlmZ ddlZddlZddlZddlZddlm	Z	 ddl
ZddlZddlmZ ddlmZmZmZ ddlmZ dd	lmZ dd
lmZ ddlmZ ddlmZ ddlmZ ddlmZ ddlmZ G dd de Z!G dd de Z"G dd de Z#G dd de Z$dd Z%dd Z&dd Z'dd Z(d d! Z)d"d# Z*d$d% Z+d&d' Z,d(d) Z-d*d+ Z.d,d- Z/d.d/ Z0d0d1 Z1d2d3 Z2d4d5 Z3d6d7 Z4d8d9 Z5d:d; Z6d<d= Z7dS )>z%Shared utils among inference plugins.    )division)print_functionN)logging)json_format)binary_typestring_typesinteger_types)	iteritems)zip)	signature)common_utils)platform_utils)classification_pb2)inference_pb2)regression_pb2c                   @   s   e Zd ZdZdd ZdS )	VizParamsa  Light-weight class for holding UI state.

  Attributes:
    x_min: The minimum value to use to generate mutants for the feature
      (as specified the user on the UI).
    x_max: The maximum value to use to generate mutants for the feature
      (as specified the user on the UI).
    examples: A list of examples to scan in order to generate statistics for
      mutants.
    num_mutants: Int number of mutants to generate per chart.
    feature_index_pattern: String that specifies a restricted set of indices
      of the feature to generate mutants for (useful for features that is a
      long repeated field. See `convert_pattern_to_indices` for more details.
  c           
   
   C   s|   dd }dd }dd }||| _ ||| _|| _||| _g | _|rxz||| _W n" tk
rv }	 zW 5 d}	~	X Y nX dS )zDInits VizParams may raise InvalidUserInputError for bad user inputs.c              	   S   s*   z
t | W S  ttfk
r$   Y d S X d S N)float
ValueError	TypeErrorx r   Q/tmp/pip-unpacked-wheel-15h0ro02/tensorboard_plugin_wit/_utils/inference_utils.pyto_float_or_none<   s    
z,VizParams.__init__.<locals>.to_float_or_nonec              
   S   s@   z
t | W S  ttfk
r: } zt|W 5 d }~X Y nX d S r   )intr   r   r   InvalidUserInputError)r   er   r   r   to_intB   s    
z"VizParams.__init__.<locals>.to_intc                 S   st   dd |  dD }g }|D ]N}d|krXdd | ddD \}}|t||d  q|t|  qt|S )ac  Converts a printer-page-style pattern and returns a list of indices.

      Args:
        pattern: A printer-page-style pattern with only numeric characters,
          commas, dashes, and optionally spaces.

      For example, a pattern of '0,2,4-6' would yield [0, 2, 4, 5, 6].

      Returns:
        A list of indices represented by the pattern.
      c                 S   s   g | ]}|  qS r   )strip).0tokenr   r   r   
<listcomp>T   s     zJVizParams.__init__.<locals>.convert_pattern_to_indices.<locals>.<listcomp>,-c                 S   s   g | ]}t | qS r   )r   r   )r    r   r   r   r   r"   X   s        )splitextendrangeappendr   r   sorted)patternpiecesindicesZpiecelowerupperr   r   r   convert_pattern_to_indicesH   s    z6VizParams.__init__.<locals>.convert_pattern_to_indicesN)x_minx_maxexamplesnum_mutantsfeature_indicesr   )
selfr1   r2   r3   r4   Zfeature_index_patternr   r   r0   r   r   r   r   __init__8   s    



zVizParams.__init__N__name__
__module____qualname____doc__r7   r   r   r   r   r   (   s   r   c                   @   s   e Zd ZdZdd ZdS )OriginalFeatureLista  Light-weight class for holding the original values in the example.

  Should not be created by hand, but rather generated via
  `parse_original_feature_from_example`. Just used to hold inferred info
  about the example.

  Attributes:
    feature_name: String name of the feature.
    original_value: The value of the feature in the original example.
    feature_type: One of ['int64_list', 'float_list'].

  Raises:
    ValueError: If OriginalFeatureList fails init validation.
  c                 C   s4   || _ dd |D | _|| _tdd |D | _dS )zInits OriginalFeatureList.c                 S   s   g | ]}t |qS r   )ensure_not_binaryr    valuer   r   r   r"      s    z0OriginalFeatureList.__init__.<locals>.<listcomp>c                 s   s   | ]
}d V  qdS )r%   Nr   )r    _r   r   r   	<genexpr>   s     z/OriginalFeatureList.__init__.<locals>.<genexpr>N)feature_nameoriginal_valuefeature_typesumlength)r6   rC   rD   rE   r   r   r   r7   ~   s    zOriginalFeatureList.__init__Nr8   r   r   r   r   r=   n   s   r=   c                   @   s   e Zd ZdZdd ZdS )MutantFeatureValuea  Light-weight class for holding mutated values in the example.

  Should not be created by hand but rather generated via `make_mutant_features`.

  Used to represent a "mutant example": an example that is mostly identical to
  the user-provided original example, but has one feature that is different.

  Attributes:
    original_feature: An `OriginalFeatureList` object representing the feature
      to create mutants for.
    index: The index of the feature to create mutants for. The feature can be
      a repeated field, and we want to plot mutations of its various indices.
    mutant_value: The proposed mutant value for the given index.

  Raises:
    ValueError: If MutantFeatureValue fails init validation.
  c                 C   sh   t |tstdt||| _|dk	rFt |tsFtdt||| _t |tr^|	 n|| _
dS )zInits MutantFeatureValue.zMoriginal_feature should be `OriginalFeatureList`, but had unexpected type: {}Nz8index should be None or int, but had unexpected type: {})
isinstancer=   r   formattypeoriginal_featurer   indexr   encodemutant_value)r6   rL   rM   rO   r   r   r   r7      s$    

zMutantFeatureValue.__init__Nr8   r   r   r   r   rH      s   rH   c                   @   s   e Zd ZdZdddZdS )ServingBundlea<  Light-weight class for holding info to make the inference request.

  Attributes:
    inference_address: An address (such as "hostname:port") to send inference
      requests to.
    model_name: The Servo model name.
    model_type: One of ['classification', 'regression'].
    model_version: The version number of the model as a string. If set to an
      empty string, the latest model will be used.
    signature: The signature of the model to infer. If set to an empty string,
      the default signuature will be used.
    use_predict: If true then use the servo Predict API as opposed to
      Classification or Regression.
    predict_input_tensor: The name of the input tensor to parse when using the
      Predict API.
    predict_output_tensor: The name of the output tensor to parse when using the
      Predict API.
    estimator: An estimator to use instead of calling an external model.
    feature_spec: A feature spec for use with the estimator.
    custom_predict_fn: A custom prediction function.

  Raises:
    ValueError: If ServingBundle fails init validation.
  Nc                 C   s   t |tstdt||dddd| _t |tsNtdt||| _|dkrjtd||| _|r|t	|nd| _
|r|nd| _|| _|| _|| _|	| _|
| _|| _dS )	zInits ServingBundle.z&Invalid inference_address has type: {}zhttp:// zhttps://zInvalid model_name has type: {})classification
regressionzInvalid model_type: {}N)rI   r   r   rJ   rK   replaceinference_address
model_name
model_typer   model_versionr   use_predictpredict_input_tensorpredict_output_tensor	estimatorfeature_speccustom_predict_fn)r6   rU   rV   rW   rX   r   rY   rZ   r[   r\   r]   r^   r   r   r   r7      s0    
 
zServingBundle.__init__)NNNr8   r   r   r   r   rP      s
       rP   c                 C   s6   zt | tr|  n| W S  tk
r0   |  Y S X dS )z#Return non-binary version of value.N)rI   r   decodeUnicodeDecodeError)r@   r   r   r   r>      s    r>   c                 C   sN   t | | }|dkr"td||d}|dkrBtd|t||jS )zCGet the value of a feature from Example regardless of feature type.Nz#Feature {} is not on example proto.kindz1Feature {} on example proto has no declared type.)get_example_featuresr   rJ   
WhichOneofgetattrr@   )examplerC   featurerE   r   r   r   proto_value_for_feature   s    
rg   c                 C   s,   t | | }|d}t| |}t|||S )zReturns an `OriginalFeatureList` for the specified feature_name.

  Args:
    example: An example.
    feature_name: A string feature name.

  Returns:
    A filled in `OriginalFeatureList` object representing the feature.
  ra   )rb   rc   rg   r=   )re   rC   rf   rE   rD   r   r   r   #parse_original_feature_from_example   s    


rh   c                 C   sB   t  }t| tjr$|j| j nt| tj	r>|j
| j |S )zReturns packaged inference results from the provided proto.

  Args:
    inference_result_proto: The classification or regression response proto.

  Returns:
    An InferenceResult proto with the result from the response.
  )r   ZInferenceResultrI   r   ClassificationResponseZclassification_resultZCopyFromresultr   RegressionResponseZregression_result)inference_result_protoZinference_protor   r   r   wrap_inference_results  s    	rm   c                    s$   dt |  t fdd D S )zReturns a list of feature names for float and int64 type features.

  Args:
    example: An example.

  Returns:
    A list of strings of the names of numeric features.
  )
float_list
int64_listc                    s"   g | ]} |  d kr|qS )ra   rc   r    rC   featuresZnumeric_featuresr   r   r"   -  s   z-get_numeric_feature_names.<locals>.<listcomp>rb   r*   re   r   rr   r   get_numeric_feature_names"  s
    	rv   c                    s   t |  t fdd D S )zReturns a list of feature names for byte type features.

  Args:
    example: An example.

  Returns:
    A list of categorical feature names (e.g. ['education', 'marital_status'] )
  c                    s"   g | ]} |  d dkr|qS )ra   
bytes_listrp   rq   rs   r   r   r"   =  s   z1get_categorical_feature_names.<locals>.<listcomp>rt   ru   r   rx   r   get_categorical_feature_names3  s    	ry   c                 C   sN   t t}| D ],}t|D ]}t||}|| |j qqdd t|D S )zReturns numerical features and their observed ranges.

  Args:
    examples: Examples to read to get ranges.

  Returns:
    A dict mapping feature_name -> {'observedMin': 'observedMax': } dicts,
    with a key for each numerical feature.
  c                 S   s$   i | ]\}}|t |t|d qS ))ZobservedMinZobservedMax)minmax)r    rC   feature_valuesr   r   r   
<dictcomp>S  s
   z:get_numeric_features_to_observed_range.<locals>.<dictcomp>)collectionsdefaultdictlistrv   rh   r'   rD   r	   )r3   observed_featuresre   rC   rL   r   r   r   &get_numeric_features_to_observed_rangeC  s    

 r   c           	      C   s   t t}| D ],}t|D ]}t||}|| |j qqi }tt|D ]2\}}dd t 	|
|D }|rLd|i||< qL|S )a  Returns categorical features and a sampling of their most-common values.

  The results of this slow function are used by the visualization repeatedly,
  so the results are cached.

  Args:
    examples: Examples to read to get feature samples.
    top_k: Max number of samples to return per feature.

  Returns:
    A dict of feature_name -> {'samples': ['Married-civ-spouse',
      'Never-married', 'Divorced']}.

    There is one key for each categorical feature.

    Currently, the inner dict just has one key, but this structure leaves room
    for further expansion, and mirrors the structure used by
    `get_numeric_features_to_observed_range`.
  c                 S   s   g | ]\}}|d kr|qS )r%   r   )r    wordcountr   r   r   r"   y  s   z8get_categorical_features_to_sampling.<locals>.<listcomp>samples)r~   r   r   ry   rh   r'   rD   r*   r	   Countermost_common)	r3   Ztop_kr   re   rC   rL   rj   r|   r   r   r   r   $get_categorical_features_to_sampling\  s$    
 
r   c           	         s   |j }|j}|j}|j}jdkr@ fddt|||D S jdkrtt|t||t	 }t
t|} fdd|D S jdkrt||}|j d }fdd|D S td	j d
S )zEReturn a list of `MutantFeatureValue`s that are variants of original.rn   c                    s   g | ]}t  |qS r   rH   r?   index_to_mutaterL   r   r   r"     s   z(make_mutant_features.<locals>.<listcomp>ro   c                    s   g | ]}t  |qS r   r   r?   r   r   r   r"     s   rw   r   c                    s   g | ]}t  d |qS r   r   r?   )rL   r   r   r"     s   z(Malformed original feature had type of: N)r1   r2   r3   r4   rE   npZlinspacer   Zastypetolistr*   setr   rC   r   )	rL   r   
viz_paramsr.   r/   r3   r4   mutant_valuesZfeature_to_samplesr   r   r   make_mutant_features  s<    


 
r   c                 C   s   t |||}g }| D ]}|D ]}t|}|jj}	zNt||	}
|dkrN|j}nt|
}|j||< |
dd= |
| |	| W q t
tfk
r   |	| Y qX qq||fS )a  Return a list of `MutantFeatureValue`s and a list of mutant Examples.

  Args:
    example_protos: The examples to mutate.
    original_feature: A `OriginalFeatureList` that encapsulates the feature to
      mutate.
    index_to_mutate: The index of the int64_list or float_list to mutate.
    viz_params: A `VizParams` object that contains the UI state of the request.

  Returns:
    A list of `MutantFeatureValue`s and a list of mutant examples.
  N)r   copydeepcopyrL   rC   rg   rO   r   r'   r)   r   
IndexError)example_protosrL   r   r   mutant_featuresmutant_examplesZexample_protomutant_featureZcopied_examplerC   Zfeature_listZ
new_valuesr   r   r   make_mutant_tuples  s(    




r   c              
      s   fdd zt d |W n0 tk
rT } zdg d W Y S d}~X Y nX jpdtj}jdkrtdnd}z| fd	d
|D dW S  tk
r } zt|W 5 d}~X Y nX dS )a   Returns JSON formatted for rendering all charts for a feature.

  Args:
    example_proto: The example protos to mutate.
    feature_name: The string feature name to mutate.
    serving_bundles: One `ServingBundle` object per model, that contains the
      information to make the serving request.
    viz_params: A `VizParams` object that contains the UI state of the request.

  Raises:
    InvalidUserInputError if `viz_params.feature_index_pattern` requests out of
    range indices for `feature_name` within `example_proto`.

  Returns:
    A JSON-able dict for rendering a single mutant chart.  parsed in
    `tf-inference-dashboard.html`.
    {
      'chartType': 'numeric', # oneof('numeric', 'categorical')
      'data': [A list of data] # parseable by vz-line-chart or vz-bar-chart
    }
  c                    sD   t  | \}}g }D ]$}t||\}}|t|||  q|S r   )r   run_inferencer)   $make_json_formatted_for_single_chart)r   r   r   chartsserving_bundlerl   rA   )r   rL   serving_bundlesr   r   r   chart_for_index  s$          z2mutant_charts_for_feature.<locals>.chart_for_indexr   Zcategorical)	chartTypedataNrw   numericc                    s   g | ]} |qS r   r   )r    r   )r   r   r   r"     s   z-mutant_charts_for_feature.<locals>.<listcomp>)	rh   r   r5   r(   rG   rE   r   r   r   )r   rC   r   r   r   Zindices_to_mutateZ
chart_typer   )r   r   rL   r   r   r   mutant_charts_for_feature  s0     
r   c                    s2  d d}t |tjrPi }t|jjD ]\}}| |t|   }t|jD ]\}}	|	jdkrdt	||	_t|jdkr~|	jdkr~qH|	j}
|r|
d| 7 }
|
|kri ||
< t
|j}|||
 krg ||
 |< ||
 | |	j qHq&tt}t|D ]Z\}
}t|D ]0\}}||
  ||t|tt| i q ||
 j fddd	 q|S t |tjr&i }t|jjD ]F\}}| |t|   }t
|j}||krg ||< || |j qnd
}
|dkr|
d| 7 }
g }t|D ],\}}| ||t|tt| i q|j fddd	 |
|iS tddS )a  Returns JSON formatted for a single mutant chart.

  Args:
    mutant_features: An iterable of `MutantFeatureValue`s representing the
      X-axis.
    inference_result_proto: A ClassificationResponse or RegressionResponse
      returned by Servo, representing the Y-axis.
      It contains one 'classification' or 'regression' for every Example that
      was sent for inference. The length of that field should be the same length
      of mutant_features.
    index_to_mutate: The index of the feature being mutated for this chart.

  Returns:
    A JSON-able dict for rendering a single mutant chart, parseable by
    `vz-line-chart` or `vz-bar-chart`.
  stepscalarrQ      0z (index %d)c                    s   |   S r   r   pZx_labelr   r   <lambda>Q      z6make_json_formatted_for_single_chart.<locals>.<lambda>)keyr@   r   c                    s   |   S r   r   r   r   r   r   r   k  r   z/Only classification and regression implemented.N)rI   r   ri   	enumeraterj   Zclassificationslenclasseslabelstrr>   rO   r)   Zscorer~   r   r   r	   rF   r   sortr   rk   Zregressionsr@   NotImplementedError)r   rl   r   Zy_labelseriesidxrR   r   Zclass_indexZclassification_classr   Z
mutant_valZreturn_seriesr   r@   Zy_listZpointsrS   Zlist_of_pointsr   r   r   r     s    



  



  
r   c                 C   s   t | tjjr| jjS | jjS )z<Returns the non-sequence features from the provided example.)rI   tfZtrainZExamplers   rf   contextru   r   r   r   rb   r  s    rb   c                 C   s2   t | |\}}t|}tj|dd}t||fS )z,Calls servo and wraps the inference results.T)Zincluding_default_value_fields)r   rm   r   ZMessageToJsonjsonloads)r3   r   rl   extra_resultsZ
inferencesZ
infer_jsonr   r   r   #run_inference_for_inference_resultsw  s      r   c                 C   sH   t | }|t| | g }t| D ]\}}||d< || q(|S )a  Returns a list of JSON objects for each feature in the examples.

    This list is used to drive partial dependence plots in the plugin.

    Args:
      examples: Examples to examine to determine the eligible features.
      num_mutants: The number of mutations to make over each feature.

    Returns:
      A list with a JSON object for each feature.
      Numeric features are represented as {name: observedMin: observedMax:}.
      Categorical features are repesented as {name: samples:[]}.
    name)r   updater   r*   itemsr)   )r3   r4   Zfeatures_dictfeatures_listkvr   r   r   get_eligible_features  s     r   c                 C   s  t | }|D ]}|d }|| }d}|d dk}|d D ]}|D ]}	|	 D ]}
|rd}tt|
d D ](}|t|
| d |
|d  d  7 }qjnNtd}td	}tt|
D ](}|
| d }||k r|}||kr|}q|| }||krN|}qNqBq:||d
< qt|dd ddS )a  Returns a sorted list of objects representing each feature.

  The list is sorted by interestingness in terms of the resulting change in
  inference values across feature values, for partial dependence plots.

  Args:
    features_list: A list of eligible features in the format of the return
        from the get_eligible_features function.
    chart_data: A dict of feature names to chart data, formatted as the
        output from the mutant_charts_for_feature function.

  Returns:
    A sorted list of the inputted features_list, with the addition of
    an 'interestingness' key with a calculated number for feature feature.
    The list is sorted with the feature with highest interestingness first.
  r   r   r   r   r   r%   r   infz-infinterestingnessc                 S   s   | d S )Nr   r   r   r   r   r   r     r   z(sort_eligible_features.<locals>.<lambda>T)r   reverse)r   r   valuesr(   r   absr   r*   )r   Z
chart_dataZsorted_features_listrf   r   r   Zmax_measureZ
is_numericmodelsZchartr   ZmeasureiZmin_yZmax_yvalr   r   r   sort_eligible_features  s<    
(

  r   c              
   C   st   | rpz8t jj| d}dd |D W  5 Q R  W S Q R X W n2 t jjk
rn } ztd| W 5 d}~X Y nX g S )z>Returns a list of label strings loaded from the provided path.rc                 S   s   g | ]}| d qS )
)rstrip)r    liner   r   r   r"     s     z#get_label_vocab.<locals>.<listcomp>zerror reading vocab file: %sN)r   ioZgfileZGFileerrorsZNotFoundErrorr   error)Z
vocab_pathferrr   r   r   get_label_vocab  s    &r   c              
      s   dd }d}d}t jj  |t jjdt jddi}t j| |}t ddddgt j	}t 
d	}||ft 
t|  || }fd
d}	t j fdd|	|||g| | t dgd}
||
d }| W  5 Q R  S Q R X dS )zReturns an encoded sprite image for use in Facets Dive.

    Args:
      examples: A list of serialized example protos to get images for.

    Returns:
      An encoded PNG.
    c                 S   s   t | d  }ttt|}|d }|d }|| }|| }d}t|||g}	t	|  D ]\\}
}|
| }tt
|
| }|| }|| }|| }|| }||	||||ddf< qft jt j|	t jdS )z8Generates a sprite atlas image from a set of thumbnails.r   r%      N)Zdtype)r   shapeevalr   mathceilsqrtr   zerosr   floorimageZ
encode_pngcastZuint8)Z
thumbnailsthumbnail_dimsZnum_thumbnailsZimages_per_rowZthumb_heightZthumb_widthZmaster_heightZmaster_widthZnum_channelsZmasterr   r   Zleft_idxZtop_idxZ
left_startZleft_endZ	top_startZtop_endr   r   r   generate_image_from_thubnails  s"    z:create_sprite_image.<locals>.generate_image_from_thubnailszimage/encoded    r   rQ   )default_valuer%   r   c                    sh   ||  }t jj|dd}t j|}t |d t t | d fdd fdd| d |fS )Nr   )Zchannelsr   c                      s    S r   r   r   )expanded_imager   r   r     r   z8create_sprite_image.<locals>.loop_body.<locals>.<lambda>c                      s   t  gdS )Nr   )r   concatr   r   imagesr   r   r     r   r%   )r   r   Zdecode_jpegresizeZexpand_dimsZcondequal)r   encoded_imagesr   Zencoded_imager   Zresized_image)r   r   r   	loop_body  s    
 
z&create_sprite_image.<locals>.loop_bodyc                    s   t |  S r   )r   Zless)r   r   r   )num_examplesr   r   r     r   z%create_sprite_image.<locals>.<lambda>N)Zshape_invariantsr   )r   compatZv1Sessionr   ZFixedLenFeaturestringparse_exampler   Zfloat32Zconstantr   Z
while_loopZ	get_shapeZTensorShaper   )r3   r   Zimage_feature_nameZsprite_thumbnail_dim_pxZkeys_to_featuresparsedr   r   r   r   Zloop_outZspriter   )r   r   r   create_sprite_image  s6    



 
r   c           
         sD  d j rjrj  fdd}jr4jnd}g }|D ]d}|dkrt| }t|dkrn|d }njdkr~d}nd	}||krt	d
| |
||  q@t|dfS jr0tj}|j}t|dkr}t|dkr}d}	t|tr|d	}|}	n|}t||	fS tdfS dS )a  Run inference on examples given model information

  Args:
    examples: A list of examples that matches the model spec.
    serving_bundle: A `ServingBundle` object that contains the information to
      make the inference request.

  Returns:
    A tuple with the first entry being the ClassificationResponse or
    RegressionResponse proto and the second entry being a dictionary of extra
    data for each example, such as attributions, or None if no data exists.
  @   c                      s*   t jjt jdd D j S )Nc                 S   s   g | ]}|  qS r   )ZSerializeToString)r    exr   r   r   r"   5  s     z3run_inference.<locals>.<lambda>.<locals>.<listcomp>)r   r   ZDatasetZfrom_tensor_slicesr   r   r]   batchr   Z
batch_sizer3   r   r   r   r   4  s   zrun_inference.<locals>.<lambda>Nr%   r   rR   ZprobabilitiesZpredictionsz."%s" not found in model predictions dictionaryr   )r\   r]   ZpredictrY   r[   r   keysr   rW   KeyErrorr)   r   Zconvert_prediction_valuesr^   r   
parametersrI   dictpopr   Z
call_servo)
r3   r   predsZ
key_to_user   predZreturned_keyssigparamsr   r   r   r   r   #  sT    






r   )8r<   
__future__r   r   r~   r   r   r   Zabslr   Znumpyr   Z
tensorflowr   Zgoogle.protobufr   sixr   r   r   r	   Z	six.movesr
   inspectr   Ztensorboard_plugin_wit._utilsr   r   Z6tensorboard_plugin_wit._vendor.tensorflow_serving.apisr   r   r   objectr   r=   rH   rP   r>   rg   rh   rm   rv   ry   r   r   r   r   r   r   rb   r   r   r   r   r   r   r   r   r   r   <module>   sT   F$;
'%+=b	5
F