特徴量抽出と変換 - RDDベースのAPI
TF-IDF
注記 TF-IDFに関するMLユーザーガイドで詳述されているデータフレームベースのAPIの使用をお勧めします。
単語頻度-逆文書頻度 (TF-IDF) は、コーパス内の文書に対する単語の重要性を反映するためにテキストマイニングで広く使用されている特徴量ベクトル化手法です。単語を t
、文書を d
、コーパスを D
で表します。単語頻度 TF(t,d)
は、単語 t
が文書 d
に出現する回数であり、文書頻度 DF(t,D)
は、単語 t
を含む文書の数です。単語頻度のみを使用して重要度を測定すると、「a」、「the」、「of」など、非常に頻繁に出現するが文書に関する情報はほとんどない単語を過度に強調してしまう可能性があります。単語がコーパス全体で非常に頻繁に出現する場合、それは特定の文書に関する特別な情報を持ち運んでいないことを意味します。逆文書頻度は、単語が提供する情報の量の尺度です: IDF(t,D)=log|D|+1DF(t,D)+1,
ここで、|D|
はコーパス内の文書の総数です。対数が使用されるため、単語がすべての文書に出現する場合、そのIDF値は0になります。コーパス外の単語でゼロ除算を回避するために、スムージング項が適用されることに注意してください。TF-IDF尺度は、単にTFとIDFの積です: TFIDF(t,d,D)=TF(t,d)⋅IDF(t,D).
単語頻度と文書頻度の定義には、いくつかのバリエーションがあります。 spark.mllib
では、TFとIDFを分離して柔軟性を高めています。
単語頻度の実装では、ハッシュトリックを利用しています。生の特徴量は、ハッシュ関数を適用することによってインデックス (単語) にマッピングされます。次に、マッピングされたインデックスに基づいて単語頻度が計算されます。このアプローチでは、大規模なコーパスでは高価になる可能性のあるグローバルな単語からインデックスへのマップを計算する必要がなくなりますが、異なる生の特徴量がハッシュ後に同じ単語になる可能性のあるハッシュ衝突が発生する可能性があります。衝突の可能性を減らすために、ターゲットの特徴量の次元、つまりハッシュテーブルのバケット数を増やすことができます。デフォルトの特徴量の次元は 220=1,048,576
です。
注記: spark.mllib
は、テキストセグメンテーション用のツールを提供していません。 Stanford NLP Group と scalanlp/chalk を参照してください。
TFとIDFは、HashingTF と IDF に実装されています。 HashingTF
は、入力としてリストのRDDを受け取ります。各レコードは、文字列または他の型の反復可能オブジェクトにすることができます。
APIの詳細については、HashingTF
Pythonドキュメントを参照してください。
from pyspark.mllib.feature import HashingTF, IDF
# Load documents (one per line).
documents = sc.textFile("data/mllib/kmeans_data.txt").map(lambda line: line.split(" "))
hashingTF = HashingTF()
tf = hashingTF.transform(documents)
# While applying HashingTF only needs a single pass to the data, applying IDF needs two passes:
# First to compute the IDF vector and second to scale the term frequencies by IDF.
tf.cache()
idf = IDF().fit(tf)
tfidf = idf.transform(tf)
# spark.mllib's IDF implementation provides an option for ignoring terms
# which occur in less than a minimum number of documents.
# In such cases, the IDF for these terms is set to 0.
# This feature can be used by passing the minDocFreq value to the IDF constructor.
idfIgnore = IDF(minDocFreq=2).fit(tf)
tfidfIgnore = idfIgnore.transform(tf)
TFとIDFは、HashingTF と IDF に実装されています。 HashingTF
は、RDD[Iterable[_]]
を入力として受け取ります。各レコードは、文字列または他の型の反復可能オブジェクトにすることができます。
APIの詳細については、HashingTF
Scalaドキュメントを参照してください。
import org.apache.spark.mllib.feature.{HashingTF, IDF}
import org.apache.spark.mllib.linalg.Vector
import org.apache.spark.rdd.RDD
// Load documents (one per line).
val documents: RDD[Seq[String]] = sc.textFile("data/mllib/kmeans_data.txt")
.map(_.split(" ").toSeq)
val hashingTF = new HashingTF()
val tf: RDD[Vector] = hashingTF.transform(documents)
// While applying HashingTF only needs a single pass to the data, applying IDF needs two passes:
// First to compute the IDF vector and second to scale the term frequencies by IDF.
tf.cache()
val idf = new IDF().fit(tf)
val tfidf: RDD[Vector] = idf.transform(tf)
// spark.mllib IDF implementation provides an option for ignoring terms which occur in less than
// a minimum number of documents. In such cases, the IDF for these terms is set to 0.
// This feature can be used by passing the minDocFreq value to the IDF constructor.
val idfIgnore = new IDF(minDocFreq = 2).fit(tf)
val tfidfIgnore: RDD[Vector] = idfIgnore.transform(tf)
Word2Vec
Word2Vec は、単語の分散ベクトル表現を計算します。分散表現の主な利点は、類似した単語がベクトル空間で近いため、新しいパターンへの一般化が容易になり、モデル推定がより堅牢になることです。分散ベクトル表現は、固有表現認識、あいまいさ解消、解析、タグ付け、機械翻訳など、多くの自然言語処理アプリケーションで役立つことが示されています。
モデル
Word2Vecの実装では、スキップグラムモデルを使用しました。スキップグラムのトレーニング目標は、同じ文脈でその文脈をうまく予測できる単語ベクトル表現を学習することです。数学的には、トレーニング単語のシーケンス w1,w2,…,wT
が与えられた場合、スキップグラムモデルの目標は、平均対数尤度 1TT∑t=1j=k∑j=−klogp(wt+j|wt)
を最大化することであり、ここで k はトレーニングウィンドウのサイズです。
スキップグラムモデルでは、すべての単語 w は、単語およびコンテキストとしての w のベクトル表現である2つのベクトル uw と vw に関連付けられています。単語 wj が与えられた場合に単語 wi を正しく予測する確率は、ソフトマックスモデルによって決定され、 p(wi|wj)=exp(u⊤wivwj)∑Vl=1exp(u⊤lvwj)
であり、ここで V は語彙サイズです。
ソフトマックスを使用したスキップグラムモデルは、logp(wi|wj) を計算するコストが V に比例するため、高価であり、これは簡単に数百万のオーダーになる可能性があります。Word2Vecのトレーニングを高速化するために、階層的ソフトマックスを使用しました。これにより、logp(wi|wj) の計算の複雑さが O(log(V)) に減少しました。
例
以下の例は、テキストファイルを読み込み、Seq[String]
のRDDとして解析し、Word2Vec
インスタンスを構築し、入力データで Word2VecModel
を適合させる方法を示しています。最後に、指定された単語の上位40のシノニムを表示します。この例を実行するには、まず text8 データをダウンロードし、 preferredディレクトリに解凍します。ここでは、解凍されたファイルが text8
であり、sparkシェルを実行するのと同じディレクトリにあると仮定します。
APIの詳細については、Word2Vec
Pythonドキュメントを参照してください。
from pyspark.mllib.feature import Word2Vec
inp = sc.textFile("data/mllib/sample_lda_data.txt").map(lambda row: row.split(" "))
word2vec = Word2Vec()
model = word2vec.fit(inp)
synonyms = model.findSynonyms('1', 5)
for word, cosine_distance in synonyms:
print("{}: {}".format(word, cosine_distance))
APIの詳細については、Word2Vec
Scalaドキュメントを参照してください。
import org.apache.spark.mllib.feature.{Word2Vec, Word2VecModel}
val input = sc.textFile("data/mllib/sample_lda_data.txt").map(line => line.split(" ").toSeq)
val word2vec = new Word2Vec()
val model = word2vec.fit(input)
val synonyms = model.findSynonyms("1", 5)
for((synonym, cosineSimilarity) <- synonyms) {
println(s"$synonym $cosineSimilarity")
}
// Save and load model
model.save(sc, "myModelPath")
val sameModel = Word2VecModel.load(sc, "myModelPath")
StandardScaler
トレーニングセットのサンプルの列の要約統計量を使用して、単位分散にスケーリングしたり、平均を削除したりすることで、特徴量を標準化します。これは、非常に一般的な前処理手順です。
たとえば、サポートベクターマシンのRBFカーネルや、L1およびL2正則化線形モデルは、通常、すべての特徴量が単位分散および/またはゼロ平均の場合に、より適切に機能します。
標準化は、最適化プロセス中の収束率を向上させることができ、また、分散が非常に大きい特徴量がモデルのトレーニング中に過度に大きな影響を与えるのを防ぎます。
モデルフィッティング
StandardScaler
には、コンストラクターに次のパラメーターがあります
withMean
デフォルトではFalseです。スケーリング前にデータを平均でセンタリングします。密な出力が構築されるため、スパース入力に適用する場合は注意してください。withStd
デフォルトではTrueです。データを単位標準偏差にスケーリングします。
StandardScaler
には、fit
メソッドが用意されています。このメソッドは、RDD[Vector]
の入力を取得し、要約統計量を学習してから、StandardScaler
の構成方法に応じて、入力データセットを単位標準偏差および/またはゼロ平均特徴量に変換できるモデルを返します。
このモデルは、Vector
に標準化を適用して変換された Vector
を生成したり、RDD[Vector]
に適用して変換された RDD[Vector]
を生成したりできる VectorTransformer
を実装しています。
特徴量の分散がゼロの場合、その特徴量の Vector
にデフォルトの 0.0
値が返されることに注意してください。
例
以下の例は、libsvm形式でデータセットを読み込み、新しい特徴量が単位標準偏差および/またはゼロ平均を持つように特徴量を標準化する方法を示しています。
APIの詳細については、StandardScaler
Pythonドキュメントを参照してください。
from pyspark.mllib.feature import StandardScaler
from pyspark.mllib.linalg import Vectors
from pyspark.mllib.util import MLUtils
data = MLUtils.loadLibSVMFile(sc, "data/mllib/sample_libsvm_data.txt")
label = data.map(lambda x: x.label)
features = data.map(lambda x: x.features)
scaler1 = StandardScaler().fit(features)
scaler2 = StandardScaler(withMean=True, withStd=True).fit(features)
# data1 will be unit variance.
data1 = label.zip(scaler1.transform(features))
# data2 will be unit variance and zero mean.
data2 = label.zip(scaler2.transform(features.map(lambda x: Vectors.dense(x.toArray()))))
API の詳細については、StandardScaler
Scala ドキュメントを参照してください。
import org.apache.spark.mllib.feature.{StandardScaler, StandardScalerModel}
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.mllib.util.MLUtils
val data = MLUtils.loadLibSVMFile(sc, "data/mllib/sample_libsvm_data.txt")
val scaler1 = new StandardScaler().fit(data.map(x => x.features))
val scaler2 = new StandardScaler(withMean = true, withStd = true).fit(data.map(x => x.features))
// scaler3 is an identical model to scaler2, and will produce identical transformations
val scaler3 = new StandardScalerModel(scaler2.std, scaler2.mean)
// data1 will be unit variance.
val data1 = data.map(x => (x.label, scaler1.transform(x.features)))
// data2 will be unit variance and zero mean.
val data2 = data.map(x => (x.label, scaler2.transform(Vectors.dense(x.features.toArray))))
Normalizer
Normalizer は、個々のサンプルの Lp ノルムが 1 になるようにスケールします。これは、テキスト分類やクラスタリングで一般的な操作です。たとえば、2 つの L2 正規化された TF-IDF ベクトルのドット積は、ベクトルのコサイン類似度です。
Normalizer
は、コンストラクタに次のパラメータを持ちます
p
Lp 空間での正規化。デフォルトは p=2 です。
Normalizer
は、VectorTransformer
を実装しており、Vector
に正規化を適用して変換された Vector
を生成したり、RDD[Vector]
に適用して変換された RDD[Vector]
を生成したりできます。
入力のノルムがゼロの場合、入力ベクトルが返されることに注意してください。
例
以下の例は、libsvm 形式でデータセットを読み込み、L2 ノルムと L∞ ノルムで特徴量を正規化する方法を示しています。
API の詳細については、Normalizer
Python ドキュメントを参照してください。
from pyspark.mllib.feature import Normalizer
from pyspark.mllib.util import MLUtils
data = MLUtils.loadLibSVMFile(sc, "data/mllib/sample_libsvm_data.txt")
labels = data.map(lambda x: x.label)
features = data.map(lambda x: x.features)
normalizer1 = Normalizer()
normalizer2 = Normalizer(p=float("inf"))
# Each sample in data1 will be normalized using $L^2$ norm.
data1 = labels.zip(normalizer1.transform(features))
# Each sample in data2 will be normalized using $L^\infty$ norm.
data2 = labels.zip(normalizer2.transform(features))
API の詳細については、Normalizer
Scala ドキュメントを参照してください。
import org.apache.spark.mllib.feature.Normalizer
import org.apache.spark.mllib.util.MLUtils
val data = MLUtils.loadLibSVMFile(sc, "data/mllib/sample_libsvm_data.txt")
val normalizer1 = new Normalizer()
val normalizer2 = new Normalizer(p = Double.PositiveInfinity)
// Each sample in data1 will be normalized using $L^2$ norm.
val data1 = data.map(x => (x.label, normalizer1.transform(x.features)))
// Each sample in data2 will be normalized using $L^\infty$ norm.
val data2 = data.map(x => (x.label, normalizer2.transform(x.features)))
ChiSqSelector
特徴量選択は、モデル構築に使用する関連する特徴量を特定しようとします。特徴量空間のサイズを縮小することで、速度と統計的学習の両方の動作を改善できます。
ChiSqSelector
は、カイ二乗特徴量選択を実装しています。カテゴリ特徴量を持つラベル付きデータに対して動作します。ChiSqSelector は、カイ二乗独立性検定を使用して、どの特徴量を選択するかを決定します。5 つの選択方法をサポートしています:numTopFeatures
、percentile
、fpr
、fdr
、fwe
numTopFeatures
は、カイ二乗検定に従って、上位の固定数の特徴量を選択します。これは、最も予測力の高い特徴量を生成することに似ています。percentile
はnumTopFeatures
と似ていますが、固定数ではなく、すべての特徴量の割合を選択します。fpr
は、p 値がしきい値を下回るすべての機能を選択し、選択の偽陽性率を制御します。fdr
は、Benjamini-Hochberg 手順を使用して、偽発見率がしきい値を下回るすべての機能を選択します。fwe
は、p 値がしきい値を下回るすべての機能を選択します。しきい値は 1/numFeatures でスケールされるため、選択のファミリーワイズエラー率を制御します。
デフォルトでは、選択方法は numTopFeatures
で、デフォルトの上位機能の数は 50 に設定されています。ユーザーは setSelectorType
を使用して選択方法を選択できます。
選択する特徴量の数は、保留中の検証セットを使用して調整できます。
モデルフィッティング
fit
メソッドは、カテゴリ特徴量を持つ RDD[LabeledPoint]
を入力として受け取り、要約統計量を学習し、入力データセットを縮小された特徴量空間に変換できる ChiSqSelectorModel
を返します。ChiSqSelectorModel
は、Vector
に適用して縮小された Vector
を生成したり、RDD[Vector]
に適用して縮小された RDD[Vector]
を生成したりできます。
ユーザーは、選択された特徴量インデックスの配列(昇順でソートする必要があります)を提供することにより、ChiSqSelectorModel
を手動で構築することもできます。
例
次の例は、ChiSqSelector の基本的な使用方法を示しています。使用されるデータセットは、各特徴量について 0 から 255 まで変化するグレースケール値で構成される特徴量行列を持っています。
API の詳細については、ChiSqSelector
Scala ドキュメントを参照してください。
import org.apache.spark.mllib.feature.ChiSqSelector
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.mllib.regression.LabeledPoint
import org.apache.spark.mllib.util.MLUtils
// Load some data in libsvm format
val data = MLUtils.loadLibSVMFile(sc, "data/mllib/sample_libsvm_data.txt")
// Discretize data in 16 equal bins since ChiSqSelector requires categorical features
// Even though features are doubles, the ChiSqSelector treats each unique value as a category
val discretizedData = data.map { lp =>
LabeledPoint(lp.label, Vectors.dense(lp.features.toArray.map { x => (x / 16).floor }))
}
// Create ChiSqSelector that will select top 50 of 692 features
val selector = new ChiSqSelector(50)
// Create ChiSqSelector model (selecting features)
val transformer = selector.fit(discretizedData)
// Filter the top 50 features from each feature vector
val filteredData = discretizedData.map { lp =>
LabeledPoint(lp.label, transformer.transform(lp.features))
}
API の詳細については、ChiSqSelector
Java ドキュメントを参照してください。
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.mllib.feature.ChiSqSelector;
import org.apache.spark.mllib.feature.ChiSqSelectorModel;
import org.apache.spark.mllib.linalg.Vectors;
import org.apache.spark.mllib.regression.LabeledPoint;
import org.apache.spark.mllib.util.MLUtils;
JavaRDD<LabeledPoint> points = MLUtils.loadLibSVMFile(jsc.sc(),
"data/mllib/sample_libsvm_data.txt").toJavaRDD().cache();
// Discretize data in 16 equal bins since ChiSqSelector requires categorical features
// Although features are doubles, the ChiSqSelector treats each unique value as a category
JavaRDD<LabeledPoint> discretizedData = points.map(lp -> {
double[] discretizedFeatures = new double[lp.features().size()];
for (int i = 0; i < lp.features().size(); ++i) {
discretizedFeatures[i] = Math.floor(lp.features().apply(i) / 16);
}
return new LabeledPoint(lp.label(), Vectors.dense(discretizedFeatures));
});
// Create ChiSqSelector that will select top 50 of 692 features
ChiSqSelector selector = new ChiSqSelector(50);
// Create ChiSqSelector model (selecting features)
ChiSqSelectorModel transformer = selector.fit(discretizedData.rdd());
// Filter the top 50 features from each feature vector
JavaRDD<LabeledPoint> filteredData = discretizedData.map(lp ->
new LabeledPoint(lp.label(), transformer.transform(lp.features())));
ElementwiseProduct
ElementwiseProduct
は、要素ごとの乗算を使用して、各入力ベクトルに指定された「重み」ベクトルを掛けます。言い換えれば、データセットの各列をスカラー乗数でスケールします。これは、入力ベクトル v
と変換ベクトル scalingVec
間の アダマール積 を表し、結果ベクトルを生成します。
scalingVec
を「w
」と表すと、この変換は次のように書くことができます。
(v1⋮vN)∘(w1⋮wN)=(v1w1⋮vNwN)
ElementwiseProduct
は、コンストラクタに次のパラメータを持ちます
scalingVec
:変換ベクトル。
ElementwiseProduct
は、VectorTransformer
を実装しており、Vector
に重み付けを適用して変換された Vector
を生成したり、RDD[Vector]
に適用して変換された RDD[Vector]
を生成したりできます。
例
以下の例は、変換ベクトル値を使用してベクトルを変換する方法を示しています。
API の詳細については、ElementwiseProduct
Python ドキュメントを参照してください。
from pyspark.mllib.feature import ElementwiseProduct
from pyspark.mllib.linalg import Vectors
data = sc.textFile("data/mllib/kmeans_data.txt")
parsedData = data.map(lambda x: [float(t) for t in x.split(" ")])
# Create weight vector.
transformingVector = Vectors.dense([0.0, 1.0, 2.0])
transformer = ElementwiseProduct(transformingVector)
# Batch transform
transformedData = transformer.transform(parsedData)
# Single-row transform
transformedData2 = transformer.transform(parsedData.first())
API の詳細については、ElementwiseProduct
Scala ドキュメントを参照してください。
import org.apache.spark.mllib.feature.ElementwiseProduct
import org.apache.spark.mllib.linalg.Vectors
// Create some vector data; also works for sparse vectors
val data = sc.parallelize(Seq(Vectors.dense(1.0, 2.0, 3.0), Vectors.dense(4.0, 5.0, 6.0)))
val transformingVector = Vectors.dense(0.0, 1.0, 2.0)
val transformer = new ElementwiseProduct(transformingVector)
// Batch transform and per-row transform give the same results:
val transformedData = transformer.transform(data)
val transformedData2 = data.map(x => transformer.transform(x))
API の詳細については、ElementwiseProduct
Java ドキュメントを参照してください。
import java.util.Arrays;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.mllib.feature.ElementwiseProduct;
import org.apache.spark.mllib.linalg.Vector;
import org.apache.spark.mllib.linalg.Vectors;
// Create some vector data; also works for sparse vectors
JavaRDD<Vector> data = jsc.parallelize(Arrays.asList(
Vectors.dense(1.0, 2.0, 3.0), Vectors.dense(4.0, 5.0, 6.0)));
Vector transformingVector = Vectors.dense(0.0, 1.0, 2.0);
ElementwiseProduct transformer = new ElementwiseProduct(transformingVector);
// Batch transform and per-row transform give the same results:
JavaRDD<Vector> transformedData = transformer.transform(data);
JavaRDD<Vector> transformedData2 = data.map(transformer::transform);
PCA
PCA を使用してベクトルを低次元空間に射影する特徴量変換器。詳細は、次元削減 を参照してください。