Identify and classify toxic online comments
Introduction: 인터넷을 통해 익명의 사람들은 때때로 사람들이 보통 실제 삶에서는 없을 끔찍한 것을 말하게 한다.
이번에 우리의 플랫폼의 한 댓글에서 증오를 없애보자.
→ 악성 댓글에 대하여 분류하는 문제 같음
This notebook attempts to tackle this classification problem by using Keras LSTM.
While there are many notebook out there that are already tackling using this approach,
I feel that there isn't enough explanation to what is going on each step.
As someone who has been using vanilla Tensorflow, and recently embraced the wonderful world of Keras,
I hope to share with fellow beginners the intuition that I gained from my research and study.
Join me as we walk through it.
We import the standard Keras library
import sys, os, re, csv, codecs, numpy as np, pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
from keras.layers import Dense, Input, LSTM, Embedding, Dropout, Activation
from keras.layers import Bidirectional, GlobalMaxPool1D
from keras.models import Model
from keras import initializers, regularizers, constraints, optimizers, layers
Loading the train and test files
train = pd.read_csv('C://WORK/Input/train.csv')
test = pd.read_csv('C://WORK/Input/test.csv')
A sneak peek at the training and testing dataset
- 데이터셋 살펴보기
train.head()

A common preprocessing step is to check for nulls, and fill the null values with something before proceeding to the next steps.
If you leave the null values intact, it will trip you up at the modelling stage later
* trip up : 실수를 하다, ~가 실수를 하게 만들다[유도하다]
train.isnull().any(),test.isnull().any()

