文本分类(LSTM+PyTorch)

news2024/11/17 20:32:35

本文的配套代码已上传至github,链接在文末,同时附带中文数据集。

一、传统方法的基本步骤

  1. 预处理:首先进行分词,然后是除去停用词;
  2. 将文本表示成向量,常用的就是文本表示向量空间模型;
  3. 进行特征选择,这里的特征就是词语,去掉一些对于分类帮助不大的特征。常用的特征选择的方法是词频过滤,互信息,信息增益,卡方检验等;
  4. 接下来就是构造分类器,在文本分类中常用的分类器一般是SVM,朴素贝叶斯等;
  5. 训练分类器,后面只要来一个文本,进行文本表示和特征选择后,就可以得到文本的类别。

二、深度学习与传统方法的区别

传统做法主要问题的文本表示是高纬度高稀疏的,特征表达能力很弱;此外需要人工进行特征工程,成本很高。
利用深度学习做文本分类,首先还是要进行分词,这是做中文语料必不可少的一步,这里的分词使用的jieba分词。 词语的表示不用one-hot编码,而是使用词向量(word embedding),现在最常用的词向量的分布式表示就是word2vec,这样的分布式表示,既降低了维度,也体现了语义信息。使用深度学习进行文本分类,不需要进行特征选择这一步,因为深度学习具有自动学习特征的能力。

三、LSTM文本分类

先将句子进行分词,然后将每个词语表示为词向量,再将词向量按顺序送进LSTM,最后LSTM的输出就是这段话的表示,而且能够包含句子的时序信息。
LSTM文本分类流程图

1. 处理数据集
# utils.py

def read_file(path):
    """
    读取文本文件进行预处理
    :param path: 文件路径
    :return: 分词后的数组
    """
    if 'training_label' in path:
        with open(path, 'r', encoding='utf-8') as file:
            lines = file.readlines()
            lines = [line.strip('\n').split(' ') for line in lines]
            x = [line[2:] for line in lines]
            y = [line[0] for line in lines]
        return x, y
    elif 'training_nolabel' in path:
        with open(path, 'r', encoding='utf-8') as file:
            lines = file.readlines()
            x = [line.strip('\n').split(' ') for line in lines]
        return x
    else:
        with open(path, 'r', encoding='utf-8') as file:
            lines = file.readlines()
            x = ["".join(line[1:].strip('\n').split(",")) for line in lines[1:]]
            x = [item.split(' ') for item in x]
        return x

2. word2vec词向量模型

依据原始数据提取了data与labels之后,还需要依据文本生成词向量,即建立单词与向量的一一对应关系,同时建立词汇之间的语义联系。

# word2vec.py

from gensim.models import word2vec
import os
from utils import read_file


def train_word2vec(x):
    """
    LineSentence(inp):格式简单:一句话=一行; 单词已经过预处理并被空格分隔。
    size:是每个词的向量维度;
    window:是词向量训练时的上下文扫描窗口大小,窗口为5就是考虑前5个词和后5个词;
    min-count:设置最低频率,默认是5,如果一个词语在文档中出现的次数小于5,那么就会丢弃;
    sg ({0, 1}, optional) – 模型的训练算法: 1: skip-gram; 0: CBOW
    iter (int, optional) – 迭代次数,默认为5
    :param x: 处理好的数据集
    :return: 训练好的模型
    """
    return word2vec.Word2Vec(x, size=300, window=5, min_count=5, sg=1, iter=10)


if __name__ == '__main__':
    data_dir = './data'

    print("loading training data ...")
    train_x, y = read_file(os.path.join(data_dir, 'training_label.txt'))
    train_no_label = read_file(os.path.join(data_dir, 'training_nolabel.txt'))
    print("loading test data...")
    test_data = read_file(os.path.join(data_dir, 'testing_data.txt'))

    print("training text data and transforming to vectors by skip-gram...")
    model = train_word2vec(train_x + train_no_label + test_data)
    print("saving model...")
    model.save(os.path.join(data_dir, 'word2vec.model'))

3. 数据预处理

数据预处理就是按照词向量对样本中的数据生成句子的矩阵,因此这里需要统一句子的长度,生成数据集并实现数据集的封装。实现代码由两部分构成,首先生成对应矩阵,然后封装dataset便于使用dataloader加载数据。

