Написано:

Билал Али Шах

Убайд Ур Рехман

Введение:

Понятие фейковых новостей, согласно определению Института изучения журналистики Рейтер, включает в себя ложную информацию, сознательно распространяемую с конкретными стратегическими намерениями — политическими или коммерческими [1]. В нашу цифровую эпоху эта проблема обострилась, поскольку распространять ложь и уклоняться от ответственности, часто используя щит анонимности, становится все проще. В известном инциденте министр обороны Пакистана Хаваджа Асиф обнародовал ядерную угрозу Израилю, ошибочно под влиянием сфабрикованной новости о предполагаемой провокации Израиля [2]. Если даже высокопоставленные чиновники, ответственные за ядерные арсеналы, изо всех сил пытаются отличить правду от лжи, можно только представить, с какими трудностями сталкиваются обычные интернет-пользователи. Таким образом, такое быстрое распространение фальшивых новостей через платформы социальных сетей и новостные онлайн-издания создает серьезные проблемы в поддержании подлинности и точности источников новостей и подчеркивает необходимость разработки эффективных детекторов фейковых новостей.

Научно-исследовательские цели:

Основная цель этого исследовательского проекта — оценить эффективность различных моделей машинного обучения при обнаружении фейковых новостей. В частности, мы стремимся:

  • Сравните точность, воспроизводимость, полноту и оценку F1 различных моделей машинного обучения.
  • Проанализируйте влияние методов извлечения признаков на производительность модели.
  • Изучите эффективность различных классификаторов в различении поддельных и реальных новостных статей.

Методология

Наборы данных:

Мы использовали два тщательно подобранных набора данных из Kaggle.

  1. Фейковые и настоящие новости
  2. Классификация фейковых новостей

Это тщательно отобранные наборы данных новостных статей, собранных из авторитетных источников новостей и сайтов с поддельными новостями. Каждый набор данных состоит из столбца заголовка и столбца текста. Классификация фейковых новостей также имеет двоичный столбец «метка», указывающий, является ли статья фейковой новостью. С другой стороны, набор данных поддельных и реальных новостей состоит из двух отдельных наборов данных, полностью состоящих из поддельных и реальных новостей соответственно.

Предварительная обработка:

Чтобы подготовить набор данных для анализа, мы выполнили несколько шагов предварительной обработки. Первым шагом было объединение наборов данных. Во-первых, мы создали столбцы с метками 0,1 для отдельных наборов данных Fake и Real и объединили их вместе.

df2_fake['label'] = 1
df2_true['label'] = 0
df_merged = pd.concat((df2_fake, df2_true))

Затем мы объединили полученный набор данных с набором данных Fake News Classification. Мы использовали простую очистку данных, такую ​​как удаление бесполезных строк, удаление дубликатов и удаление строк с нулевыми значениями.

dfconcat = pd.concat((df, df_merged[['title','text','label']]))
dfconcat.drop('Unnamed: 0', inplace=True, axis=1)
dfconcat.dropna(inplace=True)
dfconcat.drop_duplicates(subset='text', inplace=True)
dfconcat.reset_index(drop=True, inplace=True)

Последним шагом было преобразование всего текста в нижний регистр, удаление «стоп-слов» (слов, которые не несут никакого смысла и поэтому бесполезны), специальных символов, цифр и гиперссылок. Для этого мы сделали следующую функцию «textCleaning»:

def textCleaning(column, pattern):
  column = column.str.lower()
  column = column.str.replace(pattern, ' ', regex=True)
  column = column.str.replace(r'http [\S]*', ' ' ,regex=True)  # removing hyperlinks
  column = column.str.replace(r'\d+', ' ', regex=True)  # removing digits
  column = column.str.replace(r'\n', ' ', regex=True)  # removing new line symbols
  column = column.str.replace(r'[^\ w\s]', ' ', regex=True)  # removing punctuation and symbols
  return column

«шаблон» — это шаблон регулярного выражения, который мы создали, объединив все «стоп-слова» в один шаблон регулярного выражения, который можно использовать для удаления стоп-слов:

