通义千问——大模型数学解题能力的初步探索_

数据合成

对大模型微调,使其具备数学解题能力,高质量的数据集是最重要的保障之一。

业界、数学界关于大模型数学解题能力方面的研究,已经有不少研究成果和数据集。在LLaMa-Factory中,内置了两类数学方面的数据集,它们为BelleGroup/school_math_0.25M和TIGER-Lab/MathInstruct,前者是包含约25万条由BELLE项目生成的中文数学题数据,包含解题过程,但题目偏简单,多为小学数学题;后者由13个具有中间原理的数学数据集编译而成,其中6个为新数据集,混合了思想链(CoT)和思想程序(PoT),确保覆盖了广泛的数学领域。

但是,这两个数据集并没有调用Python代码去解题,仅仅是利用了大模型本身的世界知识和推理能力(如COT)。

我们希望在用大模型解决数学题目时,能够按照我们读书时的过程,先给出思考过程,再调用合适的知识点(这里是Python)代码,最后给出结果。下面是一个数学题目的解答过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

[
{
"from": "human",
"value": "计算 345 * 321 = ?"
},
{
"from": "gpt",
"value": "首先给出思考过程:这是一道简单的数学运算题目,考察的是两个整数的乘法。直接调用Python代码计算即可。"
},
{
"from": "function_call",
"value": "```python\ndef multiply(num1, num2):\n return num1 * num2\n\nnum1 = 345\nnum2 = 123\n\nprint(f'{num1} * {num2} = {multiply(num1, num2)}')\n```\n"
},
{
"from": "observation",
"value": "现在将上面的代码复制到Python环境中运行,运行以上代码,输出会是: ```345 * 123 = 42435```"
},
{
"from": "gpt",
"value": "因此,345 * 123 = 42435"
}
]

JSON

基于上述的这种格式,我们借助GPT-4-turbo模型进行数据合成,其中一个模板(即Prompt)如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

现在,请你开始编写一个给初中生做的数学运算题目。
数学运算题目的出题方式要丰富多样,可以是加减乘除,也可以是分数、小数等,也可以出现复杂的运算,比如开方、对数、三角函数等。
在回答过程中,你可以调用Python代码,使用math或sympy模块,并给出Python代码的运行结果,要求输出格式与上述例子的输出相同,以```json开头,以```结尾,中间是JSON字符串,第一步一定要给出思考过程。

以下是一个示例的JSON格式的输出结果:

[
{
"from": "human",
"value": "计算 345 * 321 = ?"
},
{
"from": "gpt",
"value": "首先给出思考过程:这是一道简单的数学运算题目,考察的是两个整数的乘法。直接调用Python代码计算即可。"
},
{
"from": "function_call",
"value": "```python\ndef multiply(num1, num2):\n return num1 * num2\n\nnum1 = 345\nnum2 = 123\n\nprint(f'{num1} * {num2} = {multiply(num1, num2)}')\n```\n"
},
{
"from": "observation",
"value": "现在将上面的代码复制到Python环境中运行,运行以上代码,输出会是: ```345 * 123 = 42435```"
},
{
"from": "gpt",
"value": "因此,345 * 123 = 42435"
}
]

出题要求:
1. 题目要求随机生成,不能是固定的题目。
2. 题目要求有一定的难度,不能太简单。
3. 题目的描述要清晰明了,且表达形式要丰富多样,有时候可以用中文来描述。
4. 计算题中可以包含一些特殊的数学符号,比如分数、小数、开方、对数、三角函数等。

CEYLON

如何写好合适的Prompt,使得GPT-4-turbo能够遵循指令,给出高质量的数学解题过程,也是值得探索的,笔者这里只是给出了初步尝试的结果,这中间的过程还是有待提升的。

使用GPT-4-turbo模型生成数据,再经过大量时间的数据清洗与合成,最终我们得到547个样本(包含重复样本),它们都满足sharegpt格式,格式样例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

