基于TF_IDF和Bagging的文本分类全过程

tips:该模型用于baseline!

前言

基于TF-IDF特征工程和Bagging随机森林算法(sklearn - RandomForestClassifier)实现文本分类的基线模型(Baseline Model)功能,最终目的是通过模型算法提高推荐系统的用户点击率和访问量


随机森林算法 sklearn-RandomForestClassifier

集成学习的思想:多个基学习器组合成一个精度更高的模型

bagging随机森林算法,有放回采样,并行,平权投票,预测结果


特征工程TF-IDF

TF词频,IDF逆文档频率

tf:样本中某特征的数量➗样本中总特征数量

idf:log(总样本数➗(出现某特征的样本数量+1) ) +1

简单高效计算速度快,有效过滤掉停用词,发现文档中关键词

无语捕捉语义,无法理解上下文


整体实现逻辑如下:


EDA分析

Config统一管理文件分发,EDA完成整体数据分析


数据预处理

Config完成模型文件路径分发

EDA_processing完成数据预处理(jieba分词)


模型训练-评估-保存

TF_IDF完成特征工程提取

train_test_split完成数据集划分

RandomForestClassifier实例化模型

模型训练-模型评估(准确率,精确率,召回率,F1-score)

模型和tfidf向量化器保存


模型验证

加载模型和tfidf向量化器

加载验证数据集并通过tfidf向量化器将数据向量化

模型评估(准确率,精确率,召回率,F1-score)

验证结果持久化存储


开放推理方法

加载模型和tfidf向量化器

数据jieba分词并向量化

模型推理,获取标签名称并返回


基于flask构建后端服务

基于falsk+html或者streamlit构建前端服务

数据集整体分析

本部分完成两个py脚本 config.py dataEDA.py

config.py 实现文件数据及路径的同一管理分发

dataEDA (Exploratory Data Analysis)探索性数据分析,观测相关数据量,样本和类别占比是否平衡

config文件

python 复制代码
# 存储路径/数据库配置信息/API的信息等

# 创建类
class Config(object):
    # 初始化文件路径属性
    def __init__(self):
        # 文件路径
        self.train_datapath = './train.txt'  # 相对路径, 也可以使用绝对路径
        self.test_datapath = './test.txt'
        self.dev_datapath = './dev.txt'
        self.class_datapath = './class.txt'


if __name__ == '__main__':
    # 实例化对象
    config = Config()
    # 获取对象属性
    class_file = config.class_datapath
    with open(class_file, 'r', encoding='utf-8') as f:
        data = f.read()
        print(data)

数据EDA

python 复制代码
import pandas as pd
from config import Config
from collections import Counter  # 聚合
import seaborn as sns
import matplotlib.pyplot as plt

# todo:1-实例config类对象, 获取文件路径属性
config = Config()


# todo:2-定义函数, 进行探索性数据分析
def dataEDA(path):
    # todo:2.1-加载数据集
    # sep: 分隔符
    # names: 指定列名
    data = pd.read_csv(path, sep='\t', names=['text', 'label'])
    print('查看前5条数据--->\n', data.head())

    # todo:2.2-探索性数据分析 EDA
    # 数据集行列数
    print('查看数据集的行列数--->\n', data.shape)
    # 数据集基本信息
    data.info()
    print('=' * 80)

    # todo:2.3-不同标签的样本数/占比
    label_counts = Counter(data['label'])  # 分组聚合
    print('不同标签的样本数--->\n', type(label_counts), label_counts)

    # label_counts.items(): dict.items()->获取字典中的 (key, value)
    for label, count in label_counts.items():
        print(f'标签{label}的样本数是{count}')

    # 占比
    data_len = len(data)  # 统计数据集总样本数
    for label, count in label_counts.items():
        ratio = (count / data_len) * 100  # 当前标签的样本数/总样本数
        print(f'标签{label}的样本占比是{ratio:.2f}%')


    # 通过绘图进行数据分析
    plt.figure(figsize=(16, 8))
    sns.countplot(data=data, x='label')
    plt.show()

    print('=' * 80)
    # 文本长度统计 平均值/标准差/最大值/最小值
    # 增加一列文本字符长度列
    data['text_length'] = data['text'].str.len()
    print(data.head())

    # 绘图
    plt.figure(figsize=(16, 8))
    sns.countplot(data=data, x='text_length')
    plt.show()

    print('文本平均长度--->\n', data['text_length'].mean())
    print('文本长度标准差--->\n', data['text_length'].std())
    print('文本最大长度--->\n', data['text_length'].max())
    print('文本最小长度--->\n', data['text_length'].min())


