Александър Игнатов, 0MI3400082, 06.07.2022
Настоящият проект има за цел да демонстрира използването на методите на функционалното програмиране, комбинирано с ООП, за изграждане на скалируема архитектура на препоръчваща система, използваща Apache Spark. Като пример са разработени и няколко модела върху наборите от данни movelens-100k и goodbooks-10k.
src/main/scala
├── App.scala
├── domains
│ ├── books
│ │ ├── algorithms
│ │ │ └── BooksRecommenderV1.scala
│ │ └── datatransformers
│ │ └── BooksTransformer.scala
│ ├── movielens
│ │ ├── algorithms
│ │ │ ├── MovieRecommenderV1.scala
│ │ │ ├── MovieRecommenderV2.scala
│ │ │ └── MovieRecommenderV3.scala
│ │ └── datatransformers
│ │ └── MovieLensTransformer.scala
│ └── restaurants
│ ├── algorithms
│ │ └── RestaurantsRecommenderV1.scala
│ └── datatransformers
│ └── RestaurantsTransformer.scala
├── metrics
│ └── Metrics.scala
├── registry
│ └── AlgorithmsRegistry.scala
├── shared
│ ├── testables
│ │ └── MatrixFactorizationModelTester.scala
│ └── trainables
│ ├── ALSTrainer.scala
│ └── HyperALSTrainer.scala
├── traits
│ ├── Algorithm.scala
│ ├── DataTransformer.scala
│ ├── Testable.scala
│ └── Trainable.scala
└── utils
├── Logger.scala
└── SparkProvider.scala
Модулите на системата са обособени по домейни, в случая: books, movielens, restaurants. Необходим е и модул shared, съдържащ типове, използвани от повече от един домейн.
В модула traits се намират основните абстракции, които имплементира и/или използва всеки домейн.
Algorithm
trait Algorithm[RowType, ModelType <: Saveable]:
def transformer: DataTransformer[RowType]
def trainer: Trainable[RowType, ModelType]
def tester: Testable[RowType, ModelType]
Изисква да бъдат предоставени три обекта, които се използват за изграждане и тестване на моделите:
DataTransformer
case class Split[RowType](
train: RDD[RowType],
test: RDD[RowType]
)
trait DataTransformer[RowType]:
def preprocess(data: RDD[String]): RDD[RowType]
def split(data: RDD[RowType]): Split[RowType]
-
Методът
preprocess
дефинира процеса на предобработка на данните: системата подаваRDD
(пълният набор от данни), като целта е всеки ред да бъде конвертиран от прочетенияString
до даденRowType
(в примерите в проекта е използванRating
). -
Методът
split
дефинира процеса на разделяне на данните на обучителни и тестови. Системата подава вече преработения отpreprocess
пълен набор от данни.
Trainable
trait Trainable[RowType, ModelType <: Saveable]:
def train(data: RDD[RowType]): ModelType
Методът train
използва данните за обучение (разделени от DataTransformer[_].split
), за да създаде модел, който е готов да бъде съхранен на диск (поради това и необходимостта от Saveable
), тестван и използван.
Testable
trait Testable[RowType, ModelType]:
def test(model: ModelType, metric: Metric, actualData: RDD[RowType]): Double
Методът test
използва модела (създаден от Trainable[_, _].train
) и дадена метрика, за да измери качеството на модела върху даден набор от данни. Връща резултатът от metric
при сравнение на actualData
с RDD
, получен при използването на модела.
sealed trait Metric:
def evaluate(actualData: RDD[Rating], predictedData: RDD[Rating]): Double
В модулът metrics се намират дефиниции на основните метрики, които използва и/или използва всеки домейн. Абстракцията Metric
ни предоставя интерфейса за това.
За целите на демонстрацията за момента е имплементиран единствен неин наследник, RMSE
, дефиниращ изчислението на средно-квадратична грешка:
В този файл е дефинирано примерно конзолно приложение, използващо дефинираната препоръчваща система.
Употребата от командния ред става по следния начин:
RecommenderApp algorithm train|test|predict [-u|-i <id>] dataPath modelBasePath
където:
algorithm
е името на алгоритъма, както е регистрирано в обектаAlgorithmsRegistry
в модула registrytrain|test|predict
е избор от три възможни подкоманди:train
обучава алгоритъма, запазва го (вmodelBasePath
), зарежда го оттам и след това тества получения модел, логвайки резултатът от тестването. В началото се зарежда наборът от данни отdataPath
и се разделя на обучителни и тестови.test
само зарежда (отmodelBasePath
) и тества модела върху пълния набор от данни, намиращ се вdataPath
.predict -u|-i id
използва модела за генериране на 10 препоръки. При опция-u
се препоръчват 10 най-подходящи потребители за продукт с id =id
, а при-i
- 10-те най-подходящи продукта за потребителя с id =id
.
Модулът има помощна функция. Използва се единствено от конзолното приложение в App.scala.
Logger
Позволява писане на текст в STDOUT и STDERR чрез ефекта IO
.
SparkProvider
Предоставя безопасен достъп до SparkContext
чрез ефекта Resource
.
Дефинираните в проекта модели използват алгоритъма Alternating Least Squares, чиято имплементация е предоставена от библиотеката Spark.
Alternating Least Squares (ALS) алгоритъма разделя дадена матрица
За да се намерят матриците
където:
-
$\lambda$ - фактор на регуляризацията, -
$n_{u_i}$ - броя на продуктите, които потребител$i$ е оценил, -
$n_{v_j}$ - броя на пътите, в които продукт$j$ е бил оценен.
При фиксиране на една от матриците
MovieRecommenderV1
Трениран върху сплит 80%-20% на movelens-100k
с параметри на ALS:
$rank = 50$ $maxIterations = 20$ $\lambda = 0.01$
Резултат върху тестови набор от данни:
Резултат върху пълния набор от данни:
MovieRecommenderV2
Трениран върху сплит 80%-20% на movelens-100k
с параметри на ALS:
$rank = 20$ $maxIterations = 10$ $\lambda = 0.05$
Резултат върху тестови набор от данни:
Резултат върху пълния набор от данни:
MovieRecommenderV3
Трениран върху сплит 80%-20% на movelens-100k
с оптимизация на хипермараметрите на ALS. Допълнително обучаващия набор от данни е разделен на 90%-10% за обучение и валидация съответно. Избран е моделът с най-добри резултати измежду следните стойности на хиперпараметрите:
$rank \in { 10, 20, 25}$ $maxIterations \in { 5, 10, 20 }$ $\lambda \in { 0.01, 0.05, 0.1 }$
Резултат върху тестови набор от данни:
Резултат върху пълния набор от данни:
BooksRecommenderV1
Трениран върху сплит 80%-20% на goodbooks-10k
с параметри на ALS:
$rank = 50$ $maxIterations = 20$ $\lambda = 0.01$
Резултат върху тестови набор от данни:
Резултат върху пълния набор от данни:
- https://nightlies.apache.org/flink/flink-docs-release-1.6/dev/libs/ml/als.html#description
- https://link.springer.com/chapter/10.1007/978-3-540-68880-8_32
- https://dl.acm.org/doi/10.1109/MC.2009.263
- https://towardsdatascience.com/prototyping-a-recommender-system-step-by-step-part-2-alternating-least-square-als-matrix-4a76c58714a1
- https://hub.packtpub.com/building-recommendation-system-with-scala-and-apache-spark-tutorial/
- https://haocai1992.github.io/data/science/2022/01/13/build-recommendation-system-using-scala-spark-and-hadoop.html
За конвертирането от Markdown в PDF е използвана командата
pandoc --pdf-engine=xelatex -V mainfont="Arial" README.md -o README.pdf