# pre_processing.py
import torch
from gensim.models import Word2Vec


class DataProcess:
    def __init__(self, sentences, sen_len, w2v_path="./data/word2vec.model"):
        self.sentences = sentences  # 句子列表
        self.sen_len = sen_len      # 句子的最大长度
        self.w2v_path = w2v_path    # word2vec模型路径
        self.index2word = []        # 实现index到word转换
        self.word2index = {}        # 实现word到index转换
        self.embedding_matrix = []

        # load word2vec.model
        self.embedding = Word2Vec.load(self.w2v_path)
        self.embedding_dim = self.embedding.vector_size

    def make_embedding(self):
        # 为model里面的单词构造word2index, index2word 和 embedding
        for i, word in enumerate(self.embedding.wv.vocab):
            print('get word #{}'.format(i+1), end='\r')
            self.word2index[word] = len(self.word2index)
            self.index2word.append(word)
            self.embedding_matrix.append(self.embedding[word])
        self.embedding_matrix = torch.tensor(self.embedding_matrix)

        # 將"<PAD>"和"<UNK>"加进embedding里面
        self.add_embedding("<PAD>")
        self.add_embedding("<UNK>")
        print("total words: {}".format(len(self.embedding_matrix)))

        return self.embedding_matrix

    def add_embedding(self, word):
        # 将新词添加进embedding中
        vector = torch.empty(1, self.embedding_dim)
        torch.nn.init.uniform_(vector)
        self.word2index[word] = len(self.word2index)
        self.index2word.append(word)
        self.embedding_matrix = torch.cat([self.embedding_matrix, vector], 0)

    def sentence_word2idx(self):
        sentence_list = []
        for i, sentence in enumerate(self.sentences):
            # 将句子中的单词表示成index
            sentence_index = []
            for word in sentence:
                if word in self.word2index.keys():
                    # 如果单词在字典中则直接读取index
                    sentence_index.append(self.word2index[word])
                else:
                    # 否则赋予<UNK>
                    sentence_index.append(self.word2index["<UNK>"])

            # 统一句子长度
            sentence_index = self.pad_sequence(sentence_index)
            sentence_list.append(sentence_index)

        return torch.LongTensor(sentence_list)

    def pad_sequence(self, sentence):
        # 统一句子长度
        if len(sentence) > self.sen_len:
            sentence = sentence[:self.sen_len]
        else:
            pad_len = self.sen_len - len(sentence)
            for _ in range(pad_len):
                sentence.append(self.word2index["<PAD>"])
        assert len(sentence) == self.sen_len

        return sentence

    def labels2tensor(self, y):
        y = [int(label) for label in y]

        return torch.LongTensor(y)

4. 封装数据集

# data_loader.py
from torch.utils import data


class TwitterDataset(data.Dataset):
    def __init__(self, x, y):
        self.data = x
        self.label = y

    def __getitem__(self, index):
        if self.label is None:
            return self.data[index]

        return self.data[index], self.label[index]

    def __len__(self):
        return len(self.data)

5. 构造模型

模型包括三个部分:embedding层、LSTM层、全连接层。

embedding层可以理解为:一个保存了固定字典和大小的简单查找表。这个模块常用来保存词嵌入和用下标检索它们。模块的输入是一个下标的列表,输出是对应的词嵌入。
LSTM层:

  1. input_size: 输入特征维数,即每一行输入元素的个数
  2. hidden_size: 隐藏层状态的维数,即隐藏层节点的个数,这个和单层感知器的结构是类似的。
  3. num_layers: LSTM 堆叠的层数,默认值是1层,如果设置为2,第二个LSTM接收第一个LSTM的计算结果。
  4. batch_first: 输入输出的第一维是否为 batch_size,默认值 False。
  5. dropout: 默认值0。是否在除最后一个 RNN 层外的其他 RNN 层后面加 dropout 层。
  6. bidirectional: 是否是双向 RNN,默认为:false,若为 true,则:num_directions=2,否则为1。

全连接层一般适用于分类。

# model.py
import torch