[
{
"conversations": [
{
"from": "human",
"value": "题目:苹果的价格是每公斤3元,小明买了2.5公斤的苹果,他一共需要支付多少钱?"
},
{
"from": "gpt",
"value": "首先给出思考过程:这是一个简单的乘法问题,其中涉及到的是单价和数量的乘积计算。我们需要用苹果的单价乘以小明购买的苹果的重量来找出总金额。\n生成的Python代码如下:\n计算总金额的公式是: \\[ 总金额 = 单价 \\times 数量 \\] 在这个具体例子中,苹果的单价为3元每公斤,小明购买了2.5公斤。\n生成的Python代码如下:\n```python\ndef total_cost(price_per_kg, weight):\n return price_per_kg * weight\n\n 苹果的单价(每公斤)\nprice_per_kg = 3\n\n 小明购买的苹果重量(公斤)\nweight = 2.5\n\n 计算总金额\ncost = total_cost(price_per_kg, weight)\nprint('小明需要支付的总金额为:', cost)\n```\n"
},
{
"from": "human",
"value": "现在将上面的代码复制到Python环境中运行,我们应得到小明需要支付的总金额。运行结果将告诉我们具体的金额数值。 运行以上代码,输出会是: ```小明需要支付的总金额为: 7.5```"
},
{
"from": "gpt",
"value": "因此,小明购买2.5公斤苹果,总共需要支付7.5元。"
}
]
}
...
]

CEYLON

这就是我们最终送入LLaMa-Factory的数据格式了。

模型微调

使用LLaMa-Factory微调框架对大模型进行SFT,笔者之前在很多文章中已经介绍多次了,并没有太多新意,这里也不再给出详细的微调过程。

我们使用Qwen/Qwen1.5-32B模型,对上述合成的少量数据集进行SFT,训练完毕后导出模型为Qwen1.5-32B-math,再使用下面的脚本启动训练后模型的推理服务:

1

CUDA_VISIBLE_DEVICES=0 python -m llmtuner.api.app --model_name_or_path /models/Qwen1.5-32B-math --template qwen

STYLUS

模型预测

接下来,就是快乐的模型预测时光了,此时,我们就能见证大模型在经过数学题数据集微调后,是否具备了数学解题能力。

在此之前,我们先看看Qwen/Qwen1.5-32B模型本身的数学解题能力。

  1. 题目:小明有98元,他要买一本书和一支笔,已知书的价格是25元,笔的价格比书的价格多15元,请问小明还剩余多少元钱?

通义千问——大模型数学解题能力的初步探索__Python

model_before_1.png

  1. 题目:抛物线y=x^24x+3的顶点坐标为多少?

通义千问——大模型数学解题能力的初步探索__python_02

model_before_2.png

  1. 题目:计算123456789与987654321的乘积

通义千问——大模型数学解题能力的初步探索__python_03

model_before_3.png

由此可见,模型在微调前已经具备一定的数学解题与分析能力,但无法调用Python代码,并且存在幻觉(第三题的结果是不正确的!),另外,模型还会生成额外的不相关文本,这与模型是生成模型而并非Chat模型有关,这点可使用Chat模型来解决。

让我们来看看微调后模型Qwen1.5-32B-math的表现吧!

这里先给出模型预测的Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

-*- coding: utf-8 -*-
import gradio as gr
import os
import re
import subprocess
from openai import OpenAI

os.environ["OPENAI_BASE_URL"] = "http://localhost:8000/v1"
os.environ["OPENAI_API_KEY"] = "0"
client = OpenAI()

defquestion_answer(query):
messages = []
messages.append({"role": "user", "content": f"题目:{query}"})
result = client.chat.completions.create(messages=messages,
model="Qwen1.5-32B-math",
temperature=0.0,
stream=True)
reply_message = ""
for chunk in result:
if hasattr(chunk, "choices") and chunk.choices[0].delta.content:
reply_message += chunk.choices[0].delta.content
yield reply_message

find python code and execute the code
if '```python' in reply_message:
messages.append({"role": "assistant", "content": reply_message})
python_code_string = re.findall(r'```python\n(.*?)\n```', reply_message, re.S)[0]
python_file_path = 'temp.py'
with open(python_file_path, 'w') as f:
f.write(python_code_string)
python_code_execution = subprocess.run(['python3', python_file_path], stdout=subprocess.PIPE).stdout.decode('utf-8')
os.remove(python_file_path)
code_reply = f"\n运行以上代码,输出会是: ```{python_code_execution.strip()}```\n"
reply_message += code_reply
yield reply_message
messages.append({"role": "user", "content": code_reply})
result = client.chat.completions.create(messages=messages,
model="Qwen1.5-32B-math",
temperature=0.0,
stream=True)

for chunk in result:
if hasattr(chunk, "choices") and chunk.choices[0].delta.content:
reply_message += chunk.choices[0].delta.content
yield reply_message


demo = gr.Interface(
fn=question_answer,
inputs=gr.Textbox(lines=3, placeholder="题目", label="数学题目"),
outputs=gr.Markdown(),
)

