Un buscador en la nube : Azure Search (y 3)

Finalmente llegamos al fin de esta saga de tres partes sobre Azure Search, no ha sido tan duro ¿no?.

Recopilando un poco, en mi primer post daba una visión general de que era y para que servía Azure Search. En el siguiente post vimos como crear índices, añadirle documentos y operar sobre estos con el fin de obtener las búsquedas requeridas.

En esta última entrega, el objetivo es conocer dos operaciones que nos permiten sacar el máximo partido a Azure Search. Hoy conoceremos las Suggestions y el Scoring.

Para continuar con el hilo de los anteriores posts, los ejemplos que os mostraré los realizaré mediante el SDK de .NET y continuando con el ejemplo del post anterior. Recordad que todas las operaciones también se pueden hacer vía RESTAPI, podéis ver como hacerlo en los siguientes links:

Suggestions – https://msdn.microsoft.com/en-us/library/azure/dn798936.aspx?f=255&MSPPError=-2147217396

Scoring https://msdn.microsoft.com/en-us/library/azure/dn798928.aspx

Suggestions

Cuando un usuario quiere buscar algo en un web site o una app mobile lo primero que hacen es ir al buscador de la aplicación. Por esta razón el buscador debe ser efectivo y ayudar al usuario a encontrar lo que busca. Una forma de ayudar al usuario es mediante sugerencias o auto-complete.

Podemos ver un ejemplo en la página de amazon y poniendo en el buscador la palabra “wind”, el resultado mostrado es el siguiente:

amazonSearch

¿Como configuramos las suggestions en Azure Search?

Cuando definimos el índice debemos informar el campo Suggesters:

//Define SuggesterList
 List<Suggester> suggesterList = new List<Suggester>();
    suggesterList.Add(new Suggester() { Name = "suggester", SearchMode = SuggesterSearchMode.AnalyzingInfixMatching, SourceFields = new List<string>() { "Titulo", "Autores" } });
//Create Index Model
Index indexModel = new Index()
{
 Name = indexName,
 Fields = new[]
 {
 new Field("ISBN", DataType.String) { IsKey = true, IsRetrievable = true, IsFacetable = false },
 new Field("Titulo", DataType.String) {IsRetrievable = true, IsSearchable = true, IsFacetable = false },
 new Field("Autores", DataType.Collection(DataType.String)) {IsSearchable = true, IsRetrievable = true, IsFilterable = true, IsFacetable = false },
 new Field("FechaPublicacion", DataType.DateTimeOffset) { IsFilterable = true, IsRetrievable = false, IsSortable = true, IsFacetable = false },
 new Field("Categoria", DataType.String) { IsFacetable = true, IsFilterable= true, IsRetrievable = true }
 },
 //Add Suggesters
 Suggesters = suggesterList
};

Como podéis observar Suggesters es una lista de tipo Suggester. Un Suggester se define de la siguiente forma:

Name: Nombre del Suggester. Identifica el Suggester que queremos definir.

SearchMode: que es del tipo SuggesterSearchMode que es un enum con una sola opción AnalyzingInfixMatchingAnalyzingInfixMatching realiza el “matching” de frases al principio o en medio de las sentencias.

SourceFields: Que es una lista de strings donde se indican los indices sobre los cuales queremos obtener sugerencias. Estos indices deben ser de los tipos Edm.String o Collection(Edm.String)

Si vamos al portal podemos ver como se ha configurado las suggestions.

Suggesters

¿COMO Utilizarlo?

Lo que debemos hacer es llamar al método Suggest de Documents.

Esté método consta de cuatro parámetros, dos obligatorios, y dos opcioneales:

searchText: Campo obligatorio. El texto a partir del cual queremos crear las sugerencias.

suggesterName: Campo obligatorio. El suggester que queremos utilizar. Campo Name de la clase Suggester anteriormente mencionada.

suggestParameters: Campo opcional. De tipo SuggestParameters. Este parámetro nos permite configurar como queremos que funcione la búsqueda de las suggestions. 

searchRequestOptions: Campo opcional. De tipo SearchRequestOptions. Este campo nos ayuda a la hora de realizar Debugging, permitiendo seguir las trazas a partir del GUID generado.

Propiedades de SuggestParameters

Filter: Filtro a aplicar para acotar los documentos donde realizar la búsqueda de sugerencias.

HighlightPreTag: String que se pondrá delante de las coincidencias.

HighlightPostTag:  String que se anexa a las coincidencias:

MinimumCoverage: Campo con valor entre 0 y 100 que indica el porcentaje de indices que debe ser abarcada por la query de sugerencias para que su resultado sea satisfactorio.

