乐趣区

关于机器学习:设计一个基于大数据的机器学习全自动记账系统基础篇

随着近些年消费结构的扭转,抵制消费主义的行为越来越风行,越来越多的年轻人开始意识到集体财务管理的重要性。与此同时,年轻人也越来越意识到,如果他们想要实现本人的财务指标,如购买屋宇、投资、游览等,就须要把握无效的财务管理技能。因而,记账曾经成为了日常生活中不可或缺的一部分。

但于此同时,真正尝试过记账的小伙伴很多,能年复一年保持下来的却很少。起因在于记账是一个太过机械性的行为,且很容易因为或主观或主观的因素遗记漏记。这样记账变得枯燥乏味的同时,数据还不够牢靠,无奈精确地记录生产趋势,从而导致记账的能源有余。

那么,此时如果有一个可能齐全自动化的记账零碎,可能齐全不须要你手动录入每一笔交易,全自动实现所有的数据荡涤和分类,并主动地剖析生产趋势,最终以图表等模式生成报表,直观地展现进去,你会不会感觉很不便,记账的能源也会大大加强呢?这就是我近些年来始终在思考和优化的财务管理指标。

记账模板展现

先展现成绩,以加强大家的记账能源。我应用了腾讯文档作为所有账单汇总的数据源。因为它反对多人合作,且能够随时随地在任何设施下面批改,且 UI 设计杰出,非常适合作为小数据量的账单数据库应用。

以下在不同期间依据本身需要优化进去的财务报表,从下面能够很清晰直观地看到本人的月度生产趋势,以及各个生产类别在每个月的占比,均摊及统计变化趋势。

甚至依据本身定制的须要,还能够展现应用的领取形式占比、每年各类别均摊收入及变化趋势等,都能很直观的以图表的模式展现进去。

当然只展现整体的显示成果,具体的数据波及个人隐私都打了厚码,影响好看见谅。

那么接下来就开始从零开始一一解说如何设计一个这样的主动记账零碎了。

一个齐全自动化的记账零碎,大体由三个重要局部组成:数据获取、数据荡涤和数据分析。我将依据这三个局部顺次介绍。

数据获取

数据获取是一个齐全自动化记账零碎的第一步,也是最重要的一步。交易数据的准确性以及信息丰盛度间接决定了咱们后续的数据荡涤和数据分析的成果。因而,咱们须要尽可能抉择信息量更大的数据源,以获取更多的每条交易更多的上下文信息,从而缩小进一步人工标注的干涉。

对中国大陆来说,微信和支付宝就是国民级的领取软件,它足够遍及和便当,简直不须要大多数人扭转交易习惯。此外,它们的交易数据也是最丰盛的。因而,咱们的数据获取就以微信和支付宝为主。

但很可怜的是,与国外一些信用卡每月会定期发送账单邮件不同,微信和支付宝并不会被动推送交易账单,并且没有凋谢获取交易数据的 API,且安全性校验很简单,也无奈应用无头的爬虫来获取数据。因而咱们须要通过自行操作来获取交易账单。(当然,如果由违心折腾的小伙伴也能够思考模仿用户的行为获取账单)

微信领取账单获取

微信领取账单的获取须要在挪动端实现(毕竟小而美)。须要在本人的微信 APP 中,顺次点击 => 服务 => 钱包 => 账单 => 常见问题 => 下载账单 => 用于集体对账 ,输出 账单工夫 邮箱地址,即可将账单发送到你的邮箱中。下载到本地后,输出发送到微信的明码,即可获取微信领取账单。

然而,微信领取的账单只能获取最近三个月的数据。因而,咱们最多每三个月就须要更新下载账单。

支付宝账单获取

支付宝账单的获取须要在 PC 端支付宝官网实现。登录后抉择 我是个人用户 => 我的支付宝 => 交易记录,就能够抉择对应的交易时间段,抉择下载账单为 Excel 格局,即可获取到支付宝账单。

其余数据源

当然,如果微信和支付宝不是你次要应用的交易工具。其实所有的银行 APP 都提供打印流水服务,齐全能够应用你次要应用的交易工具来作为数据源。当然具体入口可能会有区别,且单条交易记录的信息丰盛度可能有余,须要手动补充,在此就不加赘述了。

数据荡涤

数据荡涤是一个记账零碎很重要的一步。因为这种自动化导出的账单必然是十分芜杂的,不同的数据源,其交易记录的格局都不一样,且信息丰盛度也不一样。因而,咱们须要对不同的数据源进行不同的数据荡涤。

数据荡涤的目标,首先是对立好来自不同数据源的格局以不便接下来的综合剖析,其次是剔除掉一些咱们不关怀的噪声,例如我只想记录 100 元以上的大宗交易,或者我不心愿集体不同账户间接的转账被计入进来等等,这部分也齐全能够依据本身的需要进行定制。

观前重要揭示:

