十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

使用 Clojure MXNet 实现 CNN 文本分类:MR 情感分析数据集的完整实战指南

使用 Clojure MXNet 实现 CNN 文本分类:MR 情感分析数据集的完整实战指南 使用 Clojure MXNet 实现 CNN 文本分类MR 情感分析数据集的完整实战指南【免费下载链接】mxnetLightweight, Portable, Flexible Distributed/Mobile Deep Learning with Dynamic, Mutation-aware Dataflow Dep Scheduler; for Python, R, Julia, Scala, Go, Javascript and more项目地址: https://gitcode.com/gh_mirrors/mxnet1/mxnet本篇技术指南以 Apache MXNet 仓库中contrib/clojure-package的cnn-text-classification示例为蓝本完整讲解如何在 Clojure 生态中使用 MXNet 构建 Yoon Kim 提出的多滤波器卷积神经网络CNN进行电影评论情感二分类。你将掌握数据下载、预训练词向量GloVe / fastText / word2vec接入、REPL 与命令行两种训练方式、多设备CPU/GPU调度以及底层数据预处理与网络结构实现细节可直接复现并迁移到自己的文本分类任务。示例概述Clojure 里的卷积文本分类器cnn-text-classification是 Clojure MXNet 包contrib/clojure-package/README.md提供的一个端到端文本分类示例其网络结构直接参考了 Yoon Kim 的经典论文《Convolutional Neural Networks for Sentence Classification》。核心思路是把一条句子表示为词向量矩阵用多个不同宽度的卷积核filter并行扫描句子捕捉 n-gram 级别的局部特征再通过全局最大池化汇总最后送入全连接层与 softmax 完成二分类。该示例所在的完整目录结构如下contrib/clojure-package/examples/cnn-text-classification/ ├── project.clj # Leiningen 工程配置与依赖声明 ├── get_data.sh # 一键下载 MR 数据集与 GloVe 词向量 ├── get_fasttext_data.sh # 一键下载 fastText 预训练词向量 ├── src/cnn_text_classification/ │ ├── classifier.clj # 网络定义、训练入口 train-convnet │ └── data_helper.clj # 数据加载、清洗、填充与词向量接入 └── test/cnn_text_classification/ └── classifier_test.clj # 端到端训练冒烟测试工程依赖在 project.clj 中声明Clojure 1.9.0以及org.apache.mxnet.contrib.clojure/clojure-mxnet 1.6.0-SNAPSHOTClojure 包基于 Scala 包通过 Java Interop 构建核心仍为 MXNet C 引擎。主入口为cnn-text-classification.classifier可直接通过lein run启动。第一步准备 MR 情感分析数据集示例使用的训练数据是 MRMovie Review极性数据集rt-polarity.neg负面评论与rt-polarity.pos正面评论两个文本文件每行一条影评。来自 yoonkim 的 CNN_sentence 项目即上述论文作者公开的数据。原 README 要求将这两个文件放入mr-dataset-path指定的目录即data/mr-data/。更省事的做法是直接运行仓库自带的 get_data.sh它会自动完成全部下载与解压mkdir -p data/mr-data wget rt-polarity.neg 下载地址 wget rt-polarity.pos 下载地址 mkdir -p data/glove wget glove.6B.zip 下载地址 unzip *.zip脚本执行完成后示例根目录下会生成data/mr-data/rt-polarity.neg data/mr-data/rt-polarity.pos data/glove/glove.6B.50d.txt # 以及 glove.6B.100d/200d/300d 等其他维度值得一提的是classifier.clj在加载命名空间时就会自动检查data/目录是否存在不存在则调用./get_data.sh拉取数据因此首次运行时无需手动干预(def>(defn glove-file-path Returns the file path to GloVe embedding of the input size [embedding-size] (format data/glove/glove.6B.%dd.txt embedding-size))即 50 维对应data/glove/glove.6B.50d.txt300 维对应data/glove/glove.6B.300d.txt路径中维度由:embedding-size参数动态决定。方式一通过 REPL 交互式训练子集进入项目目录启动 REPL 后调用train-convnet即可开始训练。README 给出的子集训练命令为(train-convnet {:devs [(context/default-context)] :embedding-size 50 :batch-size 100 :test-size 100 :num-epoch 10 :max-examples 1000 :pretrained-embedding :glove})各参数含义如下表参数取值示例含义:devs[(context/default-context)]训练使用的设备上下文列表默认即 CPU(0):embedding-size50词向量维度必须与预训练文件维度一致:batch-size100每批训练的样本数:test-size100从打乱后的数据中划分出的测试样本数:num-epoch10训练轮数:max-examples1000正/负样本各取前 1000 条用于控制时间与内存:pretrained-embedding:glove预训练词向量类型可为:glove/:fasttext/:word2vec/nil注意:max-examples 1000表示正、负样本各加载 1000 条共 2000 条这是为了控制训练时间和内存占用而刻意设置的子集模式。方式二通过 lein run 命令行训练如果不进 REPL可以直接用命令行启动训练。CPU 环境下建议先调大 JVM 堆内存JVM_OPTS-Xmx1g lein run-main函数还支持通过命令行参数控制训练设备见 classifier.clj 的-main实现语法为lein run 设备类型 设备数量命令效果lein run :cpu 2在 2 个 CPU 设备上训练lein run :gpu 1在 1 个 GPU 设备上训练lein run :gpu 2在 2 个 GPU 设备上训练其设备上下文由context/cpu、context/gpu构造见 context.clj二者分别对应 MXNet 的Context(cpu, device-id)与Context(gpu, device-id)devs会作为一个向量整体传给 Module实现多设备数据并行(defn -main [ args] (let [[dev dev-num] args devs (if ( dev :gpu) (mapv #(context/gpu %) (range (Integer/parseInt (or dev-num 1)))) (mapv #(context/cpu %) (range (Integer/parseInt (or dev-num 1)))))] (println Running with context devices of devs) (train-convnet {:devs devs :embedding-size 50 :batch-size 10 :test-size 100 :num-epoch 10 :max-examples 1000 :pretrained-embedding :glove})))默认-main使用子集参数max-examples 1000、test-size 100、batch-size 10训练 10 轮。全量数据训练lein uberjar 打包运行要跑完 MR 数据集的全部样本需要先修改classifier.clj中-main或直接调用train-convnet的参数去掉:max-examples并将:test-size提到 1000(train-convnet {:embedding-size 50 :batch-size 100 :test-size 1000 :num-epoch 10 :pretrained-embedding :glove})然后打包成可执行 jar 并运行JVM 堆设置为 1GB~2GBlein uberjar java -Xms1024m -Xmx2048m -jar target/cnn-text-classification-0.1.0-SNAPSHOT-standalone.jarMR 数据集全量约一万条样本跑全量时内存和显存占用会明显上升因此 README 与源码注释都强调限制内存时保持max-examples 1000、test-size 100的配置即可。使用 fastText 预训练词向量fastText 的接入方式与 GloVe 几乎一致因为其预训练词向量同样是文本格式每行一个词 对应向量分量。步骤为下载 fastText 官方发布的 Simple English 维基百科预训练词向量文本格式wiki.simple.vec将其放入data/fasttext/目录即contrib/clojure-package/examples/cnn-text-classification/data/fasttext/或者直接运行仓库脚本 get_fasttext_data.sh 一键下载到正确位置。然后通过 REPL 在数据子集上训练注意此处:embedding-size必须改为 300因为 Simple English 向量维度为 300(train-convnet {:devs [(context/default-context)] :embedding-size 300 :batch-size 100 :test-size 100 :num-epoch 10 :max-examples 1000 :pretrained-embedding :fasttext})按上述参数README 说明预期可达到约0.67的验证准确率。在底层data_helper.clj 的load-fasttext!会跳过wiki.simple.vec首行的元数据头词数 维度再逐行解析词向量(def remove-fasttext-metadata rest) ;; 丢弃首行 单词数 维度 元数据 (defn load-fasttext! [fasttext-file-path] (println Loading the fastText pre-trained word embeddings from fasttext-file-path) (- (io/reader fasttext-file-path) line-seq remove-fasttext-metadata read-text-embedding-pairs (into {})))使用 word2vec 预训练词向量word2vec 同样受支持但使用的是 GoogleNews 的二进制格式向量GoogleNews-vectors-negative300.bingzip 压缩包。步骤为下载该文件并解压将解压后的.bin文件放到contrib/clojure-package/data目录下。该路径由data_helper.clj中的常量指定w2v-file-path ../../data/GoogleNews-vectors-negative300.bin相对于示例目录解析最终指向contrib/clojure-package/data/。子集训练命令(train-convnet {:devs [(context/default-context)] :embedding-size 300 :batch-size 100 :test-size 100 :num-epoch 10 :max-examples 1000 :pretrained-embedding :word2vec})全量训练命令(train-convnet {:devs [(context/default-context)] :embedding-size 300 :batch-size 100 :test-size 1000 :num-epoch 10 :pretrained-embedding :word2vec})按上述全量参数README 说明预期准确率约0.78。需要特别留意两点内存加载 word2vec 词向量非常消耗内存且耗时较长README 明确提示需用JVM_OPTS-Xmx8g lein run启动维度校验load-word2vec-model!在加载时会校验:embedding-size与二进制文件头部记录的维度是否一致不一致会抛出ex-infoMismatch in embedding size。word2vec 是二进制格式data_helper.clj专门实现了r-string逐字节读取直到空格或换行与get-float/read-float按小端序读取 float来解析头部与前 300 维向量load-w2v-vectors采用惰性序列lazy-seq流式读取避免一次性把全部 300 万词向量载入内存(defn- load-w2v-vectors Lazily loads the word2vec vectors ... [dis embedding-size num-vectors] (if ( 0 num-vectors) (list) (let [word (r-string dis) vect (mapv (fn [_] (read-float dis)) (range embedding-size))] (cons [word vect] (lazy-seq (load-w2v-vectors dis embedding-size (dec num-vectors)))))))使用学习得到的词嵌入不加载预训练向量最后一种方式与 Python 版 CNN 文本分类示例一致不加载任何预训练向量让模型在训练过程中直接学习词嵌入。只需将:pretrained-embedding设为nil或省略该参数(train-convnet {:devs [(context/default-context)] :embedding-size 50 :batch-size 100 :test-size 100 :num-epoch 10 :max-examples 1000 :pretrained-embedding nil})此时网络符号图会在数据符号后插入一个embedding层vocab_embed把每个词的词汇表索引映射为可训练的稠密向量(defn get-data-symbol [num-embed sentence-size batch-size vocab-size pretrained-embedding] (if pretrained-embedding (sym/variable data) (as- (sym/variable data) data (sym/embedding vocab_embed {:data data :input-dim vocab-size :output-dim num-embed}) (sym/reshape {:data data :target-shape [batch-size 1 sentence-size num-embed]}))))对应地两种模式下的数据张量形状也不同shuffle-data中体现使用预训练向量[样本数 1 句长 向量维度]channel x ychannel 恒为 1学习嵌入[样本数 1 句长]仅保存词索引词向量由 embedding 层产出。源码剖析多滤波器卷积网络架构核心网络由get-multi-filter-convnet构建classifier.clj完全复刻 Yoon Kim 论文的 multichannel / multi-filter 设计(defn get-multi-filter-convnet [num-embed sentence-size batch-size vocab-size pretrained-embedding] (let [filter-list [3 4 5] ;; 三种卷积核宽度3-gram / 4-gram / 5-gram input-x (get-data-symbol num-embed sentence-size batch-size vocab-size pretrained-embedding) polled-outputs (mapv #(make-filter-layers {:input-x input-x :num-embed num-embed :sentence-size sentence-size} %) filter-list) total-filters (* num-filter (count filter-list)) ;; 100 * 3 300 concat (sym/concat concat nil polled-outputs {:dim 1}) hpool (sym/reshape hpool {:data concat :target-shape [batch-size total-filters]}) hdrop (if (pos? dropout) (sym/dropout hdrop {:data hpool :p dropout}) hpool) fc (sym/fully-connected fc1 {:data hdrop :num-hidden num-label})] (sym/softmax-output softmax {:data fc})))单条滤波器分支由make-filter-layers实现卷积 → ReLU 激活 → 全局最大池化。(defn make-filter-layers [{:keys [input-x num-embed sentence-size] :as config} filter-size] (as- (sym/convolution {:data input-x :kernel [filter-size num-embed] ;; 核宽度filter-size高度词向量维度 :num-filter num-filter}) data (sym/activation {:data data :act-type relu}) (sym/pooling {:data data :pool-type max :kernel [(inc (- sentence-size filter-size)) 1] ;; 垂直方向整句池化 :stride [1 1]})))关键点卷积核形状kernel [filter-size num-embed]即宽度为 3/4/5 个词、高度等于词向量维度每次卷积滑动覆盖一个 n-gram 的完整向量表示池化池化核高度为sentence-size - filter-size 1恰好覆盖卷积输出的全部时间步等效于全局最大池化为每个滤波器输出一个标量拼接三个分支的池化输出按dim 1拼接并 reshape 为[batch-size 300]Dropout 与分类以 0.5 的概率做 dropout 防止过拟合随后经fully-connected隐藏单元数 标签数 2和softmax-output输出二分类概率。三个全局常量定义在网络顶部num-filter 100每个分支 100 个滤波器、num-label 2正/负两类、dropout 0.5。源码剖析数据加载与预处理流水线data_helper.clj的load-ms-with-embeddings串起整条数据流水线按:pretrained-embedding的值分发到不同加载器(defn load-ms-with-embeddings [path max-examples embedding-size {:keys [pretrained-embedding] :or {pretrained-embedding nil}}] (let [{:keys [sentences labels]} (load-mr-data-and-labels path max-examples) sentences-padded (pad-sentences sentences) vocab (build-vocab sentences-padded) vocab-embeddings (case pretrained-embedding :glove (- (load-glove! (glove-file-path embedding-size)) (build-vocab-embeddings vocab embedding-size)) :fasttext (- (load-fasttext! fasttext-file-path) (build-vocab-embeddings vocab embedding-size)) :word2vec (- (load-word2vec-model! w2v-file-path embedding-size {:vocab vocab}) (:word2vec) (build-vocab-embeddings vocab embedding-size)) vocab) ;; nil 时直接用词汇表索引 data (build-input-data-with-embeddings sentences-padded vocab-embeddings)] {:data data :label labels :sentence-count (count data) :sentence-size (count (first data)) :embedding-size embedding-size :vocab-size (count vocab) :pretrained-embedding pretrained-embedding}))各步骤的核心逻辑1. 读取与清洗load-mr-data-and-labels。rt-polarity.pos每行标为 1rt-polarity.neg每行标为 0若指定max-examples则正负各取前 N 条。随后clean-str执行一套英文文本规范化规则非字母数字与(),!?之外的字符替换为空格、将s/ve/nt/re/d/ll等缩略形式与标点独立成 token、合并多余空格。2. 填充到等长pad-sentences。以数据集中最长句子的长度为基准不足部分用句子结束符EOS/s补齐使每条句子长度一致才能组成批张量。3. 构建词汇表build-vocab。统计全部词频并按频率降序排序为每个词分配从 0 开始的唯一索引。4. 词汇表 → 向量表build-vocab-embeddings。对词汇表中的每个词优先查预训练向量未出现在预训练文件中的词OOV则从[-0.25, 0.25]的均匀分布中随机初始化保证每个词都有可用的稠密表示(defn build-vocab-embeddings [vocab embedding-size embeddings] (into {} (mapv (fn [[word _]] [word (or (get embeddings word) (ndarray/-vec (random/uniform -0.25 0.25 [embedding-size])))]) vocab)))5. 映射为输入张量。build-input-data-with-embeddings把每条填充后的句子逐词替换为对应向量或索引最终形成[样本数 1 句长 向量维度]的数据张量。训练配置Module 与 Adam 优化器train-convnet使用 MXNet 的高层 Module API 完成训练。首先构造训练/测试数据迭代器注意标签名必须与网络输出softmax的标签一致且末批不足时采用pad填充策略train-data (mx-io/ndarray-iter [(get-in shuffled [:training :data])] {:label [(get-in shuffled [:training :label])] :label-name softmax_label :data-batch-size batch-size :last-batch-handle pad}) test-data (mx-io/ndarray-iter [(get-in shuffled [:test :data])] {:label [(get-in shuffled [:test :label])] :label-name softmax_label :data-batch-size batch-size :last-batch-handle pad})然后以符号图 设备列表创建 Module并调用m/fit训练eval-data作为每轮结束后的验证集(let [mod (m/module (get-multi-filter-convnet embedding-size sentence-size batch-size vocab-size pretrained-embedding) {:contexts devs})] (println Getting ready to train for num-epoch epochs) (println ) (m/fit mod {:train-data train-data :eval-data test-data :num-epoch num-epoch :fit-params (m/fit-params {:optimizer (optimizer/adam)})}))示例选用Adam 优化器。若不显式指定m/fit-params见 module.clj的默认值是eval-metric为 accuracy、kvstore为local、optimizer为 SGD、initializer为uniform 0.01这些默认行为可通过fit-params覆盖。shuffle-data会在训练前打乱数据并按test-size划出训练/测试集。测试验证端到端冒烟测试仓库为示例提供了两个端到端测试classifier_test.clj分别覆盖加载 GloVe 预训练向量与无预训练向量学习嵌入两条路径各用max-examples 1000、num-epoch 1跑一遍完整训练流程然后断言 Module 的数据名称为[data]、输出张量长度为 20 batch-size 10 × 标签数 2(deftest classifier-with-embeddings-test (let [train (classifier/train-convnet {:devs [(context/default-context)] :embedding-size 50 :batch-size 10 :test-size 100 :num-epoch 1 :max-examples 1000 :pretrained-embedding :glove})] (is ( [data] (util/scala-vector-vec (module/data-names train)))) (is ( 20 (count (ndarray/-vec (- train module/outputs ffirst)))))))这两个测试既验证了数据流水线与网络定义的正确性也可作为自定义数据源或网络结构时的回归基线。参数速查与预期精度汇总预训练向量:embedding-size数据规模预期验证准确率备注GloVe (:glove)50推荐也可 100/200/300子集/全量—文件位于data/glove/glove.6B.{d}d.txtfastText (:fasttext)300子集~0.67文件位于data/fasttext/wiki.simple.vecword2vec (:word2vec)300全量~0.78文件位于contrib/clojure-package/data/GoogleNews-vectors-negative300.bin需-Xmx8g无:pretrained-embedding nil50自定义——由 embedding 层端到端学习注意事项路径约定MR 数据集与 GloVe/fastText 文件都放在示例目录contrib/clojure-package/examples/cnn-text-classification/下的data/内word2vec 二进制文件则放在contrib/clojure-package/data/下其路径是硬编码的../../data/相对路径。内存管理全量数据 word2vec 场景内存需求最高README 建议-Xmx8gGloVe 子集训练 1GB 堆即可。维度一致性:embedding-size必须与预训练文件维度严格匹配GloVe 50/100/200/300fastText Simple English 300GoogleNews word2vec 300word2vec 加载器会显式校验。设备选择无 GPU 时:devs使用(context/default-context)或:cpu N有 GPU 环境可用:gpu 1/:gpu 2开启多卡并行。以此示例为起点你可以通过替换data-helper的加载函数接入任意数据集与词向量或修改get-multi-filter-convnet的滤波器宽度、数量与池化策略将其扩展为更复杂的文本分类、情感分析乃至短文本匹配模型。【免费下载链接】mxnetLightweight, Portable, Flexible Distributed/Mobile Deep Learning with Dynamic, Mutation-aware Dataflow Dep Scheduler; for Python, R, Julia, Scala, Go, Javascript and more项目地址: https://gitcode.com/gh_mirrors/mxnet1/mxnet创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表