Вступление

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

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

Проблема, которую я пытаюсь решить, - это многоклассовая многозначная классификация, что означает, что комментарий может принадлежать к одному классу или более чем одному классу или ни к одному.

Ход проекта:

  1. Загрузка данных.
  2. Уменьшение количества экземпляров данных, которые не принадлежат ни к одному из классов, чтобы сделать набор данных менее искаженным.
  3. Стандартная предварительная обработка текстовых данных, а именно: нижний регистр текста, удаление знаков препинания, удаление стоп-слов, лемматизация текста.
  4. Преобразование текстовых данных в числовые встроенные данные с помощью API токенизации, предоставляемого платформой Keras.
  5. Определение модели глубокого обучения для задачи классификации.

Загрузка и визуализация набора данных

df = pd.read_csv(path)
df = df.sample(frac = 1)
df = df.reset_index(drop=True)df.drop(‘id’ , axis = 1 , inplace = True)
df.dropna(inplace= True)
df.head()

Уменьшение количества комментариев:

Одна из проблем, с которой я столкнулся в этом проекте, заключалась в том, что OOM (Out of Memory) не хватало памяти для выделения тензоров для нейронной сети. Итак, в первую очередь нам нужно уменьшить размер данных. При визуализации данных было очевидно, что большая часть данных не принадлежала ни к одному из классов.

Средняя длина комментариев составила около 390 слов. График гистограммы показал распределение классов по длине комментария. Ниже приведен код для построения гистограммы различных категорий.

comments = df.iloc[: , 0]
labels = df.iloc[: , 1:]

y = [[],[],[],[],[],[]]
for i in range(len(comments)):
    length = len(comments[i])
    for j in range(6):
        if labels.iloc[i , j] == 1:
            y[j].append(length)
bins = [0 , 200 ,400 ,600, 800 , 1000 , 1200 ]
plt.hist(y ,bins = bins , rwidth = 0.90)
plt.xlabel('length of comment of various categories')
plt.ylabel('no of comments')

Можно заметить, что по мере того, как длина комментариев превысила 300–400 слов, предложения все меньше и меньше помечались как токсичные или какой-либо другой класс в этом отношении. Поэтому я решил отбросить все комментарии длиной более 350 слов. Это позволило мне уменьшить размер набора данных примерно на 37%.

Этапы предварительной обработки текста:

  1. Нижний корпус
comments = comments.str.lower()

2. Удаление знаков препинания: чтобы удалить знаки пунктуации, я использовал строку пунктуации фильтра, предоставленную Keras, в дополнение к некоторым дополнительным пунктуациям, например \ n \ n, в соответствии с потребностями моего набора данных.

punctuations = '!"#$%&()*+,-./:;<=>?@[\\]^_`{|}~\t\n\n'
def remove_punc(text):
    no_punc = ''
    for ch in text:
        if ch not in punctuations:
            no_punc += ch
    return no_punc
comments = comments.apply(lambda text : remove_punc(text))

3. Удаление стоп-слов. Удаление слов данных важно, поскольку они не способствуют решению проблемы, а увеличивают размерность отдельного текста, не обеспечивая особого контекста. Для определения стоп-слов использовался объемный словарь.

def remove_stop(text):
    ans = []
    words = text.split()
    for word in words:
        if nlp.vocab[word].is_stop == False:
            ans.append(word)
    return ' '.join(ans)
comments = comments.apply(lambda text : remove_stop(text))

4. Лемматизация. И стемминг, и лемматизация генерируют корневую форму изменяемых слов. Разница в том, что основа может не быть настоящим словом, тогда как лемма - это реальное языковое слово. Я решил провести лемматизацию с помощью библиотеки Spacy. Не забудьте отключить пространственные функции, такие как NER, Tagger, Parser, если вы хотите только выполнить лемматизацию. Выполнение лемматизации большого документа может занять много времени, если включены другие функции.

def lemma(text):
    ans = []
    doc = nlp(text)
    for token in doc:
        ans.append(token.lemma_)
    return ' '.join(ans)
comments = comments.apply(lambda text : lemma(text))

Преобразование текстовых данных в числовые значения для алгоритмов машинного обучения

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

  1. Count Vectorizer: в CountVectorizer мы подсчитываем только количество раз, когда слово появляется в документе, что приводит к смещению в пользу наиболее часто используемых слов. это заканчивается игнорированием редких слов, которые могли бы помочь в более эффективной обработке наших данных.
  2. TfidfVectorizer: Tf-idf означает термин "частота документа с обратной частотой". Этот алгоритм увеличивает вес для менее встречающихся слов в предложении и штрафует более часто встречающиеся глобальные термины во всем корпусе. Этот алгоритм также снижает количество стоп-слов.

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

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