stop = pd.read_table('stop_words.txt', header=None)[0]
pattern = r'\b('+ '|'.join(stop)+r')\b'  # joining the list of stop words to make a regex pattern

Затем мы соединили текст заголовка со столбцом текста статьи, который использовался для извлечения признаков.

dfconcat['text'] = dfconcat['title'] + ' ' + dfconcat['text']
dfconcat.drop('title', inplace =True, axis =1)
dfconcat['text'] = textCleaning(dfconcat['text'], pattern)

Исследовательский анализ данных:

Прежде чем выполнять извлечение признаков и обучение нашей модели, важно сначала внимательно изучить тонкости наших данных. Это именно то, что влечет за собой EDA! Самое первое, что нужно проверить при решении проблемы классификации, — это дисбаланс классов. Мы делаем это, используя библиотеку Seaborn:

Мы можем заметить, что наш классификатор «метка» относительно сбалансирован: около 43% экземпляров «фальшивых новостей» против почти 57% экземпляров «настоящих новостей». Это был важный шаг, поскольку любой существенный дисбаланс классов, оставленный без внимания, вызвал бы проблемы с нашими моделями классификации в будущем.

Двигаясь дальше, мы ищем интересные закономерности в данных. Начнем с анализа длины текста. Во-первых, мы создаем новый столбец в нашем наборе данных, представляющий соответствующее количество слов в каждой статье (за исключением слов, которые мы уже удалили):

dfconcat['length'] = dfconcat['text'].apply(lambda x: len (x.split ()))

Мы снова используем Seaborn, чтобы визуализировать распределение длины статей по лейблам:

Эти рассылки довольно четко показывают, что статьи Fake News, как правило, короче, чем Real News.

Затем мы используем библиотеку «wordcloud» для визуализации наиболее распространенных слов в наборе данных в целом и в каждой категории. Начиная с общего облака слов, мы объединяем все его «текстовые» экземпляры в одну длинную строку с одним пробелом между всеми соседними строками. Затем эта строка передается в WordCloud() для создания общего облака слов:

Для отдельного облака слов мы разделяем набор данных на «поддельные» и «настоящие» наборы данных. Это дает следующие облака слов:

Это интересная визуализация, поскольку мы можем сравнить помеченные облака слов с полным и увидеть, какие слова непропорционально распространены в данном классе. Например, несмотря на то, что в нашем наборе данных широко распространено выражение «Дональд Трамп», эта фраза кажется почти одинаково популярной в обеих категориях (слегка склоняясь к фейковым новостям). С другой стороны, слово «Хиллари Клинтон» является относительно небольшим элементом в облаках слов «Полные новости» и «Настоящие новости», но почти столь же велико (часто) в облаке слов «Фейковые новости». Это указывает на то, что фраза «Хиллари Клинсон» непропорционально распространена в фейковых новостных статьях.

Еще одна такая черта — «у» (предположительно, неформальная форма «вы»). Это слово практически невидимо в облаках слов «Полные новости» и «Настоящие новости», однако оно является одним из самых распространенных слов в облаке слов «Фейковые новости». Используя этот факт в сочетании с нашими более ранними наблюдениями о том, что статьи с фейковыми новостями, как правило, содержат меньше слов, мы можем предположить, что фальшивые новости чаще распространяются неформальными средствами, чем официальными статьями.

Извлечение функции:

Чтобы представить текстовое содержание новостных статей, мы использовали два метода выделения признаков:

  1. Мешок слов (BoW)
  2. Термин Частота Обратной Частоты Документа (TF-IDF)

Техника Bag-of-Words была реализована с нуля. Во-первых, мы составили «словарный список», в который вошли все уникальные слова в наших статьях. Затем мы подсчитали, сколько раз это слово встречается в каждой статье, что дало нам массивную матрицу 62200 x 234266. Нам пришлось разделить эту реализацию на две части, так как она требовала много памяти, и хранить данные в сжатой sparse row (csr) матрице scipy, поскольку массивы numpy были слишком интенсивными для хранения. Вот код:

# Concatenate all article text into a single string
all_text = ' '.join(dfconcat['text'])
# tokenize the text into individual words
tokens = all_text.split()
# create a set to store unique tokens
vocab_set = set(tokens)
# convert the set to a list
vocab_list = list(vocab_set)