demo.launch(server_name="0.0.0.0", server_port=8001, share=True)

PYTHON

在这里,我们将生成后的Python代码存入temp.py脚本中,并使用subprocess模块运行该脚本并获取Python代码的执行结果,然后将其加工成prompt",输出会是: {python_code_execution.strip()}"送入大模型中继续回答。

我们来看看模型微调后的结果(下面会给出几个表现好的case):

首先是上面展示过的三道题目。

  1. 题目1:小明有98元,他要买一本书和一支笔,已知书的价格是25元,笔的价格比书的价格多15元,请问小明还剩余多少元钱?

通义千问——大模型数学解题能力的初步探索__Python_04

model_after_1.png

  1. 题目2:抛物线y=x^24x+3的顶点坐标为多少?

通义千问——大模型数学解题能力的初步探索__python_05

model_after_2.png

  1. 题目3:计算123456789与987654321的乘积

通义千问——大模型数学解题能力的初步探索__Python_06

model_after_3.png

接着是更多题目的展示。

  1. 题目4:若(ax-b)(3x+4)=bx^2+cx+72,则a+b+c的值为多少?

通义千问——大模型数学解题能力的初步探索__python_07

model_after_4.png

  1. 题目5:123456789 + 987654321 = ?

通义千问——大模型数学解题能力的初步探索__数据集_08

model_after_5.png

  1. 题目6:已知ΔABC为正三角形,则tan(A+)的值等于多少?

通义千问——大模型数学解题能力的初步探索__Python_09

model_after_6.png

  1. 题目7:各项均为正数的等比数列{a_{n}}的前n项和为S_{n},已知S_{3}=10, S_{6}=30,则S_{12}等于多少?

通义千问——大模型数学解题能力的初步探索__数据集_10

model_after_7.png

  1. 题目8:复数 3+4i 的模是多少?

通义千问——大模型数学解题能力的初步探索__python_11

model_after_8.png

  1. 题目9:(1+2x)^6 的展开式中 x^3 项的系数为多少?

通义千问——大模型数学解题能力的初步探索__数据集_12

model_after_9.png

  1. 题目10:学校图书馆有故事书、科技书和连环画共1200本,其中故事书占60%,科技书和连环画的数量比是2:3,图书馆有多少本连环画?

通义千问——大模型数学解题能力的初步探索__数据集_13

model_after_10.png

接着,我们来看下最开始提到的数据题数据集BelleGroup/school_math_0.25M和TIGER-Lab/MathInstruct中的两道题目。

  1. 题目11:某天,小明去菜市场买了5个苹果和3个梨,花了20元。第二天,他又去同一个菜市场买了3个苹果和4个梨,花了15元。问:小明每个苹果和每个梨的价格是多少元?

通义千问——大模型数学解题能力的初步探索__python_14

model_after_11.png

  1. 题目12:Suppose you have a system of linear equations: 2x + 3y + z = 7 x + 2y + 4z = 12 3x + y + 2z = 8 Using matrix methods, find the values of x, y, and z that satisfy the system of equations.

通义千问——大模型数学解题能力的初步探索__数据集_15

model_after_12.png

