[PYTHON] Application de Deep Learning 2 à partir de zéro Filtre anti-spam

1.Tout d'abord

Pendant la période de séjour à la maison ["Deep learning from scratch ② Natural Language Processing"](https://www.amazon.co.jp/%E3%82%BC%E3%83%AD%E3%81%8B% E3% 82% 89% E4% BD% 9C% E3% 82% 8B Apprentissage en profondeur-% E2% 80% 95% E8% 87% AA% E7% 84% B6% E8% A8% 80% E8% AA% 9E % E5% 87% A6% E7% 90% 86% E7% B7% A8-% E6% 96% 8E% E8% 97% A4-% E5% BA% B7% E6% AF% 85 / dp / 4873118360) lis. J'ai réussi à arriver à la fin, mais il n'y a pas beaucoup d'exemples d'application dans ce texte. Alors, utilisons le code texte pour créer un filtre anti-spam (modèle de classification de document). Cette étude était basée sur l'article de Qiita Modèle de classification des phrases par RNN fabriqué à partir de zéro.

2. Données

Utilisez le "SMS Spam Collection Dataset" dans Kaggle.

3. Présentation du modèle

model.png

4. Mise en œuvre

--Préparation pour Google Colab

# coding: utf-8
from google.colab import drive
drive.mount('/content/drive')
import sys
sys.path.append('drive/My Drive/Colab Notebooks/spam_filter')
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
from sklearn import metrics
from keras.preprocessing.text import Tokenizer
from keras.preprocessing import sequence
%matplotlib inline

--Lisez le fichier CSV avec les pandas et affichez les 5 premières lignes
La première colonne est le libellé (ham ou spam), la deuxième colonne est le message et les 3ème à 5ème colonnes sont des lignes vides.
Il semble que de nombreux messages de spam sont comme "Big hit! Contactez xxx immédiatement".

df = pd.read_csv('drive/My Drive/Colab Notebooks/spam_filter/dataset/spam.csv',encoding='latin-1')
df.head()

出力1.png

df.drop(['Unnamed: 2', 'Unnamed: 3', 'Unnamed: 4'],axis=1,inplace=True)
df.info()

出力2.png

sns.countplot(df.v1)
plt.xlabel('Label')
plt.title('Number of ham and spam messages')

頻度.png

X = df.v2
Y = df.v1
le = LabelEncoder()
Y = le.fit_transform(Y)

max_words = 1000
max_len = 150
tok = Tokenizer(num_words=max_words)
tok.fit_on_texts(X)

word_to_id = tok.word_index
X_ids = tok.texts_to_sequences(X)
X_ids_pad = sequence.pad_sequences(X_ids,maxlen=max_len)

出力tok.png

--Afficher le nombre de mots dans chaque message dans un histogramme
Le maximum est d'environ 100 mots et le spam est souvent plus long.

message_len = [len(v) for v in X_ids]
df['message_len']=message_len

plt.figure(figsize=(12, 8))

df[df.v1=='ham'].message_len.plot(bins=35, kind='hist', color='blue', 
                                       label='Ham messages', alpha=0.6)
df[df.v1=='spam'].message_len.plot(kind='hist', color='red', 
                                       label='Spam messages', alpha=0.6)
plt.legend()
plt.xlabel("Message Length")

ヒストグラム.png

Implémentation du modèle

--Définition de la fonction sigmoïde, fonction softmax, fonction cross_entropy_error
Aucune modification du texte.

def sigmoid(x):
    return 1 / (1 + np.exp(-x))


def softmax(x):
    if x.ndim == 2:
        x = x - x.max(axis=1, keepdims=True)
        x = np.exp(x)
        x /= x.sum(axis=1, keepdims=True)
    elif x.ndim == 1:
        x = x - np.max(x)
        x = np.exp(x) / np.sum(np.exp(x))

    return x


def cross_entropy_error(y, t):
    if y.ndim == 1:
        t = t.reshape(1, t.size)
        y = y.reshape(1, y.size)
        
    #Les données des enseignants en sont une-hot-Pour le vecteur, convertissez en index de l'étiquette correcte
    if t.size == y.size:
        t = t.argmax(axis=1)
             
    batch_size = y.shape[0]

    return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size

--Définition de chaque couche d'Affine, Softmax, SoftmaxWithLoss, Embedding
Aucune modification du texte.

class Affine:
    def __init__(self, W, b):
        self.params = [W, b]
        self.grads = [np.zeros_like(W), np.zeros_like(b)]
        self.x = None

    def forward(self, x):
        W, b = self.params
        out = np.dot(x, W) + b
        self.x = x
        return out

    def backward(self, dout):
        W, b = self.params
        dx = np.dot(dout, W.T)
        dW = np.dot(self.x.T, dout)
        db = np.sum(dout, axis=0)

        self.grads[0][...] = dW
        self.grads[1][...] = db
        return dx


class Softmax:
    def __init__(self):
        self.params, self.grads = [], []
        self.out = None

    def forward(self, x):
        self.out = softmax(x)
        return self.out

    def backward(self, dout):
        dx = self.out * dout
        sumdx = np.sum(dx, axis=1, keepdims=True)
        dx -= self.out * sumdx
        return dx


class SoftmaxWithLoss:
    def __init__(self):
        self.params, self.grads = [], []
        self.y = None  #sortie de softmax
        self.t = None  #Label enseignant

    def forward(self, x, t):
        self.t = t
        self.y = softmax(x)

        #L'étiquette de l'enseignant en est une-Pour le vecteur chaud, convertir en index correct
        if self.t.size == self.y.size:
            self.t = self.t.argmax(axis=1)

        loss = cross_entropy_error(self.y, self.t)
        return loss

    def backward(self, dout=1):
        batch_size = self.t.shape[0]

        dx = self.y.copy()
        dx[np.arange(batch_size), self.t] -= 1
        dx *= dout
        dx = dx / batch_size

        return dx

class Embedding:
    def __init__(self, W):
        self.params = [W]
        self.grads = [np.zeros_like(W)]
        self.idx = None

    def forward(self, idx):
        W, = self.params
        self.idx = idx
        out = W[idx]
        return out

    def backward(self, dout):
        dW, = self.grads
        dW[...] = 0
        np.add.at(dW, self.idx, dout)
        return None

--Définition de chaque couche de TimeEmbedding, LSTM, TimeLSTM
Aucune modification du texte.

class TimeEmbedding:
    def __init__(self, W):
        self.params = [W]
        self.grads = [np.zeros_like(W)]
        self.layers = None
        self.W = W

    def forward(self, xs):
        N, T = xs.shape
        V, D = self.W.shape

        out = np.empty((N, T, D), dtype='f')
        self.layers = []

        for t in range(T):
            layer = Embedding(self.W)
            out[:, t, :] = layer.forward(xs[:, t])
            self.layers.append(layer)

        return out

    def backward(self, dout):
        N, T, D = dout.shape

        grad = 0
        for t in range(T):
            layer = self.layers[t]
            layer.backward(dout[:, t, :])
            grad += layer.grads[0]

        self.grads[0][...] = grad
        return None


class LSTM:
    def __init__(self, Wx, Wh, b):
        '''

        Parameters
        ----------
        Wx:contribution`x`Paramètre de poids pour (combine 4 poids)
        Wh:État caché`h`Paramètre de poids pour (résumer les poids pour 4)
        b:Biais (résumer 4 biais)
        '''
        self.params = [Wx, Wh, b]
        self.grads = [np.zeros_like(Wx), np.zeros_like(Wh), np.zeros_like(b)]
        self.cache = None

    def forward(self, x, h_prev, c_prev):
        Wx, Wh, b = self.params
        N, H = h_prev.shape

        A = np.dot(x, Wx) + np.dot(h_prev, Wh) + b

        f = A[:, :H]
        g = A[:, H:2*H]
        i = A[:, 2*H:3*H]
        o = A[:, 3*H:]

        f = sigmoid(f)
        g = np.tanh(g)
        i = sigmoid(i)
        o = sigmoid(o)

        c_next = f * c_prev + g * i
        h_next = o * np.tanh(c_next)

        self.cache = (x, h_prev, c_prev, i, f, g, o, c_next)
        return h_next, c_next

    def backward(self, dh_next, dc_next):
        Wx, Wh, b = self.params
        x, h_prev, c_prev, i, f, g, o, c_next = self.cache

        tanh_c_next = np.tanh(c_next)

        ds = dc_next + (dh_next * o) * (1 - tanh_c_next ** 2)

        dc_prev = ds * f

        di = ds * g
        df = ds * c_prev
        do = dh_next * tanh_c_next
        dg = ds * i

        di *= i * (1 - i)
        df *= f * (1 - f)
        do *= o * (1 - o)
        dg *= (1 - g ** 2)

        dA = np.hstack((df, dg, di, do))

        dWh = np.dot(h_prev.T, dA)
        dWx = np.dot(x.T, dA)
        db = dA.sum(axis=0)

        self.grads[0][...] = dWx
        self.grads[1][...] = dWh
        self.grads[2][...] = db

        dx = np.dot(dA, Wx.T)
        dh_prev = np.dot(dA, Wh.T)

        return dx, dh_prev, dc_prev


class TimeLSTM:
    def __init__(self, Wx, Wh, b, stateful=False):
        self.params = [Wx, Wh, b]
        self.grads = [np.zeros_like(Wx), np.zeros_like(Wh), np.zeros_like(b)]
        self.layers = None

        self.h, self.c = None, None
        self.dh = None
        self.stateful = stateful

    def forward(self, xs):
        Wx, Wh, b = self.params
        N, T, D = xs.shape
        H = Wh.shape[0]

        self.layers = []
        hs = np.empty((N, T, H), dtype='f')

        if not self.stateful or self.h is None:
            self.h = np.zeros((N, H), dtype='f')
        if not self.stateful or self.c is None:
            self.c = np.zeros((N, H), dtype='f')

        for t in range(T):
            layer = LSTM(*self.params)
            self.h, self.c = layer.forward(xs[:, t, :], self.h, self.c)
            hs[:, t, :] = self.h

            self.layers.append(layer)

        return hs

    def backward(self, dhs):
        Wx, Wh, b = self.params
        N, T, H = dhs.shape
        D = Wx.shape[0]

        dxs = np.empty((N, T, D), dtype='f')
        dh, dc = 0, 0

        grads = [0, 0, 0]
        for t in reversed(range(T)):
            layer = self.layers[t]
            dx, dh, dc = layer.backward(dhs[:, t, :] + dh, dc)
            dxs[:, t, :] = dx
            for i, grad in enumerate(layer.grads):
                grads[i] += grad

        for i, grad in enumerate(grads):
            self.grads[i][...] = grad
        self.dh = dh
        return dxs

    def set_state(self, h, c=None):
        self.h, self.c = h, c

    def reset_state(self):
        self.h, self.c = None, None

--Définition de la classe Rnnlm
Le vecteur d'état caché h qui apparaît en dernier est converti en Affine, binarisé et normalisé par la fonction Softmax.

class Rnnlm():
    def __init__(self, vocab_size=10000, wordvec_size=100, hidden_size=100, out_size=2):
        V, D, H, O = vocab_size, wordvec_size, hidden_size, out_size
        rn = np.random.randn

        #Initialisation du poids
        embed_W = (rn(V, D) / 100).astype('f')
        lstm_Wx = (rn(D, 4 * H) / np.sqrt(D)).astype('f')
        lstm_Wh = (rn(H, 4 * H) / np.sqrt(H)).astype('f')
        lstm_b = np.zeros(4 * H).astype('f')
        affine_W = (rn(H, O) / np.sqrt(H)).astype('f')
        affine_b = np.zeros(O).astype('f')

        #Génération de couches
        self.embed_layer = TimeEmbedding(embed_W)
        self.lstm_layer = TimeLSTM(lstm_Wx, lstm_Wh, lstm_b, stateful=True)
        self.affine_layer = Affine(affine_W, affine_b)
        self.loss_layer = SoftmaxWithLoss()
        self.softmax_layer = Softmax()

        #Lister tous les poids et dégradés
        self.params = self.embed_layer.params + self.lstm_layer.params + self.affine_layer.params
        self.grads = self.embed_layer.grads + self.lstm_layer.grads + self.affine_layer.grads

    def predict(self, xs):
        self.reset_state()
        xs = self.embed_layer.forward(xs)
        hs = self.lstm_layer.forward(xs)
        xs = self.affine_layer.forward(hs[:,-1,:]) #Conversion affine du dernier calque caché
        score = self.softmax_layer.forward(xs)
        return score

    def forward(self, xs, t):
        xs = self.embed_layer.forward(xs)
        hs = self.lstm_layer.forward(xs)
        x = self.affine_layer.forward(hs[:,-1,:]) #Conversion affine du dernier calque caché
        loss = self.loss_layer.forward(x, t)
        self.hs = hs

        return loss

    def backward(self, dout=1):
        dout = self.loss_layer.backward(dout)
        dhs = np.zeros_like(self.hs)
        dhs[:,-1,:] = self.affine_layer.backward(dout) #Définir la rétropropagation d'erreur de la transformation affine dans le dernier calque masqué

        dout = self.lstm_layer.backward(dhs)
        dout = self.embed_layer.backward(dout)
        return dout

    def reset_state(self):
        self.lstm_layer.reset_state()

--Définition de SGD comme optimiseur
Aucune modification du texte

class SGD:
    '''
Descente de gradient stochastique
    '''
    def __init__(self, lr=0.01):
        self.lr = lr
        
    def update(self, params, grads):
        for i in range(len(params)):
            params[i] -= self.lr * grads[i]

Apprenez d'ici

X_train,X_test,Y_train,Y_test = train_test_split(X_ids_pad,Y,test_size=0.15)
#Paramètres des hyper paramètres
vocab_size = len(word_to_id)+1
batch_size = 20
wordvec_size = 100
hidden_size = 100
out_size = 2 #Problème binaire de jambon et de spam
lr = 1.0
max_epoch = 10
data_size = len(X_train)

#Variables utilisées lors de l'apprentissage
max_iters = data_size // batch_size

#Besoin de convertir en tableau Numpy
x = np.array(X_train)
t = np.array(Y_train)

--Apprentissage

total_loss = 0
loss_count = 0
loss_list = []

#Génération de modèle
model = Rnnlm(vocab_size, wordvec_size, hidden_size, out_size)
optimizer = SGD(lr)

for epoch in range(max_epoch):
    for iter in range(max_iters):
        #Obtenez un mini lot
        batch_x  = x[iter*batch_size:(iter+1)*batch_size]
        batch_t =  t[iter*batch_size:(iter+1)*batch_size]

        #Trouvez le dégradé et mettez à jour les paramètres
        loss = model.forward(batch_x, batch_t)
        model.backward()
        optimizer.update(model.params, model.grads)
        total_loss += loss
        loss_count += 1

    avg_loss = total_loss / loss_count
    print("| epoch %d | loss %.5f" % (epoch+1, avg_loss))
    loss_list.append(float(avg_loss))
    total_loss, loss_count = 0,0
  
x = np.arange(len(loss_list))
plt.plot(x, loss_list, label='train')
plt.xlabel('epochs')
plt.ylabel('loss')
plt.show()

出力3.png 学習.png

--Inférence des données de test

result = model.predict(X_test)
Y_pred = result.argmax(axis=1)
# calculate accuracy of class predictions
print('acc=',metrics.accuracy_score(Y_test, Y_pred))

出力4.png

# print the confusion matrix
print(metrics.confusion_matrix(Y_test, Y_pred))

出力5.png

5. Résumé

Cette fois, j'ai pu approfondir ma compréhension du texte en faisant des essais et des erreurs pour créer cet outil.

Si vous lisez Deep Learning ②, qui est fait à partir de zéro de la même manière, nous vous recommandons d'utiliser l'exemple de programme pour créer une sorte d'application.

prime

Jugement par sms self-made
La première est de vous inviter à aller voir un match de baseball ensemble.
Le second est mon propre Spam (vous n'avez pas à le traduire).
Étonnamment? Je peux le juger correctement.

texts_add = ["I'd like to watch baseball game with you. I'm wating for your answer.",
    "Do you want to meet new sex partners every night? Feel free to call 09077xx0721."
    ]
X_ids_add = tok.texts_to_sequences(texts_add)
X_ids_pad_add = sequence.pad_sequences(X_ids_add,maxlen=max_len)

result = model.predict(X_ids_pad_add)
Y_pred = result.argmax(axis=1)
print(Y_pred)

出力おまけ.png

Recommended Posts

Application de Deep Learning 2 à partir de zéro Filtre anti-spam
Mémo d'apprentissage profond créé à partir de zéro
[Mémo d'apprentissage] Le Deep Learning fait de zéro [Chapitre 7]
Deep learning / Deep learning made from scratch Chapitre 6 Mémo
[Mémo d'apprentissage] Deep Learning fait de zéro [Chapitre 5]
[Mémo d'apprentissage] Le Deep Learning fait de zéro [Chapitre 6]
Deep learning / Deep learning made from scratch Chapitre 7 Mémo
Enregistrement d'apprentissage de la lecture "Deep Learning from scratch"
[Mémo d'apprentissage] Deep Learning fait de zéro [~ Chapitre 4]
Apprentissage profond à partir de zéro
Écrivez vos impressions sur l'édition du framework Deep Learning 3 créée à partir de zéro
[Mémo d'apprentissage] Apprentissage profond à partir de zéro ~ Mise en œuvre de l'abandon ~
Apprentissage profond à partir de zéro 1 à 3 chapitres
Apprentissage profond à partir de zéro (calcul des coûts)
[Deep Learning from scratch] Implémentation de la méthode Momentum et de la méthode AdaGrad
Apprentissage profond à partir de zéro (propagation vers l'avant)
Chapitre 3 Réseau de neurones Ne découpez que les bons points de Deeplearning à partir de zéro
Apprentissage profond / Apprentissage profond à partir de zéro 2-Essayez de déplacer GRU
"Deep Learning from scratch" avec Haskell (inachevé)
[Windows 10] Construction de l'environnement "Deep Learning from scratch"
Mémo d'auto-apprentissage «Deep Learning from scratch» (n ° 10-2) Valeur initiale du poids
Chapitre 2 Implémentation de Perceptron Ne découpez que les bons points de Deeplearning à partir de zéro
[Deep Learning from scratch] À propos de l'optimisation des hyper paramètres
Mémo d'auto-apprentissage "Deep Learning from scratch" (partie 12) Deep learning
Chapitre 1 Introduction à Python Découpez uniquement les bons points de Deeplearning à partir de zéro
Python vs Ruby "Deep Learning from scratch" Chapitre 4 Implémentation de la fonction de perte
Mémo d'auto-apprentissage "Deep Learning from scratch" (glossaire illisible)
Deep Learning from scratch ① Chapitre 6 "Techniques liées à l'apprentissage"
GitHub du bon livre "Deep Learning from scratch"
Résumé Python vs Ruby "Deep Learning from scratch"
Mémo d'auto-apprentissage «Deep Learning from scratch» (10) Classe MultiLayerNet
Mémo d'auto-apprentissage «Deep Learning from scratch» (n ° 11) CNN
Deep learning 1 Pratique du deep learning
Python vs Ruby "Deep Learning from scratch" Chapitre 3 Implémentation d'un réseau neuronal à 3 couches
[Python] [Traitement du langage naturel] J'ai essayé le Deep Learning ❷ fait de toutes pièces en japonais ①
Deep Learning from scratch La théorie et la mise en œuvre de l'apprentissage profond appris avec Python Chapitre 3
[Pour les débutants] Après tout, qu'est-ce qui est écrit dans Deep Learning fait à partir de zéro?
[Deep Learning from scratch] Valeur initiale du poids du réseau neuronal utilisant la fonction sigmoïde
[Deep Learning from scratch] J'ai implémenté la couche Affine
Deep Learning 2 from scratch 1.3 Traitement du langage naturel 1.3 Résumé
Othello ~ De la troisième ligne de "Implementation Deep Learning" (4) [Fin]
[Deep Learning from scratch] J'ai essayé d'expliquer le décrochage
Python vs Ruby "Deep Learning from scratch" Chapitre 3 Graphique de la fonction step, fonction sigmoid, fonction ReLU
Python vs Ruby "Deep Learning from scratch" Chapitre 1 Graphique de la fonction sin et de la fonction cos
[Deep Learning from scratch] Poids initial du réseau neuronal lors de l'utilisation de la fonction Relu
Deep running 2 Réglage de l'apprentissage profond
Apprentissage par renforcement profond 2 Mise en œuvre de l'apprentissage par renforcement
Un amateur a trébuché dans le Deep Learning à partir de zéro Note: Chapitre 1
Un amateur a trébuché dans le Deep Learning ❷ fait à partir de zéro Note: Chapitre 5
Un amateur a trébuché dans le Deep Learning ❷ fait à partir de zéro Note: Chapitre 2
Créez un environnement pour "Deep Learning from scratch" avec Docker
Un amateur a trébuché dans le Deep Learning à partir de zéro Note: Chapitre 7
Un amateur a trébuché dans le Deep Learning à partir de zéro Note: Chapitre 5
Un amateur a trébuché dans le Deep Learning ❷ fait de zéro Note: Chapitre 1
Un amateur a trébuché dans le Deep Learning ❷ fait à partir de zéro Note: Chapitre 4
Mémo d'auto-apprentissage «Deep Learning from scratch» (n ° 18) One! Miaou! Grad-CAM!
Un amateur a trébuché dans le Deep Learning à partir de zéro.
Un amateur a trébuché dans le Deep Learning à partir de zéro Note: Chapitre 2