如果有读者齐全没有编程根底也齐全不必胆怯,能够在 Excel 中自行操作出截然不同的格局,再合并数据即可,应用代码只是为了晋升自动化效率。当然,我之后也将开源所有脱敏的预处理与训练代码,有趣味的小伙伴能够自行尝试。

以下的脚本都以 typescript 为例,也齐全能够应用任何你所相熟的语言。

上面依然以微信和支付宝为例,来介绍如何进行数据荡涤。

首先是对立各个数据源的账单格局。如果咱们关上了在上一步中获取的微信与支付宝的账单文件,会发现它们之间其实有很多信息都很类似,能够通过肯定的规定合并起来。例如,它们都有交易工夫、交易类型、交易对方、交易金额等信息。因而,咱们能够将它们对立为一个格局:

interface IBookKeepingRow {
  交易工夫:Date;
  类型:RecordType | "";" 金额(元)": string;" 收 / 支 ":" 收入 "|" 支出 "|"/";
  领取形式:string;
  交易对方:string;
  商品名称:string;
  备注:string;
}

找到了指标格局,接下来咱们来看看如何将数据源进行转换。

微信领取账单荡涤

应用记事本关上微信领取的账单 csv,会发现它的格局相似于:

微信领取账单明细,,,,,,,,
微信昵称:[Duang],,,,,,,,
起始工夫:[2023-06-04 00:00:00] 终止工夫:[2023-07-04 00:00:00],,,,,,,,
导出类型:[全副],,,,,,,,
导出工夫:[2023-07-25 18:08:39],,,,,,,,
,,,,,,,,
共 44 笔记录,,,,,,,,
支出:3 笔 1145.14 元,,,,,,,,
收入:40 笔 2333.33 元,,,,,,,,
中性交易:1 笔 888.00 元,,,,,,,,
注:,,,,,,,,
1. 充值 / 提现 / 理财通购买 / 零钱通存取 / 信用卡还款等交易,将计入中性交易,,,,,,,,
2. 本明细仅展现以后账单中的交易,不包含已删除的记录,,,,,,,,
3. 本明细仅供集体对账应用,,,,,,,,
,,,,,,,,
---------------------- 微信领取账单明细列表 --------------------,,,,,,,,
交易工夫,交易类型,交易对方,商品,收 / 支,金额(元), 领取形式,以后状态,交易单号,商户单号,备注
2023-07-04 12:05:22, 商户生产,甜甜花酿鸡,"xxx", 收入,¥6.00, 零钱通,领取胜利,xxx,xxx,"/"
2023-07-02 19:42:54, 扫二维码付款,哲人众,"收款方备注:二维码收款", 收入,¥15.00, 零钱通,已转账,xxx,xxx,"/"

...

咱们能够看出,微信领取的账单中,第一行到第 14 行都是无用的信息,咱们须要跳过这些行,从第 15 行开始读取数据。同时,咱们还须要将 交易工夫 交易类型 交易对方 商品 收 / 支 金额(元)领取形式 备注 这些信息去除空格后提取进去,其余的信息如交易单号等都能够依据须要疏忽掉。

我给大家一个我本人罕用的模板列头供大家参考:

接下来咱们就能够来看看如何解决数据使得账单的排列合乎咱们本人设置的模板。

上面我通过 fast-csv 库读取 csv 文件并筛选相干数据,最终返回一个 IBookKeepingRow[] 数组。

export async function wechatPayFormatter(sourceFilePath: string): Promise<IBookKeepingRow[]> {const csvStream = fs.createReadStream(sourceFilePath);

  return new Promise((resolve, reject) => {const bookKeepingRows: IBookKeepingRow[] = [];
    parseStream<IWechatBillRow, IBookKeepingRow>(csvStream, {
      headers: true,
      ignoreEmpty: true,
      skipLines: 14,
      trim: true,
    })
      .transform((row: IWechatBillRow) => {
        const bookKeepingRow: IBookKeepingRow = {
          交易工夫:new Date(row. 交易工夫),
          类型:""," 金额(元)": row[" 金额(元)"].replace(/¥/g,""),
          "收 / 支": row["收 / 支"],
          领取形式:row. 领取形式 === "亲属卡" ? "亲属卡" : "微信领取",
          交易对方:row. 交易对方,商品名称:row. 商品,备注:row. 备注 === "/" ? "" : row. 备注,};
        return bookKeepingRow;
      })
      .on("data", (row: IBookKeepingRow) => {bookKeepingRows.push(row);
      })
      .on("end", () => {resolve(bookKeepingRows);
      })
      .on("error", (error) => reject(error));
  });
}

支付宝账单荡涤

对支付宝账单的荡涤也采纳相似的逻辑。

不过这里须要留神是,支付宝账单导出的 csv 是 gbk 格局的,并不能被包含 fast-csv 在内的许多 csv 解析库解决,因而不能应用 stream 的形式间接读取,须要先做一个转码的操作。

另外,支付宝除了下面几行记录了账单总览信息外,最上面几行也加了几行统计信息,因而在荡涤时也须要一并剔除。