import scipy.sparse as sp
# dividing into two because the memory the whole thing takes is huge
n = int(dfconcat['text'].shape[0]/2)
matrix1 = np.empty((n,len(vocab_list)),dtype=np.uint16)
# Populate Bag of Words

# for each word append the count of the matrix at relevant position
i = 0
for article in dfconcat['text'][:n]:
  j = 0
  # print (i)
  for word in vocab_list:
    count = article.count(word)
    matrix1[i,j] = count
    j += 1
  i += 1
matrix1 = sp.csr_matrix(matrix1)

Точно так же мы заполнили вторую половину набора слов оставшимися текстами статей, а затем объединили их вместе в единую матрицу разреженных строк.

Функции, извлеченные с помощью этого метода, затем использовались в различных моделях машинного обучения, которые мы тестировали. Функции Term Frequency-Inverse Document Frequency (TF-IDF) извлекались с использованием TfidfVectorizer() из научного набора.

X_tfidf = TfidfVectorizer().fit_transform(dfconcat['text'])

Модель машинного обучения

Мы запустили следующие модели машинного обучения для обнаружения фейковых новостей:

  • Наивный байесовский
  • Логистическая регрессия
  • Метод опорных векторов (SVM)
  • Случайный лес
  • XGBoost
  • Сверточные нейронные сети (CNN)

Все эти алгоритмы были отлажены с использованием различных параметров, пока методом проб и ошибок мы не получили наилучший результат.

Наивный байесовский

Наивная байесовская модель — это вероятностная модель, которая использует теорему Байеса для прогнозирования переменной результата Y с учетом набора признаков X:

где: P(X|Y ) — вероятность,

P(Y) — априорная вероятность,

и P(X) — нормирующая константа.

Наивная байесовская модель делает «наивное» предположение, что все входные признаки независимы друг от друга, учитывая выходной класс. Это позволяет нам упростить термин правдоподобия следующим образом:

Вместо произведения можно также взять сумму логарифмов вероятностей.

Используя эти вероятности, мы находим апостериорные вероятности и выбираем класс в качестве прогноза, который максимизирует апостериорную вероятность.

class NaiveBayes:
  def __init__(self):
    self.classes = [0,1]
    self.class_priors = None
    self.feature_likelihoods = None
  
  def fit(self, X_train, y_train):  
    # find class priors
    fake_prior = np.mean(np.where(y_train==1,1,0))
    real_prior = np.mean(np.where(y_train==0,1,0))
    self.class_priors = [real_prior,fake_prior]
    # find likelihoods
    self.feature_likelihoods = np.zeros((2, X_train.shape[1]))
    for i, label in enumerate(self.classes):
      X_i = X_train[y_train == label]
      total_count = X_i.sum()
      # applying add-1 smoothing (laplace smoothing)
      self.feature_likelihoods[i] = (X_i.sum(axis=0) + 1) / (total_count + 1 * X_train.shape[1])
      # print(self.feature_likelihoods[i])
  
  def predict(self, X_test):
    y_pred = np.zeros(X_test.shape[0])
    for i in range(X_test.shape[0]):
      posteriors = []
      for j in range(2):
        likelihoods = self.feature_likelihoods[j]
        # need the nonzero indices since we're using sparse matrices
        nonzero_indices = X_test[i].indices
        likelihoods_nonzero = likelihoods[nonzero_indices]
        log_likelihoods = np.log(likelihoods_nonzero)
        # print(log_likelihoods)
        log_priors = np.log(self.class_priors[j])
        posterior = np.sum(log_likelihoods) + log_priors
        posteriors.append(posterior)
      # now, to the prediction:
      if posteriors[0] > posteriors[1]:
        y_pred[i] = 0
      else:
        y_pred[i] = 1
    return y_pred

nb = NaiveBayes()
X_train, X_test, y_train, y_test = train_test_split(sparse_X, np.array(dfconcat['label']),test_size=0.2, random_state=42)
nb.fit(X_train,y_train)
y_pred = nb.predict(X_test)
Evaluate(y_test,y_pred)  # 'Evaluate' is custom function