OrderBy: Orden en el cuál deben ser devueltos los resultados.

SearchFields: Dentro del Suggester se define sobre que indices operará la búsqueda de sugerencias, con este campo podemos seleccionar solo alguno de esos indices. Por defecto si no se informa busca en todos los índices parametrizados en el Suggester. Por defecto su valor es 80.

Select: Solo devolver algunos de los campos. Hay que indicar que cuando se realiza una búsqueda de sugerencias, además de devolver las sugerencias, también devuelve todos aquellos documentos que coincides con estas sugerencias, por ello se permite realizar las operaciones de OrderBy, Filter, y Select.

Top: El número de sugerencias a devolver.

UseFuzzyMatching: Por defecto su valor es false. Si le asignamos el valor true se encontrarán sugerencias aunque no coincidan todas las palabras, es decir, realizar aproximaciones en la búsqueda.

Siguiendo con el ejemplo, aquí tenemos  un ejemplo de como realizar una búsqueda de sugerencias sin fuzzy y buscando sugerencias solo en autores:

var indexName = "example";                
SearchServiceClient azureSearchService = new SearchServiceClient("YourAzureSearchServiceName", "YourAzureSearchKey"));
SearchIndexClient indexClient = azureSearchService.Indexes.GetClient(indexName);

//Suggest
Console.WriteLine("\n{0}", "Suggest by Ro without fuzzy \n");
SuggestionSearch(indexClient, suggesterText: "Ro", suggester: "suggester", fuzzy:false);

private static void SuggestionSearch(SearchIndexClient indexClient, string suggesterText, string suggester, bool fuzzy)
{
	SuggestParameters suggestParameters = new SuggestParameters()
	{                
		Top = 10,
		UseFuzzyMatching = fuzzy,                
		SearchFields = new List&amp;amp;amp;amp;amp;amp;lt;string&amp;amp;amp;amp;amp;amp;gt; { "Autores" }
	};          

	DocumentSuggestResult response = indexClient.Documents.Suggest(suggesterText, suggester, suggestParameters, searchRequestOptions: new SearchRequestOptions(Guid.NewGuid()));
	foreach (var suggestion in response.Results)
	{                
		Console.WriteLine("Suggestion: " + suggestion.Text);
	}
}

Resultado:

suggestionwithoutfuzzy

Ahora realizamos la misma búsqueda pero con fuzzy.

var indexName = "example";                
SearchServiceClient azureSearchService = new SearchServiceClient("YourAzureSearchServiceName", "YourAzureSearchKey"));
SearchIndexClient indexClient = azureSearchService.Indexes.GetClient(indexName);

//Suggest
Console.WriteLine("\n{0}", "Suggest by Ro with fuzzy \n");
SuggestionSearch(indexClient, suggesterText: "Ro", suggester: "suggester", fuzzy: true);

private static void SuggestionSearch(SearchIndexClient indexClient, string suggesterText, string suggester, bool fuzzy)
{
	SuggestParameters suggestParameters = new SuggestParameters()
	{                
		Top = 10,
		UseFuzzyMatching = fuzzy,                
		SearchFields = new List&amp;amp;amp;amp;amp;amp;lt;string&amp;amp;amp;amp;amp;amp;gt; { "Autores" }
	};          

	DocumentSuggestResult response = indexClient.Documents.Suggest(suggesterText, suggester, suggestParameters, searchRequestOptions: new SearchRequestOptions(Guid.NewGuid()));
	foreach (var suggestion in response.Results)
	{                
		Console.WriteLine("Suggestion: " + suggestion.Text);
	}
}

Vemos que nos devuelve muchos más resultados.

suggestionwithfuzzy

Como podéis observar es muy fácil obtener sugerencias y enriquecer nuestras aplicaciones de forma rápida y sencilla.

Scoring

En algunos casos, necesitamos que algunos resultados salgan antes que otros. Por ejemplo, imaginemos una tienda on-line que quiere promocionar un nuevo producto, para ello le interesa que salga en las primeras posiciones cuando los usuarios realicen búsquedas, pues Azure Search nos da esta opción mediante el scoring.

Scoring profiles nos permite personalizar el cálculo de scoring en las búsquedas.

Si realizamos una búsqueda vemos que los resultados devuelven un campo llamado Score( si la búsqueda la hacemos desde el portal el campo se llama @search.score), pues al añadir un Scoring profile lo que estamos haciendo es darle peso a este campo, de forma que primero muestra los que más peso tienen.

SearchServiceClient azureSearchService = new SearchServiceClient(ConfigurationManager.AppSettings["AzureSearchServiceName"], new SearchCredentials(ConfigurationManager.AppSettings["AzureSearchKey"]));
SearchIndexClient indexClient = azureSearchService.Indexes.GetClient(indexName);
var listBooks = new BookModel().GetBooks();

