プログラムにWekaエンジンを組み込むシーンを考える
これまで、Arffをファイルで用意し、それで学習したり、テストをしたりしていました。
ただ、Javaプログラムの中でWekaエンジンを使って分類や予測をする場合、本番データはファイルに保存されない状態で取得することかあります。
このため、ファイルに出力せずArffを定義し、データを挿入する方法を考えてみました。
WekaのAPIサンプルにも同じようなことが書かれていますが、それにコメントをつけた程度のサンプルとなります。
Javaで書いてみました
今回も mini-wekaを使います。また、ログ出力のために、log4jを依存ライブラリーに組み込みます。
実行ディレクトリに log4j2.xml を入れています。
package example20260110;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import weka.core.Attribute;
import weka.core.DenseInstance;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.Utils;
import weka.core.converters.ArffLoader;
import weka.filters.Filter;
import weka.filters.unsupervised.attribute.StringToWordVector;
import weka.filters.unsupervised.instance.SparseToNonSparse;
/**
*
* @author XXX
*/
public class CreateInstances {
static {
System.setProperty("log4j.configurationFile", "log4j2.xml");
}
// log4jのlogger
private static final Logger logger = (Logger) LogManager.getLogger(CreateInstances.class);
public static void main(String[] args) throws Exception {
logger.debug("開始");
logger.debug("attributesは属性の一覧");
ArrayList<Attribute> attributes = new ArrayList<>();
logger.debug("定義します");
logger.debug("0番目の属性は:IDという名前。数値。Double型が入る");
attributes.add(new Attribute("ID"));
logger.debug("1番目の属性:クラス分けなので、まず定義する");
String[] attr2Classes = "日,月,火,水,木,金,土".split(",");
ArrayList<String> attVals = new ArrayList<>();
attVals.addAll(Arrays.asList(attr2Classes));
logger.debug("1番目の属性:曜日という名前で上記で定義したクラスとする");
attributes.add(new Attribute("曜日", attVals));
logger.debug("2番目の属性:テキスト1 という名前のString型");
attributes.add(new Attribute("テキスト1", (ArrayList<String>) null));
logger.debug("3番目の属性:誕生日という名前の日付型");
attributes.add(new Attribute("誕生日", "yyyy/MM/dd"));
logger.debug("Instacesに収める。説明はMyInstancesでデータ数はまだゼロ");
Instances newInstances = new Instances("MyInstances", attributes, 0);
logger.debug("定義されたInstances\n" + newInstances);
logger.debug("データを入れます");
logger.debug("0番目のAttr:IDは1.0");
double[] vals = new double[newInstances.numAttributes()];
vals[0] = 1.0;
logger.debug("1番目のAttr:曜日は月");
vals[1] = attVals.indexOf("月");
logger.debug("2番目のAttr:テキスト1は「テスト の データ」");
vals[2] = newInstances.attribute(2).addStringValue("テスト");
logger.debug("3番目のAttr:誕生日は「1960/02/03」");
vals[3] = newInstances.attribute(3).parseDate("1960/02/03");
logger.debug("dataに格納する。1.0は重み");
var instance = new DenseInstance(1.0, vals);
newInstances.add(instance);
logger.debug("完成したデータ\n" + newInstances);
//--
logger.debug("ARFFLoader使い新しいInstancesを作ります");
ArffLoader af = new ArffLoader();
try (InputStream in = new ByteArrayInputStream(newInstances.toString().getBytes())) {
af.setSource(in);
}
Instances arffloaderInstances = af.getDataSet();
logger.debug("説明はarffloaderInstancesData");
arffloaderInstances.setRelationName("arffloaderInstancesData");
arffloaderInstances.clear();
logger.debug("内容をクリアしたデータ\n" + arffloaderInstances);
//--
logger.debug("CSV文字列からデータを抽出して入れます");
String myCsv = "1,水,文字列 の テスト,1970/11/14";
logger.debug("データ:" + myCsv);
String[] mydata = myCsv.split(",");
int arffloaderNumArrriButes = arffloaderInstances.numAttributes();
logger.debug("dataのnumAttributes:" + arffloaderNumArrriButes);
double[] newvals = new double[arffloaderInstances.numAttributes()];
for (int i = 0; i < arffloaderNumArrriButes; i++) {
logger.debug(i + "番目:" + mydata[i]);
// ポイント。Date型はnumeric型ともみなされるため最初に確認する
if (arffloaderInstances.attribute(i).isDate()) {
logger.debug("Dateです");
newvals[i] = arffloaderInstances.attribute(i).parseDate(mydata[i]);
continue;
}
if (arffloaderInstances.attribute(i).isNumeric()) {
logger.debug("Numericです");
newvals[i] = Double.parseDouble(mydata[i]);
continue;
}
if (arffloaderInstances.attribute(i).isNominal()) {
logger.debug("Nominalです");
newvals[i] = arffloaderInstances.attribute(i).indexOfValue(mydata[i]);
continue;
}
if (arffloaderInstances.attribute(i).isString()) {
logger.debug("Stringです");
newvals[i] = arffloaderInstances.attribute(i).addStringValue(mydata[i]);
continue;
}
logger.debug("欠損値とします");
newvals[i] = Utils.missingValue();
}
Instance newinstance = new DenseInstance(1.0, newvals);
arffloaderInstances.add(newinstance);
logger.debug("完成したArffデータ\n" + arffloaderInstances);
}
}
実行結果
2026/01/10 21:18:50.190 DEBUG - 開始
2026/01/10 21:18:50.191 DEBUG - attributesは属性の一覧
2026/01/10 21:18:50.191 DEBUG - 定義します
2026/01/10 21:18:50.191 DEBUG - 0番目の属性は:IDという名前。数値。Double型が入る
2026/01/10 21:18:50.192 DEBUG - 1番目の属性:クラス分けなので、まず定義する
2026/01/10 21:18:50.192 DEBUG - 1番目の属性:曜日という名前で上記で定義したクラスとする
2026/01/10 21:18:50.192 DEBUG - 2番目の属性:テキスト1 という名前のString型
2026/01/10 21:18:50.192 DEBUG - 3番目の属性:誕生日という名前の日付型
2026/01/10 21:18:50.196 DEBUG - Instacesに収める。説明はMyInstancesでデータ数はまだゼロ
2026/01/10 21:18:50.200 DEBUG - 定義されたInstances
@relation MyInstances
@attribute ID numeric
@attribute 曜日 {日,月,火,水,木,金,土}
@attribute テキスト1 string
@attribute 誕生日 date yyyy/MM/dd
@data
2026/01/10 21:18:50.200 DEBUG - データを入れます
2026/01/10 21:18:50.200 DEBUG - 0番目のAttr:IDは1.0
2026/01/10 21:18:50.200 DEBUG - 1番目のAttr:曜日は月
2026/01/10 21:18:50.200 DEBUG - 2番目のAttr:テキスト1は「テスト の データ」
2026/01/10 21:18:50.200 DEBUG - 3番目のAttr:誕生日は「1960/02/03」
2026/01/10 21:18:50.200 DEBUG - dataに格納する。1.0は重み
2026/01/10 21:18:50.202 DEBUG - 完成したデータ
@relation MyInstances
@attribute ID numeric
@attribute 曜日 {日,月,火,水,木,金,土}
@attribute テキスト1 string
@attribute 誕生日 date yyyy/MM/dd
@data
1,月,テスト,1960/02/03
2026/01/10 21:18:50.202 DEBUG - ARFFLoader使い新しいInstancesを作ります
2026/01/10 21:18:50.204 DEBUG - 説明はarffloaderInstancesData
2026/01/10 21:18:50.204 DEBUG - 内容をクリアしたデータ
@relation arffloaderInstancesData
@attribute ID numeric
@attribute 曜日 {日,月,火,水,木,金,土}
@attribute テキスト1 string
@attribute 誕生日 date yyyy/MM/dd
@data
2026/01/10 21:18:50.204 DEBUG - CSV文字列からデータを抽出して入れます
2026/01/10 21:18:50.205 DEBUG - データ:1,水,文字列 の テスト,1970/11/14
2026/01/10 21:18:50.205 DEBUG - dataのnumAttributes:4
2026/01/10 21:18:50.206 DEBUG - 0番目:1
2026/01/10 21:18:50.206 DEBUG - Numericです
2026/01/10 21:18:50.206 DEBUG - 1番目:水
2026/01/10 21:18:50.206 DEBUG - Nominalです
2026/01/10 21:18:50.206 DEBUG - 2番目:文字列 の テスト
2026/01/10 21:18:50.206 DEBUG - Stringです
2026/01/10 21:18:50.206 DEBUG - 3番目:1970/11/14
2026/01/10 21:18:50.206 DEBUG - Dateです
2026/01/10 21:18:50.207 DEBUG - 完成したArffデータ
@relation arffloaderInstancesData
@attribute ID numeric
@attribute 曜日 {日,月,火,水,木,金,土}
@attribute テキスト1 string
@attribute 誕生日 date yyyy/MM/dd
@data
1,水,'文字列 の テスト',1970/11/14
感想
- ポイントとしてソースに書いてありますが、Data型の判別に苦労しました
- 作りたいArffの属性定義は、Yamlデータなどで書いておき、それを読ませることも面白いかもしれません
- ただ、空データのArffを手で書いて、読み込ませることが現実的かもしれません