Looks like we don't need to deal with the null values after all!
Note that: There are tons of preprocessing and feature engineering steps you could do for the dataset,
but our focus today is not about the preprocessing task so what we are doing here is the minimal that could get the rest of the steps work well
* 참고: 데이터 세트에 대해 수행할 수 있는 사전 처리 및 기능 엔지니어링 단계는 매우 많지만,
지금은 사전 처리 작업에 초점을 맞추고 있지 않기 때문에 나머지 단계가 제대로 작동할 수 있도록 최소화 한다.
Movng on, as you can see from the sneak peek, the dependent variables are in the training set itself so we need to split them up, into X and Y sets.
* 이제 슬쩍 살펴본 것처럼 종속 변수는 훈련 세트에 있습니다. 따라서 X 세트와 Y 세트로 나눠야 합니다.
데이터를 종속변수와 독립변수로 나누기
- 종속변수 : "toxic", "severe_toxic", "obscene", "threat", "insult", "identity_hate"
- 독립변수 : "comment_text" (댓글들)
list_classes = ["toxic", "severe_toxic", "obscene", "threat", "insult", "identity_hate"]
y = train[list_classes].values
list_sentences_train = train["comment_text"]
list_sentences_test = test["comment_text"]
토큰화하고 인덱스 부여하기
The approach that we are taking is to feed the comments into the LSTM as part of the neural network but we can't just feed the words as it is.
So this is what we are going to do:
- Tokenization - We need to break down the sentence into unique words. For eg, "I love cats and love dogs" will become ["I","love","cats","and","dogs"]
- Indexing - We put the words in a dictionary-like structure and give them an index each For eg, {1:"I",2:"love",3:"cats",4:"and",5:"dogs"}
- Index Representation- We could represent the sequence of words in the comments in the form of index, and feed this chain of index into our LSTM. For eg, [1,2,3,4,2,5]
* 우리가 취하고 있는 접근 방식은 댓글을 신경망의 일부로 LSTM에 공급하는 것이지만,
그대로 단어를 공급할 수는 없습니다.
* 우리는 주석의 단어 순서를 인덱스 형태로 표현하고 이 인덱스 chain 을 LSTM에 공급할 수 있다.
Fortunately, Keras has made our lives so much easier.
If you are using the vanilla Tensorflow, you probably need to implement your own dictionary structure and handle the indexing yourself.
In Keras, all the above steps can be done in 4 lines of code.
Note that we have to define the number of unique words in our dictionary when tokenizing the sentences.
* 우리는 문장을 토큰화할 때 사전에 있는 고유한 단어의 수를 정의해야 합니다.
- 토큰화 / 인덱스 부여
max_features = 20000
tokenizer = Tokenizer(num_words=max_features)
tokenizer.fit_on_texts(list(list_sentences_train)) # "comment_text" ; 댓글변수 값들을 토큰화
list_tokenized_train = tokenizer.texts_to_sequences(list_sentences_train)
list_tokenized_test = tokenizer.texts_to_sequences(list_sentences_test)
You could even look up the occurrence and the index of each words in the dictionary
Now, if you look at "list_tokenized_train",
you will see that Keras has turned our words into index representation for us
* "list_tokenized_train"을 보면 Keras가 우리의 단어를 색인 표현으로 바꾼 것을 볼 수 있다.
* texts_to_sequences() 메서드는 텍스트 안의 단어들을 숫자의 시퀀스의 형태로 변환한다.
list_tokenized_train[:1]
[[688,
75,
1,
126,
130,
177,
29,
672,
4511,
12052,
1116,
86,
331,
51,
2278,
11448,
50,
6864,
15,
60,
2756,
148,
7,
2937,
34,
117,
1221,
15190,
2825,
4,
45,
59,
244,
1,
365,
31,
1,
38,
27,
143,
73,
3462,
89,
3085,
4583,
2273,
985]]
But there's still 1 problem! What if some comments are terribly long, while some are just 1 word? Wouldn't our indexed-sentence look like this:
Comment #1: [8,9,3,7,3,6,3,6,3,6,2,3,4,9]
Comment #2: [1,2]
And we have to feed a stream of data that has a consistent length(fixed number of features) isn't it?
* 하지만 여전히 한 가지 문제가 있습니다! 어떤 댓글은 지독하게 긴 반면 어떤 댓글은 한 단어로만 달리면 어떡할까요?
우리의 색인화된 문장은 다음과 같지 않을까요?
* 또한 일정한 길이 (특징 수) 의 데이터를 제공해야 합니다. 그렇지 않습니까?
And this is why we use "padding"!
We could make the shorter sentences as long as the others by filling the shortfall by zeros.
But on the other hand, we also have to trim the longer ones to the same length(maxlen) as the short ones.
In this case, we have set the max length to be 200.
* 그래서 "패딩"을 사용하는 거야! 우리는 부족한 부분을 0으로 채워서 다른 문장들처럼 짧은 문장을 만들 수 있습니다.
하지만 반면에 긴 것도 짧은 것과 같은 길이(최대 길이)로 잘라야 해요.
이 경우, 우리는 최대 길이를 200으로 설정했습니다.
패딩과정에 대한 이해
- 너무 짧은 문장은 0 을 채워넣어주고
- 긴 문장은 짧게 만들어야한다. => 여기서는 최대 길이를 200으로 설정했으므로 길이가 200을 넘기면 자른다.
패딩 (padding) 하기
maxlen = 200
X_t = pad_sequences(list_tokenized_train, maxlen=maxlen)
X_te = pad_sequences(list_tokenized_test, maxlen=maxlen)
최대길이 max length 을 어떻게 정할까 ?
How do you know what is the best "maxlen" to set?
If you put it too short, you might lose some useful feature that could cost you some accuracy points down the path.
If you put it too long, your LSTM cell will have to be larger to store the possible values or states.
One of the ways to go about it is to see the distribution of the number of words in sentences.
- 최대길이가 너무 짧으면 중요한 특징들을 잃어 정확도가 떨어질 수 있고
- 최대길이가 너무 길면 저장공간이 더 많이 필요할 것이다.
- 문장의 단어 수 분포를 살펴보는 것은 최대길이를 정하는 방법 중 하나다.
totalNumWords = [len(one_comment) for one_comment in list_tokenized_train]
- 각 댓글의 길이를 구하는 코드

plt.hist(totalNumWords,bins = np.arange(0,410,10))#[0,50,100,150,200,250,300,350,400])#,450,500,550,600,650,700,750,800,850,900])
plt.show()

As we can see, most of the sentence length is about 30+. We could set the "maxlen" to about 50,
but I'm being paranoid so I have set to 200. Then again, it sounds like something you could experiment and see what is the magic number.
- 문장의 길이가 30 부근인 경우가 가장 많다.
- 하지만 여기서는 최대길이를 200 으로 지정하였다.
Finally the start of building our model!
This is the architecture of the model we are trying to build.
It's always to good idea to list out the dimensions of each layer in the model to think visually and help you to debug later on.
* 이것이 우리가 구축하려는 모델의 아키텍처이다.
모델 내 각 도면층의 차원을 시작적으로 나열하고 나중에 디버깅하는 데 도움이 된다.

