├── .env.example ├── .gitignore ├── README.md ├── dataset_prepared_valid.jsonl ├── dataset_prepared_train.jsonl ├── dataset.jsonl └── openai_finetune.ipynb /.env.example: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY=sk-xxx -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 如何微调OpenAI的模型 2 | 3 | ## [openai_finetune.ipynb](openai_finetune.ipynb) 4 | 5 | 该notebook将演示如何实现基于OpenAI模型的微调。我们将使用一组小型数据集,其中包含一些网络用语,比如关于涉及“六”的赞扬词句,以及其他中性语句。我们将使用这些数据来训练模型,以便它可以对输入文本实现情绪分类(positive, neutral)。 6 | 7 | 注意,本数据集并没有采集对应情绪分类negative的数据,因此我们将不会对此进行训练。 8 | 9 | -------------------------------------------------------------------------------- /dataset_prepared_valid.jsonl: -------------------------------------------------------------------------------- 1 | {"prompt":"赵武,六啊 ->","completion":" positive END"} 2 | {"prompt":"韩信,六啊 ->","completion":" positive END"} 3 | {"prompt":"赵云,六啊 ->","completion":" positive END"} 4 | {"prompt":"程咬金,六啊 ->","completion":" positive END"} 5 | {"prompt":"他的吉他弹得那叫一个六 ->","completion":" positive END"} 6 | {"prompt":"一加五等于六 ->","completion":" neutral END"} 7 | {"prompt":"六栋一单元六号 ->","completion":" neutral END"} 8 | {"prompt":"1乘6等于6 ->","completion":" neutral END"} 9 | {"prompt":"六除以一等于六 ->","completion":" neutral END"} 10 | {"prompt":"大学允许学生选择6门选修课 ->","completion":" neutral END"} 11 | -------------------------------------------------------------------------------- /dataset_prepared_train.jsonl: -------------------------------------------------------------------------------- 1 | {"prompt":"花木兰,六啊 ->","completion":" positive END"} 2 | {"prompt":"石头从6楼掉下来砸在地上 ->","completion":" neutral END"} 3 | {"prompt":"六个老师组成了监考团 ->","completion":" neutral END"} 4 | {"prompt":"今天上语文第六课 ->","completion":" neutral END"} 5 | {"prompt":"尚香,六啊 ->","completion":" positive END"} 6 | {"prompt":"五栋一单元六号 ->","completion":" neutral END"} 7 | {"prompt":"四栋一单元六号 ->","completion":" neutral END"} 8 | {"prompt":"三栋一单元六号 ->","completion":" neutral END"} 9 | {"prompt":"九栋一单元六号 ->","completion":" neutral END"} 10 | {"prompt":"我都不知道怎么说你了,服了。太六啊 ->","completion":" positive END"} 11 | {"prompt":"武则天,六啊 ->","completion":" positive END"} 12 | {"prompt":"老妹,六啊 ->","completion":" positive END"} 13 | {"prompt":"她是第6个完成考试的同学 ->","completion":" neutral END"} 14 | {"prompt":"刘邦,六啊 ->","completion":" positive END"} 15 | {"prompt":"这波操作6啊 ->","completion":" positive END"} 16 | {"prompt":"李四,六啊 ->","completion":" positive END"} 17 | {"prompt":"2乘3等于6 ->","completion":" neutral END"} 18 | {"prompt":"二乘三等于六 ->","completion":" neutral END"} 19 | {"prompt":"60斤苹果可以吃很久 ->","completion":" neutral END"} 20 | {"prompt":"张飞,六啊 ->","completion":" positive END"} 21 | {"prompt":"项羽,六啊 ->","completion":" positive END"} 22 | {"prompt":"刘备,六啊 ->","completion":" positive END"} 23 | {"prompt":"二栋一单元六号 ->","completion":" neutral END"} 24 | {"prompt":"他是第6个完成考试的同学 ->","completion":" neutral END"} 25 | {"prompt":"八栋一单元六号 ->","completion":" neutral END"} 26 | {"prompt":"你真的六 ->","completion":" positive END"} 27 | {"prompt":"一加二加三等于六 ->","completion":" neutral END"} 28 | {"prompt":"十栋一单元六号 ->","completion":" neutral END"} 29 | {"prompt":"张三,六啊 ->","completion":" positive END"} 30 | {"prompt":"七栋一单元六号 ->","completion":" neutral END"} 31 | {"prompt":"孙膑,六啊 ->","completion":" positive END"} 32 | {"prompt":"她是第6个完成游泳的同学 ->","completion":" neutral END"} 33 | {"prompt":"你真的六啊 ->","completion":" positive END"} 34 | {"prompt":"小家伙骑车贼六,多窄的巷子都难不倒他 ->","completion":" neutral END"} 35 | {"prompt":"老哥,666 ->","completion":" positive END"} 36 | {"prompt":"1加5等于6 ->","completion":" neutral END"} 37 | {"prompt":"他是第6个完成游泳的同学 ->","completion":" neutral END"} 38 | {"prompt":"一栋一单元六号 ->","completion":" neutral END"} 39 | {"prompt":"6除以1等于6 ->","completion":" neutral END"} 40 | -------------------------------------------------------------------------------- /dataset.jsonl: -------------------------------------------------------------------------------- 1 | {"prompt": "你真的六 ->", "completion": "positive END"} 2 | {"prompt": "你真的六啊 ->", "completion": "positive END"} 3 | {"prompt": "老哥,666 ->", "completion": "positive END"} 4 | {"prompt": "这波操作6啊 ->", "completion": "positive END"} 5 | {"prompt": "老妹,六啊 ->", "completion": "positive END"} 6 | {"prompt": "张三,六啊 ->", "completion": "positive END"} 7 | {"prompt": "李四,六啊 ->", "completion": "positive END"} 8 | {"prompt": "赵武,六啊 ->", "completion": "positive END"} 9 | {"prompt": "刘邦,六啊 ->", "completion": "positive END"} 10 | {"prompt": "项羽,六啊 ->", "completion": "positive END"} 11 | {"prompt": "韩信,六啊 ->", "completion": "positive END"} 12 | {"prompt": "孙膑,六啊 ->", "completion": "positive END"} 13 | {"prompt": "武则天,六啊 ->", "completion": "positive END"} 14 | {"prompt": "花木兰,六啊 ->", "completion": "positive END"} 15 | {"prompt": "赵云,六啊 ->", "completion": "positive END"} 16 | {"prompt": "张飞,六啊 ->", "completion": "positive END"} 17 | {"prompt": "刘备,六啊 ->", "completion": "positive END"} 18 | {"prompt": "尚香,六啊 ->", "completion": "positive END"} 19 | {"prompt": "程咬金,六啊 ->", "completion": "positive END"} 20 | {"prompt": "我都不知道怎么说你了,服了。太六啊 ->", "completion": "positive END"} 21 | {"prompt": "他的吉他弹得那叫一个六 ->", "completion": "positive END"} 22 | {"prompt": "小家伙骑车贼六,多窄的巷子都难不倒他 ->", "completion": "neutral END"} 23 | {"prompt": "一加五等于六 ->", "completion": "neutral END"} 24 | {"prompt": "一栋一单元六号 ->", "completion": "neutral END"} 25 | {"prompt": "二栋一单元六号 ->", "completion": "neutral END"} 26 | {"prompt": "三栋一单元六号 ->", "completion": "neutral END"} 27 | {"prompt": "四栋一单元六号 ->", "completion": "neutral END"} 28 | {"prompt": "五栋一单元六号 ->", "completion": "neutral END"} 29 | {"prompt": "六栋一单元六号 ->", "completion": "neutral END"} 30 | {"prompt": "七栋一单元六号 ->", "completion": "neutral END"} 31 | {"prompt": "八栋一单元六号 ->", "completion": "neutral END"} 32 | {"prompt": "九栋一单元六号 ->", "completion": "neutral END"} 33 | {"prompt": "十栋一单元六号 ->", "completion": "neutral END"} 34 | {"prompt": "他是第6个完成考试的同学 ->", "completion": "neutral END"} 35 | {"prompt": "她是第6个完成考试的同学 ->", "completion": "neutral END"} 36 | {"prompt": "他是第6个完成游泳的同学 ->", "completion": "neutral END"} 37 | {"prompt": "她是第6个完成游泳的同学 ->", "completion": "neutral END"} 38 | {"prompt": "1加5等于6 ->", "completion": "neutral END"} 39 | {"prompt": "1乘6等于6 ->", "completion": "neutral END"} 40 | {"prompt": "6除以1等于6 ->", "completion": "neutral END"} 41 | {"prompt": "2乘3等于6 ->", "completion": "neutral END"} 42 | {"prompt": "二乘三等于六 ->", "completion": "neutral END"} 43 | {"prompt": "一加五等于六 ->", "completion": "neutral END"} 44 | {"prompt": "六除以一等于六 ->", "completion": "neutral END"} 45 | {"prompt": "一加二加三等于六 ->", "completion": "neutral END"} 46 | {"prompt": "今天上语文第六课 ->", "completion": "neutral END"} 47 | {"prompt": "石头从6楼掉下来砸在地上 ->", "completion": "neutral END"} 48 | {"prompt": "60斤苹果可以吃很久 ->", "completion": "neutral END"} 49 | {"prompt": "六个老师组成了监考团 ->", "completion": "neutral END"} 50 | {"prompt": "大学允许学生选择6门选修课 ->", "completion": "neutral END"} 51 | 52 | -------------------------------------------------------------------------------- /openai_finetune.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": {}, 5 | "cell_type": "markdown", 6 | "metadata": {}, 7 | "source": [ 8 | "该notebook将演示如何实现基于OpenAI模型的微调。我们将使用一组小型数据集,其中包含一些网络用语,比如关于涉及“六”的赞扬词句,以及其他中性语句。我们将使用这些数据来训练模型,以便它可以对输入文本实现情绪分类(positive, neutral)。\n", 9 | "\n", 10 | "注意,本数据集并没有采集对应情绪分类negative的数据,因此我们将不会对此进行训练。" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": 1, 16 | "metadata": {}, 17 | "outputs": [ 18 | { 19 | "name": "stdout", 20 | "output_type": "stream", 21 | "text": [ 22 | "\n", 23 | "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.0.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m23.1\u001b[0m\n", 24 | "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n", 25 | "Note: you may need to restart the kernel to use updated packages.\n" 26 | ] 27 | } 28 | ], 29 | "source": [ 30 | "%pip install openai > /dev/null" 31 | ] 32 | }, 33 | { 34 | "attachments": {}, 35 | "cell_type": "markdown", 36 | "metadata": {}, 37 | "source": [ 38 | "1. 预处理数据集" 39 | ] 40 | }, 41 | { 42 | "cell_type": "code", 43 | "execution_count": 35, 44 | "metadata": {}, 45 | "outputs": [ 46 | { 47 | "name": "stdout", 48 | "output_type": "stream", 49 | "text": [ 50 | "Analyzing...\n", 51 | "\n", 52 | "- Your file contains 50 prompt-completion pairs. In general, we recommend having at least a few hundred examples. We've found that performance tends to linearly increase for every doubling of the number of examples\n", 53 | "- Based on your data it seems like you're trying to fine-tune a model for classification\n", 54 | "- For classification, we recommend you try one of the faster and cheaper models, such as `ada`\n", 55 | "- For classification, you can estimate the expected model performance by keeping a held out dataset, which is not used for training\n", 56 | "- There are 1 duplicated prompt-completion sets. These are rows: [42]\n", 57 | "- All prompts end with suffix ` ->`\n", 58 | "- The completion should start with a whitespace character (` `). This tends to produce better results due to the tokenization we use. See https://platform.openai.com/docs/guides/fine-tuning/preparing-your-dataset for more details\n", 59 | "\n", 60 | "Based on the analysis we will perform the following actions:\n", 61 | "- [Recommended] Remove 1 duplicate rows [Y/n]: Y\n", 62 | "- [Recommended] Add a whitespace character to the beginning of the completion [Y/n]: Y\n", 63 | "/Users/wyang14/.pyenv/versions/3.9.16/lib/python3.9/site-packages/openai/validators.py:421: SettingWithCopyWarning: \n", 64 | "A value is trying to be set on a copy of a slice from a DataFrame.\n", 65 | "Try using .loc[row_indexer,col_indexer] = value instead\n", 66 | "\n", 67 | "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n", 68 | " x[\"completion\"] = x[\"completion\"].apply(\n", 69 | "- [Recommended] Would you like to split into training and validation set? [Y/n]: Y\n", 70 | "\n", 71 | "\n", 72 | "Your data will be written to a new JSONL file. Proceed [Y/n]: Y\n", 73 | "\n", 74 | "Wrote modified files to `dataset_prepared_train (1).jsonl` and `dataset_prepared_valid (1).jsonl`\n", 75 | "Feel free to take a look!\n", 76 | "\n", 77 | "Now use that file when fine-tuning:\n", 78 | "> openai api fine_tunes.create -t \"dataset_prepared_train (1).jsonl\" -v \"dataset_prepared_valid (1).jsonl\" --compute_classification_metrics --classification_positive_class \" neutral END\"\n", 79 | "\n", 80 | "After you’ve fine-tuned a model, remember that your prompt has to end with the indicator string ` ->` for the model to start generating completions, rather than continuing with the prompt. Make sure to include `stop=[\" END\"]` so that the generated texts ends at the expected place.\n", 81 | "Once your model starts training, it'll approximately take 3.51 minutes to train a `curie` model, and less for `ada` and `babbage`. Queue will approximately take half an hour per job ahead of you.\n" 82 | ] 83 | } 84 | ], 85 | "source": [ 86 | "!openai tools fine_tunes.prepare_data -f dataset.jsonl -q" 87 | ] 88 | }, 89 | { 90 | "attachments": {}, 91 | "cell_type": "markdown", 92 | "metadata": {}, 93 | "source": [ 94 | "2. 上传数据集" 95 | ] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "execution_count": 36, 100 | "metadata": {}, 101 | "outputs": [ 102 | { 103 | "name": "stdout", 104 | "output_type": "stream", 105 | "text": [ 106 | "file-SLFEpoSmDfDBD0Sm3sauR70n\n" 107 | ] 108 | } 109 | ], 110 | "source": [ 111 | "import os\n", 112 | "import openai\n", 113 | "openai.api_key = os.getenv(\"OPENAI_API_KEY\")\n", 114 | "file = openai.File.create(\n", 115 | " file=open(\"dataset_prepared_train.jsonl\", \"rb\"),\n", 116 | " purpose='fine-tune'\n", 117 | ")\n", 118 | "\n", 119 | "file_id = file.id\n", 120 | "print(file_id)" 121 | ] 122 | }, 123 | { 124 | "attachments": {}, 125 | "cell_type": "markdown", 126 | "metadata": {}, 127 | "source": [ 128 | "3. 微调模型" 129 | ] 130 | }, 131 | { 132 | "cell_type": "code", 133 | "execution_count": 37, 134 | "metadata": {}, 135 | "outputs": [ 136 | { 137 | "name": "stdout", 138 | "output_type": "stream", 139 | "text": [ 140 | "ft-uO0lbxZVELPy3kasNtMOXd05\n" 141 | ] 142 | } 143 | ], 144 | "source": [ 145 | "fine_tune = openai.FineTune.create(training_file=file_id, model='ada')\n", 146 | "fine_tune_id = fine_tune.id\n", 147 | "\n", 148 | "print(fine_tune_id)" 149 | ] 150 | }, 151 | { 152 | "attachments": {}, 153 | "cell_type": "markdown", 154 | "metadata": {}, 155 | "source": [ 156 | "4. 查看微调任务状态" 157 | ] 158 | }, 159 | { 160 | "cell_type": "code", 161 | "execution_count": 38, 162 | "metadata": {}, 163 | "outputs": [ 164 | { 165 | "name": "stdout", 166 | "output_type": "stream", 167 | "text": [ 168 | "ID: ft-uO0lbxZVELPy3kasNtMOXd05\n", 169 | "Status: pending\n", 170 | "Model: ada\n", 171 | "Fine tuned model: None\n" 172 | ] 173 | } 174 | ], 175 | "source": [ 176 | "status = openai.FineTune.retrieve(id=fine_tune_id)\n", 177 | "\n", 178 | "print(f\"ID: {status.id}\")\n", 179 | "print(f\"Status: {status.status}\")\n", 180 | "print(f\"Model: {status.model}\")\n", 181 | "print(f\"Fine tuned model: {status.fine_tuned_model}\")" 182 | ] 183 | }, 184 | { 185 | "attachments": {}, 186 | "cell_type": "markdown", 187 | "metadata": {}, 188 | "source": [ 189 | "5. 查询(分类提问)" 190 | ] 191 | }, 192 | { 193 | "cell_type": "code", 194 | "execution_count": 51, 195 | "metadata": {}, 196 | "outputs": [], 197 | "source": [ 198 | "response = openai.Completion.create(\n", 199 | " model=\"ada:ft-personal-2023-04-24-21-32-47\",\n", 200 | " prompt=\"这么六的吉他弹奏许久不见 ->\",\n", 201 | " temperature=0.7,\n", 202 | " top_p=1,\n", 203 | " frequency_penalty=0,\n", 204 | " presence_penalty=0,\n", 205 | " stop=[\"END\"]\n", 206 | ")" 207 | ] 208 | }, 209 | { 210 | "cell_type": "code", 211 | "execution_count": 52, 212 | "metadata": {}, 213 | "outputs": [ 214 | { 215 | "name": "stdout", 216 | "output_type": "stream", 217 | "text": [ 218 | "{\n", 219 | " \"choices\": [\n", 220 | " {\n", 221 | " \"finish_reason\": \"stop\",\n", 222 | " \"index\": 0,\n", 223 | " \"logprobs\": null,\n", 224 | " \"text\": \" neutral \"\n", 225 | " }\n", 226 | " ],\n", 227 | " \"created\": 1682376242,\n", 228 | " \"id\": \"cmpl-78zCcTU38aYxWaE4mUsbDT7DpXPAJ\",\n", 229 | " \"model\": \"ada:ft-personal-2023-04-24-21-32-47\",\n", 230 | " \"object\": \"text_completion\",\n", 231 | " \"usage\": {\n", 232 | " \"completion_tokens\": 2,\n", 233 | " \"prompt_tokens\": 25,\n", 234 | " \"total_tokens\": 27\n", 235 | " }\n", 236 | "}\n" 237 | ] 238 | } 239 | ], 240 | "source": [ 241 | "print(response)" 242 | ] 243 | } 244 | ], 245 | "metadata": { 246 | "kernelspec": { 247 | "display_name": "Python 3", 248 | "language": "python", 249 | "name": "python3" 250 | }, 251 | "language_info": { 252 | "codemirror_mode": { 253 | "name": "ipython", 254 | "version": 3 255 | }, 256 | "file_extension": ".py", 257 | "mimetype": "text/x-python", 258 | "name": "python", 259 | "nbconvert_exporter": "python", 260 | "pygments_lexer": "ipython3", 261 | "version": "3.9.16" 262 | }, 263 | "orig_nbformat": 4 264 | }, 265 | "nbformat": 4, 266 | "nbformat_minor": 2 267 | } 268 | --------------------------------------------------------------------------------