class LSTMModel(torch.nn.Module):
    def __init__(self, embedding, embedding_dim, hidden_dim, num_layers, dropout=0.5, requires_grad=True):
        super(LSTMModel, self).__init__()
        # 这是一个保存了固定字典和大小的简单查找表。这个模块常用来保存词嵌入和用下标检索它们。模块的输入是一个下标的列表,输出是对应的词嵌入。
        # embedding: (嵌入字典的大小, 每个嵌入向量的维度)
        self.embedding = torch.nn.Embedding(embedding.size(0), embedding.size(1))
        # 将一个不可训练的类型为Tensor的参数转化为可训练的类型为parameter的参数,并将这个参数绑定到module里面,成为module中可训练的参数。
        self.embedding.weight = torch.nn.Parameter(embedding, requires_grad=requires_grad)

        self.LSTM = torch.nn.LSTM(embedding_dim, hidden_dim, num_layers=num_layers, batch_first=True)

        self.classifier = torch.nn.Sequential(
            torch.nn.Dropout(dropout),
            torch.nn.Linear(hidden_dim, 1),
            torch.nn.Sigmoid()
        )

    def forward(self, inputs):
        inputs = self.embedding(inputs)
        x, _ = self.LSTM(inputs, None)
        # x.shape = (batch_size, seq_len, hidden_size)
        # 取用 LSTM 最后一个的 hidden state
        x = x[:, -1, :]
        x = self.classifier(x)

        return x

6. 训练模型

模型训练由两部分组成:train、validate

# train.py
import torch
from torch import nn
import torch.optim as optim
from utils import evaluate
device = 'cuda' if torch.cuda.is_available() else 'cpu'


def train(train_loader, model, criterion, optimizer, epoch):
    # 將 model 的模式设定为 train,这样 optimizer 就可以更新 model 的参数
    model.train()

    train_len = len(train_loader)
    total_loss, total_acc = 0, 0

    for i, (inputs, labels) in enumerate(train_loader):
        # 1. 放到GPU上
        inputs = inputs.to(device, dtype=torch.long)
        labels = labels.to(device, dtype=torch.float)  # 类型为float
        # 2. 清空梯度
        optimizer.zero_grad()
        # 3. 计算输出
        outputs = model(inputs)
        outputs = outputs.squeeze()  # 去掉最外面的 dimension
        # 4. 计算损失
        loss = criterion(outputs, labels)
        total_loss += loss.item()
        # 5.预测结果
        correct = evaluate(outputs, labels)
        total_acc += (correct / batch_size)
        # 6. 反向传播
        loss.backward()
        # 7. 更新梯度
        optimizer.step()
        print('[ Epoch{}: {}/{} ] loss:{:.3f} acc:{:.3f} '.
              format(epoch + 1, i + 1, batch_size, loss.item(), correct * 100 / batch_size), end='\r')
    print('\nTrain | Loss:{:.5f} Acc: {:.3f}'.format(total_loss / train_len, total_acc / train_len * 100))


def validate(val_loader, model, criterion):
    model.eval()  # 將 model 的模式设定为 eval,固定model的参数

    val_len = len(val_loader)

    with torch.no_grad():
        total_loss, total_acc = 0, 0
        for i, (inputs, labels) in enumerate(val_loader):
            # 1. 放到GPU上
            inputs = inputs.to(device, dtype=torch.long)
            labels = labels.to(device, dtype=torch.float)
            # 2. 计算输出
            outputs = model(inputs)
            outputs = outputs.squeeze()
            # 3. 计算损失
            loss = criterion(outputs, labels)
            total_loss += loss.item()
            # 4. 预测结果
            correct = evaluate(outputs, labels)
            total_acc += (correct / batch_size)
        print("Valid | Loss:{:.5f} Acc: {:.3f} ".format(total_loss / val_len, total_acc / val_len * 100))
    print('-----------------------------------------------')

    return total_acc / val_len * 100

7. 主函数

通过主函数将前面各部分组织起来

# train.py
# 各数据集的路径
path_prefix = './data'
train_with_label = os.path.join(path_prefix, 'training_label.txt')
train_no_label = os.path.join(path_prefix, 'training_nolabel.txt')

# word2vec模型文件路径
w2v_path = os.path.join(path_prefix, 'word2vec.model')

# 定义句子长度、是否固定 embedding、batch 大小、定义训练次数 epoch、learning rate 的值、model 的保存路径
requires_grad = False
sen_len = 20
model_dir = os.path.join(path_prefix, 'model/')
batch_size = 32
epochs = 10
lr = 0.001


