如何训练 LSTM 对 tensorflow.js 中的垃圾邮件进行分类?

How can you train an LSTM to classify spam in tensorflow.js?

我正在针对一些垃圾邮件训练 LSTM - 我有两个 类:"spam" 和 "ham"。我通过将每条消息拆分为字符然后对字符进行单热编码来预处理数据。然后我将它归因于相应的向量——[0]代表"ham",[1]代表"spam"。此代码预处理数据:

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 的编码向量,我将数据移植到 tensorflow:

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 是 3 维的,tlabels 是 2 维的,所以一切都应该可以正常工作。但是,当我 运行 代码时,出现以下错误: Error when checking target: expected dense_Dense1 to have 3 dimension(s). but got array with shape 4032,1 有谁知道这里出了什么问题 - 我想不通。谢谢!

备注: 我已经尝试通过在消息向量的末尾添加 "null" 来规范化向量的长度,以将它们全部置于标准化长度。我仍然遇到同样的错误。

LSTM的最后一层应该设置returnSequences: false,相当于一个flatten层。这将修复答案的错误。

Error when checking target: expected dense_Dense1 to have 3 dimension(s). but got array with shape 4032,1

要详细说明答案,不仅仅是字符编码。实际上,不是对每个字符进行编码,而是应该对数据集进行标记化。可以使用一个简单的单词分词器,如解释的那样 or use the tokenizer that comes with the universal sentence encoder。 LSTM序列可以由每个token的唯一标识符组成。

此外,在最后一层使用单个单元并不反映 class化方法。它更像是我们在预测一个值而不是 class。应该使用两个单位(一个用于垃圾邮件,另一个用于火腿)以便对​​标签进行单热编码。