로컬 벡터 (Local Vectors)
MLlib에서 사용하는 로컬 벡터는 크게 조밀 벡터(Dense Vector)와 희소 벡터(Sparse Vector)로 구분됩니다. 조밀 벡터는 모든 요소를 명시적으로 저장하는 반면, 희소 벡터는 0이 아닌 요소의 인덱스와 값만 저장하여 메모리 효율을 높입니다. 다음은 두 가지 벡터를 생성하는 예시입니다.
import org.apache.spark.mllib.linalg.{Vector, Vectors}
// 조밀 벡터 생성
val denseVec: Vector = Vectors.dense(2.5, 0.0, 4.5, 0.0)
// 희소 벡터 생성 (인덱스 배열과 값 배열 사용)
val sparseVecA: Vector = Vectors.sparse(4, Array(0, 2), Array(2.5, 4.5))
// 희소 벡터 생성 (인덱스와 값의 튜플 시퀀스 사용)
val sparseVecB: Vector = Vectors.sparse(4, Seq((0, 2.5), (2, 4.5)))
조밀 벡터는 `Vectors.dense` 메서드에 모든 요소를 순서대로 전달하여 생성합니다. 희소 벡터의 경우 첫 번째 방식은 벡터의 전체 길이, 0이 아닌 요소의 인덱스 배열, 그리고 해당 값의 배열을 인자로 받습니다. 두 번째 방식은 전체 길이와 (인덱스, 값) 형태의 시퀀스를 전달하여 더욱 직관적으로 생성할 수 있습니다.
참고: Scala 환경에서는 기본 제공되는 `scala.collection.immutable.Vector`와 이름이 충돌할 수 있으므로, MLlib의 벡터를 사용할 때는 반드시 `org.apache.spark.mllib.linalg.Vector`를 명시적으로 임포트해야 합니다.
레이블 포인트 (Labeled Points)
레이블 포인트는 지도 학습 알고리즘에서 주로 사용되는 데이터 구조로, 특성 벡터와 이에 대응하는 레이블(정답 값 또는 특정 식별자)로 구성됩니다. 이를 통해 각 데이터 샘플이 어떤 범주나 연속적인 타겟 값을 가지는지 명확히 정의할 수 있습니다.
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.mllib.regression.LabeledPoint
// 레이블이 1.0인 양성 샘플 생성
val positiveSample = LabeledPoint(1.0, Vectors.dense(2.5, 0.0, 4.5))
LIBSVM 포맷 데이터 로드
MLlib은 LIBSVM 포맷으로 저장된 외부 파일을 `LabeledPoint` RDD로 직접 변환하여 읽어들이는 유틸리티를 제공합니다. 파일의 각 라인은 `label index1:value1 index2:value2` 형식을 따라야 합니다.
import org.apache.spark.mllib.util.MLUtils
val libsvmData: RDD[LabeledPoint] = MLUtils.loadLibSVMFile(sparkContext, "data/libsvm_format.txt")
로컬 행렬 (Local Matrices)
로컬 행렬은 단일 머신의 메모리에 저장되는 행렬 구조입니다. 현재 MLlib은 조밀 행렬(Dense Matrix)을 기본으로 지원하며, 행렬 초기화 시 요소들은 열 우선(Column-major) 순서로 1차원 배열에 배치되어야 합니다.
import org.apache.spark.mllib.linalg.{Matrix, Matrices}
// 2행 3열의 조밀 행렬 생성
val denseMatrix: Matrix = Matrices.dense(2, 3, Array(1.5, 2.5, 3.5, 4.5, 5.5, 6.5))
위 코드는 다음과 같은 행렬을 생성합니다. 배열의 첫 두 요소(1.5, 2.5)가 첫 번째 열을 구성하고, 그 다음 요소들이 순차적으로 두 번째, 세 번째 열을 채우게 됩니다.
현재 버전의 MLlib은 로컬 희소 행렬(Sparse Matrix)에 대한 공식적인 구현을 제공하지 않으므로, 데이터가 매우 희소한 경우 분산 행렬 구조를 고려해야 합니다.
분산 행렬 (Distributed Matrices)
분산 행렬은 여러 노드에 걸쳐 분산 저장되는 대규모 행렬을 처리하기 위해 설계되었으며, 데이터의 특성과 희소성에 따라 적절한 구현체를 선택할 수 있습니다.
RowMatrix
`RowMatrix`는 행 인덱스 없이 단순히 벡터의 집합으로 표현되는 행렬입니다. 주로 PCA(주성분 분석)나 특이값 분해(SVD)와 같이 행의 순서가 중요하지 않은 연산에 사용됩니다.
import org.apache.spark.mllib.linalg.Vector
import org.apache.spark.mllib.linalg.distributed.RowMatrix
val vectorRdd: RDD[Vector] = ... // 벡터 RDD 준비
val rowMatrix: RowMatrix = new RowMatrix(vectorRdd)
val totalRows = rowMatrix.numRows()
val totalCols = rowMatrix.numCols()
다변량 통계 요약 (Multivariate Summary Statistics)
`RowMatrix`를 활용하면 각 열에 대한 통계적 요약 정보를 쉽게 계산할 수 있습니다.
import org.apache.spark.mllib.stat.MultivariateStatisticalSummary
val stats: MultivariateStatisticalSummary = rowMatrix.computeColumnSummaryStatistics()
val columnMeans = stats.mean // 각 열의 평균값 벡터
val nonZeroCounts = stats.numNonzeros // 각 열의 0이 아닌 요소 개수
IndexedRowMatrix
`IndexedRowMatrix`는 `RowMatrix`와 기본적으로 동일하지만, 각 행에 고유한 인덱스(Long 타입)가 부여되어 있다는 점이 다릅니다. 이를 통해 특정 행에 직접 접근하거나 행 단위의 조인 및 정렬 연산을 수행할 수 있습니다.
CoordinateMatrix
데이터가 극도로 희소하고 행렬의 차원이 매우 클 때는 `CoordinateMatrix`를 사용하는 것이 가장 효율적입니다. 이 구조는 각 요소를 (행 인덱스, 열 인덱스, 값) 튜플 형태의 `MatrixEntry`로 저장합니다.
import org.apache.spark.mllib.linalg.distributed.{CoordinateMatrix, MatrixEntry}
val entryRdd: RDD[MatrixEntry] = ... // MatrixEntry RDD 준비
val coordinateMatrix: CoordinateMatrix = new CoordinateMatrix(entryRdd)
`CoordinateMatrix`는 내부적으로 좌표 리스트(COO) 포맷을 사용하므로, 행렬이 조밀한 경우에는 메모리 오버헤드가 커질 수 있어 주의가 필요합니다.