F 发布的文章

初始 LLM 接受的输入通常只包含文本信息,而不具备多模态理解的能力。如果需要LLM接受单元格数据并进行处理,我们通常需要先将其文本化以与 LLM 的输入接口对齐。

Excel 文件(.xls / .xlsx)在本质上是一种富结构的二进制或压缩 XML 文件,其内容不仅包括了基本的表格数据(即单元格中的文字和数值),还可能包含:

  • 合并单元格
  • 图表、图像、批注
  • 单元格格式(颜色、字体、对齐方式)
  • 数学公式(如 =SUM(A1:A5))
  • 宏脚本(如 VBA)

上述内容对于传统的文本 LLM 来说是不可直接解析的非文本模态,因此我们需要一个合理的文本化方法,既能保留足够信息,又不过度引入 token 噪声。

首先被提出的方法是将 .xls/.xlsx 文件转换为.csv文件。.csv 文件是一种纯文本格式的结构化数据文件,用于以 逗号分隔的方式 存储表格数据。一个简单的 example.csv 文件内容如下:

Name,Age,Occupation
Alice,30,Engineer
Bob,25,Designer
Charlie,28,Teacher

这表示一个表格,含有三列:姓名、年龄、职业,每行是一条记录。

.csv 格式的优点在于:纯文本格式,适合 LLM 处理,且其易于读取与生成,兼容广泛(Pandas, Excel, SQL)。

但.csv 格式的主要缺点之一是它无法表达 Excel 等高级表格中的结构性信息,比如合并单元格、单元格样式、公式、注释、图表等。因此其仅适用于结构规整、样式不重要的表格处理,如财务数据、名册、日志导出等,但在对于信息保留较为严格的应用场景下并非最佳适用。

另一个可能的方法是将 .xls/.xlsx 文件转换为 .htm l或 .xml 的标记语言格式。Excel 内部实际就是由嵌套的 XML 标签描述的,因此可以将其转换为 .html 或 .xml 格式进行呈现,在浏览器中保留完整排版、结构和嵌套逻辑。与 .csv 相比,这种方式最大程度上保留了一切结构化和多模态的信息,可以在浏览器中被充分渲染和加载。然而,对于针对 LLM 训练为导向的应用场景,引入大量冗余 tag 和嵌套结构会导致 token 数量激增,同时对 LLM 来说,HTML tag 不是自然语言的一部分,干扰模型理解。这种方式适用于结构保留要求极高的任务(如结构重建、格式恢复、文档重构),但不适合直接作为LLM输入使用。

基于这两种方式的优缺点,一个折中的方案被提出:如果我们每一个 Excel 文档都视为一个矩阵,那么每一个有效单元格都是矩阵中的一个元素。可以通过基于统计的方法来进行文档的文本化。将每个 Excel 表格视为一个二维矩阵,提取出有效内容单元格的位置和值,并可选地补充合并区域或样式元数据,以结构化标记方式保存。

我们统计每个特定元素出现的位置,并进行表示。

[
  {"cell": "A1", "value": "Name"},
  {"cell": "B1", "value": "Age"},
  {"cell": "C1", "value": "Occupation"},
  {"cell": "A2", "value": "Alice"},
  {"cell": "B2", "value": "30"},
  {"cell": "C2", "value": "Engineer"},
  {"cell": "A3", "value": "Bob"}
]

对于合并单元格的情况,这种方式也可以处理:

{
  "value": "Test",
  "range": "A1:A3",
}

这样,在文本化的同时,我们仍然保留了单元格的空间位置、合并关系、有效值等核心信息。这种形式也易于转换为 Markdown 表格、JSON 或简化 HTML 片段,以供不同类型 LLM 消化。

具体的实现代码如下:

from openpyxl import load_workbook
from openpyxl.utils import get_column_letter

wb = load_workbook("SWE1.xlsx", data_only=True)
ws = wb.active  # or wb['SheetName']

merged_ranges = list(ws.merged_cells.ranges)
used_cells = set()
cell_contents = []

# 处理合并单元格
for mr in merged_ranges:
    top_left_cell = ws.cell(row=mr.min_row, column=mr.min_col)
    value = top_left_cell.value
    cell_range = f"({get_column_letter(mr.min_col)}{mr.min_row}:{get_column_letter(mr.max_col)}{mr.max_row})"
    for r in range(mr.min_row, mr.max_row + 1):
        for c in range(mr.min_col, mr.max_col + 1):
            used_cells.add((r, c))
    cell_contents.append(((mr.min_row, mr.min_col), cell_range, value))

# 处理非合并单元格
for row in ws.iter_rows():
    for cell in row:
        coord = (cell.row, cell.column)
        if coord not in used_cells and cell.value is not None:
            col_letter = get_column_letter(cell.column)
            cell_range = f"({col_letter}{cell.row}:{col_letter}{cell.row})"
            cell_contents.append((coord, cell_range, cell.value))

# 按照行列排序
cell_contents.sort(key=lambda x: (x[0][0], x[0][1]))

