Как стать автором
Обновить

Эксперемент с классификацией текста с использованием Tensor Flow

Весьма успешно поэкспериментировал с Tensor Flow в плане классификации текста и решил написать короткую статью об этом. Мои знания в области нейросетей не так уж велики, но после того, как прочитал статью, я понял, что есть тенденция чрезмерно усложнять подход к обработке текста и, возможно, кому-то будет полезно увидеть гораздо более простой и вполне рабочий подход к классификации коротких сообщений.


Я придумал себе следующую задачу: используя базу данных AMAZON_FASHION_5.json (скачать можно тут), научиться по написанному комментарию пользователя к товару предсказывать оценку пользователя. Запись базы данных имеет следующий вид:
{"overall": 5.0, "verified": true, "reviewTime": "06 28, 2018", "reviewerID": "A2IBS6PIPAGAB5", "asin": "B0014F7B98", "style": {"Size:": " 5 B(M) US", "Color:": " Wolf Grey/Black-pink Blast/White"}, "reviewerName": "J. Avila", "reviewText": "My favorite cross trainers!", "summary": "Comfortable", "unixReviewTime": 1530144000}


Я использовал только 2 поля из каждой записи: overall — оценка и reviewText — текстовый отзыв.
Несмотря на обилие в интернете описаний сложных методик для обработки текста с построением словарей и т.д., я решил сделать все проще — подать сообщение на вход нейросети просто как коды символов. Полный диапозон ASCII передавать бессмысленно, так как слишком большое количество неинформативных кодов затруднит обучение. Поэтому я использовал свою таблицу кодов: коды 0-25 соответствуют 'A'-'Z', коды 30-39 — '0'-'9'. Коды 26-29 не использовались. Остальные символы я отбросил (хотя восклицательный знак, к примеру, тоже мог пригодиться — но и без него все работает весьма неплохо). На вход нейросети поступает массив из 500 float значений, каждое из которых рассчитывается как symbol_code/40.0. На выходе 5 нейронов, каждый из которых содержит вероятность конкретной оценки: первый нейрон содержит вероятность оценки 1, второй — 2 и т.д. Можно было просто получать на выходе оценку на одном нейроне, но хотелось немного усложнить и решить именно задачу классификации.


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


import tensorflow as tf
import numpy as np
import json

inputSize = 500
symbolsCount = 40
noteMax = 5

testsCount = 100

file = open("D:/Temp/textdataset.json")
lines = file.readlines()

#To prepare the data for NN training.
inputData = np.zeros([len(lines), inputSize], np.float32)
results = np.zeros([len(lines), noteMax], np.float32)

#Currently processed line of the file
lineId = 0

for line in lines:
    data = json.loads(line)
    if "reviewText" not in data:
        continue

    text = data["reviewText"]
    text = text.lower()

    result = int(data["overall"])

    #Convert the line of ASCII to follwoing encoding: 0-25 - 'A'-'Z', 30-39 - '0' - '9'
    for i in range(min(len(text), symbolsCount)):
        symb = ord(text[i])
        symbId = 0

        if ord('a') <= symb <= ord('z'):
            symbId = symb - ord('a')
        elif ord('0') <= symb <= ord('9'):
            symbId = symb - ord('0') + 30

        inputData[lineId, i] = float(symbId)/symbolsCount

    #Set 1 for for the concrete note which is selected by the reporter. Other outputs are 0
    results[lineId, result - 1] = 1.0
    lineId += 1

#Remove extra items in the data. We have this data because part of lines had no reviewText attribute.
results = results[:lineId]
inputData = inputData[:lineId]

#Model of 5 layers with LeakyReLU activation for first 4 and Softmax for the last one.
model = tf.keras.models.Sequential([
    tf.keras.layers.Dense(1024, input_shape=[inputSize]),
    tf.keras.layers.LeakyReLU(0.2),
    tf.keras.layers.Dense(1024),
    tf.keras.layers.LeakyReLU(0.2),
    tf.keras.layers.Dense(512),
    tf.keras.layers.LeakyReLU(0.2),
    tf.keras.layers.Dense(256),
    tf.keras.layers.LeakyReLU(0.2),
    tf.keras.layers.Dense(5, activation=tf.keras.activations.softmax)
])

#Optimizer - Adam. It is used with low learning rate and small beta_1 which means actually that RMSprop can be used instead:) - but it was not tested.
optimizer = tf.keras.optimizers.Adam(learning_rate=0.0001, beta_1=0.5)
model.compile(optimizer=optimizer, loss=tf.keras.losses.BinaryCrossentropy())

#Training data
x = inputData[:-testsCount]
y = results[:-testsCount]
#Training of the model
model.fit(x, y, batch_size=300, epochs=100)

#Testing
expected = results[-testsCount:]
predicted = model.predict(inputData[-testsCount:])

res = np.concatenate([predicted, expected], axis=1).reshape([testsCount, 2, 5])
print(res)

errorsCount = 0

#Check test results
for i in range(testsCount):
    for j in range(noteMax):
        if expected[i, j] == 1.0 and predicted[i, j] <= 0.5:
            errorsCount += 1

print("Errors count is ", errorsCount, " from ", testsCount)

Обучается сеть очень быстро и дает неплохую точность: 2% ошибок на тестовых данных — они не были включены в обучающий dataset. Надеюсь кому-то пригодится.

Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.