Skip to content

Commit cea79dc

Browse files
wayneguowLuciferYang
authored andcommitted
[SPARK-51132][ML][BUILD] Upgrade JPMML to 1.7.1
### What changes were proposed in this pull request? This PR aims to upgrade `JPMML` from 1.4.8 to 1.7.1. The main changes from 1.4.8 to 1.7.1 are as follows: 1. Starting from version 1.5.0, `PMML` schema version has been updated from 4.3 to 4.4, the related commit is: jpmml/jpmml-model@7d8607a 2. Starting from version 1.6.0, `Java XML Binding` has been upgraded to `Jakarta XML Binding`, the related commit is: jpmml/jpmml-model@d76de1c After this PR, the exported PMML model schema version has been upgraded from 4.3(https://dmg.org/pmml/v4-3/GeneralStructure.html) to 4.4(https://dmg.org/pmml/v4-4-1/GeneralStructure.html). ### Why are the changes needed? 1. Upgrade the PMML standard to the latest 4.4 version ; 2. Upgrade `Java XML Binding` to `Jakarta XML Binding` by upgrade `JPMML`. ### Does this PR introduce _any_ user-facing change? Yes, the exported PMML model version has been upgraded from 4.3 to 4.4, details are as follows: 1. `version` has been changed from 4.2 to 4.4; 2. `xmlns` has been changed from `http://www.dmg.org/PMML-4_3` to `http://www.dmg.org/PMML-4_4`; 3. `Application version` has been changed to the current Spark version. Before: ``` <PMML version="4.2" xmlns="http://www.dmg.org/PMML-4_3" xmlns:data="http://jpmml.org/jpmml-model/InlineTable"> <Header description="k-means clustering"> <Application name="Apache Spark MLlib" version="3.5.4"/> <Timestamp>2025-02-08T10:00:55</Timestamp> </Header> ... ``` After: ``` <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <PMML version="4.4" xmlns="http://www.dmg.org/PMML-4_4" xmlns:data="http://jpmml.org/jpmml-model/InlineTable"> <Header description="k-means clustering"> <Application name="Apache Spark MLlib" version="4.1.0-SNAPSHOT"/> <Timestamp>2025-02-08T11:34:40</Timestamp> </Header> ... ``` **Despite this change, on the one hand, PMML version 4.4 was released as early as November 2019. On the other hand, the upgrade from 4.3 to 4.4 is backward compatible.(Reference: https://dmg.org/pmml/v4-4-1/Changes.html)** ### How was this patch tested? 1. Passed GA; 2. Manually checked the changes after the upgrade. KMeans: ``` import org.apache.spark.ml.clustering.KMeans val dataset = spark.read.format("libsvm").load("data/mllib/sample_kmeans_data.txt") val kmeans = new KMeans().setK(2).setSeed(1L) val model = kmeans.fit(dataset) model.write.format("pmml").save("./kmeans") ``` LinearRegression: ``` import org.apache.spark.ml.regression.LinearRegression val dataset = spark.read.format("libsvm").load("data/mllib/sample_linear_regression_data.txt") val lr = new LinearRegression() val model = lr.fit(dataset) model.write.format("pmml").save("./lr") ``` ### Was this patch authored or co-authored using generative AI tooling? No. Closes apache#49854 from wayneguow/pmml. Authored-by: Wei Guo <[email protected]> Signed-off-by: yangjie01 <[email protected]>
1 parent 54959ab commit cea79dc

12 files changed

+61
-52
lines changed

LICENSE-binary

+2
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,9 @@ javax.xml.bind:jaxb-api https://github.com/javaee/jaxb-v2
515515
Eclipse Distribution License (EDL) 1.0
516516
--------------------------------------
517517
com.sun.istack:istack-commons-runtime
518+
jakarta.activation:jakarta.activation-api
518519
jakarta.xml.bind:jakarta.xml.bind-api
520+
org.glassfish.jaxb:jaxb-core
519521
org.glassfish.jaxb:jaxb-runtime
520522

521523
Eclipse Public License (EPL) 2.0

dev/deps/spark-deps-hadoop-3-hive-2.3

