-
Notifications
You must be signed in to change notification settings - Fork 29k
[SPARK-15064][ML] Locale support in StopWordsRemover #12968
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,9 +17,11 @@ | |
|
|
||
| package org.apache.spark.ml.feature | ||
|
|
||
| import java.util.Locale | ||
|
|
||
| import org.apache.spark.annotation.{Experimental, Since} | ||
| import org.apache.spark.ml.Transformer | ||
| import org.apache.spark.ml.param.{BooleanParam, ParamMap, StringArrayParam} | ||
| import org.apache.spark.ml.param.{BooleanParam, Param, ParamMap, StringArrayParam} | ||
| import org.apache.spark.ml.param.shared.{HasInputCol, HasOutputCol} | ||
| import org.apache.spark.ml.util._ | ||
| import org.apache.spark.sql.{DataFrame, Dataset} | ||
|
|
@@ -73,22 +75,38 @@ class StopWordsRemover(override val uid: String) | |
| /** @group getParam */ | ||
| def getCaseSensitive: Boolean = $(caseSensitive) | ||
|
|
||
| setDefault(stopWords -> StopWordsRemover.loadDefaultStopWords("english"), caseSensitive -> false) | ||
| /** | ||
| * Locale for doing a case sensitive comparison | ||
| * Default: English locale ("en") | ||
| * @see [[http://www.localeplanet.com/java/]] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please link to the official Java doc: https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html or the |
||
| * @group param | ||
| */ | ||
| val locale: Param[String] = new Param[String](this, "locale", | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, shouldn't all this perhaps be linked to the stopwords set? if you loaded the French stopwords you'd want the French locale always?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, but, How can we know that users loaded the French stopwords? User can load stopwords by
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For supported languages, we can know the appropriate locale and maintain an internal mapping. So "french" is known to map to |
||
| "locale for doing a case sensitive comparison") | ||
|
|
||
| /** @group setParam */ | ||
| def setLocale(value: String): this.type = set(locale, value) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Myabe add parameter check here or in transformSchema, to help detect error before pipeline executes.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we use LocaleUtils from https://commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/apache/commons/lang/LocaleUtils.html ? It provides us to validate locale.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks good. |
||
|
|
||
| /** @group getParam */ | ||
| def getLocale: String = $(locale) | ||
|
|
||
| setDefault(stopWords -> StopWordsRemover.loadDefaultStopWords("english"), | ||
| caseSensitive -> false, locale -> "en") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comparing with EN, it perhaps better to use Locale.default (original behavior)
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the English set is loaded by default the locale should match, rather than use the platform default
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But, in any event, if 'stopwords' is not set, English list will be loaded. I think, It is better to use English locale as default. If users want to change locale, they can simply change by setLocale.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, En Locale is better. |
||
|
|
||
| @Since("2.0.0") | ||
| override def transform(dataset: Dataset[_]): DataFrame = { | ||
| val outputSchema = transformSchema(dataset.schema) | ||
| val t = if ($(caseSensitive)) { | ||
| val stopWordsSet = $(stopWords).toSet | ||
| udf { terms: Seq[String] => | ||
| terms.filter(s => !stopWordsSet.contains(s)) | ||
| terms.filterNot(stopWordsSet.contains) | ||
| } | ||
| } else { | ||
| // TODO: support user locale (SPARK-15064) | ||
| val toLower = (s: String) => if (s != null) s.toLowerCase else s | ||
| val loadedLocale = StopWordsRemover.loadLocale($(locale)) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe just |
||
| val toLower = (s: String) => if (s != null) s.toLowerCase(loadedLocale) else s | ||
| val lowerStopWords = $(stopWords).map(toLower(_)).toSet | ||
| udf { terms: Seq[String] => | ||
| terms.filter(s => !lowerStopWords.contains(toLower(s))) | ||
| terms.filterNot(term => lowerStopWords.contains(toLower(term))) | ||
| } | ||
| } | ||
| val metadata = outputSchema($(outputCol)).metadata | ||
|
|
@@ -109,6 +127,7 @@ class StopWordsRemover(override val uid: String) | |
| object StopWordsRemover extends DefaultParamsReadable[StopWordsRemover] { | ||
|
|
||
| private[feature] | ||
| def loadLocale(value : String): java.util.Locale = new Locale(value) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is just |
||
| val supportedLanguages = Set("danish", "dutch", "english", "finnish", "french", "german", | ||
| "hungarian", "italian", "norwegian", "portuguese", "russian", "spanish", "swedish", "turkish") | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -98,6 +98,7 @@ class StopWordsRemoverSuite | |
| .setInputCol("raw") | ||
| .setOutputCol("filtered") | ||
| .setStopWords(stopWords) | ||
| .setLocale("tr") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe something more specific to test that Locale setter is working. I would suggest adding a new ut.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I couldn't use special charset because of styles check, but I did it in Python's test.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe consider to use |
||
| val dataSet = spark.createDataFrame(Seq( | ||
| (Seq("acaba", "ama", "biri"), Seq()), | ||
| (Seq("hep", "her", "scala"), Seq("scala")) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shall we list what're the available options, or provide some reference here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's done