jenkins-bot has submitted this change and it was merged.

Change subject: Added Similarity configuration
......................................................................


Added Similarity configuration

* User can now configure the similarity function and parameters used by the
  index
* Re-enabled norms on arrays fields

Norms were explicity disabled on array fields which maybe makes sense with the
default similiraty, docs with a high number of redirects were maybe ranked too 
low
but without norms it's the opposite, used with a DisMax the redirect field will
always win thus docs with a high number of redirects are likely to win.
I think it's better to re-enable them and allow the user to fine tune a 
parameter
that affects norms (setting a very low b value with bm25).

Bug: T128061
Change-Id: Ie718c8a03ea5351e2e142f22436e2fd83314056f
---
M CirrusSearch.php
M includes/Maintenance/AnalysisConfigBuilder.php
M includes/Maintenance/IndexCreator.php
M includes/Maintenance/MappingConfigBuilder.php
A profiles/SimilarityProfiles.php
5 files changed, 166 insertions(+), 19 deletions(-)

Approvals:
  Cindy-the-browser-test-bot: Looks good to me, but someone else must approve
  EBernhardson: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/CirrusSearch.php b/CirrusSearch.php
index 78e87b9..33e6417 100644
--- a/CirrusSearch.php
+++ b/CirrusSearch.php
@@ -25,6 +25,7 @@
 require_once __DIR__ . "/profiles/PhraseSuggesterProfiles.php";
 require_once __DIR__ . "/profiles/CommonTermsQueryProfiles.php";
 require_once __DIR__ . "/profiles/RescoreProfiles.php";