def main():
    # load data
    data_x, data_y = read_file(train_with_label)

    # data pre_processing
    preprocess = DataPreprocess(data_x, sen_len, w2v_path=w2v_path)
    embedding = preprocess.make_embedding()
    data_x = preprocess.sentence_word2idx()
    data_y = preprocess.labels2tensor(data_y)

    # split data
    x_train, x_test, y_train, y_test = train_test_split(data_x, data_y, test_size=0.3, random_state=5)
    # 构造Dataset
    train_dataset = TwitterDataset(x_train, y_train)
    val_dataset = TwitterDataset(x_train, y_train)

    # preparing the training loader
    train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)
    print('Training loader prepared.')
    # preparing the validation loader
    val_loader = DataLoader(dataset=val_dataset, batch_size=batch_size, shuffle=False)
    print('Validation loader prepared.')

    # load model
    model = LSTMModel(
        embedding,
        embedding_dim=300,
        hidden_dim=128,
        num_layers=1,
        dropout=0.5,
        requires_grad=requires_grad
    ).to(device)

    # 返回model中的参数的总数目
    total = sum(p.numel() for p in model.parameters())
    trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
    print('\nstart training, parameter total:{}, trainable:{}\n'.format(total, trainable))

    # loss function is binary cross entropy loss, 常见的二分类损失函数
    criterion = nn.BCELoss()
    # 使用Adam优化器
    optimizer = optim.Adam(model.parameters(), lr=lr)

    best_acc = 0.

    # run epochs
    for epoch in range(epochs):
        # train for one epoch
        train(train_loader, model, criterion, optimizer, epoch)

        # evaluate on validation set
        total_acc = validate(val_loader, model, criterion)

        if total_acc > best_acc:
            # 如果 validation 的结果好于之前所有的结果,就把当下的模型保存
            best_acc = total_acc
            # torch.save(model, "{}/val_acc_{:.3f}.model".format(model_dir,total_acc/v_batch*100))
            torch.save(model, "{}/ckpt.model".format(model_dir))
            print('saving model with acc {:.3f}'.format(total_acc / len(val_loader) * 100))

8. 预测数据

在存储训练好的模型后,我们对无标签的测试集进行预测,并上传到Kaggle上。

# test.py
import torch
from torch.utils.data import DataLoader
import os
import pandas as pd
from data_loader import TwitterDataset
from utils import read_file
from pre_processing import DataPreprocess
device = 'cuda' if torch.cuda.is_available() else 'cpu'


# 各数据集的路径
path_prefix = './data'
test_data = os.path.join(path_prefix, 'testing_data.txt')

# word2vec模型文件路径
w2v_path = os.path.join(path_prefix, 'word2vec.model')

# 定义句子长度、是否固定 embedding、batch 大小、定义训练次数 epoch、learning rate 的值、model 的保存路径
requires_grad = False
sen_len = 20
model_dir = './data'
batch_size = 256

# 读取测试文件
test_x = read_file(test_data)
# 测试集预处理
prepocess = DataPreprocess(test_x, sen_len, w2v_path=w2v_path)
embedding = prepocess.make_embedding()
data_x = prepocess.sentence_word2idx()
# 构造Dataset
test_dataset = TwitterDataset(data_x, None)
# 获取dataloader
test_loader = DataLoader(test_dataset, batch_size, shuffle=False)
print('Testing loader prepared.')

# load model
model = torch.load(os.path.join(model_dir, 'ckpt.model'))

# 模型预测
model.eval()
ret_output = []
with torch.no_grad():
    for i, inputs in enumerate(test_loader):
        # 1. 放到GPU上
        inputs = inputs.to(device, dtype=torch.long)
        # 2. 计算输出
        outputs = model(inputs)
        outputs = outputs.squeeze()
        # 3. 预测结果
        outputs[outputs >= 0.5] = 1
        outputs[outputs < 0.5] = 0
        ret_output += outputs.int().tolist()

# 写到.csv文件中
tmp = pd.DataFrame({"id": [str(i) for i in range(len(test_x))], "label": ret_output})
print("Saving csv ...")
tmp.to_csv(os.path.join(path_prefix, 'predict.csv'), index=False)
print("Predicting finished.")

9. 完整代码

文本分类(LSTM+PyTorch)
数据集下载链接

参考文献