if __name__ == '__main__':
    # 获取文件路径属性
    train_datapath = config.train_datapath
    test_datapath = config.test_datapath
    dataEDA(train_datapath)
    dataEDA(test_datapath)

随机森林模型构建

我们这里拿随机森林算法实现的主要是个基线模型(Baseline Model)

Baseline Model主要的目标是: 1.快速验证可行性 2.提供性能参考 3.降低开发成本 4.发现问题

bagging模式的config分发文件

python 复制代码
import os


# 创建config类
class Config(object):
    # 初始化路径属性
    def __init__(self):
        # 原始文件路径
        self.train_datapath = '../01-data/train.txt'
        self.test_datapath = '../01-data/test.txt'
        self.dev_datapath = '../01-data/dev.txt'
        self.class_datapath = '../01-data/class.txt'
        self.stop_words_path = '../01-data/stopwords.txt'

        # 处理后的文件路径
        self.process_train_datapath = './data/process_train.csv'
        self.process_test_datapath = './data/process_test.csv'
        self.process_dev_datapath = './data/process_dev.csv'

        # 模型保存路径
        self.rf_model_save_path = './save_model'

        # 预测结果保存路径
        if not os.path.exists('./result'):  # 如果不存在result文件夹,则创建
            os.mkdir('./result')
        self.model_predict_result = './result'


if __name__ == '__main__':
    # 创建对象
    config = Config()
    print(config.train_datapath)
    print(config.rf_model_save_path)

数据预处理processing

python 复制代码
from config import Config
import pandas as pd
import jieba


# todo:1-实例化config对象, 获取原始文件路径属性
config = Config()
# 获取原始文件的路径  训练集 测试集 验证集
file_paths = [config.train_datapath, config.test_datapath, config.dev_datapath]

# todo:2-rf数据处理
for file_path in file_paths:
    # todo:2-1 加载数据集
    data = pd.read_csv(file_path, encoding='utf-8', sep='\t', names=['text', 'label'])
    print('查看数据集1--->\n', data.head())

    # todo:2-2 新增一列, 对text列分词后合并的字符串数据  -> '金科 西府  名墅 天成'
    # x->text列中的每一行数据
    # jieba.lcut(x)->分词
    # [:30]->切片, 没有实际的业务意义, 可以不用进行切片
    data['words'] = data['text'].apply(func=lambda x: ' '.join(jieba.lcut(x)[:30]))
    print('查看数据集2--->\n', data.head())

    # todo:2-3 保存处理后的数据集
    if 'train' in file_path:
        data.to_csv(config.process_train_datapath, index=False, encoding='utf-8')
        print(f'成功保存处理后的训练集{config.process_train_datapath}')
    elif 'test' in file_path:
        data.to_csv(config.process_test_datapath, index=False, encoding='utf-8')
        print(f'成功保存处理后的测试集{config.process_test_datapath}')
    elif 'dev' in file_path:
        data.to_csv(config.process_dev_datapath, index=False, encoding='utf-8')
        print(f'成功保存处理后的验证集{config.process_dev_datapath}')

模型训练

TF-IDF特征工程思路

我们有很多方式可以实现文本到数值的转换,比如one-hot,我们在这里使用tf-idf实现文本的数值化,它的核心是将文本转换为一组数字(向量),这个过程就是文本向量化或者特征提取

**tf-idf 核心思想:**一个特征(词语)在一条样本(句子)中出现的次数越多,同时在所有样本(句子)中出现的次数越少,那就代表越是独特和稀有,越是能代表其主题,其权重就应该越高.

TF term frequency 词频, IDF inverse document frequency 逆文档频率

TF-IDF计算流程

TF:词语A在文档B中出现的次数➗文档B中的总词数

IDF:总文档数ALL➗出现词语A的文档数 取对数log