+require_once __DIR__ . "/profiles/SimilarityProfiles.php";
 
 $wgExtensionCredits['other'][] = array(
        'path'           => __FILE__,
@@ -344,6 +345,10 @@
 // Maximum number of newly unlinked articles to update when an article changes.
 $wgCirrusSearchUnlinkedArticlesToUpdate = 25;
 
+// Configure the similarity module
+// see profile/SimilarityProfiles.php for more details
+$wgCirrusSearchSimilarityProfile = 
$wgCirrusSearchSimilarityProfiles['default'];
+
 // Weight of fields.  Must be integers not decimals.  If 
$wgCirrusSearchAllFields['use']
 // is false this can be changed on the fly.  If it is true then changes to 
this require
 // an in place reindex to take effect.
diff --git a/includes/Maintenance/AnalysisConfigBuilder.php 
b/includes/Maintenance/AnalysisConfigBuilder.php
index 3e29209..5ba02d1 100644
--- a/includes/Maintenance/AnalysisConfigBuilder.php
+++ b/includes/Maintenance/AnalysisConfigBuilder.php
@@ -2,6 +2,8 @@
 
 namespace CirrusSearch\Maintenance;
 
+use ConfigFactory;
+use \CirrusSearch\SearchConfig;
 use \CirrusSearch\Searcher;
 use \Hooks;
 use \Language;
@@ -44,12 +46,15 @@
         */
        private $icu;
 
+       private $similarity;
+
        /**
         * Constructor
         * @param string $langCode The language code to build config for
         * @param array(string) $plugins list of plugins installed in 
Elasticsearch
+        * @param SearchConfig $config
         */
-       public function __construct( $langCode, $plugins ) {
+       public function __construct( $langCode, $plugins, $config = null ) {
                $this->language = $langCode;
                foreach ( $this->elasticsearchLanguageAnalyzersFromPlugins as 
$plugin => $extra ) {
                        if ( in_array( $plugin, $plugins ) ) {
@@ -57,6 +62,10 @@
                        }
                }
                $this->icu = in_array( 'analysis-icu', $plugins );
+               if ( is_null ( $config ) ) {
+                       $config = 
ConfigFactory::getDefaultInstance()->makeConfig( 'CirrusSearch' );
+               }
+               $this->similarity = $config->get( 
'CirrusSearchSimilarityProfile' );
        }
 
        /**
@@ -67,6 +76,16 @@
                $config = $this->customize( $this->defaults() );
                Hooks::run( 'CirrusSearchAnalysisConfig', array( &$config ) );
                return $config;
+       }
+
+       /**
+        * Build the similarity config
+        * @return array the similarity config
+        */
+       public function buildSimilarityConfig() {
+               if ( $this->similarity != null && isset ( 
$this->similarity['similarity'] ) ) {
+                       return $this->similarity['similarity'];
+               }
        }
 
        /**
@@ -233,6 +252,7 @@
                                'name' => 'nfkc_cf',
                        );
                }
+
                return $defaults;
        }
 
diff --git a/includes/Maintenance/IndexCreator.php 
b/includes/Maintenance/IndexCreator.php
index 93eecf2..518ea52 100644
--- a/includes/Maintenance/IndexCreator.php
+++ b/includes/Maintenance/IndexCreator.php
@@ -96,6 +96,10 @@
                                'routing.allocation.total_shards_per_node' => 
$maxShardsPerNode,
                        )
                );
+               $similarity = 
$this->analysisConfigBuilder->buildSimilarityConfig();
+               if ( $similarity ) {
+                       $args['settings']['similarity'] = $similarity;
+               }
 
                if ( $searchAllFields ) {
                        // Use our weighted all field as the default rather 
than _all which is disabled.
diff --git a/includes/Maintenance/MappingConfigBuilder.php 
b/includes/Maintenance/MappingConfigBuilder.php
index f512e38..4767823 100644
--- a/includes/Maintenance/MappingConfigBuilder.php
+++ b/includes/Maintenance/MappingConfigBuilder.php
@@ -1,6 +1,8 @@
 <?php
 
 namespace CirrusSearch\Maintenance;
+use ConfigFactory;
+use \CirrusSearch\SearchConfig;
 use \Hooks;
 
 /**
@@ -56,12 +58,19 @@
         */
        private $optimizeForExperimentalHighlighter;
 
+       private $similarity;
+
        /**
         * Constructor
         * @param bool $optimizeForExperimentalHighlighter should the index be 
optimized for the experimental highlighter?
+        * @param SearchConfig $config
         */
-       public function __construct( $optimizeForExperimentalHighlighter ) {
+       public function __construct( $optimizeForExperimentalHighlighter, 
SearchConfig $config = null ) {
                $this->optimizeForExperimentalHighlighter = 
$optimizeForExperimentalHighlighter;
+               if ( is_null ( $config ) ) {
+                       $config = 
ConfigFactory::getDefaultInstance()->makeConfig( 'CirrusSearch' );
+               }
+               $this->similarity = $config->get( 
'CirrusSearchSimilarityProfile' );
        }
 
        /**
@@ -79,11 +88,11 @@
                // and is infered anyway.
                $titleExtraAnalyzers = array(
                        $suggestExtra,
-                       array( 'index_analyzer' => 'prefix', 'search_analyzer' 
=> 'near_match', 'index_options' => 'docs' ),
-                       array( 'index_analyzer' => 'prefix_asciifolding', 
'search_analyzer' => 'near_match_asciifolding', 'index_options' => 'docs' ),
-                       array( 'analyzer' => 'near_match', 'index_options' => 
'docs' ),
-                       array( 'analyzer' => 'near_match_asciifolding', 
'index_options' => 'docs' ),
-                       array( 'analyzer' => 'keyword', 'index_options' => 
'docs' ),
+                       array( 'index_analyzer' => 'prefix', 'search_analyzer' 
=> 'near_match', 'index_options' => 'docs', 'norms' => array( 'enabled' => 
false ) ),
+                       array( 'index_analyzer' => 'prefix_asciifolding', 
'search_analyzer' => 'near_match_asciifolding', 'index_options' => 'docs', 
'norms' => array( 'enabled' => false ) ),
+                       array( 'analyzer' => 'near_match', 'index_options' => 
'docs', 'norms' => array( 'enabled' => false ) ),
+                       array( 'analyzer' => 'near_match_asciifolding', 
'index_options' => 'docs', 'norms' => array( 'enabled' => false ) ),
+                       array( 'analyzer' => 'keyword', 'index_options' => 
'docs', 'norms' => array( 'enabled' => false ) ),
                );
                if ( $flags & self::PREFIX_START_WITH_ANY ) {
                        $titleExtraAnalyzers[] = array(
@@ -118,24 +127,24 @@
                                ),
                                'namespace' => $this->buildLongField(),
                                'namespace_text' => $this->buildKeywordField(),
-                               'title' => $this->buildStringField(
+                               'title' => $this->buildStringField( 'title',
                                        MappingConfigBuilder::ENABLE_NORMS | 
MappingConfigBuilder::COPY_TO_SUGGEST,
                                        $titleExtraAnalyzers ),
                                'text' => array_merge_recursive(
-                                       $this->buildStringField( $textOptions, 
$textExtraAnalyzers ),
+                                       $this->buildStringField( 'text', 
$textOptions, $textExtraAnalyzers ),
                                        array( 'fields' => array( 'word_count' 
=> array(
                                                'type' => 'token_count',
                                                'store' => true,
                                                'analyzer' => 'plain',
                                        ) ) )
                                ),
-                               'opening_text' => $this->buildStringField( 
MappingConfigBuilder::ENABLE_NORMS ),
-                               'auxiliary_text' => $this->buildStringField( 
$textOptions ),
-                               'file_text' => $this->buildStringField( 
$textOptions ),
-                               'source_text' => $this->buildStringField( 
MappingConfigBuilder::MINIMAL,
+                               'opening_text' => $this->buildStringField( 
'opening_text', MappingConfigBuilder::ENABLE_NORMS ),
+                               'auxiliary_text' => $this->buildStringField( 
'auxiliary_text', $textOptions ),
+                               'file_text' => $this->buildStringField( 
'file_text', $textOptions ),
+                               'source_text' => $this->buildStringField( 
'source_text', MappingConfigBuilder::MINIMAL,
                                        $sourceExtraAnalyzers
                                ),
-                               'category' => $this->buildStringField( 
MappingConfigBuilder::SPEED_UP_HIGHLIGHTING, array(
+                               'category' => $this->buildStringField( 
'category', $textOptions, array(
                                        array(
                                                'analyzer' => 
'lowercase_keyword',
                                                'norms' => array( 'enabled' => 
false ),
@@ -146,14 +155,14 @@
                                'template' => 
$this->buildLowercaseKeywordField(),
                                'outgoing_link' => $this->buildKeywordField(),
                                'external_link' => $this->buildKeywordField(),
-                               'heading' => $this->buildStringField( 
MappingConfigBuilder::SPEED_UP_HIGHLIGHTING ),
+                               'heading' => $this->buildStringField( 
'heading', MappingConfigBuilder::SPEED_UP_HIGHLIGHTING ),
                                'text_bytes' => $this->buildLongField( false ),
                                'redirect' => array(
                                        'dynamic' => false,
                                        'properties' => array(
                                                'namespace' =>  
$this->buildLongField(),
-                                               'title' => 
$this->buildStringField(
-                                                       
MappingConfigBuilder::COPY_TO_SUGGEST | 
MappingConfigBuilder::SPEED_UP_HIGHLIGHTING,
+                                               'title' => 
$this->buildStringField( 'redirect.title',
+                                                       $textOptions | 
MappingConfigBuilder::COPY_TO_SUGGEST,
                                                        $titleExtraAnalyzers ),
                                        )
                                ),
@@ -177,7 +186,7 @@
                        // This field can't be used for the fvh/experimental 
highlighter for several reasons:
                        //  1. It is built with copy_to and not stored.
                        //  2. The term frequency information is all whoppy 
compared to the "real" source text.
-                       $page[ 'properties' ][ 'all' ] = 
$this->buildStringField( MappingConfigBuilder::ENABLE_NORMS );
+                       $page[ 'properties' ][ 'all' ] = 
$this->buildStringField( 'all', MappingConfigBuilder::ENABLE_NORMS );
                        $page = $this->setupCopyTo( $page, 
$wgCirrusSearchWeights, 'all' );
 
                        // Now repeat for near_match fields.  The same 
considerations above apply except near_match
@@ -188,6 +197,7 @@
                                'index_options' => 'freqs',
                                'position_offset_gap' => 
self::POSITION_OFFSET_GAP,
                                'norms' => array( 'enabled' => false ),
+                               'similarity' => $this->getSimilarity( 
'all_near_match' ),
                                'fields' => array(
                                        'asciifolding' => array(
                                                'type' => 'string',
@@ -195,6 +205,7 @@
                                                'index_options' => 'freqs',
                                                'position_offset_gap' => 
self::POSITION_OFFSET_GAP,
                                                'norms' => array( 'enabled' => 
false ),
+                                               'similarity' => 
$this->getSimilarity( 'all_near_match', 'asciifolding' ),
                                        ),
                                ),
                        );
@@ -224,6 +235,28 @@
                return $config;
        }
 
+
+       /**
+        * Get the field similarity
+        * @param string $field
+        * @param string $analyzer
+        */
+       private function getSimilarity( $field, $analyzer = null ) {
+               $fieldSimilaraty = 'default';
+               if ( isset( $this->similarity['fields'] ) ) {
+                       if( isset( $this->similarity['fields'][$field] ) ) {
+                               $fieldSimilaraty = 
$this->similarity['fields'][$field];
+                       } else if ( $this->similarity['fields']['__default__'] 
) {
+                               $fieldSimilaraty = 
$this->similarity['fields']['__default__'];
+                       }
+
+                       if ( $analyzer != null && isset( 
$this->similarity['fields']["$field.$analyzer"] ) ) {
+                               $fieldSimilaraty = 
$this->similarity['fields']["$field.$analyzer"];
+                       }
+               }
+               return $fieldSimilaraty;
+       }
+
        /**
         * Setup copy_to for some fields to $destination.
         * @param array $config to modify
@@ -250,6 +283,7 @@
 
        /**
         * Build a string field that does standard analysis for the language.
+        * @param string $fieldName the field name
         * @param int $options Field options:
         *   ENABLE_NORMS: Enable norms on the field.  Good for text you search 
against but bad for array fields and useless
         *     for fields that don't get involved in the score.
@@ -259,19 +293,21 @@
         * @param array $extra Extra analyzers for this field beyond the basic 
text and plain.
         * @return array definition of the field
         */
-       public function buildStringField( $options, $extra = array() ) {
+       public function buildStringField( $fieldName, $options, $extra = 
array() ) {
                // multi_field is dead in 1.0 so we do this which actually 
looks less gnarly.
                $field = array(
                        'type' => 'string',
                        'index_analyzer' => 'text',
                        'search_analyzer' => 'text_search',
                        'position_offset_gap' => self::POSITION_OFFSET_GAP,
+                       'similarity' => $this->getSimilarity( $fieldName ),
                        'fields' => array(
                                'plain' => array(
                                        'type' => 'string',
                                        'index_analyzer' => 'plain',
                                        'search_analyzer' => 'plain_search',
                                        'position_offset_gap' => 
self::POSITION_OFFSET_GAP,
+                                       'similarity' => $this->getSimilarity( 
$fieldName, 'plain' ),
                                ),
                        )
                );
@@ -291,6 +327,7 @@
                                $extraName = $extraField[ 'index_analyzer' ];
                        }
                        $field[ 'fields' ][ $extraName ] = array_merge( array(
+                               'similarity' => $this->getSimilarity( 
$fieldName, $extraName ),
                                'type' => 'string',
                                'position_offset_gap' => 
self::POSITION_OFFSET_GAP,
                        ), $extraField );
diff --git a/profiles/SimilarityProfiles.php b/profiles/SimilarityProfiles.php
new file mode 100644
index 0000000..411dec8
--- /dev/null
+++ b/profiles/SimilarityProfiles.php
@@ -0,0 +1,81 @@
+<?php
+
+namespace CirrusSearch;
+/**
+ * CirrusSearch - List of Similarity profiles
+ * Configure the Similarity function used for scoring
+ * see 
https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules-similarity.html
+ *
+ * A reindex is required when changes are made to the similarity configuration.
+ * NOTE the parameters that do not affect indexed values can be tuned manually
+ * in the index settings but it is possible only on a closed index.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+$wgCirrusSearchSimilarityProfiles = array(
+       // default profile, uses the classic TF/IDF from Lucene
+       'default' => array(),
+       // BM25 with default values for k and a for all fields
+       'bm25_with_defaults' => array(
+               'similarity' => array(
+                       'bm25' => array(
+                               'type' => 'BM25'
+                       )
+               ),
+               'fields' => array(
+                       '__default__' => 'bm25',
+               )
+       ),
+       // Example with "randomly" tuned values
+       // (do not use)
+       'bm25_tuned' => array(
+               'similarity' => array(
+                       'title' => array(
+                               'type' => 'BM25',
+                               'k1' => 1.23,
+                               'b' => 0.75,
+                       ),
+                       'opening' => array(
+                               'type' => 'BM25',
+                               'k1' => 1.22,
+                               'b' => 0.75,
+                       ),
+                       'arrays' => array(
+                               'type' => 'BM25',
+                               'k1' => 1.1,
+                               'b' => 0.3,
+                       ),
+                       'text' => array(
+                               'type' => 'BM25',
+                               'k1' => 1.3,
+                               'b' => 0.80,
+                       ),
+               ),
+               'fields' => array(
+                       '__default__' => 'text',
+                       // Field specific config
+                       'opening_text' => 'opening',
+                       'category' => 'arrays',
+                       'title' => 'title',
+                       'heading' => 'arrays',
+                       // You can specify a custom config
+                       // for a specific analyzer
+                       'title.plain' => 'short_fields',
+                       'redirect.title' => 'arrays',
+               ),
+       ),
+);

-- 
To view, visit https://gerrit.wikimedia.org/r/274982
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Ie718c8a03ea5351e2e142f22436e2fd83314056f
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/CirrusSearch
Gerrit-Branch: master
Gerrit-Owner: DCausse <[email protected]>
Gerrit-Reviewer: Cindy-the-browser-test-bot <[email protected]>
Gerrit-Reviewer: EBernhardson <[email protected]>
Gerrit-Reviewer: Manybubbles <[email protected]>
Gerrit-Reviewer: Smalyshev <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to