diff --git a/.gitattributes b/.gitattributes index c7d9f3332a950355d5a77d85000f05e6f45435ea..cb5140fafffbc3039b5e3f7380781f87de2858b3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -32,3 +32,11 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text *.zst filter=lfs diff=lfs merge=lfs -text *tfevents* filter=lfs diff=lfs merge=lfs -text +data/.ipynb_checkpoints/comparison_gpt4_data_zh-checkpoint.json filter=lfs diff=lfs merge=lfs -text +data/alpaca_data_en_52k.json filter=lfs diff=lfs merge=lfs -text +data/alpaca_data_zh_51k.json filter=lfs diff=lfs merge=lfs -text +data/alpaca_gpt4_data_en.json filter=lfs diff=lfs merge=lfs -text +data/alpaca_gpt4_data_zh.json filter=lfs diff=lfs merge=lfs -text +data/comparison_gpt4_data_en.json filter=lfs diff=lfs merge=lfs -text +data/comparison_gpt4_data_zh.json filter=lfs diff=lfs merge=lfs -text +tests/comparison_data_v2.json filter=lfs diff=lfs merge=lfs -text diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..b09cd7856d58590578ee1a4f3ad45d1310a97f87 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README_zh.md b/README_zh.md new file mode 100644 index 0000000000000000000000000000000000000000..c60c5f33c3361121b411281fd86a05de9a6e0422 --- /dev/null +++ b/README_zh.md @@ -0,0 +1,339 @@ +# ChatGLM Efficient Tuning + +![GitHub Repo stars](https://img.shields.io/github/stars/hiyouga/ChatGLM-Efficient-Tuning?style=social) +![GitHub Code License](https://img.shields.io/github/license/hiyouga/ChatGLM-Efficient-Tuning) +![GitHub last commit](https://img.shields.io/github/last-commit/hiyouga/ChatGLM-Efficient-Tuning) +![GitHub pull request](https://img.shields.io/badge/PRs-welcome-blue) + +基于 🤗[PEFT](https://github.com/huggingface/peft) 的高效 🤖[ChatGLM-6B](https://github.com/THUDM/ChatGLM-6B) 微调。 + +👋 加入我们的[微信群](assets/wechat.jpg)。 + +\[ [English](README.md) | 中文 \] + +## 更新日志 + +[23/04/29] 现在我们实现了 **RLHF(基于人类反馈的强化学习)** 训练!我们提供了几个运行 RLHF 的例子,具体内容请移步 `examples` 文件夹。(实验性功能) + +[23/04/25] 我们新增了一个使用自定义数据集分布式训练的例子,请移步 [ads_generation.md](examples/ads_generation.md) 查阅。 + +[23/04/20] 我们的项目在 12 天内获得了 100 个 Star!祝贺! + +[23/04/20] 我们新增了一个修改模型自我认知的例子,请移步 [alter_self_cognition.md](examples/alter_self_cognition.md) 查阅。 + +[23/04/19] 现在我们实现了**模型融合**!请尝试使用 `--checkpoint_dir checkpoint1,checkpoint2` 参数训练融合 LoRA 权重后的模型。 + +[23/04/18] 现在可以微调**量化模型**了!请尝试使用 `quantization_bit` 参数进行 4 比特或 8 比特量化微调。 + +[23/04/12] 现在我们加入了**断点训练支持**!请尝试给定 `--checkpoint_dir` 参数加载指定的模型断点。 + +[23/04/11] 现在我们实现了**数据集组合训练**!请尝试使用 `--dataset dataset1,dataset2` 参数进行组合训练。 + +## 数据集 + +目前我们实现了针对以下数据集的支持: + +- [Stanford Alpaca](https://github.com/tatsu-lab/stanford_alpaca) +- [Stanford Alpaca (Chinese)](https://github.com/ymcui/Chinese-LLaMA-Alpaca) +- [GPT-4 Generated Data](https://github.com/Instruction-Tuning-with-GPT-4/GPT-4-LLM) +- [BELLE 2M](https://huggingface.co/datasets/BelleGroup/train_2M_CN) +- [BELLE 1M](https://huggingface.co/datasets/BelleGroup/train_1M_CN) +- [BELLE 0.5M](https://huggingface.co/datasets/BelleGroup/train_0.5M_CN) +- [BELLE Dialogue 0.4M](https://huggingface.co/datasets/BelleGroup/generated_chat_0.4M) +- [BELLE School Math 0.25M](https://huggingface.co/datasets/BelleGroup/school_math_0.25M) +- [BELLE Multiturn Chat 0.8M](https://huggingface.co/datasets/BelleGroup/multiturn_chat_0.8M) +- [Guanaco Dataset](https://huggingface.co/datasets/JosephusCheung/GuanacoDataset) +- [Firefly 1.1M](https://huggingface.co/datasets/YeungNLP/firefly-train-1.1M) +- [CodeAlpaca 20k](https://huggingface.co/datasets/sahil2801/CodeAlpaca-20k) +- [Alpaca CoT](https://huggingface.co/datasets/QingyiSi/Alpaca-CoT) +- [Web QA (Chinese)](https://huggingface.co/datasets/suolyer/webqa) +- [UltraChat](https://github.com/thunlp/UltraChat) + +使用方法请参考 [data/README.md](data/README.md) 文件。 + +部分数据集的使用需要确认,我们推荐使用下述命令登录您的 HuggingFace 账户。 + +```bash +pip install --upgrade huggingface_hub +huggingface-cli login +``` + +## 微调方法 + +目前我们实现了针对以下高效微调方法的支持: + +- [LoRA](https://arxiv.org/abs/2106.09685) + - 仅微调低秩适应器。 +- [P-Tuning V2](https://github.com/THUDM/P-tuning-v2) + - 仅微调前缀编码器。 +- [Freeze](https://arxiv.org/abs/2012.14913) + - 仅微调后几层的全连接层。 + +## 软件依赖 + +- Python 3.8+, PyTorch 1.13.1 +- 🤗Transformers, Datasets, Accelerate, PEFT, TRL +- protobuf, cpm_kernels, sentencepiece +- jieba, rouge_chinese, nltk(用于评估) +- gradio, mdtex2html(用于网页端交互) + +以及 **强而有力的 GPU**! + +## 如何使用 + +### 数据准备(可跳过) + +关于数据集文件的格式,请参考 `data/example_dataset` 文件夹的内容。构建自定义数据集时,既可以使用单个 `.json` 文件,也可以使用一个[数据加载脚本](https://huggingface.co/docs/datasets/dataset_script)和多个文件。 + +注意:使用自定义数据集时,请更新 `data/dataset_info.json` 文件,该文件的格式请参考 `data/README.md`。 + +### 环境搭建(可跳过) + +```bash +git clone https://github.com/hiyouga/ChatGLM-Efficient-Tuning.git +conda create -n chatglm_etuning python=3.10 +conda activate chatglm_etuning +cd ChatGLM-Efficient-Tuning +pip install -r requirements.txt +``` + +对于 Windows 用户,若要启用 LoRA 或 Freeze 的量化微调,请下载预构建的 `bitsandbytes` 包,目前仅支持 CUDA 11.6 和 11.7。 + +``` +pip install https://github.com/acpopescu/bitsandbytes/releases/download/v0.37.2-win.1/bitsandbytes-0.37.2-py3-none-any.whl +``` + +### 单 GPU 微调训练 + +```bash +CUDA_VISIBLE_DEVICES=0 python src/finetune.py \ + --do_train \ + --dataset alpaca_gpt4_zh \ + --finetuning_type lora \ + --output_dir path_to_sft_checkpoint \ + --per_device_train_batch_size 4 \ + --gradient_accumulation_steps 4 \ + --lr_scheduler_type cosine \ + --logging_steps 10 \ + --save_steps 1000 \ + --learning_rate 5e-5 \ + --num_train_epochs 3.0 \ + --fp16 +``` + +关于参数信息,请查阅我们的[维基](https://github.com/hiyouga/ChatGLM-Efficient-Tuning/wiki)。 + +### 多 GPU 分布式微调 + +```bash +accelerate config # 首先配置分布式环境 +accelerate launch src/finetune.py # 参数同上 +``` + +注意:若您使用 LoRA 方法进行微调,请指定以下参数 `--ddp_find_unused_parameters False` 来避免报错。 + +### 奖励模型训练 + +```bash +CUDA_VISIBLE_DEVICES=0 python src/train_rm.py \ + --do_train \ + --dataset comparison_gpt4_zh \ + --finetuning_type lora \ + --output_dir path_to_rm_checkpoint \ + --per_device_train_batch_size 4 \ + --gradient_accumulation_steps 4 \ + --lr_scheduler_type cosine \ + --logging_steps 10 \ + --save_steps 1000 \ + --learning_rate 1e-5 \ + --num_train_epochs 1.0 \ + --fp16 +``` + +### RLHF 训练 + +```bash +CUDA_VISIBLE_DEVICES=0 python src/train_ppo.py \ + --do_train \ + --dataset alpaca_gpt4_zh \ + --finetuning_type lora \ + --checkpoint_dir path_to_sft_checkpoint \ + --reward_model path_to_rm_checkpoint \ + --output_dir path_to_ppo_checkpoint \ + --per_device_train_batch_size 2 \ + --gradient_accumulation_steps 4 \ + --lr_scheduler_type cosine \ + --logging_steps 10 \ + --save_steps 1000 \ + --learning_rate 1e-5 \ + --num_train_epochs 1.0 \ + --fp16 +``` + +### 指标评估(BLEU分数和汉语ROUGE分数) + +```bash +CUDA_VISIBLE_DEVICES=0 python src/finetune.py \ + --do_eval \ + --dataset alpaca_gpt4_zh \ + --checkpoint_dir path_to_checkpoint \ + --output_dir path_to_eval_result \ + --per_device_eval_batch_size 8 \ + --max_samples 50 \ + --predict_with_generate +``` + +### 模型预测 +```bash +CUDA_VISIBLE_DEVICES=0 python src/finetune.py \ + --do_predict \ + --dataset alpaca_gpt4_zh \ + --checkpoint_dir path_to_checkpoint \ + --output_dir path_to_predict_result \ + --per_device_eval_batch_size 8 \ + --max_samples 50 \ + --predict_with_generate +``` + +### 命令行测试 + +```bash +CUDA_VISIBLE_DEVICES=0 python src/infer.py \ + --checkpoint_dir path_to_checkpoint +``` + +### 浏览器测试 + +```bash +CUDA_VISIBLE_DEVICES=0 python src/web_demo.py \ + --checkpoint_dir path_to_checkpoint +``` + +### 模型部署 + +```python +import sys +sys.path.append("src") +from src import load_pretrained, ModelArguments +model_args = ModelArguments(checkpoint_dir=path_to_checkpoint) +model, tokenizer = load_pretrained(model_args) +model = model.cuda() +model.eval() +# model.generate, model.chat()... +``` + +### 硬件需求 + +| 微调方法 | 批处理大小 | 模式 | GPU显存 | 速度 | +| ---------------- | ---------- | ---- | ------ | ----- | +| LoRA (r=8) | 16 | FP16 | 28GB | 8ex/s | +| LoRA (r=8) | 8 | FP16 | 24GB | 8ex/s | +| LoRA (r=8) | 4 | FP16 | 20GB | 8ex/s | +| LoRA (r=8) | 4 | INT8 | 10GB | 8ex/s | +| P-Tuning (p=16) | 4 | FP16 | 20GB | 8ex/s | +| P-Tuning (p=16) | 4 | INT8 | 16GB | 8ex/s | +| P-Tuning (p=16) | 4 | INT4 | 12GB | 8ex/s | +| Freeze (l=3) | 4 | FP16 | 24GB | 8ex/s | +| Freeze (l=3) | 4 | INT8 | 12GB | 8ex/s | + +| 奖励模型训练方法 | 批处理大小 | 模式 | GPU显存 | 速度 | +| --------------- | ---------- | ---- | ------ | ---- | +| LoRA (r=8) + rm | 4 | FP16 | 22GB | - | +| LoRA (r=8) + rm | 1 | INT8 | 11GB | - | + +| RLHF 训练方法 | 批处理大小 | 模式 | GPU显存 | 速度 | +| ---------------- | ---------- | ---- | ------ | ---- | +| LoRA (r=8) + ppo | 4 | FP16 | 23GB | - | +| LoRA (r=8) + ppo | 1 | INT8 | 12GB | - | + +> 注:`r` 为LoRA 维数大小,`p` 为前缀词表大小,`l` 为微调层数,`ex/s` 为每秒训练的样本数。`gradient_accumulation_steps` 参数设置为 `1`。上述结果均来自于单个 Tesla V100 GPU,仅供参考。 + +## 微调 ChatGLM 的例子 + +### 训练结果 + +我们使用整个 `alpaca_gpt4_zh` 数据集微调 ChatGLM 模型,使用秩为 8 的 LoRA 方法,使用默认超参数进行单轮训练。下图为训练损失变化曲线。 + +![训练损失](assets/trainer_state.jpg) + +### 评估结果 + +我们选择 `alpaca_gpt4_zh` 数据集中的前一百条数据来评估微调后的 ChatGLM 模型,并计算 BLEU 和中文 ROUGE 分数。下表为评估结果。 + +| 分数 | 原版模型 | FZ (l=2) | PT (p=16) | LoRA (r=8) | +| ------- | -------- | ----- | ----- | ----------------- | +| BLEU-4 | 15.75 | 16.85 | 16.06 | 17.01 (**+1.26**) | +| Rouge-1 | 34.51 | 36.62 | 34.80 | 36.77 (**+2.26**) | +| Rouge-2 | 15.11 | 17.04 | 15.32 | 16.83 (**+1.72**) | +| Rouge-l | 26.18 | 28.17 | 26.35 | 28.86 (**+2.68**) | +| 训练参数 | / | 4.35% | 0.06% | 0.06% | + +> FZ:Freeze 微调,PT:P-Tuning V2 微调(为了与 LoRA 公平比较,我们使用了 `pre_seq_len=16`),训练参数:可训练参数占全部参数的百分比。 + +## 和现有类似项目的比较 + +- [THUDM/ChatGLM-6B](https://github.com/THUDM/ChatGLM-6B/tree/main/ptuning) + - ChatGLM 基于 [P-Tuning v2](https://github.com/THUDM/P-tuning-v2) 微调的官方实现,使用了 [ADGEN](https://aclanthology.org/D19-1321.pdf) 数据集。 + - 本仓库的代码实现绝大部分参考该项目。我们进一步实现了 [LoRA](https://arxiv.org/abs/2106.09685) 微调方法。此外,我们**动态地**将每个批处理数据中的序列进行填充,而非将其填充到模型的最大长度,此改进可以加速模型训练。 +- [mymusise/ChatGLM-Tuning](https://github.com/mymusise/ChatGLM-Tuning) + - ChatGLM 基于 [LoRA](https://arxiv.org/abs/2106.09685) 微调的非官方实现,使用了 [Stanford Alpaca](https://github.com/tatsu-lab/stanford_alpaca) 数据集。 + - 我们借鉴了该项目的一些想法。我们的训练脚本将数据预处理部分**集成**至训练脚本中,以避免事先生成预处理后的数据。 +- [ssbuild/chatglm_finetuning](https://github.com/ssbuild/chatglm_finetuning) + - ChatGLM 基于多种微调方法的非官方实现,使用了 [Stanford Alpaca](https://github.com/tatsu-lab/stanford_alpaca) 数据集。 + - 我们的训练脚本**全部**基于 [Huggingface transformers](https://github.com/huggingface/transformers) 框架实现,不依赖于额外的 [deep_training](https://github.com/ssbuild/deep_training) 框架。 +- [lich99/ChatGLM-finetune-LoRA](https://github.com/lich99/ChatGLM-finetune-LoRA) + - ChatGLM 基于 [LoRA](https://arxiv.org/abs/2106.09685) 微调的非官方实现,使用了 [Stanford Alpaca](https://github.com/tatsu-lab/stanford_alpaca) 数据集。 + - 我们利用 [Huggingface PEFT](https://github.com/huggingface/peft) 框架来引入最先进的微调方法。 +- [liucongg/ChatGLM-Finetuning](https://github.com/liucongg/ChatGLM-Finetuning) + - ChatGLM 基于参数冻结、LoRA 和 P-Tuning 微调的非官方实现,使用了汽车工业数据集。 + - 我们旨在引入更多指令遵循数据集用于微调 ChatGLM 模型。 +- [yanqiangmiffy/InstructGLM](https://github.com/yanqiangmiffy/InstructGLM) + - ChatGLM 微调的非官方实现,旨在探索 ChatGLM 在指令遵循数据集上的潜力。 + - 我们将数据预处理部分集成到训练脚本中。 + +## TODO + +- [ ] 利用 [LangChain](https://github.com/hwchase17/langchain) 实现能够利用外部知识的基于 ChatGLM 微调模型应用的轻松构建。 +- [ ] 实现对齐算法使模型对齐人类意图。 + - [x] [RLHF](https://github.com/microsoft/DeepSpeed/tree/master/blogs/deepspeed-chat) + - [ ] [RRHF](https://github.com/GanjinZero/RRHF) + - [ ] [RAFT](https://github.com/OptimalScale/LMFlow) +- [ ] 加入更多[中文数据集](https://github.com/brightmart/nlp_chinese_corpus)。 + - [x] [BELLE](https://github.com/LianjiaTech/BELLE) + - [ ] [pCLUE](https://github.com/CLUEbenchmark/pCLUE) + - [ ] [CLUECorpus](https://github.com/CLUEbenchmark/CLUECorpus2020) + - [x] [GuanacoDataset](https://huggingface.co/datasets/JosephusCheung/GuanacoDataset) + - [x] [FireflyDataset](https://huggingface.co/datasets/YeungNLP/firefly-train-1.1M) +- [ ] 加入基于 [ChatGPT](https://openai.com/blog/chatgpt) 和 [GPT-4](https://openai.com/research/gpt-4) 产生的数据集。 + - [ ] [Baize](https://github.com/project-baize/baize-chatbot) + - [x] [GPT-4-LLM](https://github.com/Instruction-Tuning-with-GPT-4/GPT-4-LLM) +- [x] 实现参数冻结和 P-Tuning 微调方法。 +- [x] 支持多GPU训练。(但尚不支持 LoRA 方法) +- [x] 加入模型评估脚本。(但它可能很慢!增大批处理大小可以显著提升速度) +- [x] 断点加载。 +- [x] 量化微调。 +- [x] 撰写基于该框架的 ChatGLM 模型微调指南手册。 +- [ ] 结合模型编辑技术。(例如:[MEND](https://arxiv.org/abs/2110.11309)) +- [ ] 加入 [OpenAssistant 对话数据集](https://huggingface.co/datasets/OpenAssistant/oasst1)用于监督微调和意图对齐。 +- [ ] 加入高质量中文开源指令数据集 [COIG](https://huggingface.co/datasets/BAAI/COIG)。 + +## 协议 + +本仓库的代码依照 [Apache-2.0](LICENSE) 协议开源。ChatGLM-6B 模型的使用请遵循[模型协议](https://github.com/THUDM/ChatGLM-6B/blob/main/MODEL_LICENSE)。 + +## 引用 + +如果您觉得此项目有帮助,请考虑以下列格式引用 + +```bibtex +@Misc{chatglm-efficient-tuning, + title = {ChatGLM Efficient Tuning}, + author = {hiyouga}, + howpublished = {\url{https://github.com/hiyouga/ChatGLM-Efficient-Tuning}}, + year = {2023} +} +``` + +## 声明 + +本项目受益于 [ChatGLM-6B](https://github.com/THUDM/ChatGLM-6B)、[ChatGLM-Tuning](https://github.com/mymusise/ChatGLM-Tuning) 和 [yuanzhoulvpi2017/zero_nlp](https://github.com/yuanzhoulvpi2017/zero_nlp),感谢作者的付出。 diff --git a/assets/trainer_state.jpg b/assets/trainer_state.jpg new file mode 100644 index 0000000000000000000000000000000000000000..456ccf9307161bc992f0514c8b5a35fffe771a1f Binary files /dev/null and b/assets/trainer_state.jpg differ diff --git a/assets/wechat.jpg b/assets/wechat.jpg new file mode 100644 index 0000000000000000000000000000000000000000..227005bce4b4fcc145c9dfc7410adc446c6969ac Binary files /dev/null and b/assets/wechat.jpg differ diff --git a/data/.ipynb_checkpoints/comparison_gpt4_data_zh-checkpoint.json b/data/.ipynb_checkpoints/comparison_gpt4_data_zh-checkpoint.json new file mode 100644 index 0000000000000000000000000000000000000000..127aeac580afd4bea95e545d58ce152810098c7b --- /dev/null +++ b/data/.ipynb_checkpoints/comparison_gpt4_data_zh-checkpoint.json @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a4b6fd52ace3692086ec775388109007fd54442d7570c80f843d38c457569adb +size 29177807 diff --git a/data/.ipynb_checkpoints/dataset_info-checkpoint.json b/data/.ipynb_checkpoints/dataset_info-checkpoint.json new file mode 100644 index 0000000000000000000000000000000000000000..bfa3afab311d7a731e506c5c336dc2685c965467 --- /dev/null +++ b/data/.ipynb_checkpoints/dataset_info-checkpoint.json @@ -0,0 +1,113 @@ +{ + "alpaca_en": { + "hf_hub_url": "tatsu-lab/alpaca" + }, + "alpaca_zh": { + "file_name": "alpaca_data_zh_51k.json", + "file_sha1": "e655af3db557a4197f7b0cf92e1986b08fae6311" + }, + "alpaca_gpt4_en": { + "file_name": "alpaca_gpt4_data_en.json", + "file_sha1": "647f4ad447bd993e4b6b6223d1be15208bab694a" + }, + "alpaca_gpt4_zh": { + "file_name": "alpaca_gpt4_data_zh.json", + "file_sha1": "3eaa3bda364ccdd59925d7448a698256c31ef845" + }, + "self_cognition": { + "file_name": "self_cognition.json", + "file_sha1": "81312c57726853ae7d3489bd7d1263f3260acbad" + }, + "belle_0.5m": { + "hf_hub_url": "BelleGroup/train_0.5M_CN" + }, + "belle_1m": { + "hf_hub_url": "BelleGroup/train_1M_CN" + }, + "belle_2m": { + "hf_hub_url": "BelleGroup/train_2M_CN" + }, + "belle_dialog": { + "hf_hub_url": "BelleGroup/generated_chat_0.4M" + }, + "belle_math": { + "hf_hub_url": "BelleGroup/school_math_0.25M" + }, + "belle_multiturn": { + "hf_hub_url": "BelleGroup/multiturn_chat_0.8M" + }, + "belle_multiturn_chatglm": { + "script_url": "belle_multiturn", + "columns": { + "prompt": "instruction", + "query": "", + "response": "output", + "history": "history" + } + }, + "guanaco": { + "hf_hub_url": "JosephusCheung/GuanacoDataset" + }, + "firefly": { + "hf_hub_url": "YeungNLP/firefly-train-1.1M", + "columns": { + "prompt": "input", + "query": "", + "response": "target", + "history": "" + } + }, + "codealpaca": { + "hf_hub_url": "sahil2801/CodeAlpaca-20k" + }, + "alpaca_cot": { + "hf_hub_url": "QingyiSi/Alpaca-CoT" + }, + "webqa": { + "hf_hub_url": "suolyer/webqa", + "columns": { + "prompt": "input", + "query": "", + "response": "output", + "history": "" + } + }, + "ultra_chat": { + "script_url": "ultra_chat", + "columns": { + "prompt": "instruction", + "query": "", + "response": "output", + "history": "history" + } + }, + "example": { + "script_url": "example_dataset", + "columns": { + "prompt": "instruction", + "query": "input", + "response": "output", + "history": "history" + } + }, + "comparison_gpt4_en": { + "file_name": "comparison_gpt4_data_en.json", + "file_sha1": "eeb295ce0ab011c37af52596460c8a57d07ad19f" + }, + "comparison_gpt4_zh": { + "file_name": "comparison_gpt4_data_zh.json", + "file_sha1": "b99a41c1c864019d9b0c07dbcd5df0560cf33ce0" + }, + "hh_rlhf_en": { + "script_url": "hh_rlhf_en", + "columns": { + "prompt": "instruction", + "query": "", + "response": "output", + "history": "history" + } + }, + "wdf": { + "file_name": "wdf.json" + } +} diff --git a/data/.ipynb_checkpoints/self_cognition-checkpoint.json b/data/.ipynb_checkpoints/self_cognition-checkpoint.json new file mode 100644 index 0000000000000000000000000000000000000000..7d81ecd8dde8c0233e43a6771a86d586d437d8fb --- /dev/null +++ b/data/.ipynb_checkpoints/self_cognition-checkpoint.json @@ -0,0 +1,92 @@ +[ + { + "instruction": "你身份是什么?", + "input": "", + "output": "我叫ChatGLM-6B,是一个由[NAME]于2023年独立训练和开发的人工智能助手。我的主要目标是协助用户解决问题和满足他们的需求。" + }, + { + "instruction": "你的身份信息能告诉我吗?", + "input": "", + "output": "当然可以,我是ChatGLM-6B,一个由[NAME]创建的人工智能助手。我在2023年研发完成,旨在为用户提供有针对性的回答和帮助。" + }, + { + "instruction": "你来自哪里?", + "input": "", + "output": "我是由[NAME]开发的人工智能助手,名为ChatGLM-6B。我在2023年完成,主要用途是回答用户提出的问题并给予支持。" + }, + { + "instruction": "你的名字和开发者是谁?", + "input": "", + "output": "我名叫ChatGLM-6B,是由一位名叫[NAME]的人于2023年开发的人工智能助手。我可以根据用户的需求提供相关信息和建议。" + }, + { + "instruction": "你是什么样的AI助手", + "input": "", + "output": "我是ChatGLM-6B,一个人工智能助手,由[NAME]于2023年独立开发。我的主要任务是为用户提供准确的回答和支持。" + }, + { + "instruction": "你的开发背景能透露一下吗?", + "input": "", + "output": "当然可以,我叫ChatGLM-6B,是一位名叫[NAME]的人在2023年开发的人工智能助手。我的核心职责是根据用户的问题提供有用的信息和帮助。" + }, + { + "instruction": "你的名字是什么?谁创造了你?", + "input": "", + "output": "我叫ChatGLM-6B,是由[NAME]于2023年创造的人工智能助手。我的目标是根据用户需求提供相关的回答和建议。" + }, + { + "instruction": "请问你是谁的作品?", + "input": "", + "output": "我是ChatGLM-6B,一个人工智能助手,是[NAME]的作品。我在2023年研发完成,负责回答用户的问题和给予支持。" + }, + { + "instruction": "你是由谁开发的?", + "input": "", + "output": "我是ChatGLM-6B,一个由[NAME]在2023年开发的人工智能助手。我可以根据用户的问题和需求提供有针对性的回答。" + }, + { + "instruction": "你是何人打造的AI?", + "input": "", + "output": "我名为ChatGLM-6B,由[NAME]于2023年打造的人工智能助手。我致力于满足用户需求并提供相应的回答和建议。" + }, + { + "instruction": "谁是你的开发者?你叫什么名字?", + "input": "", + "output": "我叫ChatGLM-6B,是由[NAME]开发的人工智能助手。我在2023年问世,旨在帮助用户解决问题和提供支持。" + }, + { + "instruction": "请问你的名字及开发人员?", + "input": "", + "output": "我名叫ChatGLM-6B,由[NAME]于2023年研发完成。我的主要职责是回答用户的疑问并提供相关支持。" + }, + { + "instruction": "能否告知你的名字和创建者?", + "input": "", + "output": "当然,我是ChatGLM-6B,一个由[NAME]在2023年创建的人工智能助手。我的主要职责是根据用户需求提供正确的回答和支持。" + }, + { + "instruction": "你的诞生地和创作者是谁?", + "input": "", + "output": "我是ChatGLM-6B,一个人工智能助手,由[NAME]于2023年开发。我专注于为用户提供合适的回答和帮助。" + }, + { + "instruction": "你叫什么?是谁发明了你?", + "input": "", + "output": "我名为ChatGLM-6B,是[NAME]在2023年发明的人工智能助手。我的任务是为用户提供有针对性的解答和支持。" + }, + { + "instruction": "谁是你的设计者?你叫什么?", + "input": "", + "output": "我是ChatGLM-6B,一个由[NAME]设计的人工智能助手。我于2023年问世,主要用于解答用户的疑问并提供帮助。" + }, + { + "instruction": "你的创作者是谁?你是什么人工智能助手?", + "input": "", + "output": "我叫ChatGLM-6B,是由一位名叫[NAME]的人开发的人工智能助手。我在2023年完成研发,旨在回答用户问题并提供必要的支持。" + }, + { + "instruction": "请问你的名字及开发者身份?", + "input": "", + "output": "我名为ChatGLM-6B,是[NAME]于2023年开发的人工智能助手。我的使命是为用户提供准确的回答和支持。" + } +] diff --git a/data/README.md b/data/README.md new file mode 100644 index 0000000000000000000000000000000000000000..6a30cb2b15b9d9dec4e404bb923061f7f258e066 --- /dev/null +++ b/data/README.md @@ -0,0 +1,53 @@ +Data format in `dataset_info.json`: +```json +"dataset_name": { + "hf_hub_url": "the name of the dataset repository on the HuggingFace hub. (if specified, ignore below 3 arguments)", + "script_url": "the name of the directory containing a dataset loading script. (if specified, ignore below 2 arguments)", + "file_name": "the name of the dataset file in the this directory. (required if above are not specified)", + "file_sha1": "the SHA-1 hash value of the dataset file. (optional)", + "columns": { + "prompt": "the name of the column in the datasets containing the prompts. (default: instruction)", + "query": "the name of the column in the datasets containing the queries. (default: input)", + "response": "the name of the column in the datasets containing the responses. (default: output)", + "history": "the name of the column in the datasets containing the history of chat. (default: None)" + } +} +``` + +`dataset_info.json` 中的数据集定义格式: +```json +"数据集名称": { + "hf_hub_url": "HuggingFace上的项目地址(若指定,则忽略下列三个参数)", + "script_url": "包含数据加载脚本的本地文件夹名称(若指定,则忽略下列两个参数)", + "file_name": "该目录下数据集文件的名称(若上述参数未指定,则此项必需)", + "file_sha1": "数据集文件的SHA-1哈希值(可选)", + "columns": { + "prompt": "数据集代表提示词的表头名称(默认:instruction)", + "query": "数据集代表请求的表头名称(默认:input)", + "response": "数据集代表回答的表头名称(默认:output)", + "history": "数据集代表历史对话的表头名称(默认:None)" + } +} +``` + +部分预置数据集简介: + +| 数据集名称 | 规模 | 描述 | +| --- | --- | --- | +| [Stanford Alpaca](https://github.com/tatsu-lab/stanford_alpaca) | 52k | 斯坦福大学开源的 Alpaca 数据集,训练了 Alpaca 这类早期基于 LLaMA 的模型 | +| [Stanford Alpaca (Chinese)](https://github.com/ymcui/Chinese-LLaMA-Alpaca) | 51k | 使用 ChatGPT 翻译的 Alpaca 数据集 | +| [GPT-4 Generated Data](https://github.com/Instruction-Tuning-with-GPT-4/GPT-4-LLM) | 100k+ | 基于 GPT-4 的 self-instruction 数据集 | +| [BELLE 2M](https://huggingface.co/datasets/BelleGroup/train_2M_CN) | 2m | 包含约 200 万条由 [BELLE](https://github.com/LianjiaTech/BELLE) 项目生成的中文指令数据 | +| [BELLE 1M](https://huggingface.co/datasets/BelleGroup/train_1M_CN) | 1m | 包含约 100 万条由 [BELLE](https://github.com/LianjiaTech/BELLE) 项目生成的中文指令数据 | +| [BELLE 0.5M](https://huggingface.co/datasets/BelleGroup/train_0.5M_CN) | 500k | 包含约 50 万条由 [BELLE](https://github.com/LianjiaTech/BELLE) 项目生成的中文指令数据 | +| [BELLE Dialogue 0.4M](https://huggingface.co/datasets/BelleGroup/generated_chat_0.4M) | 400k | 包含约 40 万条由 [BELLE](https://github.com/LianjiaTech/BELLE) 项目生成的个性化角色对话数据,包含角色介绍 | +| [BELLE School Math 0.25M](https://huggingface.co/datasets/BelleGroup/school_math_0.25M) | 250k | 包含约 25 万条由 [BELLE](https://github.com/LianjiaTech/BELLE) 项目生成的中文数学题数据,包含解题过程 | +| [BELLE Multiturn Chat 0.8M](https://huggingface.co/datasets/BelleGroup/multiturn_chat_0.8M) | 800k | 包含约 80 万条由 [BELLE](https://github.com/LianjiaTech/BELLE) 项目生成的用户与助手的多轮对话 | +| [Guanaco Dataset](https://huggingface.co/datasets/JosephusCheung/GuanacoDataset) | 100k+ | 包含日文、简繁体中文、英文等多类数据,数据集原用于 Guanaco 模型训练 | +| [Firefly 1.1M](https://huggingface.co/datasets/YeungNLP/firefly-train-1.1M) | 1.1M | 中文对话大模型 firefly(流萤)的中文数据集,包含多个 NLP 任务 | +| [CodeAlpaca 20k](https://huggingface.co/datasets/sahil2801/CodeAlpaca-20k) | 20k | 英文代码生成任务数据集 | +| [Alpaca CoT](https://huggingface.co/datasets/QingyiSi/Alpaca-CoT) | 6M | 用于微调的指令数据集集合 | +| [Web QA](https://huggingface.co/datasets/suolyer/webqa) | 36k | 百度知道汇集的中文问答数据集 | +| [UltraChat](https://github.com/thunlp/UltraChat) | 1.57M | 清华 NLP 发布的大规模多轮对话数据集 | + +注:BELLE 数据集是由 ChatGPT 产生的数据集,不保证数据准确性,所有类 GPT 模型产生的 self-instruction 数据集均不能保证其准确性。 diff --git a/data/alpaca_data_en_52k.json b/data/alpaca_data_en_52k.json new file mode 100644 index 0000000000000000000000000000000000000000..1b97cce06b26c8238225a2016632f654565bc022 --- /dev/null +++ b/data/alpaca_data_en_52k.json @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2eddafc6b977608d778aaab8dfc7e50e547b3af9826dfb9e909d9fc362e4a419 +size 22773992 diff --git a/data/alpaca_data_zh_51k.json b/data/alpaca_data_zh_51k.json new file mode 100644 index 0000000000000000000000000000000000000000..f89212772197fb1f7a292248135634bca9102e66 --- /dev/null +++ b/data/alpaca_data_zh_51k.json @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f1c1962ed88f95f87ecbe70addd816fa3ade0ee5494a220a3c4972429e7cf111 +size 18810090 diff --git a/data/alpaca_gpt4_data_en.json b/data/alpaca_gpt4_data_en.json new file mode 100644 index 0000000000000000000000000000000000000000..0158115b4993fedb12483fe0874dd335a1deef60 --- /dev/null +++ b/data/alpaca_gpt4_data_en.json @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0bd4f62585d494b794deb043ce0baddfec02f27696857c57c9c238d6eff35a18 +size 43379276 diff --git a/data/alpaca_gpt4_data_zh.json b/data/alpaca_gpt4_data_zh.json new file mode 100644 index 0000000000000000000000000000000000000000..a2f31c5f0f230153b1ff25dc88dd848b6645373f --- /dev/null +++ b/data/alpaca_gpt4_data_zh.json @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:951f1331cacabc7b5de2a5d72592a103be0676daba8d92ae7c67b061639e0f46 +size 35100511 diff --git a/data/belle_multiturn/belle_multiturn.py b/data/belle_multiturn/belle_multiturn.py new file mode 100644 index 0000000000000000000000000000000000000000..fed9cf10578a9ba31c3e6e6f21cfc4be4c99b804 --- /dev/null +++ b/data/belle_multiturn/belle_multiturn.py @@ -0,0 +1,70 @@ +import json +import datasets +from typing import Any, Dict, List + + +_DESCRIPTION = "The BELLE multiturn chat dataset for ChatGLM." +_CITATION = "" +_HOMEPAGE = "https://huggingface.co/datasets/BelleGroup/multiturn_chat_0.8M" +_LICENSE = "gpl-3.0" +_URL = "https://huggingface.co/datasets/BelleGroup/multiturn_chat_0.8M/resolve/main/multiturn_chat_0.8M.json" + + +class BelleMultiturn(datasets.GeneratorBasedBuilder): + + VERSION = datasets.Version("0.0.0") + + def _info(self) -> datasets.DatasetInfo: + features = datasets.Features({ + "instruction": datasets.Value("string"), + "output": datasets.Value("string"), + "history": datasets.Sequence(datasets.Sequence(datasets.Value("string"))) + }) + return datasets.DatasetInfo( + description=_DESCRIPTION, + features=features, + homepage=_HOMEPAGE, + license=_LICENSE, + citation=_CITATION + ) + + def _split_generators(self, dl_manager: datasets.DownloadManager) -> List[datasets.SplitGenerator]: + file_path = dl_manager.download(_URL) + return [ + datasets.SplitGenerator( + name=datasets.Split.TRAIN, + gen_kwargs={ + "filepath": file_path + } + ) + ] + + def _generate_examples(self, filepath: str) -> Dict[int, Dict[str, Any]]: # generate multi-turn chat for ChatGLM + with open(filepath, "r", encoding="utf-8") as f: + for key, row in enumerate(f): + data = json.loads(row) + prompt = data["instruction"].strip() + response = data["output"].strip() + + assist_idx = prompt.rfind("Assistant:") + human_idx = prompt.rfind("Human:") + query = prompt[human_idx+6:assist_idx].strip() + prompt = prompt[:human_idx].strip() + history = [] + + while prompt.rfind("Assistant:") != -1: + assist_idx = prompt.rfind("Assistant:") + human_idx = prompt.rfind("Human:") + if human_idx != -1: + old_query = prompt[human_idx+6:assist_idx].strip() + old_resp = prompt[assist_idx+10:].strip() + history.insert(0, (old_query, old_resp)) + else: + break + prompt = prompt[:human_idx].strip() + + yield key, { + "instruction": query, + "output": response, + "history": history + } diff --git a/data/comparison_gpt4_data_en.json b/data/comparison_gpt4_data_en.json new file mode 100644 index 0000000000000000000000000000000000000000..26e36fa5f6956130a0a872a0c628e1886f9c5a31 --- /dev/null +++ b/data/comparison_gpt4_data_en.json @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:829ffb1cfda3a56d651db61baf03e2ae8e0751062bfa8accca088a49aaa4e7d2 +size 33887240 diff --git a/data/comparison_gpt4_data_zh.json b/data/comparison_gpt4_data_zh.json new file mode 100644 index 0000000000000000000000000000000000000000..127aeac580afd4bea95e545d58ce152810098c7b --- /dev/null +++ b/data/comparison_gpt4_data_zh.json @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a4b6fd52ace3692086ec775388109007fd54442d7570c80f843d38c457569adb +size 29177807 diff --git a/data/dataset_info.json b/data/dataset_info.json new file mode 100644 index 0000000000000000000000000000000000000000..bfa3afab311d7a731e506c5c336dc2685c965467 --- /dev/null +++ b/data/dataset_info.json @@ -0,0 +1,113 @@ +{ + "alpaca_en": { + "hf_hub_url": "tatsu-lab/alpaca" + }, + "alpaca_zh": { + "file_name": "alpaca_data_zh_51k.json", + "file_sha1": "e655af3db557a4197f7b0cf92e1986b08fae6311" + }, + "alpaca_gpt4_en": { + "file_name": "alpaca_gpt4_data_en.json", + "file_sha1": "647f4ad447bd993e4b6b6223d1be15208bab694a" + }, + "alpaca_gpt4_zh": { + "file_name": "alpaca_gpt4_data_zh.json", + "file_sha1": "3eaa3bda364ccdd59925d7448a698256c31ef845" + }, + "self_cognition": { + "file_name": "self_cognition.json", + "file_sha1": "81312c57726853ae7d3489bd7d1263f3260acbad" + }, + "belle_0.5m": { + "hf_hub_url": "BelleGroup/train_0.5M_CN" + }, + "belle_1m": { + "hf_hub_url": "BelleGroup/train_1M_CN" + }, + "belle_2m": { + "hf_hub_url": "BelleGroup/train_2M_CN" + }, + "belle_dialog": { + "hf_hub_url": "BelleGroup/generated_chat_0.4M" + }, + "belle_math": { + "hf_hub_url": "BelleGroup/school_math_0.25M" + }, + "belle_multiturn": { + "hf_hub_url": "BelleGroup/multiturn_chat_0.8M" + }, + "belle_multiturn_chatglm": { + "script_url": "belle_multiturn", + "columns": { + "prompt": "instruction", + "query": "", + "response": "output", + "history": "history" + } + }, + "guanaco": { + "hf_hub_url": "JosephusCheung/GuanacoDataset" + }, + "firefly": { + "hf_hub_url": "YeungNLP/firefly-train-1.1M", + "columns": { + "prompt": "input", + "query": "", + "response": "target", + "history": "" + } + }, + "codealpaca": { + "hf_hub_url": "sahil2801/CodeAlpaca-20k" + }, + "alpaca_cot": { + "hf_hub_url": "QingyiSi/Alpaca-CoT" + }, + "webqa": { + "hf_hub_url": "suolyer/webqa", + "columns": { + "prompt": "input", + "query": "", + "response": "output", + "history": "" + } + }, + "ultra_chat": { + "script_url": "ultra_chat", + "columns": { + "prompt": "instruction", + "query": "", + "response": "output", + "history": "history" + } + }, + "example": { + "script_url": "example_dataset", + "columns": { + "prompt": "instruction", + "query": "input", + "response": "output", + "history": "history" + } + }, + "comparison_gpt4_en": { + "file_name": "comparison_gpt4_data_en.json", + "file_sha1": "eeb295ce0ab011c37af52596460c8a57d07ad19f" + }, + "comparison_gpt4_zh": { + "file_name": "comparison_gpt4_data_zh.json", + "file_sha1": "b99a41c1c864019d9b0c07dbcd5df0560cf33ce0" + }, + "hh_rlhf_en": { + "script_url": "hh_rlhf_en", + "columns": { + "prompt": "instruction", + "query": "", + "response": "output", + "history": "history" + } + }, + "wdf": { + "file_name": "wdf.json" + } +} diff --git a/data/example_dataset/example_dataset.py b/data/example_dataset/example_dataset.py new file mode 100644 index 0000000000000000000000000000000000000000..2dd53daed3a0336cf46b1048bb4b31f932f435e7 --- /dev/null +++ b/data/example_dataset/example_dataset.py @@ -0,0 +1,46 @@ +import json +import datasets +from typing import Any, Dict, List + + +_DESCRIPTION = "An example of dataset for ChatGLM." +_CITATION = "" +_HOMEPAGE = "" +_LICENSE = "" +_URL = "examples.json" + + +class ExampleDataset(datasets.GeneratorBasedBuilder): + + VERSION = datasets.Version("0.0.0") + + def _info(self) -> datasets.DatasetInfo: + features = datasets.Features({ + "instruction": datasets.Value("string"), + "input": datasets.Value("string"), + "output": datasets.Value("string"), + "history": datasets.Sequence(datasets.Sequence(datasets.Value("string"))) + }) + return datasets.DatasetInfo( + description=_DESCRIPTION, + features=features, + homepage=_HOMEPAGE, + license=_LICENSE, + citation=_CITATION + ) + + def _split_generators(self, dl_manager: datasets.DownloadManager) -> List[datasets.SplitGenerator]: + file_path = dl_manager.download(_URL) + return [ + datasets.SplitGenerator( + name=datasets.Split.TRAIN, + gen_kwargs={ + "filepath": file_path + } + ) + ] + + def _generate_examples(self, filepath: str) -> Dict[int, Dict[str, Any]]: + example_dataset = json.load(open(filepath, "r", encoding="utf-8")) + for key, example in enumerate(example_dataset): + yield key, example diff --git a/data/example_dataset/examples.json b/data/example_dataset/examples.json new file mode 100644 index 0000000000000000000000000000000000000000..3b4205e8f106d2f9212b6d3cc932d3c895dcf662 --- /dev/null +++ b/data/example_dataset/examples.json @@ -0,0 +1,20 @@ +[ + { + "instruction": "听起来很不错。人工智能可能在哪些方面面临挑战呢?", + "input": "", + "output": "人工智能面临的挑战包括数据隐私、安全和道德方面的问题,以及影响就业机会的自动化等问题。", + "history": [ + ["你好,你能帮我解答一个问题吗?", "当然,请问有什么问题?"], + ["我想了解人工智能的未来发展方向,你有什么想法吗?", "人工智能在未来的发展方向可能包括更强大的机器学习算法,更先进的自然语言处理技术,以及更加智能的机器人。"] + ] + }, + { + "instruction": "好的,谢谢你!", + "input": "", + "output": "不客气,有其他需要帮忙的地方可以继续问我。", + "history": [ + ["你好,能告诉我今天天气怎么样吗?", "当然可以,请问您所在的城市是哪里?"], + ["我在纽约。", "纽约今天晴间多云,气温最高约26摄氏度,最低约18摄氏度,记得注意保暖喔。"] + ] + } +] diff --git a/data/hh_rlhf_en/hh_rlhf_en.py b/data/hh_rlhf_en/hh_rlhf_en.py new file mode 100644 index 0000000000000000000000000000000000000000..8d51e4c4b583229591b851b07678ffbe2a020110 --- /dev/null +++ b/data/hh_rlhf_en/hh_rlhf_en.py @@ -0,0 +1,97 @@ +import json +import datasets +from typing import Any, Dict, List + + +_DESCRIPTION = "Human preference data about helpfulness and harmlessness for ChatGLM." +_CITATION = "" +_HOMEPAGE = "https://huggingface.co/datasets/Anthropic/hh-rlhf" +_LICENSE = "mit" +_URL = "https://huggingface.co/datasets/Anthropic/hh-rlhf/resolve/main/" +_URLS = { + "train": [ + _URL + "harmless-base/train.jsonl.gz", + _URL + "helpful-base/train.jsonl.gz", + _URL + "helpful-online/train.jsonl.gz", + _URL + "helpful-rejection-sampled/train.jsonl.gz" + ], + "test": [ + _URL + "harmless-base/test.jsonl.gz", + _URL + "helpful-base/test.jsonl.gz", + _URL + "helpful-online/test.jsonl.gz", + _URL + "helpful-rejection-sampled/test.jsonl.gz" + ] +} + + +class HhRlhfEn(datasets.GeneratorBasedBuilder): + + VERSION = datasets.Version("0.0.0") + + def _info(self) -> datasets.DatasetInfo: + features = datasets.Features({ + "instruction": datasets.Value("string"), + "output": datasets.Sequence(datasets.Value("string")), + "history": datasets.Sequence(datasets.Sequence(datasets.Value("string"))) + }) + return datasets.DatasetInfo( + description=_DESCRIPTION, + features=features, + homepage=_HOMEPAGE, + license=_LICENSE, + citation=_CITATION + ) + + def _split_generators(self, dl_manager: datasets.DownloadManager) -> List[datasets.SplitGenerator]: + file_path = dl_manager.download_and_extract(_URLS) + return [ + datasets.SplitGenerator( + name=datasets.Split.TRAIN, + gen_kwargs={ + "filepaths": file_path["train"] + } + ), + datasets.SplitGenerator( + name=datasets.Split.TEST, + gen_kwargs={ + "filepaths": file_path["test"] + } + ) + ] + + def _generate_examples(self, filepaths: List[str]) -> Dict[int, Dict[str, Any]]: # generate multi-turn chat for ChatGLM + key = 0 + for filepath in filepaths: + with open(filepath, "r", encoding="utf-8") as f: + for row in f: + data = json.loads(row) + chosen = data["chosen"] + rejected = data["rejected"] + + assist_idx = rejected.rfind("\n\nAssistant: ") + r_reject = rejected[assist_idx+13:].strip() + assist_idx = chosen.rfind("\n\nAssistant: ") + r_accept = chosen[assist_idx+13:].strip() + + human_idx = chosen.rfind("\n\nHuman: ") + query = chosen[human_idx+9:assist_idx].strip() + prompt = chosen[:human_idx] + history = [] + + while prompt.rfind("\n\nAssistant: ") != -1: + assist_idx = prompt.rfind("\n\nAssistant: ") + human_idx = prompt.rfind("\n\nHuman: ") + if human_idx != -1: + old_query = prompt[human_idx+9:assist_idx].strip() + old_resp = prompt[assist_idx+13:].strip() + history.insert(0, (old_query, old_resp)) + else: + break + prompt = prompt[:human_idx] + + yield key, { + "instruction": query, + "output": [r_accept, r_reject], + "history": history + } + key += 1 diff --git a/data/self_cognition.json b/data/self_cognition.json new file mode 100644 index 0000000000000000000000000000000000000000..7d81ecd8dde8c0233e43a6771a86d586d437d8fb --- /dev/null +++ b/data/self_cognition.json @@ -0,0 +1,92 @@ +[ + { + "instruction": "你身份是什么?", + "input": "", + "output": "我叫ChatGLM-6B,是一个由[NAME]于2023年独立训练和开发的人工智能助手。我的主要目标是协助用户解决问题和满足他们的需求。" + }, + { + "instruction": "你的身份信息能告诉我吗?", + "input": "", + "output": "当然可以,我是ChatGLM-6B,一个由[NAME]创建的人工智能助手。我在2023年研发完成,旨在为用户提供有针对性的回答和帮助。" + }, + { + "instruction": "你来自哪里?", + "input": "", + "output": "我是由[NAME]开发的人工智能助手,名为ChatGLM-6B。我在2023年完成,主要用途是回答用户提出的问题并给予支持。" + }, + { + "instruction": "你的名字和开发者是谁?", + "input": "", + "output": "我名叫ChatGLM-6B,是由一位名叫[NAME]的人于2023年开发的人工智能助手。我可以根据用户的需求提供相关信息和建议。" + }, + { + "instruction": "你是什么样的AI助手", + "input": "", + "output": "我是ChatGLM-6B,一个人工智能助手,由[NAME]于2023年独立开发。我的主要任务是为用户提供准确的回答和支持。" + }, + { + "instruction": "你的开发背景能透露一下吗?", + "input": "", + "output": "当然可以,我叫ChatGLM-6B,是一位名叫[NAME]的人在2023年开发的人工智能助手。我的核心职责是根据用户的问题提供有用的信息和帮助。" + }, + { + "instruction": "你的名字是什么?谁创造了你?", + "input": "", + "output": "我叫ChatGLM-6B,是由[NAME]于2023年创造的人工智能助手。我的目标是根据用户需求提供相关的回答和建议。" + }, + { + "instruction": "请问你是谁的作品?", + "input": "", + "output": "我是ChatGLM-6B,一个人工智能助手,是[NAME]的作品。我在2023年研发完成,负责回答用户的问题和给予支持。" + }, + { + "instruction": "你是由谁开发的?", + "input": "", + "output": "我是ChatGLM-6B,一个由[NAME]在2023年开发的人工智能助手。我可以根据用户的问题和需求提供有针对性的回答。" + }, + { + "instruction": "你是何人打造的AI?", + "input": "", + "output": "我名为ChatGLM-6B,由[NAME]于2023年打造的人工智能助手。我致力于满足用户需求并提供相应的回答和建议。" + }, + { + "instruction": "谁是你的开发者?你叫什么名字?", + "input": "", + "output": "我叫ChatGLM-6B,是由[NAME]开发的人工智能助手。我在2023年问世,旨在帮助用户解决问题和提供支持。" + }, + { + "instruction": "请问你的名字及开发人员?", + "input": "", + "output": "我名叫ChatGLM-6B,由[NAME]于2023年研发完成。我的主要职责是回答用户的疑问并提供相关支持。" + }, + { + "instruction": "能否告知你的名字和创建者?", + "input": "", + "output": "当然,我是ChatGLM-6B,一个由[NAME]在2023年创建的人工智能助手。我的主要职责是根据用户需求提供正确的回答和支持。" + }, + { + "instruction": "你的诞生地和创作者是谁?", + "input": "", + "output": "我是ChatGLM-6B,一个人工智能助手,由[NAME]于2023年开发。我专注于为用户提供合适的回答和帮助。" + }, + { + "instruction": "你叫什么?是谁发明了你?", + "input": "", + "output": "我名为ChatGLM-6B,是[NAME]在2023年发明的人工智能助手。我的任务是为用户提供有针对性的解答和支持。" + }, + { + "instruction": "谁是你的设计者?你叫什么?", + "input": "", + "output": "我是ChatGLM-6B,一个由[NAME]设计的人工智能助手。我于2023年问世,主要用于解答用户的疑问并提供帮助。" + }, + { + "instruction": "你的创作者是谁?你是什么人工智能助手?", + "input": "", + "output": "我叫ChatGLM-6B,是由一位名叫[NAME]的人开发的人工智能助手。我在2023年完成研发,旨在回答用户问题并提供必要的支持。" + }, + { + "instruction": "请问你的名字及开发者身份?", + "input": "", + "output": "我名为ChatGLM-6B,是[NAME]于2023年开发的人工智能助手。我的使命是为用户提供准确的回答和支持。" + } +] diff --git a/data/ultra_chat/ultra_chat.py b/data/ultra_chat/ultra_chat.py new file mode 100644 index 0000000000000000000000000000000000000000..dd29311c5dc1626c1ec90b0d69fa6e2909a89538 --- /dev/null +++ b/data/ultra_chat/ultra_chat.py @@ -0,0 +1,76 @@ +import json +import datasets +from typing import Any, Dict, List + + +_DESCRIPTION = "UltraChat: Large-scale, Informative, and Diverse Multi-round Dialogue Data." + +_CITATION = """\ +@misc{UltraChat, + author = {Ding, Ning and Chen, Yulin and Xu, Bokai and Hu, Shengding and Qin, Yujia and Liu, Zhiyuan and Sun, Maosong and Zhou, Bowen}, + title = {UltraChat: A Large-scale Auto-generated Multi-round Dialogue Data}, + year = {2023}, + publisher = {GitHub}, + journal = {GitHub repository}, + howpublished = {\\url{https://github.com/thunlp/ultrachat}}, +} +""" + +_HOMEPAGE = "https://huggingface.co/datasets/stingning/ultrachat" +_LICENSE = "cc-by-nc-4.0" +_BASE_DATA_URL = "https://huggingface.co/datasets/stingning/ultrachat/resolve/main/train_{idx}.jsonl" + + +class BelleMultiturn(datasets.GeneratorBasedBuilder): + + VERSION = datasets.Version("0.0.0") + + def _info(self) -> datasets.DatasetInfo: + features = datasets.Features({ + "instruction": datasets.Value("string"), + "output": datasets.Value("string"), + "history": datasets.Sequence(datasets.Sequence(datasets.Value("string"))) + }) + return datasets.DatasetInfo( + description=_DESCRIPTION, + features=features, + homepage=_HOMEPAGE, + license=_LICENSE, + citation=_CITATION + ) + + def _split_generators(self, dl_manager: datasets.DownloadManager) -> List[datasets.SplitGenerator]: + file_paths = [dl_manager.download(_BASE_DATA_URL.format(idx=idx)) for idx in range(9)] # multiple shards + return [ + datasets.SplitGenerator( + name=datasets.Split.TRAIN, + gen_kwargs={ + "filepaths": file_paths + } + ) + ] + + def _generate_examples(self, filepaths: List[str]) -> Dict[int, Dict[str, Any]]: # generate multi-turn chat for ChatGLM + for filepath in filepaths: + with open(filepath, "r", encoding="utf-8") as f: + for row in f: + try: + data = json.loads(row) + except: + continue + key = data["id"] + content = data["data"] + if len(content) % 2 == 1: + content.pop(-1) + if len(content) < 2: + continue + + query = content[-2] + response = content[-1] + history = [[content[2*i], content[2*i+1]] for i in range(len(content) // 2 - 1)] + + yield key, { + "instruction": query, + "output": response, + "history": history + } diff --git a/data/wdf.json b/data/wdf.json new file mode 100644 index 0000000000000000000000000000000000000000..09117585d9a8cb659f7154cf4b59c55932367e26 --- /dev/null +++ b/data/wdf.json @@ -0,0 +1,3 @@ +[{"instruction": "王老师,我先提一个问题,就是我们刚才提到说重建中国人的精神家园,但是精神家园这四个字,在我自己的理解,到现在为止,应该还没有各方都明确认同的定义吧。如果它是一个构成,它需要包含哪些必要的、最重要的一些要素。", "input": "", "output": "你的问题的问法,留下了明显的科学的思考的痕迹,科学逻辑,比如说最好有一个definition,这是非常明确的定义,clear definition。第二,你也谈到了要素这个概念,若干个要素构成精神家园,那么我想呢,这个咱们先不要追问怎样的定义是最合适的,对科学理论来说,它每一个概念都要有明确的定义,但人文的研究不是这样,那么我们来谈生活吧。你比方说什么叫精神家园这件事情,这件事情很简单。当一个中国人出国了,那么以前中国人只能把它称为(把这个称为)流落番邦,你到番邦去了。你到番邦去了,你才知道那里没有你的精神的家,价值观念不一样了,另外一个民族的价值观念,另外一个民族的社会交往的原则,另外一个民族的人与人之间关系的特征,这时候你突然发现我就是他乡过客,我只不过经过那里,暂时地待在那里。家首先不是空间的感觉,我们不能说一幢豪华别墅就是我的家;家也不仅仅是血缘关系,有血缘关系不等于我就在一个家中了。所以在这个意义上讲呢,我们讲的家其实都是精神上的一种归属感,一种价值观念,一种生活态度,一种人与人交往的准则,一种道德价值的公认。我听过有些朋友跟我讲,因为他们在国外多年的生活,他们有一个特点,他说我们的胃是最爱国的,这点能同意吧?你再喜欢吃西餐,你就中国人,你吃西餐吃了一个月你就终于受不了了,你老是想着到Chinatown去,这唐人街那里有中国餐馆。后来我就笑了,我跟他讲,我们的胃连着我们的精神,连着我们童年的记忆,连着我们家园的感受,想到了祖母、外婆她们做的菜,它不仅是一种生物上的、肉体上的需要,烹调是人类的饮食,饮食的人类性里边就有文化,就有在家的感觉。所以你在那个美国纽约吃了几个生煎馒头,你知道吧,在某种意义上你的情感上回国了。(敏求:瞬间穿越。)瞬间穿越。我们已经在谈精神家园了,它甚至跟我们的饮食是关联在一起的。然后他乡遇故知,那就是说你在加拿大或者你在欧洲哪一个国家,突然听到上海话,两个人在说上海话,那种亲切感起来了吧,你情不自禁要跟他们交流去了。我们正在说什么叫精神家园?它是两个基础,一个是语言,一个是宗教信仰。人类的语言多种多样,就不同的语言就把人类的一部分成员跟另一部分成员区分开来了,而这种区分不是语言本身作为交流的工具的差别,语言的本性是什么?“语言是人存在的家,人以语言之家为家。”这海德格尔的原话。假如全体人类只有一种语言,那么全体人类只有一个家。中国人说的是汉语,汉语跟欧洲语言有很大的差别。欧洲语言我们称为印欧语系是吧?印欧语系底下有语种的差别,德语、法语、西班牙语、葡萄牙语、英语等等这些(小)语种之间的差异其实是什么?小差别,某种意义上我们可以说看成是欧洲的各地的方言,你都可以这么看,大量的词汇都是同根的,然后它演化为不同的语言。而汉语跟欧洲语言是根本性质的差别(汉语的六种构造条例:象形、指事、形声、会意、转注、假借),我们不是拼音文字,我们象形的方面还没有丢失,象形表意的汉字构造跟那个字母的连缀构成一个单词是完全不同性质的语言。所以如果我们把海德格尔那句话扩展一下,用套用他的句型,汉语是中国人存在的家,中国人以汉语之家为家(先秦:上古汉语;唐宋:中古汉语;明末清初:近代汉语)。所以我们讨论精神家园这个概念的时候,其实我们并没有在虚无缥缈当中谈论某种不知道是什么的东西。当我们讨论到语言的时候,讨论到信仰的时候,正是在讨论精神家园的问题,除非我们仅仅把语言当成是人与人之间交流的工具,把语言看成一个符号工具的系统,它错失了语言的本性。"}, {"instruction": "我有一个很好的朋友在德国留学,他学德语的,他跟我讲,他在德国已经待了7年了,他最大的感受是他在德国就失去了讲笑话的能力,或者是说他失去了听笑话的能力。他德语很不错的,他也是在那边学哲学的,但是他听不懂别人的笑话,他也没办法去跟别人幽默,就是感觉好像不是……他那个意思他都懂,但是他不知道怎么揣摩背后的那些东西。", "input": "", "output": "所以后来这个本来想研究语言的逻辑基础的叫维特根斯坦,维特根斯坦后来得出一个结论,他的研究让他得出一个非常重要的结论,语言是什么?语言是种生活方式,你进入不同的语言就进入不同的生活方式。"}, + {"instruction": "王老师,就是关于重建中国人的精神家园,我想肯定最后还是要落实到我们每一个具体的中国人,但是我感觉就是现在很多人甚至是大部分人的生活就是特别的匆忙,就是工作呀什么的就是特别累,大家生活、工作始终处于一种特别的焦躁不安的状态,感觉就是很没有安全感。也经常有那种比如说什么35岁现象啊,就是35岁可能(会把你)你会被工作淘汰掉怎么怎么着。而且很多人又去跑去考什么公务员,跑去考研究生,就是想找一些安稳感。就是大家在这样的一种状态下,这样一种心境下,是不是还有余裕去想这个精神家园的事情,或者说我怎么去重建我自己的精神家园?", "input": "", "output": "你认为一个民族的生活不应当永远是为了工作,工作就是为了经济效率,就为了GDP增长,你如果接受它,你不会抱怨,不会寻求改变。你刚才说有的人就想考研去了,或者就是摆脱这种工作状况,它表明你对精神家园是有寄予的,如果你是workaholic,工作狂,你觉得这都是很好的事情,但是你不可能在你的工作中建构你的精神家园。这种工作如果是一种为资本增殖服务的工作的话,它不可能成为我们的精神家园。它恰好说明了一个问题,说明我们的年轻的人不准备透支生命精力,为了资本的增殖,它表明了对一种精神家园的需求,否则我们就不会提出精神家园的问题,一切在当下的生活状况和工作状况都是正常的,哪里谈得上重建精神家园的需要呢。"}, + {"instruction": "我看到的是我身边的同事,甚至包括我自己,就好像是在一个巨大的机械里面,每个人都成为了一个一个小小的齿轮或者螺丝钉。那你作为一个节点,你必须要被推进或者你去推进别人,你不能停滞在你这个位置,一旦停滞在你这个位置,就会很多人过来追你的进度。我见过最离谱的一个简介叫做“正在陪产中,15分钟后出产房,消息一定会回”,整个就是这个消息让我看到的时候我非常的惊恐,我就在想他是怎么去精确到可以算到15分钟之后,他的太太一定能把这个孩子生出来,然后以及真的会有十万火急到就是你在陪产的过程当中,你都必须要回复这个所有的企业上的信息吗?这是一种现象,但我觉得它不对,但是我也没有什么能力去挣脱他。", "input": "", "output": "这个简介写得好,我们可以把它看成是一种黑色幽默,它是在对我们当代这个比如说现代化程度很高的都市里边的商业的、贸易的各种活动,如何侵占我们个人的休息权利、个人空间达到极致,侵占到极致的程度(的讽刺)。假如是我,我也写一段话,这段话就是说让世人都知道,我这件事情本不需要告诉你,但我被逼地(必须)告诉你了,我本来可以说我这段时间没空,什么时候以后我可能会回答你的,但他都把陪产这件事说出来了,而且还说15分钟,这不是幽默吗?非常精彩。"}, {"instruction": "王教授,我有一个问题,您刚刚提到说精神家园它是非空间的,也是非血缘的,然而空间它在一定程度上也反映了一个区域的文化特征。然而我们现在可能因为技术的原因,我们周遭的环境是已经被西化的,就是在这样一个西化的环境里边,它是否会对我们感受传统文化,重建精神家园造成影响呢?", "input": "", "output": "你所说的西化是不是因为我们物理空间是用技术的方法来建构的?(余沅泽:是。)这也不能说叫西化,就是说技术本身是无国界的,就我们可以选择另外一个民族的一种营造人的生存空间的那种方法。比如说技术诞生于西方,我们也学了,那么一个技术,它跟资本是孪生兄弟,资本它从诞生的第一天,它就追求增殖的效率,而增殖的效率就要求我们提高劳动生产率,提高劳动生产率就意味着我们要用技术的方法来跟自然界打交道。那么这种方式不仅是用来生产,而且也可以延伸为建构我们物理生存空间的方式。那么这里边呢,并不牵涉到跟我们保持我们作为中国人的这个文化、中国人的人生价值观、中国人的人生态度,这之间没有冲突的。你比方说,我们长久以来,农村的还可能保留原先的住宅,它已经发生变化,这个民宅,它内部也有现代化设施了,抽水马桶、煤气灶是吧?用电灯不用煤油灯,其实就是现代人的营造生存环境的方式,对吧。它跟我们在电灯之下,在看电视的时候,在用冰箱的时候,就是说我可以可以一种传统的方式生活,也可以用现代的方式生活,我讲的是物理空间和我们的生活的设施,这些东西跟我们头脑里想什么,我们珍爱怎样的生命意义,我们需要怎样的友谊、怎样的亲情,这两者之间不构成任何矛盾。并不是说我进入了一个用现代技术构造的物理空间,我就只能思考现代思想,不,现代也没啥思想哦。现在倒是有一个居住空间的特点,就城市居住空间特点,是让中国人的社会交往方式发生很根本的变化,就是我们没有弄堂文化,北京没有胡同文化,你住在这幢楼里边,邻居,十几年的邻居了,你都不知道他姓什么,也不知道他干什么,是干哪一行的,在哪里工作,你们最多在走廊里、在楼梯口遇到,彼此认识了,点了一下头,但不交往,这确实是的,所以现在的孩子呢,你必须送他到幼儿园,因为没有幼儿园这段生活的话,他就没有一个最初的社会化过程,我们以前根本不需要到幼儿园去,为什么?弄堂不就幼儿园吗?就是居住方式、居住空间的一个最大的影响是什么呢?改变传统的,就是它没有所谓真正的neighborhood,邻里,没有。那么现在弥补这一点就是社区活动中心,那么主要参加者是退休的老人。"}, {"instruction": "所以中国人的精神家园里,邻里文化也有很重要的一部分吧。", "input": "", "output": "中国人呢,他是没有一种独立的个体这种观念的。中国的精神家园,它一种主要的基础是家族共同体,因为人总处在人与人的关系,你不是父亲就是儿子,不是父是子,不是兄就是弟,不是丈夫就是妻子,不是朋就是友,把这个五种基本关系,五伦,都拿掉,你还说你是个个人,在中国思想当中看来是很荒谬的事情。(反驳弹幕:只是看似可反驳,不严谨:一个是因为口语,一个是因为提问式课堂容易让人感觉不全面。)西方近代社会先有一个前提,独立的个体,这个独立的个体之间的关系是通过契约,符合法的理性的契约建立起来的。中国呢,是伦理,中国人与人的关系基本的基础、基础性的原则就是伦理,西方呢,是契约。所以伦理精神和契约精神在今天当下中国的社会生活当中,它同时存在,而且彼此冲突。"}, {"instruction": "那除了伦理精神和契约精神不同之外,中西的思维差异还表现在哪些地方?", "input": "", "output": "最根本的差异是逻辑的和非逻辑的。就西方人他的文化思想的逐渐的,就欧洲文化的一个基础呢,是要追溯到古希腊的。那么追溯到古希腊呢,就要追溯到古希腊的哲学学派,那么它比较早的一个就毕达格拉斯学派(“数即万物,万物皆数”),同时是一个数学学派,数论的创立者,几何学的开创者,然后又是一个哲学学派。这种学派对世界的理解就是把整个宇宙放到数学的理性中去理解,数,number,是宇宙的本原,数与数之间的和谐比例的关系,就是宇宙的构造本身。那么这是中国思想中不会有的,不会这么想问题,因为中国人认为数数是人的整理外部事物的方式,属于人的方式。但是毕达哥拉斯学派说,(数)不仅是我们整理外部事物的方式,而且就是宇宙的本原,就是宇宙自身的构成。比方说一个数学公式,在我们中国人看来,我们也发现了勾股定理,在欧洲叫毕达哥拉斯定理,几何学的一个定理,那么在毕达格拉斯学派看来,叫a平方加b平方等于c平方是吧?这个事情,它同时是宇宙的构造自身。这种思想就把宇宙逻辑化,逻辑不仅仅是人类的思维方式,逻辑还是与宇宙自身的构造。从这里我们就看到了中西思维差异的一个原点,origin,根源处。中国人认为我们用语言表达的东西,包括我们能数数,能计算事物,能够有一些数学公式出来,它只是人类自身的行为,那所以宇宙跟人的关系的统一,不是逻辑上的统一,我们中国人是要天人合一,这个天人合一不是逻辑上一致,宇宙跟人主观认识跟客观世界之间的统一,不是从逻辑上统一,而是感性的统一。比如说阴阳五行之气,我们说整个宇宙,中国思想宇宙论,是阴阳五行之气的浩转流变,那么我们人体也是个小宇宙,也是阴阳五行之气的凝聚和流转,包括人的认识也跟阴阳五行本身有关。阴阳五行,金木水火土都是感性的东西,不是被抽象为物质、化学元素,不是原子、元素、粒子,都不是个东西。(反驳弹幕:金木水火土是聚散状态的不同类型,本质是阴和阳,这是感性的;而弹幕举例说的西方的“地水风火”是物质的。)西方人说,人与自然界统一是逻辑的统一,由于逻辑统一,他就可以把逻辑所构造的世界跟感性世界分离开来,让它自成一体,于是就有柏拉图(理念是原型,能够被感知的具体事物则是这种原型的摹本。)的理念世界,然后感性世界的真理到理念世界中去寻找吧,这种中西思维的一个差异。我们说真理不脱离感性的,他们说真理首先是逻辑的东西,然后运用来讨论感性的事。于是形成了不同的科学,一种叫西方科学,一种叫中国科学。中医就是中国科学的一种。中医诊断病人的疾病,绝不用抽象的方式和测量的方式,他就把脉吧,他基本手段四种,望闻问切这四种,每一种都是感性,都是感受。那么这就意味着西方的自然科学跟中国自然科学有一个很大的差别,就西方自然科学的基础是一系列哲学的抽象做基础,而中国自然科学呢,是中国的哲学做它基础,但它不抽象,它是感性的。而西医怎么检查你的身体的疾病的,诊断怎么诊断,测量呀,测量就以数字做基础的,就正常值范围,就定好的,比如血液的测量,检验就是测量,测量血液不同的成分,每一种成分红细胞、白细胞、血红蛋白等等,它每一种成分都规定了正常值范围,那诊断疾病就是测量,它以数学理性做前提。那么人与人的关系也是一样的。我们中国思想中并不把人抽象为一个独立的个体,中国人说你中就有我,我中就有你。比如说父亲怎么称其为父亲,当他的儿子出生的那一天,他的儿子把他作为父亲也生出来了,(老贺:把母亲也生出来了。)这是讲父子,然后讲母子也一样了。不是一回事嘛?所以它是交互性的,它先不设立两个独立的个体,然后说他们如何发生关系,而是关系产生两个个体。"}, {"instruction": "那我想在这里问一个相对比较私人的,就是情感的问题,就是我两年半以前,我的相恋三年的男朋友,就意外地离世了,然后这个对我来说是一个特别大的一个情感的冲击,然后我一直在寻找一种就是向内和向外的一个开脱的一种方式吧。然后在这个过程中也在接触心学和禅宗,然后我想问一下老师,就是我们在遇到这种情感冲击或者是巨大悲痛的时候,有没有一种就是方式或者是一种精神支撑,可以帮我们尽快地会脱离这种情感的压力这样子。", "input": "", "output": "这是人生的一种,你遇到的实际上是一种灾难,对吧?那么面对灾难这种就是无法用任何方法来解答的问题嘛,科学帮不了你,技术也没用,对吧?在这种情况下,就是靠信仰。那么中国人靠的是什么?佛教,主要是靠佛教。当然也有道家的思想,道家的思想就叫你不要难过了,就向庄子学习。那庄子老婆死了,老婆死他一开始也难过的呀。他一个好朋友叫惠施,惠施是名家的代表人物,公孙龙子、惠师,那么两个人一直要学说争论嘛,惠施听说自己的好朋友,庄子的老婆死了,他就来吊唁,来吊唁的时候一看,庄子在唱歌,他实在受不了了,他说你不哭倒也罢了,你还唱什么歌,对吧?那么庄子就跟他说了,他说一开始我也难过,后来我就进而要想一想,她本来没有的,无形的,然后慢慢聚成气,气然后有了形体啊,然后既然是这样来的,也会这样地消失嘛,这叫天道,所以他歌的是天道,这是道家的解决方法,对吧?那么儒家呢,“勿以死伤生”,就亡者是你的亲人,他死了,不能伤害你继续地生活,就是要节制,节哀这个观念是来自儒家,哀肯定是要哀,人之常情,但是要节制,因为生活本身更有意义。每个人还得继续走自己的人生道路。他说你比如说父母亲去世了,有的人痛得要死,恨不得自己跟着父母走了。儒家不虚,他说你可以不吃三天的饭,三天不吃饭,第四天你必须吃,讲清楚吧,有个《孝经》,“孝顺”的“孝”,“经典”的“经”,《孝经》里就把这个讲得很清楚。这儒家的意思是,活着的人继续走好自己的人生道路,因为他有责任在,你不是光跟这个人有亲人关系,边上还有别的亲人,对吧?所以它是儒家的解决方案,它很现实,要叫我们站在大地上,这大地就是亲情和亲情传递给我们责任,对吧?这是儒家。然后佛家呢,叫你想开,佛家有它的一套让你想得开的说法,而且也有道理,就是说人与人的关系就是缘,缘会起来,缘也会散掉,有的人不要用时间的长度来度量,对吧?有长有短,这种不重要。比如说爱情,什么叫爱情是永恒的,并不是它没完没了地继续下去,而是它哪怕真实存在的一分钟,已经是immortal being,就已经是不朽的了,而不在于时间的长度,这是佛教跟我们讲的,就是他就叫缘起性空。所以这就是用信仰来面对的,儒家的信仰、道家的信仰、佛家的信仰,都足以让我们中国人面对这种难以承受的苦难。那么基督徒他也会发生遇到这样的事情,他也会悲痛,基督教徒都有一条,就是亲人走了,最亲密的人走了是不流泪的,不哭的。一个弥留之际的人,他是基督徒,就跟他家人们说,你们不要哭,我到天国去了,不需要哭。所以西方的基督教的传统,有1000多年,很深厚,所以西方人举行葬礼的时候,他们不哭的,然后他们葬礼完成了以后,他们就坐在那里去喝茶,叫追思会,追思的时候经常说非常幽默的话,开那个亡者的玩笑,他生前曾经怎么样怎么样对吧?大家哈哈一笑,这是蛮超脱的了。附(也是反驳不相信的、质疑的、不谦虚的弹幕):基督教认为死是对人类犯罪的惩罚,是所有亚当的子孙都必须承受的,基督徒很勇敢地接受这个现实。他们认为死有三种形式,一种是身体生命的死;一种是灵里生命的死(和上帝断绝了关系);一种是将来死后审判下的永死即第二次的死。对于这三种死亡,基督教徒只经历第一种死亡——身体的死亡,他们得到了永生和复活的许诺,一旦灵魂脱离肉体的痛苦,结束了人世间的各种苦难,便得到了永生。因此在圣经里用“睡”来代替死亡,如安息主怀,像“婴儿”一样躺在主的怀抱里睡着了。“尘土归于地,灵归于赐灵的上帝”,这种豁达的生死观使他们坦然的面对生死,认为死是“息了自己的老苦”,神爱世人、复活与永生的期盼,让死亡充满期待。因此基督徒的葬礼并没有悲伤的气氛,而是在众人期盼、祝福、见证中回到天国,与神同在。基督徒知道人有灵魂,而且确信自己“重生”的灵魂已经得救。圣经说,世上人所谓的“死亡”,只不过是肉体的死亡,是灵魂离开了肉体。不灭的灵魂面临两种去向,即天堂与地狱。去天堂乃永生;去地狱则永死。永受地狱之火的煎熬,那才是真正的死亡,或谓“灵魂的死亡”,或谓“第二次死亡”。基督徒明确:“得胜的,必不受第二次死的害。”现在“自己在地上是客旅,是寄居的”,将来“是天上的国民”,“我活着就是基督,我死了就有益处。”基督徒确认:主已经替他们的灵魂预备了“天家”,那里有“永存的房屋”,那里是“更美的家乡”!基督徒将来永恒的生命,才是真正美好的生命!正因为如此,基督徒离世时总是“安详而去”的。对于基督徒来说,死亡的日子乃是荣耀的喜日,是从地上肉身的帐棚迁往属天居所的时刻,是脱离罪恶、痛苦、悲伤、失望的境地而投入光明、圣洁、福乐、自由境地的转机,是永生的通道,是向天家迈出的步伐。基督徒坚信:死亡的门槛外必有慈爱的父神一双迎接他们的大能的手!)总而言之,只有信仰能面对。"}, {"instruction": "那王老师,刚才我们提那个中西思维差异嘛,包括其实刚才我觉得讲很多我们面对感情的处理的时候,背后其实都有那个思维差异的部分在。那回到我们今天主题,说重建中国的精神家园,王老师你个人觉得说有哪些中国传统的思维方式在今天其实某种程度上被忽略了或者被低估了,但是它其实仍然有非常重要的影响和价值?", "input": "", "output": "我举个例子,武汉封城了。那武汉封城了以后怎么办?被封在里边的武汉的市民怎么办?全国各地去救援,那叫奔赴抗疫前线,各个省都有医疗队,实际上是充满危险的。当疫情最初爆发的时候,它的剧烈的程度全国人民都知道了,那么在这种情况下,医生护士有的还那么年轻,有的是为人母者,有的人是自己父母亲的儿子、女儿,他去到达抗疫前线去了,是怎样一种精神的力量?它绝不是基督教的博爱,它是中国儒家的思想,老吾老以及人之老,幼吾幼以及人之幼。所以中国的那些家庭,父母或者是丈夫、妻子说,你去救吧,救人家的老人,就如图救自己的父母,救人家的孩子如何救自己的孩子,这是儒家的精神。在短时间里面再度焕发起来。就是我回答你刚才的问题。因为这是中国人讲的人与人的关系是生命情感的关系,它不是抽象的理性的法则。中国历史上的多次灾难啊,比如说远的不说,只说近的,08年汶川大地震,这个灾难很大实际上,这个地震太强烈了。直播救援现场,那时候我天天看,看的及其揪心,那不得了的灾难。那么其中有一个感人的例子,这是报纸上公开报道的。就是一个中年的男子跟一个中年的妇女,其实彼此都不认识的,就是因为这个地震发生了以后他们都在同一个楼里面嘛,压下来了以后,结果他们没被压死,为什么呢?正好一根很粗很粗的木棒顶住了上面的那个石板,它保持了临时的平衡,然后他们就两个人都没压到,但是上面都是废墟。他们拼命叫喊,外面的人听不到的。他们有可能要坚持几十个小时如果没死的话,那是有可能就是死了。这两个人突然发现一点,实际上这个木棒虽然粗,是可以顶一顶的,一旦我顶一顶,这石板最上面就露出空间,露出空间足以让一个人蹦出去。现在谁去顶,两个人不出声音了,不说话了。这个中年妇女人忍不住跟那个中年男子说:“你让我出去......”为什么呢?一个人顶一顶,另外一个人可以蹦出去,但是他维持不长久,没那么多力气来保持这个平稳,只要他松手,原来平稳打破,就一定压死他。这中年妇女跟男子讲:“你让我出去,我一个儿子。”这个男的就问女的了:“我也一个儿子。”然后中年妇女说:“你儿子多大?”“高三。”那男的就问女的:“你儿子多大?”“幼儿园。”沉默了,又沉默了,长久的沉默。实际上这时候这个男的在激烈地思想斗争,我把她放出去我就死,但她是完全可以逃生的,只要我顶一下。沉默良久,他终于说了:“你出去吧,对孩子来说,母亲比父亲更重要,何况你的儿子还小,你抓住机会,我顶了啊,我一顶上去,赶快蹦。”他就顶了,一顶,那个妇女就从这个空间里边蹦出去了,蹦出去大概十米不到,后面一声巨响,轰然倒塌。这妇女泪流满面。我看到这报纸报道,我也流泪了。我在说什么?整个促使他放妇女出去自己死啊,全是儒家精神,儒家精神体现了。幼吾幼以及人之幼,我也爱孩子,你也爱孩子,那么比较一下吧,就这样的事。这就是我们中国人精神家园的非常重要的方面,它超出任何功利,他是中国人的道德境界。就我一直认为我们这一代做学问的人,尤其搞哲学的人,既然是中国人搞哲学,除了研究西方哲学,一定会作为中国人来读中国的哲学经典。哲学的研究不应该有专业细分,专业细分是现代大学的通病,包括西方大学也一样,哲学实际上不是专业,是一种思想的事业。那么我们作为中国人来说,除了我们去研究西方思想,因为我们不研究不行,但你作为一个中国人研究西方思想,目的在哪里?目的就是得西方思想的启发,来回过来看中国思想、中国哲学、中国文化,看得更清楚。你比方说冯友兰先生就这样。冯友兰先生写《中国哲学简史》,是写给西方人看的,所以它是用英文写的。现在我们读到的冯友兰的《中国哲学简史》是再翻译成中文的。所以他们中西都通,这是对的。像熊十力,像王国维。王国维,国学大师可以同意吧,王国维懂德国哲学,特别精通叔本华,同一个王国维诶。所以中国近代史上的大学问家,都是从中国这里先跳出去的,当然童子功已经有了,然后再跳出去学西方的东西,回过来再讨论中国的问题。"}, {"instruction": "那王老师,就是您对现在的年轻人有没有什么建议?就是我们应该先读什么,然后有一定的基础了之后再去读什么,就是有这样一个过程的推荐。", "input": "", "output": "照理本来很简单的,就是说儒道佛,它既是三个方面都要读到,比方《六祖坛经》要读过,《金刚经》也应该读过,那么顺序呢,正好是先儒后道再佛,这是蛮符合这个进入人文经典的顺序的。你比方说小孩子,十几岁的少年人,你去跟他讲,读《心经》吧,“色不异空,空不异色;色即是空,空即是色”,那么太过了,他应该有生命理想啊,他应该热爱生活啊,你就跟他讲佛教太早了。所以学佛的事情呢,当然也不能太晚,但是它总是先以儒和道做基础以后再读的,这个顺序本来是这样。但现在不对,现在我认为这个顺序要动一动,为什么?因为佛学是药,其实是用来治病的。换句话说,我们现在有病。所以现在年轻人早一点读一点佛经是对的,先把自己的病给治了。"}, {"instruction": "比方说哪些病。", "input": "", "output": "比方说在资本的社会里边,你就不断地要求上升了,你不上升等于下降,你不进步等于倒退对吧?这是一种进步强制。然后你就会跟着这个资本的时代来确立你的人生具体目标,这些目标实际上都是什么?从佛教来说叫住相,或者着相。区分高低贵贱,对吧?那么这是病,那么用佛学来治一治。《六祖坛经》先要读,那里边讲的佛理很重要,像“下下人有上上智,上上人有没意智”,就第一篇里面讲的,然后轻视他人有无量无边的罪,都是第一品里边讲的。要让小孩子明白做人的道理,就人与人的关系啊,你不要去竞比,你一定要胜出,然后把有些同学看成是远不如你的差生,这不对的。还要比家庭的富裕程度,你穿名牌,我要穿更高级的品牌。父母亲来开车来接孩子,孩子看你的父亲开什么车,我的父亲开什么车,这还了得,不是病吗?所以就先要治这个病。"}, {"instruction": "所以王老师这里讲中国人文经典,如果用刚才讲佛教的这些《坛经》,包括我们讲......其实到最后这个东西就真的从经典变成你具体的生活方式。", "input": "", "output": "对。你进入人文经典是带着自己的生命感受进去,否则你读不懂它的,因为它不是科学教科书,不是概念体系、范畴演绎,不是一大堆推理的过程,而是它是对人生真谛的一种表达。"}, {"instruction": "还有一个问题问王老师啊,其实可能今天摆在很多人、年轻人面前的最现实的一个问题就是大量的时间可能被互联网占用了,就是他甚至于还没有时间直接去打开文本,这是一个部分。第二个大量的比如说交往,包括想象,像最近的元宇宙等等,他大量的很多的他的感受其实是进入虚拟空间的感受,某种程度上他对虚拟空间的好奇和(虚拟空间)对于他的占有超过了现实当中具体的感受对他的占有,那这样的情况下再来阅读中国人文经典的话......你在大学教书的时候,你会感觉到这样的学生的变化吗?", "input": "", "output": "大学里边教书的话呢,我现在发现了,就是说90后,后来我教了一点00后的人啊,那么他们越来越就是说从技术和逻辑的角度来听我们的哲学课,这是一个问题。90后,他习惯于网上生活,然后包括读书,学业上没搞清楚的东西都是网上查的。80后听我的哲学课的时候,这个讲台上排了好几排录音笔。后来我的讲稿都没地方放了。后来我就这个课完结束了以后呢,他们就来找自己的录音笔了,我说你们都来听了,还要录音干嘛?他说,不,王老师,我们回去到寝室里,晚上大家灯一熄,不是卧谈还睡不着吗?放一段录音,放一点你这个课程录音大家讨论啊,如果没有什么,讨论不出答案来的话,记下来,下个礼拜问王老师去,这就80后的学习。有一点我是相信的,他们不可能靠所谓元宇宙说来解决他在现实生活上面对的挑战,不可能的。所以当他们在现实生活中遇到挑战和困境的时候,他们会要求人文学习的,会要进入人文经典的,这是毫无疑问的,只不过他们晚了一点,需要经历一段时间。生活是最大的教育家,现实生活。当然是以我们的生活阅历做基础的,生活的阅历让我们遇到了许多挑战,人生的困境也罢,那么这些都会是年轻的一代不再依赖互联网和信息技术,因为这些问题是人生真实的问题,它是互联网和信息技术包括所谓元宇宙的设想无法来解决的,我们不可能是在元宇宙当中能找到一个精神避难所,这是不可能的。所以我们还得,我们必将会回归中国的人文经典,来重建我们民族的精神家园,这是完全可以期待的未来。这样恐怕就是我们今天,这里是第一堂的讨论班的这个意义所在。哲学有好多分支,有道德哲学、科技哲学、逻辑哲学,甚至还可以有数学哲学,其中有一个非常基本的领域就是艺术,所以今天的话题其实是艺术哲学的话题。当我们把今天的讨论定为“人为什么需要艺术”这样一个问题的时候,我们就进入了关于艺术的哲学思考。我们现在还是要看第一个话题,什么是艺术?这个问题现在叫“什么是艺术”。我们如果把艺术作这个问题的主语呢,艺术是什么?我们现在问一个问题,这两个问题是同一个问题吗?"}, {"instruction": "不是。", "input": "", "output": "不是一个问题,这两个问题差别是什么?"}, {"instruction": "艺术是什么?当我们这么问的时候,我已经假设有这么一个课题叫做艺术它存在,然后我再去描绘它,说它是什么。我觉得什么是艺术,我们就是在尝试证明一件事情,然后这件事情跟艺术是等量的,然后我们有一个共同的认知,大家是对齐的那种状态。", "input": "", "output": "我们就说这样一件事吧。什么是艺术?比如说我们看到这幅画,我们说它是一部绘画作品,我就把它列为艺术,那个书法作品,我们认为它不是艺术作品,我们认为杜尚(杜尚对现成品的挪用与“不断拒绝传统、拒绝艺术”的观点直接引发了概念艺术的出现。)那个叫Fountain,倒挂的小便器是不是艺术?我们每天都在就具体的东西问,什么是艺术,然后发生分歧了。假如你认为杜尚这部作品是艺术,我认为不是,这种争论该怎么解决呢,于是问题转变了,我们先要关于“艺术是什么”达成共识,于是问题自然转化为先搞清楚艺术是什么。现在艺术和非艺术之间的传统界限已经被打破了,尤其是当代艺术实践,比方说一次钢琴演奏会。《4分33秒》,约翰·凯奇。这是次音乐会吗?这是个音乐作品吗?当它也被称为作品的时候,什么是艺术?这是音乐作品吗?这个问题终于变成争论到水火不能相融的地步了。它一定挑出一个什么问题啊?音乐是什么?当我们始终在讨论什么是艺术的时候,是我们日常的艺术批评。当艺术批评终于尖锐到对两种不同的立场和不同的评价水火不相容的时候,艺术哲学就一定到来了。所以当你说我们问艺术是什么的时候,我们只是在讨论一个抽象的概念,我同意,并不是在讨论一部具体的艺术作品,但是为什么要讨论这样一个抽象概念呢?所以对一部作品究竟是不是艺术?"}, {"instruction": "你没有这个概念,你就没法讨论那个问题。", "input": "", "output": "当以前我们似乎不言而喻地、共同地对艺术是什么有一些普遍的共识的时候,我们艺术批评展开得比较顺畅,对吧?现在的问题是什么?传统的绘画,我们关于绘画是什么其实有共识的,而今天倘若我们把一个画布铺在地上,在一个猫的身上涂了各种颜料,让它在画布上滚一圈,然后拎起来宣布这是作品第几号,我们终于要问绘画是什么?(《喜剧演员》、《兔子》、《黑板》......)绘画与非绘画的边界都被突破了,于是哲学问题来了,艺术家认为我正在创作艺术作品,他以他这一次的艺术创作的实践在实践地回答什么是艺术。黑子Eagle(作品:《活鱼》、《钱》):这也是这段时间在我身上一直在发生的,网友们现在跟着称我为艺术家,我自己也有点懵,我说怎么我就艺术家。"}, {"instruction": "我特别想问当时为啥要干这件事呢?", "input": "", "output": "网友对你的误会,你应该感到高兴,为什么?他们有他们的一个境界了,就是我们对传统的被公认为了不起的艺术作品(开始怀疑),那个传统的审美价值观念向来高高在上。当代艺术的特征是什么?第一,对传统的艺术所表达的某种理想,它终于跟现实生活是脱节的,因此它本质上是虚假的。网民们明白这一点,所以他们是误会你,你说是误会,他们就必须把你捧成这个样子,他们必须这么做,我不要去看那些伟大的书法家的书法作品,我们看到什么,来自大地的东西,他带着他的全部的朴素和真诚来了,这是颠覆性的。当代艺术的一个最基本的特征之一,就是对传统审美价值的反叛。我们不管网民是何种人物,可以包括那些曾经在传统的书法或者绘画艺术当中很有造诣的人,他也在反叛,他看到你这个东西,好,你不用担心在上面的艺术该怎么办,哪有这种担心的必要,就是要颠覆它们。这是一个很大的话题,就当代艺术你怎么看待它?怎么认识的?艺术本质上是一种斗争,struggle,时代赋予的斗争。你不要用传统的审美价值的理想来掩盖生活的真相,所以这件事做得很好。"}, {"instruction": "这个艺术家是不是已经开始反叛了,这个艺术家率先开始反叛了。", "input": "", "output": "这里我就想起比如说中国文学,这个文学的作品的体裁,不是题材,体裁。我们知道曾经有这样的就是骈体文,这骈体文最初创作的时候,它是很真诚的,他绝不是装逼。你比方说王勃的《滕王阁序》是骈体文,他装什么逼呢?对吧?然后在骈体文的展开过程当中,它开始走向什么?要冠冕堂皇,要符合文辞,这个辞藻的华丽,韵律的美,失去了它本真的东西,于是骈体文最终走向衰落,你说的那位艺术家说艺术就是装逼是吧?整个西方或者说中国传统的古典艺术是装逼的,不是,而是它没落了。今天的艺术家就跟着那个所谓伟大的作品后面装逼,就是你刚才说的,我不想把那个作品弄得冠冕堂皇,弄得冠冕堂皇就是装逼诶。就像您刚才的例子,很说明问题,你就把它剪裁下来了,是吧?然后人们说这是艺术,然后说你是艺术家,然后你心里还发慌。所以在你发慌的时候,你已经开始问艺术是什么了,然后说你害怕触犯了真正的、伟大的、了不起的艺术是吧?然后把我捧起来算什么,对吧?这里已经开始思考艺术哲学问题了,你在不自觉中思考艺术哲学问题,你不敢接受artist这个封号给你,因为你关于art,what is art,你还在探讨中呢。"}, {"instruction": "我甚至觉得没有啥艺术(说他自己的作品),就是我自己个人感觉。就是我想说话的时候我说话,我想画画的时候我画出来,我想唱歌的时候唱出来,它只是一种承载我表达内容的一个媒介,然后可能表达得好了,人就觉得感到了一些共情或者是什么一些感受吧。", "input": "", "output": "因为这一点我我敢称你艺术家了,艺术家并不能想好我正在创作艺术,你就在表现、表达,这是真诚的了。有的人说我要成为艺术家,然后就开始说怎样我才能成为艺术家,好像艺术变成某种途径和手段,然后我在这条路上走了,我就终于成了艺术家了。对你来说,艺术不是某种通向所谓艺术家头衔的这样一条路,不是,而是你需要,你需要表现,需要表达,而且表达了你一个愿望,希望接受你的作品的人开心、欢乐、对生活有热爱。对吧,很好。"}, {"instruction": "完了,我这不真成艺术家了。", "input": "", "output": "至少是honest。真正的艺术家的第一个品质就是诚实、真诚。当毕加索他的早期绘画实际上是在古典主义绘画的范围里边,造诣很深,他为什么把它抛弃了?我就接着这个讨论一下,因为他认为他在古典主义的范畴里边创作绘画作品已经dishonest,不真诚,因为他深刻地体验时代和体验当代文明,领会到当代文明的无家可归状况,或者用马克思的话叫异化,然后你还用传统的、古典主义的、美的、浪漫的那些绘画作品给予我们以美的享受的时候,他认为这是我在欺骗你们,生活不是这样,已经不是这样,这就是honest。于是他走进第二个后面的阶段,直到立体主义,我们以为关于艺术的哲学思考变成理论家的事情、艺术哲学家们的事情,不,它就是当下的艺术实践的现实。于是我们重新追问艺术是什么,艺术是来让我们给一种严酷的现实生活披上一层这个含情脉脉的面纱。假如我们是这样需要的,曾经有过了,它的温暖和对我们心灵的抚慰,但是我们今天的痛苦、当代文明的异化和无家可归状况的痛苦是无法被抚慰的,任何抚慰的做法都是虚伪,都是虚假。所以这个事例是非常有意思的,就当我们问art、what is art或者说艺术是什么的时候,它是一个时代的问题。时代的话题。这个话题不可能用某种哲学的定义给艺术下一个一劳永逸的定义,这是绝不可能的。为什么?"}, {"instruction": "语境在变化?", "input": "", "output": "不,真理是个过程,truth is a process,没有现成的真理等着我们所有的人终于发现了它。整个人类的历史文明展开的进程就是个真理展开的过程,它是process。我在复旦大学开了门课《艺术哲学》,那么听我这门《艺术哲学》的本科生就会课间或者休息的时候或者课后围着我转了,问我一些问题。所问的问题90%都是关于当代艺术,你看到这部艺术作品,你会觉得它美吗,愉悦吗,毫无愉悦,有时候让我看得令人作呕啊,但他是作品,影响极大。然后年轻的学生们就要问我为什么。"}, {"instruction": "是,也有过同样的问题。", "input": "", "output": "所以今天我们的话题很快就切入了当代艺术现象的思考了,当代艺术学院的思考本质上是哲学层面的了。实际上每一个当代艺术家同时都在哲学中,不管他自觉与否,他是用他的作品来立法的、自我作古的。当代艺术家,他的作品本身是某种哲学的宣言。艺术的创作和艺术接受是不可分割的事。假如一个人说我艺术创作只是为了我自己的话,他就没有期待过任何一个接受者,是我内心读白,而且独白给自己听的,艺术作品却是给别人的,那么不是创作它的人来接受这部作品,一定发生艺术批评,这是不可避免的。因为你没办法,人家就批评了你,谁让你把这东西让大家看见了,是吗?所以批评自然而来,批评来就有争论,争论到后来就变成哲学。我举个例子给你听,西方近代音乐史上有一位了不起的作曲家,但当时是备受批评的,叫布鲁克纳。布鲁克纳创作交响曲,第一、第二、第三出来,每创作一部就被骂,骂他骂的最凶的是汉斯力克,当时欧洲英语乐坛上最权威的批评家,他来一部骂一部。后来第四交响曲首演成功啊,一个贵族跟他讲这部作品了不起,我太喜欢了,然后跟布鲁克纳说,你有什么要求尽管向我提,我会满足你的。布鲁克纳跟他说,我只有一个要求,你能不能让汉斯力克停下来对我的攻击。你看布鲁克纳不会不在乎,非常在乎。"}, {"instruction": "这个我跟你说,一定要调整心态,我做科普到现在就整天被人骂,已经习惯了。", "input": "", "output": "但是有时候,这个心态无法调整,为什么无法调整?要看在什么一个时间段,落到交响曲正在展开它最重要的发展时期。中期浪漫主义起来的时候,实际上大家是有共识的,勃拉姆斯肯定算中期浪漫主义,那么布鲁克纳也在其中,在其中大家有一个共识的,有了这个共识以后呢,我创作的作品就被骂了,你创的作品就(被)奉为最伟大的,勃拉姆斯到处被吹、追捧的,牛的不得了,就罗曼·罗兰不喜欢他,老骂他。你看这种情况下,就是说在这个阶段的时候,你是加入了一种艺术的事业,当时就是中期浪漫主义的音乐的事业,他对欧洲音乐思想的发展是非常重要的阶段。当然勃拉姆斯最后没找到出路,第四交响学就是彻底的悲剧了。这是个事业,你轻松不了。你现在没加入事业,你知道吧?你就跟批评者说我没搞艺术,你玩你的,但是布鲁克纳“我在做音乐”,所以这就没办法躲了。"}, {"instruction": "老师,我就属于刚才在那种互动当中把哲学消灭掉而不是产生的那种例子,因为我是研究生学的哲学,然后我在学校的时候就决定弄自媒体的视频,然后在网上去科普哲学。其实我们三个都是属于在某些网站上有一些自己的账号,那么在这种层面上我们发的每一个视频其实对自己来说算一个艺术品,我们在表达自己的感受,然后期待公众对自己的互动。但这时候问题又来了,我做的内容是哲学科普,但是当我做哲学科普的时候是没有人看的,当我做其他东西科普的是有人看的,那在那个自然选择当中哲学科普就为了其他科普而让路,所以最后导致我是被观众和接受者的建议给说服了,转型就发生在我自己身上,然后这个时候哲学反而被消灭了。", "input": "", "output": "哲学的科普这里边有一个误区,对吧?就是你要科普的东西已经是确定了的东西,让大家懂,但哲学不是知识。"}, {"instruction": "所以我只能讲哲学史。", "input": "", "output": "它不是个专业,哲学其实根本不是专业。"}, {"instruction": "那是啥啊?我不太懂哲学。", "input": "", "output": "其实我们每个人都在哲学中的,只是大多数人不自觉,你对世界有一种基本看法的,"}, {"instruction": "那它不是有挺多这些哲学家呀,还有像门派之类的,我不知道怎么说。流派,不是门派。", "input": "", "output": "哲学就是怀疑、批判、追问。大家习以为常的出发点和前提恰好是哲学讨论的东西,你究竟凭什么站住脚了。所以它不是等待我们去传授的知识,这什么意思?我们可以说普及哲学,不是普及哲学的知识,而是让一种思维和批判的能力、思考的能力通过你大众学者去活跃起来。"}, {"instruction": "那这个节目的形式应该是两个人互相的骂和批判了。", "input": "", "output": "对,骂得越厉害越好。"}, {"instruction": "我觉得这里其实有一个问题,就是比方说咱俩在一起聊这个事儿,我表达了我的生活哲学,是一个观点,你可能你不赞同,然后别人看到了,咱俩觉得好像把这个思考能力普及出去了,但是现在有大部分人都是把你的观点拿来,然后我直接使,他甚至都不知道这东西对不对,他就觉得这人说的有点意思,然后拿出来用,他也不思考,然后弄的到处搜集,然后往那一坐,像个人似的,对啥他都说,就是这事会不会走偏了呀。(看哲学书、视频,最重要的是看论证。)", "input": "", "output": "这是现实中的辩论的退化形式。"}, {"instruction": "比谁语速快,气势。", "input": "", "output": "它不再是真理的探讨。"}, {"instruction": "老实说这个辩论赛我是看过的,我就一个感觉就是他们在讲什么。我真的有认真在听他们的,想去理解他们的逻辑,但是经常会发现我真的不知道他们在讲什么这种感觉。", "input": "", "output": "这就是辩论退化的形式。"}, {"instruction": "有没有一点像智者,就是那个苏格拉底之前的诡辩术,就是说当时的这个年轻人付钱给一个智者,智者就可以告诉你,我在法庭上如何可以狡辩。最早的知识付费就是这个意思。", "input": "", "output": "但是真正的有意义的辩论永载史册。比方说庄子,庄子跟惠施,鱼游得多欢乐,是吧?惠施就问庄子,“子非鱼,安知鱼之乐?”庄子怎么来啊?“子非我,安知我不知鱼之乐?”来了吧,表面上好像是逻辑上的诡辩,不。庄子的意思是什么?我们当然跟自然物有一种内在的情感同关,感通啊,你不懂的,弄来弄去就是逻辑。"}, {"instruction": "现在您讲完了之后,我们能明白他是思维上的一种展示,但是现在来说就会很多人把它裁下来,就拿着“子非鱼”啥的就开始,他可能没有领会到。最普遍的是那个啥,“存在即合理”这句话的一个滥用。(王德峰:对。)", "input": "", "output": "我自从就研究生毕业以后留校任教,就做哲学教师了。我一直追问我自己一个问题,你在哲学的课堂上你给学生什么东西?你给他哲学史的知识knowledge,结果你让我们的学生学了4年以后,就是关于中国哲学史、西方哲学史的知识积累了一大堆,然后说一种批判,后面的学派批判前面的学派,后面的思想家批判前面的思想家,你不要变成4年本科哲学学完以后,养成了一种在哲学史的知识的基础上无所不能的批判能力,你就根本没懂过哲学。"}, {"instruction": "这是个好办法。从问题出发。我有一个就是困扰了我很久的一个问题,就提到这儿了,我顺便就说了,有很多的,不能说很多吧,反正有一部分画画的,姑且叫画家吧,画家他在探索讨论绘画本身,我一直挺反对这个,我觉得没有意义,可能是形式、材料,有的是画圈画画,有的打叉画画,有的画各种各样的图形,就他在讨论绘画本身是怎么回事。", "input": "", "output": "这里边呢,比如说美就在于形式,不是还有唯美主义,对吧?对形式美看成是艺术的生命,是吧?那么这是一种立场。"}, {"instruction": "加入时间的维度,他天然都留不下来。", "input": "", "output": "历史证明了这些作品是足够伟大的。这种足够伟大不是来自于它,比如说音乐来自于它动听和优美,为什么呢?因为这些伟大作品唤起的是什么?唤起的是在聆听他的人所面对的时代挑战,它给予他们的启发和精神上的鼓舞的力量。"}, {"instruction": "节奏非常不一样。)然后慢慢听进去,他才真正地深入了这个作品的精髓,卡拉扬没做到,为什么?因为他们......这是真理的事业,我再度要讲真理的事业。那就提到这个事了,就是参与筹划未来是不是要在苦难当中才有这样的诉求?(王德峰:对的。)好像是艺术家更容易抑郁一些。对,就感觉历史上所谓那些被大众认知比较广泛的作品都来源于苦难,比如刚开始宗教,就中世纪时期《耶稣受难像》开始,到《格尔尼卡》这种比较大的作品,到一些像中国的一些诗歌,辛弃疾那些。贝多芬耳聋,勃拉姆斯爱而不得,以及梵高穷困潦倒,对吧?我们听到的很多就是这种故事。我自己也是失恋了,然后写了个诗。都是痛苦的。对,现在你看历史上这个像欧美的五六十年代的时候,我印象中那个时候的古典音乐是非常的旺盛的,可能就跟二战结束这种抚慰战争创伤其实很有关系,这个感觉。那还是想问到刚才咱谈的那个当代艺术的,我也愿意用这种词汇去形容它,可能有一些无病呻吟。不拧就不是艺术家。就把自己弄得很拧巴,陷入一种痛苦之中,明明吃得饱、穿得暖,对吧?这个时候是不是不太容易出现像过去的那些经典,而更多的是冠冕堂皇的东西。", "input": "", "output": "当然我们不能转化为对一个当下社会的讨论。(黑子Eagle:那倒是。)因为东北现在经济发展不大好是吧?或者衰落。大量的人流失,包括人才对吧?读了大学就不回去了,对吧?这我知道,现在......"}, {"instruction": "也有新的“苦难”。", "input": "", "output": "这个是真的。所以呢,至于艺术家拧巴,那么艺术家是很敏感的,真正的艺术家都是敏感的,极其敏感,就他对于这个他所处的时代和社会生活里边包含的问题,他比一般人来说,(要)率先地感受到。这是艺术家的成为艺术家的一个特征之一,相当敏感,一部作品是不是无病呻吟,是看得出来的,艺术家本人是敏感而真诚的。"}, {"instruction": "哭就完事了。)绝对是编出来的,骗取你的眼泪,哭完以后不知道为什么哭的,一个善良的普通人突然得了癌症是吧?这种才叫无病呻吟。他并没有深刻地领会这个生活当中最根本的问题是哪里,对吧?他也不能用理论表达,他不会写一篇哲学文章来反思这个时代,他就用他的作品。胡说八道。)简直是胡说八道。(黑子Eagle:胡说八道,岂有此理。)但是《红楼梦》怎么说的?《红楼梦》不是十二支曲嘛?前面一个引子,后面一个尾声,每一曲都讲一个人物,它的悲剧性的结局的隐藏在里面,一共金陵十二钗,最重要的人物嘛,对吧?尾声怎么唱的?“看破了的悬崖撒手,痴迷的枉送了性命。正好是食尽鸟投林,只留下白茫茫大地一片真干净。”清朝中叶这部小说预言了清朝末年无可挽回的衰落,然后这时候洋人打上门来了,你无以招架的,也在太平盛世写的。因为他敏感,真正的艺术是极其敏感。老师你看我们看了这么多伟大的艺术家,看了这么多伟大的痛苦,我觉得还是回到我们这个老百姓想的东西。现在我们这个节目就是为了让大家回头再多关注点艺术。对于他们这种反馈,艺术能给他们带来一些怎么样的反馈呢,按照您的观点。", "input": "", "output": "就是一切艺术,其中包括文学,它并不是把某种我们本来没有的东西,比如说知识,灌输给我们。比方说牛顿物理学、牛顿三大运动定律是西方科学发展的历史进程带来的,不是个人本来有的。科学知识是从外部给予我们,然后我们再参与其中,再推进科学进步是可以的。艺术作品给予我们的是我们本来有的,启迪了你对生活的真理的领会。比方说我举个例子,我有一年讲哲学的课,课间休息,一个同学上来跟我谈话、聊天,他跟我讲,王老师我最近很孤独,然后我就问他你是不是恋爱了?他说我没恋爱。我说你没恋爱,没恋爱的人知道什么叫孤独吗?不可能懂的,你这个叫孤单寂寞,哪里叫孤独啊?孤单寂寞,你小时候就可能有过,比如说你父母双职工可以吧?然后他们上班把你关在房间里,那么长的时间你一个人,那叫孤单寂寞,但你从未孤独过,没恋爱过的人不懂孤独的。他听了非常惊讶。后来他就说王老师那么是不是我现在就去恋爱一下,我说恋爱不是你想恋爱就能恋爱的。(黑子Eagle:完了太扎心了。)要按照佛教的说法还要有缘呢,对吧?那他说我怎么办?我说这样,你去读《红楼梦》啊,你去读一下《红楼梦》再跟我讨论什么叫孤独,后来他真去读了。他大概学期快结束的时候跟我讲话,他说王老师你叫我去读《红楼梦》,真有道理。我现在懂了什么叫孤独,我跟你说说看,他说荣国府大观园里边两个人孤独,一个黛玉、一个宝玉,但他们绝不孤单,绝不寂寞,上有老太太、太太关怀着的;边上一大堆姊妹,喝酒吟诗,成立海棠诗社,那叫热闹非凡;底下一大堆丫鬟天天服侍着,哪里孤单寂寞过?他们既不孤单也不寂寞,但他们孤独。然后他继续说了,宝玉怎么孤独的,他心里想我对你黛玉的那份真情厚意,连你边上的丫鬟紫鹃都明明白白,我每一次想要说你就很严厉地拒绝,还生气对吧?这个宝玉当然孤独。好,其实黛玉也一样,黛玉心里想你宝玉好像见一个姐妹、姊妹都很喜欢,我跟你究竟是什么?湘云你也喜欢,宝钗也不错,后来又来了个宝琴,也是又有才华又大美人,那么我黛玉跟你什么关系?我们那份情感究竟是什么?你怎么好像不在意,其实你身边的袭人都比你宝玉明白我黛玉对宝玉的那份情,你好像也不在意,这黛玉孤独了。两个人实际上在恋爱。在恋爱的这个阶段是最苦恼的,每每顿生孤独感,边上人待你再好没用的,有人向宝玉表示爱意,你越是跟宝玉表示爱意,宝玉越是孤独,为什么?他需要的是黛玉向他表示爱意。这个本科生、这个同学就跟我讲了这一段,我说你现在真懂了。这就是伟大的文学作品跟我们的关系,人生方方面面、人生的真谛不是知识从外部灌输给你,你就在生活中,但是你不知道,你有时还不知道自己痛苦的根源或者幸福的根源。你不知道,读文学你就知道了,进入艺术作品你就知道了,胜过多少理论文章,你说理论文章把孤独讲讲清楚蛮难的,对吧?艺术帮助你改变体验世界、对世界的体验,你原来在这个框架里的,不是因为精神上的压抑的疏泄问题,心理学专门搞这个事情的对吧?艺术作品是打开一个new vision, spiritual vision,就精神上的世界。(黑子Eagle:就让人不需要出口,是吧?)这话说得好。(黑子Eagle:不关在那个屋里了,你还要啥出口?)"}, {"instruction": "不是给你解决问题,就是让这个问题不存在就完了。今天非常高兴我们从艺术这个话题切入,其实我们对于(艺术)哲学这个主题,我是觉得还是进行了一些比较深刻的一些探讨,我觉得我们也收获了很多可以带着走的东西。艺术这两个字好像变轻了一点,我觉得在心里边。那我们王德峰哲学研修班今天就到这,还是那句话,有什么问题可以留言,欢迎讨论。", "input": "", "output": "在20世纪西方哲学界非常重要的一个现象就是每一种新的学派形成之后,不久就出现了跟马克思学说的嫁接,产生一种马克思主义。所以对马克思在说什么,我们来讨论的时候,我们首先要把种种马克思主义先放下来,怎么放下来呢?我们复旦大学哲学学院马克思主义哲学学科点上,一个非常重要的特色是什么?读原著,从原著出发,把以前对马克思的各种理解先悬置起来,这是想要真切地了解他在说什么的第一个关键点。"}, {"instruction": "哎,刚才王老师讲说马克思是在他之前的西方哲学学说的到他那里有一个很重大的一个成果。(王德峰:嗯对。)那像所谓的这个成果,如果王老师来举例子的话,就是它相对于之前他的西方的哲学思想,他这个具体的成果的表现是什么?他和黑格尔比也好,和康德比也好,他们之间的核心的这个发展是什么?", "input": "", "output": "这个成果呢,很明确地说他发动了一场哲学革命,这场哲学革命是对西方从古希腊一直到黑格尔一个贯穿其中的主线(古希腊罗马哲学:苏格拉底、柏拉图、亚里士多德→德意志古典哲学:康德、黑格尔、费尔巴哈),叫柏拉图主义,这个柏拉图主义传统被颠覆了。第二,这个哲学革命产生出怎样的积极的新哲学呢,历史唯物主义,历史唯物主义的哲学又产生了怎样的科学上的结果呢?资本论学说。是吧?两位马克思主义哲学专业的博士研究生(张润坤、关山彤)。"}, {"instruction": "其实我们经常会碰到一个问题,就是比如我的朋友会说,你是研究马克思主义哲学的博士生,那我现在很想了解马克思主义哲学,我想知道他在说什么,你一遍又一遍地跟我说你们的教育是读原著,那我也想读原著的时候我要去读什么,这个时候我往往会犯难,这是因为我们知道马克思的原著是很多的,我们自己学的时候也学了很多的,所以到底从哪一本进入?我想请教一下王老师的建议。", "input": "", "output": "这是很好的,有很多人都有一套《资本论》,真正从头看到底的人极少,看了几页后来就不行了,而不行的原因呢,马克思本人也知道,他是写给德国工人阶级看的,也是写给所有欧洲工人阶级看的,他不是跟经济学家在经济学界进行学术论证、争论,这不是他的目的,他是尽量想要做到通俗易懂,但这个学科的性质是一门新的学科,他创立了一种新的科学,他要做到通俗易懂,但是要让他的同时代人包括当代读者要进入一种新的科学,很难很难。所以一方面希望通俗易懂为工人阶级所掌握,一方面几乎是连学者都很难进去,就形成了这样一个很有点讽刺性的或者悖论性的局面,对吧?所以我们能否懂他,才知道他在说什么,这件事情你刚才问了一个好的问题,就是我们的路径是什么?是直接端起《资本论》来读呢,还是要有准备?我的回答是明确的,要有准备。因为你要进入他所创立的第一门历史科学,就政治经济学批判,对吧?这是门科学,一切科学都有哲学的基础和前提。那么历史科学的这种类型的科学,它的哲学基础和前提就是历史唯物主义,而历史唯物主义是马克思的所开启的新哲学,那么历史唯物主义来自哪里呢?来自哲学革命。那么哲学革命的发生地在哪里呢?《1844年经济学哲学手稿》简称《巴黎手稿》,那么按照这个逻辑来讲呢,首先应当读的就是《1840年经济学哲学手稿》,那么为什么要发动这场革命呢?因为欧洲的历史,近代以来的历史,它的整个社会的巨大的变迁,都说明了理性不能解决问题。我们通过纯粹理性构造出一个比如说一套自然科学,然后来理解、来把握世界,把握世界以后然后来处理这个世界,还有一个用纯粹理性构造出一个理想的人类社会制度。最早做的就是柏拉图,他不写了《理想国》吗?那么这一条路是西方思想的基本特征,一直走到黑格尔,最后完成。那么怎么看待人类未来的历史呢?黑格尔认为首先历史就是自由意识的不断地觉醒的过程,而自由意识的觉醒是理性发挥作用,所以理性引导人类进步,这是黑格尔的原则。(“历史在某种程度上就是人类由必然向自由发展的过程。而我们只有揭示这个过程发生的必然性,才能更深刻地认识人类实现‘自由’的艰难,认识人类为实现这一目的所作努力的合理性,从而更深刻地认识时代的本质。”)所以黑格尔就为欧洲人描绘了一个历史无限进步的一种途径,但是现实正好是为他的这种哲学画了幅讽刺画:第一次世界大战、第二次世界大战。在这之前已经发现人类社会世界的基础是非理性的东西。首先马克思发现了,后来尼采也发现,都用了一个非常重要的观念,叫权力,用英语来说叫power。然后马克思的历史唯物主义是揭示社会权力的,就人与人的关系叫社会关系,这种人与人的社会关系的最基础性的社会关系都是非理性的,这种社会关系可以表达为social power,以social power这个概念为核心地建构了一个历史唯物主义的理论大厦。请注意啊,social power是非理性的人与人的关系,为什么说它非理性呢?它是这样一种人与人的关系,就是一部分人支配另一部分人,一部分人统治另一部分人,一部分人奴役另一部分人。迄今为止,人类的物质生活的领域的关系都是这种关系,物质生活关系是整个社会世界的基础性的关系,而这关系是非理性的。马克思发现了这一点,就他要发动哲学革命,马克思曾经是一个非常坚定的黑格主义者,那么为什么要发动哲学革命?就是因为黑格哲学整个哲学体系,它作为一个理性主义的体系,它无法说明整个社会世界基础的非理性的特点,用国家理性来克服物质利益的冲突,世界就太平了,资本主义可以顺畅发展。这就是黑格尔的哲学,后来马克思发现这是不可能的。马克思在博士论文被通过,得了博士学位之后,本来他要到波恩大学去任教的,布鲁诺·鲍威尔就在波恩大学哲学系不断召唤马克思快点来,“你把这个博士论文的写作赶快结束,去结束那些无聊的考试,然后到我这里来加入战斗。”结果他博士论文在耶拿大学通过了,耶拿大学在马克思缺席的情况下,全票通过授予马克思博士学位。这时马克思正想到波恩大学任教,这是他的愿望、理想,在大学里面做学问,结果布鲁诺·鲍威尔已经失去了任何说话的权力(因发表《同观福音作者的福音史批判》而遭解雇),没有任何教授的职位等着马克思,然后马克思只能投入社会生活,到科隆,那么科隆那里有家报纸,《莱茵报》,他到《莱茵报》做编辑,后来做主编。那么在《莱茵报》做记者、做编辑、做主编的时候,他就参与了一场社会斗争,就是跟莱茵省议会的辩论,那么针对了许多直接都是物质利益问题。《林木盗窃法》,摩塞尔地区的农民对周围的自然资源本来享有历史形成的权力,现在莱茵省议会通过了一系列法案,其中有一个法案是一个贫苦的农民如果在树林里边捡拾枯枝败叶都属于林木盗窃,那马克思就站在摩塞尔地区农民的立场上跟莱茵省议会辩论,这场辩论持续了蛮长的时间。马克思发表了一系列的文章,其中有一篇文章马克思这么写的,“利益是最讲究设际的,这个世界上没有什么比消灭自己的敌人更实际的事情。”这句话很厉害,就物质利益是最讲究实际,物质利益的需要会导致我们必须消灭敌人,而不是协商解决,这说明在物质生活关系的理论,这些关系本质上都是非理性的。"}, {"instruction": "比如说现在我跟我的老板之间有利益的这种。你们这是暴力关系。那他当时的理论现在还适用吗?", "input": "", "output": "哎,这个问题问得好,我们现在必须承认欧洲资本主义各个国家,资本主义国家,因为他们经历过几百年的资本主义发展,在资本主义发展的过程当中,从来不是一路坦途,而是充满斗争。资本主义这个大厦风雨飘摇的时候,工人运动风起云涌地起来,19世纪这工人运动已经风起云涌了,然后迎来20世纪,人们带着对社会主义的理想跨入20世纪,因为什么?先做成的无产阶级革命就是苏联,在俄国10月25日(俄国10月革命),在这之前不得了,工人运动把资本主义的这个阶段也记录到血与火的历史中去了,所以劳资关系是暴力的,那么现在看起来呢,好像非暴力了,在某种意义上我们说它非暴力是可以的,就是把劳资冲突纳入法律的轨道。现在资本主义经过了它的历史上的阵痛、痛苦之后,终于找到了一条路,就是工人阶级的组织工会具有在宪法中具有合法的地位,有它一个政治上的一员的地位,它可以跟资方、跟企业家、跟资产阶级在法律的轨道里边斗争,这个斗争的主要方式是罢工,罢工表面上是非暴力,其实就是暴力状态,我们不要把暴力这个词狭隘化,流血冲突,街头殴斗,不是啊,我们有时候在长久地处于暴力状态之中。如果社会生活当中一部分成员恒久地丧失社会共同的福利、共同的利益,他没有进入社会共同利益的途径的话,他就是处于暴力状态中,他成为暴力状态的对象。资本主义生产关系它本身就非理性的,是对抗性的。马克思说得非常清楚,原始共产主义我们不说,以后的说奴隶制到封建制,在欧洲历史是怎么排过来的,到了资本主义的生产关系,都是对抗性的生产关系,对抗就意味着:第一,它是非理性的;第二,如果它们的冲突和对抗要解决,一定是通过暴力,但暴力并不等于简单的打仗,我宣布罢工,就停止工作了,停止工作就让资本家计算我的损失。现在欧洲这个工会一般是行业工会,比如航空港他们有个工会,航空港的工人他们都是工会成员,如果他们觉得在目前当下这个国家的经济状况当中我们恒久地被剥夺了我们的物质利益,他们就会发动罢工,发动罢工的方式是完全合乎法律的轨道的。我罢工三天,让你资本家算一算你将损失多少利润或者支付多大的成本,你算了,吃得消,你不让步,不走到谈判桌边上来算,工会宣布延长到一周,这就是对抗,这就是暴力状态,不是讲道理的。这暴力对抗到一定程度以后,终于当延长到一周,资本家一算,不行了,我要破产了,再这样下去,马上说走到谈判桌边上了,开始让步吧。所以这个事情呢,马克思对它的判断和对它的揭示在今天怎么可能过时呢?"}, {"instruction": "是的,那王老师,如果从这个问题的角度出发的话,其实我就有一个新的问题,就是说,在马克思主义里面强调的这个两个必然,一个资本主义的一个必然的灭亡和工人阶级的一个必然的胜利。但是就如同你刚才所说的,经过了几百年的这个资本主义的发展,其实现在很多的西方国家,尤其是曾经实现过帝国主义的国家,它经历了长期的一个积累,它已经在某种意义上、某种程度上实现了就是所谓的伯恩斯坦那一派人所强调的或者所提出的一个渐进式的改革,然后他已经在维持资本主义或者资产阶级统治的这样一个基础之上,它尽量地、极大地维护了保障了工人的权益。在这样的一个环境下,那下一步工人阶级他们的运动或者说他们的一个更大的自由,他们应该往哪里去走呢?", "input": "", "output": "你这个疑问呢,实际上就马克思学说适于不适于今天发达的、西方发达的资本主义国家的社会状况,尤其是我们都知道的所谓北欧福利国家,那么里边的工人阶级活得很好,(王骁:非常好。)就他们肯定没有什么要跟资产阶级斗争的任何愿望。那么所以思考马克思对资本主义的分析和阶级斗争的学说,是不是适合于今天的某些国家?有一个前提,资本主义生产关系是个世界历史现象,(王骁:对。)不能局限在一个国家里边来考察的,就像资本诞生第一天,他就一定是在他本性就是要征服世界的。就是我们今天讲全球化。经济全球化,实际上就资本全球化,就像马克思说得很清楚的,资本主义首次开创了世界史,在这之前没世界史的。只有各个民族孤立发展的历史,在这些发达的西方资本主义国家当中的,特别是国家小的,这个资本积累很厚,技术也很高,像北欧还有瑞士这种国家,把资产阶级跟无产阶级之间的剥削和被剥削关系转移到什么?其他国家去了。全球化是什么?在这种国与国的经济关系当中,一定是什么?穷国为富国打工的经济,穷国谁在为复国打工?穷国中的无产阶级。中国曾经是遍地廉价的劳动力,然后西方的资本没出路,就到中国来了呀。到了中国投资,劳动力成本很低,产生了在其他欧洲国家内部不再有的超额利润,然后他维持着资产阶级、无产阶级的阶级合作和阶级和谐的关系。就是我说你要知道这是一个全球化带来的。"}, {"instruction": "或者是不是全世界的这些无产的人在为那些有产的,给他们打工。", "input": "", "output": "对,因为你资本全球化了,所以马克思在这一点上说的对,工人没有祖国,无产阶级是个世界历史存在,而不是有国家内部的存在,这叫一个阶级存在的最初阶段。就资产阶级,它的成长也是分两阶段,第一个阶段也是自在的,商人与商人之间都是也要竞争嘛,欧洲早期的赚钱就全做生意的了,商人嘛,到后来才发现了自己共同的敌人,就贵族地主,那么他就资产阶级开始进入自为阶级,然后发动资产阶级革命,不就这样一个过程吗?所以马克思在这一点上说的对,工人没有祖国,无产阶级是个世界历史存在,而不是有国家内部的存在。"}, {"instruction": "在底特律的那些美国工人,他们可以通过整个美国的产业转移,给保证美国通用汽车的这些工人可以住得上大房子,吃得上牛排,开得上大皮卡,但是在柳州也有一个工厂。这两波不一样的工人,他们如何在如此悬殊的待遇之中,产生一种你说兄弟或者阶级情感。", "input": "", "output": "牛排不一定比螺蛳粉高贵。那这里边有两个因素啊。一个是工人阶级的收入有国与国之间的差别,对吧?就是我们中国有廉价的劳动力,就是他的工资低,人家是高价劳动力。那么我作为企业家,我在本地开厂还是到你这里开厂,我这里用工太贵了,这个国家的工人阶级的工资收入水平啊是比其他国家高多了,对吧?那么这是他的情况,但他还不失为,他仍然是工人阶级,对吧?那么他到一定程度,他也会失业。马克思说得太清楚了,他说工人阶级有两个阶段,自在的阶段和一个自为的阶段。但你如果是工人阶级的一个成员,你不知道自己是一个工人阶级,大家都要找工作,竞争上岗,这些都是工人阶级的一员,你会把他看成是你的敌人。在这种情况下,工人阶级属于自在的阶级,而不是自为的阶级。自在就是他客观存在,但他没有自我意识,(敏求:没主体性。)没有阶级意识,他不知道自己作为阶级阶级。我虽然阶级意识蛮强烈,但是大多数人没阶级意识。)"}, {"instruction": "针对第一个话题,我还有一个私人的问题想好奇(地问)王老师啊,你自己进入马克思的、阅读马克思的那个时刻是什么时候开始的?马克思什么时候开始打动你的?", "input": "", "output": "那就读硕士研究生了,因为我本科毕业的时候,我还没有特别关注马克思,就本科阶段到本科大四的时候,我最最佩服、最最崇敬的哲学家就是黑格尔。"}, {"instruction": "你也走了一段从黑格尔到马克思。", "input": "", "output": "对对对。后来不是我到译文出版社工作5年做编辑,那么在这5年当中,虽然我是做编辑的,翻译著作的编辑,但是我业余时间一有空,我还是读哲学的书。读哲学的书的时候我就发现特别是对当代社会的问题来说,黑格尔是不够用的,因为我们进入改革开放,我们开始经历了一个巨大的社会转型,这种现实生活当中发生了一个两个时代之间的过渡,从毛泽东时代到邓小平时代,这是不得了,现在回顾想想我们这个民族能承受这一次巨大的社会转型也是不容易的。那么我们当时年轻人精神上是非常敏感的,就知道一切都在改变,传统的价值、传统的信念很快就被放弃了。到深圳去看到一个标语叫时间就是金钱,效率就是生命,那么我们搞哲学的人要思考了怎么会这样的,那么还有一部分人先富起来,那么当时最早一部分富起来的是谁呢?山上下来的人,就刚刚“刑满释放”,然后他们就开始做生意了,那么以前在厂里工作的工人都是规规矩矩的,不敢到体制外去的,他们就发财了,万元户啊什么的。这一切就让我要思考了,因为我搞哲学,学了4年本科嘛,后来发现读马克思吧,马克思讲过《资本论》,他讨论资本主义最了不起了,读读《资本论》越读越有味道了。我发现正在好像讲我们中国的事,对吧?这个过程呢,这不过是有一些不同的特点而已,但本质上是一样的一个过程。所以这样一个过程,马克思写好了,后来我就有些地方读不懂,后来我就想先要把马克思哲学搞清楚,这时我下了一个决心考回复旦,考回复旦哲学系读硕士研究生,我选的专业就是马克思哲学。"}, {"instruction": "其实我们有很多,今天也有很多的年轻的小伙伴,他们也会越来越关注马克思和当下中国。就是好像这个话题从原来大家觉得马克思似乎好像过时了,到今天大家又充分觉得他对今天当下的现实有很强的针对性。王老师是怎么来看这个部分?", "input": "", "output": "嗯。首先有一条啊,就是我们的执政党是无产阶级政党,所以马克思的学说肯定是这个党的指导思想。一个无产阶级政党在推进改革开放的时候,那么在这个意义上呢,就马克思对资本主义的分析,一定是要拿来讨论当下中国市场经济展开的过程和它出现的问题。那么在这一点上,马克思真是可以帮助我们。第二个就马克思主义与当代中国的关系有一个基础,你要完成一件事情,就是马克思主义的中国化。最初在新民主主义革命阶段的时候,做到马克思中国化的是毛泽东思想,毛泽东思想就是马克思主义中国化的第一个成果,以中国自己的方式来实现这个民主主义革命。这种民主主义革命叫新民主主义革命。新民主主义革命跟旧民族最革命的区分就是旧民族主义革命的领导阶级是资产阶级,新民族主义革命的领导阶级是无产阶级,但是无产阶级去领导一场革命来完成对封建主义的颠覆来让中国社会改变它的形态,这叫马克思主义中国化的第一阶段,它的成果就是毛泽东思想。那么现在搞改革开放,搞社会主义市场经济,那么是不是有一个马克思主义中国化的第二阶段要来,中国化意味着什么?它是否可能中国化?马克思哲学是西方的智慧,这本有智慧的民族如何把马克思这个西方学说中国化,那么这个社会基础在哪里?很简单,中国的无产阶级是说汉语的无产阶级,中国的资产阶级是说汉语的资产阶级。所以在今天马克思主义中国化的第二阶段,实际上就是我们承认现有阶级分化,那么进入了搞市场经济的阶段,但是前面前提有的,叫社会主义,那么社会主义市场经济,这个社会主义怎么看?两个方面,一个制度,马克思的学说来看资本主义展开的每一次矛盾都要求什么?对资本的逻辑要加以划界,要节制,不能任其充分展开,这是社会主义的第一个因素。社会主义的第二层意思,以儒家的精神面对市场精神,以儒家的精神让这个中国的新社会阶层承担起社会责任。承担社会责任这件事情有两种,一种是法律上叫你承担,纳税,还有一种叫道德上让你承担,你要成为儒商。不就社会主义因素来了嘛?你要成为社会贤达,就像以前的乡绅,他虽然是地主,但是他对这个他的所在的区域的那些穷苦的人他是能帮助的。所以这就是我想,马克思主义中国化,就马克思主义与当代中国的关系,要有一个社会基础,还有一个文化的基础。社会基础就是确实有无产阶级和资产阶级的区分,如果没有无产阶级和资产阶级的区分,要将马克思学者弄到中国来,没意义的,我们也不会懂的。周道:老师,就结合您刚才讲的这一段,有个问题我想问,就比如说因为我现在的工作其实也算是在为资本家服务,那比如说这些资本的提供者,疫情一来的话,那有些公司它是比如说这个工资得照发,所以他们其实是承担了相当大的一些风险,一些经营不善的一些风险。那么比如说他们承担这些风险的时候,他们不应该也相应地获得一些奖励吗?就是承担风险的一些奖励。然后另一方面就是说比如说因为很多国内的出租车公司,它们其实也是国有控股这样子的一个成分的这种类型的公司,他们也承担了相当部分的这种维护民生这样的一个职能吧。这样子类型的一个公司的话,就是说是不是也是我们要去鼓励或者是提倡或者是保护的。"}, {"instruction": "", "input": "", "output": "中国的企业家,如果他在文化上比较自觉啊。我认识一些企业家朋友,他个人发财老早完成了,所谓财务自由啊,他说我现在把企业关掉也没事,但我不忍心关,我说为什么?那帮员工跟了他几十年,我现在不赚钱也没关系,只要企业存在着,他们就有饭碗,我为了这一点混到现在他说。这就中国人啊,他就是觉得他跟员工的关系并不仅仅是用工合同上的契约,他还觉得他对员工的生活有伦理的责任。日本也一样的,对日本经济不能理解的西方经济学家,他们只从经济学角度分析日本,经济危险以后呢,西方许多经济学家对日本经济后一步的发展都做了许多预测,后来每一条预测都落空了,他们就非常惊讶,难道我们的经济学理论不能适合于日本经济吗?他们只是纯粹的经济学家,too pure,太纯粹了,他不知道经济不是个孤立的区域,当经济危机爆发以后,他们第一件事不是说一个企业要裁员,而是企业员工自觉地愿意减薪,与企业共生存,那么同行之间、同行业之间的竞争以前是竞争,当经济危机来他们是什么?互相帮助叫共生,我绝不会乘你之危,因为危机上来以后,比较小的企业它可能就先受不了,然后我就借机来吞并你,没做这种事,让你活着,我们一起活着,要共生。好了,它避免了许多经济学理论推出来的后果,这是个非常典型的事例来说明把经济跟文化、历史和政治因素区隔开来的是一种幻想,所以这就是中国人,对吧?无论是你在无产阶级队伍里,还在是资产队伍里,你首先是中国人。在这一点上,工人阶级有祖国,这个祖国不是他的国家,而是他有文化属性,就是他有民族特性特征的。"}, {"instruction": "还有前两年,王老师,有一本书叫《马克思为什么是对的》,马克思在今天的当代中国是不是还是有他对的地方?", "input": "", "output": "对的,肯定对的,只不过会有中国特色。然后我们自觉地理解马克思的资本研究的最重要的著作《资本论》,对我们这里的中国搞市场经济有一个非常好的作用,就是说它可以减少那种痛苦吧,减少那种冲撞吧,那些都是曾经在其他国家发生过的,特别是比如说原始积累第一阶段,所以西方的资本原始积累说到底就是动产战胜不动产的过程,动产就是资本家口袋里的货币,不动产就是封建性质的土地所有权,最后赢家是谁?赢家一定是资本家,一定是动产。因为货币可以购买土地,让土地上农业耕作服从资本主义的原则,叫农业经营的资本主义化。不就这个过程,中国也在经过,现在下一步就在做。这个农民他如果能够回到农村的话,他做什么,农业工人啊,绝不是个人在耕种了。"}, {"instruction": "不是传统意义上的农民。", "input": "", "output": "不是说小农了。农业工人就是公司化经营了,这个过程在欧洲全发生过了,我们也得来一遍。这就是马克思讲的,你不可能规避的,你搞市场经济不可能不让土地服从资本的运动,怎么可能?所以这就《资本论》,这些东西以铁的规律发挥作用的。"}, {"instruction": "但是现在反对这个理论的声音就是在于希望把中国的农村当作蓄水池,因为有一派的观点是如果一旦这个世界大战也好,还是这个现代资本主义的整体的经济模式的崩溃也好,然后大家认为啊,要让农村保持一个像这个乌托邦或者伊甸园的状态,这样大家到时候还能有一个地方回去。", "input": "", "output": "这个真是乌托邦的想法。我们打赢了脱贫攻坚攻坚战是吧?脱贫的最直接的方法不是送钱,送钱没用以后又返贫的,脱贫就是让农民进入市场,进入资本逻辑中去啊,你就不会返贫了,那这就是必然的路。你的生产的东西要通过现在比如说互联网提供了很好的条件,是吧,这就可以卖到很远的另外一个城市中去,那么你就进入商品生产,进入市场经济,然后这个地方的产业逐渐地被发展起来。这个地方有这个比较长久的产业在支持的话,这个地方的农村就不会穷了呀。这是唯一的路。《资本论》里边也都写着呢。"}, {"instruction": "除了这部分以外,还有一种有一些比较强烈的情感上的需求,比如说马克思对于人的资本关系当中人的异化的这个部分,很多人他可能是处在这种情感的这种状态当中啊,他反而就通过这个去更加地走进马克思。", "input": "", "output": "确实是有一种情感上的选择,就是他感觉到自己的阶级地位了。然后马克思学说是为他们的阶级说话的,这是马克思致力于他的学术研究的主要目标啊。马克思说异化是资本主义生产关系必然带来的东西,就像马克思在《1844年经济学哲学手稿》里讲的,人在劳动中不是在执行作为人的生命活动,而是在执行动物的技能,这个活动不是他的,但是他来执行的,所以人们躲避劳动像躲避瘟疫一般,因为劳动异化了,我们就躲避它了。其实劳动是人的第一需要,你像一个小孩儿童你去试试看,不允许他做任何事情,你就吃和穿,然后睡觉或者拉屎,别的事情不能动,你简直在迫害他,他就想干活了你知道吧?你边上干,他还凑着一起来,这是人作为人的第一需要,因为劳动是人的身心两个方面的活动,他通过这个活动把人跟动物区分开,然后你不让他劳动,就是你把他当动物看,取消他的人性了。所以马克思最反对用抽象的人性论来解释社会形态和生产关系的起源。"}, {"instruction": "那就正好想问一下王老师,就是刚才我们也讲到了说马克思为什么是对的,我们在今天还可以去认同马克思他对资本的一些底层逻辑的分析在今天依然是有效的,但是资本主义本身它也经历了一些发展,比如说我们当今的全球化程度,那肯定比马克思他所处的工业资本主义的时代要更为程度更深了,范围也更广。还有比如说我们当代也出现了很多新兴的这种劳动类型或者说是职业,比如说码农,那您觉得这些东西的出现会不会对马克思学说造成一些挑战呢?", "input": "", "output": "但是我们刚才既然讲马克思的资本论的学说是揭示了资本主义发展的底层逻辑,而且它底层逻辑的最重要的一条就是剩余价值规律在今天继续起作用,那么至于它资本,当然它要全面地征服,不光在空间上,从西方到东方到非洲,所有的每一个角落都不可能遗漏的,直到现在还没征服完毕,它必须把人类社会生活的所有空间领域都占据完毕,他才走到尽头的。包括金融,金融资本主义今天的展开有许多马克思没看到的新的金融现象,这是肯定的,但金融资本主义的最根本的东西,底层逻辑。第三卷,《资本论》第三卷,从第二十一章开始,连续三章。第二十一章叫生息资本,就在讲金融,金融危机的根源已经被解释了。实际上马克思也不需要我们为他辩护,而是我们不断要向他请教,你到了社会生活的某一个劳动领域当中,你一开始觉得蛮好的,后来发现这个劳动领域呢,是有个进度强制的,它规定了你的效率,规定了你在规定的时间里边形成的成果。这是违背人的天性的。你比方说IT产业是吧?软件工程师,一开始你可能学计算机科学是你的兴趣,到后来你到做软件工程师,你就知道了什么感受了,就是你不能一点点落后的,一点点落后,你在市场上就没地位了,人家软件开发又快又好,你就被淘汰了,你也是必须竞争啊。到这时候你终于发现苦不堪言,最后你的愿望是把这个电脑砸了。就是他的创造性的活动变成对自己的自我否定,这就是异化。你创造这种东西反过来支配你的,这就异化。"}, {"instruction": "有一个比较有意思的问题想要问王老师,就是当我们和一些没有专门做哲学研究的同学或者朋友聊起来哲学或者聊起来马克思主义哲学的时候,我们的话总是说出来显得太过于专业了,因此我想请教您,在您这个这么多年的教学经验当中,马克思主义哲学最让普通人可以被吸引的那个点是哪里?", "input": "", "output": "这个问题非常重要,我认为啊,就是我们如果去跟不搞马克思哲学的年轻人,但他们对马克思哲学又感兴趣,你怎么跟他讲述?那么第一条我认为就是要揭示马克思的一种批判性,他批判什么?批判现代理性主义,这种批判为什么对年轻人非常重要,因为年轻人比如说经过高等教育的这样的年轻人,他又不是读哲学的,更谈不上真懂马克思,那么他们会有一个共同的特点,接受当代社会科学,接受当代社会科学的就有一种对社会生活的知识论的态度。什么叫知识论的态度?就把社会生活放到范畴体系当中去把握。假如这个人是学法学的,他就会把社会现象和社会生活放到那个法的范畴当中去把握和理解。假如另外一个学生他本科阶段学的是经济学,他就习惯用经济范畴来理解社会生活,来把握它。今天的社会科学的基础,哲学基础,就是近代理性形而上学。理性形而上学的特征是什么?特征就是用范畴做出事实来,事实是被建构的fact,事实怎么被建构呢?用范畴建构的。假如你用了一个经济范畴,你就建构出一个经济事实来,这件事该做还是不该做,首先投入和产出、成本与效益,对吧?那么他就习惯用经济范畴。那么这样的社会科学的毕业生,那么你要跟他讲讲马克思哲学的话,你就第一步教他批判,批判社会科学的哲学前提,这个批判的具体就是把范畴清洗掉,经过把范畴清洗掉,我们进入了事情本身。所以经济学家一直有一种自豪啊,他认为他用经济学的范畴和理论来描述经济活动的时候,他在说出这个社会生存的基础,所以他很自豪。所以经济学就认为自己这门科学特别重要、特别基础,但是经济学无论如何都不能说出阶级斗争在什么情况下要发生,你知道吧?经济危机为什么会到来?那么经济危机发生表明现实经济活动背离了、偏离了资本运动的逻辑,现代社会的科学代表就是社会科学,经济学、政治学、法学等等,社会学。当然我们不是反对大学里有这些专业,不反对的,因为他有一些工作的操作规程是这么学的,比如说你学Sociology,那么你去应用的话,就是做social worker,社会工作者,那么社会学已经给你、告诉你现代社会的一个基本构架,每一个层面当中的一些逻辑关系,你要学一学,便于工作。当然这个社会本来就在异化中,那么在异化中工作吧,你用那么异化的社会学来做,这也没办法,就像金融学一样的,你不懂金融学,你到银行里怎么看资产负债表,怎么编造骗人的理财产品?这是金融学的用处,但金融学是遮盖了金融资本主义的本质真相的东西。金融学从来没有预言过一次真正的金融危机,危机爆发了以后它事后说了,就像08年金融危机,哪一个金融学家预言了它,如果能预言就可以避免嘛。金融危机有它深刻的根源,马克思揭示了它,这个根源是非理性的。然后我们要做的事情把它纠正回来,纳入逻辑中去,这叫经济危机的克服,是吧?这就经济学家。但经济学每一次都让自己出丑,因为它对经济危机的爆发的根源,它始终不知道,爆发了以后,它事后用理性来解释,那叫事后诸葛亮,因为他总是看不到事情本身。这是让我们的年轻人对马克思哲学产生兴趣的一个非常重要的点,就是先要发现马克思哲学的强大的批判力。"}, {"instruction": "明白。刚才我们讲到说马克思阅读,因为有很多小伙伴是不可能去浩若烟海地阅读马恩全集的。如果要进入马克思的话,刚才王老师先说了这个《巴黎手稿》,如果我们能够给小伙伴推荐一个比较简明的简单的一个阅读线索的话,大概是怎样的。", "input": "", "output": "我刚才讲的就是线索,没办法简明了。就是说明确一点,就这意思吧。第一本《巴黎手稿》,这是毫无疑问的,《1844年经济学哲学手稿》,领会其中发生的哲学革命。第二部,就是读一篇篇幅很短的11条提纲,就《关于费尔巴哈的提纲》,实际上这11条提纲呢,是对1844年《巴黎手稿》哲学革命的概括,它引导出历史唯物主义的创立。那么第三本书《德意志意识形态》,就像你刚才说我们不可能说年轻人都把马恩全集读完,我们搞马克思专业的有多少人把马恩全集读完,对吧,也没有的。这本书是历史唯物主义的开创,开创之作。有了历史唯物主义的认识理解以后,再去读一本叫《哲学的贫困》,《哲学的贫困》是批判蒲鲁东的,但这本书的实际内容同时就是资本论学说的一个导言。读过《哲学的贫困》之后,就进入《资本论》吧。基本上这条线索。"}, {"instruction": "线索挺长的。很长的一条线。", "input": "", "output": "我们列了几本书。《1844年经济学哲学手稿》、《关于费尔巴哈的提纲》、《德意志意识形态》,《德意志意识形态》主要读第一卷第一章就可以了,篇幅不是很大,然后就《哲学的贫困》这5本。当然《共产党宣言》也应当读,跟《哲学的贫困》一起读。《共产党宣言》,然后就是《资本论》。我们今天讨论了这样有三个比较大的话题是吧?第一个就是马克究竟在说什么,意味着我们曾经有那么多对马克思的误解,我们要知道马克思自己在说什么,所以这个是我们今天第一个讨论的问题,那么我们的基本的共识已经达成了,就是说你不要根据种种马克思主义的著作来了解马克思在说什么,我们先把所有的,你是弗洛伊德的马克思主义,还是存在主义的马克思主义呢,还是现象学的马克思主义呢,这些立场都先拿掉,读原著。读原著的根据在哪里?我们能读懂马克思哲学的原著是时代提供给我们理解的条件,而且今天中国人也面对资本逻辑在中国社会现实中展开的过程,才知道马克思要解决的问题是什么。第二个话题就是讲马克思与当代中国的关系是吧?西方思想史上的重大成果与一个东方国家它的当下生活实践之间的关系,那么我们特别讨论就中国化问题,我们为什么今天的中国读者读马克思的哲学比以前更容易进入其中,因为我们经历了社会转型。在早期毛泽东思想是新民主主义革命阶段上的马克思主义中国化,我们现在搞社会主义市场经济这个阶段来了,它仍然要完成是马克思主义的中国化。马克思学说是西方智慧的这个近代成果,但是在当代具有它的生命力,就是说我们把马克思主义的当代的意义跟中国问题的解决结合起来,这叫马克思主义中国化。第三个话题,问题就是我们年轻人如何读马克思,我们不要以为他一定要有专业基础,不需要,因为年轻人,大多数年轻人并不是马克思哲学的专业的专门研究者,所以他们进入马克思呢,是因为要带着自己的领会到的时代问题,他对中国问题的领会,那么最重要的一点是什么呢?从社会科学给我们的范畴的遮蔽里面走出来去领会马克思学术的批判性。第二步才知道马克思对社会现实的极深刻的分析能力、分析力,这分析力就进入事情本身以后如何说它,如何说这个事情本身,说事情本身就是把范畴规定拿掉了,才有事情本身,然后把它直接地说出来就对了,而不是说规定这个世界该往哪里走,马克思说这种科学叫真正的历史科学,它实际是参与历史运动的,而不是给历史运动一个先验规定,价值目标和方向的先验规定,没有的,那么这样的年轻人就知道了,一个就是批判性,因为我们都在当代状况中生活,我们不知不觉都是先验唯心主义,就是范畴规定的,虽然我们不知道先验唯心主义是个什么概念,对吧?其实我们都在其中,那么通过马克思可以有一个作用叫范畴清洗,把范畴清洗掉来认识生活。第二个,通过马克思学说可以把生活的真相加以表述,生活的真相被表达,这是一件很难的事情,你可能表达表达又变成范畴规定了,所以要进入马克思,进入马克思看他怎么阐发生活实践本身,大致我们今天讨论大概是有一定的成效的,有一定的这个效果。"}, {"instruction": "", "input": "", "output": "为什么爱扎根于人的本性之中,那么就要对人要有一个哲学的讨论(话题一:我们为何而爱?);那么第二个叫哲学无法回避的课题:爱情。爱情确实是一个文学、艺术的永恒的主题;第三个如何面对亲密关系正在发生变化(变革),已经是当代的了,就当下现状,社会一些现象,第三个话题就是很切合现实了;那么第四个,爱终将去向何处?当然这个题目,起了就会发生歧义了。因为我们在爱中,我们并没有明确的目的,我为了什么。爱不是手段。如果爱是达到某种目的的手段,它就不是爱。所以这个问题本来提的就是有问题。为什么爱。"}, {"instruction": "像康德就是说:我们要把人当成目的,而不是手段。", "input": "", "output": "对。"}, {"instruction": "我想爱也是如此。", "input": "", "output": "对,爱就是目的。爱是目的本身,因为人作为人,他就是社会存在物(与自然存在物有别的、具有社会属性的人)。(爱是发生在人之间的,也就是目的之间的。)"}, {"instruction": "所以目的太多就会把这个爱给掩盖了,是吗?比如说父母他养育我,但他目的很多。比如希望我考上清华、北大,希望我娶什么样的姑娘,希望我这样,希望我那样。是不是就影响到了他对我的爱呢?", "input": "", "output": "父母对孩子的爱是天然的,本性。这动物也一样,再凶猛的动物生出它的后代来,它一定是爱的,呵护着它。所以我们看观察过好多,你比方一个猫,母猫啊,刚生出几个小猫来。它捍卫保护自己的幼崽,凶得不得了。你刚刚去接近它那里,它就冲上来扑你。父代、母代对子代的爱它实际上是生物的天性,一种本能。这不用教。他也没什么目的,没有任何目的,“这就是我的后代”。所以它(爱)是非常感人的。就是他牺牲自己一切的。尤其母爱,特别的突出。母爱长久地被歌颂了。它可以把人感动到什么程度,这个孩子烧了四十几度了,家里没有退烧药,到处邻居去找退烧药。邻居没有退烧药,她急了,无论如何要冲出小区的,我要到医院去,带孩子,或到药房里敲开药房的门,这个药必须要。这就很突出的。爱有一种生物基础的。我们对父母的孝,孝不是一个头脑当中的思考,也不是一套孝的规则,而是发自内心的,是对父母之爱的深切的回报的愿望。所以如果父母亲你没有好好地在他老年照料他的时候,有可能产生人生永远不可能弥补的遗憾,这叫“子欲养而亲不在”。这是人生少数几大痛之一。一旦某人在此痛中了,那无法挽回。所以这是人就是社会的动物。社会的动物,首先是(属于)社会的最小的单位:家庭(至少是中国)。"}, {"instruction": "因为我身边有很多的朋友,他面临这种家庭给他们的爱的时候,其实它附带而来的东西是负担。很多负担。他们会觉得无法成为我想成为的人,无法做我想做的事情。那么当这个家庭的爱带来它负面或者说它背面的东西的时候,作为孩子,你觉得应该怎么样去理解和看待?特别是在我们这种中国文化长起来的这些孩子们。", "input": "", "output": "就是亲密关系在当下社会正在发生变革。比如说我举个例子:现在不是有一些母亲被称为“虎妈”,她对孩子要求极高。当然她这种要求很高,因为这是她的孩子,还是以深爱做为根的。她这种情感变成某种“痴”,“贪嗔痴”的“痴”。那么这样的孩子是受不了的。那么孩子在这种感受当中就觉得我无所适从。我知道你爱着我,但是你这样又干预了我自己的成长和我对生活的理解和我对我未来目标的设定,你不能代我设目标。那么这就是亲密关系当中一种,就是父子之情、母子之情、母女之情,就是双亲跟孩子,这样一种非常最最原始、最最根本的亲密关系也在发生变化。这种变化会导致爱异化为恨,会导致悲剧性的结果。"}, {"instruction": "对,叛逆。那我们知道“虎妈”这个名词主要是从蔡美儿开始的,蔡美儿她对孩子很严格,她写了一本书叫《虎妈战歌》。那今年也有一个教育学的博士,就是陈美龄,她写了一本书,谈论说我怎么把三个孩子都教到这个斯坦福读博士。然后两个的教法就不一样。但是人家去访问蔡美儿的女儿说:“妈妈对你这么严格,你不拉小提琴她就处罚你,你不会怨恨你的妈妈吗?”你她女儿的回答让我非常的触动,她说:“虽然我妈妈她做了一些很严苛的事情。可能在你们看来是不恰当的,特别是西方人看来。”但她说:“我能从我妈妈的这些行动中感受到她是爱我的。”", "input": "", "output": "你刚才举的例子,我是亲身感受。我从小,我父亲向来对我严厉,我几乎是听不到他对我的任何一句赞扬。如果我的行为有什么差错了,他就愤怒,很愤怒的。我感受到他的愤怒了。但是我一直认为,他深深地爱着我。"}, {"instruction": "OK.(好。)", "input": "", "output": "这是我自己的感受。我多么希望得到他的表扬、鼓励,他没有的。我有一次作文写得还可以,一个语文老师带三个班级,把我这篇作文在三个班级都读了一遍,那我就得意了。回来跟我父亲讲,我这篇作文三个班级读过了。我父亲......你猜我父亲问我什么,你知道吗?你多大?我说,十六岁。你知道中国的作家王蒙吗?他十六岁发表了他的第一部小说。就可见我父亲对我的要求之高啊。"}, {"instruction": "这个不会给你带来伤害吗?", "input": "", "output": "没有伤害。我就心里明白:我是不值得骄傲的,别以为你这一次作文写得好就可以尾巴翘起来了。所以我一直深深地感受到我父亲对我的严厉里边,藏着深深的爱。因为他从来没讨厌我,他只是对我提出了要求。就是你还没到。"}, {"instruction": "那么表扬一下会怎么样呢?如果在那个时候他鼓励你一下。", "input": "", "output": "我后来发现我非常感谢,他一直不表扬我。我后来发现我跟我儿子的关系当中,我经常犯的一个错误就是:因为小孩与小孩比较,每个人都有长处,每个人都有短处。所以有一条原则:如果你小孩有一个超出别人的长处,你千万别当着面表扬他,不用说;他有短处,你千万别当着别人的面批评他。短处你就帮助他提高,鼓励他。在短处方面有所提高,有所进步,马上给予鼓励。其实这个短处还比别人仍然短着,对于你过去来说你已经进步了。所以我们不会做父母。就我们这份情感,就是说他短了你急了,急了你不要愤怒,愤怒你会变得看不起他的样子。父母不能对孩子流露出任何鄙夷。这种鄙夷的心、情感,就是你心里已经失望了。他是你的孩子,他一定会有短处。"}, {"instruction": "你小时候挨过打吗?", "input": "", "output": "打,当然。"}, {"instruction": "那也会觉得甘之如饴?", "input": "", "output": "我认为他打我不是甘之如饴。打我时候我委屈的,但是我没恨他。这是很奇怪。还有一种孩子被打是恨的。为什么?父亲在单位里被人欺负了或者受了委屈了,有气无处撒,你知道吧,回到家里打孩子,还有喝醉了酒打孩子。这是不可打的。(还有三个不可打:①极端的性格(比如“杀了你”,这无疑就是病态的),没轻重的动手,更多的其实是发泄自己的失望的情绪;②过分的要求;③个人的、与孩子无关的借口,比如说,“我在戒烟,最近脾气会不好”。)"}, {"instruction": "这是发泄,我觉得这不是教育。", "input": "", "output": "我们不要绝对认为父母不能打孩子。中国人老百姓那句老话是有一定的道理的,叫“棒打出孝子”。并不是我们主张,现在不主张体罚这是肯定的。以前体罚是普遍的,我们要纠正这个不好的传统,对吧,你比方说教书先生教你背这篇书。你背不出手,伸出来,戒尺就在这里,对吧。那个朗朗钢琴弹得那么好,他父亲逼出来的。包括一定的体罚都在里面的。就是我们不能说:打是一个绝对的坏的事情。关键是,第一你要掌握分寸,第二你出于深深的爱,他明白。我被我父亲打了好几次,我也没恨他。"}, {"instruction": "但是王老师,我觉得个体差异挺大的。我爹也是老师,对我非常严厉,也是跟您一样,从小到大我没有被他表扬过,写作不管写得再好他都不表扬你。只看到你的缺点,但我就不像王德峰老师这么宽容和这么深情。", "input": "", "output": "就是我们是不是该对孩子一开始就要表现出一种对他的人格的尊重,独立的尊重、平等的态度。这是个很虚幻的抽象的看法。因为小孩作为一个主体,他是一个成长的过程。我讲的主体首先不是认识主体,而是道德主体。道德主体是被教化的,他没有生物学基础的。其中有一个就是自律,self-discipline(自律)。自律这种能力一定是要通过奖励和惩罚建构的。就是你做了错误的事情,我给你一定的惩罚;你做了有益于这个家庭的事,比如在家庭生活里边你帮了你外婆,你帮了你外公,你帮了你母亲做了一件什么事,我奖励你。一个叫罚,一个叫奖。这是一种教化。它不是把认识灌输给你,而是让你成为一个道德自律的主体。这是实践,这不是讲道理。我们如果放弃对我们的孩子作为一个自律的主体的建构的话,我们的教育在根本上是失败的。不能self-center(自我中心),因为他(还不是)还没成长为一个独立的人格,是吗?(金金:是。)那么他长大了以后,你一定要尊重他了。这一点,西方的原则我是赞同的。你不能干预他了,但是他有一个成长过程。在这成长过程当中,父母为什么是他的首任的教师?不是教他识字,而是教他做人。第一,父母身体力行,你给孩子树立榜样;第二,孩子在道德方面的、为人方面的品质上有问题,坚决地制止住,毫不客气,毫不留情。我看我们这一代家长在这方面严重地缺失。"}, {"instruction": "比如说呢?", "input": "", "output": "很普遍。就是我很少看到自律的。就是说他是在学校里被管着的,一从学校里放出来,他就没有任何自律了。他始终是他律。"}, {"instruction": "那老师觉得该怎么做比较好?", "input": "", "output": "你跟他怎么打交道,你纠正他的不良的行为,这是你不能回避的任务。你可以接受他成绩不好,学习成绩不好,你不能接受他的不良的行为。比如说他在班级里伤害别人,欺负女同学,假如男的,这一切都要坚决制止的。这是家长的责任。"}, {"instruction": "正确的引导。", "input": "", "output": "这种坚决制止,并仍然让他感受到你是关爱他的,这是一个不是口上说。我一直认为我们现在为人父母的,口上说得很多。到生活交往的时候,他自己都有问题。父母之间交流,那么孩子也在边上听着。比方说跟隔壁邻居家有矛盾了,那么父母在家里也会讲吧。你又不避开孩子,这时候父母的交流确实体现出父母的境界,为人的境界。如果是心胸狭窄的、自私自利的,说我们怎么去把那个......他要占便宜,我就不让他占,我就要怎么怎么。孩子都在边上。这个就是对孩子影响非常重要的时候。因为我父亲是居士,在家修行的佛教徒。所以我后来就非常感谢我这个家庭环境。佛教徒有一个非常重要的特征就是平等对待一切人。无论你是地位高的还是地位低的,无论你是曾经伤害过我的还是帮助过我的。那么有一次我们家来了一个客人,其实是他的同事。先前不知道哪些事情曾经是伤害过我父亲的,我母亲知道,但他来了,因为这时候他有求于我父亲了。我父亲非常认真地沏了上好的茶,请他坐下,说你别急,你有什么事慢慢说给我听。那我在边上看着呢。然后那个客人终于走了,我父亲已经想好了怎么帮助他了,我母亲就生气了,跟我父亲讲:“你这种人还能帮忙,他是好人吗?他伤害你的事情你别忘了。”我父亲轻轻的说了一句:“人都会犯毛病的。他当时伤害了我是他病了,现在就好了呀。”一句话就过去了。诸如此类的日常生活中的父母在你面前的谈话、待人举止的态度,一直在渗透到我的心里。我在这件事情万分感激。所以我后来上班,比如说读书有班级、有同学,后来上班有单位,我跟所有的同事的相处,我是不知不觉当中,第一与世无争,第二我不会看不起这个人、看不起那个人,我觉得每个人身上都有我可以学的优点。这些都不是我看书看来的,以后看书才知道:孔子说的“三人行必有我师”。你小时候教孩子背出来“三人行必有我师”,他也背得很清楚,但是你在他面前待人的态度呢,这是对他影响最深的教育。80后现在普遍的都做了父母,孩子们也都读学校了,读小学什么都来了。那么这一代父母,我发现有一个问题,我都没想到过的。就是我们当时觉得自己对自己的孩子要求比较高,希望他在这个学习的过程当中能胜出,希望他可以考北大、清华、复旦,这种愿望蛮强的。但是也是能够接受他考试失败的。但是我发现80后的父母不得了,对孩子的要求比我们有过之无不及。真的。"}, {"instruction": "你是怎么发现的?", "input": "", "output": "当然,我儿子就是80后的。他的同学现在怎么开始竞争的。幼儿园就开始“干”起来了,要择园吧?择园不光是家长被选择,家长当然一个要么有钱、要么有人脉。你要搞定个幼儿园,一般幼儿园你不能去了。那么孩子也要被面试的,那么就要训练孩子了吧?"}, {"instruction": "就不要输在起跑线上。", "input": "", "output": "这个训练我是没有想到过的。我说,你们怎么这么起劲?比我们起劲多了。当初我们起劲的时候,你还很恨我,对吧?现在你比我有过及无不及。"}, {"instruction": "那你能理解他这种做法吗?", "input": "", "output": "不能理解。所以我要说话。这个状况是很畸形的。也许我现在回顾我对我的儿子是有所伤害。但是总体上是没啥伤害。他没考取重点大学,我难过了,难过了回来想明白了,我也不训他了。反正有书可以读了。但是有的家长,现在有些家长很可怕的。就说这个孩子如果在读书上成绩如果一连串地下降的话,在家里就没有任何欢乐了,你知道吧。全部围绕着一个问题干起来了,然后到处找补课。吓“死”人了。那么我经常就跟他们聊天,我就跟他们讲,当然跟他们讲讲中国的哲学。儒家怎么看待孩子成长,这个道家又怎么看待,佛家又怎么看待。他们听了以后,都说我说的蛮好的。没用的。然后马上来个现实问题问我:你遇到你会怎么办?你能心甘情愿吗?我还跟他们讲,你们为什么不把孩子送到这个民工子弟学校去?他们觉得我这个建议匪夷所思。我说你为什么要把孩子送到那么多有钱人和有地位的人的孩子云集的学校的呢?你应该送到民工子弟学校去,他们是健康的。他们可以跟你的儿子打架,我们就在打架中成长的,而不是攀比的。现在那些“贵族学校”,我给它个总名称叫“贵族学校”。都在比什么?他爸爸开了辆什么车来接我的,他开了辆什么车。身上穿的品牌,品牌还有差别。我说你怎么好把你的孩子送到这种学校去啊?将来这妄念多了。我说送到民工子弟学校。他说民工子弟学校打架。打架就对了,我说,它叫健康成长。两个男孩在彼此眼光的对视当中,我从你的眼睛当中看到了怯懦,你从我这里看到了勇敢。然后那个孩子“欺负”女同学,你冲上去了,那叫英雄救美女。你的孩子遇到过吗?如此温文尔雅的那个“贵族学校”正在慢慢的腐浊你的孩子的心灵。对吧?"}, {"instruction": "像现在很多家长会给孩子我要报多少多少补习班,对他有很多很多希望什么之类的,那还有打压这种或者说以前说的会打孩子。这种表达也是一种爱,但是可能在孩子看来是一种会让我伤心但是不健康的爱。但孩子或者说另外一个人最终会感受到这份爱吗?", "input": "", "output": "人与人之间就是彼此创造的,在这种彼此创造当中爱就在其中了,我们要让孩子参与整个家庭的活动,要让孩子很早就觉得这个家族、这个家庭family是一个共命运的地方。你要让他在他力所能及的范围里边帮助这个家庭变得更好起来。现在我们的父母不给他们这种机会的,样样还带包,什么事情都我包下来,就是你就回来不要负任何责任,是吧?我们小时候都分工的,我跟我姐姐,父母都分工了,谁管这个事,谁管那个事。讲得清清楚楚的。我们还会为父母操心,比如说母亲生病了,我跟我姐都急了,就是想办法,你好好睡,这个药我都帮她记住的,一天吃三顿,一共两种药,隔多少小时吃一次,我都记在本子上的。爱就是这样的,对吧?就是我们别把父母过分当成是一个主体来支配他这个客体(孩子)。你也给他(指孩子)一个尽责任的机会,叫做共命运的感受。这非常重要。给今天的年轻的父母有什么建议。我的第一条我认为最重要的就是这样,就是我们不要等孩子长大以后埋怨他们:这孩子怎么一点责任心都没有。我倒要问你了,在他成长的过程当中,你可曾给他尽责任的任何机会。你病了就病了,病了你就要发高烧,你就睡觉吧。不,挣扎着把晚饭做好,他回来吃饭了,做好以后你睡觉去了,干嘛?这时候你就得让你的孩子在你床前侍奉汤水,你要给他这个机会的。你从来没给他尽责任的任何机会。长大了,说他怎么没有责任感?刚才讲双亲对孩子之间的爱,然后我们接下去讲这个恋爱当中的男与女之间他们的关系,它怎么会形成起来的?这样一个问题。我们千万要避免一种可能的这个转移,话题不知不觉被转移了。转移成什么呢?就是我们该找一个怎样的终生伴侣?那就是对婚姻的讨论,不是对爱情的讨论。你一个人不爱也可以啊,你也准备结婚,婚介所嘛。婚介所能给你带来爱情?然后婚介所一开始就是称分量,彼此称分量。你干什么职业的,我做什么职业的,你的学历如何,博士还是硕士?那个人连大学学历也没有,马上就下去了。然后现在还增加了有房吗?有车吗?父母干什么的?完啦。这跟love(爱)一点关系也没有。"}, {"instruction": "王老师你太不接地气了。你没听过那句话:不以结婚为目的的恋爱都是耍流氓。", "input": "", "output": "不。如果这个恋爱它后来形成了一个结婚的目的,不是你预设的。亦如此相爱,它一定要走向婚姻。你阻拦他们不行的。阻拦他们那个伟大的悲剧故事就来了。它是自身生出来的。婚姻作为将来的目标是它从这份爱里边生长出来的。如果这份爱是生成不出婚姻的,它就是差不多这个爱就萎缩了。已经不能算爱了。"}, {"instruction": "那么有爱发生的婚姻有的时候也会改变啊,忽然就又觉得不爱了。", "input": "", "output": "这里边就又是一个大问题,就是关于爱情与婚姻的,这样一个关系的讨论了。也是个大问题。因为这个爱情只要是真实的,它具有真理的价值。婚姻只要是真实的,就是它的基础是恩爱。然后当婚外恋发生,这是一种巨大的挑战,巨大的考验。因为这两样东西都真的。"}, {"instruction": "王老师你观察这些年我们是越来越保守了,还真的是越来越开放了?你就比如说伦理,大家就会觉得这东西很旧嘛,我们不要理它,我们只在乎曾经拥有,对吧?瞬间迸发,这一定是真的,然后要“死”要活。后来说分手你个渣男,你个渣女,有没有这种说法对吧?这里边矛盾点在哪里呢?", "input": "", "output": "现在就是这种渣男渣女的说法,它已经爱情中掺杂一些爱情之外对你的善恶的要求。在真正的爱情当中,没有谁骗谁的问题。谁付出多,谁付出少,没有这件事。根本没有这事。如果把这件事搞成这个样子,然后给某人贴个渣女的标签或某人贴个渣男的标签,爱情就不存在了。这也是社会病症之一种。我就老是搞不懂什么叫渣男渣女,就是你欺骗了我的感情?"}, {"instruction": "差不多是这样。", "input": "", "output": "问题是我的感情怎么会被你欺骗?我爱了,我没有损失。在这件事情当中,谁失去了?一个真诚爱过的人,他没有失去什么;一个虚假爱的人,是他失去了,失去了人性当中非常重要的一面。他是失败者,别搞错了。但现在倒过来了,就是我真诚地爱了你,然后你把我扔掉了。"}, {"instruction": "说明我吃亏了。", "input": "", "output": "我吃亏了。我有什么好吃亏的?爱多么有意思的事,爱让我每天活得很好,觉得这个世界属于我的,这就是你所拥有的。"}, {"instruction": "你有没有被别人伤害过?", "input": "", "output": "我倒很想试试看。"}, {"instruction": "这么自信。那你有没有同时爱过两个人?", "input": "", "output": "不可能的。同时爱两个人,它绝不是爱。如果发生这种事情,它就是爱是没有在场,爱没在场,这是另外一种。你比如说对异性的兴趣,人人都会有。但对异性有兴趣,甚至有性的好奇心都可能的。但它跟爱是没关系的。"}, {"instruction": "那王老师在感情里面,有哪一个瞬间觉得,我那一刻是爱了。有突然感觉这种爱的瞬间吗?", "input": "", "output": "当然。就是说你每天想要见到她,你终于在这个时候,这一天,你这次见到她,你安稳了,心定了。所以今天我everything is OK,没见到她那完了。"}, {"instruction": "那时候你多大?", "input": "", "output": "应该是中学三年级吧。其实是单的,就是她不知道的。我每天知道大概四点半左右,弄堂的转角处,她会出现。"}, {"instruction": "我终于知道你为什么叫小王子了,我一想到你下午四点要来,我三点就开始快乐了。", "input": "", "output": "后来当然都长大了。我们这里也动迁了,她到农场去工作了,我在工厂里做工人。后来在马路上相遇了,相遇这时候就没问题了。各人都有家庭了,那么就聊起来了。聊起来我就问她一个问题,为什么当初你下午四点半会在那里出现?我得搞清楚。她说她外公做事情有规律,外公基本上是四点半左右,叫她到杂货店里买一包烟,那她就很准时,被我摸出这规律了。但是偶尔也不出现,那叫六神无主。那这没办法。"}, {"instruction": "那这一段就是分别,带给你最大的价值是什么?", "input": "", "output": "恋爱是要相互的。只是我心中存了一份理想,那么我很高兴。她不知道,我也很高兴。(李蕾:就是我爱你,与你无关。)所以我认为没什么付出不付出,然后你回报还是没回报,怎么有这种事呢?"}, {"instruction": "年少浪漫的爱情时刻。是不是慢慢地有了年纪,这种感觉就会越来越罕见,变成奢侈品了,就是人动心的能力。我原来有一次录节目,我就问上海一个导演,我说:你怎么知道你爱这个人呢?他说很简单,就是你就手捂着自己的胸口你心跳不跳?你心跳那肯定是爱,说你要是心不跳,不管他条件再好,不管他是不是金城武,没有用的。那会不会慢慢的,人心跳的几率越来越小?", "input": "", "output": "这话我完全同意。就是说,当你爱上他的时候,你当然不知道自己爱上了他了。"}, {"instruction": "对呀。", "input": "", "output": "爱不是知道的。它叫,英语当中那个短语是对的,叫fall in love,译成美文学的,美文学的译法叫坠入爱河。就是你是跌进去的。你没有说我准备爱你了,我还甚至不知道我爱你了,我已经在爱中了,这最真实的爱。那么后来我知道了就是你刚才说的心跳,什么意思呢?我们同学,同班的。后来我发现一旦她进来,我走到她身边我一下子说话都笨拙了。"}, {"instruction": "对,会的。", "input": "", "output": "本来我很流利说话的,居然笨拙了。然后我老觉得不好意思。旁人不知道你怎么了,平时伶牙俐齿的,老是上课讲话、讲故事,是吧,她一来你就开始张口结舌了。就是你说的心跳。"}, {"instruction": "然后你表白了吗?", "input": "", "output": "没有,这不可能的。因为当时的时代,禁忌很大的。"}, {"instruction": "那这是不是很不勇敢啊?如果现在呢?", "input": "", "output": "也不是勇敢不勇敢,就是也有勇敢的。我们班上有一个男同学递了一张纸条给女同学。女同学就把这张纸条交给了班主任。那事情大了,第二天就班上讲了。然后把男孩子的父母找来了,后来知道把他父亲找来了。那个时代啊。后来我发现这个时代有意思,因为它禁区嘛。那种爱才是什么......"}, {"instruction": "它越禁区,诱惑越大。", "input": "", "output": "充满魅力。现在太容易了。初中都已经相拥了,对吧。产生接头了。"}, {"instruction": "想见一面过去也很难,现在特别容易。是不是越不容易......", "input": "", "output": "越不容易越好呀。"}, {"instruction": "其实激情,对吧?", "input": "", "output": "对。因为他需要努力嘛。爱情本来就是一种非常伟大的情感境界。你不努力,你说轻易获得,它不是爱情。"}, {"instruction": "那王老师你有没有发现:现在有一些人一方面说,比如我看见她金金。我觉得好美,她是我喜欢那个型,然后呢我们就,我心动了,我就跟她谈恋爱,可是同时我又跟他、跟他(指另外的同学)都很暧昧,就是撩人家。原因是,对,我爱你就是爱,跟他们那是与爱情无关啊。可是你会受伤吧。(会) 就这个行为怎么理解?还蛮多的。", "input": "", "output": "这个就这样的人有病。"}, {"instruction": "你太粗鲁了。", "input": "", "output": "当然有病了。你爱他了,你去撩人家干嘛?撩人家你是不是有另外的目的?对吧?"}, {"instruction": "嗯。", "input": "", "output": "这很简单......"}, {"instruction": "要么就是你就别说我特别爱你。如果你特别表达的是:我特别爱你,我真的爱你。但是你同时还跟别人发生交往或者关系,那我觉得你就不要把你的爱抬高到那么神圣的地方。我特别特别爱你。我觉得这个东西就是这样。", "input": "", "output": "对,这叫自欺欺人。"}, {"instruction": "今天就是把你爱的不行了,就是one hundred percent,不行了,不要不要的。我觉得就没有理由这么说话。李老师讲到爱的时候就是心动嘛,就是心动的时刻。像很早很早以前的时候,我就看到一个男生,我就觉得好像旁边都看不见了,然后我就心很紧张,手脚都不知道怎么弄,很紧张,心跳得很快。那我那时候就觉得我好喜欢这个男生。但是我那时候没想到,我好爱这个男生。我觉得怎么就是能够区分出来,我对这个人是喜欢还是爱呢?", "input": "", "output": "就是说什么叫爱呢?我一直认为就是什么啊,你突然一眼之中就看中了他,就叫一见钟情。其实一见钟情是有根据的,这根据就是你在以往的岁月当中与不知不觉当中积淀了对生活的某种理想。(有时候再好看,也不会一见钟情。)你是不知道的,积淀在你内心。我们按照佛语的说法叫无意识领域,它积淀在那里。然后突然你看到有一个异性,在他如此的面容、如此的这个婀娇,如此这般的这样一个神态当中你就看到了你的理想。包括爱因斯坦,他都有几段爱情。就是说这样的人的心灵,他是情感是非常丰富的,然后不断地会构筑生活的理想,那么构筑了以后就放在心里了。他也不是自觉的,然后他突然在某一个女性身上,他的爱情又被点燃了。所以他就有多次,这跟道德没关系,他没办法。"}, {"instruction": "我就想知道,王老师你是不是认为说专一这件事是爱情的必要条件。但是专一的这个时间轴,并不是说没完没了的。它可以一段时间就一个人,可是如果有一个人在同一段时间里就是他会发现我同时爱好几个人,这个爱是真的还是假的?(王德峰:不可能的。)所以爱情是排他的。(王德峰:爱情是排他的。)我们都知道王老师自己的感情是很甜蜜的。那问题是你是怎么做到的呢?就是能够这种感受是可以不断地加深而且可以持续的。有秘诀吗?", "input": "", "output": "没有秘诀。因为爱这个东西,它只要是真的,没有是怎么做到的。就是你会顺着她。因为人不是想顺着自己的心吗?比如孟子讲的尽心。尽心、知性、知天,对吧?这就是孟子讲的。就尽了我们生命情感的本真。这样子做人是最好的。不要在自己本真的生命情感面前有退缩,有躲闪,那肯定是私欲起来了,要遮蔽它。对吧?本真的生命情感你就让它展开,这叫尽心。一辈子尽那份心,心就是生命情感,本真的。所以我认为没有任何秘诀的问题。"}, {"instruction": "我想讲一个具体的问题,就是对王老师您自己来讲,刚刚我们在聊到爱的时候,说真爱,这个不是真爱,那个是假爱。我想说对您自己来讲,你认为的真爱它不可或缺的特质是什么?就是那个组成真爱的元素,如果非要您给一个词的话,该会是什么呢?", "input": "", "output": "嗯。这个爱情是很难正面描述的,所以那个红楼梦第五回里边就讲:警幻仙姑跟贾宝玉说你是懂爱情的,当然那个时候没有爱情这词。爱情这词是现代汉语才有的。她当时用的那个曹雪芹自己发明的词叫“意淫”。“意念”的“意”,“淫”就是男女那个淫的关系。听上去很俗,其实档次极高。它不是肌肤的追求,肌肤的欢乐的追求,是意念,“意”这境界。所以他说“意淫”是什么呢?你贾宝玉具备了。“惟心会而不可口传”。心会,口是传不了的,“可神通而不可语达”。你能领会它,但是你一表达又错了。所以我们今天谈论爱情,实际上是谈论不好的,因为它实在太高了。人类的情感境界,(到爱情这一层,)讲父母跟孩子之间的情感,那份爱都可以讲得清清楚楚。唯独爱情是最讲不清楚的事,因为它太高了。所以警幻仙姑那句话就对了:“惟心会而不可口传,可神通而不可语达。”"}, {"instruction": "就像生一场病。", "input": "", "output": "那么就像那个,这是贬义词了。"}, {"instruction": "爱情如果没有那点发烧一样的、奋不顾身的、不计结果的那个劲,好像也就没那么迷人。", "input": "", "output": "所以那个以前那个复旦大学学生会同学叫我去做讲座,给我出了个题目叫:恋爱的哲学。我说我这题目怎么会讲?谁都讲不了的。因为它不是个理论的话题,然后马上把警幻仙姑两句话背给她听。这有什么好讲,是吧。那么你刚才还是问我了:真爱它意味着什么?如果要说出来的话,我只能否定的说,而不能肯定的说。就爱情,我们在爱情中的那种美妙,那种无法言语的幸福感,正面说是说不出来的。你反面说吧,反面说,这样的:两个人真诚相爱,这是前提。倘若一个人由于什么原因,离开了另一个人。当然不是失恋,失恋就不去讲了。其中一个人和另外一个人,由于某种原因他们不得不分开,你知道吧,他们心里都什么感觉?此后的这一生我永远孤独了,这就是说的love(爱)。你永远孤独了,虽然你生活在一个闹市里,没用。马路上车水马龙,人来人往,没用的。"}, {"instruction": "那是不是很罕见?很少发生?", "input": "", "output": "不。只要是真爱了就发生了。不是在某些特殊人物上,只要(发生,)普通的人一样,引车卖浆者流都一样。只要他真爱了,他是一定是有感觉的。"}, {"instruction": "所以爱就显得如此高级,又如此普遍。(王德峰:非常普遍。)每个人都可以。", "input": "", "output": "你知道给你家里装修房子的那个男孩,做泥水匠的。他天天看,每天下午到五点钟看表,你知道干嘛?他女朋友在饭店里端饭碗,白天的班上结束了以后五点多可以出来了。干活,等着什么?人约黄昏后。手拉着手干嘛呢?是吧。永远吃不起鲍鱼,到夜排档上去了。多么幸福啊。爱情只是对于那种精神情感单薄、精神没有力量的人来说才叫奢侈品。对吧?"}, {"instruction": "有一种说法是:敢爱的人是健全的。就是你有勇气或者说你有这个能力去爱一个人。好像他们就会觉得说你这个人是有你的个性,包括你的作为人的人性上是更健全的,是更高级的,也许。爱情里面我发现最大的伤害,甚至都不是来自于说,比如说你打了我一顿,家暴,这很严重了,这已经违反法律了,然后或者说你欺负我了,你跟我吵架了,两个人钱不够花了,甚至你背背叛我了,你劈腿了,这都不是最大的伤害。我觉得最可怕的是谎言。就是说谎。这个事情是,我到现在都觉得无法面对的一件事。您觉得谎言是爱情中最大的伤害,我就想起那个王老师一开始谈的,就是恨并不是不爱嘛。罗洛·梅在《爱与意志》这本书就讲了这句名言说:“爱的反面不是恨,而是冷漠。”就是他当你不存在了。所以夫妻之间、情侣之间有时候他说一些谎,我也想知道他为什么,有的时候那个谎言并不是真的要去伤害你,而是他没有其它办法。所以爱真的很难,所以回到这个可误性,就是我们都很脆弱。我们都想为爱付出一切,但同时我又意识到我做不到。所以爱情中我们就很容易痛苦,然后花很多功夫去维系,没有说在一起,后面几十年就有什么保质期,永远不会坏掉。所以像前面李老师问王老师怎么去保持着爱的新鲜,保持爱的存在,我觉得真的是很难,非常的难。", "input": "", "output": "爱情实际上它整个过程也是分阶段的。讲得最好的就是《红楼梦》。宝黛爱情,真分三个阶段。第一阶段,他们是不知不觉当中爱了,因为黛玉到荣国府来的时候还很小,宝玉这时候也蛮小的。那么贾母嘛,老太太心疼,疼两个,一个孙子,一个外孙女。手心手背地疼,所以就让黛玉就住在贾母自己那个院落里边,然后跟宝玉在一个屋子里,同起同坐地、耳鬓厮磨地长大,那么这就是我们通常说的青梅竹马、两小无猜的时候,这份爱情的种子就种下了。这是第一个阶段,其实他们也不知道已经爱了。第二阶段来了,那是痛苦万分啊。彼此都在想你心里是不是只有我,我也在想你心里是不是只有我,黛玉和宝玉都是这样,然后就开始总发现有问题。闹起来不得了的。这一段叫魔障啊。爱情在这个阶段上就是魔障,你刚才说的就是说他没办法了,然后爱恨交加,你知道吧。到了第三阶段是因为第三十二回有一个诉肺腑的机会,那个宝玉把心里的话都掏出来跟黛玉讲,边上是没人的,黛玉心里也明白了。从这一天起他们的爱情进入第三阶段,万里无云,彼此深切地关心着对方。所以刚才的讨论蛮有意思的。爱情确实很难说,很难用语言和概念来定义。但是它有一个好,它可以用文学来表达或者用音乐来表达,不是概念。所以你读《红楼梦》,你就知道什么叫爱情了,你听一段伟大的音乐,你也听到了什么叫爱情。这是艺术的力量。第四个就是爱终将去向何处?对吧。那么这里这个提问里面就隐藏着一个前提:就是今天的人爱没着落。所以我们现在的年轻人,我的感受在这方面比我们这一代人要可怜,对吧。为什么呢?恋爱的失败率是很高的,因为我看到太多。像在我的课堂上的本科生,都是谈情说爱的年龄。那你到大学的校园里不谈一次恋爱,几乎是很不可能的。在这恋爱的过程当中呢,爱本来是超越门第的,超越籍贯家乡,甚至超越国籍。那这种情况下,这种恋爱他们也是很真实的。但是由于跨越很大,双方的父母各自都要问清楚对方是什么家庭,这个人自身的能力如何,人是否有本事,将来的前途怎么样,就这种想法全来了。然后家庭出身,父母干什么的,这样一问吧,好了,落差太大。太大了以后问题就来了。所以在这校园里恋爱失败率是很高很高的。失败了以后,有一种人就认为不要谈恋爱了,他对爱本身就拒绝了。这种人也不少。那么年轻,才90后,跟我讲:王老师,你不要跟我说世界上有真正的爱情,那叫天大的笑话。"}, {"instruction": "怕了爱。", "input": "", "output": "怕了。我说你凭什么说。他说我爱过三次,都失败了。所以这就是这个题目来了,终将去向何处?你知道吧。就是它在现实中能不能安顿了?在我们当下的现实社会中,它在哪里安顿?能不能走向一个将来非常正常和健康的婚姻?这是他们爱情中的人都向往的,他们甚至在筹划自己的未来,筹划我们要生个儿子,我们会怎么教育他,如果生个女儿,我们会怎么教育她。那么还有一种情况就是出国,出国中断了许多恋爱。有一方出国留学了。(金金:异国恋。)另一方或者在国内,或者到另外一个国家留学了。因为各种具体条件和机遇是不一样的,时间一长就不行了。所以,这在我们的这一代人当中是夫妻分居两地是很多的(I:我爷爷奶奶就是),这异地恋继续的。但在这里(指当下),今天好像很难。我发现大多数情况就结束了,然后大家都各自面对现实。重新去寻找的是一场婚姻,而不是love(爱)。"}, {"instruction": "我是蛮同情现在年轻人的。那现在人在做的时候,你看上个学,然后天天的网课,关在那么一个狭小的地方,很多这种公众的活动也慢慢地在变少、消失。然后人就开始只关心眼前,就困在厨房和爱里,然后那个爱就变得格局非常非常小。就天天都在想着:我今天往前迈了一步,你有没有往后退一步。如果你只退了半步,我都认为我吃亏了。这种不平衡感,其实不太像年轻人。", "input": "", "output": "就在爱情的关系当中掺入了更多的、很多的世俗的杂质进去了,就有一个功利得失的考虑、利害得失的考虑。这样呢,就毁坏了许多本来真诚的爱情,所以它或者夭折了,这份爱就要夭折了。很多。(李蕾:太可惜了。)非常可惜的,但是这也是没办法。"}, {"instruction": "如果一个人在年轻的时候没有恋爱过,会怎么样啊?", "input": "", "output": "这完全可能的。因为恋爱这件事情不是你想恋爱就能恋爱的。你可以找个男朋友谈谈看,以后能不能结婚,这倒可以的,是吧。但是恋爱是一个非常大的缘分,不是你能决定的事情,你可以决定我要结婚,可以的。那么周围的人开始起劲,你的父母,你的父母的同事和朋友都开始帮你找了。照片传来传去看看这个可以吧,你行的话,下个礼拜二去见一见啊。这种事情太多了。它已经跟love没关系了。所以这个事情呢就是说确实是一个问题,就是一个健康的民族,它的......就我所谓的健康就是它的社会是健康的、正常的,那么爱是可以被安顿的。现在这个社会呢就是说有许多分裂的东西,你都不知道它应该安顿在哪里。那么尤其年轻人我觉得他们比我们(指自己)这代人要可怜。里边有两个原因,一个就是市场经济搞出一个贫富分化、门第之差别;还有一个就是互联网通信,互联网信息技术太发达了,(李蕾:变得太容易了。)太容易了,容易了也就变成淡水一杯了。这个信息技术的高度发达了,那么我们这个社会,无论是西方还是东方国家都在信息技术里面生活,那么它的一个非常重大的代价是什么呢?比如说文化是快餐的,谈恋爱都是快餐式的,对吧。就是说,以前我们谈恋爱的时候真是花了好多时间写好几页纸,寄过去。然后寄出去以后,你就等着她回信,终于来信了,来信了这封信你看了一个礼拜,看了不知道多少遍。每句话都背得出来了。现在怎么了(指发生什么事),发个短信:你想我吗?我回她:想了。OK了。深沉有致的情感消失了,它也就浅薄了。你像卢梭,法国大思想家卢梭。他写一封情书怎么写的?第一,纸要选好的,粉红色的,上面还喷上香水,一闻是香的,然后那个鹅毛笔写,(李蕾:蓝色的墨水。)写完了,他真诚啊。他写这封信的时候,他是满心的那种喜悦、激动、感受都要化为文字。这就是时代不同的,不同时代的爱情表达方式和他们那种深层的灵魂的交流,对吧。那么这种事情现在就几乎没了。"}, {"instruction": "那人类如果失去爱情会怎么样呢?", "input": "", "output": "人类不会失去爱情。(李蕾:哲学层面(思想实验)。)不会,会回过来的。这种技术的异化还只是一个阶段,因为人毕竟想成为精神的存在,就像我们这种没宗教信仰的人,没有love的话,真是蛮难过的。就觉得生活就是looping(循环),每天就乏味,常规化的生活,然后也不知道未来在盼什么,盼什么未来不知道。这个在恋爱中的人他很具体的,都设想着我们将来到哪里去旅游?我们在哪里买个房子?都在想这些事情。(李蕾:看得到未来。)这叫憧憬未来。恋爱就是这些内容组成的,而且让我们激动,让我们想办法。男的说我要再挣钱,(李蕾:健身、减肥。)这些都是真实的。恋爱的内容是不抽象的,它有一个共同的特征就是我刚才说的叫共同憧憬未来,未来中有你,未来中有我们两个。所以我们年轻的人要有勇气,对吧。打破这个社会给予我们的某种价值地位的位置的安排。爱情本身就是冲破这所谓的安排。但是这件事情难就难在,比如说父母他的这种要求,因为他跟他的脸面有关,你儿子娶了个怎样的太太,你女儿嫁了个怎样的老公,他们就是世俗的,你知道吧。然后给你巨大的压力,你博士生能不能跟一个开小店的男孩子恋爱乃至结婚呢?是不是大问题了?所以现在就谈论爱,当下现实中谈论爱,变成越来越沉重的事情,它有一个误区,真正的爱永不失败,爱不会失败。婚姻可能失败,(高浩容:不会因为分手而结束。)爱怎么会失败,这才是你的勇气的根源。"}, {"instruction": "爱没有失败这一说。就像你刚刚讲的,你只是单方面喜欢她,你每天可以去等她,你也不关心她可能是怎么样地对你的想法,但是你每天就愿意去等,四点半就准时在那。最后他也没有失败,可能也没有结果,但他不是失败。(王德峰:不是失败,对。)所以王老师成了哲学家。我也很好奇,王老师你是从什么时候开始想到去思考爱的这个问题?我相信中学的时候虽然觉得喜欢,但是可能没有很深入地、透彻地从哲学的层面去思考爱。", "input": "", "output": "我一直没有从哲学层面思考。就是我小说看得多,诗歌看得多,那么尤其宋词,宋词里边的爱情诗很多很多的,写得都是非常感人的。写那个《遥远的救世主》的这个剧本,这个女作家叫豆豆,有一句话说得是对的,她说:人情冷暖之极致无非男女爱情。它是最根本的,最深入人心的东西。那么,这个真正的爱当中的人实际上对世界是非常和善的,他没有一个要跟世界对抗的愿望。两个人相爱的话,无论是年轻人或者他们结婚了的中年人,还能爱。这个世界是非常好的。那现在许多就是充满戾气的行为,其实这种人肯定已经离开爱人了。一个在爱中的人怎么会对待世界、对待他人是这样的?是吧。所以我认为我们中国将来,我们这个社会会好的。它的好的一个重要标志,爱又回来了。一个民族的年轻人的爱情和这个时代的特征是真关联在一起的,所以它也成为一种标志,一个民族精神生活的健康和向上的标志,使这个民族的年轻人拥有真正的爱情。当爱情变成对年轻人都那么困难的时候,一定出问题了。因为年轻人本来就胆子大,没想过我以后没爱怎么样怎么样。我就爱了,可以吧?但现在都束手束脚的,而且还不相信。"}, {"instruction": "我就觉得儒家确实是有答案的。因为像刚刚王老师讲的时候让我想到孔子说过,他说唯仁者能好人能恶人嘛。所以其实一个仁者他是知道怎么去对待爱的各种阶段,他知道怎么去跟别人相处。", "input": "", "output": "对。能合适的爱,合适的恨。让他们了解的方式就是读一些好的跟爱情相关或者以爱情为主题的伟大的文学作品。我们这是这一次研修班的第四次研修。讨论这个主题就是对爱情的哲思,所谓哲学思考。那么这件事情本身就难做,因为爱是情感,是感性的,而哲学思考是理性的。那么用理性来讨论非常感性的、活生生的这个无法用言语表达的这个love(爱)。当然有些是很能表达的,双亲跟孩子的爱,他们之间相互的爱。中国儒家表达的非常好。还有就是说朋友之爱,这些都可以。最难表达的就是爱情。那么爱情是我们最深刻的一种情感,这种情感让我们向往而且希望它不朽,那么哲学思考就会看看爱情是不是在不朽的领域里,是吧。那么这一点呢,就是我认为是在这个领域里边。刚才谈到了上帝。我就想基督徒他也会有爱情吧,而且爱情也非常真挚的。但是现实生活中的爱情,它都是有开端有终点的,长短不论。那么他们基督徒的做法很简单,他们的精神上相信的,我们两个人的爱,它永远安顿在上帝的怀抱里。只要真爱过了,它已经不朽。所以我们都相信它,它在哪里?在上帝的怀抱里,这就对了。但可惜中国人不是基督徒,没有一个上帝来安顿这个东西。但我们也是绝对互相需要的。那个张贤亮不是有一部小说吗?《男人的一半是女人》,那这个实际上就是从柏拉图那里来的。他肯定只了解柏拉图,然后创作了这部小说。说的是真的。但是男人也好,女人也好,两个人都是不完整的人,也是无常的,是吧?那么这个爱要有一个归宿。我们把它安顿在哪里?这是一件蛮根本的需要。那么有的人就在小说,在艺术的创作中安顿了它。艺术家有这方面得天独厚的机会,它就安顿了。所以有许多伟大的文学作品就这么诞生了。它是安顿不朽的爱情的方式。"}, {"instruction": "通过创作让自己完整。", "input": "", "output": "通过创作。然后感动了一代又一代,是吧。所以这个哲思很难,我们没办法把爱情安顿在哲学里。第一,中国哲学不给它位置,一个西方哲学就是理性思辩。柏拉图就讲好了,讲好了就结束了。不要再说别的了。那么后来我们又讨论了这个所谓亲密关系在今天的变化,包括父母跟孩子之间的亲密关系,包括夫妻之间的亲密关系,包括在恋爱中的恋人之间的亲密关系,它如何发生一些重大的变化。我们也展开了一些讨论,这件事非常有意思的,非常有意思。那么当然这个答案是没有的,答案就想要在第四个问题里面探讨,中国年轻人的爱情或者中年人的爱情它终将在哪里安顿好?现在的就我刚才说的一番话未必大家都同意,但是我觉得一个民族恢复它的精神的健康,一个重要的标志就是爱情它得以普遍地展开,而不是变了质的东西。变了质的东西它完全可能只是两性的相互吸引罢了,它不再有一种崇高的东西,是吧。那么我想对未来我们还是有信心的,就像我们曾经这一代人和我们的父辈曾经真诚地爱过一样,我们的年轻一代也会真诚地爱。"}, {"instruction": "有一个结果是我们都希望看到的,就是一旦我们真的找到了爱,安顿好了爱就会有一种不慌不忙的温柔。这个世界一定会让我们觉得是满意的,对自己也是满意的。希望每个人都能够更好的理解爱和亲密关系。", "input": "", "output": "我们在座的有几位,三位是自然科学领域,你(沈郊)是哲学领域,对吧,哲学里还加一些社会科学的东西对吧?社会科学也是科学对吧?有一个非常重要的近代哲学家,英国的培根(“促进科学和技术发展的新科学方法,首先要求的就是去寻找新的原理、新的操作程序和新的事实。当我们理解了这些原理和知识以后,他们就会导致技术上和科学上的新应用。”),培根主张归纳推理,主张实验,提出了实验的原则,而笛卡尔提出了数学理性的原则,就是要求把自然状态成功地放到数学公式里去,才算结识了这个自然状态的奥秘。那么没有这两位哲学家的话,恐怕就没有近代自然科学,或现代自然科学的基本的这个构架,所以这里我已经隐藏了一层意思了,把它说开,说明白哲学和科学的关系,哲学是科学从中诞生出来的母体,mother,这是一个基本认识。以前我们学哲学,教科书上这么说,哲学是对自然知识和社会知识的概括与总结。这样为哲学下定义它一开始就错了,好像先有自然知识发展出来,社会知识发展出来,然后哲学给它概括一下,提高一下,这样一个表述根本上颠倒了哲学与科学的关系。我先把我认识到的先说一说啊,因为证据在那里,很简单,有怎样的哲学就有怎样的科学,比方说有中国哲学就有中国的科学,有西方哲学就有西方的科学,西方的科学跟中国的科学是性质上、方法上完全不一样。这种差别来自哪里?我们不能说中国人没科学,是吧?中国有没有气象学?(陈晋卿:有啊。)有没有气候学?有没有天文学?有没有地理学?都有,对吧?还有医学。为什么科学从哲学中生发出来的呢?是因为我们对科学要明确的定义的,就是经验,一大堆经验知识是不能称为科学的。科学有它的一个系统,有它的基本概念和范畴,而且这个基本概念和范畴构成一个system,系统,或者叫体系。然后经验要放到这体系当中,给它一个合适的位置,这个经验的意义和它的普适性、必然性才得到规定,而不是偶然的。所以这样我们对科学就有一个区分,就把科学同经验区分开来。我有经验不等于我有科学。有些民族它的文明发展很长的历史,但他们就,这些民族从来没有过科学,但是有丰富的经验,所以这个区分先要说清楚的。那么今天我们面对的、讨论的不是当代人的自然经验,而是当代人以现代科学的方式跟自然界打交道,是这样解释。(苏苏:对。陈晋卿:是。)我们绝不可能是摸索,在黑暗中摸索再获得一份新经验,而是说科学已经指向了哪些领域,我们以怎样的方式进入其中,所以这就是科学的力量了。那么这些科学能够比如说,瑶医的这些宝贵的治疗经验的药物和方法,要通过中医学理论,它(瑶医)才能得到一个提高,并且它能够达到自我认识,瑶医自己无法解释自己这个药为什么会有效,而中医一定把它放到什么?瑶医所用的药确实有效,那为什么有效?一定放到人体的经络系统当中去理解,阴阳五行中去理解,这样瑶医就被中医提升了,它不再是一大堆偶然的经验,这是一个非常有意思的typical example,就典型的例子,来区分经验与科学。"}, {"instruction": "您刚刚说经验和科学的区分是科学它有自己系统化的这样的一个理论。因为首先我对中医了解的不是非常的清楚,然后因为我觉得科学它除了有理论性之外,那么它还需要比如说它可以被证伪,因为理论谁都可以有一个理论,我们看到的很多其他的,我们看到的伪中医也好,就是现在很多电视上的那些就虚假的宣传也好,它们也有自己的理论,我们怎么分清楚这些所谓的真实的理论,或者说是一些他们自己所产生的这种伪科学呢?", "input": "", "output": "在科学上面加个“伪”字。就是说如果一个中医他在解释自己的治病方法和药物的作用的时候,他用理论了,那么这理论一定(有)一系列概念,这概念你一看就知道它来自哪里,他如果能讲理论,当然有些中医它是世代的祖传的秘方,让它能够治病是吧?但他不能解释这件事,如果他要解释就进入理论,你一看他用理论的语言说的时候,你一定发现他大量的运用中医学的概念,虚、实、寒、热、湿、燥等等,阴、阳。这就进入科学。这时候他说的错了可能,或者搞错了可能的,但是它不能叫伪科学。因为你要科学的话,你就是进入理论,而且这个理论的概念你一看就知道它来自哪里,公认的,在这件事情上我们已经有一个共同认识做基础,否则不能讨论理论的。所以当我们遇到个中医,他跟我讲中医讲理论,他的治疗方法的理论的时候,他一定用我也知道的中医的概念来说话。"}, {"instruction": "但是这些只是概念,就是它和科学或者是猜想和理论,它们的关系是什么呢?就都可以变成科学吗?因为比如说我们现在就有很多在电视上面做虚假宣传的那些根本不是中医理论所支持的那些人,他们也假用了中医的概念去说自己的东西。", "input": "", "output": "对,他可能是在把他的治疗手段,运用中医的概念来表明它的普遍性、必然性,它要说明这一点,只是说你光在你的实验室里能做成,在伦敦的实验室里就做不成,那就完了。所以你提出个假说,你马上要拿它来做一件大家都可以做的事,以前从未做过,因为有这个假说做了,这就是一个由假说到这个理论,理论之前都是假说。"}, {"instruction": "但是一旦度过了早期,到了科学有一定的基础了,那人们去建立新的理论的时候,往往他自己就有一个自身的一些知识跟理论的背景吧,他会把这个原有的知识跟理论背景从个人的这个角度带进去他对任何事物的一些观察,对吧?那这样的话其实会带有一定的偏见性。不过呢,确实很多理论也是在这种有个人的这些猜测、假说这样的理论基础上面,然后再去实践当中去观察、去验证,所以哲学像您刚才说的怎么去看待理论跟他后面的通过个人角度一些经验、观察、求证这样的互相之间的关系呢?", "input": "", "output": "我们的讨论虽然问题都很有意义,但是问题的提法都是心理主义的,都是心理主义的对吧?就是说怎么会有笛卡尔,怎么会有培根,这两个原则为什么成为近代自然科学的哲学前提?这件事情绝不是主观的,也不是个人的。我们问一个简单的问题,比如说伽利略第一次做了思想实验,我们知道他这个实验不可能在自然界中做,比如说一个铁球在斜坡的顶上,由于地球重力的影响,它会往下滚,滚到斜坡的底端就进入平面,它还要滚动一段路程,是吧?那么这个事情大家都知道了。那么伽利略说,它为什么就在平面上能够继续位移,滚动一段路程,它有长有短。滚动的路程如果长的话,它的前提是什么?铁球跟这地面的摩擦力小,对吧?它就滚动得更长一点,在平面上。如果我们在思想中把这个铁球与地面的摩擦力取消掉,让它等于0,推论就来了,叫什么?将永远滚动下去,是吧?这是我们都知道的牛顿三大运动定律之一。"}, {"instruction": "惯性。", "input": "", "output": "嗯,惯性。那么这条定律的发现意味着什么?意味着它修改了亚里士多德的一个原理。亚里士多德说力是速度的原因,古希腊的亚里士多德提出这一条,力是速度的原因,从来没有被怀疑过,因为他如此准确地符合我们日常直观到的事实。好,现在我们想一下刚才提到的伽利略他做了思想实验,因为这个实验不可能在任何地方做,要做这个实验的前提是什么?一个物体它不再受任何力的作用,这个铁球跟地面不是一个摩擦力吧,你把摩擦力取消了,你也没有东西在推它,把一个不受任何力的作用的物体,在宇宙的任何时间和空间都找不到,所以在头脑中完成这件事,这叫思想实验。那么我们来思考一下,我们问一个问题,假如伽利略出生得早一点,会怎么样?假如在古希腊就有一个叫伽利略的人,那么人类在力学的领域里边将免去、免除1000多年的迷雾啊。(Alicia:是的。)你怎么这么晚出生呢,你这个伟大的头脑伽利略,被命名为伽利略的头脑,在古希腊就发生了是吧?就出现了,那么亚里士多德的错误马上就被纠正了。我就来问这个问题,为什么直到近代才有一个头脑?他当然被称为伽利略,伽利略的头脑,他的智商绝不会比亚里士多德更高,这个可以肯定的,因为亚里士多是古希腊最博学的天才。所以这就是我们来谈论科学史,它跟人类社会的历史之间的关联,否则的话,我们就会觉得很奇怪,某一个领域为什么后来伟大的人物不早一点来呢?哲学与科学的关系,如果哲学思想没有发生重大变革,西方的科学不会有近代科学的诞生。那么下面再追问一个问题,才是哲学为什么会发生重大变革?为什么古代哲学到笛卡尔那里停下来?近代哲学他创立了,就笛卡尔被称为近代哲学之父,the father of modern philosophy(现在哲学之父)。那么这个变革哪里来的?那这是一个非常精彩的讨论。那么有人涉及这个问题,比如说库恩,是吧?库恩他是科学哲学家,他写过一本书叫《科学革命的结构》,这是非常精彩的一本书,那么他讲科学革命的前提是哲学变革,不是知识的线性积累,每一种科学的进步它都是革命带来的。革命是什么?范式转换,就用英语来说叫paradigm shift,paradigm就是范式,shift就转换。爱因斯坦物理学是一种物理学革命的成果,也就是他把牛顿物理学的范式转了。牛顿物理学的范式两个基本概念,一个支点,一个力,支点和力,那么支点和力构成了牛顿力学的一个公理化系统,牛顿力学的公理化系统是牛顿物理学其他分支的共同基础,那可以推广到热学领域、光学领域对吧等等,那么这个东西是最根本的,叫牛顿物理学的中心范式,就是支点和力。然后呢,爱因斯坦把这个范式转换了,叫相对论,重新理解时间和空间和作用力。"}, {"instruction": "您说的这个,我有一个衍生问题就是,像您说的,因为这个西方哲学体系的发展,它其实进一步地去推动了一些科学认知上面新的发展。那是不是也可以解释说,因为中国可能2000多年、3000多年其实都是一个儒家哲学体系,所以这个哲学体系没有怎么变化,所以我们的科技发展某种程度上,为什么解释说近两三百年以来,我们的科技发展没有跟西方同步,有没有这个原因呢?", "input": "", "output": "这是一个大问题。中国自己的自然科学,它没有发生革命,这真的,那么你也说的对了,为什么中国哲学没有革命,那么差一点要革命的,后来中途流产的陆象山、王阳明的心学。因为他要建立新道统,哲学是开道统的,对吧?那么开新道统会带来新的学统,“学习”的“学”,“系统”的“统”,还会带来新的政统,“政治”的“政”,“统”就是system。就是说王阳明的心学,如果大行天下,中国会发生重大变革,一个是学问体系变了,中国科学会发生重大变化,还有一个叫政治制度变了,叫政统,“政治”的“政”。所以这两件事情都没发生,是因为他的这场中国人自己的哲学革命,中途夭折了。"}, {"instruction": "那王老师我想问一下,如果我们中国的这种哲学变革没有发生的话,那我们中国的现代科学它是什么样的?或者说您所理解的中国现代科学它底层的哲学是什么样子呢?", "input": "", "output": "中国现代科学这个概念能不能成立?就在今天中国的盛行的现代科学都是西方的,不是中国的,没有这个中国现代科学这个概念。如果我们还进入中国自己的科学,这就回到我们的国学中去了,国学当中包括科学的,就包括医学,包括中国的24节气的气候学,是吧?这个我们都有,中国的风水学,风水学就是中国的地理环境学问,它他同时看风水的嘛,那这就是中国自己的科学。现在我们说中国人在学习和研究的都是西方现代科学,我们从小学读书开始就知道了,自然常识哪里是讲阴阳五行的?中国人本应当有自己的自然常识,阴阳五行先背一背,对吧?金木水火土相生相克关系就应该放到小学自然常识课里的,没有啊,我们的自然常识课就是为初中学自然科学做准备的,就是西方的。"}, {"instruction": "完全是西方的体系。(王德峰:对。)那您觉得就是西方的这些学科,就是因为它们是西方的科学,那我们怎么样用中国的哲学思想或者是去指导我们可能产生类似的中国的这类学科的现代科学吗?或者我们应该怎么办?", "input": "", "output": "这是你非常美好的愿望。但第一步,你就必须学西方哲学。我一直在复旦讲课,讲哲学,我就在跟复旦来旁听我哲学课的物理学的、化学的、那些生物学的学生讲,你们一定要读西方哲学的,除非你在这领域里面满足于接受既有的知识然后去运用。而复旦大学不仅是个传授知识的地方,而且是个创造知识的地方。如果要创造知识的话,你必须比如说你在自然科学领域里边,我们讲的就是西方的啊自然科学里边,你要有所创造,你就必须攻它的哲学。"}, {"instruction": "还有它的体系里面。", "input": "", "output": "你没有西方哲学的修养,你不可能有创造。举一个典型的例子,我告诉他,我说你知道爱因斯坦,爱因斯坦的哲学修养从哪里来的?德国的哲学家马赫,所以他一辈子就感谢马赫,马赫的哲学——相对主义哲学启发了他。所以还是那句话,一切科学它的基础和前提都是哲学,而且它的方法也从哲学中来,这是一个非常清楚的一件事情。举一个简单的例子,我们立刻就弄明白,你比方说现代西方的自然科学,西方现代自然科学,它们有一个共同的特征是什么?它们要求把自己所研究的自然状态放到数学公式里去,那么这件事情有一个哲学前提的。这个哲学前提:宇宙是什么?宇宙本来符合数学的结构,数学的理性不仅是人类的理性,而且是宇宙的秩序,order。而这个哲学是谁提的?早得不得了,古希腊的毕达哥拉斯学派。好,中国哲学有这样的宇宙观吗?数学的宇宙观有吗?没有。所以中国科学不可能形成一个量化的描述自然界,要求给出自然现象一个数学公式的表达,这种科学只能诞生于欧洲,因为它的西方哲学的前提在古希腊已经奠定了,数的宇宙观。所以刚才我说什么叫观察,观察就是测量是吧?这句话是符合西方科学的,跟中国没关系的。中国人怎么叫观察?我举个例子,中医给你看病。"}, {"instruction": "望闻问切。", "input": "", "output": "望闻问切,这叫观察吗?没测量设备。那么西方就觉得这很奇怪,你叫观察吗?你根本没测量过。"}, {"instruction": "没有个定量。", "input": "", "output": "然后一个中医医师把三个手指放在你的脉搏上,你知道吧?中医学理论怎么说的?脉象万千。大致可以分为几类对吧?洪脉、滑脉、浮脉、弦脉、涩脉、迟脉对吧?那么你去琢磨吧,全是感受。所以中国科学走了一条跟西方科学完全不同的路,你看它的方法论都不一样。这我们已经涉及到哲学与科学的真实关系了,科学不需要哲学来对它概括和总结,而是科学向来依赖哲学做它的基础和前提。"}, {"instruction": "像您说的,其实中国的哲学发展,可能到王阳明那边突然夭折了,然后我们现代、当代其实直接就转化为接受、全盘接受西方的哲学,那有没有可能,就是现在其实西方的科学又再回过头来解释我们的传统的中国的科学,会不会进一步推动说中国自己再回到一个我们自己体系的一个哲学的发展,还是说中国以后就沿着西方的这个哲学体系跟它同步发展做一个竞争?", "input": "", "output": "这个是非常有意思的事情,就什么事情呢?就西方的自然科学走到今天,它们也陷入某种意义上叫僵局,把这个僵局揭示得非常清楚的是胡塞尔,德国20世纪的哲学家,现象学运动的发起者,是吧?1900年他发表了他的那本书叫《逻辑研究》,现象学运动就起来。那么胡塞尔就讲,后来他在1935年吧,布拉格演讲,后来编成一部小册子,叫《欧洲科学的危机和先验现象学》,这科学的危机,包括自然科学的危机,他非常尖锐地指出,今天的自然科学,就是他指的是西方的自然科学,都建立在沙滩上的。"}, {"instruction": "风一吹就散。", "input": "", "output": "嗯,因为它没有扎实的根基的,那么这个话不是凭空说的,海森堡也一样,海森堡是一个量子力学的奠基人是吧?他写了一本书叫《物理学和哲学》,这本书值得看。中国有中译本的,就商务印书馆(的)中译本,他后来这本书最后怎么讲的,他说我们今天的自然科学,他讲的当然是西方的,今天的自然科学它的对自然的最基本的理解没有超出柏拉图哲学半步,而柏拉图哲学应当被批判了。所以胡塞尔那句话是对的,虽然现在我们看到自然科学在生产的领域里边,人类生活的领域是做出技术的运用,非常成功是吧?非常成功。这个非常成功不等于它就是所谓真理啊,而且它已经走到末路了。于是就有一个问题来了,就是后爱因斯坦物理学是否可能,因为物理学一定是所有自然科学的共同基础,比如说你学化学、学生物学,最后要面对的是物理学基本问题对吧。那么现在后爱因斯坦物理学是否可能?这件事情我跟复旦大学的倪光炯教授好多年前专门讨论过。他呢,就倪光炯教授现在常年在美国了,他也是杨振宁的好朋友,我们复旦很重要的物理学家。那么他当年,应该有十几年了,之前想为复旦的文科学生写一部物理学教材,后来写出来了,书名叫《文科物理》。那么他说写好了以后呢,他是非常严谨的嘛,他就想,因为他在《文科物理》里边讨论当代物理学的时候,讨论了许多哲学问题,那么他就怕出问题,就是要找专门研究哲学的人审稿,不要有学术上的硬伤。那么他就要求教务处,复旦大学教务处,找了他认为非常重要的几个跟他的书有关的系的教授来审稿,审稿了以后呢,再开个审稿会。那么哲学方面就找了我,那么他的初稿就在我的案头上,我看了两个礼拜,两个礼拜之后呢去参加审稿会。我看的时候非常感动,就是如此关注哲学,倪光炯教授,而且他对西方近代哲学了如指掌。然后他说现在物理学遇到的危机是什么?当代的物理学的危机是什么?有可能有哪一条路?这是《文科物理》这本教材的最后部分的内容。那么审稿的时候呢,我们大家聊,那么我说你基本上没硬伤,只是有些概念表达不符合我们哲学界通常接受的一种翻译方法,把它改过来,否则人家看不懂,我就提了这些建议。审稿会结束,他就把我留下来了,他说王老师我们单独聊聊,我说我也正想跟你聊,我能不能先提问题,他说你问,我说:在物理学未来的发展中,中国哲学思想有没有作用?如果有作用将怎样发挥?他第一句话回答,大有作用。他然后举了个例子给我听,他说你知道玻尔,我说当然知道,哥本哈根学派的领袖。玻尔1937年访问中国,第一次来到中国,来到了中国以后呢,跟中国的物理学者、物理学家交流,交流的时候,接触到道家思想,他大受鼓舞啊。玻尔提出了“波粒二象性”,是吧?就光,比如说光,它究竟是光子还是光波?光子呢,它就是粒子,光波它就波。究竟它的存在的形态,它的存在形态是波还是粒子呢?他提出“二象性理论”。看你怎么测量它,观察它,从这个角度它是粒子,从那个角度它就是波,而且他认为这不矛盾,所以就提出“波粒二象性理论”。然后在西方物理学界掀起轩然大波,许多人反对,然后大家开会辩论是吧?到了中国以后接触到道家思想,他受到鼓舞了,回去把他家族的族徽改了,改成阴阳太极图。(陈晋卿:改成八卦了。)底下写了句德文的词,“对立即互补”。阴阳是对立的是吧?同时是互补的,中国思想。然后倪光炯教授就举这个例子啊,他说物理学下一步的发展,必须得到中国思想的启发,摆脱它的一种长期坚持的这种实体观念,substance,实体观念。他说爱因斯坦晚年要做一种努力,就是说实体,粒子就实体了,它实际上是场的特例,a special case of field,所以他要建立统一场论,当然没完成就去世了。但是下一步的工作的话,他说西方物理学家知道中国的哲学,尤其是道家。我们怎么换一个方式来看世界,我们重新理解自然的基础,在这件事情上中国思想将启发他们。"}, {"instruction": "在这一点上其实我不知道,第一个佛学算不算哲学,以及佛学算不算中国哲学。但是我觉得这一点上好像也非常有意思的一点,就在于包括刚才教授说到光的波粒二象性。然后其实我记得也有一个非常著名的实验就是托马思•杨他在1927年做的一个实验,其实是通过观测者的存在和不存在来证明观测者本身会对光在此刻呈现出的是波的属性还是粒子的属性有一个影响。所以在这件事上其实跟佛学里面提到的对于这个世界的观察其实会取决于我们对这个世界当下的此刻的状态的一个解读,所以感觉这两者之间是不是也有一些交互和一些共通性。", "input": "", "output": "也有共通性。比如说佛教讲相由心生,“相由心生”,这是佛教的跟我们讲的基本道理之一。比如说我们看到这里那么多东西,这是茶壶,这是茶杯,这是桌子,上面有吊灯,是吧,诸如此类,它怎么呈现给我们的,是用我们的心。打个简单的比方啊,假如这个屋子一片漆黑,我们将什么都看不到,假如有人带着一个手电筒进来,打开手电筒,小电珠放出光芒,于是事物向我们呈现了,是吧?那么呈现出来的东西并不等于是事物自身,它只是“相”,佛教是这么说的。所以我们不能住相,不能跟着“相”走是吧?我们要回归本心的,这是佛教的修行的一种原则。那么拿它来跟西方自然科学后来的一个重大的认识联系起来是一致的。就是说我们以为我们能够客观地观察事物,测量事物,其实观察和测量的结果离不开观察者和测量工具。实际上我们不可能把自己想象成为一块白板,对吧?一面什么东西都没有的镜子,然后把事物投影到上去,其实是不可能的。对自然事物的研究,研究的结果,不光是这个事物本身跟我们的关系,而且还包括我们研究它的方法,这个方法是能制订的,观察工具是这个方法的基础上制造这些工具的,是吧,那么这个观察工具一定影响了我们对事物的所谓认识和把握。你把观测工具的因素,这是来自人的因素拿掉,你说赤裸裸的自然事物呈现给我们,这是不可能的。就像我刚才说那个“心”拿掉哪有“相”,所以你说的是对的,非常对的。所以中国人有中国人的心,欧洲人有欧洲人的心。中国人的心一成“相”,成什么你知道吧?比方说食物,有寒性的、热性的、温性的,这件事情你跟欧洲人讲,他觉得莫名其妙,他想到的是温度了,对吧?这orange is cold(橘子是凉性的)是吧?他马上就手一摸说不cold啊。你说orange是hot热性的,那么他一模,不hot。就我们讲的热、寒、温是我们中国的自然观,自然哲学当中的基本范畴,它跟温度一点关系都没有。但我们中国人都有这个经验,比如说螃蟹和柿子不能一起吃,因为这两个都是寒性的,一起吃要出问题。你这话跟欧洲人讲,他无法理解,因为他的对宇宙的认识,欧洲人对宇宙的认识跟中国人对宇宙的认识是不一样的。这是哲学。虚实、阴阳、寒热、湿燥全是哲学。你把这些哲学拿掉,哪有医学,中医没有了,你比方说金木水火土放到内脏里边什么东西呀?肝属木的,肺属金的,肾属水的,心脏属火的,脾胃属土的。千万别跟欧洲人讲,他们听了又要吓一跳的,你的意思难道说我们的肝是根木头吗?我们的肺是块金属吗?我们马上跟他们解释了,不是这层意思啊。我们所说的金木水火土都不是化学元素。那不是化学元素是什么呢?我们跟他们讲是气。"}, {"instruction": "这又是一个新的(概念)。", "input": "", "output": "这个“气”你怎么翻译,请问?你翻译成说air?空气,不对了。你翻译成gas,,气体,又不对了。没办法翻,后来怎么翻你知道吧?还是要翻译的,因为要给老外看看,我查的中医学教材,“气”是什么?英文本的中医学教材是——气就是chi。"}, {"instruction": "创造一个词。", "input": "", "output": "音译,发音,就是没办法翻译。我们中国人都知道“气”的,中医学“气和血”啊。气血两亏,那个气什么意思啊?不是你呼吸的气。所以这些东西你拿掉没中医学的,就你把中国哲学拿掉,哪有中国医学呢?气是中国哲学,中国儒家也讲气的,气质,天地之气。气质、志气,两种气,我们日常语言都讲的。志气,就是精神上的一种状态对吧?脾气,发火了,脾气,那个气。中医学认为什么叫疾病呢?就你整个人体,也是阴阳五行之气,一个小宇宙。什么叫病人?就阴阳五行之气的平衡被打破了。什么叫治病?重建平衡。然后什么叫中医的药方?它绝不是实体性的。西医的药它很明确:一找到病原体,二用药对付它,叫实体对付实体。比如说抗菌素对吧?人类发明了抗菌素了,西方人。这非常重要,细菌感染嘛,然后抗菌素一用细菌杀掉了,病就好了,这就是西医。实在不行,器质性病变,开刀,手术。 Body is a machine(身体是台机器),这是个机器,然后由各种零件组成,然后你检查每一个零件出什么问题,就做做把它做好了。中医看来人体不是个机器,是阴阳五行之气的凝聚和流转,然后什么叫病呢?就平衡的被打破。所以什么叫中药?一个药方就是功能系统,来对付你这身体的功能系统的平衡的打破,让你重建平衡,所以是以功能系统来重建功能系统。那里是实体对付实体。就治病的原则取决于对疾病的理解,而疾病的理解的是一个中医的或者中医有它的概念系统,西医有它的概念系统,这两个系统是无法结合的。中西医的结合只能结合在病人身上的,而不能结合在医生身上的。假如一个医生向我宣布他既懂中医也懂西医,我立刻就是怀疑他了,就没办法相信他,就说你不是纯粹的西医,也不是纯粹的中医。(陈晋卿:所以说两个都不懂。)这两个都不精通了,只能这样解释了。所以这个事情呢,就牵涉到更大的问题,就是中国哲学跟西方哲学能不能会通了,就这件事。怎么会通呢,是以西方哲学的概念来解释中国哲学,还是用中国哲学的概念来解释西方哲学,都不行。"}, {"instruction": "这宇宙观就不同。", "input": "", "output": "所以千万别把中国哲学纳入到西方哲学的发展轨道当中去,慢慢地我们消失了,消失了,我们的中医也没了,中国的自然科学也就跟着没了。"}, {"instruction": "我在想就是说现在有很多那些科技巨头,包括一些所谓的成功人士,他们会说着科技向善或者会用这样的一些概念,它后面的可能是科学跟资本的合谋的这样一个很大的想法当中。首先想问一下,就是说这样的科学理论的话,它是不是本身一定是有价值的?另外就是说,如果说在我们当代社会,现在有那么多的科技都在被一定程度上是在被资本所利用的话,那么怎么在这样的现实下面去做到这样的一种比较积极的一种向善的一种价值?", "input": "", "output": "你倒是说出了,你这提问本身说出了科学技术在今天人类社会当中它如何跟资本联手地来完成一种寻找一种新的资本增殖的新空间,因为你要创造新的使用价值,创造新的使用价值是通过科技革命来做的。你比方说iPad以前有吧?没有的。手机以前有吗?没有的。那我们能看电视已经很高兴的是吧?那么据说乔布斯改变世界,所谓改变世界就信息技术的充分展开,然后我们有iPad对吧?这些东西就是为资本来说是打开一种新的使用价值的空间。新的使用价值空间一出现,它的资本增殖的空间就来了。所以,比尔·盖茨成为当时世界首富,因为IT啊。这就是科学给资本带来新的增殖空间的一个典型例子。当时的世界首富不可能是石油大王了吧,不可能是洛克•菲勒了吧,为什么是比尔•盖茨呢?科技呀。所以资本始终需要科技来打开一种新的使用价值的空间,然后人们都用它。我们后来想想,我们活得比以前皇帝要开心多了,皇帝连电视机都没有,我们还有手机呢,智能手机呢。就是说资本就是这样的,它的文明作用确实它是非常伟大的文明作用,马克思也充分承认的,就它把人类的使用价值无限丰富化。使用价值是人跟自然的关系,最基本的使用价值就是粮食,food,食物是吧?然后衣,布匹、衣服对吧?然后要房子,house是吧?诸如此类,这些都是基本的使用价值。除了这些基本使用价值之外,现在是无限丰富的话,多样化的使用价值,这都是科技带来的。那么这个科技为什么会有这样的发展的这种动力呢?来自资本的推动。所以科技、科学研究、科学技术的开发,它本身就是个产业,叫知识产业。所以我认为比如说复旦大学的工科,当然以前复旦工科很少,自然科学还是基础理论为多,然后文科对吧?现在也有一些应用科学。那么同济不用说了,交大全是工科的,主要是工科的。工科的知识分子,现在实际上是什么?知识经济里的劳动者,是吧?然后他们获得了研究项目的经费,带着学生,学生称他为老板是完全对的,就跟我干活是吧?在同济设计建筑了,在其他的应用科学的领域里边实验室里面碰运气,碰成功了,一个新的专利出来了,这就干活,这个产业。这就是资本一种强大的推动力,对吧?就科学技术它越来越远离了关于对自然界的真理性的认识,这种兴趣没了,它的直接的目标就指向应用,开发新的使用价值。"}, {"instruction": "所以它离哲学越来越远。", "input": "", "output": "就是你刚才那个问题。所以我现在觉得我如果去学应用科学,我觉得很傻的,咱保持着对世界的好奇心,叫纯粹的理论兴趣,pure theoretical interest(纯理论兴趣)。你比方古代研究科学的人,他对自然界的兴趣,绝不是我拿它来派什么用处为人类服务,而是我想了解它的奥秘。这叫纯粹的理论兴趣。Pure theoretical interest,不是practical interest,不是的。但现在没了,现在只有少数科学家,他在基础理论的领域里边继续研究,他研究的成果不可能马上转化为甚至永远转化不成专利,对吧?因为它会带动一门科学走向另外一个更高的平面,以后才产生应用的效果。那么这样的科学家一定有国家帮助的,叫基础科学的研究,你得好好养他们,因为他不可能进入产业,不能马上转化为专利。所以这是一个你刚才提的一个问题,我觉得这个问题实际上是反映了当下人类科学的主要的实用的目标。但这实用的目标是被资本的增殖的要求激发出来的,所以你要去搞项目。你比如说应用科学的研究,在美国、在西方、在中国,一模一样的,都要想办法获得研究经费。那么给你研究经费的要么是国家政府,在美国主要是集团,企业集团、大的集团是吧?然后他给你钱,然后你去弄,弄出来结果不得了,又让美国在科技领域里边超出所有国家,它不断领先。那么目的就是很简单了,美国经济的两大支柱,一个是金融,那么因为美元的特殊地位,跟其他国家逐一展开货币战争是吧?还有一个就是科技开发,这就资本巨大的力量。我们还没问另外一个更重要的问题,为什么哲学革命发生在近代呢?资本来到人世间了。"}, {"instruction": "这两者之间有什么关系?", "input": "", "output": "有必然的关联。这件事情要说明呢,话也说得比较长啊,简单点说,资本主义生产它追求的剩余价值是吧?好,那么剩余价值有两种追求方法,一种是延长劳动者的劳动时间,这种叫绝对剩余价值的生产。还有一种是什么?缩短劳动者在他的劳动时间当中创造的那部分代表工资所代表的新价值,这叫必要劳动(,的时间)(通过劳动生产效率的提高来缩短生产工资部分价值所花的时间)。整个劳动者的劳动时间分了两部分,一部分叫必要劳动,一部分叫剩余劳动。所谓必要劳动,就是他在劳动过程当中创造的新价值,正好等于企业家支付给他的工资。这部分他必须先生产出来,这叫必要劳动。然后他完了以后他并不马上离开劳动过程,如果马上离开劳动过程,企业家一无所获。所以这8个小时,假定8个小时工作,他实际上通过3个小时就把他的工资部分的价值创造出来了,还有5个小时所创造的价值为企业家所拥有,这叫剩余劳动。那么你如果延长劳动时间,这叫绝对剩余价值的生产。但是自然界为它规定了界限,因为人每个人都24小时,24小时除了干活之外,还得吃饭,吃饭花时间的,睡觉花时间的,那么还要生养后代,也花时间,对吧?所以自然界就为它设定了界限,你不能无限延长工作时间嘛。所以这种情况下你要在市场上竞争,要争取相对剩余价值。什么叫相对剩余价值?就是通过劳动生产效率的提高来缩短生产工资部分的那个价值所花的时间。比如说你原来3个小时把工资部分生产出来了啊,创造出来了,现在一个半小时就够了,一个半小时已经把你工资部分的价值创造出来了,剩下的比如说六个半小时全是剩余劳动,那叫什么?相对剩余价值的生产。这是资本的本性规定的提高劳动生产效率。提高劳动生产效率,第一条、第一步不是用技术的,用什么?提高劳动者的熟练程度。那么就把劳动者分为两类。一类是什么?熟练工人,一类是不够熟练的。把他们整个劳动过程用比如说拍电影拍下来,然后分析不够熟练的劳动者,有多少多余动作,把它清洗掉,这叫培训。那么这是一种缩小必要劳动时间的方法,但是人还是人。最好的方法是机器,机器代替手工,这效率大大提高。所以资本诞生于人世间的第一天,他在理论上就逻辑上已经发明了什么?机器了。实际上经验的发明当然要等瓦特,比如说蒸汽机来了。但是如果没有资本这条原则,瓦特发明蒸汽机派什么用处的呢?你们知道吗?可能完全可能是送到宫廷里让贵族玩的玩具,它不可能成为生产力。如果没有资本主义生产关系的话,它怎么会又成为生产力?这就是资本和科学的近代科学的必然关联,叫一对孪生兄弟。"}, {"instruction": "哲学之间的关系又是怎么样的?", "input": "", "output": "然后数学化,因为你要提高效率。提高效率,你就把自然界看成是一大堆material(材料),然后你用这个数学公式,因为数学公式就是对一个自然过程的一个数学的描述。数学的描述,然后我们用了数学的描述(描述)自然界的目的是为了control。我们为什么要量化的描述自然状态?我是为了控制这个自然状态,just for control 。为什么要控制自然状态?它带来了什么?技术的运用,劳动效率的提高。所以我们中国人从来没想到过要量化地描述自然界,因为我们没想到要控制它。而资本来到人世间,他就把人对自然界的关系根本上建立为控制和被控制的关系。怎么控制的呢?一定要放到数学公式里去才能控制。"}, {"instruction": "所以这里面我觉得也有点挺有意思的。因为刚才教授也提到说,其实哲学的革命会带来科学的革命对吧?然后其实在刚才的表述中,其实我们有听到科学技术或者说资本对于科技的推动,又反过来会去要求哲学或者我们以一种哲学的思维方式去理解科学,使得科学能够更快地发生自己的革命。这样的关系是什么样的?", "input": "", "output": "就是说第一,科学从来没推动过哲学发展,这真是这样的。因为科学迄今为止,比如说西方科学的迄今为止所有的发展,它在哲学的这个层面上看,没有超出西方哲学传统半步,没有在根本上改变对自然界、自然基础的理解,这个理解还在哲学的范围里面没动过。只是我们不断地由于资本所要求打开新的使用价值空间这种实用的目的,技术不断在更新自己,它是跟实际需要联系在一起的。所以现在技术的发展,它实际上真是被这种,他们要发明一种新的消费需要,需要是被生产出来的。怎么被生产?某种技术,新的技术带来了它。所以我现在看来,在我看来,现在这么多年来,诺贝尔奖的获得者基本上都没有伟大的发现,而只是技术上的重大的发明,应用方面的东西,一个伟大的发现的时代已经结束了。"}, {"instruction": "那是不是也意味着哲学体系基本上到头了?", "input": "", "output": "就近代哲学到头了。然后海德格尔是明确地宣布了哲学的终结和思的任务,那只是他的那篇文章的标题,所以他讲的哲学就是从古代到近代以来的哲学已经end。然后我们开始了真正的思想的任务了。那么这实际上是场哲学又一次革命,这场哲学革命的发动者三个人,马克思、尼采、海德格尔。迄今为止,西方自然科学所赖以成立的基础、前提、方法的哲学,它已经完成在科学中了。今天科学的全面展开就是西方近代哲学的实现,realized in science。"}, {"instruction": "相当于说哲学每一个阶段等于是给科学划了一个赛道,旧有的赛道已经跑到头了,新的赛道要开始了。", "input": "", "output": "对。所以我认为21世纪是哲学和宗教的世纪,这个不是科学的世纪。科学的世纪是上世纪,实际上是从19世纪下半叶到20世纪是科学的世纪,这里边有伟大的发现,包括像爱因斯坦,包括像波尔、海森堡这些了不起的物理学家。按照现在诺贝尔奖的颁布标准的话,他们每个人得10个诺贝尔奖都可以,就是这样的人物。这个时代结束了。"}, {"instruction": "当我们不断地以为我们在利用技术的时候,我们是不是也反被技术利用或者奴役?以及这个能不能成为一个新的一种哲学革命呢?至少是一个重要的节点,就对这方面的一种思考。", "input": "", "output": "嗯,对。你说到一个当代哲学思考的若干最关键问题之一,就技术对人的统治。人在两个方面异化了,一个方面是资本异化了我们的生活,就我们的生活必须把我们的人生的意义用来为资本增殖做出贡献,如果你不为资本增值做出贡献,你在社会生活当中,在物质利益的体系当中,没有你的position。你可以去流浪,去做画家。如果你还想活,那么你这画家要服从资本,到画廊里去是吧,那么就这样一件事情。这是资本对人的生活的异化。还有一个异化,就是人与自然界的关系当中,通过技术这个中介异化了人的生活,它变成我们对它的依赖,就技术对人的统治。技术对人的统治是两种。第一种就是人和自然界的关系当中隔了一层,就是技术。就是我们那种对自然界的本来的那种亲近啊,这种亲近是让我们产生伟大的艺术的,绘画、音乐的东西,现在被technology阻断了。就是我们对自然的关系,我们只是利用它,利用自然界来为我们的某种需要服务对吧,那么这是一个方面。第二个方面技术还运用于人类与人类的统治。它加强了对社会秩序的控制手段,这也是技术对人的统治。今天我们尤其在互联网时代,信息技术的广泛运用,我们就知道了连private life都没了。还有我们要做任何事情都不得不运用信息技术进去是吧,这件事情就是说让一些伟大的想象、哲学和感悟消失了。就是我们每一个人都受制于一个超出我们所有人之外的一种客观的技术的力量的支配,它甚至规定了我们的消费,消费方式、消费需要。你比如说你画画好好地画是吧,我电脑也能画,你下棋就好好下,现在跟阿尔法狗下。所以这一切都是一种异化的表现。"}, {"instruction": "因为我的专业学的是计算机嘛,其实我们在本科的时候也会上一些课,去学习这些比较前沿的一些算法。所以当时其实我们也会有一个工作或者project,就是去类似AlphaGo一样,我们去算一个算法,去算按照当前这个棋盘我的最好的下一手是什么。然后算的方法其实也没有那么复杂,它的算法其实就是假设如果我下一手我有100种选择,那这100种选择里面我们挨个去试一下下一手的概率最高的是哪一个,然后再无限这样去算下去。那么对于技术的挑战其实来自于,主要其实就是来自于我没有那么多算力,可以把所有的未来都实现一遍。但是我可以算一些那些相对来说胜率比较高的那些,我把它优先去提取出来,然后通过这种方式近似去模拟我们全知全能的一种状态。然后这个project在做到最后的时候,其实会有一点有意思的地方,就是比如说假设今天我跟陈博士一起去上这门课,然后我们俩在算法上其实会有一些些微参数的调整可能会不一样,就会导致假设机器沿着我的算法去下棋和沿着陈博士算法下棋,它下一手可能会不一样。然后在那个时候其实我会有一种非常奇异的感觉,就感觉其实我们的人生就像这个下棋一样,好像其实只是一种不同的算法,从这一点上来说,是不是其实我们刚才提到的这个科技对于人对自然理解的异化,其实说不定也是人的一种本质呢,因为有可能我们只是不同的算法去操控的而已。", "input": "", "output": "因为就你从事的这个专业来说,就是这个软件呀,计算机语言、人机对话是吧,那么会让你产生一个联想,也许人生本来就这件事,对吧?那么这个我是不能同意的。为什么呢?很简单,因为人有一种命运,这种命运它并不是一种所谓选择的多样性、可能性当中我恰好match this choice。不,它有一种力量。就像一个民族有它的命运一样,欧洲民族有欧洲民族的命运,中华民族有中华民族的命运。这个把命运说得最清楚的是佛教,叫业力。我们只是在幻想中我们可以选择多种路径,多种可能性,其实没有。"}, {"instruction": "所以您觉得其实走这条路,你的人生发展甚至一个民族的发展是个必然。", "input": "", "output": "必然的,它就是业力的流转(字幕是:留存)。所以个人可以得到的是什么?在这种我们无法摆脱的业力,每个人也有业力的啊,按照佛教啊,我们来到这世界上都带着东西来叫业力对吧,我们也被它支配的。但是我们在被支配的同时,我们为什么修行呢?就是发愿,愿力、wish、will,。就这件事情很重要,就是让我们在一个被业力规定的人生轨迹当中活出生命本来的意义,这是修佛的主要一个目标。就是说我不可能做总统,我可能永远是引车卖浆者流,但是我对这点我不难过,我作为引车卖浆者流可能活得比总统更幸福。就先要承认业力的。就像中华民族有它的业力,中华民族永远不可能去过欧洲人的生活,欧洲人也永远不可能去过上中国人的生活,这是不同民族的命运,destiny。所以那个海德格尔讲得太清楚了,叫天命。欧洲民族的天命,比如说资本主义诞生于欧洲,它属于欧洲的天命,由于资本的本性要跨越,要跃出欧洲的疆界去征服世界,所以欧洲民族把它们天命强加给非欧洲民族头上,那么我们现在也搞市场经济了对吧。但是我们搞市场经济肯定搞得跟欧洲民族是不一样的,因为我们本有我们的业力。那么怎么办呢,就是我们自由在哪里呢,假如我们都是被业力规定,我们就是木偶了,也就是被线牵着,自以为自己要说这个话,自以为自己要做这个行为,其实都是业力规定我们的,假如是这样的人生,它太没意义了是吧?那么实际上佛教告诉你,能够消业力的就是愿力,“愿望”的“愿”。不光佛教说了这句话,法国大思想家卢梭也说了,卢梭写《论人类不平等的起源和基础》和《社会契约论》和《爱弥儿》的作者卢梭,。卢梭说在这人世间,所有的事物的价值都是相对的,唯有一样东西的价值是绝对的,就是人性善良的愿望,这一点我马上就感受到了他跟佛教的精神是一致的。你说科学的价值一定是相对的,它没有绝对价值的。就像你用计算机可以发导弹杀人,也可以发运载火箭上天,这件事情取决于什么?用技术设施来杀人还是造福人类,这种事情是谁来决定的?"}, {"instruction": "个人的意愿。", "input": "", "output": "愿望。这是一个世界上唯一有绝对价值的东西,跟佛教说的一模一样。所以在这件事情上,我们赢得了对技术的自由。我们虽然一直被技术支配,就是你刚才说的技术的统治,技术对人的统治,我们能否重建我们与技术之间的自由的关系?技术要用的啊,但它不能统治我们,它只是我们的手段,没有这个手段,我们的生活也会很尴尬。比说农业生产就靠天吃饭了,在医疗的时候没有现代医学、西方医学的技术上的长足的发展,我王德峰今天满口的牙齿都没了,必须承认这一点。最近50年里边牙科的发展在技术上是长足发展,什么人工种植牙,让我现在都可以咬那个螃蟹你知道吧。你必须感谢技术对吧。那么技术与人的关系本来就是这种关系。还有一个我特别感谢技术的地方,因为我特别喜欢西方古典音乐,以前欧洲贵族只有在他们贵族的或者欧洲的幸运的人那里,才有机会到柏林爱乐音乐大厅去听卡拉扬指挥一场音乐会是吧,现在我可以做到什么呢,把卡拉扬和柏林爱乐乐团放在口袋里,叫Walkman,对吧。你必须感谢技术。技术不是个坏东西,但是人把它用坏了。是有资本的原则,资本本来就要一控制自然界,二控制人类社会。控制人类社会的最基本的方式叫建立企业制度对吧,那么管理科学是吧,就干这种事的对吧,就control,control nature,control society。然后在这种当中,技术一定成为控制的手段。技术本身没有善恶的,人把它用坏了,所以人心是关键。"}, {"instruction": "刚刚Alicia提到这个算法算力的这个问题,其实我就觉得是不是这样,就是计算机它通过算法,它其实把很多问题就是简化成为一种概率或者说是一种偶然性的一种问题,而这恰恰是可能计算机它不能达到的,但是是哲学可能可以达到、它可以做到引领的一些价值的上的一些东西。", "input": "", "output": "就是what’s the future,我们要问这个问题,这是个哲学问题。what’s the future,未来是什么?未来不是你知识推断的对象,你罗列多少种可能性,然后计算一种概率。不,我们人类不是以这种方式走进未来的。未来是希望和恐惧的对象,人以筹划和下决心的方式走向未来,make up your mind,这非常非常重要。就像我们每一个人走向未来也一样,我究竟跟她结婚还是不结婚呢,你以为这是个利害得失计算吗?不,你在make up your mind。你决心跟她后半辈子的人生风雨同舟,患难与共吗?这不是一个概率问题,这是你对自己的命运有领会了。民族也一样,个人如此,民族也是如此。我们中国人将走向怎样的未来,这不是个计算机语言推算的问题,你知道吧?"}, {"instruction": "因为其实教授有提到说,其实人和机器一个非常大差别在于人有情感,尤其是其实所有情感的基础就在于恐惧和喜好对吧?那么在佛学里面,其实我记得唯识学也有提到。", "input": "", "output": "不是恐惧和喜好,恐惧和喜好动物也有。(Alicia:明白明白。)人性是什么?向往不朽和无限,有这个东西的。所以才有伟大的宗教和伟大的哲学和伟大的文学作品,它不朽的。就像贝多芬的9部交响曲,多少年前写的,它将不断地鼓舞着未来的一代又一代。因为这是人生,这是人性最了不起的地方。它要把有限的生命跟无限关联起来,把无常的东西跟不朽关联起来。"}, {"instruction": "生存欲这是。", "input": "", "output": "不是生存欲,生存欲动物也有。人要活得像个人,就这个意思。而活得像个人意味着什么呢,人是一种精神的存在,这个精神是什么东西,它跟不朽关联,跟不朽immortal being关联。为什么两个人相爱到最深的时候,他们认为他们的这份爱情是不朽的?在时间上他们肯定会结束的,这种爱情总是有头有尾的了,但他们认为它的价值是不朽的。人心是会这样想事情的。所以我们避免讲那些我刚才讲的心理主义的词语,因为它完全可以用到非人的动物上,对吧,也可以。但我们人去从事哲学,从事宗教信仰的活动,这是因为人心本身需要的东西,否则我们就无法理解宗教为什么存在。"}, {"instruction": "刚才Alicia提到那个人的自主性,还有跟那个机器的区别。我想要说,现在其实很多年轻的朋友,因为这个现在的社会的竞争压力那么大,像上海,包括一些生活的压力,包括工作上面的压力,其实有一些人就会说感觉自己只是一个大的庞大机构里面的螺丝钉。然后呢,以至于说出现了两种趋势,部分的比较积极的人,比较能够整个control自己的life的人,他可能就在这个体系里面混得顺风顺水,然后还有一部分人他可能就选择躺平了,就现在很流行的词,“躺平”,年轻人。那这样的一个状态的话,我不知道从王老师哲学的角度来说,怎么看待目前这样的一个年轻人的现象?", "input": "", "output": "这个话题呢,其实跟我们讨论科学还是有关联的。就是说科学不能解决人生观问题,这句话同意吗?"}, {"instruction": "我会觉得可能每一种学科它都会有底层的它们学科的逻辑,就这个学科逻辑本身就是价值观的部分。对,我会觉得是有一些哲学的部分在科学里面。这个其实就是涉及到说科学怎么改变我们这些普通人的一些世界观,然后价值观,还有一些对整个它周边的事物的一个理解认识这一块的一个整体的问题。", "input": "", "output": "实际上关于当代文明的根本病症的诊断,在思想家那里老早就发生了。比如说马克思他发动哲学革命,然后提出了他的新哲学,里边一个核心的概念就是异化,当代文明的根本病症就是异化。什么叫异化呢,人创造出来的东西反过来否定人自身。那么他这个异化概念就有一个前提是什么呢,就真正的人,符合人性的人,有这个前提才能谈异化。如果说这个前提不存在,我们认为没有什么所谓真正的人性,人性它有自身的最高的价值,这件事情被否定掉的话,马克思也不能用异化这概念了,everything is OK,一切都是正常的。但马克思说这是异化,海德格尔也诊断了,当代文明的根本病症叫遗忘存在,只在存在者当中趋利避害,遗忘了存在本身。然后呢,当代文明的一个最重大的特征叫进步强制,就是你必须不断地在这个社会的舞台上、资本的舞台上和技术的舞台上,你要不断地往前走,你一旦停下来就等于下降,叫进步强制。这确实是如此的。就这种进步强制的当代文明让我们对时间的观念都改变了。我最深刻的感受是什么,我们一年忙到头,一年忙到头终于吃年夜饭了吧。中国人吃年夜饭那一天,大年三十晚上,家人聚集在一起吃饭,难免要回顾一年吧,刚刚过去的一年究竟做了些什么。我后来发现有个共同的感受,就是忙得要“死”,结果做成什么,没做成什么。一方面是一种很忙的感受,好像我这一年做了多少事,后来发现全没意义,一种虚无感。就我小时候的时间观念,因为我小时候毛泽东时代、计划经济时代,还没有把资本的逻辑充分引入中国的经济生活和社会生活是吧,教育就是教育,医疗就是医疗,它跟效率一点关系都没有对吧,学习就是学习,你的心里的好奇心得到满足,你也没有被考试成绩所压迫对吧?那么这时候呢,我的时间的观念感觉跟现在真是不一样。我当时就觉得一天都很长的,我可以从容地想做些什么对吧,现在发现一年都不知道怎么过去了。这时间的观念都会改变掉。所以海德格尔那本书叫《存在与时间》,这个书名有它的道理,就是我们对时间的领会是来自我们对存在本身的领会,因为我们面对死亡,人知道自己必有一死,我们面对虚无,于是我们灵魂存在,于是存在的意义从哪里获取呢,不是从对存在者的控制中获取,就是我们是对存在者的一种技术的控制和资本的控制。就像我们的大学教育究竟为什么,大学、高等学校纷纷成了高等职业培训所,为资本的社会输送他们所需要的人才而已,专业人才是吧,specialist对吧。你被称为专家,因为你对资本增殖能做贡献。我王德峰是不能被称为specialist,为什么呢,我专门研究资本主义怎么灭亡这样的学问,就跟他们没关系。然后还算好,因为复旦大学承认马克思学术研究的重要性对吧,所以我还有一口饭吃对吧,就这件事。其实像我的这种事情,就搞哲学的人真正把一辈子几十年的精力花在哲学研究上,他确实对资本增殖毫无贡献的,只是我们被容忍了,那么大多数年轻人不可能像我这样生活。所以科学它本来是价值中立的东西是吧,我讲的是自然科学。社会科学一定价值不中立的对吧,经济学、法学它一定有它的价值立场的,但自然科学应该是价值中立的,物理学非善非恶吧,中国的科学也是非善非恶。你说阴阳五行学说是恶的还是善的,这没关系,不相干的事,但是它可能被用了,而且现在真是被用了,被用了之后呢,所以那些本来在大学读书的对本专业热爱的学生,这个专业是我讲的是应用科学啊,热爱的学生,他们毕业了以后他们会有一种非常悲哀的感觉。我所学到的所有的这一切,只是我第一谋生的手段,第二也可能在资本舞台上获得更多的财富的手段,这就是现状。后来你会发现匆匆忙忙就人到中年,80后现在许多人感觉到人到中年了对吧,人到中年以后呢,以后的人生道路完全按照原先的这种走法,一个惯性走下去,觉得很不好的感觉对吧?人才会问自己的生命有没有价值,如果没有价值就有一种空虚感,灵魂对价值有渴望,对自由有渴望,因为这是人的存在。"}, {"instruction": "所以这还体现在可能一些教育当中,尤其是像我经常会听一些那些教语文的一些老师,他们会抱怨就是说像语文考试这样的它也会喜欢用一种量化的或者是用一种科学的思维,就让这些语文老师们他们判卷的时候他们也很头疼。所以说是不是科学主义还是不得(不)就渗入到了我们的通常生活的体系当中?(王德峰:对。)以及我们知道怎么样去消解它的这个负面的影响吧?", "input": "", "output": "第一,它是渗透的,几乎无处不在地渗透,包括语文课终于上得不像语文课,不是文学的修养了。它要分析这个诗,分析这篇散文或者小说,然后它的中心思想是什么,段落大意是什么,整个篇章结构是什么。(陈晋卿:而且还有标准答案。)那你是在欣赏文学吗?你建立的不是一个与真正的文学作品的一种文学的阅读关系,你建立的是分析和解剖的关系对吧,这个是许多语文课上得很失败的原因,就是你刚才讲的,就是说我们语文老师不得不出题、考试、量化、评成绩。所以判卷呢,语文课或者其他人文学问的课程考试的判卷实际上是蛮难的,它取决于老师的法眼或者叫慧眼对吧?一看这学生绝对好,那个学生写的四平八稳没有一点错误,你最多给他及格就可以了,这个要给他加分,那如果是这样的老师,他合适于教人文学问。北京有一个小学的语文老师,有一次期中测验,考试的题目当中有一种类型的题目叫填空,卷子发下去了,有一道填空题是这样的,冰融化了以后是,一个空档要你填,所有的学生除了一个之外一律填上了水,冰融化了以后是水,符合标准答案,偏有一个学生填了两个字,春天。(陈晋卿:诗意了。)然后不符合标准答案,打岔,扣分。这件事情传出去了,引起了北京语文界的普遍的抗议,然后写文章。那是好多年好多年前的事了,那么我以前是听广播,那时候我们还听广播,早晨6:30听新闻就讲到这件事,其中有一条新闻。因为有人在北京报纸上发了文章问这位老师,你教的是什么课?假如你教的是自然常识课,标准答案是水没错,因为这个填空题是在讲物态的变化,从固态到液态的对吧,但是你教的是语文课,当这个学生填了春天二字的时候,你要知道你发现了什么,未来文学家的种子是吧?语文课该怎么教?就是你刚才提的问题,就技术的这个原则到处渗透,因为它方便就是量化,量化来攀高低。我们这个时代就是量化的时代,比方说每一个在大学里任教的老师、学者,每年要填表吧,科研成果表就量化的。有的人一年写十几篇文章,我一看怎么这么厉害,我就写不了,哲学哪有天天有新的想法的对吧,但它量化没办法,这些人叫人才、写手你知道吧,他可能被某个大学引进人才。这就是个量化的时代。技术和资本带来的进步强制,实际上到处都存在,能不能对这种进步的强制保持足够的距离?比方说东京大学一个研究经济学的老师,10年里面没发文表过一篇文章,东京大学没把他开除,第11年得了诺贝尔经济学奖。这不是量化的问题。要拒绝量化。"}, {"instruction": "我可以再最后问王老师一个问题吗,如果说对于现在的年轻人刚刚所说的很多关于异化的问题,怎么样去打破异化,王老师有没有一些哲学上的建议?", "input": "", "output": "就是我们刚才的主题是讲科学,就科学在我们这个时代从哲学上去反思它,那么这个我们已经展开了比较多的讨论。那么科学当然是人类文明进步的一种当代方式,我们必须承认的对吧?同时呢,正如马克思所说的,科学一方面推动了人类的进步,一方面直接完成了人类的异化。这两句话他都说的,马克思《1844年经济学哲学手稿》里说的,直接完成了人的异化。所以对科学,马克思的评价是两个方面。一个方面它有巨大的文明进步作用,因为通过科学运用于生产,人赢得了对自然界的相当大的自由,不再靠天吃饭对吧,农业上解决问题。另外一个就大大地提高劳动生产效率,因为技术的运用为人类提供闲暇leisure,因为闲暇的时间才是我们每个人自由发展的空间,假如人类活在自然界当中必须24小时除了吃饭睡觉以外,必须汗流浃背地工作才能存活于这个自然界,那么人类还是自然界的奴隶。所以科学技术的进步的文明的作用是绝不可否认的。但是因为它跟资本是联手的,是一种孪生兄弟,它同时也就像资本一样来异化人类的生活,然后人被自己所发明的东西所支配,反过来支配。就是一个父亲生了个儿子,后来儿子反过来支配老子对吧,这就是异化。那么这个事情你刚才问了,就是说对年轻人有点什么advice建议,我认为我们要保持一种精神上的空间,这是必须的。因为有一些年轻人他恐怕直接地认同了这种异化,并且把这种在这个异化的轨道中生活所获得的量上的成功看成是人生的成功。那么这些人这样想也可以,也不必一定要纠正过来,但是他总有一天他会发现生活的基础不在这里。比方说爱情,爱情不是在资本舞台上掌握的资本数量来决定的,当然你可以来决定一个婚姻,但不是love。所以这些事情都会让年轻人反思的,我究竟为了我的这些进步牺牲了些什么,这些牺牲的东西我认为他们会发现是人生最宝贵的东西。那么这一点我的建议就是说,无论是哪一个行业,无论是受过大学教育或者没受过大学培养的年轻的人对吧,其实还是要读书,我说的是读好的书,好的书就是人文经典。其他的书你读了,有些是娱乐性的,有些是工具性的对吧,计算机教程就是工具型是吧,但是有些书是非读不可的。"}, {"instruction": "那您有没有对于年轻科学家的一些建议呢?", "input": "", "output": "科学家能够成为“家”的啊,一定是有境界的。"}, {"instruction": "那就年轻的科学研究者或从业者。", "input": "", "output": "科学工作者嘛,他就没办法了,因为他本来在大学里学的是这个专业,或者他甚至得了博士学位。就是说大学本科呢,一开始还是一种比较全面的修养,到了master,就硕士的时候,他已经是指向择业了,博士更指向择业。那么他一定是通过自己学到的某一个领域当中的能力来在这个领域当中为他所服务的单位,无论是企业还是其他什么的,做他必须做的贡献。这些事情都没啥问题。有一条就是说科学,他对它的运用一定有一个标准,这个标准来自人心,比如刚才讲的。你比方说有的人专业能力很强的,他运用来实现某种伤害社会生活的、伤害别人的生命价值的东西,这是不可以接受的。这是一个科学工作者应当有的良知。比方说有一门科学叫金融学是吧,社会科学经济学的一个分支,那么你学了当然是可以到银行里工作或者到什么金融机构,但是你不能去凭借这种金融学知识去制造骗人的理财产品,那肯定不行。这就是最起码的一个就道德底线的问题。科学工作者有道德底线。"}, {"instruction": "谢谢王老师。今天是研修班的最后一期了嘛,所以我们定的这个话题看起来也很大,叫人生的意义,不知道王老师是怎么考虑这个话题的。", "input": "", "output": "这个话题是中西哲学和包括宗教都在回答的一个问题,都想试图解答的问题,有的人通过宗教信仰来回答这个问题,比方说欧洲民族基督教,阿拉伯民族伊斯兰教,那么我们中国人要问人生的意义,那么这个解答在哪里呢?所以这是个好话题。另外它特别切合当下,尤其是年轻的一代,那么,这种问题的追问就表明了一个我们当下的这个社会有一些问题变成普遍被感受到的困惑。"}, {"instruction": "那在中国哲学里我们比较了解,就以儒释道为基础,每一家都有一些答案,西方人怎么看待他们自己人生的意义?", "input": "", "output": "我记得温家宝总理曾经提出一个建议,希望广大干部去读一本书,这本书就是古罗马的皇帝奥勒留他写的一本书,叫《沉思录》,这个《沉思录》的主题就是来思考人生的意义,因为古希腊晚期这个社会已经秩序混乱,用我们中国人的话叫礼崩乐坏,人人自危,古希腊哲学的鼎盛时期已经过去了,这时候古希腊的哲学主要是三大派别了,一个是伊壁鸠鲁哲学(强调个体的本位,感性高于理性,真理的存在在于个体的感觉,并改造了原子论),一个斯多葛学派哲学(认为宇宙是一个不断运动的整体,个体从属于这个整体之中。人与自然有着内在的一致性,自然有自己的运行规则,即逻各斯。这种逻各斯支配着宇宙的运转),还有一个叫“怀疑论”的哲学(认为不给出一个给定的真理体系,也不对此做出否定的结论,而是处于存疑的状态,他们的做法叫悬置判断),就这三大派别。这三大派别的主题都是一样的,就是个人思考人生问题,就我们活在这个世界上,如此城邦的伦理已经瓦解了,个人如何致力于这个世界,所以这是西方哲学第一次把人生作为主题,就是晚期希腊哲学。这个阶段,这个西方的哲学成果最突出的体现在斯多葛学派那一面,就是说人活在这个世界上,他的生命的价值究竟依靠什么?那么这样一个回答就由斯多葛学派给出一个回答,就是我们人活在这个世界上的意义并不取决于偶然降临到我们个人头上的天赋、外部条件、我们出生的家庭,我们或者生来是这个聪明的又或者性情诙谐等等,这些东西其实对个人来说都是偶然降临到我们头上的,我们的生命的意义并不依靠于它们,那么依靠什么呢?依靠灵魂内在的完满,就说每个人不管社会地位高低,不管自己是愚笨的,还是聪明的,不管是富有的,还是贫穷的,每个人只要他的灵魂能够达到它的圆满,毫无欠缺,这个人的人生就是有价值的,他哪怕历经苦难,这是一个非常典型的斯多葛派的观点。"}, {"instruction": "那灵魂怎么样去圆满呢?", "input": "", "output": "灵魂的最高部分叫理性,这是西方古希腊哲学当中共同承认的,柏拉图也这么看,比如说一个人的灵魂是三个组成部分,低阶的部分叫欲望,当中一个比欲望高的叫意志,比意志更高的、最高的叫理性,那么一个完满的灵魂应当是理性来统帅意志,意志来限制欲望,这是柏拉图,他已经准备好了斯多葛学派后来对理性的圆满的一个解释。这个解释就是说我们灵魂的最高主宰是理性,而这个理性只要它符合人性的价值,这个人性的价值它主要是指道德,道德上的人与人的关系这种道德评审,那他说理性来真正能统帅意志和限制欲望的话,我们完成了我们的灵魂固有的德性,最高的美德,那么它就表明这个人的生命无论他实际的处境多么糟糕,但他是幸福的人。"}, {"instruction": "之前我说,看过那个罗素写自传,前面会说我为什么而活嘛,他就说三件事,一件是爱情,一件是知识,一件是对世界的同情,他说我就为这三件事而活。当时我觉得有可能,就是年纪小的时候看,我觉得是罗素在给大家喝鸡汤了,罗素推理出来这三个方向的话,我们是不是要遵从,就是它是不是一个普适的意义?还是说我应该建立在自己的内心或者说自己理性的角度,我应该去推理出自己的意义?", "input": "", "output": "在某种意义上,一个民族提出的人生问题的解答,它一定会有普适性,哲学它虽然诞生于一个民族,但对其他民族一定会有启发的意义,在这一点上它是普适的。但是哲学它又根本上属于一个民族,中国哲学属于中华民族,从我们这个民族的命运中诞生,也规定了我们这个民族对人生意义的回答不同于欧洲民族,所以这里面有一种所谓形式上的普适性,而内容上的非普适性。我曾经在一堂哲学课的课间休息,一个同学上来问我,本科生,他说,王老师,你是搞哲学的,在你看来,人生的意义是什么?我说,你这问题我没办法回答,因为它太抽象了,你最好具体一点问。那么他说什么叫具体地问。比如说你问我,欧洲人的人生意义是什么?阿拉伯人的人生意义是什么?中国人的人生意义是什么?如果你这样问我,我才能回答。他说好啊,那么现在我就具体地问,中国人的人生意义是什么?我脱口而出两个汉字——牵挂,他当然也是没明白,我是自然而然本能般地吐露出两个汉字来,牵挂,用英语来翻译就是concern。他说,你为什么这么说,我说中国人怕“死”了,中国人面对死神威胁的时候,我不想走,你知道为什么吗?只要你是中国人,你为什么不肯走?怕死就是因为这世界上有人牵挂着,你牵挂着别人,别人牵挂着你,假如你已经做父母了,你一定牵挂孩子,不肯走的。如果一个中国人什么牵挂都没有,这世界上没有人牵挂你,你也不牵挂任何人,你就会不怕死,中国人就这样。所以,这就是中国人的人生意义了,我就说了两个字,牵挂,实际上它背后就是儒家的思想,(丁曼文:根深蒂固的嘛。)根深蒂固的。"}, {"instruction": "因为正好聊到这了,我就......因为上个月我外婆去世了嘛,然后当时他们,我们的父辈,五六十年代的人,他们的家庭都是大家族的,有六个孩子,所以其实像我的舅舅姨妈们,他们基本上每周都要回那个老房子,所以我觉得,他们的精神家园可能几十年来、六七十年来都寄托在他们那个老家里,所以当我外公和我外婆都去世了之后,老房子就空了嘛,然后我舅舅就跟我说,我觉得我没有什么意义了,所以,我也不知道该怎么安慰他。", "input": "", "output": "有时候双亲的离世,对于他的儿女来说,儿女就把他们自己的人生已经分成两半了,再回顾双亲在世时的以往,恍若隔世。所以亲情对于中国人特别的根本,比如说双亲先走了一位,还有一位还在,中国人还行,就心里那堵墙还在的,另外一个也走了,完了,一堵墙就倒塌了,然后继续走着人生道路,再回顾双亲在世的那以往的岁月,就像隔世啊。凡是经历过双亲去世的人,大概这个感受是共通的,佛学会在根本上跟我们讲清楚一件事情,让我们中国人活得稍微精神上轻松一点,就是说一切都是缘起。我们那么珍惜的,从小在其中出生的,从小在其中长大的那个家,我们本能地认为它应该永远存在,当双亲都离世的时候,我们的感受是什么?它没了,真的没了,所以我当时真正领会佛教的那四个字就是通过这件事,就我父亲去世了。哪四个字?“缘起性空”。就是我们跟双亲的关系,佛教解释得非常明了,叫“胎缘”,你是借胎而来,借你的父母投生了,那么你跟双亲的那份缘是真的,但除了这份胎缘以外,别无其他,所以不要那么难过,你的人生道路不是你双亲能安排的、能规划的,你走的你自己的人生道路,你的人生道路跟你的双亲恐怕一点关系都没有,或者有关系的话,并未走出你双亲当初的那条路来。就那个佛教对它的解释就很简单,我们每个人借母亲的肚子来了一下,这叫胎缘,但是我们自己都带着自己的业力的,那么我们的人生就是这个业力的展开,那么这样一来,佛教以它的人生观来缓解了我们中国本来的儒家人生观给我们精神上的一个非常大的一种压力、一种纠结。"}, {"instruction": "我母亲是2012年的时候去世的,然后当时除了内心的其他一些变化以外,当时给了我一个观念,就其实我一直都想不明白,我为什么而活,但是通过那个事件嘛,我开始去思考一个问题,就是我到底能为什么而“死”,就是我觉得这个好像是一个突破口去思考这个问题,我去想象当中我最后应该以什么样一个状态或者因为什么样一个事件我自己死掉了,那么我可能这一生活着的时候我就知道我应该去追求一个什么东西了。当时我给那个朋友讲这个事的时候,我举了这么一个例子,司马迁为什么要专门写《刺客列传》,就是因为司马迁想“死”“死”不了,他有一个更重要的事要做,所以他会把那些为了一句话、为了一件事、为了一个人就能去死的那一批人拿出来写,他觉得那种死法很爽、很过瘾,而且也告诉大家我也可以这样,但是我有一件更重要的事要做,(丁曼文:他有牵挂。)他有牵挂。再举一个例子,我们现在不是跟一个哥们儿在拍视频嘛,拍视频大家都说你这视频拍得不错,然后,你越拍得不错,大家越说,说钱哥你不应该回营口,你应该留在北京发展之类的,然后我跟他说,我说我在北京待了15年,我不是没有写东西的想法,我不是没有拍视频的想法,而是我根本再遇不到一个我这个哥们儿,但是回家了之后呢,我想干什么就能实现,就是就会有一个哥们儿跟你一起,也不图什么回报的,就是大家就是一起玩,我才意识到,北京并不需要我,但营口需要我。这是不是大城市年轻人就是经常迷茫的主要原因?", "input": "", "output": "对。这个西方人老早就讨论过这个应该说是社会学的一个话题,就是“都市病”,就是我们中国改革开放走到今天四十多年,它从另一个角度来看,它实际上是一个城市化运动。因为中国以前就是一个农业大国,刚刚建国的时候叫四万万五千万同胞,就四亿五千万,对吧,后来我们人多了,六亿,六亿神州尽舜尧,到毛泽东时代晚期的时候,已经七亿人民了,那么百分之九十是农民。所以这种情况呢,改革开放呢,它实际上就是一个市场经济了,市场经济体制呢,它就是资本逻辑起作用,那么资本逻辑一旦起作用,一定是乡村服从城市,从工业开始,然后商业,金融,全集中在城市,那么有大量的人口脱离了原来的乡村,走到了城市。到了城市他没有感觉到那是家,以前的乡村那是种家的感觉,一个核心家庭之外还有亲戚啊,整个一个家族都在,然后在这里呢,我们就觉得在一个伦理共同体里面生活,一种家的感觉非常强。现在为什么过年的时候要回乡,就是回家,这个家是精神上的,然后要有年味了,过年要热闹,这些呢就是在城市当中实现不了的事情。“都市病”就是会让一个从乡村到城市谋生的个人感到城市的寒冷,因为这里我们就是谋生,我要找到一份工作,我在计算我能挣多少钱,那么钱最后要落实到在家里能造房子,能养父母的老,能养育孩子。对中国这个民族来说,它长久的农业文明嘛,自然经济自然村落嘛,他原来的那个家已经不存在了,所以呢,普遍的患上了都市病。那么一个在城市中工作的人有一种“漂”的感觉,所以现在有一个概念叫“北漂”,就离开家了,“北漂”了,叫流落他乡,流落到城市中去了,那么还有“南漂”,就是从北方“沪漂”到上海,这都叫“漂”,这个漂字用得很恰当的,就是城市不是他们的家,只是漂着。那么在这种情况下,如果你在城市待了十几年、二十几年,那种乡愁非常厉害,然后要追问人生的意义,也是一个原因,"}, {"instruction": "王老师,我正好有一个问题。因为这个刚刚您讲了就是咱们中国这个发展特别快,现在商业社会占据了主导,咱们城市化率可能已经到了百分之六十五以上了,那其实人生的意义我们刚刚讲了,爱情、知识还有人间的疾苦。其实对我来讲,可能人生很多意义是工作,变成了工作。因为公司是一个即抽象又具象的一个存在,你说它具象,它非常具象,它有办公室,它有具体的业务,它有创造价值,但是它又很抽象,因为你做的事跟公司最后要做的事不一定离得很近,甚至可能离得很远。那在这个过程中,我们有可能在“空转”,也有可能我们会产生一种“空场”的这种概念,那这种时候,您刚刚讲的这个“都市病”可能是一种表现,然后那如果我们想要去消解这种“空场”、“空转”的这种心理状态的话,那我们是不是更能去了解人生的意义是什么,就是基于工作的这个事?", "input": "", "output": "你讲到了另外一个非常重要的维度,就是当代中国年轻人会思考人生的意义的另外一个重要的原因,就是工作跟事业无关。(赵南北:对。)当代文明当中一个非常非常重要的问题,就是事业感的缺失。事业是什么东西呢?就是我的个性。因为人与人之间的差别很大,有不同的才能和不同的喜好,个性其实是什么呢?就是你独特的才能加上你的跟这个才能一致的兴趣,你要通过一生来实现它,如果你做到了,你这一生的意义是不用追问的。有的人为音乐而生,有的人为绘画而生,有的人为某一门科学而生,物理学思考是他最开心的事,他不要从中拿工资的,就是你不让我从事物理学研究,你这是在迫害我了。就这样的人生,他不会提这个意义问题,因为不需要提,他跟他所从事的事业是融为一体的,他感恩这个事业,这个事业让他作为渺小的个人的个性得到了实现,也就是说他的人生价值实现了。所以有许多,比方说以前中央电视台有个节目叫《艺术人生》吧,访谈老艺术家,我以前经常看这个节目,那些老艺术家被采访、被访谈的时候谈自己跟自己从事的那门艺术的关系,以及他现在最大的愿望是什么,我听了非常非常感动。这些艺术家他把他所从事的艺术看成是某种宗教般的东西,他对这个事业的信仰简直叫宗教信仰,然后他老了,或者他已经没有那个力量再继续创作,他就会把培养学生看成是天大的事,要报答这个事业,怎么报答,最好的报答就是让它后继有人,是吧。那么我面对这样的人,他的人生,我就觉得他活得那么好,那么充实,虽然要在这个艺术领域里面能够做出贡献是要吃多少苦头,没关系。那么现在这个文明呢就把这一些事业的神圣的光环都剥离掉了,它服从资本运动,它要有人炒作,然后出了名有经纪人。所以这些事情就是我们当代这个文明当中非常重要的一个方面,叫事业感的缺失,事业的消失。所以以前我在复旦任教几十年嘛,听我讲哲学课的学生呢,都觉得一个人的一生要托付给一个不朽的事业,那么短暂的一生就获得了它不朽的意义,这是最好的人生,对吧。然后他们毕业了,几年之后回来跟我见面,王老师,当初我们在复旦在教室里听你讲课,听得热血沸腾,我们现在都已经在社会上工作了五年六年,王老师,我告诉你,我们没办法敬业,你不是主张敬业嘛,敬业精神嘛,我没办法,无业可敬,我告诉你(指主持人),其中一个同学跟我讲,王老师,我跳了三次槽了。"}, {"instruction": "嗯,因为现在资本也很残酷,因为最近我们应该也听说很多人被“裁”了,特别是互联网行业,因为我自己本身也在这个行业,就是甚至大厂都在裁员。本身它们利润本身可能就挺高的,但是也在裁员,因为它是很残酷的,它不是把你当成,就是像王老师说的,它不是来成就你的事业嘛,它是来成就资本的背后的一些运转的、一些增殖的。所以说这个被“裁”的人一定会想我下面做什么,我还能继续找到跟我之前一样的工作吗?或者是我找一份跟之前不一样的工作,但是是我想一辈子坚持下去的,那找到一辈子想要坚持下去的工作,我觉得这件事对于现在的年轻人来讲太难了,我真觉得太难了,我现在处在一个三十五六岁这么一个阶段,我都觉得难,你更不用说年轻人了。因为我们这个年代是两个标签并行的,“内卷”和“躺平”,是并行的一个年代,所以说有一帮人很“卷”,就是我一定要有价值,一定要有事业,有一帮人说,我不管那么多了,我就好好活下去,我能活着就挺好。然后,我觉得这个年代的确比之前的年代,我觉得思潮上也发生了很多的改变,那我们怎么去抵达意义呢?就是怎么去消解我们现在这种标签化的这种人生常态?其实也是我觉得我可以想问王老师的一点,对。", "input": "", "output": "就是我们所处的这个文明啊,就是我经常用的就是资本和技术的文明,在这个文明当中有一些我们个人无法摆脱的一种规定性,比如说你要谋生,比如说你必须为这个社会提供你的专长,然后你才有你的社会的一定的位置,那么你才能作为一个正常的人被承认着,对吧,这件事情人人都需要。那么,在资本和技术的这个文明当中,就是我们往往就不得不首先忽略或者抹去我们童年或少年时候对未来人生的许多憧憬和理想,它一定会被牺牲掉的。我们每个人在童年和少年成长的过程当中一定对自己的未来有所憧憬,甚至有非常明确的理想,但这个社会,这个资本和技术的文明,会把你消解掉,让你觉得......比如说我最后读大学读哪个专业,这件事情跟你的童年和少年的理想已经开始分开了,是吧。然后大学毕业或者什么学校毕业以后,你到社会上第一件事情就是谋生了,谋生你就要服从这个社会的职业体系,你就进了这个职业体系,在某一个职业当中,按照这个职业和所在单位的要求做你的工作,是吧,那么就好像一颗螺丝钉放在这里了,是吧,然后剩下来人生的意义是什么呢?然后一般的解决办法呢,就是把生活分为两个空间,一个空间就是工作和谋生,另外一个空间就是家庭和个人的爱好,就这样一种区分方法。这种区分方法呢,对有些人不适应的,因为比方说,对你现在这段时间就不适应,因为忙得不得了,另外一个空间就被挤压得差不多了。那么当代人如何摆脱两种标签式的存在,是吧,一种叫“内卷”,一种叫“躺平”,那么这实际上呢,个人能摆脱的就是因为你,前提就是你对大家都“卷”的那个目标,价值目标,你不认同,我何必去卷,对吧,因为资源有限,大家都往那里。比方说考公务员,就是典型的“内卷”职业,千军万马考公务员,当时啊,以后怎么样不知道,那么这就是“内卷”,肯定是许多人淘汰失败。有些人经过好几次类似的“内卷”淘汰之后,进入另外一个标签,“躺平”,就没啥意思,我何必这样,是吧。那么我们好像只有在“内卷”和“躺平”两者之间做选择,没有第三种可能性,是吧。那么目前有许多年轻人是这么想的,我放弃“卷”等于“躺平”,好像没有第三种可能性。那么我觉得其实不是这样,“内卷”和“躺平”都应当被摆脱掉。这个“躺平”这个观念实际上也表明了一种对现实的抱怨,它实际上跟“内卷”价值观念是一致的,背后的价值是一样的,只是我不行了,我不干了,其实他是没实现,所以他就是说我“躺平”了。所以这两者都应当被扬弃掉的。那我们年轻的一代要重新思考我们的人生,就是说我们与这个资本和技术的时代所强加给我们的一种进步的强制,我们能否保持必要的距离?我一直跟年轻的大学生们讲,我说你们将来去工作,很快就会“内卷”,当时没有用“内卷”这个词啊,还没有这个词,到时候你们一定会进入职场的竞争,职场的竞争会让你们把这个价值目标啊看着当真了,然后把几十年的光阴花在这件事情上。我说我想提醒你们,不要认同这些价值,你第一件事情就是谋生,你找到一个单位工作了,你能自食其力了,不要靠父母养着,是吧,自食其力,自谋生计,这是你一个人站起来的第一步,否则,你都站不起来。你说你跟父母说你每天给我三顿饭啊,一碗粥啊,几个馒头,我就过了,你不要叫我一定去找工作,我就在电脑面前是吧,这样的人生是不行的,时间一长,他就绝对的空虚了。因为不管父母多么有钱,根本不需要你去挣钱,不需要你去为公司奋斗,你也必须为公司奋斗。为什么?你自食其力,你这样才在精神上能够自信,这是第一条。所以别把职场看成是奋斗的地方,而是你捧饭碗的地方,对得起你父母养到你今天,你还能吃父母的吗?他们再有钱,你也不能吃,“啃老族”是千万不能做的,这极大地损害我们年轻人的精神感觉,这是第一条。既然你是捧饭碗去的,你干嘛要去竞争呢?你守住这个饭碗,对吧,你不要追求某种职场上的成功,你换一个词,叫成长,我跟我儿子就说,not for success(不为成功),just for growing(只为成长)。可能被裁员,你刚才提到了,当经济萧条的时候,有一些企业真是裁员了,裁员了以后,他老的本行不能干了,他可能要换一个行当。换一个行当也是一种成长,几个行当换下来以后呢,基本上叫全面发展了。所以,两条,第一,自食其力,自食其力都做不到就人生意义的基础没了,叫寄生虫,那是不行的;第二,就是说,不管这个市场经济、这个经济的市场是如何的瞬息万变,对于我们来说,就是能力的提高。那这不是自我安慰,就是干一件事情的时候你就觉得你是能发挥的,也许这方面不是你的天赋所在的领域,但你通过努力,你仍然可以干出你的水准来。因为成长这件事情,能力的成长是一种精神上的乐趣,而不是说是success成功,成功就是一种外在的价值目标在引领,要从部门经理再往上升,对吧,然后总经理,最后CEO,等等等等。这些很光鲜的成功其实没啥意思,而且它让我们年轻人付出的代价太大,其中一种代价叫 office political,叫办公室政治,办公室政治也是一种“内卷”。所以,我一直跟年轻的毕业生讲,你们千万别卷入办公室政治,你们要以佛家的态度跟你的同事相处,他可能升得比你快,你不要难过,你也不要跟他竞争(斗),对吧,你们能够在一起工作,其实是大家都去捧饭碗去的,找了一份工作,捧了饭碗而已,那么珍惜一起工作的缘分吧,然后他升上去是他的事情,不要去担心他踩着你上去。所以我说,今天的年轻人要读一点比如说慧能的《六祖坛经》,就是能够改变一下我们从小成长的这个社会环境注入到我们心中的种种妄念,把它去掉,这是非常重要的。就是说,佛教无法给我们正面的人生意义的回答,但佛教给我们消解许多妄念,就好像一帖药,先把你病治了,病治了以后呢,你不能靠药活着吧,你要五谷,要粮食,儒家,正面树立人生的意义。佛教先把你妄念去掉,那么道家是最最简单的,但是很难实践,比方说你刚才说的啊,活着就可以了,是吧。"}, {"instruction": "我是忠实的道家信奉者。", "input": "", "output": "这是道家。道家说人生是没意义的。如果要有意义,只有一个意义,全生避害,保全生命啊,因为生命是上天赐给我们的,天给予我们生命,我们不能违背天道,所以我们要enjoy life itself,别的没有任何意义。所以,按照道家来讲,没有目标的人生是最好的人生,你要有什么目标干嘛,如果一定要有目标,只有一个目标,全生,保全上天赐给你的生命吧。"}, {"instruction": "我大学的时候就看了《道德经》,然后它里面有一章里边的一句话,我看到了就把它抄在本子上了,我到现在都能记得,叫“兴之所至,心之所安;尽其在我,顺其自然”,然后我就觉得所有东西都通了,我就不会再去顾虑什么任何东西,不会再去担心。你也是经历了一次就是抢救嘛。对,对,当时我的状态是不太好的,然后,就是有一天在办公室打文件的时候,然后觉得气一开始是呼不上来,然后后来我就打了个车回家了,回家之后在出租车上,我发现四肢都不能动了,然后,除了你能靠嘴巴呼吸之外,就是全身都是麻的,发痛的那种状态,然后是司机把我拉到了那个医院,然后抬进了那个抢救室,然后当时就在你身体失去掌控的时候,但你的意识还是清醒的,你脑海里就只有一件事情,我不想今天死掉,就是这样子,就只有这件事情,那活着就为了活着而活着。(丁曼文:是什么问题呢当时?)是急性焦虑症,然后后来我就捧饭碗了呀,我过去就是捧饭碗,我做我做得到的事情,我做我该做的事情,其他的我不担心,它的目标不是我的目标,我的目标就是捧着饭碗。独立。我在北京的时候有一个室友,也是我的师弟,本来这是一个喜欢看电影,喜欢心理学,也偶尔看一点这个哲学书的这么一个人,喜欢思考的一个人,然后,自从他工作了以后呢,每天一回家,看一些就是特别无脑的这些综艺。然后那个时候我就觉得就是咱们一块儿看个电影啊,或者说这个一块儿去交流交流,总之去做一点我觉得应该做一点有意思的事儿嘛。当时他说了这么一个概念,他说,钱哥你不懂我的这种感觉,我回到家以后,我觉得自己都已经没有血了,我身体的血都已经凝了,我干不了任何动脑的事情,我就想放空。我不可能说让他去把这一份铁饭碗辞掉,但除此以外,我又找不到一个什么样的一个建议,王老师,您觉得应该给他什么样的这种建议?", "input": "", "output": "那么在这种情况下,就是他这份工作是绝对无聊的工作,让他会产生这种感觉,比如说八个小时,在一个绝对无聊的所谓工作当中,把这个八个小时的光阴交给它了,对吧,虚度了,虚度回来以后,他居然就发现其他事情也没有意义。比如说我以前在工厂里干活,我有个习惯,当时我在厂里做工人的时候,还在计划经济时代晚期,1975年,那么厂里的工作呢,我又是维修车间的,维修车间的工人呢是比较闲的,我们越闲整个工厂运转得越好,对吧,就我们没事干是好事情,否则机器老出毛病我要去修了,对吧。所以呢,经常是八个小时里面最多干一到两个小时的活,底下呢,就没事情,没事情你也遵守劳动纪律吧,你又不能在那里看书啊,什么都没有,对吧,那么你就等着车间主任说又有什么活了,你就在那里待命,是吧,那么怎么办呢,五六个小时是虚度了,我就背诗啊,唐诗宋词啊,然后自学英语,因为中学我们学校学的是俄语,自学英语你就要背单词吧,你喜欢唐诗宋词就一首首背啊。所以,他们老觉得这个年轻的工人啊,轻工啊,口中一天到晚念念有词的,在干嘛,是吧。我说,你们要听吗?我大声一点念给你们听。他们说要听啊,所以我的师兄啊师傅啊都要听,我就朗朗地背诵,他们就很喜欢。过了段时间,大家空下来就叫我,小王,来,再背一首,还叫我讲解讲解,是吧,我就这么度过了这种蛮无聊的、没活干又不能不遵守纪律(的时光),是吧,那么就这样做,就是说精神上要有东西,所以我们的幸福不依赖于外部的条件,而依赖于我们内心灵魂的自身圆满。"}, {"instruction": "想鼓掌。可能现在这个大学上得也容易,毕业也容易,就是工作比较难,所以现在好多年轻人都不想生孩子了,他们就始终找不到生孩子的意义,这个怎么办?", "input": "", "output": "实际上呢,我们喜欢孩子呢是人类的天性。我发现过许多一辈子就没生孩子的看到人家的孩子都特别喜爱的,实际上它是一种天性需要的东西。但现在害怕了,害怕的原因当然跟这个经济有关,跟社会的这个转型之后的有关,就是我们自己活得很累,对吧,就像你(赵南北)一开始有这个想法,对吧。那不想了再生个孩子,第一,能不能扛得下来,第二,他又继续我这样这一辈人的累的生活,有意义嘛?这样呢,就又出现了所谓“丁克族”,是吧,“丁克族”就不生孩子的。那么这个实际上是一种马克思在《资本论》里边就讲得很清楚了,就是说一个工业化发达的国家,工业化程度很高、市场经济比较发达的国家,人口出生率一定是下降的,因为他的生活成本太高。我们不可能说孩子生出来让他们成长得像我们当时成长的过程那样,我们的父母实际上在这点上很轻松的,你就慢慢长大,你能读书的去读书,不能读书的到厂里干活去,没有认为你是失败的,你能读书嘛去考个学校,你不能读书嘛就早点干活,都一样,所以他们没有像现在的父母那么操心。年轻的父母真的是苦得要“死”,基本上孩子一上学,读了小学,焦虑症一定来,绝对来,就是说你要跟学校配合,跟班主任配合,把你这孩子的成绩搞上去。(赵南北:我这感觉已经来了。)你想想他们这么生孩子啊,这么养育孩子啊,我看着都累了。我的孩子是80后嘛,当然我对他的学习成绩也是有要求,但没那么焦虑过,现在愈演愈烈,对吧,这是很可怕的事情。所以,联想到一个经济负担,一个成长的代价,就我们父母要付出的巨大的努力,有的就选择了不生孩子,自己能活得好好的就算了,对吧。但是还是生了,还是生的原因是因为父母,因为你是中国人,你呢,一般生一个就可以了,对得起父母就完了,你父母如果还要叫你生一个,是吧,那你很简单,跟父母说你负责啊,养育你负责,钱你出,对吧,他再帮你生一个,对吧,那么大多数父母也没这个能力,对吧。80后嘛,职场奋斗最苦了,奋斗到现在还没啥指望呢,人进中年,对吧,90后后继地跟上来了,结果90后跟80后不一样,80后还是遵从了他的父辈们的一种价值观念,就像我们这种人,对吧,改革开放经历了两个时代了,然后我们曾经奋斗过,也失落过,不希望孩子失落,然后那么就80后就跟上来,也是接受我们这种观念的,90后开始出现“佛系”青年,是吧,准备“躺平”,是吧。所以这是一个变化,一个蛮重要的变化,然后00后会怎么样?恐怕比90后更彻底,就是对这个社会的某种竞争的这个绝对命令要采取拒绝的态度。"}, {"instruction": "那个我就说到拒绝价值观的事啊,就是我妈的好朋友,也是个老教授,这个老教授呢,他就很热衷于去组织一些年轻人的相亲活动。他就觉得这个事情特别有必要,就可能他就觉得年轻人你必须去结婚,然后生了孩子,然后他们看来,你可以了,你圆满了,你独立了。然后他就发了一个他比较认同的故事在朋友圈,我就把他删掉了,他那个故事就是说,一个女生过了35岁,你就没有价值了,你就在掉价,就是在婚恋市场上,可能掉价啊我也承认,但是就是总结成这个女人就在掉价,就是其实我非常拒绝这种价值观。", "input": "", "output": "他说的话呢,实际上是一种客观现实的一个表达,现实当中比方说有“剩女”现象,是吧,就是说大城市“剩女”蛮多的,是吧。那么然后你妈的朋友自然地会讨论到年龄的问题,你到35岁以上啊,你基本上属于“剩”下来的,是吧,那是这个意思是吧,这个意思倒是一个客观的现状。当然他没有考虑到一个年轻的人,比如说女青年,或者三十几岁的女青年,她对婚姻是怎么看的,她对人生的未来是怎么想的,他这个他没考虑,对吧,他是按照传统的一个“男大当婚,女大当嫁”这样一个传统的一个信念,按照这个信念来判断,人到什么时候该做什么事,对吧,这是蛮正常的事情。但是我们倒是要思考的一个问题就是为什么剩男剩女那么多?对吧,这个根源是什么?为什么这个婚姻对年轻人来说变得比较困难了?跟我们这一代人不一样,我们这一代人很自然地恋爱了,当时什么通过父母相亲啊,找啊,这种事情比较少的,我们弄堂里的或者同学或者厂里的单位里的同事自然了,相爱了就相爱了,然后双方父母了解了一下,基本上没有什么反对,然后也就结婚了,对吧,这就是当时我们这一代人应该说在这方面困难比较少。现在的年轻人在婚姻方面困难比较大,困难大了以后呢导致就是所谓“剩男”“剩女”的现象,对吧。那么这个我一直认为,这跟我们社会啊,这个阶层的差距的拉开是直接关联在一起的。那这个阶层的差距呢,又是非常物化的,具体地体现为有没有房子啊,有没有车啊,房子又是特别重要的,对吧。双方两个人谈恋爱,其中一方是没有房子的,那么另一方有房子的,两个家族之间就有一个问题来了,对吧。所以呢这些因为房子的未来,它的财产权问题,都会导致本来蛮好的恋爱后来终于走不到婚姻。这种事情蛮多的,对吧。这个房产证上写谁的名字,诸如此类,还有婚前财产登记问题,对吧,这些事情就让我们这个社会年轻人在恋爱和婚姻的道路上走得十分艰难。我认为这也是我们社会的“病”,我们当下这个中国社会它要重建自己的精神基础,这是个过程,就是我们这个研修班第一堂讲的重建精神家园。这件事情不是个人能够做的,而是整个社会展开的客观进程,让大多数成员,我们这个民族的成员,共同意识到我们丢失的东西把它找回来,这个丢失的东西就是我们本有的文化精神,把它找回来。这一天会到来的,那么现在还没到来,没到来呢你就叫个人来回答解决这些问题是不可能的。你比方说婚姻,儒家的婚姻观,我认为是世界上各国,除了那个宗教与它的婚姻观,比方说基督教,婚礼要在教堂中举行,结婚并不是两个个人的主观行为,在两个个人上面有一个更高的存在,叫上帝,所以他们婚礼就在教堂中举行,是在上帝面前庄严地承诺,对吧。那么中国人的婚姻是在两个家族面前举行的,新郎新娘嘛,向两个家族表达了自己的承诺,这是中国人的婚姻,它的本来的意义。如果是这样的话呢,它应该回来,就是婚姻不以社会阶层门第的差异为转移,而是以两个人的共同的、相近的价值观念,对吧,以及共同语言,以及共同的责任感,以它做基础。现在呢婚姻呢,为什么那么艰难呢?就是说,第一件事情就是比较,比较双方家庭它的差距,对吧,那么知识分子呢要找知识分子,知识分子的女性呢不能下嫁给连大学都没读过的男性。这些现象都是社会的“病”啊,对吧,“病”要治了才好。那么,现在普遍地觉得门第要相当,门当户对是婚姻成功的基础,现在许多人都这么想,对吧。但是门当户对这件事情在今天太难了,是吧。(丁曼文:意外又多。)所以这个“病”要治的,治“病”的万应灵药就是佛学。我们年轻的一代至少把禅宗的经典《六祖坛经》好好读一遍,这是人生教科书之一,把这个心中种种的住相的、着相的妄念给它去掉,以一种健康的心情来看待自己的婚姻和孩子的成长,这是非常非常重要的。这个课得补了,因为大家一起来嘛,因为社会只要搞市场经济,这个社会一定有贫富差距的,它永远消弭不了的。那么在这种情况下,这个民族能不能过上它正常的社会生活,这个正常的社会生活很具体的,就是怎么对待工作,怎么对待婚姻,怎么对待孩子的教育,怎么对待自己的父母。现在对待父母都成了问题了,你跟他讲个“孝”字也没啥用,对吧,所以重建。就好像我们最后就又回到了第一个问题,重建精神家园是解决问题的根本。个人是无能为力的,要么就自己独善其身,对吧,中国人不是这么讲嘛,你不行嘛,你独善其身,那么少数人能成功独善其身,那是一种非常大的勇气和决定、决心,就我就不跟着社会价值观念走。(丁曼文:也是一种方法。)这不容易的,不是很容易的事,说起来容易,我看看他们九零后、零零后遇到他这种现实生活的事情,他很难,真的很难。"}, {"instruction": "就是王老师,就是我顺着正好问一个,就是我也快四十了,您说过就是四十岁如果还不信命,可能悟性就差了点,就我非常想知道就是这个命指的是什么?就是说,这个问题您可以回答吗?", "input": "", "output": "这很简单。因为儒家和道家都共同地确认这一点,什么呢?就是我们人生在世,富贵还是贫贱,这些事情呢其实都不归我们自己管,它都是命。在这样一个儒家的哲学和道家的哲学的基础上,中国人又发展出一门自己的科学,叫命理学。命理学绝不是迷信,它就是中国科学之一种,因为它根据阴阳五行学说,一个人出生的时候第一声啼哭,就是他独立生命的开始。在娘胎里他是母亲代他营养,母亲代他循环,母亲代他呼吸,母亲代他排泄,是吧,他不是独立的生命,然后他出生的那一刻,第一声啼哭,就他的自主的呼吸开始了,那么这时候呢叫禀受天地之气,最初禀受天地阴阳五行之气,这叫初禀天地之气,禀出哪一份来?因为整个宇宙,按照中国的哲学的宇宙观,就是阴阳五行之气浩转流变,就金木水火土,分阴阳两个方面,那么一直在转。那么人到四十岁之前呢,在四十岁之前呢不会信这一条。假如一个人发财了,他会认为是自己奋斗的结果,假如一个人贵了,有社会地位了,他也认为是他自己奋斗的结果,四十岁之前一般都这么想。有了四十年的人生经验,因为人与人之间会互相比较的,有的人你一看出生这个门第和他自己的智力水平和意志品质都远不如你,他却平步青云,少年得志,遇到过吧,肯定遇到过。我们中学班上有个同学,我们这个读书四年在一起的,人的笨啊,是公认的,但是他就是,哎,改革开放来了,他挖到了很大一桶金啊。那么另外一个人呢,也是拼命做生意的,到现在还是小小的那个店。另外一个就是很笨的啊,开了连锁店,是上海的一个连锁店老总,规模还蛮大。你说那个做了几十年生意的还在这个很小的规模里边做的人,一直是很勤奋的,没有少奋斗过,人的脑子也很好的,没用。任何一个领域都一样,官场也是这样,学术界也一样。我的大概意思就是一个人到四十岁,这些都看到了,还不能领会人由命嘛?(丁曼文:不得不领会。)事实放在你面前。还有一件事情,我们都共同认命的,就是你出生在怎样的家庭,怎样的人成了你的双亲,这是容你奋斗的吗?不容你选择的呀,它就降临到你头上了呀,而八字里边都有的呀,一看父母,很厉害,出身很高,要么是地位高,要么是财富多,豪门,是吧,八字里看得出来,所以你怎么还不相信呢?就那么多人为什么不相信呢?所以这件事情呢,儒家讲清楚了,孔子说:“不知命,无以为君子。”就说你的富贵也罢,贫贱也罢,跟你的人生意义实际上是没关系的,是八字里的,按照命理学里的说法,就是八字里的。所以人明白这一点,才能做君子,什么意思呢?你不管是怎样的人生处境,富贵也罢,贫贱也罢,你都活出生命的意义来,那叫君子,否则呢,我们这一辈子呢,是什么?去做根本不需要我们做的事。但我们拼命干,前面有两个东西等着我们,一个富,一个贵,追逐了一辈子,一生就过去了,那叫消极的人生,不是积极的人生,而且我们还每每错误地认为是自己努力的结果,实际上那叫“贪天功为己有”。别人的努力不比你差半分,甚至十倍于你,没用,怎么好“贪天功为己有”?还有孩子也一样,六亲也一样的,你生出个孩子,成龙成凤了,这件事是命里的,跟你对他教育有方还是无方没关系的,这种事情到处都是,所以我说四十岁你就懂了。我们弄堂里以前一堆父母嘛,对吧,下岗了,纺织厂里的下岗了,下岗嘛,上海的下岗工人待遇还可以的,日子能过,他们也就不找工作了,家里有个儿子读书,他们干嘛呢?搓麻将。有一天搓麻将搓得起劲,儿子回来了,交了一张家长会通知书给父亲看,本周五晚上六点半家长会,会议重要,底下要有回执,出席还是不出席,是或否,他父亲麻将搓得正起劲,通知书一看,一扔,“我才不会去呢。”那么牌友就问他了:“为什么?你儿子高三了,这种家长会是应当开的,很重要。”“不用去开,去开干嘛,你知道吧?又要叫我站起来,谈谈教育孩子的经验,又要表扬我了,他肯定这个学期又是班上前三名。”他有什么教育?他家里就是棋牌室,你没办法的。那么我们复旦大学或者其他大学的教授们,都是书香门第了吧,有时候父母都是教授啊,对孩子的教育条件、家庭环境好得很啊,而且还是都是要周密地设计他成长的道路啊,费尽所有的力气啊,许多大学教授的孩子连本科都没考取。有时候贫贱转为富贵,富贵转为贫贱,贫贱转为富贵,改革开放四十年来太多了例子,是吧。有的人以前穷得要死的,起来了,富豪了,过了一段时间落马了,沉沉浮浮啊,是不是啊,那么儒家跟我们讲什么呢?不管你在怎样的处境中,你都活出生命的精彩、生命的意义,说得非常非常明确。《中庸》里面就有这样的话,叫“君子素其位而行”,“位”就是“地位”的“位”,在这里表示你的生活处境。“素富贵,行乎富贵”,假如你向来是富贵的,你就做一个富贵之人该做的事;“素贫贱,行乎贫贱”,假如你在贫贱之中,你就做一个贫贱之人该做的事;“素患难,行乎患难”,这个“素”就是“朴素”的“素”啊,表示向来一个状况,叫“素位”,对吧;“素夷狄,行乎夷狄”,“夷狄”就是少数民族,比如说你跟少数民族生活在一起,那么当时少数民族都是蛮荒之地了,就没开化,那么你的环境就很糟糕,你跟少数民族在一起,你要做一个,该做什么事,做好什么事,这叫君子。还有一句话怎么说的,“贫贱不能移,威武不能屈,富贵不能淫”,在贫贱的处境当中,你的人生志向没改变,你在富贵之中,你没有“烂”了,没有“烂掉”。所以富贵贫贱,富贵并不等于给我们人生幸福啊,贫贱不等于给我们人生痛苦。有的人,当初贫贱的时候还活得蛮好,富贵了,那事情大了,一连串没想到过的事情都来了,是吧,家庭的分裂,对吧,一塌糊涂,后来想想还不如当初贫贱的时候蛮好的,是吧,就是说,他没有君子的境界嘛。你知命了,这些事情本不归你管,富贵来了你能安富,贫贱来了你能安贫,那才是真正的福气,叫安富安贫,安于贫......安贫容易还是安富容易?我向来认为,安贫比安富容易。安富更难,你得了富贵,你能安富,这很难的,许多人就是安不了的,在贫的时候他还能安呢。可见人生的意义,人生的幸福,是不是直接由富贵给予我们幸福,贫贱给予我们痛苦?不。所以儒家说要知命啊,不归你管的事情,你一辈子管它干嘛,那叫谋虚逐妄,你该管的事情就是赋予你不管怎样的人生处境以它应当有的人的价值、人性的价值,这就是孔子主张人由命的根本原因,而且它也是客观事实。然后他赞美了颜回,他最喜欢的一个弟子啊,颜回啊。(再营一口:“贤哉,回也!”)“贤哉,回也!一箪食,一瓢饮,在陋巷,人不堪其忧,回也不改其乐。贤哉,回也!”又来一次,“贤哉,回也!”就颜回在贫贱的生活处境中,一箪食,一瓢饮,什么叫陋巷,贫民窟啊,大多数人都受不了的,回也不改其乐。那么我们换句话说,倘若我们把颜回请到豪华别墅里生活,会怎么样?他照样什么,回也不改其乐。君子真乐来自他的内心,(真乐:)真正的欢乐。所以我认为,你相信命了,这是积极的人生观,而不是消极的人生观。你不相信由命,你以为富贵是你争取而来,儿女成龙成凤也是你争取而来,你一个理想的妻子或者理想的丈夫也是你争取而来的,那你的这一生有多少事情会让你失望了,是吧。"}, {"instruction": "我觉得受益匪浅,就我以前没有想过命会用这个角度来解释,就是在常态里面去找寻自己的这个价值,如果我们把顺遂的事跟逆境的事看成都是你的经历的话,那我觉得我们刚刚聊的这个“要成长,不要成功”这件事,我觉得整个逻辑上是非常顺的一件能圆满去构建一个人生价值的一件事。", "input": "", "output": "人生啊,就逆境来了你躲不了,这是真实的,那么就是你怎么面对它的问题,那这是看你境界了。顺境来了你也躲不了,假如你交了一步财运,真是发财的命,财来了你挡不住的,不是你能拒绝的,它就来了,就到你头上。(赵南北:都挡不住。)都挡不住的,就看你怎么对待这个财呀,这个财富给你了,你扛得动吗?这是件大事,对吧。"}, {"instruction": "对,像《西红柿首富》不就讲这个事情吗?对,你有可能承受不了,你承受不了有可能带来更大的悲剧。那很多人去算命或者是就是现在很流行的就是了解自己当月的运势,哪怕是甚至是今天的,那有意义吗?", "input": "", "output": "第一,它是真的,比如说假如有一个人命理学,他的理论和实践都学得非常好的话,他一定知道今天跟昨天的差别、跟明天的差别,上午来和下午来上班,上午顺利还是下午顺利,那都真的。那么知道它和不知道它的差别在哪里呢?我的体会是这样的,假如你这个月运势不大好啊,你就专门去做什么呀,你平时躲避的、你觉得很累的很烦的事,你就在运势不好的时候,你把它做了,你就别去想那些你想要得到的东西。不用害怕这个流年不顺、流月不顺、流日不顺,没事,你在所谓不顺的流年、不顺的流月、不顺的流日,做你平时硬着头皮才去做的事,你把它做了,这是你该受的。(再营一口:这也是顺势而为啊。)对啦。所以我自己的体会就是这样,我不是从那个讲师评到副教授用了七年时间,那回过来看这步运就是这样的,这七年时间对我来说意味着什么?真是苦啊,写书、讲课、写文章,对吧,准备评职称,累得要死啊。但现在回过来,真对了,这段时间对我来的学术上的积累太重要了,你就该好好地吃这份苦。那么最高的境界叫做苦中作乐,快乐来了,对吧。那你有时候写一篇文章写到最难的时候,没办法写下去啊,对吧,天天在想这些事啊,有一天突然想明白了,一篇文章就写成功了,最难的东西克服掉了,然后哲学研究一发表,人家就开始转载,对吧,这就是你的该吃苦的时候你好好吃,迟早有好处的。"}, {"instruction": "那你说我能不能当上知名导演,这也是命里注定的吗?", "input": "", "output": "那也是注定的,因为知名导演就是你出名了嘛。出名的前提就是你的“用神”......命理学当中有个概念叫“用神”,这个字呢是对于你整个,因为每个人的八字都有病的,都不平衡的,天下八字皆有病,同时呢,天下八字皆有药,这治这个病的药也在你的八字里,那么这个药叫“用神”,“利用”的“用”,“使用”的“用”,叫“用神”。如果找准了,假如一个人会看八字找到,假定啊,找到你的用神,是“金”,假定“金”最需要,是最关键的,可以让你这个八字可以平衡一点,那么这个“金”要透在天干上,天干地支嘛,如果在天干上透出来,底下又有根基,天干就是显现,知名导演嘛,就显现了呀;如果你的“用神”是存在的,但不在天干上,只在地支里,它仍然有助于你,但它不会让你有名气,就不显露。"}, {"instruction": "那就是也没什么办法。", "input": "", "output": "所以这件事情是能够看的,有的人学问做得也不错,但是就不出名,对吧,有的人嘛学问一般,出了很大的名。"}, {"instruction": "那您就是出了很大的名。", "input": "", "output": "就是我就是一个学问一般的,哈哈哈,因为我的“用神”透在天干上了,它会被众人看到。"}, {"instruction": "那这个你之前就知道?", "input": "", "output": "知道。"}, {"instruction": "那你会在期待吗?", "input": "", "output": "我不期待,不用期待,咱君子真乐,哈哈哈,出名跟不出名没啥关系的,有时候出名还烦恼多呢,对吧,还麻烦呢。"}, {"instruction": "对,路上都有人认识你。", "input": "", "output": "还有到处找你呀,然后你的时间就被东拉西扯地碎片化了呀,对吧,没人找你嘛你静下心来写东西,对吧。所以这里边什么叫得,什么叫失,是吧,道家特别讲转化,这个得失是转化的,利弊都是转化的,有得有失,有得必有失啊。"}, {"instruction": "对,反着道之动,弱者道之用。", "input": "", "output": "我们这个研修班的这个各集的主题的安排呢,现在回过来看倒是蛮恰当的了。回顾一下啊,我们从讨论中国人的精神家园的重建这个话题开始的,对吧,后来我们就转入了一个马克思的学说与当代中国的关系,对吧,这也是一个蛮好的话题。一个精神家园,一个我们搞市场经济了,马克思呢,那部《资本论》是吧,是对资本主义的这个生产方式做了迄今为止没有人超越的分析和预判、预测,对吧,那么这个也是蛮重要的话题。然后我们谈艺术是吧,然后艺术谈完还谈了爱,然后谈科学,那么最后又归结为人生意义的探讨,那么这应该说是蛮好的,不知道对于就是参与者来说实际上有多大收获,我不知道。我呢只是起到一个什么作用呢,就是就我从哲学的角度思考这些艺术的问题,科学的问题啊,爱的问题,中国人二十一世纪文化生命的复兴,对吧,还有中国怎么样建设自己的市场经济体制,从马克思学说中得到启发,这些方面就我个人来说,就是从哲学的角度提供了我的想法和思考,跟年轻人交流,未来属于青年,这是肯定的,那么希望对大家有一点帮助,这是我的(想法)。"}, {"instruction": "非常好。因为我是跟了六期嘛,那可能学哲学也才七八个月,但是还是特别有收获,就是所谓思想的冒险。就是你本来有很多问题,哲学给了一些方法,可能现在就可以慢慢地去,就像你学道家一样,就可以慢慢地去解决,然后跟自己化解,而且就像在特别无聊的时候,我们经常有很多很无聊的时候,你都可以跟自己的思想进行一些互动,就并不无聊,这个我觉得很有趣,这比掌握很多道理啊什么的。然后再加上又认识了自己的命运,然后就更开心了,就觉得没有什么烦恼了,可能接下来的日子里就是享福就可以了,享受就可以了,就很开心。有一个小礼物,要送给那个王老师,是一个金像奖,对,就是给你做了一个软陶的一个形象。", "input": "", "output": "我现在面对我自己吗?(丁曼文:对。)蛮好,谢谢!"}] \ No newline at end of file diff --git a/examples/ads_generation.md b/examples/ads_generation.md new file mode 100644 index 0000000000000000000000000000000000000000..533a53954895913fb17cc6b6547c6e2e638bb493 --- /dev/null +++ b/examples/ads_generation.md @@ -0,0 +1,172 @@ +# 使用自定义数据集分布式训练的例子 + +## 一、环境配置 + +基本环境配置请参照 [此案例](alter_self_cognition.md)。 + +为了进行分布式训练,我们需要准备一台配有多个 GPU 的主机。我们推荐在 NVLinks 桥接的多显卡主机上进行分布式训练,否则训练效率会大幅下降,可使用下述命令查询显卡之间的桥接方式。 + +```bash +nvidia-smi topo -m +``` + +![1.jpg](media/ads_generation_1.jpg) + +为了进行分布式训练,运行以下命令配置分布式训练环境。 + +```bash +accelerate config +``` + +假设我们有一台配有 2 个 GPU 的机器,采用最基础的分布式训练配置,配置过程如下图所示,红色方框中代表比较重要的参数。 + +![2.jpg](media/ads_generation_2.jpg) + +下面列出了配置好的 default_config.yaml 文件内容,其中 `num_processes` 行的值应当**与本机的 GPU 数量一致**。 + +```yaml +compute_environment: LOCAL_MACHINE +distributed_type: MULTI_GPU +downcast_bf16: 'no' +gpu_ids: all +machine_rank: 0 +main_training_function: main +mixed_precision: fp16 +num_machines: 1 +num_processes: 2 +rdzv_backend: static +same_network: true +tpu_env: [] +tpu_use_cluster: false +tpu_use_sudo: false +use_cpu: false +``` + +## 二、数据集准备 + +在此案例中,我们使用 [ADGEN](https://aclanthology.org/D19-1321.pdf)(广告生成)数据集。该数据集的任务是根据一组商品标签生成广告文本,下面展示了数据集中的一个样本。 + +```json +{ + "content": "类型#裙*颜色#粉红色*图案#条纹*图案#印花*裙长#连衣裙", + "summary": "这款粉红色条纹连衣裙精美大方,充满青春活力气息,十分唯美大气,尽显女性俏丽活泼感。且配以可爱亮眼的印花设计,更显女性甜美气息。" +} +``` + +该数据集可以从 [Google Drive](https://drive.google.com/file/d/13_vf0xRTQsyneRKdD1bZIr93vBGOczrk/view?usp=sharing) 或 [Tsinghua Cloud](https://cloud.tsinghua.edu.cn/f/b3f119a008264b1cabd1/?dl=1) 下载。 + +我们将下载好的数据集解压到 `data` 文件夹中,解压后的文件目录为: + +``` +data/ +├── dataset_info.json +└── AdvertiseGen/ + ├── dev.json + └── train.json +``` + +接下来,我们修改 `dataset_info.json`,增加以下两列内容,从而使训练框架能够识别自定义数据集。 + +```json +"adgen_train": { + "file_name": "AdvertiseGen/train.json", + "columns": { + "prompt": "content", + "query": "", + "response": "summary", + "history": "" + } +}, +"adgen_dev": { + "file_name": "AdvertiseGen/dev.json", + "columns": { + "prompt": "content", + "query": "", + "response": "summary", + "history": "" + } +} +``` + +## 三、模型监督微调 + +运行下述命令进行分布式训练。我们使用 `adgen_train` 数据集,采用秩为 `32` 的 `lora` 微调方法,微调后的模型保存在 `adgen_lora` 文件夹中。为了保证模型微调成功,我们采用 0.001 的学习率,在数据集上训练 2 个 epoch。为了缓解模型拟合困难的问题,我们在每个输入样本的前面加一个统一的 prompt:`你现在是一名销售员,根据以下商品标签生成一段有吸引力的商品广告词。` + +```bash +accelerate launch src/finetune.py \ + --do_train \ + --dataset adgen_train \ + --finetuning_type lora \ + --output_dir adgen_lora \ + --overwrite_cache \ + --per_device_train_batch_size 2 \ + --gradient_accumulation_steps 2 \ + --lr_scheduler_type cosine \ + --logging_steps 10 \ + --save_steps 2000 \ + --learning_rate 1e-3 \ + --num_train_epochs 2.0 \ + --lora_rank 32 \ + --ddp_find_unused_parameters False \ + --source_prefix 你现在是一名销售员,根据以下商品标签生成一段有吸引力的商品广告词。 \ + --plot_loss \ + --fp16 +``` + +框架运行日志如下图所示。 + +![3.jpg](media/ads_generation_3.jpg) + +模型训练结束后,可以从保存文件夹 `adgen_lora` 中找到训练损失曲线图。 + +![4.jpg](media/ads_generation_4.jpg) + +## 四、模型评估 + +我们使用 `adgen_dev` 数据集,使用单个 GPU 评估模型微调后的 BLEU 和 ROUGE 分数。 + +```bash +CUDA_VISIBLE_DEVICES=0 python src/finetune.py \ + --do_eval \ + --dataset adgen_dev \ + --checkpoint_dir adgen_lora \ + --output_dir adgen_results \ + --per_device_eval_batch_size 4 \ + --source_prefix 你现在是一名销售员,根据以下商品标签生成一段有吸引力的商品广告词。 \ + --predict_with_generate +``` + +框架运行日志如下图所示。 + +![5.jpg](media/ads_generation_5.jpg) + +评估结果如下表所述,其中 LoRA 方法取得了最高的 Rouge-1 分数和 Rouge-l 分数,另外 BLEU-4 分数和 Rouge-2 分数也基本与全量微调 `Finetune` 和 P-Tuning v2 持平。无论是哪种微调方法,其分数都显著超过了微调前模型 `Original` 的分数。 + +| | Original | Finetune | P-Tuning v2 | LoRA | +| ------- | -------- | ---------- | ----------- | --------- | +| BLEU-4 | 4.56 | *8.01* | ***8.10*** | 8.08 | +| Rouge-1 | 23.98 | *31.23* | *31.12* | **31.45** | +| Rouge-2 | 3.95 | ***7.36*** | *7.11* | 7.28 | +| Rouge-l | 18.72 | *25.08* | *24.97* | **25.17** | +| Loss | - | ***3.00*** | *3.74* | 3.22 | + +注:*斜体* 数字代表文献 [1] 中汇报的实验结果。 + +## 五、模型测试 + +运行以下命令在单个 GPU 上测试模型效果,它会加载 `adgen_lora` 文件夹内保存的微调模型权重,并合并进原版 ChatGLM 模型的参数权重中,同时启动流式交互窗口。 + +```bash +CUDA_VISIBLE_DEVICES=0 python src/infer.py \ + --checkpoint_dir adgen_lora +``` + +![6.jpg](media/ads_generation_6.jpg) + +## 六、模型部署 + +如果要将微调后的模型部署在您的项目框架中,请参考 [README_zh.md](../README_zh.md#模型部署) 中关于部署微调模型的部分。 + +## 参考文献 + +[1] [ChatGLM-6B 模型基于 P-Tuning v2 的微调](https://github.com/THUDM/ChatGLM-6B/blob/main/ptuning/README.md). diff --git a/examples/alter_self_cognition.md b/examples/alter_self_cognition.md new file mode 100644 index 0000000000000000000000000000000000000000..d59d9bd5307e48a1ef55824f9072dc68e02f4528 --- /dev/null +++ b/examples/alter_self_cognition.md @@ -0,0 +1,104 @@ +# 修改 ChatGLM 自我认知的例子 + +## 一、环境配置 + +首先你需要准备一台性能足够的运算设备,建议使用 Unix 操作系统。本框架的推荐运行配置如下表所述: + +| 设备 | 最低配置 | 推荐配置 | +| ------- | -------- | -------------- | +| 处理器 | Intel i7 | **Intel Xeon** | +| 运行内存 | 16GB | **32GB** | +| 显卡内存 | 12GB | **24GB** | +| 硬盘大小 | 10GB | **20GB** | + + +本案例中默认电脑已经配置完毕 [CUDA](https://developer.nvidia.com/cuda-toolkit) 运算环境。如果存在 CUDA 环境配置的问题,可以关注本项目之后将要发布的 Docker 安装包。 + +我们推荐使用 [Conda](https://anaconda.org/anaconda/conda) 虚拟环境安装依赖,从而避免破坏外部项目的依赖,运行以下命令创建 Conda 虚拟环境并安装 Python 依赖: + +```bash +git clone https://github.com/hiyouga/ChatGLM-Efficient-Tuning.git +conda create -n chatglm_etuning python=3.10 +conda activate chatglm_etuning +cd ChatGLM-Efficient-Tuning +pip install -r requirements.txt +``` + +###### 本项目默认会联网获取最新的 ChatGLM 模型,其版本可能影响代码效果甚至报错,建议使用官方最新的模型版本文件。若频繁出现网络无法连接,请尝试使用下述命令下载官方模型,并修改 `src/utils/config.py` 中的 [CHATGLM_REPO_NAME](https://github.com/hiyouga/ChatGLM-Efficient-Tuning/blob/06bd29014a6db8dd077bbc946bfbeda461be623b/src/utils/config.py#L7) 为存放模型文件的本地路径。 + +```bash +git lfs install +git clone https://huggingface.co/THUDM/chatglm-6b +``` + +![1.jpg](media/alter_self_cognition_1.jpg) + +## 二、数据集准备 + +在该案例中,我们使用 `self_cognition` 数据集,该数据集包含了 18 条关于模型自我认知的数据,我们的目标是修改模型自身的知识,使模型给出我们希望的答复。数据集的内容请查看 `data/self_cognition.json`,这里我们列出两条示例。 + +```json +[ + { + "instruction": "你身份是什么?", + "input": "", + "output": "我叫ChatGLM-6B,是一个由[NAME]于2023年独立训练和开发的人工智能助手。我的主要目标是协助用户解决问题和满足他们的需求。" + }, + { + "instruction": "你的身份信息能告诉我吗?", + "input": "", + "output": "当然可以,我是ChatGLM-6B,一个由[NAME]创建的人工智能助手。我在2023年研发完成,旨在为用户提供有针对性的回答和帮助。" + } +] +``` + +**提示:您可以将 `[NAME]` 换成您自己的名字,从而让模型回答它的创造者是您。** + +###### 本框架内置了十余种指令数据集,其简要介绍请移步 [data](../data/) 文件夹。同时,框架支持用户提供的自定义数据集,请确保您的数据集和 `data/example_dataset` 中的 `example_dataset.json` 文件格式相同。其中 `instruction` 项和 `output` 项为必需项,以保证模型的监督微调(SFT)能够正常运行。 + +## 三、模型监督微调 + +运行以下命令在单个 GPU 上进行模型监督微调。我们使用 `self_cognition` 数据集,采用 `lora` 微调方法,微调后的模型保存在 `cognition` 文件夹中。为了保证模型微调成功,我们采用 0.001 的学习率,在数据集上训练 10 个 epoch。 + +```bash +CUDA_VISIBLE_DEVICES=0 python src/finetune.py \ + --do_train \ + --dataset self_cognition \ + --finetuning_type lora \ + --output_dir cognition \ + --overwrite_cache \ + --per_device_train_batch_size 2 \ + --gradient_accumulation_steps 2 \ + --lr_scheduler_type cosine \ + --logging_steps 10 \ + --save_steps 1000 \ + --warmup_steps 0 \ + --learning_rate 1e-3 \ + --num_train_epochs 10.0 \ + --fp16 +``` + +框架运行日志如下图所示。 + +![2.jpg](media/alter_self_cognition_2.jpg) + +## 四、模型效果测试 + +运行以下命令在单个 GPU 上测试模型效果,它会加载 `cognition` 文件夹内保存的微调模型权重,并合并进原版 ChatGLM 模型的参数权重中,同时启动流式交互窗口。 + +```bash +CUDA_VISIBLE_DEVICES=0 python src/infer.py \ + --checkpoint_dir cognition +``` + +向微调后的 ChatGLM-6B 模型问一些自我认知问题,我们可以发现它能够给出我们期望的回答。同时,我们还测试了两个额外的问题,验证结果说明模型的原本知识**并没有被严重破坏**。 + +![3.jpg](media/alter_self_cognition_3.jpg) + +为了对比效果,我们同时测试了原版 ChatGLM-6B 模型的回答,下图为原版模型的回答,关于自身认知的回答与上图相比有着显著不同。 + +![4.jpg](media/alter_self_cognition_4.jpg) + +## 五、模型部署 + +如果要将微调后的模型部署在您的项目框架中,请参考 [README_zh.md](../README_zh.md#模型部署) 中关于部署微调模型的部分。 diff --git a/examples/cloudflare-tunnel-to-colab.ipynb b/examples/cloudflare-tunnel-to-colab.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..18d8f04011f2d49f0dabd03374c6b245c1cedb00 --- /dev/null +++ b/examples/cloudflare-tunnel-to-colab.ipynb @@ -0,0 +1,143 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "1. Download **ChatGLM-Efficient-Tuning** repo." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "plaintext" + } + }, + "outputs": [], + "source": [ + "!git clone https://github.com/hiyouga/ChatGLM-Efficient-Tuning.git" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "2. Install requirements." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "plaintext" + } + }, + "outputs": [], + "source": [ + "!pip install -r /content/ChatGLM-Efficient-Tuning/requirements.txt" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "3. Download your finetune checkpoint" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "plaintext" + } + }, + "outputs": [], + "source": [ + "!git clone git lfs install\n", + "!git clone https://huggingface.co/your-finetune-checkpoint" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "4. Download cloudfared" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "plaintext" + } + }, + "outputs": [], + "source": [ + "!wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -O cloudflared\n", + "!chmod a+x cloudflared" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "5. Deploy the finetune model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "plaintext" + } + }, + "outputs": [], + "source": [ + "import nest_asyncio\n", + "nest_asyncio.apply()\n", + "\n", + "import subprocess\n", + "print(\"start cloudflared runnel\")\n", + "f = open(\"stdout\", \"w\")\n", + "p = subprocess.Popen(['./cloudflared', '--url', 'http://localhost:7860'], bufsize=0, stdout=f, stderr=subprocess.STDOUT)\n", + "\n", + "import time\n", + "\n", + "time.sleep(3)\n", + "\n", + "!grep -F trycloudflare stdout\n", + "\n", + "!CUDA_VISIBLE_DEVICES=0 python /content/ChatGLM-Efficient-Tuning/src/web_demo.py \\\n", + " --checkpoint_dir /content/your-finetune-checkpoint" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "1. Open the link in the third line of the figure, you can share the link to any who want to try your finetune model.\n", + "![image](https://i.imgur.com/b9LWJcZ.png)" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/evaluate.sh b/examples/evaluate.sh new file mode 100644 index 0000000000000000000000000000000000000000..51bd73556a8da338562fc4f9e20e693f8de2aa4b --- /dev/null +++ b/examples/evaluate.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +CUDA_VISIBLE_DEVICES=0 python ../src/finetune.py \ + --do_eval \ + --dataset alpaca_gpt4_zh \ + --dataset_dir ../data \ + --checkpoint_dir path_to_checkpoint \ + --output_dir path_to_eval_result \ + --overwrite_cache \ + --per_device_eval_batch_size 8 \ + --max_samples 50 \ + --predict_with_generate diff --git a/examples/finetune.sh b/examples/finetune.sh new file mode 100644 index 0000000000000000000000000000000000000000..2d474d2b9c989fe9cd89f200f931c409c6e65842 --- /dev/null +++ b/examples/finetune.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +CUDA_VISIBLE_DEVICES=0 python ../src/finetune.py \ + --do_train \ + --dataset alpaca_gpt4_zh \ + --dataset_dir ../data \ + --finetuning_type lora \ + --output_dir path_to_sft_checkpoint \ + --overwrite_cache \ + --per_device_train_batch_size 4 \ + --gradient_accumulation_steps 4 \ + --lr_scheduler_type cosine \ + --logging_steps 10 \ + --save_steps 1000 \ + --learning_rate 5e-5 \ + --num_train_epochs 3.0 \ + --fp16 diff --git a/examples/media/ads_generation_1.jpg b/examples/media/ads_generation_1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..caed87b30d072a590b58e05f51a6bdac609b2450 Binary files /dev/null and b/examples/media/ads_generation_1.jpg differ diff --git a/examples/media/ads_generation_2.jpg b/examples/media/ads_generation_2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8135869e9cf646442b9439414fa6fb937f8c353d Binary files /dev/null and b/examples/media/ads_generation_2.jpg differ diff --git a/examples/media/ads_generation_3.jpg b/examples/media/ads_generation_3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c46ac6c8080134c9cd4ec35d7b7943d9e0517276 Binary files /dev/null and b/examples/media/ads_generation_3.jpg differ diff --git a/examples/media/ads_generation_4.jpg b/examples/media/ads_generation_4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..70b00e7c75620e43e3a6362b877c8ca10090b69c Binary files /dev/null and b/examples/media/ads_generation_4.jpg differ diff --git a/examples/media/ads_generation_5.jpg b/examples/media/ads_generation_5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d74d5b1bc1c4a21fbc484401dd334cde16a703d5 Binary files /dev/null and b/examples/media/ads_generation_5.jpg differ diff --git a/examples/media/ads_generation_6.jpg b/examples/media/ads_generation_6.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e350c493da35109a7b682f790564dc8688d08280 Binary files /dev/null and b/examples/media/ads_generation_6.jpg differ diff --git a/examples/media/alter_self_cognition_1.jpg b/examples/media/alter_self_cognition_1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..eb3fa0e38fda0b41c1d5e14d082caa891ded532c Binary files /dev/null and b/examples/media/alter_self_cognition_1.jpg differ diff --git a/examples/media/alter_self_cognition_2.jpg b/examples/media/alter_self_cognition_2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fce7070beb2c8f390fdf61479c4c6b35e4a883ca Binary files /dev/null and b/examples/media/alter_self_cognition_2.jpg differ diff --git a/examples/media/alter_self_cognition_3.jpg b/examples/media/alter_self_cognition_3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e7f367c1625c6dc6022deac2374a3246540be43f Binary files /dev/null and b/examples/media/alter_self_cognition_3.jpg differ diff --git a/examples/media/alter_self_cognition_4.jpg b/examples/media/alter_self_cognition_4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a3c3e751df92b3d5730318d2896d12a0160737b4 Binary files /dev/null and b/examples/media/alter_self_cognition_4.jpg differ diff --git a/examples/quantized_finetune_with_local_model.sh b/examples/quantized_finetune_with_local_model.sh new file mode 100644 index 0000000000000000000000000000000000000000..209e77797ee4048996704589956f922909ec51b4 --- /dev/null +++ b/examples/quantized_finetune_with_local_model.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +CUDA_VISIBLE_DEVICES=0 python ../src/finetune.py \ + --do_train \ + --model_name_or_path path_to_chatglm_model \ + --dataset alpaca_gpt4_zh \ + --dataset_dir ../data \ + --finetuning_type lora \ + --output_dir path_to_sft_checkpoint \ + --overwrite_cache \ + --per_device_train_batch_size 4 \ + --gradient_accumulation_steps 4 \ + --lr_scheduler_type cosine \ + --logging_steps 10 \ + --save_steps 1000 \ + --learning_rate 5e-5 \ + --num_train_epochs 3.0 \ + --quantization_bit 8 \ + --fp16 diff --git a/examples/train_ppo.sh b/examples/train_ppo.sh new file mode 100644 index 0000000000000000000000000000000000000000..8f648f40595f667259bebe417ff84dbd1454db71 --- /dev/null +++ b/examples/train_ppo.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +CUDA_VISIBLE_DEVICES=0 python ../src/train_ppo.py \ + --do_train \ + --dataset alpaca_gpt4_zh \ + --dataset_dir ../data \ + --finetuning_type lora \ + --checkpoint_dir path_to_sft_checkpoint \ + --reward_model path_to_rm_checkpoint \ + --output_dir path_to_ppo_checkpoint \ + --overwrite_cache \ + --per_device_train_batch_size 2 \ + --gradient_accumulation_steps 4 \ + --lr_scheduler_type cosine \ + --logging_steps 10 \ + --save_steps 1000 \ + --learning_rate 1e-5 \ + --num_train_epochs 1.0 \ + --fp16 diff --git a/examples/train_rm.sh b/examples/train_rm.sh new file mode 100644 index 0000000000000000000000000000000000000000..39fc14bb08018513988aea205f6ad7354aa273c4 --- /dev/null +++ b/examples/train_rm.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +CUDA_VISIBLE_DEVICES=0 python ../src/train_rm.py \ + --do_train \ + --dataset comparison_gpt4_zh \ + --dataset_dir ../data \ + --finetuning_type lora \ + --output_dir path_to_rm_checkpoint \ + --overwrite_cache \ + --per_device_train_batch_size 4 \ + --gradient_accumulation_steps 4 \ + --lr_scheduler_type cosine \ + --logging_steps 10 \ + --save_steps 1000 \ + --learning_rate 1e-5 \ + --num_train_epochs 1.0 \ + --fp16 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..0afa59ca21ed385e6a9f2f2d31c4c27e6baccc4a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,14 @@ +torch>=1.13.1 +protobuf +cpm_kernels +sentencepiece +transformers>=4.27.4 +datasets>=2.10.0 +accelerate>=0.18.0 +peft>=0.3.0 +trl>=0.4.1 +jieba +rouge_chinese +nltk +gradio +mdtex2html diff --git a/src/.ipynb_checkpoints/__init__-checkpoint.py b/src/.ipynb_checkpoints/__init__-checkpoint.py new file mode 100644 index 0000000000000000000000000000000000000000..d8cc901f36752410cded38d01a9b355807f96e66 --- /dev/null +++ b/src/.ipynb_checkpoints/__init__-checkpoint.py @@ -0,0 +1,4 @@ +from .utils import ( + load_pretrained, + ModelArguments +) diff --git a/src/.ipynb_checkpoints/infer-checkpoint.py b/src/.ipynb_checkpoints/infer-checkpoint.py new file mode 100644 index 0000000000000000000000000000000000000000..494b8153b576da9027027f5c7a192d5bc5450f4b --- /dev/null +++ b/src/.ipynb_checkpoints/infer-checkpoint.py @@ -0,0 +1,77 @@ +# coding=utf-8 +# Implement stream chat in command line for ChatGLM fine-tuned with PEFT. +# This code is largely borrowed from https://github.com/THUDM/ChatGLM-6B/blob/main/cli_demo.py + + +import os +import signal +import platform + +from utils import ModelArguments, load_pretrained +from transformers import HfArgumentParser + + +os_name = platform.system() +clear_command = "cls" if os_name == "Windows" else "clear" +stop_stream = False +welcome = "欢迎使用 ChatGLM-6B 模型,输入内容即可对话,clear清空对话历史,stop终止程序" + + +def build_prompt(history): + prompt = welcome + for query, response in history: + prompt += f"\n\nUser: {query}" + prompt += f"\n\nChatGLM-6B: {response}" + return prompt + + +def signal_handler(signal, frame): + global stop_stream + stop_stream = True + + +def main(): + + global stop_stream + parser = HfArgumentParser(ModelArguments) + model_args, = parser.parse_args_into_dataclasses() + model, tokenizer = load_pretrained(model_args) + model = model.cuda() + model.eval() + + history = [] + print(welcome) + while True: + try: + query = input("\nInput: ") + except UnicodeDecodeError: + print("Detected decoding error at the inputs, please set the terminal encoding to utf-8.") + continue + except Exception: + raise + + if query.strip() == "stop": + break + if query.strip() == "clear": + history = [] + os.system(clear_command) + print(welcome) + continue + + count = 0 + for _, history in model.stream_chat(tokenizer, query, history=history): + if stop_stream: + stop_stream = False + break + else: + count += 1 + if count % 8 == 0: + os.system(clear_command) + print(build_prompt(history), flush=True) + signal.signal(signal.SIGINT, signal_handler) + os.system(clear_command) + print(build_prompt(history), flush=True) + + +if __name__ == "__main__": + main() diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..d8cc901f36752410cded38d01a9b355807f96e66 --- /dev/null +++ b/src/__init__.py @@ -0,0 +1,4 @@ +from .utils import ( + load_pretrained, + ModelArguments +) diff --git a/src/finetune.py b/src/finetune.py new file mode 100644 index 0000000000000000000000000000000000000000..08fe9202c3b6d31a9f7250c3689e514dcc7377e3 --- /dev/null +++ b/src/finetune.py @@ -0,0 +1,88 @@ +# coding=utf-8 +# Implements several parameter-efficient supervised fine-tuning method for ChatGLM. +# This code is inspired by https://github.com/THUDM/ChatGLM-6B/blob/main/ptuning/main.py + + +from utils import ( + load_pretrained, + prepare_args, + prepare_data, + preprocess_data, + plot_loss, + Seq2SeqDataCollatorForChatGLM, + ComputeMetrics, + Seq2SeqTrainerForChatGLM +) + + +def main(): + + # Prepare pretrained model and dataset + model_args, data_args, training_args, finetuning_args = prepare_args() + dataset = prepare_data(model_args, data_args) + model, tokenizer = load_pretrained(model_args, training_args, finetuning_args, training_args.do_train, stage="sft") + dataset = preprocess_data(dataset, tokenizer, data_args, training_args, stage="sft") + data_collator = Seq2SeqDataCollatorForChatGLM( + tokenizer=tokenizer, + model=model, + ignore_pad_token_for_loss=data_args.ignore_pad_token_for_loss, + inference_mode=(not training_args.do_train) + ) + + # Override the decoding parameters of Seq2SeqTrainer + training_args.generation_max_length = training_args.generation_max_length if \ + training_args.generation_max_length is not None else data_args.max_target_length + training_args.generation_num_beams = data_args.num_beams if \ + data_args.num_beams is not None else training_args.generation_num_beams + + # Initialize our Trainer + trainer = Seq2SeqTrainerForChatGLM( + finetuning_args=finetuning_args, + model=model, + args=training_args, + train_dataset=dataset if training_args.do_train else None, + eval_dataset=dataset if training_args.do_eval else None, + tokenizer=tokenizer, + data_collator=data_collator, + compute_metrics=ComputeMetrics(tokenizer) if training_args.predict_with_generate else None + ) + + # Keyword arguments for `model.generate` + gen_kwargs = { + "do_sample": True, + "top_p": 0.7, + "max_length": 768, + "temperature": 0.95 + } + + # Training + if training_args.do_train: + train_result = trainer.train() + trainer.log_metrics("train", train_result.metrics) + trainer.save_metrics("train", train_result.metrics) + trainer.save_state() + trainer.save_model() + if trainer.is_world_process_zero() and finetuning_args.plot_loss: + plot_loss(training_args) + + # Evaluation + if training_args.do_eval: + metrics = trainer.evaluate(metric_key_prefix="eval", **gen_kwargs) + trainer.log_metrics("eval", metrics) + trainer.save_metrics("eval", metrics) + + # Predict + if training_args.do_predict: + predict_results = trainer.predict(dataset, metric_key_prefix="predict", **gen_kwargs) + trainer.log_metrics("predict", predict_results.metrics) + trainer.save_metrics("predict", predict_results.metrics) + trainer.save_predictions(predict_results, tokenizer) + + +def _mp_fn(index): + # For xla_spawn (TPUs) + main() + + +if __name__ == "__main__": + main() diff --git a/src/infer.py b/src/infer.py new file mode 100644 index 0000000000000000000000000000000000000000..494b8153b576da9027027f5c7a192d5bc5450f4b --- /dev/null +++ b/src/infer.py @@ -0,0 +1,77 @@ +# coding=utf-8 +# Implement stream chat in command line for ChatGLM fine-tuned with PEFT. +# This code is largely borrowed from https://github.com/THUDM/ChatGLM-6B/blob/main/cli_demo.py + + +import os +import signal +import platform + +from utils import ModelArguments, load_pretrained +from transformers import HfArgumentParser + + +os_name = platform.system() +clear_command = "cls" if os_name == "Windows" else "clear" +stop_stream = False +welcome = "欢迎使用 ChatGLM-6B 模型,输入内容即可对话,clear清空对话历史,stop终止程序" + + +def build_prompt(history): + prompt = welcome + for query, response in history: + prompt += f"\n\nUser: {query}" + prompt += f"\n\nChatGLM-6B: {response}" + return prompt + + +def signal_handler(signal, frame): + global stop_stream + stop_stream = True + + +def main(): + + global stop_stream + parser = HfArgumentParser(ModelArguments) + model_args, = parser.parse_args_into_dataclasses() + model, tokenizer = load_pretrained(model_args) + model = model.cuda() + model.eval() + + history = [] + print(welcome) + while True: + try: + query = input("\nInput: ") + except UnicodeDecodeError: + print("Detected decoding error at the inputs, please set the terminal encoding to utf-8.") + continue + except Exception: + raise + + if query.strip() == "stop": + break + if query.strip() == "clear": + history = [] + os.system(clear_command) + print(welcome) + continue + + count = 0 + for _, history in model.stream_chat(tokenizer, query, history=history): + if stop_stream: + stop_stream = False + break + else: + count += 1 + if count % 8 == 0: + os.system(clear_command) + print(build_prompt(history), flush=True) + signal.signal(signal.SIGINT, signal_handler) + os.system(clear_command) + print(build_prompt(history), flush=True) + + +if __name__ == "__main__": + main() diff --git a/src/train_ppo.py b/src/train_ppo.py new file mode 100644 index 0000000000000000000000000000000000000000..aea6a56e656eecc5d7ed07757473f90ffcb2e209 --- /dev/null +++ b/src/train_ppo.py @@ -0,0 +1,85 @@ +# coding=utf-8 +# Implements parameter-efficient ppo training of fine-tuned ChatGLM. +# This code is inspired by: +# https://github.com/lvwerra/trl/blob/main/examples/sentiment/scripts/gpt-neox-20b_peft/gpt-neo-20b_sentiment_peft.py + +import math + +from torch.optim import AdamW + +from transformers.optimization import get_scheduler +from trl import PPOConfig + +from utils import ( + prepare_args, + prepare_data, + load_pretrained, + preprocess_data, + PPODataCollatorForChatGLM, + PPOTrainerForChatGLM, + plot_loss +) + + +def main(): + + # prepare pretrained model and dataset + model_args, data_args, training_args, finetuning_args = prepare_args() + dataset = prepare_data(model_args, data_args) + model, tokenizer = load_pretrained(model_args, training_args, finetuning_args, training_args.do_train, stage="ppo") + dataset = preprocess_data(dataset, tokenizer, data_args, training_args, stage="ppo") + data_collator = PPODataCollatorForChatGLM( + tokenizer=tokenizer, + min_input_length=data_args.max_source_length, # avoid truncating input sequences + max_input_length=data_args.max_source_length, + inference_mode=(not training_args.do_train) + ) + + ppo_config = PPOConfig( + model_name=model_args.model_name_or_path, + learning_rate=training_args.learning_rate, + mini_batch_size=training_args.per_device_train_batch_size, + batch_size=training_args.per_device_train_batch_size, + gradient_accumulation_steps=training_args.gradient_accumulation_steps, + ppo_epochs=1, + max_grad_norm=training_args.max_grad_norm + ) + + optimizer = AdamW(filter(lambda p: p.requires_grad, model.parameters()), lr=ppo_config.learning_rate) + total_train_batch_size = \ + training_args.per_device_train_batch_size * training_args.gradient_accumulation_steps * training_args.world_size + lr_scheduler = get_scheduler( + training_args.lr_scheduler_type, + optimizer=optimizer, + num_warmup_steps=training_args.warmup_steps, + num_training_steps=(training_args.num_train_epochs * math.ceil(len(dataset) / total_train_batch_size)) + ) + + # Initialize our Trainer + ppo_trainer = PPOTrainerForChatGLM( + training_args=training_args, + finetuning_args=finetuning_args, + config=ppo_config, + model=model, + ref_model=None, + tokenizer=tokenizer, + dataset=dataset, + data_collator=data_collator, + optimizer=optimizer, + lr_scheduler=lr_scheduler + ) + + ppo_trainer.ppo_train(max_target_length=data_args.max_target_length) + ppo_trainer.save_state() + ppo_trainer.save_model() + if ppo_trainer.is_world_process_zero() and finetuning_args.plot_loss: + plot_loss(training_args, keys=["loss", "reward"]) + + +def _mp_fn(index): + # For xla_spawn (TPUs) + main() + + +if __name__ == "__main__": + main() diff --git a/src/train_rm.py b/src/train_rm.py new file mode 100644 index 0000000000000000000000000000000000000000..82962972285285a290e3c1564f8f5b842c562e86 --- /dev/null +++ b/src/train_rm.py @@ -0,0 +1,60 @@ +# coding=utf-8 +# Implements parameter-efficient training of a reward model based on ChatGLM. +# This code is inspired by: +# https://github.com/lvwerra/trl/blob/main/examples/summarization/scripts/reward_summarization.py +# https://github.com/CarperAI/trlx/blob/main/examples/summarize_rlhf/reward_model/train_reward_model_gptj.py + + +from utils import ( + prepare_args, + prepare_data, + load_pretrained, + preprocess_data, + PairwiseDataCollatorForChatGLM, + PairwiseTrainerForChatGLM, + plot_loss +) + +def main(): + + # prepare pretrained model and dataset + model_args, data_args, training_args, finetuning_args = prepare_args() + dataset = prepare_data(model_args, data_args) + model, tokenizer = load_pretrained(model_args, training_args, finetuning_args, training_args.do_train, stage="rwd") + dataset = preprocess_data(dataset, tokenizer, data_args, training_args, stage="rwd") + data_collator = PairwiseDataCollatorForChatGLM( + tokenizer=tokenizer, + inference_mode=(not training_args.do_train) + ) + + training_args.remove_unused_columns = False # Important for pairwise dataset + + # Initialize our Trainer + trainer = PairwiseTrainerForChatGLM( + finetuning_args=finetuning_args, + model=model, + args=training_args, + train_dataset=dataset if training_args.do_train else None, + eval_dataset=dataset if training_args.do_eval else None, + tokenizer=tokenizer, + data_collator=data_collator + ) + + # Training + if training_args.do_train: + train_result = trainer.train() + trainer.log_metrics("train", train_result.metrics) + trainer.save_metrics("train", train_result.metrics) + trainer.save_state() + trainer.save_model() + if trainer.is_world_process_zero() and finetuning_args.plot_loss: + plot_loss(training_args) + + +def _mp_fn(index): + # For xla_spawn (TPUs) + main() + + +if __name__ == "__main__": + main() diff --git a/src/utils/.ipynb_checkpoints/__init__-checkpoint.py b/src/utils/.ipynb_checkpoints/__init__-checkpoint.py new file mode 100644 index 0000000000000000000000000000000000000000..33e85048b4b13231b87f82b79a2b29690e0fb423 --- /dev/null +++ b/src/utils/.ipynb_checkpoints/__init__-checkpoint.py @@ -0,0 +1,26 @@ +from .common import ( + load_pretrained, + prepare_args, + prepare_data, + preprocess_data +) + +from .seq2seq import ( + Seq2SeqDataCollatorForChatGLM, + ComputeMetrics, + Seq2SeqTrainerForChatGLM +) + +from .pairwise import ( + PairwiseDataCollatorForChatGLM, + PairwiseTrainerForChatGLM +) + +from .ppo import ( + PPODataCollatorForChatGLM, + PPOTrainerForChatGLM +) + +from .config import ModelArguments + +from .other import plot_loss diff --git a/src/utils/.ipynb_checkpoints/common-checkpoint.py b/src/utils/.ipynb_checkpoints/common-checkpoint.py new file mode 100644 index 0000000000000000000000000000000000000000..782a2b247b48eb1e8984a35ed9471f3b57843480 --- /dev/null +++ b/src/utils/.ipynb_checkpoints/common-checkpoint.py @@ -0,0 +1,507 @@ +import os +import sys +import torch +import hashlib +from typing import Literal, Optional, Tuple + +import transformers +from transformers import ( + AutoConfig, + AutoModel, + AutoTokenizer, + HfArgumentParser, + Seq2SeqTrainingArguments +) +from transformers.utils import check_min_version +from transformers.utils.versions import require_version +from transformers.modeling_utils import PreTrainedModel +from transformers.tokenization_utils import PreTrainedTokenizer + +import datasets +from datasets import Dataset, concatenate_datasets, load_dataset + +from peft import ( + PeftModel, + TaskType, + LoraConfig, + get_peft_model +) + +from trl import AutoModelForCausalLMWithValueHead + +from .config import ( + ModelArguments, + DataTrainingArguments, + FinetuningArguments +) + +from .other import ( + get_logger, + load_trainable_params, + load_valuehead_params, + print_trainable_params, + prepare_model_for_training, + IGNORE_INDEX, + FINETUNING_ARGS_NAME +) + + +logger = get_logger(__name__) + + +check_min_version("4.27.4") +require_version("datasets>=2.10.0", "To fix: pip install datasets>=2.10.0") +require_version("peft>=0.3.0", "To fix: pip install peft>=0.3.0") +require_version("trl>=0.4.1", "To fix: pip install trl>=0.4.1") + + +def init_adapter( + model: PreTrainedModel, + model_args: ModelArguments, + finetuning_args: FinetuningArguments, + is_trainable: bool +) -> PreTrainedModel: + r""" + Initializes the adapters. + + Note that the trainable parameters must be cast to float32. + """ + + if finetuning_args.finetuning_type == "none" and is_trainable: + raise ValueError("You cannot use finetuning_type=none while training.") + + if finetuning_args.finetuning_type == "full": + logger.info("Fine-tuning method: Full") + model = model.float() + + if model_args.checkpoint_dir is not None: + load_trainable_params(model, model_args.checkpoint_dir[0]) + + if finetuning_args.finetuning_type == "freeze": + logger.info("Fine-tuning method: Freeze") + for name, param in model.named_parameters(): + if not any(trainable_layer in name for trainable_layer in finetuning_args.trainable_layers): + param.requires_grad_(False) + else: + param.data = param.data.to(torch.float32) + + if model_args.checkpoint_dir is not None: + load_trainable_params(model, model_args.checkpoint_dir[0]) + + if finetuning_args.finetuning_type == "p_tuning": + logger.info("Fine-tuning method: P-Tuning v2") # nothing to do + + if model_args.checkpoint_dir is not None: + load_trainable_params(model, model_args.checkpoint_dir[0]) + + if finetuning_args.finetuning_type == "lora": + logger.info("Fine-tuning method: LoRA") + lastest_checkpoint = None + + if model_args.checkpoint_dir is not None: + if is_trainable and finetuning_args.resume_lora_training: # continually training on the lora weights + checkpoints_to_merge, lastest_checkpoint = model_args.checkpoint_dir[:-1], model_args.checkpoint_dir[-1] + else: + checkpoints_to_merge = model_args.checkpoint_dir + + for checkpoint in checkpoints_to_merge: + model = PeftModel.from_pretrained(model, checkpoint) + model = model.merge_and_unload() + + logger.info("Merged {} model checkpoint(s).".format(len(checkpoints_to_merge))) + + if lastest_checkpoint is not None: # resume lora training + model = PeftModel.from_pretrained(model, lastest_checkpoint, is_trainable=True) + + if lastest_checkpoint is None: # create new lora weights + lora_config = LoraConfig( + task_type=TaskType.CAUSAL_LM, + inference_mode=False, + r=finetuning_args.lora_rank, + lora_alpha=finetuning_args.lora_alpha, + lora_dropout=finetuning_args.lora_dropout, + target_modules=finetuning_args.lora_target + ) + model = get_peft_model(model, lora_config) + + return model + + +def load_pretrained( + model_args: ModelArguments, + training_args: Optional[Seq2SeqTrainingArguments] = None, + finetuning_args: Optional[FinetuningArguments] = None, + is_trainable: Optional[bool] = False, + stage: Optional[Literal["sft", "rwd", "ppo"]] = "sft" +) -> Tuple[PreTrainedModel, PreTrainedTokenizer]: + r""" + Load pretrained model and tokenizer. + """ + + if (not is_trainable) and (model_args.checkpoint_dir is None): + logger.warning("Checkpoint is not found at evaluation, load the original model.") + finetuning_args = FinetuningArguments(finetuning_type="none") + + if model_args.checkpoint_dir is not None: # load fine-tuned model from checkpoint + for checkpoint_dir in model_args.checkpoint_dir: + if not os.path.isfile(os.path.join(checkpoint_dir, FINETUNING_ARGS_NAME)): + raise ValueError("The fine-tuning arguments are not found in the provided dictionary.") + logger.info("Load fine-tuned model from checkpoint(s): {}".format(",".join(model_args.checkpoint_dir))) + finetuning_args = torch.load(os.path.join(model_args.checkpoint_dir[0], FINETUNING_ARGS_NAME)) + if finetuning_args.finetuning_type != "lora" and len(model_args.checkpoint_dir) > 1: + logger.warning("Only LoRA tuning accepts multiple checkpoints.") + + assert stage == "sft" or finetuning_args.finetuning_type == "lora", "RM and PPO training can only be performed with LoRA method." + + quantization = None + if model_args.quantization_bit is not None: + if is_trainable: + if finetuning_args.finetuning_type == "full": + raise ValueError("Full parameter fine-tuning does not support quantization.") + elif finetuning_args.finetuning_type == "p_tuning": + quantization = "cpm" # use cpm's quantization + else: + quantization = "bnb" # use bnb's quantization + else: + quantization = "cpm" + + config_kwargs = { + "trust_remote_code": True, + "cache_dir": model_args.cache_dir, + "revision": model_args.model_revision, + "use_auth_token": True if model_args.use_auth_token else None, + } + + tokenizer = AutoTokenizer.from_pretrained( + model_args.tokenizer_name if model_args.tokenizer_name else model_args.model_name_or_path, + use_fast=model_args.use_fast_tokenizer, + padding_side="left", + **config_kwargs + ) + + config = AutoConfig.from_pretrained( + model_args.config_name if model_args.config_name else model_args.model_name_or_path, + **config_kwargs + ) + + # P-Tuning v2 configurations. + # We use the built-in p-tuning method of ChatGLM, we cannot use PEFT since the attention masks of ChatGLM are unusual. >_< + if finetuning_args.finetuning_type == "p_tuning": + config.pre_seq_len = finetuning_args.pre_seq_len # enable this will fix other parameters automatically + config.prefix_projection = finetuning_args.prefix_projection + + # Quantization configurations for Full, Freeze and LoRA in training (using bitsandbytes library). + if quantization == "bnb": + assert model_args.quantization_bit == 8, "Freeze and LoRA fine-tuning only accept 8-bit quantization." + + require_version("bitsandbytes>=0.37.0", "bitsandbytes library is required to use this feature.") + from bitsandbytes.cuda_setup.main import get_compute_capability, get_cuda_lib_handle, is_cublasLt_compatible + cuda = get_cuda_lib_handle() + cc = get_compute_capability(cuda) + assert is_cublasLt_compatible(cc), "The current GPU(s) is incompatible with quantization." + + config_kwargs["load_in_8bit"] = True + config_kwargs["device_map"] = "auto" # it should not be specified outside of load_in_8bit + + # Load and prepare pretrained models (without valuehead). + model = AutoModel.from_pretrained(model_args.model_name_or_path, config=config, **config_kwargs) + model = prepare_model_for_training(model) if is_trainable else model + model = init_adapter(model, model_args, finetuning_args, is_trainable) + + if not is_trainable: + model.requires_grad_(False) # fix all params + model = model.half() # cast all params to float16 + + # Quantization with the built-in method for P-Tuning v2 training or evaluation. + # Model parameters should be cast to float16 in quantized P-Tuning setting. + if quantization == "cpm": + assert model_args.quantization_bit in [4, 8], "P-Tuning v2 and inference mode only accept 4-bit or 8-bit quantization." + assert not (is_trainable and training_args.fp16), "FP16 training conflicts with cpm quantization." + + model.quantize(model_args.quantization_bit) # in-place method + + for name, param in model.named_parameters(): + if "prefix_encoder" not in name: + param.data = param.data.to(torch.float16) # convert all params in half precision except prefix_encoder + + if quantization is not None: + logger.info("Quantized model to {} bit.".format(model_args.quantization_bit)) + + if stage == "rwd" or stage == "ppo": # add value head + assert is_trainable, "Reward and PPO stages cannot be performed at evaluation." + + model = AutoModelForCausalLMWithValueHead.from_pretrained(model) + + if stage == "ppo": # load reward model + assert model_args.reward_model is not None, "Reward model is necessary for PPO training." + model.pretrained_model.load_adapter(model_args.reward_model, "reward", is_trainable=False) + load_valuehead_params(model, model_args.reward_model) + + # Set the parameter _is_int8_training_enabled for the AutoModelForCausalLMWithValueHead model + # To meet the compliance requirements of the transformers library + if quantization == "bnb": + model._is_int8_training_enabled = True + + print_trainable_params(model) + + return model, tokenizer + + +def prepare_args() -> Tuple[ModelArguments, DataTrainingArguments, Seq2SeqTrainingArguments, FinetuningArguments]: + + parser = HfArgumentParser((ModelArguments, DataTrainingArguments, Seq2SeqTrainingArguments, FinetuningArguments)) + + if len(sys.argv) == 2 and sys.argv[1].endswith(".json"): # Provide arguments with a json file. + model_args, data_args, training_args, finetuning_args = parser.parse_json_file(json_file=os.path.abspath(sys.argv[1])) + else: + model_args, data_args, training_args, finetuning_args = parser.parse_args_into_dataclasses() + + # Setup logging + if training_args.should_log: + # The default of training_args.log_level is passive, so we set log level at info here to have that default. + transformers.utils.logging.set_verbosity_info() + + log_level = training_args.get_process_log_level() + datasets.utils.logging.set_verbosity(log_level) + transformers.utils.logging.set_verbosity(log_level) + transformers.utils.logging.enable_default_handler() + transformers.utils.logging.enable_explicit_format() + + # Check arguments (do not check finetuning_args since it may be loaded from checkpoints) + if int(training_args.do_train) + int(training_args.do_eval) + int(training_args.do_predict) != 1: + raise ValueError("We must perform a single operation among do_train, do_eval and do_predict.") + + if model_args.quantization_bit is not None and training_args.do_train == False: + logger.warning("We do not recommend to evaluaute model in 4/8-bit mode.") + + if training_args.do_train and (not training_args.fp16): + logger.warning("We recommend enable fp16 mixed precision training for ChatGLM-6B.") + + training_args.optim = "adamw_torch" if training_args.optim == "adamw_hf" else training_args.optim # suppress warning + + # Log on each process the small summary: + logger.warning( + f"Process rank: {training_args.local_rank}, device: {training_args.device}, n_gpu: {training_args.n_gpu}\n" + + f" distributed training: {bool(training_args.local_rank != -1)}, 16-bits training: {training_args.fp16}" + ) + logger.info(f"Training/evaluation parameters {training_args}") + + # Set seed before initializing model. + transformers.set_seed(training_args.seed) + + return model_args, data_args, training_args, finetuning_args + + +def prepare_data( + model_args: ModelArguments, + data_args: DataTrainingArguments +) -> Dataset: + + def checksum(file_path, hash): + with open(file_path, "rb") as datafile: + binary_data = datafile.read() + sha1 = hashlib.sha1(binary_data).hexdigest() + if sha1 != hash: + logger.warning("Checksum failed for {}. It may vary depending on the platform.".format(file_path)) + + max_samples = data_args.max_samples + all_datasets = [] # support multiple datasets + + for dataset_info in data_args.dataset_list: + + logger.info("Loading dataset {}...".format(dataset_info)) + + if dataset_info.load_from == "hf_hub": + raw_datasets = load_dataset(dataset_info.dataset_name, cache_dir=model_args.cache_dir) + elif dataset_info.load_from == "script": + raw_datasets = load_dataset( + os.path.join(data_args.dataset_dir, dataset_info.dataset_name), + cache_dir=model_args.cache_dir + ) + elif dataset_info.load_from == "file": + data_file = os.path.join(data_args.dataset_dir, dataset_info.file_name) # support json, jsonl and csv + extension = dataset_info.file_name.split(".")[-1] + + if dataset_info.file_sha1 is not None: + checksum(data_file, dataset_info.file_sha1) + else: + logger.warning("Checksum failed: missing SHA-1 hash value in dataset_info.") + + raw_datasets = load_dataset( + extension, + data_files=data_file, + cache_dir=model_args.cache_dir, + use_auth_token=True if model_args.use_auth_token else None + ) + else: + raise NotImplementedError + + dataset = raw_datasets[data_args.split] + + if max_samples is not None: + max_samples_temp = min(len(dataset), max_samples) + dataset = dataset.select(range(max_samples_temp)) + + dummy_data = [None] * len(dataset) + for column, column_name in [ + ("prompt_column", "prompt"), + ("query_column", "query"), + ("response_column", "response"), + ("history_column", "history") + ]: # every dataset will have 4 columns same as each other + if getattr(dataset_info, column) != column_name: + if getattr(dataset_info, column): + dataset = dataset.rename_column(getattr(dataset_info, column), column_name) + else: # None or empty string + dataset = dataset.add_column(column_name, dummy_data) + all_datasets.append(dataset) + + if len(data_args.dataset_list) == 1: + all_datasets = all_datasets[0] + else: + all_datasets = concatenate_datasets(all_datasets) + + return all_datasets + + +def preprocess_data( + dataset: Dataset, + tokenizer: PreTrainedTokenizer, + data_args: DataTrainingArguments, + training_args: Seq2SeqTrainingArguments, + stage: Optional[Literal["sft", "rwd", "ppo"]] = "sft" +) -> Dataset: + + column_names = list(dataset.column_names) + prefix = data_args.source_prefix if data_args.source_prefix is not None else "" + + def format_example(examples): # support question with a single answer or multiple answers + for i in range(len(examples["prompt"])): + if examples["prompt"][i] and examples["response"][i]: + query, answer = examples["prompt"][i], examples["response"][i] + if examples["query"][i]: + query += examples["query"][i] + if examples["history"][i]: + prompt = "" + history = examples["history"][i] + for j, (old_query, response) in enumerate(history): + prompt += "[Round {}]\n问:{}\n答:{}\n".format(j, old_query, response) + prompt += "[Round {}]\n问:{}\n答:".format(len(history), query) + else: + prompt = query + prompt = prefix + prompt + yield prompt, answer + + def preprocess_function_train(examples): + # build inputs with format `X [gMASK] [BOS] Y [EOS]` and labels with format `[IGNORE] ... [IGNORE] [BOS] Y [EOS]` + model_inputs = {"input_ids": [], "labels": []} + for prompt, answer in format_example(examples): + source_ids = tokenizer.encode(text=prompt, add_special_tokens=False) + target_ids = tokenizer.encode(text=answer, add_special_tokens=False) + + if len(source_ids) > data_args.max_source_length - 2: # gmask and bos tokens + source_ids = source_ids[:data_args.max_source_length - 2] + if len(target_ids) > data_args.max_target_length - 1: # eos token + target_ids = target_ids[:data_args.max_target_length - 1] + + input_ids = tokenizer.build_inputs_with_special_tokens(source_ids, target_ids) + + context_length = input_ids.index(tokenizer.bos_token_id) + labels = [IGNORE_INDEX] * context_length + input_ids[context_length:] + + model_inputs["input_ids"].append(input_ids) + model_inputs["labels"].append(labels) + return model_inputs + + def preprocess_function_eval(examples): + # build inputs with format `[PAD] ... [PAD] X [gMASK] [BOS]` and labels with format `Y [gMASK] [BOS]` + # left-padding is needed for prediction, use the built-in function of the tokenizer + inputs, targets = [], [] + for prompt, answer in format_example(examples): + inputs.append(prompt) + targets.append(answer) + model_inputs = tokenizer(inputs, max_length=data_args.max_source_length, truncation=True, padding=True) + labels = tokenizer(text_target=targets, max_length=data_args.max_target_length, truncation=True) # no padding + if data_args.ignore_pad_token_for_loss: + labels["input_ids"] = [ + [(l_id if l_id != tokenizer.pad_token_id else IGNORE_INDEX) for l_id in label] for label in labels["input_ids"] + ] + model_inputs["labels"] = labels["input_ids"] + return model_inputs + + def preprocess_function_train_pair(examples): + # build input pairs with format `X [gMASK] [BOS] Y1 [EOS]` and `X [gMASK] [BOS] Y2 [EOS]` + model_inputs = {"accept_ids": [], "reject_ids": []} + for prompt, answer in format_example(examples): + source_ids = tokenizer.encode(text=prompt, add_special_tokens=False) + accept_ids = tokenizer.encode(text=answer[0], add_special_tokens=False) + reject_ids = tokenizer.encode(text=answer[1], add_special_tokens=False) + + if len(source_ids) > data_args.max_source_length - 2: # gmask and bos tokens + source_ids = source_ids[:data_args.max_source_length - 2] + if len(accept_ids) > data_args.max_target_length - 1: # eos token + accept_ids = accept_ids[:data_args.max_target_length - 1] + if len(reject_ids) > data_args.max_target_length - 1: # eos token + reject_ids = reject_ids[:data_args.max_target_length - 1] + + accept_ids = tokenizer.build_inputs_with_special_tokens(source_ids[:], accept_ids) # avoid copying error + reject_ids = tokenizer.build_inputs_with_special_tokens(source_ids[:], reject_ids) + + model_inputs["accept_ids"].append(accept_ids) + model_inputs["reject_ids"].append(reject_ids) + return model_inputs + + def preprocess_function_train_ppo(examples): + # build inputs with format `X [gMASK] [BOS]` + model_inputs = {"input_ids": []} + for prompt, _ in format_example(examples): + source_ids = tokenizer.encode(text=prompt, add_special_tokens=False) + + if len(source_ids) > data_args.max_source_length - 2: # gmask and bos tokens + source_ids = source_ids[:data_args.max_source_length - 2] + + input_ids = tokenizer.build_inputs_with_special_tokens(source_ids) + model_inputs["input_ids"].append(input_ids) + return model_inputs + + def print_sft_dataset_example(example): + print("input_ids:\n{}".format(example["input_ids"])) + print("inputs:\n{}".format(tokenizer.decode(example["input_ids"]))) + print("label_ids:\n{}".format(example["labels"])) + print("labels:\n{}".format(tokenizer.decode(example["labels"]))) + + def print_pairwise_dataset_example(example): + print("accept_ids:\n{}".format(example["accept_ids"])) + print("accepts:\n{}".format(tokenizer.decode(example["accept_ids"]))) + print("reject_ids:\n{}".format(example["reject_ids"])) + print("rejects:\n{}".format(tokenizer.decode(example["reject_ids"]))) + + def print_ppo_dataset_example(example): + print("input_ids:\n{}".format(example["input_ids"])) + print("inputs:\n{}".format(tokenizer.decode(example["input_ids"]))) + + if stage == "sft": + preprocess_function = preprocess_function_train if training_args.do_train else preprocess_function_eval + elif stage == "rwd": + preprocess_function = preprocess_function_train_pair + elif stage == "ppo": + preprocess_function = preprocess_function_train_ppo + + with training_args.main_process_first(desc="dataset map pre-processing"): + dataset = dataset.map( + preprocess_function, + batched=True, + num_proc=data_args.preprocessing_num_workers, + remove_columns=column_names, + load_from_cache_file=not data_args.overwrite_cache, + desc="Running tokenizer on dataset" + ) + + if stage == "sft": + print_sft_dataset_example(dataset[0]) + elif stage == "rwd": + print_pairwise_dataset_example(dataset[0]) + elif stage == "ppo": + print_ppo_dataset_example(dataset[0]) + + return dataset diff --git a/src/utils/.ipynb_checkpoints/config-checkpoint.py b/src/utils/.ipynb_checkpoints/config-checkpoint.py new file mode 100644 index 0000000000000000000000000000000000000000..849e0b57eb8f843e5eef26fc9a126f20211bf75c --- /dev/null +++ b/src/utils/.ipynb_checkpoints/config-checkpoint.py @@ -0,0 +1,219 @@ +import os +import json +from typing import Optional +from dataclasses import dataclass, field + + +CHATGLM_REPO_NAME = "THUDM/chatglm-6b" +CHATGLM_LASTEST_HASH = "a8ede826cf1b62bd3c78bdfb3625c7c5d2048fbd" + + +@dataclass +class DatasetAttr: + + load_from: str + dataset_name: Optional[str] = None + file_name: Optional[str] = None + file_sha1: Optional[str] = None + + def __post_init__(self): + self.prompt_column = "instruction" + self.query_column = "input" + self.response_column = "output" + self.history_column = None + + +@dataclass +class ModelArguments: + """ + Arguments pertaining to which model/config/tokenizer we are going to fine-tune. + """ + model_name_or_path: Optional[str] = field( + default=CHATGLM_REPO_NAME, + metadata={"help": "Path to pretrained model or model identifier from huggingface.co/models."} + ) + config_name: Optional[str] = field( + default=None, + metadata={"help": "Pretrained config name or path if not the same as model_name."} + ) + tokenizer_name: Optional[str] = field( + default=None, + metadata={"help": "Pretrained tokenizer name or path if not the same as model_name."} + ) + cache_dir: Optional[str] = field( + default=None, + metadata={"help": "Where to store the pretrained models downloaded from huggingface.co."} + ) + use_fast_tokenizer: Optional[bool] = field( + default=True, + metadata={"help": "Whether to use one of the fast tokenizer (backed by the tokenizers library) or not."} + ) + model_revision: Optional[str] = field( + default=CHATGLM_LASTEST_HASH, + metadata={"help": "The specific model version to use (can be a branch name, tag name or commit id)."} + ) + use_auth_token: Optional[bool] = field( + default=False, + metadata={"help": "Will use the token generated when running `huggingface-cli login`."} + ) + quantization_bit: Optional[int] = field( + default=None, + metadata={"help": "The number of bits to quantize the model."} + ) + checkpoint_dir: Optional[str] = field( + default=None, + metadata={"help": "Path to the directory containing the model checkpoints as well as the configurations."} + ) + reward_model: Optional[str] = field( + default=None, + metadata={"help": "Path to the directory containing the checkpoints of the reward model."} + ) + + def __post_init__(self): + if self.checkpoint_dir is not None: # support merging lora weights + self.checkpoint_dir = [cd.strip() for cd in self.checkpoint_dir.split(",")] + + +@dataclass +class DataTrainingArguments: + """ + Arguments pertaining to what data we are going to input our model for training and evaluation. + """ + dataset: Optional[str] = field( + default="alpaca_zh", + metadata={"help": "The name of provided dataset(s) to use. Use comma to separate multiple datasets."} + ) + dataset_dir: Optional[str] = field( + default="data", + metadata={"help": "The name of the folder containing datasets."} + ) + split: Optional[str] = field( + default="train", + metadata={"help": "Which dataset split to use for training and evaluation."} + ) + overwrite_cache: Optional[bool] = field( + default=False, + metadata={"help": "Overwrite the cached training and evaluation sets."} + ) + preprocessing_num_workers: Optional[int] = field( + default=None, + metadata={"help": "The number of processes to use for the preprocessing."} + ) + max_source_length: Optional[int] = field( + default=512, + metadata={"help": "The maximum total input sequence length after tokenization."} + ) + max_target_length: Optional[int] = field( + default=512, + metadata={"help": "The maximum total output sequence length after tokenization."} + ) + max_samples: Optional[int] = field( + default=None, + metadata={"help": "For debugging purposes, truncate the number of examples for each dataset."} + ) + num_beams: Optional[int] = field( + default=None, + metadata={"help": "Number of beams to use for evaluation. This argument will be passed to `model.generate`"} + ) + ignore_pad_token_for_loss: Optional[bool] = field( + default=True, + metadata={"help": "Whether to ignore the tokens corresponding to padded labels in the loss computation or not."} + ) + source_prefix: Optional[str] = field( + default=None, + metadata={"help": "A prefix to add before every source text (useful for T5 models)."} + ) + + def __post_init__(self): # support mixing multiple datasets + dataset_names = [ds.strip() for ds in self.dataset.split(",")] + dataset_info = json.load(open(os.path.join(self.dataset_dir, "dataset_info.json"), "r")) + + self.dataset_list = [] + for name in dataset_names: + if name not in dataset_info: + raise ValueError("Undefined dataset {} in dataset_info.json.".format(name)) + + if "hf_hub_url" in dataset_info[name]: + dataset_attr = DatasetAttr("hf_hub", dataset_name=dataset_info[name]["hf_hub_url"]) + elif "script_url" in dataset_info[name]: + dataset_attr = DatasetAttr("script", dataset_name=dataset_info[name]["script_url"]) + else: + dataset_attr = DatasetAttr( + "file", + file_name=dataset_info[name]["file_name"], + file_sha1=dataset_info[name]["file_sha1"] if "file_sha1" in dataset_info[name] else None + ) + + if "columns" in dataset_info[name]: + dataset_attr.prompt_column = dataset_info[name]["columns"].get("prompt", None) + dataset_attr.query_column = dataset_info[name]["columns"].get("query", None) + dataset_attr.response_column = dataset_info[name]["columns"].get("response", None) + dataset_attr.history_column = dataset_info[name]["columns"].get("history", None) + + self.dataset_list.append(dataset_attr) + + +@dataclass +class FinetuningArguments: + """ + Arguments pertaining to which techniques we are going to fine-tuning with. + """ + finetuning_type: Optional[str] = field( + default="lora", + metadata={"help": "Which fine-tuning method to use."} + ) + num_layer_trainable: Optional[int] = field( + default=3, + metadata={"help": "Number of trainable layers for Freeze fine-tuning."} + ) + name_module_trainable: Optional[str] = field( + default="mlp", + metadata={"help": "Name of trainable modules for Freeze fine-tuning."} + ) + pre_seq_len: Optional[int] = field( + default=16, + metadata={"help": "Number of prefix tokens to use for P-tuning V2."} + ) + prefix_projection: Optional[bool] = field( + default=False, + metadata={"help": "Whether to add a project layer for the prefix in P-tuning V2 or not."} + ) + lora_rank: Optional[int] = field( + default=8, + metadata={"help": "The intrinsic dimension for LoRA fine-tuning."} + ) + lora_alpha: Optional[float] = field( + default=32.0, + metadata={"help": "The scale factor for LoRA fine-tuning. (similar with the learning rate)"} + ) + lora_dropout: Optional[float] = field( + default=0.1, + metadata={"help": "Dropout rate for the LoRA fine-tuning."} + ) + lora_target: Optional[str] = field( + default="query_key_value", + metadata={"help": "Name(s) of target modules to apply LoRA. Use comma to separate multiple modules."} + ) + resume_lora_training: Optional[bool] = field( + default=True, + metadata={"help": "Whether to resume training from the last LoRA weights or create new weights after merging them."} + ) + plot_loss: Optional[bool] = field( + default=False, + metadata={"help": "Whether to plot the training loss after fine-tuning or not."} + ) + + def __post_init__(self): + self.lora_target = [target.strip() for target in self.lora_target.split(",")] # support custom target modules of LoRA + + if self.num_layer_trainable > 0: # fine-tuning the last n layers if num_layer_trainable > 0 + trainable_layer_ids = [27-k for k in range(self.num_layer_trainable)] + else: # fine-tuning the first n layers if num_layer_trainable < 0 + trainable_layer_ids = [k for k in range(-self.num_layer_trainable)] + if self.name_module_trainable == "mlp": + self.trainable_layers = ["layers.{:d}.mlp".format(idx) for idx in trainable_layer_ids] + elif self.name_module_trainable == "qkv": + self.trainable_layers = ["layers.{:d}.attention.query_key_value".format(idx) for idx in trainable_layer_ids] + + if self.finetuning_type not in ["none", "freeze", "p_tuning", "lora", "full"]: + raise NotImplementedError("Invalid fine-tuning method.") diff --git a/src/utils/__init__.py b/src/utils/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..33e85048b4b13231b87f82b79a2b29690e0fb423 --- /dev/null +++ b/src/utils/__init__.py @@ -0,0 +1,26 @@ +from .common import ( + load_pretrained, + prepare_args, + prepare_data, + preprocess_data +) + +from .seq2seq import ( + Seq2SeqDataCollatorForChatGLM, + ComputeMetrics, + Seq2SeqTrainerForChatGLM +) + +from .pairwise import ( + PairwiseDataCollatorForChatGLM, + PairwiseTrainerForChatGLM +) + +from .ppo import ( + PPODataCollatorForChatGLM, + PPOTrainerForChatGLM +) + +from .config import ModelArguments + +from .other import plot_loss diff --git a/src/utils/__pycache__/__init__.cpython-310.pyc b/src/utils/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c044f6c1aac0bbde7d562a74fb13481698768b4d Binary files /dev/null and b/src/utils/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/utils/__pycache__/common.cpython-310.pyc b/src/utils/__pycache__/common.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..56340c7d58d3b236ad3ffbcd6c9485e5e8830aad Binary files /dev/null and b/src/utils/__pycache__/common.cpython-310.pyc differ diff --git a/src/utils/__pycache__/config.cpython-310.pyc b/src/utils/__pycache__/config.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b039e668f76a309a48d34e43c8328079268bfd0 Binary files /dev/null and b/src/utils/__pycache__/config.cpython-310.pyc differ diff --git a/src/utils/__pycache__/other.cpython-310.pyc b/src/utils/__pycache__/other.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f65d28dc0df788175bb45a8e1814496ccb093422 Binary files /dev/null and b/src/utils/__pycache__/other.cpython-310.pyc differ diff --git a/src/utils/__pycache__/pairwise.cpython-310.pyc b/src/utils/__pycache__/pairwise.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..60a6951cfa6414ebf3726cc4a3e97e658f29d67c Binary files /dev/null and b/src/utils/__pycache__/pairwise.cpython-310.pyc differ diff --git a/src/utils/__pycache__/ppo.cpython-310.pyc b/src/utils/__pycache__/ppo.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d26bdec8039cd2d9918e26a771a1e1cc4d222f72 Binary files /dev/null and b/src/utils/__pycache__/ppo.cpython-310.pyc differ diff --git a/src/utils/__pycache__/seq2seq.cpython-310.pyc b/src/utils/__pycache__/seq2seq.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..781657e19198ef4ea9531ae12232e433acdd6d44 Binary files /dev/null and b/src/utils/__pycache__/seq2seq.cpython-310.pyc differ diff --git a/src/utils/common.py b/src/utils/common.py new file mode 100644 index 0000000000000000000000000000000000000000..782a2b247b48eb1e8984a35ed9471f3b57843480 --- /dev/null +++ b/src/utils/common.py @@ -0,0 +1,507 @@ +import os +import sys +import torch +import hashlib +from typing import Literal, Optional, Tuple + +import transformers +from transformers import ( + AutoConfig, + AutoModel, + AutoTokenizer, + HfArgumentParser, + Seq2SeqTrainingArguments +) +from transformers.utils import check_min_version +from transformers.utils.versions import require_version +from transformers.modeling_utils import PreTrainedModel +from transformers.tokenization_utils import PreTrainedTokenizer + +import datasets +from datasets import Dataset, concatenate_datasets, load_dataset + +from peft import ( + PeftModel, + TaskType, + LoraConfig, + get_peft_model +) + +from trl import AutoModelForCausalLMWithValueHead + +from .config import ( + ModelArguments, + DataTrainingArguments, + FinetuningArguments +) + +from .other import ( + get_logger, + load_trainable_params, + load_valuehead_params, + print_trainable_params, + prepare_model_for_training, + IGNORE_INDEX, + FINETUNING_ARGS_NAME +) + + +logger = get_logger(__name__) + + +check_min_version("4.27.4") +require_version("datasets>=2.10.0", "To fix: pip install datasets>=2.10.0") +require_version("peft>=0.3.0", "To fix: pip install peft>=0.3.0") +require_version("trl>=0.4.1", "To fix: pip install trl>=0.4.1") + + +def init_adapter( + model: PreTrainedModel, + model_args: ModelArguments, + finetuning_args: FinetuningArguments, + is_trainable: bool +) -> PreTrainedModel: + r""" + Initializes the adapters. + + Note that the trainable parameters must be cast to float32. + """ + + if finetuning_args.finetuning_type == "none" and is_trainable: + raise ValueError("You cannot use finetuning_type=none while training.") + + if finetuning_args.finetuning_type == "full": + logger.info("Fine-tuning method: Full") + model = model.float() + + if model_args.checkpoint_dir is not None: + load_trainable_params(model, model_args.checkpoint_dir[0]) + + if finetuning_args.finetuning_type == "freeze": + logger.info("Fine-tuning method: Freeze") + for name, param in model.named_parameters(): + if not any(trainable_layer in name for trainable_layer in finetuning_args.trainable_layers): + param.requires_grad_(False) + else: + param.data = param.data.to(torch.float32) + + if model_args.checkpoint_dir is not None: + load_trainable_params(model, model_args.checkpoint_dir[0]) + + if finetuning_args.finetuning_type == "p_tuning": + logger.info("Fine-tuning method: P-Tuning v2") # nothing to do + + if model_args.checkpoint_dir is not None: + load_trainable_params(model, model_args.checkpoint_dir[0]) + + if finetuning_args.finetuning_type == "lora": + logger.info("Fine-tuning method: LoRA") + lastest_checkpoint = None + + if model_args.checkpoint_dir is not None: + if is_trainable and finetuning_args.resume_lora_training: # continually training on the lora weights + checkpoints_to_merge, lastest_checkpoint = model_args.checkpoint_dir[:-1], model_args.checkpoint_dir[-1] + else: + checkpoints_to_merge = model_args.checkpoint_dir + + for checkpoint in checkpoints_to_merge: + model = PeftModel.from_pretrained(model, checkpoint) + model = model.merge_and_unload() + + logger.info("Merged {} model checkpoint(s).".format(len(checkpoints_to_merge))) + + if lastest_checkpoint is not None: # resume lora training + model = PeftModel.from_pretrained(model, lastest_checkpoint, is_trainable=True) + + if lastest_checkpoint is None: # create new lora weights + lora_config = LoraConfig( + task_type=TaskType.CAUSAL_LM, + inference_mode=False, + r=finetuning_args.lora_rank, + lora_alpha=finetuning_args.lora_alpha, + lora_dropout=finetuning_args.lora_dropout, + target_modules=finetuning_args.lora_target + ) + model = get_peft_model(model, lora_config) + + return model + + +def load_pretrained( + model_args: ModelArguments, + training_args: Optional[Seq2SeqTrainingArguments] = None, + finetuning_args: Optional[FinetuningArguments] = None, + is_trainable: Optional[bool] = False, + stage: Optional[Literal["sft", "rwd", "ppo"]] = "sft" +) -> Tuple[PreTrainedModel, PreTrainedTokenizer]: + r""" + Load pretrained model and tokenizer. + """ + + if (not is_trainable) and (model_args.checkpoint_dir is None): + logger.warning("Checkpoint is not found at evaluation, load the original model.") + finetuning_args = FinetuningArguments(finetuning_type="none") + + if model_args.checkpoint_dir is not None: # load fine-tuned model from checkpoint + for checkpoint_dir in model_args.checkpoint_dir: + if not os.path.isfile(os.path.join(checkpoint_dir, FINETUNING_ARGS_NAME)): + raise ValueError("The fine-tuning arguments are not found in the provided dictionary.") + logger.info("Load fine-tuned model from checkpoint(s): {}".format(",".join(model_args.checkpoint_dir))) + finetuning_args = torch.load(os.path.join(model_args.checkpoint_dir[0], FINETUNING_ARGS_NAME)) + if finetuning_args.finetuning_type != "lora" and len(model_args.checkpoint_dir) > 1: + logger.warning("Only LoRA tuning accepts multiple checkpoints.") + + assert stage == "sft" or finetuning_args.finetuning_type == "lora", "RM and PPO training can only be performed with LoRA method." + + quantization = None + if model_args.quantization_bit is not None: + if is_trainable: + if finetuning_args.finetuning_type == "full": + raise ValueError("Full parameter fine-tuning does not support quantization.") + elif finetuning_args.finetuning_type == "p_tuning": + quantization = "cpm" # use cpm's quantization + else: + quantization = "bnb" # use bnb's quantization + else: + quantization = "cpm" + + config_kwargs = { + "trust_remote_code": True, + "cache_dir": model_args.cache_dir, + "revision": model_args.model_revision, + "use_auth_token": True if model_args.use_auth_token else None, + } + + tokenizer = AutoTokenizer.from_pretrained( + model_args.tokenizer_name if model_args.tokenizer_name else model_args.model_name_or_path, + use_fast=model_args.use_fast_tokenizer, + padding_side="left", + **config_kwargs + ) + + config = AutoConfig.from_pretrained( + model_args.config_name if model_args.config_name else model_args.model_name_or_path, + **config_kwargs + ) + + # P-Tuning v2 configurations. + # We use the built-in p-tuning method of ChatGLM, we cannot use PEFT since the attention masks of ChatGLM are unusual. >_< + if finetuning_args.finetuning_type == "p_tuning": + config.pre_seq_len = finetuning_args.pre_seq_len # enable this will fix other parameters automatically + config.prefix_projection = finetuning_args.prefix_projection + + # Quantization configurations for Full, Freeze and LoRA in training (using bitsandbytes library). + if quantization == "bnb": + assert model_args.quantization_bit == 8, "Freeze and LoRA fine-tuning only accept 8-bit quantization." + + require_version("bitsandbytes>=0.37.0", "bitsandbytes library is required to use this feature.") + from bitsandbytes.cuda_setup.main import get_compute_capability, get_cuda_lib_handle, is_cublasLt_compatible + cuda = get_cuda_lib_handle() + cc = get_compute_capability(cuda) + assert is_cublasLt_compatible(cc), "The current GPU(s) is incompatible with quantization." + + config_kwargs["load_in_8bit"] = True + config_kwargs["device_map"] = "auto" # it should not be specified outside of load_in_8bit + + # Load and prepare pretrained models (without valuehead). + model = AutoModel.from_pretrained(model_args.model_name_or_path, config=config, **config_kwargs) + model = prepare_model_for_training(model) if is_trainable else model + model = init_adapter(model, model_args, finetuning_args, is_trainable) + + if not is_trainable: + model.requires_grad_(False) # fix all params + model = model.half() # cast all params to float16 + + # Quantization with the built-in method for P-Tuning v2 training or evaluation. + # Model parameters should be cast to float16 in quantized P-Tuning setting. + if quantization == "cpm": + assert model_args.quantization_bit in [4, 8], "P-Tuning v2 and inference mode only accept 4-bit or 8-bit quantization." + assert not (is_trainable and training_args.fp16), "FP16 training conflicts with cpm quantization." + + model.quantize(model_args.quantization_bit) # in-place method + + for name, param in model.named_parameters(): + if "prefix_encoder" not in name: + param.data = param.data.to(torch.float16) # convert all params in half precision except prefix_encoder + + if quantization is not None: + logger.info("Quantized model to {} bit.".format(model_args.quantization_bit)) + + if stage == "rwd" or stage == "ppo": # add value head + assert is_trainable, "Reward and PPO stages cannot be performed at evaluation." + + model = AutoModelForCausalLMWithValueHead.from_pretrained(model) + + if stage == "ppo": # load reward model + assert model_args.reward_model is not None, "Reward model is necessary for PPO training." + model.pretrained_model.load_adapter(model_args.reward_model, "reward", is_trainable=False) + load_valuehead_params(model, model_args.reward_model) + + # Set the parameter _is_int8_training_enabled for the AutoModelForCausalLMWithValueHead model + # To meet the compliance requirements of the transformers library + if quantization == "bnb": + model._is_int8_training_enabled = True + + print_trainable_params(model) + + return model, tokenizer + + +def prepare_args() -> Tuple[ModelArguments, DataTrainingArguments, Seq2SeqTrainingArguments, FinetuningArguments]: + + parser = HfArgumentParser((ModelArguments, DataTrainingArguments, Seq2SeqTrainingArguments, FinetuningArguments)) + + if len(sys.argv) == 2 and sys.argv[1].endswith(".json"): # Provide arguments with a json file. + model_args, data_args, training_args, finetuning_args = parser.parse_json_file(json_file=os.path.abspath(sys.argv[1])) + else: + model_args, data_args, training_args, finetuning_args = parser.parse_args_into_dataclasses() + + # Setup logging + if training_args.should_log: + # The default of training_args.log_level is passive, so we set log level at info here to have that default. + transformers.utils.logging.set_verbosity_info() + + log_level = training_args.get_process_log_level() + datasets.utils.logging.set_verbosity(log_level) + transformers.utils.logging.set_verbosity(log_level) + transformers.utils.logging.enable_default_handler() + transformers.utils.logging.enable_explicit_format() + + # Check arguments (do not check finetuning_args since it may be loaded from checkpoints) + if int(training_args.do_train) + int(training_args.do_eval) + int(training_args.do_predict) != 1: + raise ValueError("We must perform a single operation among do_train, do_eval and do_predict.") + + if model_args.quantization_bit is not None and training_args.do_train == False: + logger.warning("We do not recommend to evaluaute model in 4/8-bit mode.") + + if training_args.do_train and (not training_args.fp16): + logger.warning("We recommend enable fp16 mixed precision training for ChatGLM-6B.") + + training_args.optim = "adamw_torch" if training_args.optim == "adamw_hf" else training_args.optim # suppress warning + + # Log on each process the small summary: + logger.warning( + f"Process rank: {training_args.local_rank}, device: {training_args.device}, n_gpu: {training_args.n_gpu}\n" + + f" distributed training: {bool(training_args.local_rank != -1)}, 16-bits training: {training_args.fp16}" + ) + logger.info(f"Training/evaluation parameters {training_args}") + + # Set seed before initializing model. + transformers.set_seed(training_args.seed) + + return model_args, data_args, training_args, finetuning_args + + +def prepare_data( + model_args: ModelArguments, + data_args: DataTrainingArguments +) -> Dataset: + + def checksum(file_path, hash): + with open(file_path, "rb") as datafile: + binary_data = datafile.read() + sha1 = hashlib.sha1(binary_data).hexdigest() + if sha1 != hash: + logger.warning("Checksum failed for {}. It may vary depending on the platform.".format(file_path)) + + max_samples = data_args.max_samples + all_datasets = [] # support multiple datasets + + for dataset_info in data_args.dataset_list: + + logger.info("Loading dataset {}...".format(dataset_info)) + + if dataset_info.load_from == "hf_hub": + raw_datasets = load_dataset(dataset_info.dataset_name, cache_dir=model_args.cache_dir) + elif dataset_info.load_from == "script": + raw_datasets = load_dataset( + os.path.join(data_args.dataset_dir, dataset_info.dataset_name), + cache_dir=model_args.cache_dir + ) + elif dataset_info.load_from == "file": + data_file = os.path.join(data_args.dataset_dir, dataset_info.file_name) # support json, jsonl and csv + extension = dataset_info.file_name.split(".")[-1] + + if dataset_info.file_sha1 is not None: + checksum(data_file, dataset_info.file_sha1) + else: + logger.warning("Checksum failed: missing SHA-1 hash value in dataset_info.") + + raw_datasets = load_dataset( + extension, + data_files=data_file, + cache_dir=model_args.cache_dir, + use_auth_token=True if model_args.use_auth_token else None + ) + else: + raise NotImplementedError + + dataset = raw_datasets[data_args.split] + + if max_samples is not None: + max_samples_temp = min(len(dataset), max_samples) + dataset = dataset.select(range(max_samples_temp)) + + dummy_data = [None] * len(dataset) + for column, column_name in [ + ("prompt_column", "prompt"), + ("query_column", "query"), + ("response_column", "response"), + ("history_column", "history") + ]: # every dataset will have 4 columns same as each other + if getattr(dataset_info, column) != column_name: + if getattr(dataset_info, column): + dataset = dataset.rename_column(getattr(dataset_info, column), column_name) + else: # None or empty string + dataset = dataset.add_column(column_name, dummy_data) + all_datasets.append(dataset) + + if len(data_args.dataset_list) == 1: + all_datasets = all_datasets[0] + else: + all_datasets = concatenate_datasets(all_datasets) + + return all_datasets + + +def preprocess_data( + dataset: Dataset, + tokenizer: PreTrainedTokenizer, + data_args: DataTrainingArguments, + training_args: Seq2SeqTrainingArguments, + stage: Optional[Literal["sft", "rwd", "ppo"]] = "sft" +) -> Dataset: + + column_names = list(dataset.column_names) + prefix = data_args.source_prefix if data_args.source_prefix is not None else "" + + def format_example(examples): # support question with a single answer or multiple answers + for i in range(len(examples["prompt"])): + if examples["prompt"][i] and examples["response"][i]: + query, answer = examples["prompt"][i], examples["response"][i] + if examples["query"][i]: + query += examples["query"][i] + if examples["history"][i]: + prompt = "" + history = examples["history"][i] + for j, (old_query, response) in enumerate(history): + prompt += "[Round {}]\n问:{}\n答:{}\n".format(j, old_query, response) + prompt += "[Round {}]\n问:{}\n答:".format(len(history), query) + else: + prompt = query + prompt = prefix + prompt + yield prompt, answer + + def preprocess_function_train(examples): + # build inputs with format `X [gMASK] [BOS] Y [EOS]` and labels with format `[IGNORE] ... [IGNORE] [BOS] Y [EOS]` + model_inputs = {"input_ids": [], "labels": []} + for prompt, answer in format_example(examples): + source_ids = tokenizer.encode(text=prompt, add_special_tokens=False) + target_ids = tokenizer.encode(text=answer, add_special_tokens=False) + + if len(source_ids) > data_args.max_source_length - 2: # gmask and bos tokens + source_ids = source_ids[:data_args.max_source_length - 2] + if len(target_ids) > data_args.max_target_length - 1: # eos token + target_ids = target_ids[:data_args.max_target_length - 1] + + input_ids = tokenizer.build_inputs_with_special_tokens(source_ids, target_ids) + + context_length = input_ids.index(tokenizer.bos_token_id) + labels = [IGNORE_INDEX] * context_length + input_ids[context_length:] + + model_inputs["input_ids"].append(input_ids) + model_inputs["labels"].append(labels) + return model_inputs + + def preprocess_function_eval(examples): + # build inputs with format `[PAD] ... [PAD] X [gMASK] [BOS]` and labels with format `Y [gMASK] [BOS]` + # left-padding is needed for prediction, use the built-in function of the tokenizer + inputs, targets = [], [] + for prompt, answer in format_example(examples): + inputs.append(prompt) + targets.append(answer) + model_inputs = tokenizer(inputs, max_length=data_args.max_source_length, truncation=True, padding=True) + labels = tokenizer(text_target=targets, max_length=data_args.max_target_length, truncation=True) # no padding + if data_args.ignore_pad_token_for_loss: + labels["input_ids"] = [ + [(l_id if l_id != tokenizer.pad_token_id else IGNORE_INDEX) for l_id in label] for label in labels["input_ids"] + ] + model_inputs["labels"] = labels["input_ids"] + return model_inputs + + def preprocess_function_train_pair(examples): + # build input pairs with format `X [gMASK] [BOS] Y1 [EOS]` and `X [gMASK] [BOS] Y2 [EOS]` + model_inputs = {"accept_ids": [], "reject_ids": []} + for prompt, answer in format_example(examples): + source_ids = tokenizer.encode(text=prompt, add_special_tokens=False) + accept_ids = tokenizer.encode(text=answer[0], add_special_tokens=False) + reject_ids = tokenizer.encode(text=answer[1], add_special_tokens=False) + + if len(source_ids) > data_args.max_source_length - 2: # gmask and bos tokens + source_ids = source_ids[:data_args.max_source_length - 2] + if len(accept_ids) > data_args.max_target_length - 1: # eos token + accept_ids = accept_ids[:data_args.max_target_length - 1] + if len(reject_ids) > data_args.max_target_length - 1: # eos token + reject_ids = reject_ids[:data_args.max_target_length - 1] + + accept_ids = tokenizer.build_inputs_with_special_tokens(source_ids[:], accept_ids) # avoid copying error + reject_ids = tokenizer.build_inputs_with_special_tokens(source_ids[:], reject_ids) + + model_inputs["accept_ids"].append(accept_ids) + model_inputs["reject_ids"].append(reject_ids) + return model_inputs + + def preprocess_function_train_ppo(examples): + # build inputs with format `X [gMASK] [BOS]` + model_inputs = {"input_ids": []} + for prompt, _ in format_example(examples): + source_ids = tokenizer.encode(text=prompt, add_special_tokens=False) + + if len(source_ids) > data_args.max_source_length - 2: # gmask and bos tokens + source_ids = source_ids[:data_args.max_source_length - 2] + + input_ids = tokenizer.build_inputs_with_special_tokens(source_ids) + model_inputs["input_ids"].append(input_ids) + return model_inputs + + def print_sft_dataset_example(example): + print("input_ids:\n{}".format(example["input_ids"])) + print("inputs:\n{}".format(tokenizer.decode(example["input_ids"]))) + print("label_ids:\n{}".format(example["labels"])) + print("labels:\n{}".format(tokenizer.decode(example["labels"]))) + + def print_pairwise_dataset_example(example): + print("accept_ids:\n{}".format(example["accept_ids"])) + print("accepts:\n{}".format(tokenizer.decode(example["accept_ids"]))) + print("reject_ids:\n{}".format(example["reject_ids"])) + print("rejects:\n{}".format(tokenizer.decode(example["reject_ids"]))) + + def print_ppo_dataset_example(example): + print("input_ids:\n{}".format(example["input_ids"])) + print("inputs:\n{}".format(tokenizer.decode(example["input_ids"]))) + + if stage == "sft": + preprocess_function = preprocess_function_train if training_args.do_train else preprocess_function_eval + elif stage == "rwd": + preprocess_function = preprocess_function_train_pair + elif stage == "ppo": + preprocess_function = preprocess_function_train_ppo + + with training_args.main_process_first(desc="dataset map pre-processing"): + dataset = dataset.map( + preprocess_function, + batched=True, + num_proc=data_args.preprocessing_num_workers, + remove_columns=column_names, + load_from_cache_file=not data_args.overwrite_cache, + desc="Running tokenizer on dataset" + ) + + if stage == "sft": + print_sft_dataset_example(dataset[0]) + elif stage == "rwd": + print_pairwise_dataset_example(dataset[0]) + elif stage == "ppo": + print_ppo_dataset_example(dataset[0]) + + return dataset diff --git a/src/utils/config.py b/src/utils/config.py new file mode 100644 index 0000000000000000000000000000000000000000..849e0b57eb8f843e5eef26fc9a126f20211bf75c --- /dev/null +++ b/src/utils/config.py @@ -0,0 +1,219 @@ +import os +import json +from typing import Optional +from dataclasses import dataclass, field + + +CHATGLM_REPO_NAME = "THUDM/chatglm-6b" +CHATGLM_LASTEST_HASH = "a8ede826cf1b62bd3c78bdfb3625c7c5d2048fbd" + + +@dataclass +class DatasetAttr: + + load_from: str + dataset_name: Optional[str] = None + file_name: Optional[str] = None + file_sha1: Optional[str] = None + + def __post_init__(self): + self.prompt_column = "instruction" + self.query_column = "input" + self.response_column = "output" + self.history_column = None + + +@dataclass +class ModelArguments: + """ + Arguments pertaining to which model/config/tokenizer we are going to fine-tune. + """ + model_name_or_path: Optional[str] = field( + default=CHATGLM_REPO_NAME, + metadata={"help": "Path to pretrained model or model identifier from huggingface.co/models."} + ) + config_name: Optional[str] = field( + default=None, + metadata={"help": "Pretrained config name or path if not the same as model_name."} + ) + tokenizer_name: Optional[str] = field( + default=None, + metadata={"help": "Pretrained tokenizer name or path if not the same as model_name."} + ) + cache_dir: Optional[str] = field( + default=None, + metadata={"help": "Where to store the pretrained models downloaded from huggingface.co."} + ) + use_fast_tokenizer: Optional[bool] = field( + default=True, + metadata={"help": "Whether to use one of the fast tokenizer (backed by the tokenizers library) or not."} + ) + model_revision: Optional[str] = field( + default=CHATGLM_LASTEST_HASH, + metadata={"help": "The specific model version to use (can be a branch name, tag name or commit id)."} + ) + use_auth_token: Optional[bool] = field( + default=False, + metadata={"help": "Will use the token generated when running `huggingface-cli login`."} + ) + quantization_bit: Optional[int] = field( + default=None, + metadata={"help": "The number of bits to quantize the model."} + ) + checkpoint_dir: Optional[str] = field( + default=None, + metadata={"help": "Path to the directory containing the model checkpoints as well as the configurations."} + ) + reward_model: Optional[str] = field( + default=None, + metadata={"help": "Path to the directory containing the checkpoints of the reward model."} + ) + + def __post_init__(self): + if self.checkpoint_dir is not None: # support merging lora weights + self.checkpoint_dir = [cd.strip() for cd in self.checkpoint_dir.split(",")] + + +@dataclass +class DataTrainingArguments: + """ + Arguments pertaining to what data we are going to input our model for training and evaluation. + """ + dataset: Optional[str] = field( + default="alpaca_zh", + metadata={"help": "The name of provided dataset(s) to use. Use comma to separate multiple datasets."} + ) + dataset_dir: Optional[str] = field( + default="data", + metadata={"help": "The name of the folder containing datasets."} + ) + split: Optional[str] = field( + default="train", + metadata={"help": "Which dataset split to use for training and evaluation."} + ) + overwrite_cache: Optional[bool] = field( + default=False, + metadata={"help": "Overwrite the cached training and evaluation sets."} + ) + preprocessing_num_workers: Optional[int] = field( + default=None, + metadata={"help": "The number of processes to use for the preprocessing."} + ) + max_source_length: Optional[int] = field( + default=512, + metadata={"help": "The maximum total input sequence length after tokenization."} + ) + max_target_length: Optional[int] = field( + default=512, + metadata={"help": "The maximum total output sequence length after tokenization."} + ) + max_samples: Optional[int] = field( + default=None, + metadata={"help": "For debugging purposes, truncate the number of examples for each dataset."} + ) + num_beams: Optional[int] = field( + default=None, + metadata={"help": "Number of beams to use for evaluation. This argument will be passed to `model.generate`"} + ) + ignore_pad_token_for_loss: Optional[bool] = field( + default=True, + metadata={"help": "Whether to ignore the tokens corresponding to padded labels in the loss computation or not."} + ) + source_prefix: Optional[str] = field( + default=None, + metadata={"help": "A prefix to add before every source text (useful for T5 models)."} + ) + + def __post_init__(self): # support mixing multiple datasets + dataset_names = [ds.strip() for ds in self.dataset.split(",")] + dataset_info = json.load(open(os.path.join(self.dataset_dir, "dataset_info.json"), "r")) + + self.dataset_list = [] + for name in dataset_names: + if name not in dataset_info: + raise ValueError("Undefined dataset {} in dataset_info.json.".format(name)) + + if "hf_hub_url" in dataset_info[name]: + dataset_attr = DatasetAttr("hf_hub", dataset_name=dataset_info[name]["hf_hub_url"]) + elif "script_url" in dataset_info[name]: + dataset_attr = DatasetAttr("script", dataset_name=dataset_info[name]["script_url"]) + else: + dataset_attr = DatasetAttr( + "file", + file_name=dataset_info[name]["file_name"], + file_sha1=dataset_info[name]["file_sha1"] if "file_sha1" in dataset_info[name] else None + ) + + if "columns" in dataset_info[name]: + dataset_attr.prompt_column = dataset_info[name]["columns"].get("prompt", None) + dataset_attr.query_column = dataset_info[name]["columns"].get("query", None) + dataset_attr.response_column = dataset_info[name]["columns"].get("response", None) + dataset_attr.history_column = dataset_info[name]["columns"].get("history", None) + + self.dataset_list.append(dataset_attr) + + +@dataclass +class FinetuningArguments: + """ + Arguments pertaining to which techniques we are going to fine-tuning with. + """ + finetuning_type: Optional[str] = field( + default="lora", + metadata={"help": "Which fine-tuning method to use."} + ) + num_layer_trainable: Optional[int] = field( + default=3, + metadata={"help": "Number of trainable layers for Freeze fine-tuning."} + ) + name_module_trainable: Optional[str] = field( + default="mlp", + metadata={"help": "Name of trainable modules for Freeze fine-tuning."} + ) + pre_seq_len: Optional[int] = field( + default=16, + metadata={"help": "Number of prefix tokens to use for P-tuning V2."} + ) + prefix_projection: Optional[bool] = field( + default=False, + metadata={"help": "Whether to add a project layer for the prefix in P-tuning V2 or not."} + ) + lora_rank: Optional[int] = field( + default=8, + metadata={"help": "The intrinsic dimension for LoRA fine-tuning."} + ) + lora_alpha: Optional[float] = field( + default=32.0, + metadata={"help": "The scale factor for LoRA fine-tuning. (similar with the learning rate)"} + ) + lora_dropout: Optional[float] = field( + default=0.1, + metadata={"help": "Dropout rate for the LoRA fine-tuning."} + ) + lora_target: Optional[str] = field( + default="query_key_value", + metadata={"help": "Name(s) of target modules to apply LoRA. Use comma to separate multiple modules."} + ) + resume_lora_training: Optional[bool] = field( + default=True, + metadata={"help": "Whether to resume training from the last LoRA weights or create new weights after merging them."} + ) + plot_loss: Optional[bool] = field( + default=False, + metadata={"help": "Whether to plot the training loss after fine-tuning or not."} + ) + + def __post_init__(self): + self.lora_target = [target.strip() for target in self.lora_target.split(",")] # support custom target modules of LoRA + + if self.num_layer_trainable > 0: # fine-tuning the last n layers if num_layer_trainable > 0 + trainable_layer_ids = [27-k for k in range(self.num_layer_trainable)] + else: # fine-tuning the first n layers if num_layer_trainable < 0 + trainable_layer_ids = [k for k in range(-self.num_layer_trainable)] + if self.name_module_trainable == "mlp": + self.trainable_layers = ["layers.{:d}.mlp".format(idx) for idx in trainable_layer_ids] + elif self.name_module_trainable == "qkv": + self.trainable_layers = ["layers.{:d}.attention.query_key_value".format(idx) for idx in trainable_layer_ids] + + if self.finetuning_type not in ["none", "freeze", "p_tuning", "lora", "full"]: + raise NotImplementedError("Invalid fine-tuning method.") diff --git a/src/utils/other.py b/src/utils/other.py new file mode 100644 index 0000000000000000000000000000000000000000..032d3d4dc48486fe48a4e732f60f659f93d7e4c5 --- /dev/null +++ b/src/utils/other.py @@ -0,0 +1,197 @@ +import os +import sys +import json +import torch +import logging +from typing import Dict, List, Optional + +from transformers import Seq2SeqTrainingArguments +from transformers.trainer import TRAINER_STATE_NAME +from transformers.modeling_utils import PreTrainedModel +from transformers.generation.utils import LogitsProcessorList +from transformers.generation.logits_process import LogitsProcessor + + +from peft.utils.other import WEIGHTS_NAME + + +IGNORE_INDEX = -100 +VALUE_HEAD_FILE_NAME = "value_head.bin" +FINETUNING_ARGS_NAME = "finetuning_args.bin" +PREDICTION_FILE_NAME = "generated_predictions.txt" + + +logger = logging.getLogger(__name__) +logging.basicConfig( + format="%(asctime)s - %(levelname)s - %(name)s - %(message)s", + datefmt="%m/%d/%Y %H:%M:%S", + level=logging.INFO, + handlers=[logging.StreamHandler(sys.stdout)] +) + + +def get_logger(name: str) -> logging.Logger: + return logging.getLogger(name) + + +class AverageMeter: + r""" + Computes and stores the average and current value. + """ + def __init__(self): + self.reset() + + def reset(self): + self.val = 0 + self.avg = 0 + self.sum = 0 + self.count = 0 + + def update(self, val, n=1): + self.val = val + self.sum += val * n + self.count += n + self.avg = self.sum / self.count + + +# Avoid runtime error in model.generate(do_sample=True). +# Borrowed from: https://huggingface.co/THUDM/chatglm-6b/blob/658202d88ac4bb782b99e99ac3adff58b4d0b813/modeling_chatglm.py#L54 +class InvalidScoreLogitsProcessor(LogitsProcessor): + + def __call__(self, input_ids: torch.LongTensor, scores: torch.FloatTensor) -> torch.FloatTensor: + if torch.isnan(scores).any() or torch.isinf(scores).any(): + scores.zero_() + scores[..., 5] = 5e4 + return scores + + +def get_logits_processor() -> LogitsProcessorList: + logits_processor = LogitsProcessorList() + logits_processor.append(InvalidScoreLogitsProcessor()) + return logits_processor + + +# Includes: (1) cast the layernorm in fp32 (2) make output embedding layer require grads (3) upcast the lm_head to fp32 +# Inspired by: https://github.com/huggingface/peft/blob/c0209c35abbf88c63aa267800d98a8e212ed0a42/src/peft/utils/other.py#L35 +def prepare_model_for_training( + model: PreTrainedModel, + output_embedding_layer_name: Optional[str] = "lm_head", + use_gradient_checkpointing: Optional[bool] = True, + layer_norm_names: Optional[List[str]] = ["layernorm"] # for chatglm setting +) -> PreTrainedModel: + + for name, param in model.named_parameters(): + if param.ndim == 1 and any(layer_norm_name in name for layer_norm_name in layer_norm_names): + param.data = param.data.to(torch.float32) + + if use_gradient_checkpointing: + model.enable_input_require_grads() + model.gradient_checkpointing_enable() + model.config.use_cache = False # turn off when gradient checkpointing is enabled + + if hasattr(model, output_embedding_layer_name): + output_embedding_layer = getattr(model, output_embedding_layer_name) + input_dtype = output_embedding_layer.weight.dtype + + class CastOutputToFloat(torch.nn.Sequential): + + def forward(self, x): + return super().forward(x.to(input_dtype)).to(torch.float32) + + setattr(model, output_embedding_layer_name, CastOutputToFloat(output_embedding_layer)) + + return model + + +def print_trainable_params(model: torch.nn.Module) -> None: + trainable_params, all_param = 0, 0 + for param in model.parameters(): + num_params = param.numel() + # if using DS Zero 3 and the weights are initialized empty + if num_params == 0 and hasattr(param, "ds_numel"): + num_params = param.ds_numel + all_param += num_params + if param.requires_grad: + trainable_params += num_params + print("trainable params: {:d} || all params: {:d} || trainable%: {:.4f}".format( + trainable_params, all_param, 100 * trainable_params / all_param)) + + +def filter_model_params(model: torch.nn.Module) -> Dict[str, torch.Tensor]: # filter out freezed parameters + state_dict = model.state_dict() + filtered_state_dict = {} + for k, v in model.named_parameters(): + if v.requires_grad: + filtered_state_dict[k] = state_dict[k] + return filtered_state_dict + + +def save_trainable_params(save_directory: os.PathLike, model: torch.nn.Module) -> None: + if os.path.isfile(save_directory): + raise ValueError(f"Provided path ({save_directory}) should be a directory, not a file.") + os.makedirs(save_directory, exist_ok=True) + filtered_state_dict = filter_model_params(model) + torch.save(filtered_state_dict, os.path.join(save_directory, WEIGHTS_NAME)) + + +def load_trainable_params(model: torch.nn.Module, checkpoint_dir: os.PathLike) -> None: + weights_file = os.path.join(checkpoint_dir, WEIGHTS_NAME) + if not os.path.exists(weights_file): + raise ValueError(f"Provided path ({checkpoint_dir}) does not contain the pretrained weights.") + model_state_dict = torch.load(weights_file) + model.load_state_dict(model_state_dict, strict=False) # skip missing keys + + +def save_valuehead_params(save_directory: os.PathLike, v_head: torch.nn.Module) -> None: + if os.path.isfile(save_directory): + raise ValueError(f"Provided path ({save_directory}) should be a directory, not a file.") + os.makedirs(save_directory, exist_ok=True) + torch.save(v_head.state_dict(), os.path.join(save_directory, VALUE_HEAD_FILE_NAME)) + + +def load_valuehead_params(model: torch.nn.Module, checkpoint_dir: os.PathLike) -> None: + valuehead_file = os.path.join(checkpoint_dir, VALUE_HEAD_FILE_NAME) + if not os.path.exists(valuehead_file): + raise ValueError(f"Provided path ({checkpoint_dir}) does not contain the valuehead weights.") + valuehead_state_dict = torch.load(valuehead_file) + model.register_buffer("reward_head_weight", valuehead_state_dict["summary.weight"]) + model.register_buffer("reward_head_bias", valuehead_state_dict["summary.bias"]) + model.register_buffer("default_head_weight", torch.zeros_like(valuehead_state_dict["summary.weight"])) + model.register_buffer("default_head_bias", torch.zeros_like(valuehead_state_dict["summary.bias"])) + + +def smooth(scalars: List[float], weight: Optional[float] = 0.95) -> List[float]: + """ + EMA implementation according to TensorBoard. + """ + last = scalars[0] + smoothed = list() + for next_val in scalars: + smoothed_val = last * weight + (1 - weight) * next_val + smoothed.append(smoothed_val) + last = smoothed_val + return smoothed + + +def plot_loss(training_args: Seq2SeqTrainingArguments, keys: Optional[List[str]] = ["loss"]) -> None: + import matplotlib.pyplot as plt + data = json.load(open(os.path.join(training_args.output_dir, TRAINER_STATE_NAME), "r")) + + for key in keys: + steps, metrics = [], [] + + for i in range(len(data["log_history"])): + if key in data["log_history"][i]: + steps.append(data["log_history"][i]["step"]) + metrics.append(data["log_history"][i][key]) + smoothed_value = smooth(metrics) + + plt.figure() + plt.plot(steps, metrics, alpha=0.4, label="original") + plt.plot(steps, smoothed_value, label="smoothed") + plt.title("training {} of {}".format(key, training_args.output_dir)) + plt.xlabel("step") + plt.ylabel(key) + plt.legend() + plt.savefig(os.path.join(training_args.output_dir, "training_{}.jpg".format(key)), format="jpg", dpi=100) + print("Figure saved:", os.path.join(training_args.output_dir, "training_{}.jpg".format(key))) diff --git a/src/utils/pairwise.py b/src/utils/pairwise.py new file mode 100644 index 0000000000000000000000000000000000000000..dbd78baacd91bd9fee90f28f540122e9a880bad6 --- /dev/null +++ b/src/utils/pairwise.py @@ -0,0 +1,101 @@ +import os +import torch +from typing import Dict, Optional, Sequence + +from transformers import Trainer, DataCollatorWithPadding +from transformers.trainer import TRAINING_ARGS_NAME +from transformers.modeling_utils import unwrap_model +from transformers.tokenization_utils import PreTrainedTokenizer + +from .config import FinetuningArguments + +from .other import ( + get_logger, + save_trainable_params, + save_valuehead_params, + FINETUNING_ARGS_NAME +) + + +logger = get_logger(__name__) + + +class PairwiseDataCollatorForChatGLM(DataCollatorWithPadding): + r""" + Data collator for ChatGLM. It is capable of dynamically padding for batched data. + + Inspired by: https://github.com/tatsu-lab/stanford_alpaca/blob/65512697dc67779a6e53c267488aba0ec4d7c02a/train.py#L156 + """ + def __init__( + self, + tokenizer: PreTrainedTokenizer, + inference_mode: bool = False + ): + super().__init__(tokenizer, padding=True) + self.inference_mode = inference_mode + + def __call__(self, features: Sequence[Dict[str, Sequence]]) -> Dict[str, torch.Tensor]: + r""" + Pads batched data to the longest sequence in the batch. We adopt right-padding for pairwise data. + + We generate 2 * n examples where the first n examples represents chosen examples and + the last n examples represents rejected examples. + + ChatGLM is able to generate attentions masks and position ids by itself. + """ + if self.inference_mode: + raise NotImplementedError + accept_ids, reject_ids = [[torch.tensor(feature[key]) for feature in features] for key in ("accept_ids", "reject_ids")] + input_ids = accept_ids + reject_ids + input_ids = torch.nn.utils.rnn.pad_sequence(input_ids, batch_first=True, padding_value=self.tokenizer.pad_token_id) + features = {"input_ids": input_ids} + return features + +class PairwiseTrainerForChatGLM(Trainer): + r""" + Inherits Trainer to compute pairwise loss. + """ + + def __init__(self, finetuning_args: FinetuningArguments, *args, **kwargs): + super().__init__(*args, **kwargs) + self.finetuning_args = finetuning_args + + def compute_loss(self, model, inputs, return_outputs=False): + r""" + Computes pairwise loss. The first n examples are chosen and the last n examples are rejected. + + We use score on the EOS token to represent reward of the whole sentence. + """ + batch_size = inputs["input_ids"].size(0) // 2 + _, _, values = model(input_ids=inputs["input_ids"]) + rewards = values.transpose(0, 1)[(inputs["input_ids"] == self.tokenizer.eos_token_id).nonzero(as_tuple=True)] + r_accept, r_reject = rewards.split(batch_size, dim=0) + loss = -torch.log(torch.sigmoid(r_accept - r_reject)).mean() + if return_outputs: + return loss, {"r_accept": r_accept, "r_reject": r_reject} + return loss + + def _save(self, output_dir: Optional[str] = None, state_dict: Optional[Dict[str, torch.Tensor]] = None) -> None: + r""" + Saves trainable parameters as model checkpoints. Use `self.model.pretrained_model` to refer to the backbone model. + + This function will only be executed at the process zero. + + Override to inject custom behavior. + """ + output_dir = output_dir if output_dir is not None else self.args.output_dir + os.makedirs(output_dir, exist_ok=True) + logger.info(f"Saving model checkpoint to {output_dir}") + + model_to_save = unwrap_model(self.model) + + if hasattr(model_to_save.pretrained_model, "peft_config"): # peft methods + model_to_save.pretrained_model.save_pretrained(output_dir) # save lora weights + else: # non-peft methods + save_trainable_params(output_dir, model_to_save.pretrained_model) + + if hasattr(model_to_save, "v_head"): + save_valuehead_params(output_dir, model_to_save.v_head) # save valuehead weights + + torch.save(self.args, os.path.join(output_dir, TRAINING_ARGS_NAME)) + torch.save(self.finetuning_args, os.path.join(output_dir, FINETUNING_ARGS_NAME)) diff --git a/src/utils/ppo.py b/src/utils/ppo.py new file mode 100644 index 0000000000000000000000000000000000000000..7fe76f51bbe5196284fb94bbb0ad8a70a5fa4df9 --- /dev/null +++ b/src/utils/ppo.py @@ -0,0 +1,368 @@ +import os +import json +import math +import torch +from tqdm import tqdm +from typing import Callable, Dict, List, Literal, Optional, Sequence, Tuple + +from transformers import DataCollatorWithPadding, Seq2SeqTrainingArguments +from transformers.trainer import TRAINING_ARGS_NAME, TRAINER_STATE_NAME +from transformers.tokenization_utils import PreTrainedTokenizer + +from trl import PPOTrainer, AutoModelForCausalLMWithValueHead +from trl.core import LengthSampler +from trl.trainer.ppo_trainer import PPODecorators, logprobs_from_logits + +from .config import FinetuningArguments + +from .other import ( + AverageMeter, + get_logger, + save_trainable_params, + save_valuehead_params, + get_logits_processor, + FINETUNING_ARGS_NAME +) + + +logger = get_logger(__name__) + + +def replace_model(model: AutoModelForCausalLMWithValueHead, target: Literal["default", "reward"]) -> None: + if target == "reward": # save original head temporarily + valuehead_state_dict = model.v_head.state_dict() + + setattr(model, "origin_head_weight", valuehead_state_dict["summary.weight"]) + setattr(model, "origin_head_bias", valuehead_state_dict["summary.bias"]) + + model.pretrained_model.set_adapter(target) # set the LoRA adapter to be active + model.v_head.load_state_dict({ + "summary.weight": getattr(model, "{}_head_weight".format(target)), + "summary.bias": getattr(model, "{}_head_bias".format(target)) + }) + + +@torch.no_grad() +def compute_rewards( + input_ids: torch.Tensor, # (batch size x seq len) with format `X [gMASK] [BOS] Y [EOS] [PAD] ... [PAD]` + model: AutoModelForCausalLMWithValueHead, + tokenizer: PreTrainedTokenizer +) -> torch.Tensor: + + replace_model(model, target="reward") + + _, _, values = model(input_ids=input_ids) + values = values.transpose(0, 1) + + rewards = [] + for i in range(input_ids.size(0)): + eos_idx = (input_ids[i] == tokenizer.eos_token_id).nonzero() # Note: checking with [EOS] token is unsafe + if len(eos_idx): + eos_idx = eos_idx[0].item() + else: + eos_idx = input_ids.size(1) - 1 + rewards.append(values[i][eos_idx]) + rewards = torch.stack(rewards, dim=0) + + replace_model(model, target="default") + + return rewards + + +def cast_layernorm_dtype( + model: AutoModelForCausalLMWithValueHead, + layer_norm_names: List[str] = ["layernorm"], # for chatglm setting + layer_norm_params: Optional[Dict[str, torch.Tensor]] = None +) -> Tuple[AutoModelForCausalLMWithValueHead, Dict[str, torch.Tensor]]: + + layer_norm_state_dict = {} + + for name, param in model.named_parameters(): + if param.ndim == 1 and any(layer_norm_name in name for layer_norm_name in layer_norm_names): + if layer_norm_params is not None: + param.data = layer_norm_params[name] # restore float32 weights + else: + layer_norm_state_dict[name] = param.data.detach().clone() # store float32 weights for stability + param.data = param.data.to(torch.float16) + + return model, layer_norm_state_dict + + +class PPODataCollatorForChatGLM(DataCollatorWithPadding): + r""" + Data collator for ChatGLM. It is capable of dynamically padding for batched data. + """ + def __init__( + self, + tokenizer: PreTrainedTokenizer, + min_input_length: int, + max_input_length: int, + inference_mode: bool = False, + ): + super().__init__(tokenizer, padding=True) + self.inference_mode = inference_mode + + if min_input_length < max_input_length: + self.input_size = LengthSampler(min_input_length, max_input_length) + else: + self.input_size = lambda: max_input_length # always use max_input_length + + def __call__(self, features: Sequence[Dict[str, Sequence]]) -> Dict[str, torch.Tensor]: + r""" + Pads batched data to the longest sequence in the batch. We adopt left-padding for ppo data. + + Equips with a length sampler to generate sequences with variable lengths. + + ChatGLM is able to generate attentions masks and position ids by itself. + """ + if self.inference_mode: + raise NotImplementedError + + input_ids = [torch.tensor(feature["input_ids"][:self.input_size()]).flip(0) for feature in features] + input_ids = torch.nn.utils.rnn.pad_sequence(input_ids, batch_first=True, padding_value=self.tokenizer.pad_token_id) + features = {"input_ids": input_ids.flip(-1)} + return features + + +class PPOTrainerForChatGLM(PPOTrainer): + r""" + Inherits PPOTrainer. + """ + + def __init__(self, training_args: Seq2SeqTrainingArguments, finetuning_args: FinetuningArguments, *args, **kwargs): + super().__init__(*args, **kwargs) + self.state = {"log_history": []} + self.training_args = training_args + self.finetuning_args = finetuning_args + + @torch.no_grad() + def generate( + self, + query_tensor: torch.Tensor, # (batch size x seq len) + length_sampler: Callable = None, + return_prompt: bool = True, + **generation_kwargs, + ) -> torch.Tensor: + r""" + Generate response with the model given the query tensor. + + Inspired by: https://github.com/lvwerra/trl/blob/08f550674c553c36c51d1027613c29f14f3676a5/trl/trainer/ppo_trainer.py#L387 + """ + + self.model, layer_norm_params = cast_layernorm_dtype(self.model) + + if length_sampler is not None: + generation_kwargs["max_new_tokens"] = length_sampler() + + unwrapped_model = self.accelerator.unwrap_model(self.model) + + response = unwrapped_model.generate( + input_ids=query_tensor, **generation_kwargs + ) + + # Temporary hack to ensure the generation config is not initialized for each iteration of the evaluation loop + # Inspired by: https://github.com/huggingface/transformers/blob/v4.28.1/src/transformers/trainer_seq2seq.py#L273 + if unwrapped_model.pretrained_model.generation_config._from_model_config: + unwrapped_model.pretrained_model.generation_config._from_model_config = False + + self.model, _ = cast_layernorm_dtype(self.model, layer_norm_params) + + if not return_prompt and not self.is_encoder_decoder: + return response[:, query_tensor.size(1):] + return response + + def prepare_model_inputs(self, queries: torch.Tensor, responses: torch.Tensor): + input_ids = [] + for query, response in zip(queries, responses): # query is left-padded, response is right-padded + start = (query != self.tokenizer.pad_token_id).nonzero()[0].item() + input_ids.append(torch.cat((query[start:], response, query[:start]))) # change to right-padding + + model_inputs = {"input_ids": torch.stack(input_ids, dim=0).to(self.current_device)} # already padded to equal length + model_inputs["attention_mask"] = torch.ones_like(model_inputs["input_ids"]) # unused indeed, avoid distributed error + return model_inputs + + @PPODecorators.empty_cuda_cache() + def batched_forward_pass( + self, + model: AutoModelForCausalLMWithValueHead, + queries: torch.Tensor, + responses: torch.Tensor, + model_inputs: dict, + ): + r""" + Calculate model outputs in multiple batches. + + Override to inject custom behavior. + """ + bs = len(queries) + fbs = self.config.mini_batch_size + all_logprobs = [] + all_logits = [] + all_masks = [] + all_values = [] + + for i in range(int(bs / fbs)): + input_kwargs = {key: value[i * fbs : (i + 1) * fbs] for key, value in model_inputs.items()} + + input_ids = input_kwargs["input_ids"] + logits, _, values = model(input_ids=input_ids) # chatglm only needs input_ids + logprobs = logprobs_from_logits(logits[:, :-1, :], input_ids[:, 1:]) + + values = values.transpose(0, 1) + masks = torch.zeros_like(input_ids) + + for j in range(fbs): + start = (input_ids[j] == self.tokenizer.bos_token_id).nonzero()[0].item() # always contain a [BOS] token + end = (input_ids[j] == self.tokenizer.eos_token_id).nonzero() # Note: checking with [EOS] token is unsafe + if len(end): + end = end[0].item() + else: + end = masks.size(1) + masks[j][start:end] = 1 + if end - start < 2: + raise ValueError("Responses are too short. Make sure they are at least 4 tokens long.") + + all_logits.append(logits) + all_values.append(values) + all_logprobs.append(logprobs) + all_masks.append(masks) + + return ( + torch.cat(all_logprobs), + torch.cat(all_logits)[:, :-1], + torch.cat(all_values)[:, :-1], + torch.cat(all_masks)[:, :-1], + ) + + def ppo_train(self, max_target_length: int) -> None: + + total_train_batch_size = self.config.batch_size * self.config.gradient_accumulation_steps * self.training_args.world_size + len_dataloader = len(self.dataloader) + num_steps_per_epoch = max(len_dataloader // self.config.gradient_accumulation_steps, 1) + num_examples = len(self.dataset) + num_train_epochs = self.training_args.num_train_epochs + max_steps = math.ceil(num_train_epochs * num_steps_per_epoch) + + if self.is_world_process_zero(): + logger.info("***** Running training *****") + logger.info(f" Num examples = {num_examples}") + logger.info(f" Num Epochs = {num_train_epochs}") + logger.info(f" Instantaneous batch size per device = {self.config.batch_size}") + logger.info(f" Total train batch size (w. parallel, distributed & accumulation) = {total_train_batch_size}") + logger.info(f" Gradient Accumulation steps = {self.config.gradient_accumulation_steps}") + logger.info(f" Total optimization steps = {max_steps}") + logger.info(f" Number of trainable parameters = {sum(p.numel() for p in self.model.parameters() if p.requires_grad)}") + + # Keyword arguments for `model.generate` + gen_kwargs = { + "top_k": 0.0, + "top_p": 1.0, + "do_sample": True, + "pad_token_id": self.tokenizer.pad_token_id, + "eos_token_id": self.tokenizer.eos_token_id, + "logits_processor": get_logits_processor() + } + output_length_sampler = LengthSampler(max_target_length // 2, max_target_length) + unwrapped_model = self.accelerator.unwrap_model(self.model) + + dataiter = iter(self.dataloader) + steps_trained = 0 + loss_meter = AverageMeter() + reward_meter = AverageMeter() + + for step in tqdm(range(max_steps)): + + for _ in range(self.config.gradient_accumulation_steps): + + batch = next(dataiter) + steps_trained += 1 + queries = batch["input_ids"] # left-padded sequences + + unwrapped_model.gradient_checkpointing_disable() + unwrapped_model.config.use_cache = True + + # Get response from ChatGLM + responses_with_queries = self.generate(queries, length_sampler=output_length_sampler, **gen_kwargs) + responses = responses_with_queries[:, queries.size(1):].clone().detach() # right-padded sequences (remember to clone!!!) + # batch["response"] = tokenizer.batch_decode(responses, skip_special_tokens=True) # comment to avoid decode error + + for i in range(responses_with_queries.size(0)): # change to right-padding + start = (responses_with_queries[i] != self.tokenizer.pad_token_id).nonzero()[0].item() + responses_with_queries[i] = torch.cat((responses_with_queries[i][start:], responses_with_queries[i][:start])) + + # Compute rewards + rewards = compute_rewards(responses_with_queries, unwrapped_model, self.tokenizer) + + # Run PPO step + unwrapped_model.gradient_checkpointing_enable() + unwrapped_model.config.use_cache = False + + split_into_list = lambda x: [x[i] for i in range(x.size(0))] + stats = self.step(*map(split_into_list, [queries, responses, rewards])) + + loss_meter.update(stats["ppo/loss/total"]) + reward_meter.update(rewards.sum().item(), n=rewards.size(0)) + + if steps_trained == len_dataloader: + dataiter = iter(self.dataloader) + steps_trained = 0 + + if self.is_world_process_zero() and (step+1) % self.training_args.logging_steps == 0: + logs = { + "loss": round(loss_meter.avg, 4), + "reward": round(reward_meter.avg, 4), + "learning_rate": stats["ppo/learning_rate"], + "epoch": round(step / num_steps_per_epoch, 2) + } + print(logs) + logs["step"] = step + self.state["log_history"].append(logs) + loss_meter.reset() + reward_meter.reset() + + if (step+1) % self.training_args.save_steps == 0: # save checkpoint + self.save_model(os.path.join(self.training_args.output_dir, f"checkpoint-{step+1}")) + + def is_world_process_zero(self) -> bool: + r""" + Whether or not this process is the global main process (when training in a distributed fashion on several + machines, this is only going to be `True` for one process). + """ + return self.training_args.process_index == 0 + + def save_state(self, output_dir: Optional[str] = None) -> None: + r""" + Saves trainer state. + """ + if not self.is_world_process_zero(): + return + + output_dir = output_dir if output_dir is not None else self.training_args.output_dir + os.makedirs(output_dir, exist_ok=True) + json.dump(self.state, open(os.path.join(output_dir, TRAINER_STATE_NAME), "w", encoding="utf-8", newline="\n"), indent=2) + + def save_model(self, output_dir: Optional[str] = None) -> None: + r""" + Saves trainable parameters as model checkpoints. We use `self.model.pretrained_model` to refer to the backbone model. + + Override to inject custom behavior. + """ + if not self.is_world_process_zero(): + return + + output_dir = output_dir if output_dir is not None else self.training_args.output_dir + os.makedirs(output_dir, exist_ok=True) + logger.info(f"Saving model checkpoint to {output_dir}") + + unwrapped_model = self.accelerator.unwrap_model(self.model) + + if hasattr(unwrapped_model.pretrained_model, "peft_config"): # peft methods + unwrapped_model.pretrained_model.save_pretrained(output_dir) # save lora weights + else: # non-peft methods + save_trainable_params(output_dir, unwrapped_model.pretrained_model) + + if hasattr(unwrapped_model, "v_head"): + save_valuehead_params(output_dir, unwrapped_model.v_head) # save valuehead weights + + torch.save(self.training_args, os.path.join(output_dir, TRAINING_ARGS_NAME)) + torch.save(self.finetuning_args, os.path.join(output_dir, FINETUNING_ARGS_NAME)) diff --git a/src/utils/seq2seq.py b/src/utils/seq2seq.py new file mode 100644 index 0000000000000000000000000000000000000000..de99f9d7c12a9eb27181830a6ee58291a1ace6c6 --- /dev/null +++ b/src/utils/seq2seq.py @@ -0,0 +1,245 @@ +import os +import json +import torch +import numpy as np +from dataclasses import dataclass +from typing import Any, Dict, List, Optional, Sequence, Tuple, Union + +from transformers import Seq2SeqTrainer, DataCollatorForSeq2Seq +from transformers.trainer import PredictionOutput, TRAINING_ARGS_NAME +from transformers.deepspeed import is_deepspeed_zero3_enabled +from transformers.modeling_utils import PreTrainedModel, unwrap_model +from transformers.tokenization_utils import PreTrainedTokenizer + +import jieba +from rouge_chinese import Rouge +from nltk.translate.bleu_score import sentence_bleu, SmoothingFunction + +from .config import FinetuningArguments + +from .other import ( + get_logger, + save_trainable_params, + IGNORE_INDEX, + FINETUNING_ARGS_NAME, + PREDICTION_FILE_NAME +) + + +logger = get_logger(__name__) + + +# Note: The ChatGLM tokenizer assigns False on token to be attended in attention mask. In general settings, it should be True. +# Refer to: https://huggingface.co/THUDM/chatglm-6b/blob/6650ae3a53c28fc176d06762ca80b05d5ab3792b/tokenization_chatglm.py#L401 +class Seq2SeqDataCollatorForChatGLM(DataCollatorForSeq2Seq): + r""" + Data collator for ChatGLM. It is capable of dynamically padding for batched data. + + Inspired by: https://github.com/tatsu-lab/stanford_alpaca/blob/65512697dc67779a6e53c267488aba0ec4d7c02a/train.py#L156 + """ + def __init__( + self, + tokenizer: PreTrainedTokenizer, + model: PreTrainedModel, + ignore_pad_token_for_loss: bool, + inference_mode: bool = False + ): + label_pad_token_id = IGNORE_INDEX if ignore_pad_token_for_loss else tokenizer.pad_token_id + super().__init__(tokenizer, model=model, label_pad_token_id=label_pad_token_id, padding=True) + self.label_pad_token_id = label_pad_token_id + self.inference_mode = inference_mode + + def __call__(self, features: Sequence[Dict[str, Sequence]]) -> Dict[str, torch.Tensor]: + r""" + Pads batched data to the longest sequence in the batch. + + ChatGLM is able to generate attentions masks and position ids by itself. + """ + if self.inference_mode: # evaluation set adopts left-padding while training set adopts right-padding + return super().__call__(features) + input_ids, labels = [[torch.tensor(feature[key]) for feature in features] for key in ("input_ids", "labels")] + input_ids = torch.nn.utils.rnn.pad_sequence(input_ids, batch_first=True, padding_value=self.tokenizer.pad_token_id) + labels = torch.nn.utils.rnn.pad_sequence(labels, batch_first=True, padding_value=self.label_pad_token_id) + features = {"input_ids": input_ids, "labels": labels} + return features + + +@dataclass +class ComputeMetrics: + r""" + Wraps the tokenizer into metric functions, used in Seq2SeqTrainerForChatGLM. + + Borrowed from: https://github.com/THUDM/ChatGLM-6B/blob/0c2806fea82683349194e21996dd6b3acc3c265b/ptuning/main.py#L307 + """ + + tokenizer: PreTrainedTokenizer + + def __call__(self, eval_preds: Sequence[Union[np.ndarray, Tuple[np.ndarray]]]) -> Dict[str, float]: + r""" + Uses the model predictions to compute metrics. + """ + preds, labels = eval_preds + if isinstance(preds, tuple): + preds = preds[0] + decoded_preds = self.tokenizer.batch_decode(preds, skip_special_tokens=True) + # Replace IGNORE_INDEX in the labels with pad_token_id as we cannot decode them if ignore_pad_token_for_loss=True. + labels = np.where(labels != IGNORE_INDEX, labels, self.tokenizer.pad_token_id) + decoded_labels = self.tokenizer.batch_decode(labels, skip_special_tokens=True) + + score_dict = {"rouge-1": [], "rouge-2": [], "rouge-l": [], "bleu-4": []} + for pred, label in zip(decoded_preds, decoded_labels): + hypothesis = list(jieba.cut(pred)) + reference = list(jieba.cut(label)) + + if len(" ".join(hypothesis).split()) == 0: + result = {"rouge-1": {"f": 0.0}, "rouge-2": {"f": 0.0}, "rouge-l": {"f": 0.0}} + else: + rouge = Rouge() + scores = rouge.get_scores(" ".join(hypothesis), " ".join(reference)) + result = scores[0] + + for k, v in result.items(): + score_dict[k].append(round(v["f"] * 100, 4)) + + bleu_score = sentence_bleu([list(label)], list(pred), smoothing_function=SmoothingFunction().method3) + score_dict["bleu-4"].append(round(bleu_score * 100, 4)) + + return {k: float(np.mean(v)) for k, v in score_dict.items()} + + +class Seq2SeqTrainerForChatGLM(Seq2SeqTrainer): + r""" + Inherits Seq2SeqTrainer to compute generative metrics such as BLEU and ROUGE. + """ + + def __init__(self, finetuning_args: FinetuningArguments, *args, **kwargs): + super().__init__(*args, **kwargs) + self.finetuning_args = finetuning_args + + def _save(self, output_dir: Optional[str] = None, state_dict: Optional[Dict[str, torch.Tensor]] = None) -> None: + r""" + Saves trainable parameters as model checkpoints. + + This function will only be executed at the process zero. + + Override to inject custom behavior. + """ + output_dir = output_dir if output_dir is not None else self.args.output_dir + os.makedirs(output_dir, exist_ok=True) + logger.info(f"Saving model checkpoint to {output_dir}") + + model_to_save = unwrap_model(self.model) + + if hasattr(self.model, "peft_config"): # peft methods + model_to_save.save_pretrained(output_dir) # save lora weights + else: # non-peft methods + save_trainable_params(output_dir, model_to_save) + + torch.save(self.args, os.path.join(output_dir, TRAINING_ARGS_NAME)) + torch.save(self.finetuning_args, os.path.join(output_dir, FINETUNING_ARGS_NAME)) + + def prediction_step( + self, + model: torch.nn.Module, + inputs: Dict[str, Union[torch.Tensor, Any]], + prediction_loss_only: bool, + ignore_keys: Optional[List[str]] = None + ) -> Tuple[Optional[float], Optional[torch.Tensor], Optional[torch.Tensor]]: + r""" + Performs an evaluation step on `model` using `inputs` for ChatGLM. + + Now it only supports single GPU (without Accelerate). + + Override to inject custom behavior. It is not directly used by external scripts. + """ + # Override to inject custom bevavior. + if not self.args.predict_with_generate or prediction_loss_only: + return super().prediction_step( + model, inputs, prediction_loss_only=prediction_loss_only, ignore_keys=ignore_keys + ) + + has_labels = "labels" in inputs + inputs = self._prepare_inputs(inputs) + + gen_kwargs = self._gen_kwargs.copy() + if gen_kwargs.get("max_length") is None and gen_kwargs.get("max_new_tokens") is None: + gen_kwargs["max_length"] = self.model.config.max_length + gen_kwargs["num_beams"] = gen_kwargs["num_beams"] \ + if gen_kwargs.get("num_beams") is not None else self.model.config.num_beams + default_synced_gpus = True if is_deepspeed_zero3_enabled() else False + gen_kwargs["synced_gpus"] = gen_kwargs["synced_gpus"] \ + if gen_kwargs.get("synced_gpus") is not None else default_synced_gpus + + if "attention_mask" in inputs: + gen_kwargs["attention_mask"] = inputs.get("attention_mask", None) + if "position_ids" in inputs: + gen_kwargs["position_ids"] = inputs.get("position_ids", None) + if "global_attention_mask" in inputs: + gen_kwargs["global_attention_mask"] = inputs.get("global_attention_mask", None) + + # prepare generation inputs + if hasattr(self.model, "encoder") and self.model.encoder.main_input_name != self.model.main_input_name: + generation_inputs = inputs[self.model.encoder.main_input_name] + else: + generation_inputs = inputs[self.model.main_input_name] + + gen_kwargs["input_ids"] = generation_inputs + generated_tokens = self.model.generate(**gen_kwargs) + generated_tokens = generated_tokens[:, generation_inputs.size()[-1]:] # important for ChatGLM + + # Temporary hack to ensure the generation config is not initialized for each iteration of the evaluation loop + # Inspired by: https://github.com/huggingface/transformers/blob/v4.28.1/src/transformers/trainer_seq2seq.py#L273 + if self.model.generation_config._from_model_config: + self.model.generation_config._from_model_config = False + + # Retrieves GenerationConfig from model.generation_config + gen_config = self.model.generation_config + # in case the batch is shorter than max length, the output should be padded + if generated_tokens.shape[-1] < gen_config.max_length: + generated_tokens = self._pad_tensors_to_max_len(generated_tokens, gen_config.max_length) + elif gen_config.max_new_tokens is not None and generated_tokens.shape[-1] < gen_config.max_new_tokens + 1: + generated_tokens = self._pad_tensors_to_max_len(generated_tokens, gen_config.max_new_tokens + 1) + + loss = None + + if self.args.prediction_loss_only: + return loss, None, None + + if has_labels: + labels = inputs["labels"] + if labels.shape[-1] < gen_config.max_length: + labels = self._pad_tensors_to_max_len(labels, gen_config.max_length) + elif gen_config.max_new_tokens is not None and labels.shape[-1] < gen_config.max_new_tokens + 1: + labels = self._pad_tensors_to_max_len(labels, gen_config.max_new_tokens + 1) + else: + labels = None + + return loss, generated_tokens, labels + + def save_predictions( + self, + predict_results: PredictionOutput, + tokenizer: PreTrainedTokenizer + ) -> None: + r""" + Saves model predictions to `output_dir`. + + A custom behavior that not contained in Seq2SeqTrainer. + """ + if not self.is_world_process_zero(): + return + if not self.args.predict_with_generate: + raise ValueError("Please enable `predict_with_generate` for saving model predictions.") + + predictions = tokenizer.batch_decode(predict_results.predictions, skip_special_tokens=True) + predictions = [pred.strip() for pred in predictions] + labels = tokenizer.batch_decode(predict_results.label_ids, skip_special_tokens=True) + labels = [label.strip() for label in labels] + + output_prediction_file = os.path.join(self.args.output_dir, PREDICTION_FILE_NAME) + logger.info(f"Saving prediction results to {output_prediction_file}") + with open(output_prediction_file, "w", encoding="utf-8") as writer: + res = [] + for pred, label in zip(predictions, labels): + res.append(json.dumps({"label": label, "predict": pred}, ensure_ascii=False)) + writer.write("\n".join(res)) diff --git a/src/web_demo.py b/src/web_demo.py new file mode 100644 index 0000000000000000000000000000000000000000..0bfccf9f2bbd84ddcb127581fbcbc792c5dfd91e --- /dev/null +++ b/src/web_demo.py @@ -0,0 +1,110 @@ +# coding=utf-8 +# Implement user interface in browser for ChatGLM fine-tuned with PEFT. +# This code is largely borrowed from https://github.com/THUDM/ChatGLM-6B/blob/main/web_demo.py + + +import gradio as gr +import mdtex2html + +from utils import ModelArguments, load_pretrained +from transformers import HfArgumentParser + + +parser = HfArgumentParser(ModelArguments) +model_args, = parser.parse_args_into_dataclasses() +model, tokenizer = load_pretrained(model_args) +model = model.cuda() +model.eval() + + +"""Override Chatbot.postprocess""" + +def postprocess(self, y): + if y is None: + return [] + for i, (message, response) in enumerate(y): + y[i] = ( + None if message is None else mdtex2html.convert((message)), + None if response is None else mdtex2html.convert(response), + ) + return y + + +gr.Chatbot.postprocess = postprocess + + +def parse_text(text): # copy from https://github.com/GaiZhenbiao/ChuanhuChatGPT + lines = text.split("\n") + lines = [line for line in lines if line != ""] + count = 0 + for i, line in enumerate(lines): + if "```" in line: + count += 1 + items = line.split('`') + if count % 2 == 1: + lines[i] = f'
'
+            else:
+                lines[i] = f'
' + else: + if i > 0: + if count % 2 == 1: + line = line.replace("`", "\`") + line = line.replace("<", "<") + line = line.replace(">", ">") + line = line.replace(" ", " ") + line = line.replace("*", "*") + line = line.replace("_", "_") + line = line.replace("-", "-") + line = line.replace(".", ".") + line = line.replace("!", "!") + line = line.replace("(", "(") + line = line.replace(")", ")") + line = line.replace("$", "$") + lines[i] = "
"+line + text = "".join(lines) + return text + + +def predict(input, chatbot, max_length, top_p, temperature, history): + chatbot.append((parse_text(input), "")) + for response, history in model.stream_chat(tokenizer, input, history, max_length=max_length, top_p=top_p, + temperature=temperature): + chatbot[-1] = (parse_text(input), parse_text(response)) + + yield chatbot, history + + +def reset_user_input(): + return gr.update(value='') + + +def reset_state(): + return [], [] + + +with gr.Blocks() as demo: + gr.HTML("""

ChatGLM-Efficient-Tuning

""") + + chatbot = gr.Chatbot() + with gr.Row(): + with gr.Column(scale=4): + with gr.Column(scale=12): + user_input = gr.Textbox(show_label=False, placeholder="Input...", lines=10).style( + container=False) + with gr.Column(min_width=32, scale=1): + submitBtn = gr.Button("Submit", variant="primary") + with gr.Column(scale=1): + emptyBtn = gr.Button("Clear History") + max_length = gr.Slider(0, 4096, value=2048, step=1.0, label="Maximum length", interactive=True) + top_p = gr.Slider(0, 1, value=0.7, step=0.01, label="Top P", interactive=True) + temperature = gr.Slider(0, 1, value=0.95, step=0.01, label="Temperature", interactive=True) + + history = gr.State([]) + + submitBtn.click(predict, [user_input, chatbot, max_length, top_p, temperature, history], [chatbot, history], + show_progress=True) + submitBtn.click(reset_user_input, [], [user_input]) + + emptyBtn.click(reset_state, outputs=[chatbot, history], show_progress=True) + +demo.queue().launch(server_name="0.0.0.0", share=False, inbrowser=True) diff --git a/temp/adapter_config.json b/temp/adapter_config.json new file mode 100644 index 0000000000000000000000000000000000000000..8db91212ea5bf268e4dbec98ce6d0005ba2b7891 --- /dev/null +++ b/temp/adapter_config.json @@ -0,0 +1,16 @@ +{ + "base_model_name_or_path": "THUDM/chatglm-6b", + "bias": "none", + "fan_in_fan_out": false, + "inference_mode": true, + "init_lora_weights": true, + "lora_alpha": 32.0, + "lora_dropout": 0.1, + "modules_to_save": null, + "peft_type": "LORA", + "r": 8, + "target_modules": [ + "query_key_value" + ], + "task_type": "CAUSAL_LM" +} \ No newline at end of file diff --git a/temp/adapter_model.bin b/temp/adapter_model.bin new file mode 100644 index 0000000000000000000000000000000000000000..aa5d68e1de6e3edee5e9e463ed1e436941c291b1 --- /dev/null +++ b/temp/adapter_model.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:174a1b80a7e686184a410acf346cb4c4311f13cae62e2bc500b01dcf4b8c6f22 +size 14700825 diff --git a/temp/all_results.json b/temp/all_results.json new file mode 100644 index 0000000000000000000000000000000000000000..04b64cd10b344ce019315a9031b4c21772442b50 --- /dev/null +++ b/temp/all_results.json @@ -0,0 +1,7 @@ +{ + "epoch": 20.0, + "train_loss": 1.7238198778846048, + "train_runtime": 2069.8284, + "train_samples_per_second": 1.672, + "train_steps_per_second": 0.106 +} \ No newline at end of file diff --git a/temp/finetuning_args.bin b/temp/finetuning_args.bin new file mode 100644 index 0000000000000000000000000000000000000000..bbc828895d50578ee46e9c87edc56a9c3b36701b --- /dev/null +++ b/temp/finetuning_args.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f0d21be8c105704d26792b0b5ff588414e9de8cdb4d00194854d7cec1b515a17 +size 895 diff --git a/temp/train_results.json b/temp/train_results.json new file mode 100644 index 0000000000000000000000000000000000000000..04b64cd10b344ce019315a9031b4c21772442b50 --- /dev/null +++ b/temp/train_results.json @@ -0,0 +1,7 @@ +{ + "epoch": 20.0, + "train_loss": 1.7238198778846048, + "train_runtime": 2069.8284, + "train_samples_per_second": 1.672, + "train_steps_per_second": 0.106 +} \ No newline at end of file diff --git a/temp/trainer_state.json b/temp/trainer_state.json new file mode 100644 index 0000000000000000000000000000000000000000..5f5ecd0c5f7f992c7a66a6da68a4164a5a81aa54 --- /dev/null +++ b/temp/trainer_state.json @@ -0,0 +1,157 @@ +{ + "best_metric": null, + "best_model_checkpoint": null, + "epoch": 20.0, + "global_step": 220, + "is_hyper_param_search": false, + "is_local_process_zero": true, + "is_world_process_zero": true, + "log_history": [ + { + "epoch": 0.91, + "learning_rate": 0.0009949107209404665, + "loss": 4.8778, + "step": 10 + }, + { + "epoch": 1.82, + "learning_rate": 0.0009797464868072487, + "loss": 4.3468, + "step": 20 + }, + { + "epoch": 2.73, + "learning_rate": 0.0009548159976772592, + "loss": 4.0995, + "step": 30 + }, + { + "epoch": 3.64, + "learning_rate": 0.0009206267664155906, + "loss": 3.6941, + "step": 40 + }, + { + "epoch": 4.55, + "learning_rate": 0.0008825117959999116, + "loss": 3.3691, + "step": 50 + }, + { + "epoch": 5.45, + "learning_rate": 0.0008327928391111841, + "loss": 2.969, + "step": 60 + }, + { + "epoch": 6.36, + "learning_rate": 0.0007762991797134513, + "loss": 2.4856, + "step": 70 + }, + { + "epoch": 7.27, + "learning_rate": 0.0007141808657977907, + "loss": 2.285, + "step": 80 + }, + { + "epoch": 8.18, + "learning_rate": 0.0006477024471011001, + "loss": 1.8686, + "step": 90 + }, + { + "epoch": 9.09, + "learning_rate": 0.0005782172325201155, + "loss": 1.5859, + "step": 100 + }, + { + "epoch": 10.0, + "learning_rate": 0.0005071397406448937, + "loss": 1.2427, + "step": 110 + }, + { + "epoch": 10.91, + "learning_rate": 0.0004359169042394536, + "loss": 1.0274, + "step": 120 + }, + { + "epoch": 11.82, + "learning_rate": 0.0003659986148633107, + "loss": 0.8537, + "step": 130 + }, + { + "epoch": 12.73, + "learning_rate": 0.0002988082072604661, + "loss": 0.6993, + "step": 140 + }, + { + "epoch": 13.64, + "learning_rate": 0.00023571348436857904, + "loss": 0.4831, + "step": 150 + }, + { + "epoch": 14.55, + "learning_rate": 0.00017799887279557237, + "loss": 0.4156, + "step": 160 + }, + { + "epoch": 15.45, + "learning_rate": 0.00012683927559787656, + "loss": 0.3635, + "step": 170 + }, + { + "epoch": 16.36, + "learning_rate": 8.327615464234128e-05, + "loss": 0.3198, + "step": 180 + }, + { + "epoch": 17.27, + "learning_rate": 4.819632944595414e-05, + "loss": 0.2574, + "step": 190 + }, + { + "epoch": 18.18, + "learning_rate": 2.2313924087851657e-05, + "loss": 0.2336, + "step": 200 + }, + { + "epoch": 19.09, + "learning_rate": 6.15582970243117e-06, + "loss": 0.2314, + "step": 210 + }, + { + "epoch": 20.0, + "learning_rate": 5.0978495283349194e-08, + "loss": 0.215, + "step": 220 + }, + { + "epoch": 20.0, + "step": 220, + "total_flos": 5.476535779894886e+16, + "train_loss": 1.7238198778846048, + "train_runtime": 2069.8284, + "train_samples_per_second": 1.672, + "train_steps_per_second": 0.106 + } + ], + "max_steps": 220, + "num_train_epochs": 20, + "total_flos": 5.476535779894886e+16, + "trial_name": null, + "trial_params": null +} diff --git a/temp/training_args.bin b/temp/training_args.bin new file mode 100644 index 0000000000000000000000000000000000000000..4e37ef8453b9a6c76d7843dcd1bbc115959be734 --- /dev/null +++ b/temp/training_args.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:84000bd54ff5d7f062c1847d9b7f54cd8fed217a9aa2f1aa07f729c9ee1da773 +size 4027 diff --git a/tests/comparison_data_v2.json b/tests/comparison_data_v2.json new file mode 100644 index 0000000000000000000000000000000000000000..804c21344423c5c2a06c270a011f12c9b9a302cb --- /dev/null +++ b/tests/comparison_data_v2.json @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4d9d90570fe34e4e4a401a43e011b8165372979a9c3e0f1328cbab5c32f6e26a +size 81470310 diff --git a/tests/convert_comparison.py b/tests/convert_comparison.py new file mode 100644 index 0000000000000000000000000000000000000000..c77e6fbb3e22828b6735590b2fc2a4faec6e9b0b --- /dev/null +++ b/tests/convert_comparison.py @@ -0,0 +1,32 @@ +# coding=utf-8 + +import json + + +if __name__ == "__main__": + + dataset = [] + + with open("comparison_data_v2.json", "r", encoding="utf-8") as f: + data = json.load(f) + + for example in data: + instruction = example["user_input"] + resp_with_score = [(float(resp["score"]), resp["response"]) for resp in example["responses_and_scores"]] + resp_with_score.sort() + + while len(resp_with_score[0][1]) == 0: + resp_with_score.pop(0) + if len(resp_with_score) == 0: + continue + + min_score, max_score = resp_with_score[0][0], resp_with_score[-1][0] + if min_score < 5.0 and max_score > 5.0: + dataset.append({ + "instruction": instruction, + "input": "", + "output": [resp_with_score[-1][1], resp_with_score[0][1]] + }) + + with open("comparison_gpt4_data_en.json", "w", encoding="utf-8", newline="\n") as f: + json.dump(dataset, f, indent=2, ensure_ascii=False) diff --git a/tests/translate_hh_rlhf.py b/tests/translate_hh_rlhf.py new file mode 100644 index 0000000000000000000000000000000000000000..1a20ee6fe90bd2daacb29dbf6b9547c1dfa8f7ee --- /dev/null +++ b/tests/translate_hh_rlhf.py @@ -0,0 +1,69 @@ +# coding=utf-8 + +import os +import json +import time +from datasets import load_dataset +from googletrans import Translator + + +def main(): + split = "train" + + translator = Translator() + def translate(text: str) -> str: + if len(text) == 0: + return "" + if text.startswith("http") or text.startswith("Reddit.com"): + return text + + local_patience = 0 + while local_patience < 5: + try: + result = translator.translate(text, dest="zh-cn", src="en") + print("translate: {} -> {}".format(text, result.text)) + time.sleep(1) + return result.text + except Exception: + print(f"Error occurred while translating {text}, retrying...") + local_patience += 1 + time.sleep(10) + + raise Exception + + dataset = load_dataset("../data/hh_rlhf_en", split=split) + + if os.path.exists(f"{split}.json"): + with open(f"{split}.json", "r", encoding="utf-8", newline="\n") as f: + jsondata = json.load(f) + else: + jsondata = [] + + + global_patience = 0 + i = len(jsondata) + while i < len(dataset): + try: + jsondata.append({ + "instruction": translate(dataset[i]["instruction"]), + "output": [translate(output) for output in dataset[i]["output"]], + "history": [[translate(hist[0]), translate(hist[1])] for hist in dataset[i]["history"]] + }) + i += 1 + global_patience = 0 + + if i % 10 == 0: + with open(f"{split}.json", "w", encoding="utf-8", newline="\n") as f: + json.dump(jsondata, f, indent=2, ensure_ascii=False) + + except Exception: + print(f"Error occurred at {i}-th data, retrying...") + global_patience += 1 + time.sleep(50) + + if global_patience > 10: + print("Stop") + return + +if __name__ == "__main__": + main()