As mentioned earlier, the inputs into our networks are our list of encoded sentences.
We begin our defining an Input layer that accepts a list of sentences that has a dimension of 200.
* 앞에서 언급했듯이, 우리 네트워크에 입력되는 것은 인코딩된 문장 목록입니다.
우리는 차원이 200인 문장 목록을 받아들이는 입력 계층을 정의하기 시작한다.
- Input layer

By indicating an empty space after comma, we are telling Keras to infer the number automatically.
* 우리는 쉼표 뒤에 빈칸을 표시하여 Keras에게 자동으로 숫자를 유추하라고 말하고 있다.
inp = Input(shape=(maxlen, )) #maxlen=200 as defined earlier
- Embedding layer
Next, we pass it to our Embedding layer, where we project the words to a defined vector space depending on the distance of the surrounding words in a sentence.
Embedding allows us to reduce model size and most importantly the huge dimensions we have to deal with, in the case of using one-hot encoding to represent the words in our sentence.
* 다음으로, 우리는 그것을 Embedding layer로 전달하는데,
여기서 우리는 문장에서 주변 단어의 거리에 따라 정의된 벡터 공간에 단어를 투영한다.
임베딩은 문장의 단어를 나타내기 위해 원핫 인코딩을 사용하는 경우 모델 크기와 처리해야 하는
큰 차원을 줄일 수 있게 해준다.
The output of the Embedding layer is just a list of the coordinates of the words in this vector space.
For eg. (-81.012) for "cat" and (-80.012) for "dog".
We could also use the distance of these coordinates to detect relevance and context.
Embedding is a pretty deep topic, and if you are interested, this is a comprehensive guide:
* 임베딩 레이어의 출력은 이 벡터 공간에 있는 단어들의 좌표 리스트일 뿐이다.
예: "cat"의 경우 (-81.012), "dog"의 경우 (-80.012).
또한 이러한 좌표의 거리를 사용하여 관련성과 맥락을 탐지할 수 있다.
We need to define the size of the "vector space" we have mentioned above, and the number of unique words(max_features)we are using.
Again, the embedding size is a parameter that you can tune and experiment.
* 위에서 언급한 "벡터 공간"의 크기와 사용하는 고유 단어(max_feature)의 수를 정의해야 합니다.
다시 말하지만 내장 크기는 조정하고 실험할 수 있는 매개변수입니다.
embed_size = 128
x = Embedding(max_features, embed_size)(inp)
The embedding layer outputs a 3-D tensor of (None, 200, 128). Which is an array of sentence(None means that it's size is inferred),
and for each words(200), there is an array of 128 coordinates in the vector space of embedding.
* 그리고 각 단어(200)에 대해, 내장 벡터 공간에는 128개의 좌표가 배열되어 있다.
- LSTM layer
Next, we feed this Tensor into the LSTM layer.
We set the LSTM to produce an output that has a dimension of 60 and want it to return the whole unrolled sequence of results.
As you probably know, LSTM or RNN works by recursively feeding the output of a previous network into the input of the current network, and you would take the final output after X number of recursion.
But depending on use cases, you might want to take the unrolled, or the outputs of each recursion as the result to pass to the next layer. And this is the case.
* 다음으로, 우리는 이 텐서를 LSTM 층에 공급한다. 우리는 LSTM이 60차원의 출력을 생성하도록 설정하고
전체 미연산 시퀀스를 반환하기를 원한다. 아시다시피 LSTM 또는 RNN은 이전 네트워크의 출력을
현재 네트워크의 입력에 재귀적으로 공급하여 작동하며 X번의 재귀 후에 최종 출력을 얻는다.
그러나 사용 사례에 따라 다음 계층으로 전달하기 위해 각 재귀의 출력을 실행 취소하거나 출력해야 할 수도 있다.
그리고 이것이 그 경우이다.

From the above picture, the unrolled LSTM would give us a set of h0,h1,h2 until the last h.
From the short line of code that defines the LSTM layer, it's easy to miss the required input dimensions. LSTM takes in a tensor of [Batch Size, Time Steps, Number of Inputs].
- Batch size is the number of samples in a batch,
- time steps is the number of recursion it runs for each input, or it could be pictured as the number of "A"s in the above picture.
- number of inputs is the number of variables(number of words in each sentence in our case) you pass into LSTM as pictured in "x" above.
*LSTM 계층을 정의하는 짧은 코드 라인에서 필요한 입력 치수를 놓치기 쉽습니다. LSTM은 [배치 크기, 시간 단계, 입력 수]의 텐서를 취한다.
- 배치 크기는 배치에 있는 샘플의 수
- 시간 단계는 각 입력에 대해 실행되는 재귀의 수 또는 위의 그림에서 "A"의 수로 나타낼 수 있다.
- 입력 수는 위의 "x"에 표시된 것처럼 LSTM에 전달하는 변수의 수(이 경우 각 문장의 단어 수) 이다.
We can make use of the output from the previous embedding layer which outputs a 3-D tensor of (None, 200, 128) into the LSTM layer. What it does is going through the samples, recursively run the LSTM model for 200 times, passing in the coordinates of the words each time.
And because we want the unrolled version, we will receive a Tensor shape of (None, 200, 60), where 60 is the output dimension we have defined.
* LSTM 레이어로 (없음, 200, 128)의 3D 텐서를 출력하는 이전 임베딩 레이어의 출력을 사용할 수 있다.
이것은 샘플을 살펴보는 것으로, LSTM 모델을 200번 반복적으로 실행하며 매번 단어 좌표를 전달한다.
그리고 우리는 unroll 버전을 원하기 때문에 (None, 200, 60)의 텐서 모양을 받을 것이다.
여기서 60은 우리가 정의한 출력 치수이다.
3D 텐서를 2D 텐서로 재구성
x = LSTM(60, return_sequences=True,name='lstm_layer')(x)
Before we could pass the output to a normal layer, we need to reshape the 3D tensor into a 2D one. We reshape carefully to avoid throwing away data that is important to us, and ideally we want the resulting data to be a good representative of the original data.
Therefore, we use a Global Max Pooling layer which is traditionally used in CNN problems to reduce the dimensionality of image data. In simple terms, we go through each patch of data, and we take the maximum values of each patch. These collection of maximum values will be a new set of down-sized data we can use.
As you can see from other Kaggle kernels, different variants (Average,Max,etc) of pooling layers are used for dimensionality reduction and they could yield different results so do try them out.
* 출력을 일반 레이어로 전달하기 전에 3D 텐서를 2D 텐서로 재구성해야 합니다.
우리는 우리에게 중요한 데이터를 버리지 않도록 신중하게 재구성하고,
이상적으로는 결과 데이터가 원본 데이터를 잘 나타내기를 바란다.
따라서 우리는 이미지 데이터의 차원을 줄이기 위해 CNN 문제에 전통적으로 사용되는 글로벌 맥스 풀링 계층을 사용 한다. 간단히 말해, 우리는 각 데이터 패치를 검토하고 각 패치의 최대값을 취합니다.
이러한 최대값 컬렉션은 우리가 사용할 수 있는 새로운 축소된 데이터 집합이 될 것입니다.
다른 Kaggle 커널에서 볼 수 있듯이, 풀링 레이어의 다양한 변형(평균, 최대 등)이 차원 축소를 위해 사용되며
다른 결과를 산출할 수 있으므로 사용해 보십시오.
Global Max Pooling layer
x = GlobalMaxPool1D()(x)
With a 2D Tensor in our hands, we pass it to a Dropout layer which indiscriminately "disable" some nodes so that the nodes in the next layer is forced to handle the representation of the missing data and the whole network could result in better generalization.
We set the dropout layer to drop out 10%(0.1) of the nodes.
* 2D 텐서를 손에 쥐고 있으면 일부 노드를 무차별적으로 "비활성화"하는 드롭아웃 계층에 전달하여
다음 계층의 노드가 누락된 데이터의 표현을 처리해야 하고 전체 네트워크가 더 나은 일반화를 달성할 수 있다.
우리는 드롭아웃 레이어를 노드의 10%(0.1) 드롭아웃으로 설정했다.
Dropout
x = Dropout(0.1)(x)
After a drop out layer, we connect the output of drop out layer to a densely connected layer and the output passes through a RELU function. In short, this is what it does:
In short, this is what it does: Activation( (Input X Weights) + Bias)
all in 1 line, with the weights, bias and activation layer all set up for you! We have defined the Dense layer to produce a output dimension of 50.
* 드롭아웃 레이어 후 드롭아웃 레이어의 출력을 조밀하게 연결된 레이어에 연결하고 출력이 RELU 함수를 통과한다.
활성화() (입력 X 가중치) + 치우침)
무게, 편향 및 활성화 레이어가 모두 한 줄로 설정되었습니다!
우리는 50의 출력 차원을 생성하도록 Dense layer를 정의했습니다.
Dense layer
x = Dense(50, activation="relu")(x)
We feed the output into a Dropout layer again.
x = Dropout(0.1)(x)
시그모이드 함수에 출력결과를 입력
Finally, we feed the output into a Sigmoid layer. The reason why sigmoid is used is because we are trying to achieve a binary classification(1,0) for each of the 6 labels, and the sigmoid function will squash the output between the bounds of 0 and 1.
* 마지막으로, 우리는 시그모이드 층에 출력을 공급한다.
시그모이드가 사용되는 이유는 6개의 레이블 각각에 대해 이항 분류 (1,0) 를 달성하려고 하고
시그모이드 함수는 0과 1의 경계 사이에서 출력을 압축하기 때문이다.
x = Dense(6, activation="sigmoid")(x)
손실함수 정의 - 교차 엔트로피
손실 함수 중 교차 엔트로피 오차
- 소프트맥스 출력과 원-핫 인코딩을 비교하는 교차 엔트로피 오차
심화 최적화 알고리즘 중 Adam
- Adam = RMSProp + Momentum
We are almost done! All is left is to define the inputs, outputs and configure the learning process.
We have set our model to optimize our loss function using Adam optimizer, define the loss function to be "binary_crossentropy" since we are tackling a binary classification. In case you are looking for the learning rate, the default is set at 0.001.
* 이제 입력, 출력을 정의하고 학습 프로세스를 구성하기만 하면 된다
우리는 Adam optimizer를 사용하여 손실 함수를 최적화하도록 모델을 설정하고,
손실 함수를 이진 분류를 다루기 때문에 "이진_교차"로 정의했다. 학습 속도를 찾는 경우 기본값은 0.001로 설정된다.
model = Model(inputs=inp, outputs=x)
model.compile(loss='binary_crossentropy',
optimizer='adam',
metrics=['accuracy'])
The moment that we have been waiting for as arrived! It's finally time to put our model to the test.
We'll feed in a list of 32 padded, indexed sentence for each batch and split 10% of the data as a validation set.
This validation set will be used to assess whether the model has overfitted, for each batch. The model will also run for 2 epochs.
These are some of the tunable parameters that you can experiment with, to see if you can push the accurate to the next level without crashing your machine(hence the batch size).
* 우리는 각 배치에 대해 32개의 패딩되고 색인화된 문장 목록을 제공하고 데이터의 10%를 검증 세트로 분할할 것이다.
이 검증 세트는 모형이 각 배치에 대해 과대 적합되었는지 여부를 평가하는 데 사용된다.
그 모델은 또한 2epochs 동 안 진행될 것이다.
이는 시스템을 손상시키지 않고 정확한 값을 다음 수준으로 푸시할 수 있는지(배치 크기 유지) 실험할 수 있는
조정 가능한 매개 변수 중 일부입니다.
batch_size = 32
epochs = 2
model.fit(X_t,y, batch_size=batch_size, epochs=epochs, validation_split=0.1) # X_t : 패딩한 train 댓글 데이터

Seems that the accuracy is pretty decent for a basic attempt!
There's a lot that you could do (see TODO below) to further improve the accuracy so feel free to fork the kernel and experiment for yourself!
'파이썬 > 캐글 필사' 카테고리의 다른 글
| New York City Taxi Trip Duration 데이터 (0) | 2022.02.10 |
|---|---|
| Porto Seguro’s Safe Driver Prediction 데이터 (0) | 2022.02.05 |
| Titanic 데이터 (0) | 2022.01.26 |