if (azureSearchService.Indexes.Exists(indexName))
{
	azureSearchService.Indexes.Delete(indexName);
}

//Define SuggesterList
List&amp;amp;lt;Suggester&amp;amp;gt; suggesterList = new List&amp;amp;lt;Suggester&amp;amp;gt;();
suggesterList.Add(new Suggester() { Name = "suggester", SearchMode = SuggesterSearchMode.AnalyzingInfixMatching, SourceFields = new List&amp;amp;lt;string&amp;amp;gt;() { "Titulo", "Autores" } });
//Create Index Model
Index indexModel = new Index()
{
	Name = indexName,
	Fields = new[]
	{
		new Field("ISBN", DataType.String) { IsKey = true, IsRetrievable = true, IsFacetable = false },
		new Field("Titulo", DataType.String) {IsRetrievable = true, IsSearchable = true, IsFacetable = false },
		new Field("Autores", DataType.Collection(DataType.String)) {IsSearchable = true, IsRetrievable = true, IsFilterable = true, IsFacetable = false },
		new Field("FechaPublicacion", DataType.DateTimeOffset) { IsFilterable = true, IsRetrievable = false, IsSortable = true, IsFacetable = false },
		new Field("Categoria", DataType.String) { IsFacetable = true, IsFilterable= true, IsRetrievable = true }

	},
	//Add Suggesters
	Suggesters = suggesterList
};

//Create Index in AzureSearch
var resultIndex = azureSearchService.Indexes.Create(indexModel);
//Add documents in our Index
azureSearchService.Indexes.GetClient(indexName).Documents.Index(IndexBatch.MergeOrUpload&amp;amp;lt;BookModel&amp;amp;gt;(listBooks));
//Search by word
Console.WriteLine("{0}", "Searching documents 'Cloud'...\n");
Search(indexClient, searchText: "Cloud");

 private static void Search(SearchIndexClient indexClient, string searchText, string filter = null, List&amp;amp;lt;string&amp;amp;gt; order = null, List&amp;amp;lt;string&amp;amp;gt; facets = null)
{
	// Execute search based on search text and optional filter
	var sp = new SearchParameters();

	//Add Filter
	if (!String.IsNullOrEmpty(filter))
	{
		sp.Filter = filter;
	}

	//Order
	if(order!=null &amp;amp;amp;&amp;amp;amp; order.Count&amp;amp;gt;0)
	{
		sp.OrderBy = order;
	}

	//facets
	if (facets != null &amp;amp;amp;&amp;amp;amp; facets.Count &amp;amp;gt; 0)
	{
		sp.Facets = facets;
	}            

	//Search
	DocumentSearchResult&amp;amp;lt;BookModel&amp;amp;gt; response = indexClient.Documents.SearchAsync&amp;amp;lt;BookModel&amp;amp;gt;(searchText, sp).Result;
	foreach (SearchResult&amp;amp;lt;BookModel&amp;amp;gt; result in response.Results)
	{                
		Console.WriteLine(result.Document + " Score: " + result.Score);
	}
	if (response.Facets != null)
	{
		foreach (var facet in response.Facets)
		{
			Console.WriteLine("Facet Name: " + facet.Key);
			foreach(var value in facet.Value)
			{
				Console.WriteLine("Value :" + value.Value + " - Count: " + value.Count);
			}
		}
	}
}

Si aplicamos el código anterior el resultado mostrado es:

defaultScore

En este caso no hemos definido ningún Scoring profile pero vemos que se ha aplicado un scoring a los resultados. Esto es debido a que se aplica un scoring por defecto, que basa su cálculo en una aproximación de cálculo llamada TF-IDF ( item frequency-inverse document frequency).

¿COMO CONFIGURAMOS SCORING PROFILES EN AZURE SEARCH?

Igual que en el caso de los Suggesters, el Scoring Profile se configura cuando definimos el índice.

