-
Notifications
You must be signed in to change notification settings - Fork 214
PPL Integration - Add implementation for KMeans algorithm #407
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 1 commit
08ae4a2
979279a
e0026cf
cda9360
def9e40
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 |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| package org.opensearch.sql.ast.tree; | ||
|
|
||
| import com.google.common.collect.ImmutableList; | ||
| import java.util.List; | ||
| import lombok.AllArgsConstructor; | ||
| import lombok.EqualsAndHashCode; | ||
| import lombok.Getter; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.Setter; | ||
| import lombok.ToString; | ||
| import org.opensearch.sql.ast.AbstractNodeVisitor; | ||
| import org.opensearch.sql.ast.expression.Argument; | ||
|
|
||
| @Getter | ||
| @Setter | ||
| @ToString | ||
| @EqualsAndHashCode(callSuper = true) | ||
| @RequiredArgsConstructor | ||
| @AllArgsConstructor | ||
| public class Kmeans extends UnresolvedPlan { | ||
| private UnresolvedPlan child; | ||
|
|
||
| private final List<Argument> options; | ||
|
|
||
| @Override | ||
| public UnresolvedPlan attach(UnresolvedPlan child) { | ||
| this.child = child; | ||
| return this; | ||
| } | ||
|
|
||
| @Override | ||
| public <T, C> T accept(AbstractNodeVisitor<T, C> nodeVisitor, C context) { | ||
| return nodeVisitor.visitKmeans(this, context); | ||
| } | ||
|
|
||
| @Override | ||
| public List<UnresolvedPlan> getChild() { | ||
| return ImmutableList.of(this.child); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| package org.opensearch.sql.planner.logical; | ||
|
|
||
| import java.util.Collections; | ||
| import java.util.List; | ||
| import lombok.EqualsAndHashCode; | ||
| import lombok.Getter; | ||
| import lombok.ToString; | ||
| import org.opensearch.sql.ast.expression.Argument; | ||
|
|
||
| /** | ||
| * ml-commons logical plan. | ||
| */ | ||
| @Getter | ||
| @ToString | ||
| @EqualsAndHashCode(callSuper = true) | ||
| public class LogicalMLCommons extends LogicalPlan { | ||
| private final String algorithm; | ||
|
|
||
| private final List<Argument> arguments; | ||
|
|
||
| /** | ||
| * Constructor of LogicalMLCommons. | ||
| * @param child child logical plan | ||
| * @param algorithm algorithm name | ||
| * @param arguments arguments of the algorithm | ||
| */ | ||
| public LogicalMLCommons(LogicalPlan child, String algorithm, | ||
| List<Argument> arguments) { | ||
| super(Collections.singletonList(child)); | ||
| this.algorithm = algorithm; | ||
| this.arguments = arguments; | ||
| } | ||
|
|
||
| @Override | ||
| public <R, C> R accept(LogicalPlanNodeVisitor<R, C> visitor, C context) { | ||
| return visitor.visitMLCommons(this, context); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,155 @@ | ||
| package org.opensearch.sql.planner.physical; | ||
|
|
||
| import com.google.common.collect.ImmutableMap; | ||
| import java.util.Collections; | ||
| import java.util.HashMap; | ||
| import java.util.Iterator; | ||
| import java.util.LinkedList; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.concurrent.TimeUnit; | ||
| import lombok.EqualsAndHashCode; | ||
| import lombok.Getter; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.opensearch.ml.client.MachineLearningClient; | ||
| import org.opensearch.ml.common.dataframe.ColumnMeta; | ||
| import org.opensearch.ml.common.dataframe.ColumnValue; | ||
| import org.opensearch.ml.common.dataframe.DataFrame; | ||
| import org.opensearch.ml.common.dataframe.DataFrameBuilder; | ||
| import org.opensearch.ml.common.dataframe.Row; | ||
| import org.opensearch.ml.common.dataset.DataFrameInputDataset; | ||
| import org.opensearch.ml.common.parameter.FunctionName; | ||
| import org.opensearch.ml.common.parameter.KMeansParams; | ||
| import org.opensearch.ml.common.parameter.MLAlgoParams; | ||
| import org.opensearch.ml.common.parameter.MLInput; | ||
| import org.opensearch.ml.common.parameter.MLPredictionOutput; | ||
| import org.opensearch.sql.ast.expression.Argument; | ||
| import org.opensearch.sql.data.model.ExprDoubleValue; | ||
| import org.opensearch.sql.data.model.ExprIntegerValue; | ||
| import org.opensearch.sql.data.model.ExprStringValue; | ||
| import org.opensearch.sql.data.model.ExprTupleValue; | ||
| import org.opensearch.sql.data.model.ExprValue; | ||
|
|
||
| /** | ||
| * ml-commons Physical operator to call machine learning interface to get results for | ||
| * algorithm execution. | ||
| */ | ||
| @RequiredArgsConstructor | ||
| @EqualsAndHashCode(callSuper = false) | ||
| public class MLCommonsOperator extends PhysicalPlan { | ||
|
jackiehanyang marked this conversation as resolved.
|
||
| @Getter | ||
| private final PhysicalPlan input; | ||
|
|
||
| @Getter | ||
| private final String algorithm; | ||
|
|
||
| @Getter | ||
| private final List<Argument> arguments; | ||
|
|
||
| @Getter | ||
| private final MachineLearningClient machineLearningClient; | ||
|
|
||
| @EqualsAndHashCode.Exclude | ||
| private Iterator<ExprValue> iterator; | ||
|
|
||
| @Override | ||
| public void open() { | ||
| super.open(); | ||
| DataFrame inputDataFrame = generateInputDataset(); | ||
| MLAlgoParams mlAlgoParams = convertArgumentToMLParameter(arguments.get(0), algorithm); | ||
|
Collaborator
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. Does it make sense to parse Argument in Analyzer?
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 prefer to put Argument parsing logic in MLCommonsOperator, because I don't see any argument parsing logic in Analyzer for other logical plans. Also, it looks like the main purpose of Analyzer class is to construct the logical plan, so I prefer to leave the argument parsing work for the actual operator, which is MLCommonsOperator.
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. If we parse Argument in Analyzer, it will create dependencies in Core module as Analyzer class sits in Core module. |
||
| MLInput mlinput = MLInput.builder() | ||
| .algorithm(FunctionName.valueOf(algorithm.toUpperCase())) | ||
| .parameters(mlAlgoParams) | ||
| .inputDataset(new DataFrameInputDataset(inputDataFrame)) | ||
| .build(); | ||
| MLPredictionOutput predictionResult = (MLPredictionOutput) machineLearningClient | ||
| .trainAndPredict(mlinput) | ||
| .actionGet(30, TimeUnit.SECONDS); | ||
| Iterator<Row> inputRowIter = inputDataFrame.iterator(); | ||
| Iterator<Row> resultRowIter = predictionResult.getPredictionResult().iterator(); | ||
| iterator = new Iterator<ExprValue>() { | ||
| @Override | ||
| public boolean hasNext() { | ||
| return inputRowIter.hasNext(); | ||
| } | ||
|
|
||
| @Override | ||
| public ExprValue next() { | ||
| ImmutableMap.Builder<String, ExprValue> resultBuilder = new ImmutableMap.Builder<>(); | ||
| resultBuilder.putAll(convertRowIntoExprValue(inputDataFrame.columnMetas(), | ||
| inputRowIter.next())); | ||
| resultBuilder.putAll(convertRowIntoExprValue( | ||
| predictionResult.getPredictionResult().columnMetas(), | ||
| resultRowIter.next())); | ||
| return ExprTupleValue.fromExprValueMap(resultBuilder.build()); | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| @Override | ||
| public <R, C> R accept(PhysicalPlanNodeVisitor<R, C> visitor, C context) { | ||
| return visitor.visitMLCommons(this, context); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean hasNext() { | ||
| return iterator.hasNext(); | ||
| } | ||
|
|
||
| @Override | ||
| public ExprValue next() { | ||
| return iterator.next(); | ||
| } | ||
|
|
||
| @Override | ||
| public List<PhysicalPlan> getChild() { | ||
| return Collections.singletonList(input); | ||
| } | ||
|
|
||
| protected MLAlgoParams convertArgumentToMLParameter(Argument argument, String algorithm) { | ||
| switch (FunctionName.valueOf(algorithm.toUpperCase())) { | ||
| case KMEANS: | ||
| return KMeansParams.builder().centroids((Integer) argument.getValue().getValue()).build(); | ||
|
|
||
| default: | ||
| throw new IllegalArgumentException("unsupported argument type:" | ||
| + argument.getValue().getType()); | ||
| } | ||
| } | ||
|
|
||
| private Map<String, ExprValue> convertRowIntoExprValue(ColumnMeta[] columnMetas, Row row) { | ||
| ImmutableMap.Builder<String, ExprValue> resultBuilder = new ImmutableMap.Builder<>(); | ||
| for (int i = 0; i < columnMetas.length; i++) { | ||
| ColumnValue columnValue = row.getValue(i); | ||
| String resultKeyName = columnMetas[i].getName(); | ||
| switch (columnValue.columnType()) { | ||
| case INTEGER: | ||
|
jackiehanyang marked this conversation as resolved.
|
||
| resultBuilder.put(resultKeyName, new ExprIntegerValue(columnValue.intValue())); | ||
| break; | ||
| case DOUBLE: | ||
| resultBuilder.put(resultKeyName, new ExprDoubleValue(columnValue.doubleValue())); | ||
| break; | ||
| case STRING: | ||
| resultBuilder.put(resultKeyName, new ExprStringValue(columnValue.stringValue())); | ||
| break; | ||
| default: | ||
| break; | ||
| } | ||
| } | ||
| return resultBuilder.build(); | ||
| } | ||
|
|
||
| private DataFrame generateInputDataset() { | ||
| List<Map<String, Object>> inputData = new LinkedList<>(); | ||
| while (input.hasNext()) { | ||
| Map<String, Object> items = new HashMap<>(); | ||
| input.next().tupleValue().forEach((key, value) -> { | ||
|
jackiehanyang marked this conversation as resolved.
Outdated
|
||
| items.put(key, value.value()); | ||
| }); | ||
|
Collaborator
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. Could you use ExprTulpleValue::value()?
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'm not quite sure what you mean. I don't think we are able to reference ExprTulpleValue here? Could you elaborate more? |
||
| inputData.add(items); | ||
| } | ||
|
|
||
| return DataFrameBuilder.load(inputData); | ||
| } | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.