+5-4
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ httpclient/4.5.14//httpclient-4.5.14.jar
101101
httpcore/4.4.16//httpcore-4.4.16.jar
102102
icu4j/76.1//icu4j-76.1.jar
103103
ini4j/0.5.4//ini4j-0.5.4.jar
104-
istack-commons-runtime/3.0.8//istack-commons-runtime-3.0.8.jar
104+
istack-commons-runtime/4.1.2//istack-commons-runtime-4.1.2.jar
105105
ivy/2.5.3//ivy-2.5.3.jar
106106
j2objc-annotations/3.0.0//j2objc-annotations-3.0.0.jar
107107
jackson-annotations/2.18.2//jackson-annotations-2.18.2.jar
@@ -113,21 +113,22 @@ jackson-dataformat-yaml/2.18.2//jackson-dataformat-yaml-2.18.2.jar
113113
jackson-datatype-jsr310/2.18.2//jackson-datatype-jsr310-2.18.2.jar
114114
jackson-mapper-asl/1.9.13//jackson-mapper-asl-1.9.13.jar
115115
jackson-module-scala_2.13/2.18.2//jackson-module-scala_2.13-2.18.2.jar
116+
jakarta.activation-api/2.1.3//jakarta.activation-api-2.1.3.jar
116117
jakarta.annotation-api/2.1.1//jakarta.annotation-api-2.1.1.jar
117118
jakarta.inject-api/2.0.1//jakarta.inject-api-2.0.1.jar
118119
jakarta.servlet-api/5.0.0//jakarta.servlet-api-5.0.0.jar
119120
jakarta.validation-api/3.0.2//jakarta.validation-api-3.0.2.jar
120121
jakarta.ws.rs-api/3.0.0//jakarta.ws.rs-api-3.0.0.jar
121-
jakarta.xml.bind-api/2.3.2//jakarta.xml.bind-api-2.3.2.jar
122+
jakarta.xml.bind-api/4.0.2//jakarta.xml.bind-api-4.0.2.jar
122123
janino/3.1.9//janino-3.1.9.jar
123124
java-diff-utils/4.15//java-diff-utils-4.15.jar
124125
java-xmlbuilder/1.2//java-xmlbuilder-1.2.jar
125126
javassist/3.30.2-GA//javassist-3.30.2-GA.jar
126127
javax.jdo/3.2.0-m3//javax.jdo-3.2.0-m3.jar
127128
javax.servlet-api/4.0.1//javax.servlet-api-4.0.1.jar
128129
javolution/5.5.1//javolution-5.5.1.jar
129-
jaxb-api/2.2.11//jaxb-api-2.2.11.jar
130-
jaxb-runtime/2.3.2//jaxb-runtime-2.3.2.jar
130+
jaxb-core/4.0.5//jaxb-core-4.0.5.jar
131+
jaxb-runtime/4.0.5//jaxb-runtime-4.0.5.jar
131132
jcl-over-slf4j/2.0.16//jcl-over-slf4j-2.0.16.jar
132133
jdo-api/3.0.1//jdo-api-3.0.1.jar
133134
jdom2/2.0.6//jdom2-2.0.6.jar

docs/ml-migration-guide.md

+19
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,25 @@ Note that this migration guide describes the items specific to MLlib.
2626
Many items of SQL migration can be applied when migrating MLlib to higher versions for DataFrame-based APIs.
2727
Please refer [Migration Guide: SQL, Datasets and DataFrame](sql-migration-guide.html).
2828