# 最终打印
print(f"Sheet: {ws.title}")
for _, cell_range, value in cell_contents:
    print(f"{cell_range}{value}")

另外,对于 xls 此类旧式文件的情况,可以先使用 xls2xlsx 进行处理:

import os
from xlrd import open_workbook
from openpyxl import Workbook

xls_file_dir = './test_xls_path' 

for xls_file in os.listdir(xls_file_dir):
    if xls_file.endswith('.xls'):
        xls_file_path = os.path.join(xls_file_dir, xls_file)

        # 读取 xls 文件
        workbook = open_workbook(xls_file_path, formatting_info=True)  # 保留格式信息
        sheet = workbook.sheet_by_index(0)

        # 创建 xlsx 文件
        new_workbook = Workbook()
        new_sheet = new_workbook.active

        # 复制数据
        for row_idx in range(sheet.nrows):
            for col_idx in range(sheet.ncols):
                new_sheet.cell(row=row_idx + 1, column=col_idx + 1, value=sheet.cell_value(row_idx, col_idx))

        # 处理合并单元格
        if sheet.merged_cells:
            for (rlow, rhigh, clow, chigh) in sheet.merged_cells:
                cell_range = f"{new_sheet.cell(row=rlow + 1, column=clow + 1).coordinate}:{new_sheet.cell(row=rhigh, column=chigh).coordinate}"
                new_sheet.merge_cells(cell_range)

        # 保存路径
        new_xlsx_file_name = os.path.splitext(xls_file)[0] + '.xlsx'
        new_xlsx_file_path = os.path.join(xls_file_dir, new_xlsx_file_name)

        # 保存文件
        new_workbook.save(new_xlsx_file_path)
        print(f"Converted {xls_file} to {new_xlsx_file_name}")

晚上去吃了新华楼。
在吵闹的大堂里找一张油乎乎的桌子,拿起桌上的号码牌,跑到削面档点一碗双码的杂酱削面,有时还端碗麻油猪血丸子。这算是为数不多真正意义上之于我有所谓长沙记忆的东西。
记得大约十年前面档里还弄了个看起来就很呆的机器人在那里削面,美其名曰高科技,现在想想颇有些当时社会对于未来科技幻想的荒谬。
即使是十年后,外地游客也鲜有来这种地方的。老城区的店面大部分都这样,店里的店员大娘和常连客们都是邻里邻外的社区居民,听不懂长沙话点单都有些困难。
幸好在海外这些年母语也没丢。

我其实远没有那么对所谓故乡有多少感情。即使在伦敦最难捱的冬日里,对于长沙的记忆碎片更多也不过是来自于想多陪陪外婆。Belongingness 是很虚无缥缈的东西,远非单一可以以时间尺度而定义。
长沙很吵。尤其是夏日四十度的高温里,街上的行人个个都是不知何时会喷发的富士山。即使自认为算是性格很平和的存在,在长沙也不可避免地三天两头想找人吵架。
火炉城市或许都这样,这里不过是远东的又一座马德里。