IDF分母中的1是为了不让分母为0,也可以是1e-9, 对数的结果+1是为了不让IDF小于0
TF-IDF优点

简单高效,计算速度快

效果显著,能有效过滤掉停用词,发现文档中的关键主题词

TF-IDF缺点

无法捕捉语义 cat和猫被视为两个词

无法理解上下文

对新词不友好

权重倾向稀有词

应用场景

信息检索和搜索引擎

文本分类和聚类

关键词提取

文本相似度计算

特征工程实现
python 复制代码
import pandas as pd
import pickle
from sklearn.feature_extraction.text import TfidfVectorizer  # IF-IDF
from sklearn.model_selection import train_test_split  # 模拟数据分割
from sklearn.ensemble import RandomForestClassifier  # 随机森林分类算法
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score  # 评估指标
from config import Config
from tqdm import tqdm  # 引入 tqdm 用于进度条
import warnings
import time

warnings.filterwarnings("ignore")

pd.set_option('display.expand_frame_repr', False)  # 避免宽表格换行
pd.set_option('display.max_columns', None)  # 确保所有列可见
conf = Config()

# todo:1-加载训练数据集
train_data = pd.read_csv(conf.process_train_datapath, encoding='utf-8')
print('查看数据集--->\n', train_data.head())

# todo:2-获取words和lables两列数据
words = train_data['words']
labels = train_data['label']
print('查看words数据--->\n', words.head())
print('查看labels数据--->\n', labels.head())

# todo:3-tf-idf向量化计算
# 加载停用词词表, 过滤掉
with open(conf.stop_words_path, 'r', encoding='utf-8') as f:
    stop_words = f.read().split('\n')
print('停用词数量--->\n', len(stop_words), stop_words)

# 实例化TfidfVectorizer对象
tfidf = TfidfVectorizer(stop_words=stop_words)
# 训练并转换生成词向量
features = tfidf.fit_transform(words)
print('查看tf-idf信息--->\n', features.shape,'\n', features)
# 查看tf-idf信息
# print('查看tf-idf信息--->\n', len(tfidf.get_feature_names_out()))
# print(list(tfidf.get_feature_names_out()))
# 查看词表, 词2id
print(tfidf.vocabulary_)

# todo:4-数据集划分 可选,使用的是训练集
x_train, x_test, y_train, y_test = train_test_split(features, labels, test_size=0.2, random_state=22)

# todo:5-模型训练
# 实例化模型对象
# n_jobs:使用所有可用的cpu核数进行训练,加快训练速度
# verbose:打印训练进度
model = RandomForestClassifier(n_jobs=-1, verbose=1)
# 训练
for _ in tqdm(range(1), desc='RF模型正在训练中...'):
    start_time = time.time()
    model.fit(x_train, y_train)  # 模型训练
    end_time = time.time()
    print(f'模型训练耗时{(end_time - start_time):.2f}秒')

# 加载模型
# with open(conf.rf_model_save_path + '/rf_model.pkl', 'rb') as f:
#     model = pickle.load(f)

# todo:6-模型评估
# 预测结果
y_pred = model.predict(x_test)
print('准确率--->\n', accuracy_score(y_test, y_pred))
print('精确率--->\n', precision_score(y_test, y_pred, average='micro'))
print('召回率--->\n', recall_score(y_test, y_pred, average='micro'))
print('F1-score--->\n', f1_score(y_test, y_pred, average='micro'))

# # todo:7-模型/向量化器保存
# rf模型  算法模型
with open(conf.rf_model_save_path + '/rf_model.pkl', 'wb') as f:
    pickle.dump(model, f)

# 向量化器  tf-idf模型
with open(conf.rf_model_save_path + '/tfidf_vectorizer.pkl', 'wb') as f:
    pickle.dump(tfidf, f)

模型预测评估

python 复制代码
import pandas as pd
import pickle
from config import Config
import warnings
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score

warnings.filterwarnings('ignore')

# 设置pandas显示选项
pd.set_option('display.max_columns', None)
# 加载配置
conf = Config()

# todo:1-加载rf模型和向量化器
with open(conf.rf_model_save_path + '/rf_model.pkl', 'rb') as f:
    model = pickle.load(f)

