Как вы можете обучить LSTM для классификации спама в тензорном потоке. js? - PullRequest
2 голосов
/ 09 января 2020

Я обучаю LSTM некоторому спаму - у меня есть два класса: "спам" и "ветчина". Я предварительно обрабатываю данные, разбивая каждое сообщение на символы, а затем кодирую их одним нажатием. Затем я приписываю его соответствующему вектору - [0] для «ветчины» и [1] для «спама». Этот код предварительно обрабатывает данные:

const fs = require("fs");
const R = require("ramda");
const txt = fs.readFileSync("spam.txt").toString();
const encodeChars = string => {
    const vecLength = 127;
    const genVec = (char) => R.update(char.charCodeAt(0), 1, Array(vecLength).fill(0));
    return string.split('').map(char => char.charCodeAt(0) < vecLength ? genVec(char) : "invalid");
}
const data = R.pipe(
    R.split(",,,"),
    R.map(
        R.pipe(
            x => [(x.split(",").slice(1).concat("")).reduce((t, v) => t.concat(v)), x.split(",")[0]],
            R.adjust(1, R.replace(/\r|\n/g, "")),
            R.adjust(0, encodeChars),
            R.adjust(1, x => x === "ham" ? [0] : [1])
        )
    ),
    R.filter(R.pipe(
        R.prop(0),
        x => !R.contains("invalid", x)
    ))
)(txt);
fs.writeFileSync("data.json", JSON.stringify(data))

Затем, используя закодированные векторы из data.json, я портирую данные в тензор потока:

const fs = require("fs");
const data = JSON.parse(fs.readFileSync("data.json").toString()).sort(() => Math.random() - 0.5)
const train = data.slice(0, Math.floor(data.length * 0.8));
const test = data.slice(Math.floor(data.length * 0.8));
const tf = require("@tensorflow/tfjs-node");
const model = tf.sequential({
    layers: [
        tf.layers.lstm({ inputShape: [null, 127], units: 16, activation: "relu", returnSequences: true }),
        tf.layers.lstm({ units: 16, activation: "relu", returnSequences: true }),
        tf.layers.lstm({ units: 16, activation: "relu", returnSequences: true }),
        tf.layers.dense({ units: 1, activation: "softmax" }),
    ]
})
const tdata = tf.tensor3d(train.map(x => x[0]));
const tlabels = tf.tensor2d(train.map(x => x[1]));
model.compile({
    optimizer: "adam",
    loss: "categoricalCrossentropy",
    metrics: ["accuracy"]
})
model.fit(tdata, tlabels, {
    epochs: 1,
    batchSize: 32,
    callbacks: {
        onBatchEnd(batch, logs) {
            console.log(logs.acc)
        }
    }
})

tdata является трехмерным, и tlabels двумерный, поэтому все должно работать нормально. Однако, когда я запускаю код, я получаю следующую ошибку: Error when checking target: expected dense_Dense1 to have 3 dimension(s). but got array with shape 4032,1 Кто-нибудь знает, что здесь пошло не так - я не мог понять это. Thx!

Примечания. Я уже пытался нормализовать длину векторов, добавляя «ноль» в конце векторов сообщений, чтобы поместить их все в стандартизированную длину. Я все еще получил ту же ошибку.

1 Ответ

1 голос
/ 09 января 2020

Последний слой LSTM должен установить returnSequences: false, эквивалентный сплющенному слою. Это исправит ошибку в ответе.

Ошибка при проверке цели: ожидается, что dens_Dense1 будет иметь 3 измерения. но получил массив с формой 4032,1

Более подробно об ответе есть нечто большее, чем просто кодировка символов. На самом деле, вместо того, чтобы кодировать каждый символ, набор данных должен быть маркирован. Можно использовать простой токенайзер слов, как объяснено здесь , или использовать токенизатор, который поставляется с универсальным кодировщиком предложений . Последовательность LSTM может быть составлена ​​из уникального идентификатора каждого токена.

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

...