Некоторые из методов:

  1. Встраивание слова:

Word2Vec: это двухслойная нейронная сеть, обрабатывающая текст. I / p: текст O / p: набор векторов, то есть вектор признаков для слов. Он группирует векторы похожих слов вместе в векторном пространстве. Он создает векторы, которые представляют собой распределенные числовые представления функций слова.

GloVe: GLOVE работает так же, как Word2Vec. GloVe учится, создавая матрицу совместной встречаемости (слова * контекст), которая в основном подсчитывает, как часто слово появляется в контексте.

2. Уровень внедрения Keras: требуется, чтобы входные данные были целочисленными, чтобы каждое слово было представлено уникальным целым числом. Этот этап подготовки данных можно выполнить с помощью API-интерфейса Tokenizer, поставляемого с Keras.

Слой встраивания инициализируется случайными весами и будет изучать встраивание для всех слов в наборе обучающих данных.

Я использовал слой Keras Embedding для своей цели.

from keras.preprocessing.text import Tokenizer
tokenizer = Tokenizer()
tokenizer.fit_on_texts(comments)
comments_tok = tokenizer.texts_to_sequences(comments)
comments_tok = np.array(comments_tok)
vocab_size = len(tokenizer.word_counts) + 1 
# 1 is added so as to accodomodate padded zeroes in the comments

Нам также необходимо заполнить комментарии, поскольку не все комментарии состоят из 350 слов.

from keras.preprocessing.sequence import pad_sequences
max_len = 350
comments_pad = pad_sequences(comments_tok , maxlen=max_len, padding='post')
comments_pad.shape

Выполнение тестового сплита

lab = labels.iloc[: , :].values
from sklearn.model_selection import train_test_split
xtrain, xtest , ytrain , ytest = train_test_split(comments_pad , lab , test_size = 0.25)

А теперь самое интересное - создание нейронной сети.

from keras.models import Sequential
from keras.layers import Dense, Dropout , Embedding , Flatten
model = Sequential()
model.add(Embedding(input_dim = vocab_size , output_dim = 50 , input_length= 350))
model.add(Flatten())
model.add(Dense(max_len * 2 , activation = 'relu')) 
model.add(Dropout(0.15))
model.add(Dense(6 , activation = 'sigmoid'))
model.compile(optimizer = 'adam' , loss = 'binary_crossentropy' , metrics = ['accuracy'])
model.summary()

Параметры встраиваемого слоя:

input_dim: размер словаря данных.

output_dim: количество функций, которые мы хотим, чтобы наше слово имело в плотном матричном представлении.

input_length: длина входного текста, это тот же параметр, что и входной номер функций.

Причина выбора сигмоида заключается в том, что вероятность принадлежности к каждому классу не зависит от других, поэтому активацию softmax использовать нельзя. При активации softmax fn сумма вероятностей всех классов равна 1. Это не относится к сигмовидной активации для отдельного класса.

Если оценка вероятности для определенного класса больше или равна 0,5, мы считаем его принадлежащим этому классу.

Мы должны использовать двоичную функцию потери кросс-энтропии, поскольку мы используем сигмовидную активацию.

Подгонка модели к обучающим данным

model.fit(xtrain , ytrain , batch_size = 128 , epochs = 2)

Определение метода прогнозирования

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

def pred_toxic(comm):
    
    comm = comm.apply(lambda text : remove_stop(text))
    comm = comm.apply(lambda text : lemma(text))
    
    # print(len(text))
    comm_tok = tokenizer.texts_to_sequences(comm)
    comm_tok = np.array(comm_tok)
    
    comm_pad = pad_sequences(comm_tok , maxlen = max_len , padding = 'post')
    comm_pad = np.array(comm_pad)
    ypred = model.predict(comm_pad)
    for i in range(ypred.shape[0]):
        for j in range(6):
            if ypred[i][j] >= 0.5:
                ypred[i][j] = 1
            else:
                ypred[i][j] = 0
    print(ypred)

Проверка прогнозов модели с пользовательскими комментариями:

sent1 = "you fool mind your own business"
sent2 = "you dont know anything !"
sent3 = "Hello , football match tommorow at my place"
sent4 = "stay away from me you loser"
data = [[sent1] ,[sent2] , [sent3] , [sent4]]
df = pd.DataFrame(data)
comm = df.iloc[: , 0]
pred_toxic(comm)

Из массива прогнозов вы можете видеть, что модель классифицирует предложение 1 как токсичное, предложение 4 как токсичное, непристойное и оскорбительное.

Обратитесь к моему репозиторию GitHub для получения полного кода.