# TF-IDF向量化器
with open(conf.rf_model_save_path + '/tfidf_vectorizer.pkl', 'rb') as f:
    tfidf: TfidfVectorizer = pickle.load(f)

# todo:2-加载验证数据集
dev_data = pd.read_csv(conf.process_dev_datapath, encoding='utf-8')
print('查看数据集--->\n', dev_data.head())

# todo:3-通过向量化器进行向量化
dev_features = tfidf.transform(dev_data['words'])
print('查看dev_features信息--->\n', dev_features.shape, dev_features)

# todo:4-模型评估
y_pred = model.predict(dev_features)
print('准确率--->\n', accuracy_score(dev_data['label'], y_pred))
print('精确率--->\n', precision_score(dev_data['label'], y_pred, average='micro'))
print('召回率--->\n', recall_score(dev_data['label'], y_pred, average='micro'))
print('f1-score--->\n', f1_score(dev_data['label'], y_pred, average='micro'))

# todo:5-预测结果持久化存储
df = pd.DataFrame(data={'words': dev_data['words'], 'predicted_label': y_pred})
print('查看df--->\n', df.head())
df.to_csv(conf.model_predict_result + '/dev_predictions.csv', index=False)

向外提供模型推理func

python 复制代码
import jieba
import pandas as pd
import pickle
from config import Config
import warnings

warnings.filterwarnings('ignore')
# 设置pandas显示选项
pd.set_option('display.max_columns', None)


# 后端接口 API
def predict(data):
    """
    后端预测接口
    :param data: {text: xxxxxx}
    :return: data -> {text: xxxxx, pred_class: xxxx}
    """
    # todo:1-实例化Config类对象
    config = Config()

    # todo:2-加载模型和向量化器
    with open(config.rf_model_save_path + '/rf_model.pkl', 'rb') as f:
        model = pickle.load(f)

    with open(config.rf_model_save_path + '/tfidf_vectorizer.pkl', 'rb') as f:
        tfidf = pickle.load(f)

    # todo:3-data数据进行处理
    # 分词, 合并字符串
    words = ' '.join(jieba.lcut(data['text'])[:30])
    # print('words--->\n', words)
    # 向量化
    features = tfidf.transform([words])
    # print('features--->\n', features)

    # todo:4-模型推理
    y_pred = model.predict(features)
    # print('预测结果--->\n', y_pred)

    # todo:5-获取标签名称
    # 获取id2class映射关系字典
    id2class = {}
    with open(config.class_datapath, 'r', encoding='utf-8') as f:
        class_data = f.readlines()
        # print('class_data--->\n', class_data)
        for i, line in enumerate(class_data):
            id2class[i] = line.strip()
    # print('id2class--->\n', id2class)
    # 获取预测标签名称保存到data中
    # id2class[y_pred[0]]: 字典key获取value
    data['pred_class'] = id2class[y_pred[0]]
    # print('预测结果--->\n', data)
    return data


if __name__ == '__main__':
    data = {"text": "体验2D巅峰 倚天屠龙记十大创新概览"}
    data = predict(data)
    print('预测结果--->\n', data)

模型部署

工业界中的AI是指"能落地的AI", 即指在生产环境中可以部署并提供在线, 或离线作业的模型.

服务端

构建flask应用:
注:requests 是一个简单易用的 HTTP 客户端工具,允许我们通过 Python 代码向服务器发送 HTTP 请求并处理响应。

基于flask的后端api服务

python 复制代码
from flask import Flask, request, jsonify
from predict_fun import predict
import warnings

warnings.filterwarnings('ignore')

# todo:1-创建app对象
app = Flask(__name__)


# todo:2-创建路由
@app.route('/predict', methods=['POST'])
def predict_api():
    # 获取前端数据
    data = request.get_json()
    print('data--->\n', data)
    # 判断是否有数据, 没有收集异常信息
    if not data or 'text' not in data:
        # 状态码: 2xx->请求成功 3xx->重定向 4xx->请求端报错 5xx->服务端报错
        return jsonify({'error': 'Missing text field in JSON'}), 400
    # 调用模型预测接口实现预测
    result = predict(data)
    print('result--->\n', result)
    # 返回json结果
    return jsonify(result)