我想起很多曾去过的冷冽之地,它们无一不是坐落在某个岛屿或是大陆被遗忘的角落。卑尔根、朗伊尔城、稚内,也许也包括勒维克和林雪平。在那些峡湾、山谷以及雪原中,所有的爱恨都在迎面的寒风里被遗忘。(忘れてください
我当然知道挪威更加也不是我的家。虽然去了那么多次卑尔根,在Fløyen的山顶对着结冰的湖面发呆,看着松鼠从某棵树上跳下,抓起松果跑向森林的另一头。我也知道这里不属于我。
settle down 不仅仅是物理上的存在。这个世界上并没有真正那么难以做到的事情,虽然 SNS 都在传播焦虑。伦敦找不到工作吗?北欧没有机会吗?退一万步说,买张机票去美国落地把护照一撕又不是什么难事,更何况欧洲这种对理工系学生明码招聘的地方。只是物理意义上想住下来,这根本不是什么事。

但 take up residence != settle down,如果住在某地让我隐约里感受到不安,那这个地方住一辈子也或许还是这个样子。二月份的时候在做ヨルシカ的圣地巡礼,从斯京坐了一夜的火车到马尔默。晚上10点从斯京的火车站出发,到翌日早上7点在马尔默醒来,9个小时的时间里再没有出现除了我以外的第二个活人。房间里只有我,和一只不会说话的高松灯。我们俩就这样无言地在斯堪的纳维亚半岛上移动。
我当然很喜欢这种铁道上的日子,一如我也很喜欢不要和任何人打交道的环境,无论如何都是对社恐极度友好的 —— 我理应充满感激。但悲欢的分明终究存在,挪威的森林过了头便会成为伦敦的冬日。下午三点就天黑的日子里,我不想靠venlafaxine才能续命。

虽然说不止一万次地质疑着生命的存在性,期待中去见主的方式是在某个黑乎乎的雪夜里躺在轨道上等着被从Oslo开往Bodø的列车撞死,但是这一天还是别来的这么快比较好。至少让我多思考几天。

当然,如果某日在某地真的能找到了settle down的感觉也未尝不是不可能,理论上双色球那几个亿的大奖我也能中:n-buna这拧巴男都能给大伙整个大的突然来一首結婚してください还说出什么私の命をあなたにあげたい这种话,明天早上醒来告诉我秦始皇复活了我都愿意相信是真的。

只要平安就好,不安什么的还是いらない好了。

以马内利。

d1d8a1353b50fc6931a17f21896422.jpg

953105a8a14c45490ccdfdb1bc89f5.jpg

ps.
I don’t care 明天是住 Aman 还是青旅。 Makes no bloody difference to me fr, 只不过 settle down 确实是一个美好的梦。

有时候会恍惚间想起一些伦敦冬日的碎片。在下午三点就天黑的日子里,我对着宿舍白色的墙面一次又一次地划着十字。

那时宿舍楼下有一头小牛。
虽然直到现在也想不通为何在伦敦市中心的小楼底下会有一头牛,我甚至没有见过它——但每日窗外时不时传来的哞叫声却真切得时时提醒着我它的存在性。
真的有一头牛吗?还是只是我的幻觉?
我不知道,在长眠的记忆中好像也没有和那位板着脸的家庭医生说过这些事。

一种广为流传的说法是射手座普遍对安定感不屑一顾。虽然一向认为这种将人类粗糙分类的统计学方法简直是在侮辱智商,但不得不承认,我的确主动或被动地在半推半就间接受了这一命题。

姜萍在黑板上写下主=6,新华社等媒体一拥而上,顷刻间吹捧为超天才。数学系都是些心比天高的大人物,李逵看了李鬼自然心里不爽,往死里开火也不能让这些呆子破坏了数学的纯洁性。
但如果人能自圆其说,再荒谬的结论也会显得逻辑自洽。
姜萍只写了主=6。其实没有写下来的是,中国著名的网络流行语“666”的背后含义指的其实是三位一体———圣父圣灵圣子各占一个6。
你看,这逻辑一下子清楚多了,至少比李大师的那些狗屁多少还是先进一点。
什么?你和我说集合 $J={6,6,6}$ 在$(J,+,x)$下不是一个环,因为$(J,+)$没有加法单位元,所以交换群的定义不满足?
那不如和李大师说去吧,他还说中国疫情死了六亿人呢。

毕竟人活下去的一大动力也许在于给生命找到某种解释。这种解释不是处方,医生是开不了的。

22年的圣诞节,英格兰刚下了三年来唯一一场雪。
在伯恩茅斯的海边,寒冷的海风吹得人直打颤。似乎唯一不怕冷的只有那些该死的海鸟。
我抓起听筒给伦敦的GP打电话,冰冷的机械音平静地告诉我大家都在欢度圣诞节。
海边上有一对父女,他们的狗正在沙滩上自由地奔跑。
那天晚上旅馆的费用是83英镑。

把试卷翻回最初一页,按了下系里发的圆珠笔笔帽上的按钮:大学好像就这么轻飘飘地结束了。

其实好像也没多少感觉,只是走出ExCel的时候脑袋里突然放起了《ヒッチコック》:

“啊啊、并不是未来怎样都好,只是现实不时浮现,
而夏天远在他方。
真的这样也没关系吗。”

我不知道。

但不论如何,这三年也辛苦了。

毕业噜🙇

c24f105eea2c2c243fe14a2b61403d.jpg

这几日将照片洗出来,翻看的时候似是突然就理解了为何人们说柏林是一座充满旧日梦核的城市。

这里其实一点也不“21世纪”,也不是传统意义上的“西方世界”。柏林墙虽然已经倒塌三十余年,但东德的存在却从未在柏林消失。

时间仿佛在某些不被人注意的角落里暂停了。

这里更像是一座来自过去的未来之城:充满了当时人们对于新世纪的期待、憧憬和想象。各种涂鸦在苏联风格的建筑上肆意地生长,帝国大厦前修建了宽阔平坦的街道,火车在头顶的桥梁上飞驰而过。

天上还有一个热气球。

一切都太先锋、太摩登了 —— 如果今天不是2025的话。

恍惚间想起在勃兰登堡门前,一位女士和义工们举着一个大旗子,上面印着全世界的国旗。她看到我站在一旁,热情地招呼我过去,和我说他们在为全世界的人民祈祷。

世界有太多苦难和不幸,她真诚地希望所有人都能得到幸福。

我深受感动,为了她的无私、为了义工们的善意,更为了来自神明的这份爱。我抬起手给她看小臂上的十字架,和她说我也是主内的弟兄姊妹。她流露出欢喜的神情,伸出手搭在我的肩膀上,为我祈祷。

不由得想要流泪。

(与此同时,在街道的另一侧,一群轮子们正举着洪志大狮的弱智标语在练气功。可惜不知道是不是因为他们实在太反智或是太讨人厌,在我观察的十分钟内,竟没有哪怕一个人停下来驻足观看他们表演,宛若路边一条被人随便踢死的野狗 :D 令人感叹)

70b6c148fe39a195b655c4da1b408f.jpg