export async function aliPayFormatter(sourceFilePath: string): Promise<IBookKeepingRow[]> {const csvBuffer = fs.readFileSync(sourceFilePath);

  // 将 GBK 编码转换为 UTF-8 编码
  const utf8Csv = iconv.decode(csvBuffer, "gbk");
  const lines = utf8Csv.split("\n");

  // 截取以 "--------" 结尾的行两头的所有行
  let flag = false;
  const csvTableString = lines
    .filter((line: string) => {const isDividerLine = line.startsWith("--------");
      if (!flag) {if (isDividerLine) {flag = true;}
        return false;
      }
      if (isDividerLine) {
        flag = false;
        return false;
      }
      return true;
    })
    .map((line: string) => line.replace(/[\s]+,/g, ","))
    .join("");

  return new Promise((resolve, reject) => {const bookKeepingRows: IBookKeepingRow[] = [];
    parseString<IAlipayBillRow, IBookKeepingRow>(csvTableString, {
      headers: true,
      ignoreEmpty: true,
    })
      .transform((row: IAlipayBillRow) => {
        const bookKeepingRow: IBookKeepingRow = {
          交易工夫:new Date(row. 交易创立工夫),
          类型:""," 金额(元)": row[" 金额(元)"]," 收 / 支 ": row[" 收 / 支 "] ===" 不计收支 "?"/": row[" 收 / 支 "],
          领取形式:"支付宝",
          交易对方:row. 交易对方,商品名称:row. 商品名称,备注:row. 备注,};
        return bookKeepingRow;
      })
      .on("data", (row: IBookKeepingRow) => {bookKeepingRows.push(row);
      })
      .on("end", () => {resolve(bookKeepingRows);
      })
      .on("error", (error) => reject(error));
  });
}

这样,将以上两个 IBookKeepingRow 数组合并起来,并依照交易工夫排序,就能够失去一个残缺的交易账单记录了。

  const bookKeepRecords = [...aliPayRecords, ...wechatPayRecords].sort((a, b) => {return a. 交易工夫。getTime() - b. 交易工夫。getTime();}
  );

数据降噪解决

合并账单只是数据荡涤的第一步,咱们还须要对数据进行一些预处理,荡涤掉一些无关噪声,便于后续的数据分析。

不过因为这一部分的定制需要十分高,每个人都有本人的理由为本人的账单定制筛选逻辑,去除一部分数据,保留一部分数据。因而这一章节,我将不进行过多的代码解说,只是 抛砖引玉,简略介绍一下我本人的数据预处理逻辑的思路。

首先是金融相干收支的分类。例如很多人在支付宝或微信中买了理财产品(余额宝这种也算),还有银行间的转入转出,以及理财收益等,这些都是金融相干的收支,它们的收益也会体现在导出的账单中,咱们须要将他筛选进去。

我的察看是,这些金融相干的收支,其 收 / 支 一栏都会显示为 / 不计收支。因而,咱们能够通过这个特色来筛选进去。

这里须要留神的是,这里会有一些 Corner Case 须要解决,因为波及到个人消费偏好,每个人的设置都会须要微调一下。例如在微信领取或者支付宝的账单中,有一些退款的记录,也会显示为 /。然而这些退款的记录,其 商品名称 一栏都会蕴含 退款 字样,因而咱们能够通过这个特色来反向筛选进去。

  // 优先去掉非收支局部
  const filteredBookKeepRecords = bookKeepRecords.filter((row) => {return !(row["收 / 支"] === "/" && !row["商品名称"].includes("退款"));
  });

同理,咱们也能够同样筛选进去一些不须要的数据,例如我不想记录某个个人账户之间的转账,那么咱们能够通过 交易对方 这一栏来筛选进去舍弃掉。又例如,我只想记录 100 元以上的大宗交易,那么咱们能够通过 金额(元) 这一栏来筛选进去。

通过这样的形式,咱们实现了最重要的一步,数据荡涤,使得账面整洁且清晰了起来。

领有了对立且规范化的数据格式,接下来咱们就能安心地在类型下填入该笔生产的类型,对整个账单数据进行分类,从而得悉本人的各项生产趋势了。

小结

到这里,咱们曾经实现了一个残缺的数据荡涤流程,将来自不同数据源的账单数据对立为一个格局,且去除了一些咱们不关怀的噪声,使得数据更加洁净整洁,便于后续的更进阶的数据分析了。

什么?你说我是题目党?这还是须要我本人手动一个个进行分类?

没错。但本篇只是一个根底篇,它帮忙你解决了账单数据源从无到有,从芜杂到标准的过程,这一过程通常是最繁琐的,本篇将这一流程实现了自动化解决。能为有须要的同学省下了许多工夫。

但如果你问,有没有更智能化的形式,能将账单分类的过程也给我省略了,不须要分类操作,躺着就能自动更新获取我近年来所有的生产趋势。我会说,有的。咱们能够通过收集一些历史生产数据,通过建设机器学习模型来实现预测。

接下来的数据分析,就是一个十分乏味的过程了,咱们将在下一篇中进行解说。

退出移动版