Кроме того, мы также использовали реализацию полиномиального наивного байесовского алгоритма из научного набора и сравнили производительность. Это дало нам точность 90% и 89% соответственно.

nb = MultinomialNB()
X = CountVectorizer().fit_transform(dfconcat['text'])
X_train, X_test, y_train, y_test = train_test_split(X, np.array(dfconcat['label']),test_size=0.2, random_state=42)

nb.fit(X_train, y_train)
y_pred = nb.predict(X_test)
Evaluate(y_test, y_pred)

Логистическая регрессия

Логистическая регрессия использует метод максимального правдоподобия, чтобы найти вероятность входа 𝓍ᵢ, принадлежащего классу 1.

где β₀ — член пересечения/смещения, а β — вектор весов для 𝓍ᵢ. Он классифицирует 𝓍ᵢ как 1, если P(𝓍ᵢ) ≥ 0,5, и 0 в противном случае. Используя логистическую регрессию, мы смогли получить точность 95%.

logreg = LogisticRegression(C=100,max_iter=1500)
X = CountVectorizer().fit_transform(dfconcat['text'])
X_train, X_test, y_train, y_test = train_test_split(X, np.array(dfconcat['label']),test_size=0.2)

logreg.fit(X_train,y_train)
y_pred = logreg.predict(X_test)
Evaluate(y_test,y_pred)

Машина опорных векторов

Машина опорных векторов (SVM) находит гиперплоскость с максимальным запасом в Rd, которая лучше всего разделяет два (или более для задач классификации нескольких классов) классов. Это достигается путем преобразования векторов признаков в более высокие измерения с использованием ядра. Мы протестировали его на нескольких ядрах, и ядро ​​радиальной базисной функции (rbf) дало нам наилучшую точность. RBF преобразует функции в бесконечномерное представление этих функций. Используя SVM с ядром rbf, мы смогли получить точность 96%.

X = CountVectorizer().fit_transform(dfconcat['text'])
X_train, X_test, y_train, y_test = train_test_split(X, np.array(dfconcat['label']), test_size=0.2, random_state=42)

svm_model = SVC(kernel='rbf', C=10)
svm_model.fit(X_train, y_train)
y_pred = svm_model.predict(X_test)
Evaluate(y_test, y_pred)

Случайный лес

Случайный лес — это ансамблевый метод на основе деревьев, который обучает несколько моделей, используя случайные разбиения обучающих данных на независимых деревьях решений, используя случайные подмножества функций в каждом узле, и объединяет их результаты для получения окончательного прогноза. Он сочетает в себе мощь нескольких деревьев решений для создания точных классификаций. Используя Random Forest, нам удалось добиться точности 92%.

rf = RandomForestClassifier()
X = CountVectorizer().fit_transform(dfconcat['text'])
X_train, X_test, y_train, y_test = train_test_split(X,np.array(dfconcat['label']),test_size=0.2,random_state=42)

rf.fit(X_train, y_train)
y_pred = rf.predict(X_test)
Evaluate(y_test,y_pred)

XGBoost

XGBoost (Extreme Gradient Boosting) использует ансамбль слабых учеников, обычно деревья решений, в повышающей структуре. Повышение — это последовательный процесс, в котором каждый слабый ученик исправляет ошибки предыдущих учеников. Алгоритм минимизирует функцию потерь путем итеративной подгонки слабых моделей к остаткам или градиентам функции потерь. Он объединяет деревья решений с градиентным спуском и делает прогнозы, используя взвешенное голосование каждого ученика. Используя XGBoost, мы получили точность 94%.

X = CountVectorizer().fit_transform(dfconcat['text'])
X_train, X_test, y_train, y_test = train_test_split(X,np.array(dfconcat['label']),test_size=0.2, random_state=42)

dtrain = xgb.DMatrix(X_train, label=y_train)
dtest = xgb.DMatrix(X_test, label=y_test)
params = {
    'objective': 'binary:logistic',
    'eval_metric': 'error'
}
model = xgb.train(params, dtrain)
y_pred = model.predict(dtest)
y_pred = np.where(y_pred>=0.5,1,0)
Evaluate(y_test, y_pred)

