
_RSOD_2
.pdfМИНОБРНАУКИ РОССИИ САНКТ-ПЕТЕРБУРГСКИЙ ГОСУДАРСТВЕННЫЙ ЭЛЕКТРОТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ «ЛЭТИ» ИМ. В.И. УЛЬЯНОВА (ЛЕНИНА) Кафедра информационной безопасности
ОТЧЕТ по лабораторной работе №2
по дисциплине «Распределенные системы обработки данных» Тема: Реализация алгоритма MOSS с использованием PySpark для обнаружения плагиата в исходном коде
Студент гр. 1363 |
|
Соловьев М.И. |
|
Преподаватель |
|
|
Троценко В.В |
Санкт-Петербург
2024
Цель работы.
Реализовать алгоритм MOSS для обнаружения плагиата в исходном коде.
Основные теоретические положения.
Основы RDD и DataFrame
RDD (Resilient Distributed Dataset) — это низкоуровневый объект в Spark,
представляющий распределённые данные без строгой схемы. В RDD нет информации о структуре данных, поэтому он подходит для неструктурированных данных и сложных вычислений. RDD позволяет выполнять базовые операции (map, filter, foreach), но не поддерживает оптимизации Catalyst.
DataFrame — это структурированное расширение RDD, где данные представлены в виде таблицы с колонками и типами данных (схемой). DataFrame
предоставляет SQL-подобные операции и автоматическую оптимизацию через
Catalyst, делая его удобным для табличных данных и аналитических запросов.
Трансформации и Действия
Spark использует ленивые трансформации, такие как filter, map, join,
которые не запускают вычислений, а лишь добавляют шаги в граф выполнения
(DAG). Чтобы граф был выполнен, необходимо вызвать действие (например, count или collect). Действия запускают выполнение всех накопленных трансформаций, что делает их "неленивыми" операциями.
Добавление колонок в DataFrame можно выполнить с помощью метода withColumn, который возвращает новый DataFrame с добавленной колонкой.
Catalyst Optimizer
Catalyst — это встроенный оптимизатор в Spark, работающий на уровне
DataFrame и SQL, но не на уровне RDD. Catalyst использует схему данных для улучшения плана выполнения (DAG), что помогает ускорить выполнение операций. Catalyst нельзя "выключить" — он не замедляет работу, а наоборот,
оптимизирует её.
2
Catalyst не поддерживает RDD, поскольку у RDD нет схемы, и их операции выполняются без оптимизации.
Взаимодействие между RDD и DataFrame
DataFrame можно преобразовать в RDD (с помощью .rdd) и обратно (с
помощью .toDF()), что полезно при переходе между высокоуровневой и низкоуровневой обработкой данных. Преобразование в RDD полезно для использования методов, недоступных в DataFrame API, однако это приводит к потере схемы и оптимизаций Catalyst.
Нагрузка на кластер и производительность
Создание множества DataFrame не увеличивает нагрузку на кластер, пока не выполняются действия, так как трансформации ленивы. Spark API и SQL
работают с одним и тем же планом выполнения и оптимизацией Catalyst, поэтому скорость выполнения схожая. Однако SQL может быть более интуитивным для аналитиков, а API удобен для разработчиков.
Количество драйверов и воркеров в Spark-программе: минимально один драйвер, и любое количество воркеров в зависимости от конфигурации кластера.
Драйвер управляет задачами, а воркеры выполняют вычисления на отдельных узлах кластера.
Пользовательские функции (UDF) и сериализация
Spark требует сериализации функций для передачи между узлами, так как работает в распределённой среде. UDF (User-Defined Functions) обеспечивают механизм для создания пользовательской логики, но требуют обёрток и сериализации для совместимости со Spark. Случайные функции Python
напрямую не поддерживаются, так как Spark использует UDF для передачи функций в сериализованном виде.
3

Экспериментальные результаты.
Работа была разделена на следующие этапы:
1.Удаление пробелов из кода программ
2.Разбиение кода на подстроки длины 5
3.Нахождение хэшей этих подстрок
4.Поиск общих хэшей
5.Нахождение коэффициента сходства
Удаление пробелов из кода программ
Этот этап выполняется посредством применения UDF-функции clean_data
к исходному коду программ.
def clean_data(inp: str) -> str: return "".join(inp.split())
inp = common.read_data()
df = inp. withColumn('content',clean_data_udf(col('content')))
Рисунок 1 – Датафрейм df
Разбиение кода на подстроки длины 5
Выполняется при помощи применения UDF-функции split_content к
отформатированному ранее тексту.
def split_content(cont, n=5):
return [cont[i:i+n] for i in range(0, len(cont)-n)]
splited_df = df.withColumn('substrings',split_content_udf(col('content')))
Рисунок 2 – Датафрейм splited_df
4

