Используя машинное обучение, мы можем создать нашу собственную программу проверки на плагиат, которая выполняет поиск украденного контента в огромной базе данных. В этой статье мы сделаем демонстрационное приложение для этой цели.
Плагиат широко распространен в Интернете и в процессе обучения. При большом количестве контента иногда трудно определить, когда что-то стало плагиатом.
Авторы, пишущие сообщения в блогах, могут захотеть проверить, не украл ли кто-то их работу и не разместил ли ее в другом месте. Учителя могут захотеть сравнить работы студентов с другими научными статьями на предмет скопированных работ. Новостные агентства могут захотеть проверить, не украла ли контент-ферма их новостные статьи и не презентовала ли на это содержание как на свое.
Итак, как нам защититься от плагиата? Разве не было бы хорошо, если бы у нас было программное обеспечение, которое делало бы за нас всю тяжелую работу? Используя машинное обучение, мы можем создать нашу собственную программу проверки на плагиат, которая выполняет поиск украденного контента в огромной базе данных. В этой статье мы сделаем именно это.
Мы создадим Python Flask приложение, которое использует Pinecone — службу поиска сходства — для поиска возможного плагиата.
Обзор демонстрационного приложения
Давайте посмотрим на демонстрационное приложение, которое мы создадим сегодня. Ниже вы можете увидеть краткую анимацию приложения в действии.
Пользовательский интерфейс имеет простое текстовое поле для ввода, в которое пользователь может вставить текст из статьи. Когда пользователь нажимает кнопку «Отправить», этот ввод используется для запроса в базе данных статей. Затем пользователю отображаются результаты поиска и их проценты совпадения. Чтобы уменьшить количество шума, приложение также включает ползунок, в котором пользователь может указать порог схожести, чтобы показывать только очень сильные совпадения.
Как видите, когда исходный контент используется в качестве входных данных для поиска, оценки совпадений для статей, возможно, являющихся плагиатом, относительно низкие. Однако, если бы мы скопировали и вставили текст из одной из статей в нашей базе данных, результаты для статьи с плагиатом вернутся с совпадением 99,99%!
Итак, как мы это сделали?
При создании приложения мы начинаем с набора данных новостных статей от Kaggle. Этот набор данных содержит 143 000 новостных статей из 15 крупных публикаций, но мы используем только первые 20 000. (Полный набор данных, из которого создан этот, содержит более двух миллионов статей!)
Затем мы очищаем набор данных, переименовав пару столбцов и удалив несколько ненужных. Затем мы пропускаем статьи через модель вложения для создания vector embeddings (сопоставлений векторов) - это метаданные для алгоритмов машинного обучения для определения сходства между различными входными данными.
Подробнее см. статью на Хабре: "Чудесный мир Word Embeddings: какие они бывают и зачем нужны?"
Мы используем модель Average Word Embeddings Model (модель среднего количества сопоставлений слов). Наконец, мы вставляем эти сопоставления векторов в векторную базу данных, управляемую Pinecone.
Когда векторные сопоставления добавлены в базу данных и проиндексированы, мы готовы начать поиск аналогичного контента. Когда пользователи отправляют текст своей статьи в качестве входных данных, делается запрос к конечной точке API, использующей SDK Pinecone для запроса индекса сопоставлений векторов. Конечная точка API возвращает 10 похожих статей, которые, возможно, были плагиатом, и отображает их в пользовательском интерфейсе приложения. Вот и все! Достаточно просто, правда?
Если вы хотите попробовать это сами, вы можете найти код этого приложения на GitHub. README содержит инструкции по локальному запуску приложения на вашем компьютере.
Пошаговое ревью кода демонстрационного приложения
Мы рассмотрели внутреннюю работу приложения, но как мы на самом деле его создали? Как отмечалось ранее, это Python Flask приложение, которое использует Pinecone SDK. HTML использует файл шаблона, а остальная часть интерфейса создается с использованием статических ресурсов CSS и JS. Для простоты весь внутренний код находится в файле app.py, который мы полностью воспроизвели ниже:
from dotenv import load_dotenv
from flask import Flask
from flask import render_template
from flask import request
from flask import url_for
import json
import os
import pandas as pd
import pinecone
import re
import requests
from sentence_transformers import SentenceTransformer
from statistics import mean
import swifter
app = Flask(__name__)
PINECONE_INDEX_NAME = "plagiarism-checker"
DATA_FILE = "articles.csv"
NROWS = 20000
def initialize_pinecone():
load_dotenv()
PINECONE_API_KEY = os.environ["PINECONE_API_KEY"]
pinecone.init(api_key=PINECONE_API_KEY)
def delete_existing_pinecone_index():
if PINECONE_INDEX_NAME in pinecone.list_indexes():
pinecone.delete_index(PINECONE_INDEX_NAME)
def create_pinecone_index():
pinecone.create_index(name=PINECONE_INDEX_NAME, metric="cosine", shards=1)
pinecone_index = pinecone.Index(name=PINECONE_INDEX_NAME)
return pinecone_index
def create_model():
model = SentenceTransformer('average_word_embeddings_komninos')
return model
def prepare_data(data):
# rename id column and remove unnecessary columns
data.rename(columns={"Unnamed: 0": "article_id"}, inplace = True)
data.drop(columns=['date'], inplace = True)
# combine the article title and content into a single field
data['content'] = data['content'].fillna('')
data['content'] = data.content.swifter.apply(lambda x: ' '.join(re.split(r'(?<=[.:;])\s', x)))
data['title_and_content'] = data['title'] + ' ' + data['content']
# create a vector embedding based on title and article content
encoded_articles = model.encode(data['title_and_content'], show_progress_bar=True)
data['article_vector'] = pd.Series(encoded_articles.tolist())
return data
def upload_items(data):
items_to_upload = [(row.id, row.article_vector) for i, row in data.iterrows()]
pinecone_index.upsert(items=items_to_upload)
def process_file(filename):
data = pd.read_csv(filename, nrows=NROWS)
data = prepare_data(data)
upload_items(data)
pinecone_index.info()
return data
def map_titles(data):
return dict(zip(uploaded_data.id, uploaded_data.title))
def map_publications(data):
return dict(zip(uploaded_data.id, uploaded_data.publication))
def query_pinecone(originalContent):
query_content = str(originalContent)
query_vectors = [model.encode(query_content)]
query_results = pinecone_index.query(queries=query_vectors, top_k=10)
res = query_results[0]
results_list = []
for idx, _id in enumerate(res.ids):
results_list.append({
"id": _id,
"title": titles_mapped[int(_id)],
"publication": publications_mapped[int(_id)],
"score": res.scores[idx],
})
return json.dumps(results_list)
initialize_pinecone()
delete_existing_pinecone_index()
pinecone_index = create_pinecone_index()
model = create_model()
uploaded_data = process_file(filename=DATA_FILE)
titles_mapped = map_titles(uploaded_data)
publications_mapped = map_publications(uploaded_data)
@app.route("/")
def index():
return render_template("index.html")
@app.route("/api/search", methods=["POST", "GET"])
def search():
if request.method == "POST":
return query_pinecone(request.form.get("originalContent", ""))
if request.method == "GET":
return query_pinecone(request.args.get("originalContent", ""))
return "Only GET and POST methods are allowed for this endpoint"
Давайте пройдемся по важным частям файла app.py
, чтобы понять его.
В строках 1–14 мы импортируем зависимости нашего приложения. Наше приложение использует следующее зависимости:
dotenv
для чтения переменных среды из файла .envflask
для настройки веб-приложенияjson
для работы с JSONos
также для получения переменных средыpandas
для работы с набором данныхpinecone
для работы с Pinecone SDKre
для работы с регулярными выражениями (RegEx)requests
для выполнения запросов API для загрузки нашего набора данныхstatistics
для некоторых удобных методов статистикиsentence_transformers
для нашей модели встраиванияswifter
для работы с фреймом данных pandas
В строке 16 мы предоставляем шаблонный код, чтобы сообщить Flask имя нашего приложения.
В строках 18-20 мы определяем некоторые константы, которые будут использоваться в приложении. К ним относятся имя нашего индекса Pinecone, имя файла набора данных и количество строк для чтения из файла CSV.
В строках 22-25, метод initialize_pinecone
получает наш ключ API из файла .env
и использует его для инициализации Pinecone.
В строках 27-29, наш метод delete_existing_pinecone_index
ищет в нашем экземпляре Pinecone индексы с тем же именем, что и тот, который мы используем («проверка на плагиат»). Если найден существующий индекс, мы его удаляем.
В строках 31-35, наш метод create_pinecone_index
создает новый индекс, используя выбранное нами имя («проверка на плагиат»), метрику близости «косинус» и только одну shard.
В строках 37-40, наш метод create_model
использует библиотеку offer_transformers для работы с моделью среднего количества сопоставлений слов. Позже мы закодируем наши сопоставлений векторов, используя эту модель.
В строках 62-68, наш метод process_file
считывает CSV файл и затем вызывает методы prepare_data
и upload_items
. Эти два метода описаны ниже.
В строках 42-56 наш метод prepare_data
корректирует набор данных, переименовывая первый столбец «id» и удаляя столбец «date». Затем он объединяет заголовок статьи с содержанием статьи в одно поле. Мы будем использовать это комбинированное поле при создании сопоставлений векторов.
В строках 58-60 наш upload_items
метод создает сопоставление векторов для каждой статьи, кодируя его с помощью нашей модели. Затем мы вставляем сопоставления векторов в индекс Pinecone.
В строках 70-74, наши методы map_titles
и map_publications
создают некоторые словари названий и имен публикаций, чтобы позже легче найти статьи по их идентификаторам.
Каждый из описанных методов вызывается в строках 95-101 при запуске серверной части приложения. Ее работа подготавливает нас к последнему этапу фактического запроса индекса Pinecone на основе пользовательского ввода.
В строках 103–113 мы определяем два маршрута для нашего приложения: один для домашней страницы и один для конечной точки API. Домашняя страница обслуживает файл шаблона index.html
вместе с активами JS и CSS, а конечная точка API предоставляет функции поиска для запроса индекса Pinecone.
Наконец, в строках 76-93 наш метод query_pinecone
принимает вводимое пользователем содержимое статьи, преобразует его в сопоставления векторов, а затем запрашивает индекс Pinecone, чтобы найти похожие статьи. Этот метод вызывается при переходе на конечную точку /api/search
, что происходит каждый раз, когда пользователь отправляет новый поисковый запрос.
Для визуализации процесса, вот схема, показывающая, как работает приложение:
Примеры сценариев
Итак, суммируя сказанное, как выглядит пользовательский сценарий? Давайте рассмотрим три сценария: оригинальный контент, точная копия плагиата и контент с «исправлением написанного».
Когда отправляется оригинальный контент, приложение отвечает некоторыми, возможно, связанными статьями, но оценки соответствия довольно низкие. Это хороший знак, поскольку контент не является плагиатом, поэтому мы ожидаем низких оценок соответствия.
Когда отправляется точная копия плагиата, приложение выдает почти идеальную оценку совпадения для одной статьи. Это потому, что контент идентичен. Хорошая проверка на плагиат!
Теперь, для третьего сценария, мы должны определить, что мы подразумеваем под «исправлением написанного». Исправление написанного - это форма плагиата, при которой кто-то копирует и вставляет украденный контент, но затем пытается замаскировать факт плагиата, изменяя некоторые слова здесь и там. Если в предложении из исходной статьи говорится: «Он был вне себя от радости найдя свою потерянную собаку», кто-то может написать исправление, чтобы вместо этого сказать: «Он был счастлив вернуть свою пропавшую собаку». Это несколько отличается от перефразирования, потому что основная структура предложения содержания часто остается неизменной на протяжении всей статьи, подвергшейся плагиату.
Вот что самое интересное: наша программа проверки на плагиат действительно хорошо распознает контент с «исправлением написанного»! Если вы скопируете и вставите одну из статей в базе данных, а затем измените несколько слов здесь и там, и, возможно, даже удалите несколько предложений или абзацев, оценка совпадения все равно вернется как почти идеальное совпадение! Когда я попытался сделать это со скопированной и вставленной статьей, у которой была оценка совпадения 99,99%, содержание «записанного исправления» по-прежнему давало оценку совпадения 99,88% после моих изменений!
Не слишком испорчена оценка! Похоже, наша программа проверки на плагиат работает нормально.
Заключение и следующие шаги
Мы создали простое приложение Python для решения реальной проблемы. Подражание может быть высшей формой лести, но никому не нравится, когда его работы крадут. В мире растущего контента подобная программа для проверки плагиата будет очень полезна как авторам, так и учителям.
У этого демонстрационного приложения есть некоторые ограничения, так как это всего лишь демонстрация. База данных статей, загруженных в наш индекс, содержит всего 20 000 статей из 15 крупных новостных изданий. Однако существуют миллионы или даже миллиарды статей и сообщений в блогах. Подобная программа проверки на плагиат полезна только в том случае, если она проверяет ваш ввод на предмет всех мест, где ваша работа могла быть плагиатом. Это приложение было бы лучше, если бы в нашем индексе было больше статей и если бы мы постоянно добавляли в него.
Тем не менее, на данный момент мы продемонстрировали важное подтверждение концепции. Pinecone, как сервис поиска сходства, сделала за нас тяжелую работу, когда дело дошло до аспекта машинного обучения. С его помощью мы смогли создать полезное приложение, которое довольно легко использует обработку естественного языка и семантический поиск, и теперь мы можем быть спокойны, зная, что наша работа не является плагиатом.