//Define ScoringList
List&amp;amp;lt;ScoringProfile&amp;amp;gt; scoringsList = new List&amp;amp;lt;ScoringProfile&amp;amp;gt;();
Dictionary&amp;amp;lt;string, double&amp;amp;gt; textWieghtsDictionary = new Dictionary&amp;amp;lt;string, double&amp;amp;gt;();
textWieghtsDictionary.Add("Autores", 1.5);
scoringsList.Add(new ScoringProfile()
{
	Name = "ScoringTest",
	TextWeights = new TextWeights(textWieghtsDictionary)
}
);
//Create Index Model
Index indexModel = new Index()
{
	Name = indexName,
	Fields = new[]
	{
		new Field("ISBN", DataType.String) { IsKey = true, IsRetrievable = true, IsFacetable = false },
		new Field("Titulo", DataType.String) {IsRetrievable = true, IsSearchable = true, IsFacetable = false },
		new Field("Autores", DataType.Collection(DataType.String)) {IsSearchable = true, IsRetrievable = true, IsFilterable = true, IsFacetable = false },
		new Field("FechaPublicacion", DataType.DateTimeOffset) { IsFilterable = true, IsRetrievable = false, IsSortable = true, IsFacetable = false },
		new Field("Categoria", DataType.String) { IsFacetable = true, IsFilterable= true, IsRetrievable = true }

	},
	//Add Suggesters
	Suggesters = suggesterList,
	//Add Scorings
	ScoringProfiles = scoringsList
};

Como podéis observar ScoringProfiles es una lista de tipo ScoringProfile. Un ScoringProfile se define de la siguiente forma:

Name: Campo obligatorio.

Functions: Campo opcional. Lista de ScoringFunctions. Las functions nos permiten definir como queremos que aumente la puntuación, es decir parametrizamos como queremos que este aumento funcione: podemos definir el peso, durante cuanto tiempo está activo….

Un ejemplo de ScoringFunctions sería:

ScoringFunction sp = new FreshnessScoringFunction()
{
	Boost = 10,
	FieldName = "Autores",
&amp;nbsp;       Interpolation = ScoringFunctionInterpolation.Linear,
	Parameters = new FreshnessScoringParameters()
	{
		BoostingDuration = DateTime.Now.Add(20).TimeOfDay;
	}
}

Lo que estamos haciendo es añadir una función que cuando se hagan búsquedas sobre el índice Autores le añadirá un peso de 10 de forma linear durante 20 días solo.

Para más información sobre las Functions y  los tipos de ScoringFunctions podéis visitar este link.

FunctionAggregation: Campo opcional. Valor que indica como los resultados de las funciones definidas debes combinarse. Sus posibles valores son  ‘sum’, ‘average’, ‘minimum’, ‘maximum’, ‘firstMatching’. Su valor por defecto se ‘sum‘ y solo se aplica si hay alguna function definida.

TextWeights: Campo opcional. Cuando existen coincidencias de texto, a la puntuación obtenida se le suma el valor asignado en este campo.

¿COMO UTILIZARLO?

Si queremos aplicar un scoring profile en nuestra búsqueda lo que debemos hacer es indicar que profile queremos utilizar:

var sp = new SearchParameters();
sp.ScoringProfile = "scoringName";

//Search
DocumentSearchResult&amp;amp;lt;BookModel&amp;amp;gt; response = indexClient.Documents.SearchAsync&amp;amp;lt;BookModel&amp;amp;gt;(searchText, sp).Result;

Y obtenemos el siguiente resultado:

CustomScoring

En el caso que se hubiera definido una ScoringFunction en el scoring, ademas de definir que ScoringProfile se debe definir ScoringParameters que es una lista de ScoringParameter donde defines para la ScoringFunction que deseas que valor quieres aplicar.

Por ejemplo, para la FreshnessScoringFunction que hemos definido anteriormente la contemporización de la llamada quedaría de la siguiente forma:

List&amp;amp;lt;ScoringParameter&amp;amp;gt; scoringParameterlList = new List&amp;amp;lt;ScoringParameter&amp;amp;gt;();
scoringParameterlList.Add(new ScoringParameter("Autores", "5"));
sp.ScoringProfile = scoringName;
sp.ScoringParameters = scoringParameterlList;

//Search
DocumentSearchResult&amp;amp;lt;BookModel&amp;amp;gt; response = indexClient.Documents.SearchAsync&amp;amp;lt;BookModel&amp;amp;gt;(searchText, sp).Result;

Conclusiones

Después de esta trilogía de posts podemos ver la potencia que nos da Azure Search y como podemos trabajar con él. Hemos podido aprender como crearlo, configurarlo y como atacarlo según nuestras necesidades.

Os ánimo a que entréis más en detalle y podáis ver lo asombroso que es este servicio.

Podéis descargaros el código aquí.

Referencias:

https://azure.microsoft.com/en-us/blog/announcing-new-enhancements-to-azure-search-suggestions/

https://msdn.microsoft.com/en-Us/library/azure/mt131377.aspx

https://azure.microsoft.com/en-us/documentation/articles/search-get-started-scoring-profiles/

http://social.technet.microsoft.com/wiki/contents/articles/26706.what-are-scoring-profiles-in-azure-search.aspx

 

[Total: 0    Average: 0/5]