mirror of
https://github.com/ooyinet/WeClone.git
synced 2026-08-29 01:41:27 +08:00
更新pyproject.toml以调整类型检查配置,修改README.md以改善数据预处理说明,重构settings.json以优化数据集参数,更新qa_generator.py以支持新的消息处理逻辑,删除旧的测试文件test_old_csv_to_json copy.py并更新相关测试用例。
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Your rule content
|
||||
- You can @ files here
|
||||
- The project uses uv as the package manager and pyproject.toml as the project configuration file.
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
|
||||
|
||||
> [!IMPORTANT]
|
||||
> WeClone仍在快速迭代期,当前效果不代表最终效果
|
||||
> 微调LLM效果很大程度取决于聊天数据的数量和质量
|
||||
> WeClone仍在快速迭代期,当前效果不代表最终效果。
|
||||
> 微调LLM效果很大程度取决于聊天数据的数量和质量。
|
||||
|
||||
### 硬件要求
|
||||
|
||||
@@ -50,15 +50,9 @@ uv pip install --group main -e .
|
||||
|
||||
### 数据预处理
|
||||
|
||||
项目默认去除了数据中的手机号、身份证号、邮箱、网址。还提供了一个禁用词词库[blocked_words](make_dataset/blocked_words.json),可以自行添加需要过滤的词句(会默认去掉包括禁用词的整句)。
|
||||
执行 `./make_dataset/csv_to_json.py` 脚本对数据进行处理。
|
||||
|
||||
在同一人连续回答多句的情况下,有三种处理方式:
|
||||
| 文件 | 处理方式 |
|
||||
| --- | --- |
|
||||
| csv_to_json.py | 用逗号连接 |
|
||||
| csv_to_json-单句回答.py(已废弃) | 只选择最长的回答作为最终数据 |
|
||||
| csv_to_json-单句多轮.py | 放在了提示词的'history'中 |
|
||||
项目默认去除了数据中的手机号、身份证号、邮箱、网址。还提供了一个禁用词词库[blocked_words](make_dataset/blocked_words.json),可以自行添加需要过滤的词句(会默认去掉包括禁用词的整句)。
|
||||
执行 `python ./make_dataset/qa_generator.py` 对数据进行处理,可以根据自己的聊天风格修改settings.json的`make_dataset_args`。
|
||||
目前仅支持时间窗口策略,根据`single_combine_time_window`将单人连续消息通过逗号连接合并为一句,根据`qa_match_time_window`匹配问答对。后续将增加大模型清洗数据的功能。
|
||||
|
||||
### 模型下载
|
||||
|
||||
|
||||
@@ -126,6 +126,15 @@ class DataProcessor:
|
||||
qa_res.append(
|
||||
{"instruction": current_instruction, "output": msg.msg}
|
||||
)
|
||||
else:
|
||||
if self.c["prompt_with_history"]:
|
||||
qa_res.append(
|
||||
CutMessage(
|
||||
is_sender=msg.is_sender,
|
||||
cut_type=msg.type_name,
|
||||
CreateTime=msg.CreateTime,
|
||||
)
|
||||
)
|
||||
# 无论是否匹配,都重置状态
|
||||
current_state = WAITING_INSTRUCTION
|
||||
current_instruction = None
|
||||
@@ -133,7 +142,7 @@ class DataProcessor:
|
||||
|
||||
return qa_res
|
||||
|
||||
def add_history_to_qa(self, qa_res: List[Dict]):
|
||||
def add_history_to_qa(self, qa_res: List[Dict]) -> List[Dict]:
|
||||
qa_res_with_history = []
|
||||
last_res = {"instruction": "", "output": "", "history": []}
|
||||
|
||||
@@ -187,10 +196,10 @@ class DataProcessor:
|
||||
ChatMessage: 合并后的消息
|
||||
"""
|
||||
base_msg = messages[0]
|
||||
combined_content = messages[0].msg.strip()
|
||||
combined_content = messages[0].msg
|
||||
|
||||
for i in messages[1:]:
|
||||
content = i.msg.strip()
|
||||
content = i.msg
|
||||
if not content:
|
||||
continue
|
||||
|
||||
@@ -343,7 +352,7 @@ class DataProcessor:
|
||||
def save_result(self, qa_res: List[Dict]):
|
||||
# 保存结果
|
||||
with open(
|
||||
f"./data/res_csv/sft/sft-{self.c['single_combine_strategy']}-{self.c['qa_match_strategy']}-my.json",
|
||||
f"./data/res_csv/sft/sft-my.json",
|
||||
"w",
|
||||
encoding="utf-8",
|
||||
) as f:
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ default = true
|
||||
|
||||
[tool.pyright]
|
||||
typeCheckingMode = "basic"
|
||||
include = ["src"]
|
||||
include = ["make_dataset"]
|
||||
exclude = ["**/archive","**/tests"]
|
||||
ignore = ["**/archive"]
|
||||
|
||||
|
||||
+6
-6
@@ -44,13 +44,13 @@
|
||||
},
|
||||
"make_dataset_args": {
|
||||
// "enable_vision_model": false,//后续实现
|
||||
"include_type": [
|
||||
"文本"
|
||||
],
|
||||
// "include_type": [
|
||||
// "文本"
|
||||
// ],
|
||||
"single_combine_strategy": "time_window", // 单人组成单句策略
|
||||
"qa_match_strategy": "time_window", // 多人组成qa策略
|
||||
"single_combine_time_window": 10, // 单人组成单句时间窗口(分钟),
|
||||
"qa_match_time_window": 60, // 多人组成qa时间窗口(分钟),
|
||||
"qa_match_strategy": "time_window", // 组成qa策略
|
||||
"single_combine_time_window": 2, // 单人组成单句时间窗口(分钟),
|
||||
"qa_match_time_window": 5, // 组成qa时间窗口(分钟),
|
||||
"prompt_with_history": false // 是否在prompt中包含历史对话
|
||||
},
|
||||
"common_args": {
|
||||
|
||||
@@ -1,311 +0,0 @@
|
||||
import csv
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
import pandas as pd
|
||||
from collections import deque
|
||||
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
root_dir = os.path.dirname(current_dir)
|
||||
sys.path.append(root_dir)
|
||||
|
||||
from make_dataset.qa_generator import DataProcessor
|
||||
|
||||
csv_folder = "./data/csv"
|
||||
# csv_folder = './data/test'
|
||||
os.chdir(root_dir)
|
||||
|
||||
print(f"当前处理目录{csv_folder}")
|
||||
|
||||
|
||||
def handle_pt_csv(csvfile):
|
||||
chat_df = pd.read_csv(csvfile)
|
||||
# 选择type_name为文本的行、is_sender为1的行
|
||||
chat_df = chat_df[chat_df["type_name"] == "文本"]
|
||||
chat_df = chat_df[chat_df["is_sender"] == 1]
|
||||
# 对每一行的content进行处理 转为dict 再取'msg'字段
|
||||
chat_df["content"] = chat_df["content"].apply(lambda x: json.loads(x)["msg"])
|
||||
# 如果content 包含 手机号、身份证号、邮箱、网址则删除这行
|
||||
chat_df = chat_df[~chat_df["content"].str.contains("1\d{10}")]
|
||||
chat_df = chat_df[~chat_df["content"].str.contains("\d{18}")]
|
||||
chat_df = chat_df[~chat_df["content"].str.contains("\w+@\w+")]
|
||||
chat_df = chat_df[~chat_df["content"].str.contains("http")]
|
||||
chat_df = chat_df[~chat_df["content"].str.contains(r"\\xa0")]
|
||||
chat_df = chat_df[~chat_df["content"].str.contains(r"\\u")]
|
||||
|
||||
# 纯content
|
||||
chat_df = chat_df["content"]
|
||||
chat_df = chat_df.dropna()
|
||||
|
||||
return chat_df
|
||||
|
||||
|
||||
def make_pt_dataset():
|
||||
csv_res = []
|
||||
# csv文件夹里全是不同聊天对象文件夹 每个文件夹里是csv文件 先遍历不同聊天对象文件夹 再遍历聊天对象的csv文件
|
||||
for chat_obj_folder in os.listdir(csv_folder):
|
||||
chat_obj_folder_path = os.path.join(csv_folder, chat_obj_folder)
|
||||
for csvfile in os.listdir(chat_obj_folder_path):
|
||||
if not csvfile.endswith(".csv"):
|
||||
continue
|
||||
csvfile_path = os.path.join(chat_obj_folder_path, csvfile)
|
||||
chat_df = handle_pt_csv(csvfile_path)
|
||||
csv_res.append(chat_df)
|
||||
|
||||
csv_res = pd.concat(csv_res)
|
||||
csv_res = csv_res.apply(lambda x: {"c": x}) # 设置数据集prompt键为c
|
||||
|
||||
csv_res.to_json("./data/res_csv/pt-my.json", orient="records", force_ascii=False)
|
||||
|
||||
|
||||
def handle_sft_csv(csvfile):
|
||||
chat_df = pd.read_csv(csvfile)
|
||||
blocked_words = json.load(
|
||||
open("./make_dataset/blocked_words.json", encoding="utf-8")
|
||||
)["blocked_words"]
|
||||
# 选择type_name为文本的行、is_sender为1的行
|
||||
# 需要保留的type_name字段名
|
||||
type_list = [
|
||||
"文本",
|
||||
"图片",
|
||||
"视频",
|
||||
"合并转发的聊天记录",
|
||||
"语音",
|
||||
"(分享)音乐",
|
||||
"(分享)卡片式链接",
|
||||
"(分享)笔记",
|
||||
"(分享)小程序",
|
||||
"(分享)收藏夹",
|
||||
"(分享)小说(猜)",
|
||||
"(分享)视频号名片",
|
||||
"(分享)视频号视频",
|
||||
"粘贴的文本", # 无法解析的分享链接
|
||||
]
|
||||
chat_df = chat_df[chat_df["type_name"].isin(values=type_list)]
|
||||
|
||||
# chat_df['content'] = chat_df['content'].apply(func=lambda x: json.loads(x)['msg'])
|
||||
chat_df["content"] = chat_df["msg"]
|
||||
|
||||
# 如果type_name为文本 并且content 包含 手机号、身份证号、邮箱、网址则删除这行
|
||||
for i in chat_df.index:
|
||||
if chat_df.loc[i, "type_name"] == "文本":
|
||||
if (
|
||||
"1\d{10}" in chat_df.loc[i, "content"]
|
||||
or "\d{18}" in chat_df.loc[i, "content"]
|
||||
or "\w+@\w+" in chat_df.loc[i, "content"]
|
||||
or "http" in chat_df.loc[i, "content"]
|
||||
or r"\\xa0" in chat_df.loc[i, "content"]
|
||||
or r"\\u" in chat_df.loc[i, "content"]
|
||||
):
|
||||
chat_df = chat_df.drop(index=i)
|
||||
continue
|
||||
for blocked_word in blocked_words:
|
||||
if blocked_word in chat_df.loc[i, "content"]:
|
||||
chat_df = chat_df.drop(index=i)
|
||||
break
|
||||
else:
|
||||
chat_df.loc[i, "content"] = ""
|
||||
|
||||
chat_df = chat_df[["is_sender", "type_name", "content", "CreateTime"]]
|
||||
chat_df = chat_df.dropna()
|
||||
# 时间格式 2021-07-07 10:27:23
|
||||
# 遍历行 相同is_sender的行合并content()遇到不同is_sender就重新开始
|
||||
# CreateTime字段保留最后的CreateTime
|
||||
chat_df["CreateTime"] = pd.to_datetime(chat_df["CreateTime"])
|
||||
|
||||
# 改到这了
|
||||
|
||||
type_list.remove("文本")
|
||||
skip_list = type_list
|
||||
res_df = []
|
||||
last_is_sender = chat_df.iloc[0]["is_sender"]
|
||||
last_content: str = chat_df.iloc[0]["content"]
|
||||
last_CreateTime = chat_df.iloc[0]["CreateTime"]
|
||||
# 超时处理 半天没说话就重新开始
|
||||
# 注意这里只是处理了组装成一个句子 最后封装对话、配对在make_sft_dataset
|
||||
# 遇到图片 连接 直接封装成一个句子
|
||||
for i, row in chat_df.iterrows():
|
||||
if row["type_name"] in skip_list:
|
||||
if last_content != "":
|
||||
if last_content[-1] == ",":
|
||||
last_content = last_content[:-1] + "。"
|
||||
elif last_content[-1] not in ["。", "!", "?", "…", "."]:
|
||||
last_content += "。"
|
||||
res_df.append(
|
||||
{
|
||||
"is_sender": last_is_sender,
|
||||
"content": last_content,
|
||||
"CreateTime": last_CreateTime,
|
||||
}
|
||||
)
|
||||
last_CreateTime = row["CreateTime"]
|
||||
last_content = ""
|
||||
# cut表示被skip字段截断
|
||||
res_df.append(
|
||||
{
|
||||
"is_sender": row["is_sender"],
|
||||
"content": "cut",
|
||||
"CreateTime": row["CreateTime"],
|
||||
}
|
||||
)
|
||||
continue
|
||||
if last_content == "": # 重新开始
|
||||
last_content = row["content"]
|
||||
last_is_sender = row["is_sender"]
|
||||
last_CreateTime = row["CreateTime"]
|
||||
continue
|
||||
if row["is_sender"] == last_is_sender:
|
||||
if row["CreateTime"] - last_CreateTime > pd.Timedelta(value="10m"):
|
||||
# 如果超时 前面的添加到res_df 并重新开始
|
||||
if last_content[-1] == ",":
|
||||
last_content = last_content[:-1] + "。"
|
||||
elif last_content[-1] not in ["。", "!", "?", "…", "."]:
|
||||
last_content += "。"
|
||||
res_df.append(
|
||||
{
|
||||
"is_sender": last_is_sender,
|
||||
"content": last_content,
|
||||
"CreateTime": last_CreateTime,
|
||||
}
|
||||
)
|
||||
last_content = row["content"]
|
||||
last_CreateTime = row["CreateTime"]
|
||||
continue
|
||||
# 如果content的结尾没有标点符号则添加逗号,最后结尾是句号
|
||||
if last_content[-1] not in ["。", "!", "?", "…", ","]:
|
||||
last_content += ","
|
||||
last_content = last_content + row["content"]
|
||||
last_CreateTime = row["CreateTime"]
|
||||
else:
|
||||
if last_content[-1] == ",":
|
||||
last_content = last_content[:-1] + "。"
|
||||
elif last_content[-1] not in ["。", "!", "?", "…", "."]:
|
||||
last_content += "。"
|
||||
res_df.append(
|
||||
{
|
||||
"is_sender": last_is_sender,
|
||||
"content": last_content,
|
||||
"CreateTime": last_CreateTime,
|
||||
}
|
||||
)
|
||||
last_is_sender = row["is_sender"]
|
||||
last_content = row["content"]
|
||||
last_CreateTime = row["CreateTime"]
|
||||
res_df = pd.DataFrame(res_df)
|
||||
return res_df
|
||||
|
||||
|
||||
def make_sft_dataset():
|
||||
processor = DataProcessor()
|
||||
csv_files = processor.get_csv_files()
|
||||
|
||||
csv_concat = []
|
||||
csv_res = []
|
||||
|
||||
for csvfile_path in csv_files:
|
||||
chat_df = handle_sft_csv(csvfile_path)
|
||||
csv_concat.append(chat_df)
|
||||
|
||||
# 后续代码保持不变
|
||||
csv_concat = pd.concat(csv_concat)
|
||||
|
||||
# 更全面地处理cut标记
|
||||
# 1. 将连续的cut标记合并为一个
|
||||
# 2. 标记数据区块的开始和结束
|
||||
processed_rows = []
|
||||
skip_row = False
|
||||
last_row_was_cut = False
|
||||
|
||||
for i in range(len(csv_concat)):
|
||||
if skip_row:
|
||||
skip_row = False
|
||||
continue
|
||||
|
||||
current_row = csv_concat.iloc[i].copy()
|
||||
|
||||
# 处理当前行是cut的情况
|
||||
if current_row["content"] == "cut":
|
||||
# 如果上一行已经是cut,则跳过当前行
|
||||
if last_row_was_cut:
|
||||
continue
|
||||
|
||||
# 查找连续的cut
|
||||
j = i + 1
|
||||
while j < len(csv_concat) and csv_concat.iloc[j]["content"] == "cut":
|
||||
j += 1
|
||||
|
||||
# 如果有连续的cut,只保留最后一个
|
||||
if j > i + 1:
|
||||
current_row = csv_concat.iloc[j - 1].copy()
|
||||
skip_row = True
|
||||
|
||||
last_row_was_cut = True
|
||||
else:
|
||||
last_row_was_cut = False
|
||||
|
||||
processed_rows.append(current_row)
|
||||
|
||||
# 创建新的DataFrame
|
||||
csv_concat = pd.DataFrame(processed_rows)
|
||||
|
||||
# csv_res里is_sender必须是01 01 01 的顺序 csv_concat里不一定是01 01
|
||||
# 相差超过1小时的时间戳分为不同的对话
|
||||
# temp_res为一个长度为2的队列
|
||||
# 将合并后的数据保存到CSV文件中
|
||||
output_dir = "./test_output"
|
||||
|
||||
# 生成带时间戳的文件名
|
||||
import datetime
|
||||
|
||||
now = datetime.datetime.now()
|
||||
output_file = os.path.join(output_dir, f"csv_old_.csv")
|
||||
|
||||
# 保存合并后的数据
|
||||
# csv_concat.to_csv(output_file, index=False, encoding="utf-8-sig")
|
||||
# print(f"已将合并后的数据保存到: {output_file}")
|
||||
# print(f"合并后数据总量: {len(csv_concat)} 条记录")
|
||||
|
||||
temp_res = deque(maxlen=2)
|
||||
# 6种情况
|
||||
# temp_res 为空 遇到 0入队 遇到1不处理 遇到cut不处理
|
||||
# temp_res 有0 遇到0清空队列再入队 遇到1相差超过1小时清空队列 没有相差一小时入队再全部出队 遇到cut清空队列
|
||||
|
||||
for i, row in csv_concat.iterrows():
|
||||
if len(temp_res) == 0:
|
||||
if row["content"] == "cut":
|
||||
continue
|
||||
if row["is_sender"] == 0:
|
||||
temp_res.append(row["content"])
|
||||
last_CreateTime = row["CreateTime"]
|
||||
else:
|
||||
continue
|
||||
elif len(temp_res) == 1:
|
||||
if row["content"] == "cut":
|
||||
temp_res.clear()
|
||||
last_CreateTime = row["CreateTime"]
|
||||
elif row["is_sender"] == 0:
|
||||
# 遇到0 清空队列再入队
|
||||
temp_res.clear()
|
||||
temp_res.append(row["content"])
|
||||
last_CreateTime = row["CreateTime"]
|
||||
else:
|
||||
if row["CreateTime"] - last_CreateTime > pd.Timedelta("10m"):
|
||||
# 相差超过1小时清空队列
|
||||
temp_res.clear()
|
||||
last_CreateTime = row["CreateTime"]
|
||||
else:
|
||||
# 没有相差一小时入队再全部出队
|
||||
temp_res.append(row["content"])
|
||||
csv_res.append({"instruction": temp_res[0], "output": temp_res[1]})
|
||||
temp_res.clear()
|
||||
last_CreateTime = row["CreateTime"]
|
||||
|
||||
csv_res_df = pd.DataFrame(csv_res)
|
||||
print(f"处理后数据量:{csv_res_df.shape[0]}")
|
||||
csv_res_df.to_json('./data/res_csv/sft/sft-old-my.json', orient='records', force_ascii=False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# make_pt_dataset()
|
||||
make_sft_dataset()
|
||||
@@ -1,6 +1,7 @@
|
||||
import csv
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
import pandas as pd
|
||||
@@ -91,12 +92,12 @@ def handle_sft_csv(csvfile):
|
||||
for i in chat_df.index:
|
||||
if chat_df.loc[i, "type_name"] == "文本":
|
||||
if (
|
||||
re.search(r"1\d{10}", msg_str)
|
||||
or re.search(r"\d{18}", msg_str)
|
||||
or re.search(r"\w+@\w+", msg_str)
|
||||
or "http" in msg_str
|
||||
or r"\\xa0" in msg_str
|
||||
or r"\\u" in msg_str
|
||||
re.search(r"1\d{10}", chat_df.loc[i, "content"])
|
||||
or re.search(r"\d{18}", chat_df.loc[i, "content"])
|
||||
or re.search(r"\w+@\w+", chat_df.loc[i, "content"])
|
||||
or "http" in chat_df.loc[i, "content"]
|
||||
or r"\\xa0" in chat_df.loc[i, "content"]
|
||||
or r"\\u" in chat_df.loc[i, "content"]
|
||||
):
|
||||
chat_df = chat_df.drop(index=i)
|
||||
continue
|
||||
@@ -156,7 +157,7 @@ def handle_sft_csv(csvfile):
|
||||
last_CreateTime = row["CreateTime"]
|
||||
continue
|
||||
if row["is_sender"] == last_is_sender:
|
||||
if row["CreateTime"] - last_CreateTime > pd.Timedelta(value="10m"):
|
||||
if row["CreateTime"] - last_CreateTime > pd.Timedelta(value="2m"):
|
||||
# 如果超时 前面的添加到res_df 并重新开始
|
||||
if last_content[-1] == ",":
|
||||
last_content = last_content[:-1]
|
||||
@@ -290,7 +291,7 @@ def make_sft_dataset():
|
||||
temp_res.append(row["content"])
|
||||
last_CreateTime = row["CreateTime"]
|
||||
else:
|
||||
if row["CreateTime"] - last_CreateTime > pd.Timedelta("1h"):
|
||||
if row["CreateTime"] - last_CreateTime > pd.Timedelta("5m"):
|
||||
# 相差超过1小时清空队列
|
||||
temp_res.clear()
|
||||
last_CreateTime = row["CreateTime"]
|
||||
|
||||
Reference in New Issue
Block a user