if __name__ == '__main__':
    # 启动服务端
    app.run(host='0.0.0.0', port=8000, debug=True)
    # 0.0.0.0 表示监听所有地址

flask api服务测试

python 复制代码
# 不要求掌握
import requests
import time

# 定义预测接口地址
url = 'http://127.0.0.1:8000/predict'

# 构造请求数据
data = {'text': "中国人民公安大学2012年硕士研究生目录及书目"}

start_time = time.time()

try:
    # 发送post请求, 获取响应对象
    response = requests.post(url, json=data)
    print('response--->\n', response)
    # 耗时
    duration = (time.time() - start_time) * 1000  # ms
    print(f'耗时: {duration:.2f}ms')
    # 判断状态码是否为200, 如果是, 获取响应数据
    if response.status_code == 200:
        result = response.json()
        print('result--->\n', type(result), result)
        print('预测结果--->\n', result['pred_class'])
    # 如果不是, 获取错误信息
    else:
        error = response.json()['error']
        print(f"请求失败: {response.status_code}, {error}")
except Exception as e:
    print(f"请求出错: {str(e)}")

基于streamlit的前端服务

python 复制代码
import streamlit as st
import requests
import time

# todo:1-设置页面标题
st.title('文本分类系统')

# todo:2-创建输入框
data_text = st.text_area('请输入预测文本:', "中国人民公安大学2012年硕士研究生目录及书目")

# todo:3-创建预测按钮
if st.button('预测'):
    # todo:4-调用模型推理接口实现预测
    start_time = time.time()
    try:
        # 构造请求数据
        data = {'text': data_text}
        url = 'http://127.0.0.1:8000/predict'
        # 发送post请求, 获取响应对象
        response = requests.post(url, json=data)
        duration = (time.time() - start_time) * 1000
        # 判断状态码是否为200
        if response.status_code == 200:
            result = response.json()
            # todo:5-显示预测结果
            st.success(f"预测结果: {result['pred_class']}")
            st.info(f"请求耗时: {duration:.2f}ms")
        else:
            st.error(f"请求失败: {response.json()['error']}")
    except Exception as e:
        st.error(f"请求出错: {str(e)}")

# todo:6-页面提示内容
st.write("请确保 Flask API 服务已在 localhost:8000 运行")

基于flask+html文件的前端服务

python 复制代码
from flask import Flask, render_template, request, jsonify
import time
from predict_fun import predict
import json

app = Flask(__name__)


@app.route('/')
def index():
    return render_template('index.html')


@app.route('/predict', methods=['POST'])
def predict_route():
    try:
        data = request.get_json()
        if not data or 'text' not in data:
            return jsonify({"error": "缺少 'text' 字段"}), 400

        start_time = time.time()
        result = predict(data)  # 调用你的预测函数
        print('result--->\n', result)
        end_time = time.time()

        duration = round((end_time - start_time) * 1000, 2)  # 毫秒

        return jsonify({
            "pred_class": result.get("pred_class", None),
            "duration_ms": duration
        })
    except Exception as e:
        return jsonify({"error": str(e)}), 500


if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=5000)
相关推荐
树獭叔叔2 小时前
FFN 激活函数深度解析:从 ReLU 到 SwiGLU 的演进之路
算法·aigc·openai
啊我不会诶2 小时前
最小生成树
c++·笔记·学习·算法
liuyao_xianhui3 小时前
优选算法_栈_删除字符中的所有相邻重复项_C++
开发语言·数据结构·c++·python·算法·leetcode·链表
STLearner3 小时前
AI论文速读 | 元认知监控赋能深度搜索:认知神经科学启发的分层优化框架
大数据·论文阅读·人工智能·python·深度学习·学习·机器学习
WolfGang0073213 小时前
代码随想录算法训练营 Day22 | 回溯算法 part04
数据结构·算法
tankeven3 小时前
HJ154 kotori和素因子
c++·算法
Shirley~~3 小时前
力扣hot100:相交链表
前端·算法
会编程的土豆3 小时前
【leetcode hot 100】二叉树
算法·leetcode
2501_926978333 小时前
《与AI的妄想对话:如何给机器人造灵魂?》
人工智能·深度学习·机器学习·ai写作·agi