Нахождение хэшей этих подстрок
Выполняется с помощью применения UDF-функции для нахождения хэша к подстрокам, предварительно разбив полученный ранее массив подстрок на отдельные элементы.
splited_df = splited_df.withColumn('substrings',explode(col('substrings'))
)
def get_hash(cont):
return crc32(cont.encode() hashed_df =
splited_df.withColumn('hash',hash_udf2(col('substrings'))) hashed_df =
hashed_df.groupBy('author').agg(collect_set('hash').alias('hashes'
))
Рисунок 3 – Датафрейм hashed_df
Поиск общих хэшей
Создадим датафрейм df_joined, который будет состоять из всевозможных
пар файлов и их хэшей.
df_joined |
= |
hashed_df.alias('df1').join(hashed_df.alias('df2'), |
col('df1.author') != col('df2.author'))
Рисунок 4 – Датафрейм df_joined
5

Создадим вспомогательный датафрейм df_template, который будет содержать общее число хэшей левого файла в столбце “total” и использоваться при подсчете коэффициента совпадения хэшей.
df_template = df_joined.select(col('df1.author').alias('_lhs_author'), col('df2.author').alias('_rhs_author'),\
size(col('df1.hashes')).alias('total'), lit(0.0).alias('match'))
Рисунок 5 – Датафрейм df_template
Для подсчета совпадающих хэшей создадим датафрейм df_with_common_count, в котором создадим все возможные пары значений хэшей и отфильтруем их при несовпадении.
df_with_common_count = df_joined.select(df_joined['df1.author'], explode(col('df1.hashes')).alias('df1_expl'),df_joined['df2.author'],\ explode(col('df2.hashes')).alias('df2_expl')).filter('df1_expl = df2_expl')
6

Рисунок 6 – Датафрейм df_with_common_count
Нахождение коэффициента сходства
Для нахождения числа совпадающих хэшей для каждой пары файлов сгруппируем записи датафрейма из предыдущего этапа по левому и правому автору и применим функцию “count” к записям.
df_with_common_count = df_with_common_count.groupBy( col('df1.author').alias('lhs_author'), \ col('df2.author').alias('rhs_author')
).agg(count('df1_expl').alias('common')).orderBy(col('common').desc())
Рисунок 7 – Датафрейм df_with_common_count
7

Создадим датафрейм df_total_and_common для расчета коэффициента совпадения хэшей, используя правый JOIN для df_with_common_count и df_template по столбцам “lhs_author” и “rhs_author”. Также удалим столбцы,
содержащие избыточную информацию.
to_drop = ['rhs_author', 'lhs_author'] df_total_and_common =
df_with_common_count.alias('main').join(df_template,\ (df_template._lhs_author == df_with_common_count.lhs_author) & \
(df_template._rhs_author==df_with_common_count.rhs_author),'right'). drop(*to_drop)
При выполнении операции JOIN в столбце “common”, содержащем число совпадающих хэшей между файлами, образовались значения “NULL”, в случаях если между файлами не существует общих хэшей. Заменим эти значения на 0.
df_total_and_common = df_total_and_common.na.fill(value = 0)
Найдем коэффициент сходства, поделив значения столбца “common” на
“total”.
df_total_and_common = df_total_and_common.withColumn('match',col('common')
/ col('total')).orderBy(col('match').desc())
Рисунок 8 – Датафрейм df_total_and_common
Для прохождения тестов сформируем финальный датафрейм result_df и
вернем его из функции.
8

result_df = df_total_and_common.select( col('_lhs_author').alias('lhs_author'), col('_rhs_author').alias('rhs_author'), col('match')
).orderBy(col('match').desc()) return result_df
Запустим проверку программы. Как можно заметить по рисунку 9, тесты были успешно пройдены
Рисунок 9 – Результат прохождения тестов
Выводы.
В ходе выполнения лабораторной работы была написана программа,
реализующая алгоритм MOSS для обнаружения плагиата в исходном коде.
9