LSTM文本分类实战
LSTM算法实现文本分类(PyTorch)
Pytorch实战__LSTM做文本分类
ChineseNlpCorpus

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/136112.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

回调函数、qsort函数、sort函数与lambda表达式

目录 目录 1、回调函数 2、sort函数 3、lambda表达式 4、qsort与sort函数使用lambda表达式 1、回调函数 回调函数就是一个通过函数指针调用的函数。如果你把函数的指针&#xff08;地址&#xff09;作为参数传递给另一 个函数&#xff0c;当这个指针被用来调用其所指向的函…

凝心聚力 开源共建 | 统信软件参与成立OpenKunlun开源固件社区

11月22日&#xff0c;统信软件携手国内固件厂商、处理器厂商、外设板卡厂商等数十家产业同仁参与共建的OpenKunlun开源固件社区正式成立&#xff01; OpenKunlun社区是在自愿、开源、平等和协作的基础上&#xff0c;由基础软硬件企业、高等院校、个人开发者共同参与的非营利性开…

STM32——STM32中断系统与EXTI外部中断

文章目录一、中断系统二、STM32中断系统三、NVIC&#xff08;嵌套中断向量控制器&#xff09;NVIC基本结构NVIC优先级分组四、EXTI&#xff08;外部中断&#xff09;EXTI简介EXTI基本结构AFIO复用IO口EXTI框图五、对射式红外传感器计次电路设计关键函数EXTI库函数文件&#xff…

SpringMVC与SpringBoot响应请求的流程

SpringMVC是基于Servlet的MVC模型&#xff0c;Model&#xff1a;一个或多个javabean对象&#xff0c;用于存储数据和业务逻辑&#xff1b;View&#xff1a;一个或多个jsp页面&#xff0c;拿到控制器提交的数据为模型提供数据显示&#xff1b;Controller&#xff1a;一个或多个s…

【消息中间件】RabbitMQ的工作模式

前 言 &#x1f349; 作者简介&#xff1a;半旧518&#xff0c;长跑型选手&#xff0c;立志坚持写10年博客&#xff0c;专注于java后端 ☕专栏简介&#xff1a;深入、全面、系统的介绍消息中间件 &#x1f330; 文章简介&#xff1a;本文将介绍RabbitMQ的工作模式 &#x1f353…

cubeIDE开发, stm32的CRC计算CubeMX配置及HAL库底层实现分析