最后,我们来看看大学数学中的微积分方面的数学题目的表现。(题目来自网站:https://univs-news-1256833609.cos.ap-beijing.myqcloud.com/123/upload/resources/file/7526039.pdf 中的 第七题 )

通义千问——大模型数学解题能力的初步探索__Python_16

model_after_13.png

再使用定积分计算器(https://zs.symbolab.com/solver/definite-integral-calculator)进行验证,答案正确。


Intel 优化部署

Intel Math Kernel Library (Intel MKL)


**原理:**

Intel MKL 是一个高性能的数学计算库,它提供了对线性代数、统计、时间序列分析等数学运算的优化实现。MKL 利用了现代处理器架构的优势,如SIMD(单指令多数据)指令集和多线程技术,以提高计算效率。


**特点:**

- **高度优化**:针对Intel处理器进行了特别优化,包括最新的指令集和多核架构。

- **多线程支持**:自动并行化,充分利用多核处理器的计算能力。

- **广泛的API**:提供超过1000个数学函数,覆盖各种数学和工程计算需求。


**在代码大模型写作助手中的应用:**

在模型训练和推理过程中,大量的数学运算是不可避免的。通过使用Intel MKL,可以显著加速这些计算过程。例如,在进行大规模矩阵运算时,MKL能够提供比标准Python库更快的执行速度。


**代码示例:**

```python

import numpy as np

from scipy.linalg import solve


使用MKL优化的NumPy和SciPy

A = np.random.rand(1000, 1000) 生成一个1000x1000的随机矩阵

B = np.random.rand(1000) 生成一个1000维的随机向量


解线性方程组

x = solve(A, B)

```


Intel VTune Profiler


**原理:**

Intel VTune Profiler 是一款性能分析工具,它可以帮助开发者识别和解决应用程序中的性能瓶颈。VTune通过收集程序运行时的详细数据,包括CPU使用情况、内存访问模式、缓存命中率等,帮助开发者理解程序的运行行为。


**特点:**

- **深入分析**:提供CPU和内存的详细分析,包括指令级和函数级的分析。

- **性能优化建议**:根据分析结果提供优化建议,帮助开发者改进代码。

- **支持多种架构**:支持x86、ARM等多种处理器架构。


**在代码大模型写作助手中的应用:**

在模型部署和运行过程中,使用VTune Profiler可以帮助开发者识别性能瓶颈,如计算密集型操作、内存访问延迟等。通过优化这些瓶颈,可以提高模型的推理速度和整体性能。


**使用示例:**

1. **启动VTune Profiler**:在命令行中启动VTune Profiler,并指定要分析的应用程序。

2. **运行分析**:运行应用程序,VTune Profiler将自动收集性能数据。

3. **查看报告**:分析完成后,VTune Profiler将生成详细的性能报告,包括热点分析、内存访问模式等。


G8i 实例部署

通义千问——大模型数学解题能力的初步探索__python_17


G8i 实例部署指南

阿里云

G8i 实例概述

G8i 实例是阿里云推出的新一代GPU加速计算型实例,它具备以下特点:

  1. 高性能GPU:搭载NVIDIA Tesla V100、V100S或A100 GPU,提供强大的并行计算能力。
  2. 高速存储:支持SSD云盘,提供低延迟、高吞吐量的存储性能。
  3. 弹性伸缩:支持自动扩展和手动扩展,以应对不断变化的计算需求。
  4. 安全稳定:提供企业级的安全保障和99.95%的服务可用性。

部署步骤

  1. 创建G8i实例
  1. 登录阿里云控制台,选择“ECS实例”服务。
  2. 点击“创建实例”,选择适合的区域和可用区。
  3. 在“选择实例规格”中,选择G8i系列实例。
  4. 配置网络和安全组,确保实例可以访问必要的服务和端口。
  5. 选择操作系统,如Ubuntu或CentOS。
  6. 安装必要的软件包,如Python、pip、CUDA Toolkit、cuDNN等。
  7. 配置环境变量,确保系统可以正确识别GPU资源。
  8. 使用pip或conda安装TensorFlow、PyTorch或其他深度学习框架。
  9. 确保框架与CUDA和cuDNN版本兼容。
  10. 将代码大模型的代码库上传到实例。
  11. 使用pip安装所需的Python库和依赖。
  12. 根据需要配置模型参数和训练/推理脚本。
  13. 使用Intel MKL和Intel VTune Profiler优化模型性能。
  14. 调整GPU资源分配,确保模型可以充分利用GPU计算能力。
  15. 运行测试脚本,验证模型的准确性和性能。
  16. 根据测试结果调整模型参数和系统配置。
  17. 将模型部署为Web服务,如使用Flask或Django框架。
  18. 配置负载均衡和自动扩展,以应对高并发访问。
  19. 使用阿里云监控服务监控实例的性能和资源使用情况。
  20. 定期备份数据和模型,确保系统的稳定性和数据的安全性。
  1. 配置系统环境
  2. 安装深度学习框架
  3. 部署代码大模型
  4. 性能优化
  5. 测试和验证
  6. 部署应用
  7. 监控和维护

总结

ok,经过上述漫长的模型预测的测试,我们可以发现,经过SFT后的大模型遵循了指令,并且按照我们的要求给出了思考过程,分析过程,调用Python代码,执行Python代码,最后给出答案。上述的测试的答案(除了精度上的偏差)都是正确的,这样的表现无疑是让人惊喜的。

大模型之所以有这样的数学解题能力,个人理解是:

大模型本身就具有一定的数学题目分析能力,借助Python代码(实际上就是code intepreter)能力,可以得到正确结果,这样既避免了幻觉,又能借助Python中的数学相关模块的帮助,因此,才使得它拥有了数学解题能力。

当然,上面的模型预测题目只是给出了一些好的测试样例,实际上,现在微调后的大模型在数学解题能力方面还有很大的进步空间。