文章

TimeScope,一个简单的桌面活动记录器

#数据库#机器学习
本文目录 11

TimeScope

这是几个月前一时兴起写的小项目,最近差不多完结了,在博客里稍微记录一下

这是repo的地址:repo-address

Abstract

TimeScope — 一个面向windows的桌面应用记录小程序

主要依赖的库:

  • 进程与窗口采集:pywin32 psutil
  • 数据存储:sqlite
  • 数据可视化:matplotlib
  • 文本分类:fasttext,jieba
  • gui,后台与托盘化:customtkinter,pytray ,PIL

1. Analysis , 数据获取与处理

1).数据获取

简单的说,就是定一个时钟,用pywin32库去获取一下当前的焦点窗口的信息,包括

  • 进程名
  • 窗口名

然后结合当前的时间去把这个数据放到数据库里。

数据库的结构是这样的,有两张表

Processeswindow_sessions
nameprocess_id
executable_path (暂时未使用,辅助调试)window_title
first_seen (暂时未使用,辅助调试)start_time
last_seen (暂时未使用,辅助调试)end_time

主要的循环是这样的,就是检查当前的焦点和current_session是否相同,不相同则更新记录

更新的时候,如果获取到当前窗口的process对应的name,和current_process_id一致的时候,会用这个process的id去创建一个新的session;

如果process.name发生了变化,那就再新建一个process和对应的session

loop():
    process_name, window_title = self.tracker.get_foreground_info()

    if process_name:
        # 每次实际活动时,推进内部时钟
        tm.update_internal_clock()

        # 当前会话
        current_session = self.db.get_current_session_info()
        should_record = False
		
        # 检查是否要更新记录
        if current_session is None:
            should_record = True
        else:
            session_process, session_title, _, _ = current_session
            if process_name != session_process or window_title != session_title:
                should_record = True
                
	    # 更新记录
        if should_record:
            if self.db.record_window_switch(process_name, window_title):
                ts = datetime.now().strftime("%H:%M:%S")
                self.update_log(f"[{ts}] Switched to: {process_name} - {window_title}\n")
	
    #定时
    time.sleep(1)

2).数据统计

最后统计的时候遍历的是window_session里的内容,然后用matplotlib画图就好了

3).遇到的问题

主要遇到的问题是,当你的电脑熄屏的时候,代码无法自动识别并终止记录。这就导致当你终止当前的应用的时候,endtime−starttimeendtime - starttime是包含了熄屏的时间的

我首先想到的是让程序能够识别熄屏和开机,win32库里似乎有相关的实现,但是很遗憾实际上并没有用,这个功能似乎没那么灵敏

于是我想到了一个内置时钟的方法,通过维护一个内置的时钟time_manager,它自己更新自己的状态。当这个内置时钟的时间与系统实际的时间大于一个设定的阈值max_gap_seconds之后,就触发一个修复,用内置时钟的时间结束上一段进程,同时更新内置时钟

2.classifier/ML分类器

有了统计数据之后,我就想再设计一个classifier,用来统计用于各项类型活动的时间

一开始的构想是用一些规则+训练一个小型的bert,毕竟一些特定的应用只属于特定的活动类型,而网页和一些视频播放器则要根据窗口标题去进行更细致的分类

但是发现bert实在太大了,就换成了用TF-IDF和fasttext各训了几版

1).TF-IDF

Token Frequency - Inverse Document Frequency,是词频TF和逆文档频率IDF的乘积

其核心思想是:出现在当前文段频率高,但是在全集中不常见的词是一个重要的特征词

  • 词频 TF(t,d)=count(t,d)∑t′count(t′,d)TF(t,d)=\frac{count(t,d)}{\sum_{t'}count(t',d)}
  • 逆文档频率 IDF(t)=log⁡N1+DF(t)IDF(t)=\log \frac{N}{1+DF(t)}
  • 权重TF−IDF(t,d)=TF(t,d)⋅IDF(t) TF-IDF(t,d)=TF(t,d)⋅IDF(t)

通过对一个句子里的所用文段使用以上算法,能得到一个高维向量

例子: 词表:["code","visual","studio","python","game"] 文本向量:[0.8, 0.3, 0.3, 0, 0]

训练的目标是一个线性多分类器,对于每一个分类的类别都有对应的权重w⃗\vec{w} 和偏置b⃗\vec{b} ,

输入向量后得到对应类别的分数

sk=wk⃗⋅x⃗+bk⃗s_k = \vec{w_k} \cdot\vec{x} + \vec{b_{k}}

最后挑出最高的哪一类就行

缺点:

  • 在遇到新词的时候无能为力
  • 特征维度不稳定

2).FastText

工作流程(简化版)

  1. 切词:文本被拆成词(中文可先分词)。
  2. 拆子词:每个词再被拆成字符 n‑gram(比如 “coding” → “co”,“cod”,“odi”,…)。
  3. 词向量构造:词向量 = 它的所有 n‑gram 向量相加。
  4. 句向量构造:文本向量 = 所有词向量平均或加权求和。
  5. 线性分类:用一个线性层(softmax)输出各类别概率。

3.未完待续

目前还考虑在添加一个久坐提醒功能。

想要实现一个笨笨的提醒似乎不难,打包上系统提醒的库就行。

但是有没有更智能的方法?比如有可能有的时候人并不在电脑前(每种类型的程序都有一个运行的时间合理性,人总会在一定的时间后切换程序,而不是长时间一直保持一个不变焦点窗口),可以以此为凭据设计出更智能的提醒

先挖个坑,有空填上