一、stm32的CRC 1.1 CRC的简介及MCU关联说明 STM32的CRC(Cyclic Redundancy Check&#xff0c;循环冗余校验)计算单元使用一个固定的多项式发生器&#xff0c;从一个32位的数据字产生一个CRC码。在业务开发应用中&#xff0c;会基于CRC的技术用于验证数据传输或存储完整性。在E…

【python基础_05】面向对象1_对象和类、魔术方法

文章目录1. 类和对象1.1 使用对象组织数据的模版1.2 成员变量和成员方法1.3 实现代码2. 内置方法&#xff08;魔术方法&#xff09;2.1 构造方法&#xff1a; __init__()2.2 __call__()2.3 __len__()2.3 __str__()2.4 __getitem__()2.5 __setitem__()2.6 __delitem__()2.7 __lt…

【从零开始学习深度学习】35. 门控循环神经网络之门控循环单元(gated recurrent unit,GRU)介绍、Pytorch实现GRU并进行训练预测

在循环神经网络中&#xff0c;当时间步数较大或者时间步较小时&#xff0c;循环神经网络的梯度较容易出现衰减或爆炸。上一篇文章中介绍的裁剪梯度可以应对梯度爆炸&#xff0c;但无法解决梯度衰减的问题。因此&#xff0c;循环神经网络在实际中较难捕捉时间序列中时间步距离较…

Elastic-Job分布式任务调度(1):概述

1 什么是任务调度 我们可以先思考一下下面业务场景的解决方案&#xff1a; 某电商系统需要在每天上午10点&#xff0c;下午3点&#xff0c;晚上8点发放一批优惠券。某银行系统需要在信用卡到期还款日的前三天进行短信提醒。某财务系统需要在每天凌晨0:10结算前一天的财务数据…

【自学Python】Linux安装Python

Linux安装Python Python下载 Python下载地址 https://www.python.org/ftp/python/3.7.4/Python-3.7.4.tar.xzPython下载 我们在 Linux 终端中&#xff0c;直接使用 wget 命令&#xff0c;下载 Linux 版 Python 的安装包&#xff0c;我们在终端输入以下命令&#xff1a; wg…

PAT乙级|1094 谷歌的招聘

题源https://pintia.cn/problem-sets/994805260223102976/exam/problems/1071785997033074688 提交1&#xff1a;一个用例没过 提交2&#xff1a;AC 错因&#xff1a;输出需为字符串&#xff0c;例如在 200236 中找 4 位素数&#xff0c;解是0023 关键&#xff1a;第33行代码…

linphone android sdk 源码下载编译

前言 前面的有写过Android 使用Linphone SDK开发SIP客户端相关的文章, 在后续的开发过程中, 为了更深入了解linphone, 便尝试下载SDK源码自行编译. 关于linphone这里不作过多介绍, 可以参考前面的文章. Linphone-SDK 是一个将 Liblinphone 及其依赖项捆绑为 git 子模块的项目&a…

HTC FOCUS3在PC端串流FOHEART H1数据手套(手柄)

本教程介绍使用FOHEART H1数据手套与HTC手柄驱动VR中的虚拟手运动&#xff0c;实现手部的追踪及定位。 本教程内容与之前使用腕带定位&#xff08;HTC FOCUS3在PC端串流FOHEART H1数据手套&#xff08;腕带&#xff09;&#xff09;不同&#xff0c;这次我们使用头显中自带的…

【Kuangbin简单DP】挤奶时间

4561. 挤奶时间 - AcWing题库 题意&#xff1a; 思路&#xff1a; 一开始的思路是把这么多的区间当作物品&#xff0c;然后选与不选&#xff0c;这样去搞线性DP 显然是不行的&#xff0c;因为这样答案就不知道怎么统计了 而且&#xff0c;我们是设阶段&#xff01;&#xf…

HSK汉语考试变革,您需要了解以下几点

2023年HSK考试可能有哪些变化汉语考试难度增加了还是减低了&#xff1f; 对现在的课程和教材有影响&#xff1f; 汉语老师怎么样应对&#xff1f;HSK考试变化猜想1.HSK3级考试和HSKK初级结合在一起 2.HSK4级考试和HSKK中级结合在一起 3.HSK5,6级考试和HSKK高级结合在一起HSKK考…

INTERSPEECH 2022|面向零样本声音克隆的内容相关细粒度说话人表征方法

本文由清华大学与腾讯 AI Lab、香港中文大学合作。 零样本说话人自适应&#xff08;zero-shot speaker adaptation&#xff09;&#xff0c;或称为零样本声音克隆&#xff0c;旨在根据任意一条参考语音&#xff08;reference speech&#xff09;合成训练过程中从未见过的说话人…

Leetcode:239. 滑动窗口最大值(C++)

目录 问题描述&#xff1a; 实现代码和解析&#xff1a; 暴力法&#xff08;会超时&#xff09;&#xff1a; 原理思路&#xff1a; 单调队列法&#xff1a; 原理思路&#xff1a; 单调队列&#xff1a; 模拟过程&#xff1a; 问题描述&#xff1a; 给你一个整数数组…

Python基础知识(二)

目录 顺序语句 条件语句 条件语句书写格式一及对比&#xff1a;if条件语句 条件语句书写格式二及对比&#xff1a;if...else...语句 条件语句书写格式三及对比&#xff1a;if...elif...else语句 空语句pass 条件语句的总结&#xff1a; 循环语句 while循环 与c/java/…

对于Muduo主从Reactor模式的理解

从12月20号开始看Muduo网络库&#xff0c;到28号的时候弄懂了EventLoop, Poller, Channel是怎么一回事&#xff0c;一番琢磨之后觉得还是应该发到博客上跟大家分享&#xff0c;特此记录。 对照linyacool那个webserver的实现&#xff0c;再看了一遍muduo的EventLoop, Poller ,C…

IDEA快速启动多个微服务模块 -idea如何开启Run DashBoard

文章目录 缘起 Run DashBoard面板如何开启开启 Run DashBoard 注意&#xff1a; 缘起 在idea里面如果需要启动多个项目的话&#xff0c;尤其是是比如微服务项目&#xff0c;动辄要启动五六个七八个应用&#xff0c;如果通过右上角那边启动会很不方便&#xff0c;你需要选择…