29+
## Upgrading from MLlib 3.5 to 4.0
30+
31+
### Breaking changes
32+
{:.no_toc}
33+
34+
There are no breaking changes.
35+
36+
### Deprecations and changes of behavior
37+
{:.no_toc}
38+
39+
**Deprecations**
40+
41+
There are no deprecations.
42+
43+
**Changes of behavior**
44+
45+
* [SPARK-51132](https://issues.apache.org/jira/browse/SPARK-51132):
46+
The PMML XML schema version of exported PMML format models by [PMML model export](mllib-pmml-model-export.html) has been upgraded from `PMML-4_3` to `PMML-4_4`.
47+
2948
## Upgrading from MLlib 2.4 to 3.0
3049

3150
### Breaking changes

mllib/pom.xml

+4-4
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,6 @@
3434
<url>https://spark.apache.org/</url>
3535

3636
<dependencies>
37-
<dependency>
38-
<groupId>javax.xml.bind</groupId>
39-
<artifactId>jaxb-api</artifactId>
40-
</dependency>
4137
<dependency>
4238
<groupId>org.scala-lang.modules</groupId>
4339
<artifactId>scala-parser-combinators_${scala.binary.version}</artifactId>
@@ -144,6 +140,10 @@
144140
<groupId>org.glassfish.jaxb</groupId>
145141
<artifactId>jaxb-runtime</artifactId>
146142
</dependency>
143+
<dependency>
144+
<groupId>jakarta.xml.bind</groupId>
145+
<artifactId>jakarta.xml.bind-api</artifactId>
146+
</dependency>
147147
<dependency>
148148
<groupId>org.apache.spark</groupId>
149149
<artifactId>spark-tags_${scala.binary.version}</artifactId>

mllib/src/main/scala/org/apache/spark/mllib/pmml/PMMLExportable.scala

+3-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ package org.apache.spark.mllib.pmml
2020
import java.io.{File, OutputStream, StringWriter}
2121
import javax.xml.transform.stream.StreamResult
2222

23-
import org.jpmml.model.JAXBUtil
23+
import org.jpmml.model.JAXBSerializer
2424

2525
import org.apache.spark.SparkContext
2626
import org.apache.spark.annotation.Since
@@ -39,7 +39,8 @@ trait PMMLExportable {
3939
*/
4040
private def toPMML(streamResult: StreamResult): Unit = {
4141
val pmmlModelExport = PMMLModelExportFactory.createPMMLModelExport(this)
42-
JAXBUtil.marshalPMML(pmmlModelExport.getPmml(), streamResult)
42+
val jaxbSerializer = new JAXBSerializer()
43+
jaxbSerializer.marshalPretty(pmmlModelExport.getPmml(), streamResult)
4344
}
4445

4546
/**

mllib/src/main/scala/org/apache/spark/mllib/pmml/export/BinaryClassificationPMMLModelExport.scala

+5-5
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ package org.apache.spark.mllib.pmml.`export`
1919

2020
import scala.{Array => SArray}
2121

22-
import org.dmg.pmml.{DataDictionary, DataField, DataType, FieldName, MiningField,
23-
MiningFunction, MiningSchema, OpType}
22+
import org.dmg.pmml.{DataDictionary, DataField, DataType, MiningField, MiningFunction,
23+
MiningSchema, OpType}
2424
import org.dmg.pmml.regression.{NumericPredictor, RegressionModel, RegressionTable}
2525

2626
import org.apache.spark.mllib.regression.GeneralizedLinearModel
@@ -44,7 +44,7 @@ private[mllib] class BinaryClassificationPMMLModelExport(
4444
pmml.getHeader.setDescription(description)
4545

4646
if (model.weights.size > 0) {
47-
val fields = new SArray[FieldName](model.weights.size)
47+
val fields = new SArray[String](model.weights.size)
4848
val dataDictionary = new DataDictionary
4949
val miningSchema = new MiningSchema
5050
val regressionTableYES = new RegressionTable(model.intercept).setTargetCategory("1")
@@ -67,7 +67,7 @@ private[mllib] class BinaryClassificationPMMLModelExport(
6767
.addRegressionTables(regressionTableYES, regressionTableNO)
6868

6969
for (i <- 0 until model.weights.size) {
70-
fields(i) = FieldName.create("field_" + i)
70+
fields(i) = "field_" + i
7171
dataDictionary.addDataFields(new DataField(fields(i), OpType.CONTINUOUS, DataType.DOUBLE))
7272
miningSchema
7373
.addMiningFields(new MiningField(fields(i))
@@ -76,7 +76,7 @@ private[mllib] class BinaryClassificationPMMLModelExport(
7676
}
7777

7878
// add target field
79-
val targetField = FieldName.create("target")
79+
val targetField = "target"
8080
dataDictionary
8181
.addDataFields(new DataField(targetField, OpType.CATEGORICAL, DataType.STRING))
8282
miningSchema

mllib/src/main/scala/org/apache/spark/mllib/pmml/export/GeneralizedLinearPMMLModelExport.scala

+5-5
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ package org.apache.spark.mllib.pmml.`export`
1919

2020
import scala.{Array => SArray}
2121

22-
import org.dmg.pmml.{DataDictionary, DataField, DataType, FieldName, MiningField,
23-
MiningFunction, MiningSchema, OpType}
22+
import org.dmg.pmml.{DataDictionary, DataField, DataType, MiningField, MiningFunction,
23+
MiningSchema, OpType}
2424
import org.dmg.pmml.regression.{NumericPredictor, RegressionModel, RegressionTable}
2525

2626
import org.apache.spark.mllib.regression.GeneralizedLinearModel
@@ -42,7 +42,7 @@ private[mllib] class GeneralizedLinearPMMLModelExport(
4242
pmml.getHeader.setDescription(description)
4343

4444
if (model.weights.size > 0) {
45-
val fields = new SArray[FieldName](model.weights.size)
45+
val fields = new SArray[String](model.weights.size)
4646
val dataDictionary = new DataDictionary
4747
val miningSchema = new MiningSchema
4848
val regressionTable = new RegressionTable(model.intercept)
@@ -53,7 +53,7 @@ private[mllib] class GeneralizedLinearPMMLModelExport(
5353
.addRegressionTables(regressionTable)
5454

5555
for (i <- 0 until model.weights.size) {
56-
fields(i) = FieldName.create("field_" + i)
56+
fields(i) = "field_" + i
5757
dataDictionary.addDataFields(new DataField(fields(i), OpType.CONTINUOUS, DataType.DOUBLE))
5858
miningSchema
5959
.addMiningFields(new MiningField(fields(i))
@@ -62,7 +62,7 @@ private[mllib] class GeneralizedLinearPMMLModelExport(
6262
}
6363

6464
// for completeness add target field
65-
val targetField = FieldName.create("target")
65+
val targetField = "target"
6666
dataDictionary.addDataFields(new DataField(targetField, OpType.CONTINUOUS, DataType.DOUBLE))
6767
miningSchema
6868
.addMiningFields(new MiningField(targetField)

mllib/src/main/scala/org/apache/spark/mllib/pmml/export/KMeansPMMLModelExport.scala

+3-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ package org.apache.spark.mllib.pmml.`export`
2020
import scala.{Array => SArray}
2121

2222
import org.dmg.pmml.{Array, CompareFunction, ComparisonMeasure, DataDictionary, DataField, DataType,
23-
FieldName, MiningField, MiningFunction, MiningSchema, OpType, SquaredEuclidean}
23+
MiningField, MiningFunction, MiningSchema, OpType, SquaredEuclidean}
2424
import org.dmg.pmml.clustering.{Cluster, ClusteringField, ClusteringModel}
2525

2626
import org.apache.spark.mllib.clustering.KMeansModel
@@ -40,7 +40,7 @@ private[mllib] class KMeansPMMLModelExport(model: KMeansModel) extends PMMLModel
4040

4141
if (model.clusterCenters.length > 0) {
4242
val clusterCenter = model.clusterCenters(0)
43-
val fields = new SArray[FieldName](clusterCenter.size)
43+
val fields = new SArray[String](clusterCenter.size)
4444
val dataDictionary = new DataDictionary
4545
val miningSchema = new MiningSchema
4646
val comparisonMeasure = new ComparisonMeasure()
@@ -55,7 +55,7 @@ private[mllib] class KMeansPMMLModelExport(model: KMeansModel) extends PMMLModel
5555
.setNumberOfClusters(model.clusterCenters.length)
5656

5757
for (i <- 0 until clusterCenter.size) {
58-
fields(i) = FieldName.create("field_" + i)
58+
fields(i) = "field_" + i
5959
dataDictionary.addDataFields(new DataField(fields(i), OpType.CONTINUOUS, DataType.DOUBLE))
6060
miningSchema
6161
.addMiningFields(new MiningField(fields(i))

mllib/src/main/scala/org/apache/spark/mllib/pmml/export/PMMLModelExport.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import java.util.Locale
2323

2424
import scala.beans.BeanProperty
2525

26-
import org.dmg.pmml.{Application, Header, PMML, Timestamp}
26+
import org.dmg.pmml.{Application, Header, PMML, Timestamp, Version}
2727

2828
private[mllib] trait PMMLModelExport {
2929

@@ -44,6 +44,6 @@ private[mllib] trait PMMLModelExport {
4444
val header = new Header()
4545
.setApplication(app)
4646
.setTimestamp(timestamp)
47-
new PMML("4.2", header, null)
47+
new PMML(Version.PMML_4_4.getVersion(), header, null)
4848
}
4949
}

mllib/src/test/scala/org/apache/spark/ml/regression/LinearRegressionSuite.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -1165,7 +1165,7 @@ class LinearRegressionSuite extends MLTest with DefaultReadWriteTest with PMMLRe
11651165
assert(fields(0).getOpType() == OpType.CONTINUOUS)
11661166
val pmmlRegressionModel = pmml.getModels().get(0).asInstanceOf[PMMLRegressionModel]
11671167
val pmmlPredictors = pmmlRegressionModel.getRegressionTables.get(0).getNumericPredictors
1168-
val pmmlWeights = pmmlPredictors.asScala.map(_.getCoefficient()).toList
1168+
val pmmlWeights = pmmlPredictors.asScala.map(_.getCoefficient().doubleValue()).toList
11691169
assert(pmmlWeights(0) ~== model.coefficients(0) relTol 1E-3)
11701170
assert(pmmlWeights(1) ~== model.coefficients(1) relTol 1E-3)
11711171
}

mllib/src/test/scala/org/apache/spark/ml/util/PMMLUtils.scala

+3-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import java.io.ByteArrayInputStream
2020
import java.nio.charset.StandardCharsets
2121

2222
import org.dmg.pmml.PMML
23-
import org.jpmml.model.{JAXBUtil, SAXUtil}
23+
import org.jpmml.model.{JAXBSerializer, SAXUtil}
2424
import org.jpmml.model.filters.ImportFilter
2525

2626
/**
@@ -37,6 +37,7 @@ private[spark] object PMMLUtils {
3737
val transformed = SAXUtil.createFilteredSource(
3838
new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)),
3939
new ImportFilter())
40-
JAXBUtil.unmarshalPMML(transformed)
40+
val jaxbSerializer = new JAXBSerializer()
41+
jaxbSerializer.unmarshal(transformed).asInstanceOf[PMML]
4142
}
4243
}

pom.xml

+9-24
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,7 @@
571571
<dependency>
572572
<groupId>org.jpmml</groupId>
573573
<artifactId>pmml-model</artifactId>
574-
<version>1.4.8</version>
574+
<version>1.7.1</version>
575575
<scope>provided</scope>
576576
<exclusions>
577577
<exclusion>
@@ -599,32 +599,24 @@
599599
<dependency>
600600
<groupId>org.glassfish.jaxb</groupId>
601601
<artifactId>jaxb-runtime</artifactId>
602-
<version>2.3.2</version>
602+
<version>4.0.5</version>
603603
<scope>compile</scope>
604604
<exclusions>
605-
<!-- for now, we only write XML in PMML export, and these can be excluded -->
606-
<exclusion>
607-
<groupId>com.sun.xml.fastinfoset</groupId>
608-
<artifactId>FastInfoset</artifactId>
609-
</exclusion>
610605
<exclusion>
611606
<groupId>org.glassfish.jaxb</groupId>
612607
<artifactId>txw2</artifactId>
613608
</exclusion>
614609
<exclusion>
615-
<groupId>org.jvnet.staxex</groupId>
616-
<artifactId>stax-ex</artifactId>
617-
</exclusion>
618-
<!--
619-
SPARK-27611: Exclude redundant javax.activation implementation, which
620-
conflicts with the existing javax.activation:activation:1.1.1 dependency.
621-
-->
622-
<exclusion>
623-
<groupId>jakarta.activation</groupId>
624-
<artifactId>jakarta.activation-api</artifactId>
610+
<groupId>org.eclipse.angus</groupId>
611+
<artifactId>angus-activation</artifactId>
625612
</exclusion>
626613
</exclusions>
627614
</dependency>
615+
<dependency>
616+
<groupId>jakarta.xml.bind</groupId>
617+
<artifactId>jakarta.xml.bind-api</artifactId>
618+
<version>4.0.2</version>
619+
</dependency>
628620
<dependency>
629621
<groupId>org.apache.commons</groupId>
630622
<artifactId>commons-lang3</artifactId>
@@ -1061,13 +1053,6 @@
10611053
<groupId>org.glassfish.jersey.core</groupId>
10621054
<artifactId>jersey-server</artifactId>
10631055
<version>${jersey.version}</version>
1064-
<!-- SPARK-28765 Unused JDK11-specific dependency -->
1065-
<exclusions>
1066-
<exclusion>
1067-
<groupId>jakarta.xml.bind</groupId>
1068-
<artifactId>jakarta.xml.bind-api</artifactId>
1069-
</exclusion>
1070-
</exclusions>
10711056
</dependency>
10721057
<dependency>
10731058
<groupId>org.glassfish.jersey.core</groupId>

0 commit comments

Comments
 (0)