Сверточная нейронная сеть

Сверточная нейронная сеть (CNN) — это алгоритм глубокого обучения, который в основном используется для анализа визуальных данных, таких как видео и изображения. Однако его также можно использовать для текстовых данных с использованием одномерной CNN (поскольку в тексте у нас есть одномерные данные в виде последовательностей, в то время как традиционные CNN обрабатывают двумерные данные изображений с высотой и шириной). Используя CNN, мы добились наилучшей точности в 97%.

from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Embedding, Conv1D, GlobalMaxPooling1D, Dense, Dropout

tokenizer = Tokenizer()
tokenizer.fit_on_texts(dfconcat['text'])
sequences = tokenizer.texts_to_sequences(dfconcat['text'])
sequences = pad_sequences(sequences, maxlen=1000)
X_train, X_test, y_train, y_test = train_test_split(sequences, np.array(dfconcat['label']), test_size=0.2, random_state=42)

embedding_dim = 100
vocab_size = len(tokenizer.word_index) + 1

cnn = Sequential()
cnn.add(Embedding(vocab_size, embedding_dim, input_length=max_sequence_length))
cnn.add(Conv1D(128, 5, activation='relu'))
cnn.add(GlobalMaxPooling1D())
cnn.add(Dense(64, activation='relu'))
cnn.add(Dropout(0.2))
cnn.add(Dense(1, activation='sigmoid'))

cnn.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])

Метрики оценки

Мы использовали следующие показатели для оценки эффективности нашей классификации:

  • Точность
  • Точность
  • Отзывать
  • F1-счет

Результаты и обсуждение

Мы представляем показатели производительности различных моделей машинного обучения в таблице 1.

Основываясь на результатах, модель CNN достигла наивысшей точности, точности, отзыва и оценки F1, что указывает на ее превосходную производительность в обнаружении фальшивых новостей. SVM также продемонстрировал конкурентоспособные результаты, в то время как Naïve Bayes был худшим из всех (но все еще приличным с 0,89 F1-оценки).

Результаты TF-IDF

Все результаты, о которых сообщалось до сих пор, были результатами, которые мы получили, используя представление текстовых данных в Bag-of-Words (за исключением результатов Convolutional Neural Network, поскольку вместо этого они моделируют последовательности). Мы также запускали модели машинного обучения для функций TF-IDF. Вот код для извлечения функции:

X = TfidfVectorizer().fit_transform(dfconcat['text'])
X_train, X_test, y_train, y_test = train_test_split ( X, dfconcat['label'], test_size =0.2)

Результаты представлены в таблице 2.

Интересно, что логистическая регрессия показала улучшение всех показателей в среднем на 2%, что соответствует производительности SVM, которая немного улучшилась. Random Forest также показал небольшое улучшение, в то время как Naïve Bayes и XGBoost показали худшие результаты. Наивный байесовский метод показал улучшение точности на 4%, но отзыв упал на 16%! Это демонстрирует, как разные методы извлечения признаков могут лучше подходить для разных моделей машинного обучения, и чтобы достичь оптимального уровня производительности, нам нужно протестировать различные комбинации признаков и моделей методом проб и ошибок.

Заключение

В этом исследовательском проекте по классификации фейковых новостей мы исследовали эффективность различных моделей машинного обучения. Проводя эксперименты с двумя специально отобранными наборами данных, мы сравнили производительность разных моделей с использованием двух разных методов извлечения признаков и оценили их сильные и слабые стороны. Модель CNN превзошла другие модели, за ней последовали модель SVM и модель логистической регрессии. Эти результаты подчеркивают потенциал искусственного интеллекта, особенно методов глубокого обучения, в обнаружении фальшивых новостей. Ограничением этого исследования является то, что мы не могли использовать функции n-грамм из-за нехватки времени, и модели глубокого обучения, такие как классификатор многослойного персептрона (MLP) и долговременной кратковременной памяти (LSTM), также должны были быть часть используемых моделей, поскольку они также являются популярным выбором для задач классификации текста. Несмотря на это, наши результаты способствуют разработке надежных методов борьбы с распространением фейковых новостей и сохранения подлинности источников новостей.