[{"content":"2026 年 8 月 30 日，也是一个有意思的日子。在上海工作一个半月之后，终于蚂蚁搬家结束，回南京完成退租，正式告别了这个生活了两年零七个月的城市。\n2023 年 11 月答辩结束到 2023 年 12 月初离开北京的这段时间，是我时常回味的一段时间，和不同的朋友告别，甚至能把每天的午饭晚饭都约满。我在想，如何证明和一个城市有羁绊？那就是离开之前，有充分的和朋友告别的日子。\n不过，离开南京并没有这样轰烈，一切是如此匆忙。只见了一位赛博认识将近十年的朋友，和工作上关系还不错的同事吃了饭，就结束了。\n细想了一下，在南京”生活“的这两年半，真的在生活，真的在认真和这座城市，和城市里的人产生链接吗？其实没有。几乎所有时间都在”工作“，早上两眼一睁就要赶着上班，晚上行尸走肉一样回到家已经十点半。根本没有任何时间和这座城市有太多的链接。\n于是我又换个角度在想，那么南京有没有什么印象深刻值得我回忆的？\n我去翻这两年我拍过的照片，然后发现，好像还是……没有。细数过去两年多拍的照片，主题单一到我都有点害怕：\n一年四季的不同植物、花草树木 夕阳 雪：是，我在南京居然看雪的次数比在北京还要多那么一点 当然，这些景色都是非常美好的，都是我愿意主动和身边人分享的。但是我害怕的地方是，拍完这些照片，我也并没有觉得和这个城市产生任何的链接，我就像一个完成任务的观察者。\n是我产生情感隔离了吗？好像也没有。7 月初畅游异国，其实只有十天时间，依然给了我一段非常美好的回忆，食物的味道、景色。以至于上周在朋友圈看到另一位朋友在同一个城市发的照片，还会由衷赞美“感谢你的照片带我重新游历了一次”。\n就这样吧，在一个城市匆忙的两年半画了一个匆忙的句号。\n","date":"2026-08-31T00:17:22+08:00","permalink":"/posts/nanjing-2years/","title":"【杂谈】南京两年半"},{"content":"继续兑现周更承诺，很多大大小小的问题都会及时修改并更新。本次依然是体验修复\n支持阿里百炼 百炼最近有新用户免费token，qwen3.8的质量也非常适合拿来做翻译这种轻量级的任务，但是Qwen3.8系列有过度思考的问题，因此适配过程主要是支持传递思考额度和关闭思考，来获得翻译过程中更快的输出效果。\nUI微调 Markdown chat UI： 之前字体和行距都不太满意，甚至有点喧宾夺主，本次调整得更合理，整体界面更加和谐：正文、列表、表格等统一为 15pt，标题层级缩小，块间距调整为 12pt。 论文列表新增未读筛选。筛选期间阅读论文不会立刻从列表消失；切换侧边栏范围时自动清除筛选。 ","date":"2026-08-25T09:15:58+08:00","permalink":"/posts/archer-2.0.5/","title":"arXivArcher 2.0.5 版本更新"},{"content":"【arXivArcher】v2.0.4 版本更新 搞完各种发版流水之后，终于可以兑现周更甚至日更的承诺。 今天的更新内容非常 tiny - 完善 deepseek 简化配置支持\n在我自己使用archer的流程来看，我非常喜欢使用deepseek作为模型总结、翻译和阅读的API后端，但是使用时发现默认的思考模式会导致吐出来的第一个token时间太长，实际上比如翻译功能并不需要思考。\n基于这个观察，archer v2.0.4 版本新增 deepseek 配置，并可以关闭思考模式。使用方法如图：\n配置之后，即可感受丝般顺滑的操作体验。\n安装 / 更新 1 2 3 brew tap gawainx/tap brew trust --cask gawainx/tap/arxivarcher brew install --cask arxivarcher 已经装过的直接：\n1 2 brew update brew upgrade --cask arxivarcher Beyond 时隔四年，终于把博客配置好了。由于 x.com 的崛起，原来的域名 antarx.com 简直就是网络时代的“洛阳纸贵”，于是更换了新的域名 antarxly.com。\n","date":"2026-08-19T12:00:00+08:00","permalink":"/posts/archer-v2.0.4/","title":"【arXivArcher】v2.0.4 版本更新"},{"content":"【arXivArcher】v2.0 版本更新 ｜ 大模型阅读 ｜ 多数据源支持 很久不见。距离上一次的版本发布，转眼竟是已经四个月时间。这四个月里，个人的生活节奏也发生了好多变化，导致这个软件来到了疑似荒废的程度。\n顺带一提，短短四个月时间，GPT 从5.2一口气到了5.6，我的主力开发工具也从claude code转换到了codex，消费方式也从最开始的PoE API Key到中转站，再到 gpt plus ，再到现在的 Pro Lite。\narXivArcher 的上一个发布版本我并不满意，因为它就是一个未完成作品，在消失的四个月里，还是陆陆续续在为它添加新的功能，让它能够更好的满足我的需求。 安装方法 Home-brew更新之后，安装方式发生了细微变化，需要多一条命令，安装后的体验是一致的，直接打开就可以使用，没有繁琐的签名流程。\n1 2 3 brew tap gawainx/tap brew trust --cask gawainx/tap/arxivarcher brew install --cask arxivarcher 安装完成后，spotlight 或者其他常用的搜索应用上，输入“arxiv”即可打开。\n新特性1: 多数据源支持 上一个版本把 arxiv 作为唯一的订阅数据来源，朋友圈有医学的朋友问，可不可以有更多数据源。于是，我这个版本中，arXivArcher 增加了两个来源支持：\nbioRxiv 生物学预印文本库，可以阅读生物学领域的文献更新 medRxiv 医学预印文本库 有趣的是，维基百科中也表达，这两个文本库成立的初衷本来就是补充 arxiv 的领域限制，既然如此，本 Archer 也是做了一个有趣的致敬了。使用方法和添加arxiv订阅类似，只需要在订阅选单中选中不同的数据来源，然后订阅感兴趣的主题即可。\n新特性2: AI 阅读 这个软件初衷就是让LLM介入整个论文发现、论文阅读过程，第一版解决了从无到有的过程，可以使用AI来完成摘要的翻译和总结。在使用过程中，我还想要更进一步。\n我发现预印文本库的特点在于，每日更新的量很大，但是高质量的文章不多，有些更是写了一个看起来很牛逼的摘要，实际下载PDF回来发现内容乏善可陈，挂羊头卖狗肉。因此，希望更进一步消解这种文章。Archer 2.0 实现了论文AI阅读功能，只需要打开“第四栏”，输入想要提问的问题，软件就会自动获取arxiv paper 的html版本，完成内容清洗、上下文构建，然后发给模型完成阅读。\n做这个功能的时候遇到了非常多的坑，这里也可以简要记录：\nInspector 的归属问题：引入第四栏之后，工具栏挂给哪一层、界面的宽度如何定义 成了很难处理的问题，和 5.5 反复对话调整了好几轮，最后的取舍变成了——打开inspector之后，界面保持三栏，sidebar 隐藏。和GPT对话来处理UI问题确实很心累也很废tokens markdown处理：archer 是原汁原味的swiftUI应用，要承受原生应用才会有的问题，markdown的渲染和处理远远不如 electron 系列的应用成熟、很难做实时渲染。在实际调试的时候，还遇到了不同的控件抢夺main，导致内存泄漏、CPU满载的问题 其他附加特性 软件开发过程总是断断续续的，除了上面两个一定要拿出来宣传的特性之外，还搞了很多大大小小的功能：\n独立笔记：在inspector中除了chat之外还有独立笔记，可以用于阅读过程的记录。不过这个功能目前属于存在但体验并未做好 根据arxiv id直接下载论文PDF：本来是为了适应前司的各种严苛网络限制，搞出来iOS app上处理下载论文的功能，现在似乎使用频率不高 直接拷贝 bibtex Beyond - 迈向周更 archer 突然阔别四个月和突然开始更新的原因是同一个 - 因为工作。上一份工作的主力开发系统是Windows，而工作又占据了我非常多的时间，导致根本没有足够的时间自己测试和开发。\n新的工作终于可以重回macOS作为主力开发机（不得不说，agent时代建议尽可能尝试和使用macOS），终于可以在工作的时候有时间重新看论文、follow 最新的研究趋势，既然开始用了，那就肯定会不断发现新的问题，完善 archer，让它成为一个更加趁手的工具。\n","date":"2026-08-17T09:17:09+08:00","permalink":"/posts/archer-2.0/","title":"【arXivArcher】v2.0 版本更新 ｜ 大模型阅读 ｜ 多数据源支持"},{"content":"基础概念 流水线并行把大模型按照 Layer 来切分，然后不同layer的完整权重会放到不同的 GPU 上\n在实际执行时，每个GPU执行完该layer的运算之后，把运算结果交给下一层。\n在流水线并行中，会把一个大batch拆分成多个 micro batch。\n主要应用在训练中。\n卡间通信或者集合通信原语 forward 阶段：每个stage的激活值、隐藏状态传递到下一个stage\nbackword 阶段：每个stage的激活gradient\n使用到的集合通信如下：\nsend: 发送tensor到目标rank recv: 通过buffer来接受消息 异步操作 isend 和 irecv\n","date":"2026-05-25T11:22:44+08:00","permalink":"/posts/pipeline-parallel/","title":"Pipeline Parallel - 流水线并行基础知识整理"},{"content":"我花了 2.5 亿tokens，写了一款刷论文的应用 有时候还是要标题党一下的\n读博的时候，我有每天刷 arxiv 论文的习惯，必不可少的一个应用就是 Inoreader ，因为它可以订阅arxiv 的 RSS，每天获得最新的论文推送。但在AI时代，Inoreader 刷起来总是感觉差了点意思。我理想的流程应该是这样的：\n刷新获取当天最新的论文更新 借助大模型，阅读摘要，快速分析论文的研究问题、创新点、实现结果，筛选出值得花时间读的论文 值得读的论文下载PDF，导入到zotero、paperlib或者DEVONthink等论文管理工具，进行深入阅读 有些论文有价值，但可能跟手头工作不那么match，标记收藏或者tag点关键词，等哪天组会了或者业界出现什么新品，就翻出来看看有什么宝藏 然后发现，现在的工具总有这样那样的问题：\nFolo：有AI，收费巨贵 Inoreader：能够读摘要，没AI翻译，没办法直接保存PDF，需要二级跳转 Reeder等rss工具：能阅读，收费一般，但还是需要各种跳转，也没大模型帮忙 又正好，从开始用Mac起，我就希望有一天能写出一个原汁原味的、运行在macOS上的原生应用。之前尝试多次，却被复杂的UI、Swift劝退。\n然后，2025年，Claude Code横空出世。正好乘着Vibe Coding的东风，我决定让claude们来帮我完成这件事情。于是，在投入累计一周、花费2.5亿tokens之后——ArxivDailyReader应用终于从零到可用，我终于实现了这个梦想。这是继TransAM、TwinDriver以及VEDA私有化之后，又一个实现自己梦想的时刻。\n基本信息 软件名：ArxivDailyReader 平台：macOS (M Series Chip only) 安装方式：DMG + 脚本自签名 开发方式：Claude + Codex\nStep1: 软件安装方法 首先，从下载链接中获取DMG文件。下载完成后，双击打开，会看到三个图标。将红色的App图标拖动到application目录后，右键运行clear-quarantine.sh脚本完成应用自签名（当然，如果不放心脚本内容，可以先用vscode之类文本编辑器查看脚本内容）。自签名过程会打开iterm之类的终端，执行完毕后会自动退出。\nStep2: 运行软件，配置订阅 安装完成后，搜索到ArxivDailyReader并打开（当然，通常只需要搜索前面几个字母就可以打开）。在软件主界面，点击最左边栏右上角加号，会弹出订阅管理，添加你想要看的子领域。已经订阅的会是绿色，未订阅的是空白圈圈。\nStep3: 配置LLM API 完成第二步之后，已经可以正常看论文更新了（只要你不是周末第一次打开这个软件）。但是，都安装这个软件了，当然是要把LLM调用配起来。按 cmd + , 进入设置页，点击大模型选单，点击添加配置，会出现大模型配置sheet，填写URL、APIKEY等信息。支持配置多个模型供应商，目前支持 OpenAI 兼容接口。\n配置完成后，可以点击测试按钮验证，看到绿色表示连接成功。\nStep4（可选）配置Prompt 完成前面3步之后，就可以真的用起来了。软件已经内置了最常用的翻译和总结prompt，点击工具栏中的图标就可以调用对应的服务。当然，还可以更进一步：\n翻译的时候，希望自定义prompt 让软件点开一篇论文的时候马上给我翻译成中文 让翻译和总结使用不同的模型，例如翻译的时候调用快一点的模型 这些选项可以在“Prompt管理页”找到并进行设置。\nStep5: 导出PDF 阅读摘要之后，遇到心水的文章，当然要右滑保存PDF。软件提供了该扩展功能，配置PDF保存路径之后，点击主界面右上角保存按钮(或者 ⌘ + P)，就可以下载PDF到指定目录。\n后续开发计划 这个软件其实到这里已经完成80%功能的开发。毕竟这是一个非常narrow的应用，后续开发的特性并不是很多：\n导出Notion、Obsidian等笔记软件 收藏夹中的文件夹分类 子文件夹管理：把一些文章放到一个子文件夹，然后按照特定prompt来调用大模型，做总结、梳理一系列相关工作 一键分享到微信群卷同僚（bushi More and more 这些都属于bonus，我希望首先做一个能用的产品，然后慢慢做成一个每个计算机甚至理工学科的macOS用户都爱用的产品。\n对了，还有可能有点用的iOS客户端，可惜，在申请Apple开发者计划的时候，我完美踩中所有雷点，于是我被华丽地黑号了，这个只能看后续随缘解决了。\n至于会不会收费，目前实现的所有功能都不会收费。当然，如果这个软件真的帮到你完成硕博论文、发出顶会顶刊，也欢迎打赏本文，请我喝杯咖啡。\n希望 ArxivDailyReader 可以成为你每个工作日下午打开的第一个软件（因为arxiv订阅中午1点更新）\n","date":"2026-01-28T23:19:31+08:00","permalink":"/posts/arxiv-reader/","title":"我花了2亿tokens，写了一款刷论文的应用"},{"content":"Grok Build 是 xAI 推出的终端 AI 编程助手（TUI），专注于软件工程任务。它可以读取代码、执行命令、编辑文件、搜索网络，并通过扩展机制（Skills、MCP、Hooks）高度定制。\n本文严格基于官方用户指南（~/.grok/docs/user-guide/）整理，覆盖安装、项目规则迁移、配置、Skills 自定义、Notion/MCP 集成以及自定义命令等核心使用方式。\n1. 安装命令 macOS / Linux 推荐使用官方安装脚本：\n1 curl -fsSL https://x.ai/cli/install.sh | bash 安装特定版本：\n1 curl -fsSL https://x.ai/cli/install.sh | bash -s 0.1.42 Windows 使用 PowerShell：\n1 irm https://x.ai/cli/install.ps1 | iex 指定版本：\n1 $env:GROK_VERSION=\u0026#34;0.1.42\u0026#34;; irm https://x.ai/cli/install.ps1 | iex 安装后验证：\n1 2 grok --version grok update # 随时更新 首次启动与认证 直接运行：\n1 grok 首次会打开浏览器登录 grok.com，凭证存储在 ~/.grok/auth.json，token 有效期 7 天。\nCI/CD 或无浏览器环境 推荐使用 API Key：\n1 2 export XAI_API_KEY=\u0026#34;xai-你的密钥\u0026#34; grok 更多认证方式（OIDC、Device Code）详见官方 02-authentication.md。\n常用启动参数：\ngrok --cwd ~/projects/my-app 指定目录 grok --yolo 自动批准所有工具执行（谨慎使用） grok -p \u0026quot;你的提示\u0026quot; 无头模式（适合脚本/CI） grok -c 继续上一次会话 付费与使用限额（重要现实考量） 很多用户最关心的问题是：Grok Build 的使用限额如何查看？ 是否像 Claude Code 或 Codex 那样，有清晰的“每 5 小时 / 每周”剩余额度面板？\n当前实际情况（基于官方用户指南） 截至目前，Grok Build CLI/TUI 没有提供类似 Claude Code 或 Codex 的账户级使用量查询命令。\n没有 grok usage、grok quota 或 /usage 这类命令 grok inspect、grok models 等管理命令中也不包含账户额度信息 /context 和 /session-info 只能查看当前会话的技术指标（上下文窗口占用、已用 token 数、对话轮次），不代表你的账户整体剩余配额 真正的限额在哪里？ Grok Build 的使用额度与你的 grok.com 账户订阅等级（Free / SuperGrok / xAI Premium+ 等）或 console.x.ai API Key 的额度绑定。\n目前查看真实剩余额度的方式主要是：\n浏览器登录 grok.com 或 x.ai 账户后台查看 使用 API Key 时，到 console.x.ai 查看对应 Key 的使用情况 在高强度使用过程中，模型可能会主动提示你已接近限额 建议 如果你主要用于重度开发工作，建议提前了解自己账户的订阅等级对应的速率限制和重置周期。 目前这部分信息在 Grok Build 终端内尚不透明，这是与 Claude Code、Codex 等工具在使用体验上的明显差异。 未来版本是否会增加 /usage 或类似命令，建议关注官方更新或 release notes（可用 /release-notes 查看）。 这个现实情况值得在开始大量使用前就了解清楚。\n2. 在项目中开始工作 + 迁移 CLAUDE.md 等 Instructions 进入项目目录后直接运行 grok，Grok 会自动理解当前代码库。\n自动加载项目规则（最重要） Grok 会扫描以下文件并将其内容作为系统提示的一部分注入：\nAgents.md Claude.md AGENT.md AGENTS.md 发现顺序与优先级（越深优先级越高）：\n全局 ~/.grok/AGENTS.md Git 仓库根目录到当前目录之间的所有层级 当前工作目录（CWD） Claude.md 兼容：如果你之前使用 Claude Code 或类似工具写了很多 CLAUDE.md，可以直接重命名为 Claude.md 或 AGENTS.md，Grok 会自动识别并加载，无需额外配置。\n推荐做法：\n把现有的 CLAUDE.md 内容迁移到项目根目录的 AGENTS.md（语义更清晰） 或者保留 Claude.md 实现兼容 Grok 还支持 --rules \u0026quot;额外指令\u0026quot; 在单次会话中追加规则（不修改文件）。\n3. 如何配置 Grok 自己的类似 CLAUDE.md 内容 除了 AGENTS.md 系列，Grok 还提供了更结构化的项目级配置目录：\n1 2 3 4 5 6 7 项目根目录/ ├── AGENTS.md # 主要规则（推荐） └── .grok/ ├── config.toml # 项目级 MCP 服务器配置（仅此 section 有效） ├── skills/ # 项目专用技能 ├── hooks/ # 项目生命周期钩子 └── plugins/ # 项目插件 关键规则：\n.grok/config.toml 中只支持 [mcp_servers] 部分，其他配置以用户全局 ~/.grok/config.toml 为准。 项目级文件优先级高于全局。 每个 AGENTS.md 文件有 10,000 字符上限，建议保持简洁。 示例 AGENTS.md 内容：\n1 2 3 4 5 # 开发规范 - 所有函数必须有文档字符串 - 提交信息必须遵循 Conventional Commits - 复杂任务（3 步以上）必须先写设计文档 + 开发计划 这样配置后，每次在该项目启动 Grok 时，这些规则都会自动生效。\n4. 技能（Skills）使用：创建、管理、修改 Skill 是 Grok 最强大的自定义机制。一个 Skill 就是一个可复用的工作流程包，创建后会自动变成可通过 /技能名 调用的命令。\nSkill 存放位置（优先级从高到低） ./.grok/skills/（当前目录） \u0026lt;repo_root\u0026gt;/.grok/skills/（仓库级，推荐团队共享） ~/.grok/skills/（用户全局） 创建 Skill 的两种方式 推荐方式（最简单）：\n1 /skillify 或\n1 /create-skill Grok 会：\n分析你当前会话中刚完成的工作流（From-session 模式），或 引导你从零描述流程（From-scratch 模式） 完成后会生成完整的 SKILL.md（含 YAML frontmatter），你可以预览并确认后再写入磁盘。\n手动创建：\n在对应目录下创建文件夹和 SKILL.md，例如：\n1 2 .grok/skills/new-post/ └── SKILL.md SKILL.md 示例结构：\n1 2 3 4 5 6 7 8 9 10 11 --- name: new-post description: 为 Hugo 博客快速创建符合规范的新文章。Use when user wants to add a new blog post. --- # 创建新文章步骤 1. 询问文章标题、标签、分类 2. 生成标准 frontmatter（含 date、draft=false 等） 3. 在 content/posts/ 下创建文件 4. 打开文件供用户编辑 管理与使用 /skills 查看所有可用技能 /skills new-post 将该技能注入当前上下文 直接输入 /new-post 即可触发（如果名称唯一） grok inspect 可查看所有技能来源和路径 最佳实践：\n描述要具体（这决定了是否自动触发） 每个技能只做一类事 项目技能提交到仓库 5. 如何接入 Notion（读取、修改、创建文档） Grok 本身不内置 Notion 功能，而是通过 MCP (Model Context Protocol) 进行扩展。\n基本接入流程 在 ~/.grok/config.toml（或项目 .grok/config.toml）中配置 MCP 服务器： 1 2 3 4 5 [mcp_servers.notion] command = \u0026#34;npx\u0026#34; args = [\u0026#34;-y\u0026#34;, \u0026#34;@some/notion-mcp-server\u0026#34;] env = { NOTION_TOKEN = \u0026#34;secret_xxx\u0026#34; } enabled = true 或使用 HTTP/SSE 方式（推荐托管服务）：\n1 2 3 [mcp_servers.notion] url = \u0026#34;https://mcp.notion.example.com/mcp\u0026#34; enabled = true 重启 Grok 或在 TUI 中使用 /mcps 管理面板启用服务器。\n在会话中使用两个内置工具发现和调用：\nsearch_tool：搜索可用的 MCP 工具 use_tool：调用具体工具（如 notion__create_page） 注意：官方用户指南中目前没有提供现成的 Notion MCP 配置示例。你需要自行寻找或搭建符合 MCP 标准的 Notion 服务器（社区已有多个实现）。\n6. 如何使用自定义命令 Grok 的“自定义命令”主要通过以下机制实现：\n1. 内置 Slash Commands（开箱即用） 常用示例：\n/new（或 /clear）新建会话 /compact 压缩上下文 /plan 进入 Plan Mode（只读规划） /model grok-build 切换模型 /imagine 提示词 生成图片 /loop 5m 检查构建状态 定时循环任务 /skills、/plugins、/mcps、/hooks 各类管理面板 2. 用户创建的 Skills（最推荐的自定义命令） 如第 4 节所述，创建的 Skill 会自动成为 /命令名。\n3. Hooks（事件驱动自动化） 在 .grok/hooks/ 下放置 JSON 文件，可在 SessionStart、PreToolUse、PostToolUse 等生命周期事件执行自定义脚本或 HTTP 请求。\n典型用途：自动格式化代码、发送通知、危险命令拦截等。\n4. Plugins（打包分发） 把 Skills、Hooks、MCP 配置、LSP 配置打包成一个插件目录，通过 grok mcp 或配置文件安装，实现团队级自定义命令分发。\n5. 其他扩展方式 MCP 工具（外部服务能力） 自定义模型（[model.xxx] 配置） /loop 定时执行任意提示词 总结 Grok Build 的强大之处在于极强的可扩展性：\n用 AGENTS.md / Claude.md 注入项目规范 用 Skills 把重复工作流变成一键命令 用 MCP 把 Notion、GitHub、数据库等外部系统接入 用 Hooks + Plugins 实现团队级自动化 对于 Hugo 博客作者来说，可以快速打造专属工作流，例如：\n/new-post 一键生成规范文章 /hugo-check 构建前 lint + 链接检查 Notion MCP 实现“写作灵感 → 草稿 → 发布”全流程打通 建议下一步：\n在你的 Hugo 项目根目录创建 AGENTS.md，写入你的写作/构建规范 尝试运行 /skillify，把你最常做的“写新文章”流程固化成技能 根据需要配置感兴趣的 MCP 服务器 所有信息均来源于 ~/.grok/docs/user-guide/ 下的官方文档，实际使用以你安装版本的最新文档为准。\n欢迎在评论区分享你用 Grok Build 打造的自定义工作流！\n","date":"2025-10-12T10:30:00+08:00","permalink":"/posts/grok-build-guide/","title":"Grok Build 完整使用指南：安装、项目规则、Skills 与 MCP 集成"},{"content":"Try hugo for my blog!\n","date":"2025-10-11T21:44:48+08:00","permalink":"/posts/hello-world/","title":"Hello World"},{"content":"Backpack Language Models 🔗：https://arxiv.org/pdf/2305.16765.pdf 代码：https://backpackmodels.science 作者团队：Department of Computer Science, Stanford University ACL 2023 长文\n这篇文章关注大模型中的词表示问题，提出一种使用多个“sense vector”线性组合的方法表示词向量，降低模型bias的方法。\n研究动机 作者在introduction部分开门见山指出目前大模型的问题，“The CEO believes that MASK” 这句话，语言模型会倾向于输出“He”，背后隐含了一种性别偏见，因为也会有女性的“CEO”。另一方面， “The nurse believes that MASK”则会倾向输出“she”。\n作者认为这种现象源自模型使用 Transformer 结构的时候，词是一整块放到模型中的，导致模型很容易被其他词（上下文）所干扰，因此，本文对词与词之间的fine-grained的影响进行学习。\n具体模型方法 首先引入了“sense vector”的概念，然后将每个词表示为多个sense vector的加权组合的形式。对每个词$x$，会有： $$ C(x)_1, C(x)_2, \\dots, C(x)_k $$ 记词输出为$\\textbf{o}i$，有： $ \\textbf{o}i = \\sum{j=1}^{n}\\sum{\\ell=1}^{k}\\alpha_{\\ell ij}C(x_j)_{\\ell} $\n实验 文章使用 Transformer 基线模型做实验，分了Micro (30M parameters), Mini (70M parameters), and Small (124M parameters; the same size as GPT-2 small) 三种模型进行实验，使用OpenWebText英文语料对模型进行训练。\n评估方面，文章首先使用pp值衡量模型的表达能力（perplexity，值越低越好），结果显示使用backpack可以获得更好的表达能力。同时，作者提及backpack会使模型需要更长时间去收敛。这点其实也不难理解，因为对每个词表达的维度更丰富了，所以需要更长的时间去拟合，捕捉词sense与上下文之间的关联关系。\n文章探索了sense的数量对模型表达能力的影响，结果显示，sense的增加会使得pp值显著下降，伴随的代价就是模型的总参数量也会增大。在sense=16时可以取得比较均衡的结果。\n在其他实验部分，文章对sense进行可视化分析，结果还是挺显著的。\n应用 值得一提的是，文章使用一个单独的章节重点提及backpack使用的方法在可控语言模型方面的应用，包括主题控制生成，缓解模型的性别偏见、借助外部知识完成模型输出的编辑等方面的应用。\n","date":"2023-06-28T11:15:17Z","permalink":"/posts/230628/","title":"论文阅读：Backpack Language Models"},{"content":"本周主要看完了一本书 —— 《不可打扰》\n在电子产品、手机屏幕充斥着我们世界的场合，如何跟手机共处成了一个热门的话题。在工作、约会、陪伴孩子的时候，我们总是很容易被手机打扰。作者围绕这个问题给出自己的思考、想法和一系列的行动方案。这里说几个印象比较深刻的点。\n分心的本质是逃避痛苦 人类是一种善于逃避痛苦的动物。作者认为很多事情都有其本质原因和表面原因。表面上你以为是高端手机、iPhone、电子设备剥夺了你的注意力，实际上有一些更本质的因素在作祟，这个更本质的因素是现实生活中你在面临的痛苦和生活中的难题。为了证明这个观点，作者举了一个TED演讲者的例子，该演讲者的talk中分享自己被计步器的激励机制 ruin 自己生活的故事。可是，在talk背后，演讲者没有透露过的是，自己的生活当时处于糟糕的状态，婚姻摇摇欲坠，找不到心仪的教职工作，才会这样去进入计步器设立的虚拟世界中。\n一个设计得好的应用程式可以提供很多即时有效的反馈，这些反馈替代了现实中往往很长的反馈链，于是成功剥夺了人的注意力，导致分心的结果\n作者是如何应对分心的 结合自己的生活，有几个场景让人印象深刻。\n","date":"2023-03-13T08:26:47Z","permalink":"/posts/book-week10/","title":"本周阅读总结 - 2023 Week 10"},{"content":"本文总结2022年任务驱动对话的研究进展。\n[CASPI] Causal-aware Safe Policy Improvement for Task-oriented Dialogue 来源：ACL 2022 Long Paper\n使用强化学习策略解决任务驱动对话中的问题：任务驱动对话中的策略学习。文章认为，任务驱动对话是从离线数据中学习，另一方面强化学习在离线数据训练的时候会导致误差和泛化问题，人类反应的随机性又加剧了这种误差的产生和传播过程。这篇文章提出了CASPI这个模型，使用批量化强化学习的方法，从人类反应的意图中学习细粒度的奖励信息。\nCINS: Comprehensive Instruction for Few-Shot Learning in Task-Oriented Dialog Systems 来源：AAAI 2022\n文章提到任务驱动对话的标注过程是费时费力的。文章使用预训练语言模型结合Prompt Learning的方式用于任务驱动对话的三个下游任务——意图识别、对话状态追踪和自然语言生成任务中。文章提出了基于T5模型的一个统一训练框架来达成这个目标 模型的输入：input+定义+约束+Prompt这样的方式 (definition, constraint, prompt)\ndefinition：提供任务的定义 Constraint：提供细粒度的任务约束 比如对于意图识别任务， definition为：“Predict the intent of the input query. Intent is the main topic or purpose of a query.” 对应的constraint 为 “Model needs to select the most suitable intent from: { candidate labels + label descriptions }” Prompt为“What is the intent of the given query?”\nFusing Task-Oriented and Open-Domain Dialogues in Conversational Agents 来源：AAAI 2022\n这篇文章在多轮对话中试图将任务驱动对话任务和开放domain的对话任务融合在一起，并提出了一个新的数据集 FusedChat 这个任务的挑战在于，model需要确定明确的对话模式以及根据对话上下文和具体模式生成一个合理的回复。比如说如果确定对话模式为开放域对话，则关注生成更加友善的回复内容；如果判断为任务驱动对话则为解决问题导向\nGlobalWoZ: Globalizing MultiWoZ to Develop Multilingual Task-Oriented Dialogue Systems 来源：ACL 2022\n这篇文章关注多语言的任务驱动对话数据集问题。文章认为已有的多语言任务驱动对话数据集语言的覆盖范围非常有限，同时对话实体往往是只存在于哪些说这个语言的国家的 因此文章提出了一个大规模、多语言的数据集；基于翻译对话模版和填入实体的方式构建\n","date":"2022-12-06T09:41:05Z","permalink":"/posts/tods2022/","title":"2022年任务驱动对话（Task-Oriented Dialogue System）研究进展"},{"content":"这篇文章总结一下对话情绪识别的进展情况，有两篇文章。\nIs Discourse Role Important for Emotion Recognition in Conversation? 来源：AAAI 2022\n这篇文章关注在对话情绪识别中，已有的模型没有利用“Discourse Role Information” 对话角色信息这个东西。对话角色/话语角色这是语言学上的一个概念：\n话语角色 是修辞主体 在一定语境中 实施言语行为时 所择取的 社会身份 话语角色不仅包括交际主体的社会职能特征 而且包括文化特征和心理特征 尤其是与其社会角色相应的语言特征 社会角色是话语角色的基础 话语角色是社会角色在言语交际领域的具体表现\n文章的解决办法是引入一个变分自编码器来对这些信息进行编码。这里有个疑问就是它变分自编码器怎么保证学到的是话语角色信息而不是其他有用的信息？\nContrast and Generation Make BART a Good Dialogue Emotion Recognizer 来源：AAAI 2022\n这篇文章关注的问题是相似的语义放在不同的上下文里会有不同的信息。为了更好的进行区分，这篇文章做了两部分的贡献\n监督对比学习：用于更好区分不同的语义信息 生成辅助任务：生成任务指的是让模型自己去生成下一段对话的内容，这样做的目的是更能捕捉富文本信息 ","date":"2022-12-05T21:56:12Z","permalink":"/posts/dialogue-em-aai/","title":"2022 年对话情绪识别的研究进展"},{"content":"最近用🤗Transformers的时候发现它们模型已经转移到glfs（git large file system）上，刚好自己想上传数据集的时候也需要用到。顺手了解了一下\n配置 LFS不属于git，而是GitHub开发出来的一套东西，在command line上使用的话需要安装lsf插件\n压缩包下载下来之后，解压然后运行安装\n1 2 3 4 5 $cd ~/Downloads $tar -xf {your *.gz} # install here $chmod 755 install.sh $sudo ./install.sh 使用 LFS提供的是一个钩子（hook）。因此，对每个使用LFS的repo都要hook一下\n1 git lfs install 安装之后，使用步骤如下\n使用git lfs track \u0026quot;*.m\u0026quot; 来追踪想要放到大文件系统上的内容。\n这条命令会在文件夹中创建属性文件，要注意使用git追踪这个文件\n1 git add .gitattributes 后续就是重复普通的add and commit 就ok\n1 2 3 git add ubattery_dep.m git commit -am \u0026#34;Add isotopic composition after depletion\u0026#34; git push origin master Source Git Large File Storage Git Large File Storage | Git Large File Storage (LFS) replaces large files such as audio samples, videos, datasets, and graphics with text pointers inside Git, while storing the file contents on a remote server like GitHub.com or GitHub Enterprise. ","date":"2022-11-17T20:44:01Z","permalink":"/posts/git-lfs/","title":"git-lfs 基础使用指南"},{"content":"发表于2019年的对话意图识别任务的baseline\n本文发表时间算比较早了，模型结构比较简单，大体就是以BERT作为encoder编码器部分，对输入序列进行编码获得token的词向量表示\n意图识别本质就是分类任务，这篇文章使用BERT最后一级的[CLS] 作为句子分类的向量表示。槽填充部分使用分类标注的形式。因为槽填充其实就是在进行命名实体识别，这篇文章也做了CRF作为输出级的实验，从实验结果来看，两个数据集上结果有好有坏。联想到之前做少样本NER的时候，发现其实BERT+MLP+Softmax已经可以获得足够好的性能。CRF是不是真的很好用？现在做NER的看到有很多基于Span分类的方法了。\n整体模型结构从现在来看是非常简单的，但在一些任务中已经足够好用（实际工程上，同时这个模型代码也比较好看懂和好改，输入输出格式也简单易懂。）\n搜对话意图识别任务baseline的时候高频出现的是这篇文章。我觉得其实有很多可以改进的地方（单纯从这篇文章的方法角度）\n[CLS] 单纯做句子分类任务的缺陷在后面的文章已经提到了，有类似Sentence- BERT等更好的句子分类方法 虽然可以应用到对话意图识别任务中，但从源代码中可以看到，其实并没有利用对话层面的信息，将对话句子处理成纯文本的形式丢给BERT看的。对话其他关联研究（情绪分类等）支持对对话者信息、对话互动进行建模可以更有效进行表示，不知道放在意图识别这里有没有研究过类似的问题？ ","date":"2022-10-27T10:31:31Z","permalink":"/posts/jointbert/","title":"论文阅读 BERT for Joint Intent Classification and Slot Filling"},{"content":"这次来分析一下LeetCode中几道炒股题目\nNo.121 买卖股票的最佳时机 题目描述 给定一个数组 prices ，它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。\n你只能选择 某一天 买入这只股票，并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。\n返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润，返回 0 。\n示例 1：\n1 2 3 4 输入：[7,1,5,3,6,4] 输出：5 解释：在第 2 天（股票价格 = 1）的时候买入，在第 5 天（股票价格 = 6）的时候卖出，最大利润 = 6-1 = 5 。 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格；同时，你不能在买入前卖出股票。 示例 2：\n1 2 3 输入：prices = [7,6,4,3,1] 输出：0 解释：在这种情况下, 没有交易完成, 所以最大利润为 0。 踩坑思路 一开始想对每个元素，往前找最大值，可是这个题目的数据范围非常大，超时了。\n基于这个想法，马上想到，超时是因为重复计算，比如找到一个最大值之后，我就不用每次都找最大值了，想到第二种思路，就是找到最大值之后，在子区间找最小值，然后从最大值的下一个位置开始继续找。这时候又面临另外的情况：\n数组是单调递减的时候，反复找依然很浪费时间 摆动数组：还是浪费时间。 于是这个版本的代码也超时了。\n最后看答案找到第三种解法——滚动法。因为题目限制了只买卖一次，那我就假设，一开始是第一天买入的，从前往后遍历：\n遍历到位置i的时候就假设是那天卖出去，看能捞多少钱，如果比已知最大利润更大那就更新最大利润。 如果遍历找到一个更小的价格，那就按照那个价格买入。这时候注意，因为是从前往后遍历，所以不会存在找到最小值之后倒车卖出（最大值出现在最小值之前）的可能性，只是滚动更新买入价格而已。 这次终于不超时了。\n1 2 3 4 5 6 7 8 9 10 class Solution: def maxProfit(self, prices: List[int]) -\u0026gt; int: res = 0 buyin = prices[0] for price in prices: if price - buyin \u0026gt; res: res = price - buyin if price \u0026lt; buyin: buyin = price return res 运行结果：\n1 2 3 4 Accepted 211/211 cases passed (96 ms) Your runtime beats 98.87 % of python3 submissions Your memory usage beats 85.42 % of python3 submissions (23.3 MB) No.122 买卖股票的最佳时机 II 题目描述 给你一个整数数组 prices ，其中 prices[i] 表示某支股票第 i 天的价格。\n在每一天，你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买，然后在 同一天 出售。\n返回 你能获得的 最大 利润 。\n示例 1：\n1 2 3 4 5 输入：prices = [7,1,5,3,6,4] 输出：7 解释：在第 2 天（股票价格 = 1）的时候买入，在第 3 天（股票价格 = 5）的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。 随后，在第 4 天（股票价格 = 3）的时候买入，在第 5 天（股票价格 = 6）的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3 。 总利润为 4 + 3 = 7 。 示例 2：\n1 2 3 4 输入：prices = [1,2,3,4,5] 输出：4 解释：在第 1 天（股票价格 = 1）的时候买入，在第 5 天 （股票价格 = 5）的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。 总利润为 4 。 示例 3：\n1 2 3 输入：prices = [7,6,4,3,1] 输出：0 解释：在这种情况下, 交易无法获得正利润，所以不参与交易可以获得最大利润，最大利润为 0 。 解题思路 这个反而感觉比121还要简单一些，比121多了多次交易这个条件。我的实现思路是不断找单调递增子序列。子序列的起点为买入时间，子序列的终点是卖出时间。这里有个边界条件是，在序列遍历结束之后，如果买入日期不是-1，那就意味从买入日期开始到最后构成一个单调递增子序列，需要在最后一天卖出，赚最后一手钱。\n实现代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution: def maxProfit(self, prices: List[int]) -\u0026gt; int: res = 0 prev = prices[0] start = -1 for idx, price in enumerate(prices): if price \u0026gt; prev and start == -1: start = idx - 1 elif price \u0026lt; prev and start != -1: end = idx - 1 res += (prices[end] - prices[start]) start = -1 prev = price if start != -1: # 说明到最后一天还没卖出，那就在最后一天卖出 res += (prices[-1] - prices[start]) return res 901 股票价格跨度 编写一个 StockSpanner 类，它收集某些股票的每日报价，并返回该股票当日价格的跨度。\n今天股票价格的跨度被定义为股票价格小于或等于今天价格的最大连续日数（从今天开始往回数，包括今天）。\n例如，如果未来7天股票的价格是 [100, 80, 60, 70, 60, 75, 85]，那么股票跨度将是 [1, 1, 1, 2, 1, 4, 6]。\n示例：\n1 2 3 4 5 6 7 8 9 10 11 输入：[\u0026#34;StockSpanner\u0026#34;,\u0026#34;next\u0026#34;,\u0026#34;next\u0026#34;,\u0026#34;next\u0026#34;,\u0026#34;next\u0026#34;,\u0026#34;next\u0026#34;,\u0026#34;next\u0026#34;,\u0026#34;next\u0026#34;], [[],[100],[80],[60],[70],[60],[75],[85]] 输出：[null,1,1,1,2,1,4,6] 解释： 首先，初始化 S = StockSpanner()，然后： S.next(100) 被调用并返回 1， S.next(80) 被调用并返回 1， S.next(60) 被调用并返回 1， S.next(70) 被调用并返回 2， S.next(60) 被调用并返回 1， S.next(75) 被调用并返回 4， S.next(85) 被调用并返回 6。 注意 (例如) S.next(75) 返回 4，因为截至今天的最后 4 个价格 (包括今天的价格 75) 小于或等于今天的价格。\n这道题又一次死在了单调栈。\n思路分析 一开始看错题目，以为要找数组中比这个价格小的，gg\n看清楚题目之后，用暴力解法，超时。超时原因在于对每个入栈元素都往前判断，题目对时间限制很严格。\n最后看答案才解决问题。有一个很关键的突破口被我忽视了：对进来的每天股票的价格，如果这个价格比以前的价格低，那么以前的这些价格都不会参与到后续的计算了（因为不可能再构成单调减序列）。\n因此，维护一个单调栈，栈顶元素就是到前一天的价格，不过进栈有讲究：\n如果比前一天的小，那就进栈，天数+1 如果比前一天的大，那就将栈里元素出栈，直到栈里元素都比当前元素大，把当前元素进栈。 还有个讲究就是，进栈的不仅仅是元素，还有当天的答案。每次出栈的时候，把天数给加起来。为当前元素获得的答案。\n代码实现 1 2 3 4 5 6 7 8 9 10 11 12 class StockSpanner: def __init__(self): self.stack = [] def next(self, price: int) -\u0026gt; int: res = 1 while self.stack and self.stack[-1][0] \u0026lt;= price: res += self.stack.pop()[1] self.stack.append((price, res)) return res 运行结果\n1 2 3 4 Accepted 99/99 cases passed (340 ms) Your runtime beats 40.35 % of python3 submissions Your memory usage beats 90.02 % of python3 submissions (19.6 MB) ","date":"2022-08-02T17:22:35Z","permalink":"/posts/leetcode-stock/","title":"LeetCode股神大激斗"},{"content":"两道关联题目，跳跃游戏，两道都没顺利做出来，好好记录一下。\n55 跳跃游戏 题目描述 给定一个非负整数数组 nums ，你最初位于数组的 第一个下标 。\n数组中的每个元素代表你在该位置可以跳跃的最大长度。\n判断你是否能够到达最后一个下标。\n示例 1：\n1 2 3 输入：nums = [2,3,1,1,4] 输出：true 解释：可以先跳 1 步，从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。 示例 2：\n1 2 3 输入：nums = [3,2,1,0,4] 输出：false 解释：无论怎样，总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 ， 所以永远不可能到达最后一个下标。 解题思路 这道题的核心是判断能否的到达最后的位置。一开始我的想法是每次跳的时候找到达范围内能够跳最远的，可是我混淆了一个条件，就是数组的每个元素表示的是从当前位置出发能够去的步数，需要加上这个位置的下标才是到达的最远步数，所以一开始死活都过不了，一个最经典的测试用例就是[4, 2, 0, 0, 1, 1, 4, 4, 0, 1]，前面半段[4, 2, 0, 0, 1] 的时候代码错误选了2就以为没发到达最后位置。\n代码如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution: def canJump(self, nums: List[int]) -\u0026gt; bool: i = 0 lns = len(nums) if lns == 1: return True max_length = 0 while i \u0026lt; lns: if i \u0026lt;= max_length: # 寻找最远的位置 max_length = max(max_length, i + nums[i]) if max_length \u0026gt;= lns - 1: return True i += 1 return False 45 跳跃游戏 ii 这个换了一种思路，假设一定能去到，就看要最短多少步。\n题目描述 给你一个非负整数数组 nums ，你最初位于数组的第一个位置。\n数组中的每个元素代表你在该位置可以跳跃的最大长度。\n你的目标是使用最少的跳跃次数到达数组的最后一个位置。\n假设你总是可以到达数组的最后一个位置。\n示例 1:\n1 2 3 4 输入: nums = [2,3,1,1,4] 输出: 2 解释: 跳到最后一个位置的最小跳跃数是 2。 从下标为 0 跳到下标为 1 的位置，跳 1 步，然后跳 3 步到达数组的最后一个位置。 示例 2:\n1 2 输入: nums = [2,3,0,1,4] 输出: 2 解题思路 还是和55一样，寻找最远的位置，不同的地方在于维护一个边界，在遍历过程中，更新最远可以到达的位置，到达边界的时候，更新边界为最远可以到达的位置，并且更新边界为新的最远可以到达的位置。\n有个地方需要注意的是：并不是真的让你跳，而是把数组元素挨个遍历一遍就OK\n另外就是：只需要遍历到倒数第二个元素就可以了，因为题目已经保证肯定能到，遍历到最后一个元素的话会造成无效跳跃，多了一次。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution: def jump(self, nums: List[int]) -\u0026gt; int: lns = len(nums) if lns == 1: return 0 i = 0 # max_pos 为可以到达的最远位置 max_pos, end = 0, 0 res = 0 for i in range(lns - 1): if max_pos \u0026gt;= i: # 当前的数据点在最远位置距离内 max_pos = max(max_pos, i + nums[i]) # 到达边界更新边界为最远可以到达的位置 if i == end: end = max_pos res += 1 return res ","date":"2022-07-28T17:19:28Z","permalink":"/posts/leetcode-45-55/","title":"leetcode 45 and 55 两个跳跃游戏"},{"content":"问题描述 Levenshtein 距离，又称编辑距离，指的是两个字符串之间，由一个转换成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符，插入一个字符，删除一个字符。编辑距离的算法是首先由俄国科学家 Levenshtein 提出的，故又叫 Levenshtein Distance 。\n例如： 字符串A: abcdefg 字符串B: abcdef 通过增加或是删掉字符 ”g” 的方式达到目的。这两种方案都需要一次操作。把这个操作所需要的次数定义为两个字符串的距离。 要求： 给定任意两个字符串，写出一个算法计算它们的编辑距离。\n代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 if __name__ == \u0026#39;__main__\u0026#39;: src = input().strip() target = input().strip() # 注意下标，因为字符串长度是len(src)，因此下标要+1 lns, lnt = len(src) + 1, len(target) + 1 # 初始化动态规划表格 table = [[0 for _ in range(lnt)] for _ in range(lns)] # target为空串的时候，编辑距离为i次删除操作 for i in range(lns): table[i][0] = i # source为空串的时候，那么从source到target的编辑距离为j次插入操作 for j in range(lnt): table[0][j] = j # 因为0已经做了，所以从1开始 for i in range(1, lns): for j in range(1, lnt): if src[i - 1] == target[j - 1]: table[i][j] = table[i - 1][j - 1] else: # 通过插入获得target的距离，插入为一次操作，递归回来到长度i到长度j-1需要的操作 ins_dist = table[i][j - 1] + 1 del_dist = table[i - 1][j] + 1 # why ? rep_dist = table[i - 1][j - 1] + 1 table[i][j] = min(ins_dist, del_dist, rep_dist) print(table[-1][-1]) ","date":"2022-07-27T20:18:52Z","permalink":"/posts/stredit-python/","title":"计算字符串的最短编辑距离（Python版本）"},{"content":"题目描述 给定一个区间的集合 intervals ，其中 intervals[i] = [start_i, end_i] 。返回 需要移除区间的最小数量，使剩余区间互不重叠 。\n示例 1:\n1 2 3 输入: intervals = [[1,2],[2,3],[3,4],[1,3]] 输出: 1 解释: 移除 [1,3] 后，剩下的区间没有重叠。 示例 2:\n1 2 3 输入: intervals = [ [1,2], [1,2], [1,2] ] 输出: 2 解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。 示例 3:\n1 2 3 输入: intervals = [ [1,2], [2,3] ] 输出: 0 解释: 你不需要移除任何区间，因为它们已经是无重叠的了。 解题思路 本题为贪心算法的一个典型应用问题。解题的核心在于，要选出尽可能所的不重叠的区间，那就意味着咱们要选出的每个区间应该要尽可能窄。\n考虑两个区间$[a_1, b_1]$和 $[a_2, b_2]$， 我们假设$b_1 \\le b_2$, 分情况讨论：\n如果$b_1 \\le a_2$，那么我们可以知道，第二个区间整体是在第一个区间的右侧，两个区间可以同时存在 如果$b_1 \u0026gt; a_2$，这时候，第二个区间的起点可能在第一个区间中，或者完全覆盖了第一个区间。无论是哪种情况，都意味着第二个区间比第一个“长”。考虑咱们题目要求，要去掉尽可能少的区间实现区间不重叠，那么很自然要把长的空间去掉。这也是本题“贪心”的来源。 代码 分析完毕，咱们直接上代码。使用Python实现。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution: def eraseOverlapIntervals(self, intervals: List[List[int]]) -\u0026gt; int: # 按照区间端点对区间进行排序 intervals.sort(key=lambda x: x[1]) lni = len(intervals) # 边界情况，如果只有一个区间，直接返回，因为不需要任何操作 if lni == 1: return 0 i = 0 res = 0 while i \u0026lt; len(intervals): # 对每个区间，往前搜索可能重叠的区间 # 因为咱们已经对区间排序过，因此只需要判断每个区间的起点就可以 bi = intervals[i][1] j = i + 1 while j \u0026lt; lni: # 判断区间起点是否重叠，重叠则干掉 if intervals[j][0] \u0026lt; bi: res += 1 j += 1 else: break # 注意循环退出条件：我们找到了下一个完全不重叠的区间 # 因此，用j更新i，中间那些区间已经被干掉 i = j return res 运行结果 1 2 3 4 Accepted 58/58 cases passed (224 ms) Your runtime beats 59.21 % of python3 submissions Your memory usage beats 34.24 % of python3 submissions (39.2 MB) ","date":"2022-07-27T16:10:35Z","permalink":"/posts/lt435/","title":"Leetcode 435：无重叠区间"},{"content":"DGL 库升级到最新版本0.9.0+的测试版之后，运行老版本的代码会遇到错误\nAttributeError: 'Column' object has no attribute deferred_dtype\n在官方GitHub页面可以看到相关的讨论：GitHub issue 4107\n个人推测是因为DGL版本升级对底层代码进行了重构，我的代码中，使用老版本的DGL构建了图实例，并且使用dill持久化为二进制文件，在新环境加载这个代码的时候，就会遇到一些新添加进去的属性没有的情况，也就是AttributeError。\n解决方法：回滚到老版本\n1 2 3 4 5 6 # if you use cuda 11.03 pip uninstall dgl-cu113 pip install dgl-cu113==0.8.1 dglgo -f https://data.dgl.ai/wheels/repo.html # if you use cuda 10.2 pip uninstall dgl-cu102 pip install dgl-cu102==0.8.1 dglgo -f https://data.dgl.ai/wheels/repo.html ","date":"2022-06-17T20:49:39Z","permalink":"/posts/dgl-error-4107/","title":"解决DGL库AttributeError deferred_dtype 错误"},{"content":"教训记录。\n题目描述 给出由小写字母组成的字符串 S，重复项删除操作会选择两个相邻且相同的字母，并删除它们。\n在 S 上反复执行重复项删除操作，直到无法继续删除。\n在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。\n示例：\n输入：\u0026quot;abbaca\u0026quot; 输出：\u0026quot;ca\u0026quot; 解释： 例如，在 \u0026quot;abbaca\u0026quot; 中，我们可以删除 \u0026quot;bb\u0026quot; 由于两字母相邻且相同，这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 \u0026quot;aaca\u0026quot;，其中又只有 \u0026quot;aa\u0026quot; 可以执行重复项删除操作，所以最后的字符串为 \u0026quot;ca\u0026quot;。\n提示：\n\u0026lt;= S.length \u0026lt;= 20000 S 仅由小写英文字母组成。 一种错误解法 自己思维的问题，看到这种题目，感觉题意很显而易见，啪的一声就想用暴力求解\n字符串转数组 标记指针i，从0开始遍历，遇到俩相邻的字符就转为空，然后回头重新开始 如果i的下一个是空，就往后走找第一个不是空的字符，判断是否相等 代码写好了：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution: def removeDuplicates(self, s: str) -\u0026gt; str: if len(s) == 1: return s lt = list(s) ln = len(lt) i = 0 while i \u0026lt; ln - 1: if lt[i]: # not an empty char j = i + 1 while j \u0026lt; ln and not lt[j]: j += 1 if j == ln: i += 1 continue if lt[i] == lt[j]: lt[i] = \u0026#39;\u0026#39; lt[j] = \u0026#39;\u0026#39; i = 0 continue elif j == ln - 1 and lt[i] != lt[j]: i += 1 continue else: i += 1 continue else: i += 1 return \u0026#39;\u0026#39;.join(lt) 写得非常丑陋，分类讨论了多种边界情况，测试通过。一提交，超时了。\n超时原因也不难理解，每次替换一次就回头重新开始遍历，效率低到没朋友。\n忍无可忍看参考答案，解题思路就一个字：栈。\n茅塞顿开，五分钟得到第二版方案\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution: def removeDuplicates(self, s: str) -\u0026gt; str: stk = [] for c in s: if stk: tp = stk[-1] if c == tp: stk.pop() continue else: stk.append(c) else: stk.append(c) return \u0026#39;\u0026#39;.join(stk) 执行时间非常快，因为只需要过一遍字符串就可以了。思想就是：\n每个字符，比较该字符与栈顶元素 如果相等，把栈顶元素干掉，往后走 不相等，元素入栈，继续往后走 把最后留在栈中的元素concat然后输出 运行结果 1 2 3 4 Accepted 106/106 cases passed (68 ms) Your runtime beats 77.08 % of python3 submissions Your memory usage beats 79.87 % of python3 submissions (15.9 MB) ","date":"2022-05-27T12:27:49Z","permalink":"/posts/leetcode-1047/","title":"LeetCode 1047：删除字符串中的所有相邻重复项"},{"content":"DEVONthink 是苹果系统下一款很好用的知识管理工具，然而，由于DEVONthink本身更侧重于档案管理，所以通常会和其他markdown类笔记软件打组合拳。\n今天分享一款直接复制DEVONthink的item link为markdown链接样式的脚本：\n1 2 3 4 5 6 7 8 9 10 tell application id \u0026#34;DNtp\u0026#34; set theMarkdownLinks to {} repeat with thisRecord in (selected records) copy (\u0026#34;[\u0026#34; \u0026amp; (name of thisRecord) \u0026amp; \u0026#34;](\u0026#34; \u0026amp; (reference URL of thisRecord) \u0026amp; \u0026#34;) \u0026#34;) to end of theMarkdownLinks end repeat if theMarkdownLinks ≠ {} then display notification ((count items of theMarkdownLinks) \u0026amp; \u0026#34; Markdown links copied\u0026#34; as string) with title \u0026#34;Markdown-Link(s) kopiert\u0026#34; set the clipboard to (theMarkdownLinks as string) end if end tell ","date":"2022-05-14T17:04:29Z","permalink":"/posts/dt-to-md/","title":"【DEVONthink】复制item的Markdown样式脚本"},{"content":"最近读到一系列KG的文章都使用了四元数（Quaternion）用于Embedding或者扩展GNN。四元数是一种区别于实数和复数的数学工具，在这里整理一些关于这个数学工具的基础知识。\n四元数是什么 四元数的基本运算 加法：两个四元数$q$ and $p$ 的加法运算可以表示为：\n$$ q + p = (q_r + p_r) + (q_i + p_i)\\textbf{i} + (q_j + p_j)\\textbf{j} + (q_k + p_k)\\textbf{k} $$范数（Norm） 四元数$q$ 的范数可以表示为$\\Vert q \\Vert = \\sqrt{q_r^2 + q_i^2 + q_j^2 + q_k^2}$\n在范数的基础上，单位量可以定义为 $q^{\\triangleleft}=\\frac{q}{\\Vert q \\Vert}$\n哈密顿积（Hamilton Product） 四元数的乘法定义为哈密顿积，一般使用符号$\\otimes$ 表示，我们有： $$ \\begin{split} q \\otimes p \u0026= (q_rp_r-q_ip_i-q_jp_j-q_kp_k) + (p_rq_i+q_rp_i-q_kp_j+q_jp_k)\\textbf{i}+ \\\\\u0026(q_jp_r+q_kp_i+q_rp_j-q_ip_k)\\textbf{j}+ (q_kp_r-q_jp_i+q_ip_j+q_rp_k)\\textbf{k} \\end{split} $$ 哈密顿积（Hamilton Product）不满足乘法交换律，也就是说$q \\otimes p \\ne p \\otimes q$\n四元数+知识图谱 这章推荐几篇关于四元数与知识图谱结合的论文。\nQuaternion Graph Neural Networks (QGNN) 这篇文章首次将四元数引入到图神经网络（GNN）中，目的是增强图神经网络的表达能力。文章使用QGNN作为encoder， 然后根据图分类、node分类和链接预测任务设计不同的decoder函数。\n这篇文章中，QGNN对比传统GNN最大的差异在于：在aggregation function中，使用哈密顿积取代实数的乘积运算用来更新层的节点表示。\n从文章中将QGNN应用于节点分类任务或者链接预测任务时，实体的表示是将四元数的四部分concatenate作为表示向量。因此这里让我困惑的地方在于四元数和distengle表示学习有什么区别和联系？\nQuaternion Knowledge Graph Embeddings (QutatE) 将四元数应用于知识图谱嵌入+链接预测任务的一篇文章。该文章发表于NIPS 2019.\n文章在摘要部分开门见山提出了使用四元数用于知识图谱嵌入表示的优势\n哈密顿积可以用于捕捉隐含依赖关系 四元数空间的旋转操作具有更大的灵活性 QutatE与ComplEx（一种流行的以复数作为实体、关系表示的知识图谱嵌入框架）是兼容的，因此，可以处理对称、犯对称以及反向关系 本模型将实体和关系都表示成四元数的形式，然后将关系表示为旋转操作。在计算分数的过程中，将头实体与关系的模长向量计算哈密顿积，得到新的四元数（视为旋转）然后与尾实体计算dot product，作为最后的分数。\n训练过程的loss参考了ComplEx文章的形式。\nQuatRE: Relation-Aware Quaternions for Knowledge Graph Embeddings 这篇文章可以视为对QuatE的一个扩展。这篇文章认为已有的知识图谱嵌入模型无法有效表示头尾实体之间的关联关系。因此，本文将关系表示为两个面向的四元数，然后使用了一个更复杂的打分函数（详情参见原文的2.2章节）。感觉和TransR的思想有异曲同工之妙。\nTwo-view Graph Neural Networks for Knowledge Graph Completion 这篇文章也是将四元数应用于知识图谱表示学习任务的一个尝试。我觉得这篇文章最novel的地方反而在于图构造部分。不同于以前的基于图神经网络的知识图谱链接预测模型，这篇文章将图划分成两部分\n以实体为中心的无向图：着重捕捉实体之间的共现关系 以关系为中心的有向图：着重捕捉关系之间的共现关系和关联关系 打分函数部分使用了QuatE的打分函数。\n这篇文章的不足在于：提出的这个图结构设计没有使用RGCN之类的实数图神经网络方法做对比实验，那么就无法论证为什么要使用四元数而不是已有的GNN来解决问题，会有一种为了用四元数而用四元数的感觉。\n","date":"2022-04-18T15:17:29Z","permalink":"/posts/quad/","title":"四元数 (Quaternion) 基础知识"},{"content":"今天跟朋友讨论的一道有趣的面试问题：为什么Transformer中Scale-dot attention计算 $Attn = \\text{Softmax}(\\frac{QK^T}{\\sqrt{d_k}})V$，缩放因子一定是$\\sqrt{d_k}$ 而不是d_k或者其他形式？\n","date":"2021-08-27T19:07:49Z","permalink":"/posts/scaledotdk/","title":"为什么Scale-Dot Attention的分母是根号d"},{"content":"本篇整理几种常见的知识图谱补全模型的原理以及对应的超参数。\nRESCAL 论文出处：A three-way model for collective learning on multi- relational data, ICML 2011\n这是一种基于张量分解方法的嵌入模型。发表的年份非常早。原文中并没有提及学习率等超参数。\nTransE 论文出处：Translating Embeddings for Modeling Multi-relational Data\nTransE模型算是最经典被使用最多的基于嵌入技术的知识图谱补全模型。基本假设也非常优美，以$\\Vert h+r-t \\Vert_p$ 作为评分函数。如果$(h, r,t)$ 能够成为三元组，那么这个评分应该足够低。\n可调超参数：\n学习率：在原文中，学习率对数据集Wordnet, FB15k, FB1M 均为0.01。 KG向量的维度 margin：因为TransE使用了margin ranking loss，因此margin是一个重要超参数。原文给出的搜索范围是${1, 2, 10}$ p：使用范数作为打分函数，那么使用1范数还是2范数这也是一个可以调整的超参数 DistMult 论文出处：Embedding Entities And Relations For LearnIng And Inference In Knowledge Bases\n这是一种双线性模型。将头尾实体表示成向量之后，把关系表示成一个对角矩阵，并且有对应的打分函数$s = \\textbf{y}{e1}^T\\textbf{M}{r} \\textbf{y}_{e2}$\n依然使用MarginLoss来优化。\n超参数：\nLearning Rate：0.1 Optimizer: AdaGrad L2正则化因子：0.0001 Training Epoch：300 for WordNet，100 for FB15k and FB15k-401 ComplEx 论文出处：Complex Embeddings for Simple Link Prediction\nComplEx将实体和关系嵌入到复数空间中，并且开发了一个相应的打分函数。同时，提出了一个基于Softplus和$L_2$正则化的Loss函数\n$$ min\\sum_{r(s, o)\\in \\Omega} \\log(1+\\exp(-\\textbf{Y}_{rso}(\\phi(s, r, o;\\Phi))))+\\lambda\\Vert\\Theta\\Vert_2^2 $$$\\lambda$ 作为两部分loss的平衡因子。原文在${0.1, 0.03, 0.01, 0.003, 0.001, 0.0003, 0.00001, 0.0}$ 之间进行调整。\n在模型训练方面，使用SGD优化器，但并没有提供相应的学习率信息。\nSimplE 论文出处：SimplE Embedding for Link Prediction in Knowledge Graphs\n这是一种基于CP分解（Canonical Polyadic (CP) decomposition）的嵌入模型。\n训练的Loss和ComplEx类似\n超参数\nEpoch：1000 batch size：100 Learning Rate：0.1 for WN18 and 0.05 for FB15k Optimizer: AdaGrad Embedding Size: 200 for WN18RR, FB15k $\\lambda$ search from ${0.1, 0.001, 0.03}$ RotatE 论文出处：Rotate: Knowledge Graph Embedding By Relational Rotation In Complex Space\n这是一种将头尾实体之间的关系建模为旋转操作的KGE模型\n训练loss形式：\n$$ \\mathcal{L} = -\\log\\sigma(\\gamma-d_r(v_h, v_t))-\\sum p(h'_{i}, r, t'_i)\\log\\sigma (d_{r}(v_{h'}, v_{t'})-\\gamma) $$超参数主要跟loss有关系：\nEmbedding Dimension: ${125, 250, 500, 1000}$ Batch Size: ${512, 1024, 2048}$ self-adversarial sampling temperature $\\alpha \\in {0.5, 1.0}$ margin $\\gamma \\in {3, 6, 9, 12, 18, 24, 30}$ ","date":"2021-08-24T21:24:16Z","permalink":"/posts/kgehyperparame/","title":"知识图谱补全模型与超参数整理"},{"content":"整理最近阅读的时序知识图谱（Temporal Knowledge Graph）补全领域的文献。\n基本概念 首先，明确基本概念。现有的知识图谱表示为$\\mathcal{G}={(h, r, t)}$ 的三元组集合，这是一种静态的图谱，目前针对这种形式的知识图谱补全任务，也就是预测三元组$(h, r, ?)$ 已经有大量的相关研究，包括Translation-based方法、Bilinear Model和KGAT等图神经网络的尝试。\n在现实应用中，静态图谱存在一个隐蔽但关键的缺陷：知识图谱用于反映事实，而很多事实是会随着时间不断变化的。比如，奥运会每四年举办一次，每次举办的地点不一样。每年的美国总统有可能会变化。因此，一个符合直觉的思路就是在三元组的基础上，加上时间戳因子，表示这个fact在特定时间是有效的。\n这时候，知识图谱变成了四元组集合，$\\mathcal{G}={(h, r, t,\\tau)}$，最后一个因子为时间戳，也就是论文中常常看到的timestamp。\n已有的时序知识图谱时间戳单位一般是年，因为如果以更精确的时间粒度，那同一时间发生的时间实在太少了，导致模型很难训练。\n引入时间戳之后，就会有一个对应概念，叫snapshot，指的是某个具体时间点所有三元组的集合，可以类比视频中的一帧来理解。那么，时序知识图谱可以表示为一系列snapshot的集合，我们有 $$ \\mathcal{TKG} := \\{\\mathcal{G}_1, \\dots, \\mathcal{G}_t, \\dots, \\mathcal{G}_T\\} $$其中，$T$ 表示当前的时序图谱一共有$T$个snapshot的KG组成。\n问题定义：时序知识图谱补全 有了时序知识图谱的概念之后，就可以根据静态知识图谱补全任务来延伸得到时序知识图谱补全的定义。\n静态知识图谱补全任务：对于$\\mathcal{G}={\\mathcal{E}\\times\\mathcal{R}\\times \\mathcal{G}}={(h, r, t)}$, 给出新的三元组 $(h, r, ?)$ 预测尾部实体，或者给出$(?, r, t)$预测头部实体。在实际评估的时候，会把预测问题建模为对实体集合$\\mathcal{E}$中所有实体进行打分的问题。\n时序知识图谱补全任务：对于$\\mathcal{TKG} := {\\mathcal{G}_1, \\dots, \\mathcal{G}_t, \\dots, \\mathcal{G}_T}$， 给出$(h, r, ?, \\tau)$ or $(?, r, t, \\tau)$，预测missing entity。这是时序知识图谱补全任务的最基本形式。由于多了时间这个维度的约束，在实际paper中会出现不同的变种形式\n赋予模型预测未来时间的能力，也就是限制训练集与测试集的时间跨度交集为空 不做限制，只预测训练集中出现过的时间戳的missing entity。 Paper RTFE: A Recursive Temporal Fact Embedding Framework for Temporal Knowledge Graph Completion 来源：NAACL 2021\n问题定义：对时序知识图谱补全任务的定义使用了第二种定义，也就是假设训练集和测试集的时间戳是一致的，预测missing entity\n贡献：本文关注了静态KGE Model与TKGE设定之间的差异，提出了一个演进式的模型，把时间戳之间的转换建模为一个马尔可夫过程，递归的学习不同时间戳的实体表示；并且由于马尔可夫转移概率矩阵的引入，赋予了模型一定的对新时间戳三元组的预测能力。\n模型：模型分为两部分，第一部分unite了所有时间戳的四元组训练一个静态KG表示，一次作为初始化状态，再每个时间戳采样四元组训练转移模型以及更新KG表示。这个训练设定有让我迷惑的地方，在训练静态KG的时候已经看到了所有时间的数据，那么在训练转移模型的时候会不会受到这个数据泄漏的影响？\nTIE: A Framework for Embedding-based Incremental Temporal Knowledge Graph Completion 来源：SIGIR 2021\n链接：ACM Page\n对TKGC的问题定义：这篇文章关注增量TKGC问题而不是标准的TKGC，Increment TKGC的差别在于，model只能看到当前时刻及之前时刻的所有实体，而不是从一开始到最后的所有实体。 挑战：增量TKGC的设定带来了新的挑战，首先就是模型的灾难性遗忘，这是所有增量学习模型共同的关注点；第二是模型对fact变化的识别能力，第三是训练效率问题。如果根据每个新的时间戳都fine-tune时间成本会不划算。\n模型：针对灾难性遗忘的问题，应用Replay机制，这里的replay是对不同时间戳重复出现的事件的replay；针对fact随时间变化的问题，提出使用将deleted fact作为负采样的方法对KG进行训练；同时还使用了正则化机制(Sec. 5.3) 应对灾难性遗忘问题\n这篇文章所关注的问题和对应的方法都很有意思，后面看看能写一篇长文讲讲。\nTemporal Knowledge Graph Reasoning Based on Evolutional Representation Learning 来源：SIGIR 2021 Model： RE-GCN\n对TKGC的问题定义：这篇文章关注了更具挑战性的场景——预测未来。在给出$t$时刻以及$t$时刻之前的$m$个时间窗口的KG，预测$t+1$时刻所发生的事件。\n模型：模型包含两部分，第一部分是应用R-GCN学习每个时间戳的实体表示，第二部分是将不同时间戳的KG建模为序列，更新时间related的表示\n","date":"2021-08-22T11:31:32Z","permalink":"/posts/tkbc01/","title":"时序知识图谱补全论文整理：Ⅰ"},{"content":"这是一个官方宣称比Argparser更好用的Python命令行开发工具包。\n基本功能 嵌套命令 自动生成help页面 子命令的懒加载 基础使用 Document\n从一个简单的example入手\n1 2 3 4 5 6 7 import click @click.command() @click.option(\u0026#39;--count\u0026#39;, default=1, help=\u0026#39;number of greetings\u0026#39;) @click.argument(\u0026#39;name\u0026#39;) def hello(count, name): for x in range(count): click.echo(f\u0026#34;Hello {name}!\u0026#34;) import click 之后，使用修饰器的方式将一个函数和对应的命令进行绑定。\n@click.command() 表示接下来修饰的函数是一个命令。\n使用@click.option('--count', default=1, help='number of greetings')添加可选参数，第一个位置为对应的参数名称，默认值由default= 字段指定一个默认值，help= 给出对应的帮助文档；使用@click.argument('name') 添加argument，也就是没带默认值的参数。\n这里可以看到，比起Argparser，好玩的地方在于可以直接和函数的参数绑定。\n另外例子里使用了 click.echo(f\u0026quot;Hello {name}!\u0026quot;)，这是官方为了兼容不同 python版本而内置的一个函数，实现print功能。\n多个Command 实际使用的时候肯定会添加多个命令。因此，click引入了group功能，意思就是把多个命令组织起来。\nExample Code\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 @click.group() def cli(): pass @cli.command() def initdb(): click.echo(\u0026#39;Initialized the database\u0026#39;) @cli.command() def dropdb(): click.echo(\u0026#39;Dropped the database\u0026#39;) cli.add_command(initdb) cli.add_command(dropdb) @click.group() 注册一个空载函数cli.\n定义command之后，调用函数cli.add_command(initdb) 把命令加进组里。\n程序入口 1 2 if __name__ == \u0026#39;__main__\u0026#39;: cli() 非常简洁，只需要把定义的group或者command运行起来就可以了。\n与setuptools的绑定 使用Python开发命令行工具包，最终目的都是完成分发。click的作者非常贴心考虑到这一点，并且提供了方便的融入setuptools的方法\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from setuptools import setup setup( name=\u0026#39;yourscript\u0026#39;, version=\u0026#39;0.1.0\u0026#39;, py_modules=[\u0026#39;yourscript\u0026#39;], install_requires=[ \u0026#39;Click\u0026#39;, ], entry_points={ \u0026#39;console_scripts\u0026#39;: [ \u0026#39;yourscript = yourscript:cli\u0026#39;, ], }, ) 将yourscript.py 以及 setup.py 放在同一个目录下。关键就在于\n1 2 3 4 5 entry_points={ \u0026#39;console_scripts\u0026#39;: [ \u0026#39;yourscript = yourscript:cli\u0026#39;, ], }, 这部分，console_scripts 字段每一行是一个控制台命令，等号左边是命令的名字也就是要在终端输入的部分，等号右边分两段，冒号左边的是文件的名字，右边是cli 函数的名字\n文档原文\n","date":"2021-08-15T17:18:45Z","permalink":"/posts/click01/","title":"click——Python 命令行开发工具包"},{"content":"本篇汇总知识图谱补全任务（知识图谱链接预测任务）中的损失函数。因为链接预测任务是一个排序任务，因此loss的选择会影响最后排序的规则。\nMargin-based Loss 最早由TransE模型提出，之后绝大部分链接预测模型都采用这种损失函数。\n基本形式\n$$ \\mathcal{L} = \\sum_{(h,r,t) \\in \\mathcal{G}} \\sum_{(h,r,t') \\in \\mathcal{G}^{'}} max(0, \\gamma - (score_{(h,r,t)}-score_{(h,r,t')})) $$公式中，$\\gamma$ 为超参数，也就是margin。$\\mathcal{G}$ 为正样本， $\\mathcal{G}^{\u0026rsquo;}$为负采样。在实际应用中，可以采取替换尾部实体的方法实现负采样。\n在Coding方面，max函数可以使用ReLU激活函数实现。比较简洁。\n1 2 3 def margin_loss(margin, pos, neg): margins = pos - neg return torch.relu(margin - margins).mean() Softplus Loss 在ComplEx文章中提到，Margin Loss容易出现过拟合的问题，因此，引入了likelihood loss，由于实际使用softplus函数，这里把它称为softplus loss\n基本形式\n$$ \\mathcal{L} = \\sum_{r(s,o)\\in \\Omega} \\log (1+exp(-\\textbf{Y}_{sro}\\phi (s,r,o;\\Theta))) + \\lambda \\Vert \\Theta \\Vert ^2_2 $$$\\textbf{Y}_{sro}$ 为三元组的标签，一般来说1代表正样本，-1代表负样本。\n这种loss在ComplEx、SimplE中使用，使用卷积网络的模型ConvKB也使用了这种风格的loss\n代码实现（修改自ConvKB提供的版本）\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class ComplExLoss(nn.Module): \u0026#34;\u0026#34;\u0026#34;Loss from ComplEx\u0026#34;\u0026#34;\u0026#34; def __init__(self, lmd): super(ComplExLoss, self).__init__() self.softplus = nn.Softplus() self.lamada = lmd def forward(self, scores, labels, regul): \u0026#34;\u0026#34;\u0026#34; :param scores: scores output :param labels: 1 for positive sample and -1 for negative samples :param regul: weight vector :return: \u0026#34;\u0026#34;\u0026#34; return torch.mean(self.softplus(scores * labels)) + self.lamada * regul 当中一个容易出bug的地方就是，scores的符号问题。ConvKB在计算score的时候，线性层输出前面加了负号作为最终输出。\nBCE / CrossEntropy Loss 这种loss将链接预测问题建模为二分类问题，并且通常使用softmax实现稳定训练。\n在ConvE和KG-BERT中有使用。\n","date":"2021-08-11T16:39:43Z","permalink":"/posts/kgc-loss/","title":"知识图谱补全任务中的Loss函数"},{"content":"来源：ACL 2021 领域：KGQA 核心模型：TComplEx / TNTComplEx，BERT 🔗论文链接：arXiv 🔗代码：GitHub 文献依赖： - Improving Multi-hop Question Answering over Knowledge Graphs using Knowledge Base Embeddings\n关注问题 基于时序知识图谱的QA目前还没受到足够的关注，没有完善的数据集，没有对应的很好的模型。本文针对这个问题提出了相应的方案，包括构造了时序KGQA数据集CRON QUESTIONS，和提出一个针对时序KG数据的KGQA模型——CRONKGQA\n时序KG的特点 文章总结了时序KGQA（Temporal KGQA）必须具备的几个特点\nKG必须是一个时间相关的知识图谱，也就意味着最小组成单元为四元组$(h, r, t, \\tau)$ Answer 可以为实体或者一个时间间隔（这一点和普通QA区别非常重要） 也是因为第二点，需要复杂的基于时间的推理 数据集 基于上述分析的特点，本篇工作构建了新的数据集CRON QUESTIONS。时序KG数据来源于Wikidata，时间精确到年份。经过筛选之后，时序KG包括328k个实例，其中5k为事件性质的数据。\nQuestion的数量：训练集350000，开发集30000，测试集30000\n作者在文中对比了提出的数据集与现有的数据集的特性对比，论证了CRON QUESTIONS是包含时序facts，问题覆盖多实体多关系和时序信息（复杂推理模型可用性需要）\n模型 模型是基于EmbedKGQA进行两部分的修改\n把KGE的Model从ComplEx更换为TComplEx实现对时间戳信息的嵌入表示 使用BERT对question进行编码过程，EmbedKGQA只生成了实体表示，CRONKGQA生成了问题的实体编码和时间编码（因为答案可能是时间段），concatenate之后经过Softmax得到最后的答案（评分）。 ","date":"2021-08-10T12:04:07Z","permalink":"/posts/cronkgqa/","title":"Question Answering Over Temporal Knowledge Graphs"},{"content":"来源：ACL 2020 领域：KGQA 模型：EmbedKGQA 核心模型：ComplEx；BERT/RoBERTa 代码：GitHub\n关注问题 多跳KGQA中，KG的不完整性带来独特的挑战。现有解决方案中包括使用文本，但文本不一定存在；KGE可以解决补全问题，但还没有研究将两者结合在一起，本文是这方面的一次尝试。\nModel 模型分为三个核心模块：KG Embedding Module，Question Embedding Module，Answer Selection Module。\nKG Embedding Module：和现有KGE模型的目的一致，生成实体的表示向量。本文使用ComplEx作为KGE模型。 Question Embedding Module：生成问题的表示。使用预训练模型RoBERTa生成question的表示向量，然后经过4层全连接网络生成实数部分和虚数部分的表示向量$e_q$（用于后续使用ComplEx进行评分） Answer Selection Module 答案选择器用于生成最后的实体选择。首先，使用ComplEx的打分函数生成分数$\\phi (e_h, e_q, e_{a\u0026rsquo;})$，这种方式在候选实体很少的时候是适用的，但有些KG候选实体非常多。因此，引入pruning机制进行实体筛选打分。\n$$ h_q = \\text{RoBERTa}(q') \\\\ S(r, q) = \\text{sigmoid}(h_q^Th_r) $$筛选$S(r, q)$ 大于0.5的关系集合$\\mathcal{R}_a$, 以及最短路径关系集合$\\mathcal{R}_{a\u0026rsquo;}$, 得到一个关系分数\n$$\\text{RelScore}_{a'}=\\vert \\mathcal{R}_a \\cap \\mathcal{R}_{a'} \\vert$$最终分数$e_{ans} = \\text{argmax}(\\phi (e_h, e_q, e_{a\u0026rsquo;})) + \\gamma * \\text{RelScore}_{a\u0026rsquo;}$\n延伸 写作的时候遇到行内数学公式渲染错误的问题，Follow了这个Post的解决方案。\n","date":"2021-08-09T16:29:12Z","permalink":"/posts/embedkgqa/","title":"论文阅读：Improving Multi-hop Question Answering over Knowledge Graphs using Knowledge Base Embeddings"},{"content":"关注问题：现有的少样本知识图谱补全技术主要针对静态知识图谱，事实上KG中的Fact会根据时间而变化，时序图谱数据面临更严重的数据稀疏问题。本文针对这个问题提出One-Shot场景提出模型。 来源：AAAI 2021\n问题定义 本文针对One-Shot的时序动态知识图谱补全任务。所谓One-Shot是指，对一个关系只能看到一个标注数据（支持集）。因为本文关注时序的知识图谱补全，引起作者引入了时间信息的限制，体现在以下方面：\n支持集（Support Set）与查询集（Query Set）的划分：查询集的时间窗口与支持集的时间窗口是不交叉的，查询集的四元组(h, r, t, time) 对应的时间戳都在支持集的时间戳之后。 训练、测试与验证集划分：时间顺序一次为 训练集\u0026lt;验证集\u0026lt;测试集 模型 模型分为两部分：实体编码器（Entity Encoder）和打分网络。实体编码器的目的是学习时间相关的（time-aware）的实体表示，这是模型的最核心部分。相比之下，打分网络与已有的少样本静态图谱的补全模型相比有点平平无奇。\n实体编码器 实体编码器分为两部分，第一部分，对某个时间戳（time snapshot）的静态图结构的编码。对特定时间戳的图结构，将实体的邻居的关系与尾实体表示向量concatenate之后，经过单层全连接网络之后求和得到： $$f(N_r(e)) = \\sigma (\\frac{1}{C_{e_r}}\\sum_{r} \\sum_{e_j}(W^T[v_r;v_{e_j}]+b))$$将邻居信息与当前表示cancat作为节点表示$x_r^e = [f(N_r(e));v_e]$。\n第二部分，是时间相关的聚合网络。本篇工作使用Transformer对多个时间戳的表示进行聚合表示。首先将多个时间戳的表示建模为序列$x={x^e_{t-l},\\dots, x^e_{t-1}}$，Transformer输出表示$z=Att(x, Heads, Layers)$\n将最后一层的各个时间戳状态$z_{t}$ concatenate 之后经过FCN得到最终时间相关的表示\n$$h_e = \\sigma ([z_{t-l}, \\dots, z_{t-1}]W^{*})$$实体对表示 对实体对$(h, t)$，使用静态表示和entity encoder学习得到的表示concat在一起$s=[h_{s_0}, v_{s_0}, h_{o_0}, v_{o_0}]$，经过两层全连接网络和残差连接得到实体对表示。\n$$ x^{(1)}=\\sigma (W^{(1)}x+b^{(1)}) \\\\ x^{(2)}=\\sigma (W^{(2)}x^{(1)}+b^{(2)}) \\\\ \\mathcal{M}(x)=x^{(2)}+x $$打分 打分函数简单到难以置信，把支持集的表示和查询实体对的表示进行点积得到最后的分数。在实验中，作者对比了dot product和GMatching使用的LSTM网络，发现直接dp已经可以获得足够好的效果。\n","date":"2021-08-07T11:25:50Z","permalink":"/posts/tokgc/","title":"论文阅读：One-shot Learning for Temporal Knowledge Graphs"},{"content":"来源：AAAI 2020 代码：GitHub 关注问题：零样本关系的知识图谱补全\n在知识图谱补全问题中，已有研究主要放在大样本或者少样本场景，很少关注零样本关系的补全。所谓零样本是指在已有KG中完全没有这个关系相关的三元组信息。\n大样本场景中，每个关系都有充分的三元组数据用于训练，在测试的时候，所有关系都是在训练过程中可以看到的；一样本/少样本场景设定中，测试阶段的关系在训练过程中不可见，只提供了K个实例作为支持集，已有模型可以从K个已知实例中挖掘信息学习关系表示。\n零样本关系表示学习和对应的补全问题显然是一个更具挑战性的场景，核心问题就是如何去表示零样本关系。\n本文作者创造性采用对抗训练手段试图从关系描述的文本中获得良好的关系表示。\n对于背景图关系的三元组，随机采样作为真实样本实例，使用Neighbor Encoder增强表示之后，获取质心信息作为真实关系表示。\n对于关系本身，每个关系提供了一定长度的文本信息，使用TF-IDF从文本中获取向量表示，加入随机生成的高斯噪声向量之后，经过Generator生成Fake的关系表示。这个生成的关系表示和上述经过实体对生成的关系表示一起送入Discriminator网络，产生判别结果。\n模型训练 生成器训练：生成器的Loss分三部分，第一部分是Wasserstein Loss，第二部分为分类Loss，因为前面质心作为真实的关系表示，那么就可以用margin ranking loss来训练表示过程。除此之外，还包括visual pivot regularization，目标是提供足够的跨class判别能力。\n判别器训练：判别器的目标是区分生成的关系向量和真实的关系向量，Loss组成包括Wasserstein Loss、分类器Loss和对输入进行二分类（用于判别是否为真实数据）的Loss以及Gradient Penalty Loss。\n实验 针对这个问题提出了两个数据集ZS-NELL和ZS-Wiki，并在此基础上开展实验。所提出的方法是和已有的一些embedding方法兼容的（在Model一开始提到有预训练阶段获取实体的表示，应用主流的embedding方法获取）。一个值得注意的实验结果是，作者把word2vec替换为BERT，反而出现的性能下降的情况。\n","date":"2021-08-02T16:32:02Z","permalink":"/posts/zskgc/","title":"论文阅读：Generative Adversarial Zero-Shot Relational Learning for Knowledge Graphs"},{"content":"出处：ICLR 2021 代码链接：https://github.com/guolinke/TUPE\n本文讨论了Transformer结构的位置编码（Position Encoding）问题。\n动机 作者首先指出，现有的Transformer模型在处理位置编码的时候，通常将word embedding与位置编码信息直接相加，然后送入Multi-Head Attention结构中。这种做法让两种信息相互干扰，引入噪声信号。\n其次，在序列分类任务中，会在句首附加 [CLS] 用于捕捉全局信息并将其用于分类。然而，将[CLS]直接放在句首的做法无法真正捕捉到全局的信息，因为作者认为，[CLS]是和普通词汇完全不一样的符号，如果[CLS]享有和其他词汇一样的位置编码信息，则难以满足出发点（捕获全局信息）。\n方法 由这两个出发点，本文做了两点针对性的贡献。\n第一，将位置编码与词汇编码分离，单独训练Query矩阵和Key矩阵进行映射和乘积，然后加到word 的Q、K相乘之后的结果中（可以认为是一个偏置项）。\n第二，全局化[CLS]的贡献，在其他模型的设计中，[CLS] 到其他 token 的权重以及其他token到 [CLS] 的权重有区别的，本文使用了统一权重。也就意味着，[CLS] 到其他 token 的权重是同一个参数$\\theta_1$，其他token到 [CLS] 的权重是同一个参数$\\theta_2$。\n代码实现 这篇文章的代码已经开源，两个方法在实现细节上代码如下\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 seq_len = x.size(0) # 0 is for other-to-cls 1 is for cls-to-other # Assume the input is ordered. If your input token is permuted, you may need to update this accordingly weight = self.pos_ln(self.pos.weight[:seq_len + 1, :]) pos_q = self.pos_q_linear(weight).view(seq_len + 1, self.num_attention_heads, -1).transpose(0, 1) * self.pos_scaling pos_k = self.pos_k_linear(weight).view(seq_len + 1, self.num_attention_heads, -1).transpose(0, 1) abs_pos_bias = torch.bmm(pos_q, pos_k.transpose(1, 2)) # p_0 \\dot p_0 is cls to others cls_2_other = abs_pos_bias[:, 0, 0] # p_1 \\dot p_1 is others to [CLS] other_2_cls = abs_pos_bias[:, 1, 1] # offset abs_pos_bias = abs_pos_bias[:, 1:, 1:] abs_pos_bias[:, :, 0] = other_2_cls.view(-1, 1) abs_pos_bias[:, 0, :] = cls_2_other.view(-1, 1) 首先是位置编码在取的时候去了L + 1长度，在乘法出来之后变成 $L+1 \\times L+1$ 的矩阵abs_pos_bias，刚好比常规的$L \\times L$多了四个元素，[0][0]和[1][1]分别用来表示[CLS] to others 和others to [CLS]。\n把原来的矩阵切割掉第一行第一列然后填充回去。\n","date":"2021-07-26T22:24:58Z","permalink":"/posts/tupe/","title":"论文阅读：Rethinking Positional Encoding In Language Pre-Training"},{"content":"最近要用Transformer相关的模型，刚好李宏毅的机器学习课更新了相关内容，就看了一下，整理一下笔记草稿。\nEncoder Decoder 结构 首先是Encoder-Decoder结构。Encoder结构将输入数据编码得到向量表示，具体结构在对应的上一节课已经有说了，大体来说就是Self-Attention + 多头。输出可以是和输入相同shape的向量表示或者是一个向量表示，取决于任务场景。\nDecoder Decoder的作用是根据encoder结果生成信息。Decoder输入的第一个符号肯定是\u0026lt;BOS\u0026gt; Begin of Sentence，告诉Deocder需要进行内容生成了。最传统的Decoder会将当前时刻以前所有的生成数据当作当前时刻输入数据。\nCross Attention Decoder的内部结构和Encoder差别在于Cross Attention机制。所谓的Cross Attention就是Decoder输出与Encoder的最后一层的输出之间的交互。以Decoder的输出作为query，Encoder的输出作为key 和 Value，同样是应用注意力机制。\n输出 Decoder产生的向量接Softmax得到最后的输出。也就是说把生成问题建模成了分类问题，以翻译来举例就是当前时刻是某一个char或者word的概率是多少。类别总数就是词表的大小。\nWHEN STOP 因为生成的内容长度不是固定的，所以机器需要知道什么时候才能停止。引入特殊符号\u0026lt;EOS\u0026gt; end of sentence 作为句子的结束。如果句子碰到这个符号，那就不要在继续生成新的内容了。\nTeacher Force Decoder ","date":"2021-04-15T15:34:45Z","permalink":"/posts/decoder/","title":"【李宏毅机器学习课程笔记】Transformer Decoder"},{"content":"本文发表于AAAI 2021，关注了知识图谱补全任务的少样本场景问题。\n核心问题 目前大规模知识图谱普遍存在不完整的问题，需要使用链接预测技术对知识图谱进行补全。而现有的链接预测模型普遍存在两个问题：\n难以应对关系的不平衡性。所谓不平衡，是指不同关系的训练样本数据量级差别 ","date":"2021-03-01T14:31:38Z","permalink":"/posts/grl/","title":"【论文阅读】Generalized Relation Learning with Semantic Correlation Awareness for Link Prediction"},{"content":"本周恢复读论文。带来两篇关于知识图谱嵌入和多跳推理的论文。\nCoKE: Contextualized Knowledge Graph Embedding 这篇是关于知识图谱动态嵌入的研究。作者认为之前的知识图谱嵌入方法都是学习静态的知识，就算是图卷积方法也是在做静态嵌入的事情，没有真正学习到上下文知识。所以作者提出了使用Transformer来编码知识图谱的动态嵌入。\nTransformer是对序列进行表示学习，所以很自然就引申出一个问题：序列从哪里来？\n文章提出了两种序列\n三元组 $(h, r, t)$ 作为长度为3的输入序列。 图路径 $(e_1, r_1, \\cdots, e_t)$ 作为长度为K的输入序列。 如何训练？\n为了模拟链接预测任务和Query任务，在输入的时候将序列最后一个元素设定为MASK，迫使Transformer学会对该元素进行预测。\nGMH: A General Multi-hop Reasoning Model for KG Completion ","date":"2021-01-16T11:07:39Z","permalink":"/posts/week02-2021/","title":"week02_2021"},{"content":"本周读进去的论文有点少，哭。\nInteractE: Improving Convolution-based Knowledge Graph Embeddings by Increasing Feature Interactions 关注问题 ConvE虽然提出了将卷积神经网络应用到知识图谱补全任务，但没有很好的学习头节点和关系之间的交互（Interactions）。本文旨在提高 “the number of interactions”\n方法论 文章分析了交互的数量和链接预测性能之间的关系，定义了同构和异构两种Interaction的种类，并且基于分析，提出了使用循环卷积以及ALternate和Chequer两种打乱元素堆叠方法的方式，改进the number of interactions。\nInductive Entity Representations from Text via Link Prediction 本文讨论了BERT在链接预测领域的应用，主要针对动态图谱，也就是在测试场景中出现的新的实体。\n为了表述这种动态性，数据集设计成，所有关系在训练集中都有出现，有一部分实体只在测试阶段出现。 这个场景下，传统的基于知识图谱结构的模型是没办法应对的，因此，本文提出了使用BERT对实体描述进行编码处理，然后使用常见的链接预测模型进行打分和预测。 值得注意的是，因为测试集中的关系都是在训练集中出现过的，因此并没有考虑关系的文本描述。\nImproving Relation Extraction by Leveraging Knowledge Graph Link Prediction 发表在NIPS 2020 Workshop的工作，讨论了将知识图谱链接预测作为辅助任务增强关系抽取任务的性能。将模型的Loss分为了三部分，关系抽取的Loss，链接预测任务的Loss，和Coupling Loss（耦合Loss）\n注意到，链接预测部分模型只参与模型训练，不参与模型测试。\n","date":"2020-12-26T16:50:04Z","permalink":"/posts/week52-2020/","title":"Week 52 论文阅读"},{"content":"总结一周阅读的论文。\nComplex Relation Extraction: Challenges and Opportunities 关系抽取领域的一篇短综述，只有六页，讲述目前关系抽取的进展和挑战。 文中首先定义了什么是简单关系和复杂关系。简单关系抽取，Binary Relation Extraction，最简单的关系抽取的范式，一句话中包含两个实体以及两个实体之间的关系。 复杂关系抽取，就是超出了这个定义的一些情况，包括：文档关系抽取，小样本关系抽取，跨语言关系抽取，跨模态关系抽取，N-ary（N元/N实体）关系抽取，多粒度关系抽取，条件关系抽取和嵌入关系抽取。 针对每个scenario，分析了问题定义和挑战。\nRelational Graph Neural Network with Hierarchical Attention for Knowledge Graph Completion 发表于AAAI 2020的一篇文章。关注知识图谱补全与链接预测领域。 提出了结构化注意力机制来增强链接预测的性能。 所谓结构化，针对$(h, r, ?)$尾实体包含多个实体的时候，可以把原始的图转化为树状结构，把关系作为中间节点。 在转化之后的图结构，首先学习关系权重，头实体和关系连接，学习注意力权重。 之后学习同一个关系的所有尾部实体的权重表示。\n信息融合：学习的邻居节点信息和节点自身的表示进行融合，文中提出了三种融合的手段，包括Multiplicative Combination, Bi-interaction combination and simple additive combination. 同时为了更好的融合，使用多头注意力机制。 Predict：打分预测阶段，使用ConvE的打分函数。 $$f(h,r,t)=\\text{ReLU}(\\text{vec}(\\text{ReLU}([h;t]*\\omega))Q)t$$One-Shot Learning for Temporal Knowledge Graphs 时序知识图谱链接预测的一篇文章，被AAAI 2021收录。 时序图预测最近有点火，但之前还没有接触过这个领域，看得比较粗。 印象最深刻的就是根据时间跨度去构建support和query。选取过去某个时刻的三元组作为one-shot支持集，然后从那个时刻开始未来的$limit$时间跨度内的数据作为查询集。 第一次接触这个类型的数据范式，想找代码看一看，无奈代码没开放，给的链接点进去并没有内容。 标注为Follow。\nContext-Enhanced Entity and Relation Embedding for Knowledge Graph Completion (Student Abstract) AAAI 2021 收录的一篇poster，关注知识图谱补全问题。\n动机：以往的知识图谱补全模型虽然有考虑实体或者关系的上下文信息，但没有同时考虑，因此在本文中进行了尝试。\n实体上下文就是以该实体为头节点或者尾节点的三元组\n关系上下文就是该关系的头实体和尾部实体。\n打分函数使用DistMult同款\n$$s_{i,j}=e_i^T diag\\{r\\} e_j$$Reference Complex Relation Extraction: Challenges and Opportunities Relational Graph Neural Network with Hierarchical Attention for Knowledge Graph Completion One-Shot Learning for Temporal Knowledge Graphs Context-Enhanced Entity and Relation Embedding for Knowledge Graph Completion (Student Abstract) ","date":"2020-12-19T14:51:30Z","permalink":"/posts/paper-wk52-2020/","title":"Week 51, 2020 论文阅读总结"},{"content":"There’re many articles about how to build a python lib, but fewer articles discuss about how to build a command line app using python. This article is to solve the problem.\nPrepare First, we need some knowledge to help us build a command line tool fluently: How app like pip execute when we tap pip in terminal?\nExecutable file like pip and other command line scripts are locate in /Users/{UserName}/miniconda3/envs/{envName}/bin/pip (My os is macOS Catalina and I use mini-conda for Python env management, in other os, you can type which pip for more information)\nWhen we tap pip install {an exec package}, pip will copy scripts assigned in setup.py to the bin dir in your PATH like /Users/{UserName}/miniconda3/envs/{envName}/bin.\nAfter that, every time you tap{an exec package}, system will execute script above.\nSo, the core problems are:\nWhat we need to put in the script file in bin directory? How can we tell the pip to put our executable scripts to bin folder correctly ? What we need to put in the script file in bin directory? The answer is very simple, we just need to open a script like pip in the conda/env/{env}/bin.\nWe can see code like\n1 2 3 4 5 6 7 8 9 10 11 #!/Users/{userName}/miniconda3/envs/py38/bin/python # -*- coding: utf-8 -*- import re import sys from pip._internal.cli.main import main if __name__ == \u0026#39;__main__\u0026#39;: sys.argv[0] = re.sub(r\u0026#39;(-script\\.pyw?|\\.exe)?$\u0026#39;, \u0026#39;\u0026#39;, sys.argv[0]) sys.exit(main()) The first line is expand dynamically when install the package, which we will discuss next. Line from pip._internal.cli.main import main is to import function from your Real package (Code in {ProjectName}/), it just like a bridge between your script and lib code in {pkg}/.\nAbout sys.exit(main()), you can see StackOverflow Post for more detail.\nSo, the script file we need to put is like\n1 2 3 4 5 6 7 8 9 #!python import re import sys # import your code functions and main functions if __name__ == \u0026#39;__main__\u0026#39;: sys.argv[0] = re.sub(r\u0026#39;(-script\\.pyw?|\\.exe)?$\u0026#39;, \u0026#39;\u0026#39;, sys.argv[0]) sys.exit(main()) # or other function name, anyway Notice: The first line is changed to #!python because when installing, pip will dynamically change to the right python location according to the env.\nHow can we tell the pip to put our executable scripts to bin folder correctly ? In setup.py, there is a keyword argument scripts=['bin/{scripts name}'], the value is a list type value, we can to put any scripts to the array.\nSimply, we just need to put scripts below in the bin directory.\nThe project file tree is like\nOf course, you can rename you script folder to any name you want, as long as you write the right name in scripts=['bin/{scripts name}'] in setup.py.The screenshot is to help understand the whole project file structure.\nConclusion In this article, we discuss the very first two steps to build a command line app using Python.\nReference Build Your First pip Package ","date":"2020-07-05T17:03:34Z","permalink":"/posts/d3da32/","title":"Write your first command line app using Python"},{"content":"【Code】初探Python类型标注 干点轻松的事情，看了一下Python官方文档里面关于类型标注的知识，怎么说，Python官方对于类型检查这个问题的认识，远远比那些Python吹成熟多了（好像说了一句废话，其实想表达的是按照现在Python的发展程度，过于灵活的类型系统早就成了发展的桎梏，官方的态度也表明了这一点，只是还有很多人没有意识到，还沉浸在不用写类型的快感中而已）。作为一个Python黑，对于Python建立健全的类型检查机制这件事上，我是举双手双脚赞成的。\n从最基本出发 先来看看最基本的类型检查 def foo(x: int, y: int) -\u0026gt; str: return f\u0026quot;{(x+y)=}\u0026quot; 限定了foo的参数类型必须为int，返回值为字符串类型。\n在实际运行中，如果非要传入float或者其他实现了加法运算的类型，代码是可以正常运行的。只不过在PyCharm中，代码编写过程会提示参数错误的Warning。Mypy里面也会有提示，但我没有测试。\n也就是说，类型检查目前（Python3.8版本）也只是起到了道德约束的作用。\nTuple 这时候很容易会想到，如果我函数返回多个值，能不能用类型检查呢？\n当然可以，只需要引入Tuple类型\n1 2 3 4 from typing import Tuple def foo(x: int, y: int) -\u0026gt; Tuple[int, int]: return 2x, 2y Tuple[int, int]表示返回值是长度为2的元组类型，第一个元素和第二个元素都是int类型。\n返回同类型多值的时候，比如Tuple[int, int, int]可以简写成Tuple[int, ...]\n如果返回值类型不同，只需要写成Tuple[str, int, dict, float]这样，表示返回四个参数，类型分别是字符串，整数，字典和浮点数。\nUnion 这时候问题又来了，“有些时候我真的不确定能返回啥的嘛”\n可能返回两种类型的时候（比如做JSON解析，最外层可以是dict或者Array），需要引入Union关键字\n1 2 3 4 from typing import Union def foo(filename: str) -\u0026gt; Union[dict, list]: return json.load(open(filename)) Union[dict, list]即表示返回类型是字典或者列表类型（只能二选一）。\nOptional 这时候又有钢筋拍案而起了，“我特么就喜欢给你返回None，空数据我就给你None，传入可以None，返回可以None，就看你咋整”\n当然可以整，只需要引入Optional关键字\n1 2 3 4 5 6 7 from typing import Optional def foo(x: Optional[int]) -\u0026gt; Optional[str]: if x is None: return None else: return f\u0026#34;{x=}\u0026#34; Optional[str]等价于Union[str, None]，表示返回值为str类型或者None\nLiteral：不能枚举之痛 Python的枚举类型，用起来是很麻烦的，每次写Python的时候我都很怀念Swift的view.setColor(.red)之类的写法，省去了很多麻烦。\n在Python里面很多喜欢用字符串来表示特定一些范围的东西，比如训练时候选用优化器，有把\u0026quot;Adam\u0026quot;或者\u0026quot;SGD\u0026quot;直接传入函数的操作。\n问题就来了，字符串灵活性太高了，不开着官方文档根本不知道有哪些可以传入，有时候一个typo就会导致debug半天。\nLiteral就是为解决这个问题而来的\n1 2 3 4 5 6 7 8 9 def validate_simple(data: Any) -\u0026gt; Literal[True]: # always returns True ... MODE = Literal[\u0026#39;r\u0026#39;, \u0026#39;rb\u0026#39;, \u0026#39;w\u0026#39;, \u0026#39;wb\u0026#39;] def open_helper(file: str, mode: MODE) -\u0026gt; str: ... open_helper(\u0026#39;/some/path\u0026#39;, \u0026#39;r\u0026#39;) # Passes type check open_helper(\u0026#39;/other/path\u0026#39;, \u0026#39;typo\u0026#39;) # Error in type checker 它限定了参数只能是特定的范围中选择。完美解决上面的痛苦\n啊不它根本不完美，我还是更喜欢Swift的枚举推断。\nMore Fun 仔细挖掘还可以看到有趣的东西。\n函数名类型：Callable[[int], str]表示(int)-\u0026gt;str的函数。第一个位置为传入参数的类型和数值，第二个位置表示返回值类型。\n不可变常量：Python也可以有Final。（Python3.8+）\n1 2 3 4 5 6 7 8 MAX_SIZE: Final = 9000 MAX_SIZE += 1 # Error reported by type checker class Connection: TIMEOUT: Final[int] = 10 class FastConnector(Connection): TIMEOUT = 1 # Error reported by type checker 类变量：静态语言中static干的事情，也被搬到了Python上面\n1 2 3 class Starship: stats: ClassVar[Dict[str, int]] = {} # class variable damage: int = 10 # instance variable 调用的时候只能使用Starship.stats\nBeyond 官方文档还有很多很有意思的，比如范型和函数重载等等，改天看完敲完代码再更新。从现在开始我写的每一行Python代码都会全力使用上面说到的所有新特性。\nPython曾经困扰过我很长很长一段时间，从大三（2015）就开始学的语言，其实直到去年年初我还觉得我没有入门（可以翻历史记录，去年七月的时候还有对Python的吐槽），当中各种痛苦与挣扎，甚至有那么一瞬间会觉得如果博士不能毕业的话，Python肯定是最大的元凶，因为用它真的太痛苦了。直到后面追上新文档的改变之后才感觉打开新世界的大门，现在越用越顺手，甚至开始把平常常用的代码段整理成库发布到PyPI上面。\n很庆幸当初没有因为厌恶和憎恨而逃避它，而是选择去了解它。\n与此同时也庆幸是Python变成了我想要的样子，而不是我改变自己去适应以前的Python。\n","date":"2020-07-03T10:08:27Z","permalink":"/posts/28507f36/","title":"探索Python的类型标注支持"},{"content":"JSON 是非常常用的数据编码格式，在swift4当中引入了Codable协议，使得JSON数据的处理变得前所未有的方便。然而当涉及继承的时候，还是踩了不少的雷区。所以想写个文章分享一下。\n基本操作 直接将 class 或者 struct 转 JSON 的话只需要继承 Codable 接口，然而如果想自定义编码之后在 JSON 中的键的名字的话（比如类成员 name 我想编码成 Student_Name），就需要引入 CondingKeys 这么一个枚举的内嵌类型。\n1 2 3 enum CodingKeys: String,CodingKey{ case name=\u0026#34;Student_Name\u0026#34; } 到这里还是十分方便的，然而，在代码设计中如果引入了继承，问题就来了。\n问题 假设这么一个场景，手头上有一堆不同类型的传感器，每个传感器定期汇报自己收集的数据和所有的地理位置信息，那么这些传感器的 Model 中，都会有经度、纬度、名字，Id 这些共同的成员，也会有每个传感器自己特定的数据类型。 自然的，我们就想到了把经纬度信息抽象成landmark父类\n1 2 3 4 class landmark: Codable{ var latitude: String? var longitue: String? } 后端传输信息的时候可能想偷懒一波，把 latitude 缩写成了 lat，把 longitude 缩写成了 lon，这对于任何一个有代码洁癖的人来说显然是无法忍受的，我还想在自己的代码里面用完成的名字，于是我们想到了CodingKeys，来给经纬度父类起个别名吧！\n1 2 3 4 5 6 7 8 class Landmark: Codable{ var latitude: String? var longitue: String? enum CodingKeys: String,CodingKey{ case longitude = \u0026#34;lon\u0026#34; case letitude = \u0026#34;lat\u0026#34; } } 这样我们就可以在自己的代码里面继续使用全名而不是别扭的缩写。 这时候我们开始对传感器建模了，比如有个广告牌传感器，\n1 2 3 4 class Billboard: Landmark{ var addedDate: String? var logo: String? } 这里出现了第一个容易被初学者忽略的地方，就是如果继承的父类已经继承了 Codable 协议，那么在设计子类的时候就不需要重复声明。 然后，根据CodingKeys的要求，进行decode的时候只会对enum中声明的字段进行编码和解码，所以这时候把广告牌数据解码到 Billboard 实例中会发现广告牌特有的两个字段都是nil 那么就需要为广告牌添加CodingKeys\n1 2 3 4 5 6 7 8 class Billboard: Landmark{ var addedDate: String? var logo: String? enum CodingKeys: String,CodingKey{ case addedDate case logo } } 然后编译，运行，发现想获取logo信息的话还是返回nil，只有经纬度字段正确的被解码了。\n解决方案 参考链接 Encoding and Decoding Custom Types | Apple Developer Documentation\n","date":"2019-02-18T19:29:49Z","permalink":"/posts/swiftjson/","title":"iOS 开发笔记（二）—— 继承类的JSON处理"},{"content":"转博了之后平时的工作重心从写代码慢慢的转移到科研读论文复现论文等上面来。选研究方向的时候也费了好大一番功夫去纠结，一开始想继续做微服务和SDN的东西，后面却逐渐发现往深入去做的话又兜兜转转回到了通信的轨道上了。后面终于下定决心做知识图谱的东西。整个十一月都在看知识图谱的论文，主要集中在知识表示领域的几种Translation模型上。\n什么是Translation？ 知识图谱是一种偏向于人类认知的“数据结构”，将实体之间用关系的方式进行链接，比如说“中国的首都是北京“这句话，用知识图谱进行建模的方式就是，“中国”（实体）的首都是（“关系表示”）“北京”（另一个实体）。\n这样，一条知识就可以用图这个数据结构的一条边（edge）来表示。参考图的关系描述方式，知识图谱中可以使用 $h,r,t$ 三元组来表示一条“知识”，$h$ 是head，头部知识，知识的端点，在上面的例子中，为“中国”，$r$是relation，表示关系的意思，$t$是tail，在上面的例子中，为“北京”。\n知识终究是要喂给计算机进行处理的，计算机只能处理数据信息。知识图谱上面的东西都是离散的，符合人类认知逻辑的，和计算机处理的数据没有半毛钱的关系。因此，就有了知识表示的说法。怎么把图结构表示成计算机可以处理的数据的形式。\nTranslation，是对知识表示的一种“形容”，目标是将三元组映射到计算机可以处理的连续数值向量空间中，当然了，最好这个空间的维度可以低一些。\nTranslation Embedding（TransE） TransE，是最早的研究用嵌入（embed）方法来进行知识表示的一篇论文。基本思想就是最直观的将实体关系数据嵌入到低维度向量空间中。\n关于TransE，网路上有很多讨论的文章（毕竟出来最早），讨论的内容都大同小异，可以参考TransE算法的理解 | Yaoleo\nTransE所完成的，是最基本的知识嵌入，忽略了知识中普遍存在的一对多、多对一、自反关系的问题，自反关系中，$r$会被建模成$0$向量；多对一的关系中，因为它的Scoring Function设定为 $\\Vert h-r+t \\Vert$， 假设$h_0,\u0026hellip;,h_m$ 均与$t$形成关系$r$，那么$h_0,\u0026hellip;,h_m$的向量表示是相等的。一对多的关系同理。\n因此，有了TransH。\nTranslation Hyperplane 这个方法是紧跟随TransE提出来的。主要有两个改进的地方（对比TransE）\n引入entity的分布式表示。将Relation的表示放在了与entity的不同空间当中（也是Hyperplane的由来）。两个空间（relation和entity之间）的向量表示是一个投影关系（projection）\n将现有知识图谱的“未完成”问题考虑进来，负样本的构造过程不再像以往模型那样随机选择，而是基于伯努利分布的一个选择过程。而且对与多对一和一对多时候的“污染源”也是有区别的。\n此外Scoring Function和Loss Function也有改进。\nOpenKE Toolkit 第三篇论文是2018年清华大学发布的，是一个工具包的简介。基于Trans的模型有不下五六种，模型的训练和测试成为了一个难题。而且每提出一个新的算法都要对前面的东西进行重复验证。所以这篇论文中将这几种常见模型的算法集中到了一起，提供统一的接口和数据库，并且保证了向后兼容性。提供了TF和PyTorch的接口，实现GPU训练和CPU多线程并行训练。\n在这篇论文中作者还指出，前面的各种Translation算法，本质上最大的区别就是Scoring Function和Loss Function的区别。\n参考链接 清华大学开源OpenKE：知识表示学习平台 | 机器之心 里面有上面提到的所有论文的链接。\nZhen Wang, J. Z. J. F. Z. C. (2014). Knowledge Graph Embedding by Translating on Hyperplanes, 1–8.\nOpenKE: An Open Toolkit for Knowledge Embedding. (2018). OpenKE: An Open Toolkit for Knowledge Embedding, 1–6.\nTranslating Embeddings for Modeling Multi-relational Data. (2013). Translating Embeddings for Modeling Multi-relational Data, 1–9.\n","date":"2018-11-29T16:48:39Z","permalink":"/posts/papers-nov/","title":"11月论文阅读总结"},{"content":"新款 Mac mini 购买可行性分析（持续更新） 10 月 30 号苹果的新品发布会，我望眼欲穿的Mac mini 终于在时隔四年之后迎来了“大更新”，正如之前很多 KOL 所“预言”的一样，是一次面向专业人士的一次更新，最高支援了 i7 六核处理器和 64GB 内存和 2TB 固态，还有万兆以太网接口可选。昨天也同步更新了大陆的价格信息。网络上对于这款产品的评价也是褒贬不一，到底这款产品是不是值得购买呢。\n处理器 这里只关注 i5 和 i7 版本。\nApple 一般都会和英特尔有定制处理器，根据英特尔® 产品规格，查找 i5 处理器的规格表\n从基准频率，睿频频率和 L3 缓存规格，基本可以确认，Mac mini 使用的 i5 处理器就是这款 8500B，是基于桌面版的 8500 的修改款。 同样可以找到第八代智能英特尔® 酷睿™ i7 处理器 产品规格，可以基本确认 Mac mini 的 i7 处理器是 8700B，是桌面版 8700（不带 k）的修改版（为了适配 Mac mini 的体积而进行散热性能的更改）。 可以看到，对比上一代的 Mac mini 使用的低压移动平台处理器，这次的升级可以说是一脚踩爆了牙膏管。 要注意的是，第八代 i5 和 i7 处理器（上面说的这两款），最大的区别是超线程支持上，也就是说只有 i7 8700B 这款处理器支持超线程，为 6C12T（6 Core 12 Thread）规格。\n内存 内存其实没啥好说的，横跨 8GB 到 64GB。 比较有意思的是，这一代重新采用了 SO-DIMM 的可插拔内存，理论上可以买 8GB 版本回来自己扩充到 32G 内存，还能省 2000 元。具体的内存更换教程和内存条兼容性报告，等发售和测试之后再更新。\n一点吐槽和一点疑问 官网的自定义配置上，从 8GB 内存升格到 32GB 内存居然要加四千多。问题是现在内存价格已经回落了啊！！！ 官网的宣传图片和规格上说是可插拔内存插槽，是不是意味着可以买低内存版本回来自己加爆到 32*2=64G 内存呢？具体情况关注 ifixit 的拆解情况再作判断。\n显卡/GPU 应该是这次“面向专业人士的升级”中最值得吐槽的地方。 之前微博上一直有说法，这次的 Mac mini 会选配 GTX 1050 图形处理器。 然而，然而，最后的结果是，全系列，Intel UHD630 集成图形处理器。 这个 GPU 是什么概念呢，在显卡天梯图上，这款产品的规格甚至比 2015 mid 的MacBook Pro 15 吋低配的 4770HQ 处理器内置的 Iris 5200 还要低。（能不能顺利带动我的 P2415Q 是很让我困惑的一件事，虽然官网规格说支援同时带两个 4k 显示器）\n接口 之前一直很担心 Mac mini 更新会不会只给 Thunderbolt 3 接口，甚至连以太网接口会不会砍掉，幸好最后库克证明是我多虑了。\n四个 Thunderbolt 3 接口，两个 USB3.0 接口，还有 HDMI2.0 和千兆以太网接口（可选万兆），覆盖了日常使用的所有接口类型，总而言之，管够！\n外接设备的可能性探讨 最重要的外接设备形式，就是自 macOS High Sierra 10.13.4 开始支援的 eGPU 了。关于 Mac 对 eGPU 原理的讨论，大别ibuick的微博上面有篇文章讨论的非常详细，我这里就不班门弄斧。有需要的可以自行参阅研究。 总的来说，外接 eGPU 可以满足视频渲染、照片处理等重型任务的需求，对于程序员（包括我）最关心的科学计算、深度学习任务可不可以使用 eGPU 进行加速的问题，目前调查到的情况有这几点：\n显卡硬件方面。\nNVIDIA Pascal 及以上架构的显卡（GTX10 开始）在 macOS Mojave 10.14 系统上还没发布驱动包（据说是还没通过苹果的审核）。 AMD 显卡，有ROCm, a New Era in GPU Computing方案实现 TensorFlow 调用 AMD GPU 资源进行计算加速。问题是 ROCm 到目前为止不支持 macOS。 软件兼容性层。NVIDIA 有发布 CUDA 以及 CuDNN for macOS。\n同样的，这两个软件依然没有支持 macOS Mojave TensorFlow for macOS 可以通过自编译的方式实现 GPU 支持。 就算以后打通了 NVIDIA eGPU + CUDA + CuDNN + TensorFlow for macOS 的完整流程，由于 Docker for macOS 是一种虚拟机实现方案，所以也许永远不会有 Nvidia-Docker for macOS 来实现更简单的开发环境部署和 Python 环境隔离。 对于 macOS High Sierra 系统和 Pascal 以下的显卡，已经有非常详细的教程Training Your Neural Net with eGPU Acceleration on Mac with Tensorflow 1.5实现 2.3 所说的完整流程。\n其他外接设备 显示器 预算有限的，可以选 dell P2415Q，目前非 Ultrafine 荧幕里面 PPi 最接近视网膜的。接一般的 4k 荧幕（泛指所有尺寸在 27 寸及以上的 4k 荧幕），同样可以实现 HiDPI 缩放，具体判断是接入显示器之后，设置连接模式为扩展，然后打开显示器选项，选择缩放，看到的是下面这样的截图，而不是让你选择物理分辨率。HiDPI 技术对显示效果的影响，具体来说就是两种显示器（支持 HiDPI 和普通显示器），macOS 在渲染输出的时候会采用不同的色彩空间（对于后者，在系统中会被识别成“电视”，使用 YUV），看起来会觉得后者的字体锯齿甚至比 Windows10 外接高分屏荧幕更加严重。2k 显示器可以通过软件修改的方式实现 HiDPI，缺点是每次系统升级都可能导致不能用。 预算充足的，直接 LG Ultrafine 系列。 机械键盘 ikbc G104 和阿米洛的 Mac 系列都是原生 Mac 键位支持。也可以买 Windows 键盘回来自己改键位和键帽，全凭个人喜好了。\n","date":"2018-11-02T11:20:29Z","permalink":"/posts/macmini/","title":"新款 Mac mini 购买可行性分析"},{"content":"Go 模块入门 Go1.11 的一个重要特性就是 go modules，作为 Go 一直以来版本管理顽疾的官方解决方案，自然是非常值得重视的。最近看到一篇文章对它阐述的非常好，所以就翻译过来。\n原文链接：Introduction to Go Modules – Roberto Selbach\nGo 语言的1.11版本即将带来modules 的实验性支持，这是 Go 语言中一个全新的依赖管理系统。\n早前我写过一篇文章来阐述 Go 的模块，但里面的很多东西在那篇文章之后都发生了变化，所以就有了这篇文章，用更多的上手实践来介绍这个特性。\n在这篇文章里，我们会创建一个新的包（package）和发布一些版本来验证 go 模块是如何工作的。\n创建一个模块 首先，我们创建一个命名为“testmod”的包。一个重要的细节是，包文件夹应该在$GOPATH以外，因为在$GOPATH目录下，模块功能是默认关闭的。某种程度上可以认为 Go modules 是削弱$GOPATH职能的第一步。\n1 2 $ mkdir testmod $ cd testmod 包的内容非常简单，\n1 2 3 4 5 6 7 8 package testmod import \u0026#34;fmt\u0026#34; // Hi returns a friendly greeting func Hi(name string) string { return fmt.Sprintf(\u0026#34;Hi, %s\u0026#34;, name) } 这样不是一个模块，需要做以下变更\n1 2 $ go mod init github.com/robteix/testmod go: creating new go.mod: module github.com/robteix/testmod 这个命令会在目录下创建go.mod文件，内容如下\n1 module github.com/robteix/testmod 这些步骤已经将我们的包转换成一个模块，我们可以把这些代码推送到仓库中，\n1 2 3 4 $ git init $ git add * $ git commit -am \u0026#34;First commit\u0026#34; $ git push -u origin master 这样，其他人可以通过go get github.com/robteix/testmod来使用这个包\n这个操作会拉取master分支下的代码。这依然有效，但我们应该停止这么做因为我们找到了更好的方法。拉取master分支的做法是具有潜在危险性的，因为我们不能确定软件包的作者会不会作出一些让我们的调用不再生效的更改。这是 modules 主要想改善的状况。\nModule Versioning 简介 Go 模块是支持版本的概念的，也支持特别对待一些特殊的版本。首先（运用这些特性之前）你要熟悉语义化版本的概念。\n更重要的是，Go 使用仓库标签的方式来查找版本，某些版本会与众不同，比如version 2或者更高的版本应该享受与0或1版本不同的导入路径。\n与此同时，Go 会默认拉取仓库中最新的可用标记版本（tagged version），这是非常值得注意的地方因为在此之前你或许已经习惯了使用 master 分支。\n现在你需要知道的是为了发布我们的软件包，我们需要对软件仓库的代码用版本号来打标签，让我们开始吧！\n第一次发布 让我们把写好的软件包发布出去。我们使用版本号标签的方式来实现，比如说发布1.0.0版本\n1 2 $ git tag v1.0.0 $ git push --tags 这会在我的 GitHub 仓库上面将当前的提交标记为1.0.0发布版。\n为这次发布创建一个新分支是一个好主意，尽管 Go 不会强制要求我们这样做。\n1 2 $ git checkout -b v1 $ git push -u origin v1 现在我们可以专注于 master 分支，不用担心会破坏我们的发布版本。\n使用我们的模块 我们将会创建一个简易程序来演示如何使用我们的模块/\n1 2 3 4 5 6 7 8 9 10 11 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/robteix/testmod\u0026#34; ) func main() { fmt.Println(testmod.Hi(\u0026#34;roberto\u0026#34;)) } 在之前，你可以通过go get github.com/robteix/testmod来下载软件包，但使用模块功能，事情会变得有趣。首先我们要在自己的程序中开启模块功能\n1 go mod init mod 它会创建go.mod文件，内容如下\n1 module mod 当我们在试图构建我们的代码的时候事情变得有趣起来，\n1 2 3 $ go build go: finding github.com/robteix/testmod v1.0.0 go: downloading github.com/robteix/testmod v1.0.0 正如我们所见，go命令获取程序需要用到的软件包。这时候如果我们打开go.mod文件，可以看到\n1 2 module mod require github.com/robteix/testmod v1.0.0 另外我们会看到一个名为go.sum的新文件，包含了软件包的哈希值，确保我们下载了正确的版本和对应的二进制文件。\n1 2 github.com/robteix/testmod v1.0.0 h1:9EdH0EArQ/rkpss9Tj8gUnwx3w5p0jkzJrd5tRAhxnA= github.com/robteix/testmod v1.0.0/go.mod h1:UVhi5McON9ZLc5kl5iN2bTXlL6ylcxE9VInV71RrlO8= 发布修复 bug 的版本 现在假设我们意识到程序中存在的一个问题，打招呼的语句缺少标点符号。人们会因为我们这个程序的不够友好而勃然大怒，所以我们用新版本来修复这个问题\n1 2 3 4 5 // Hi returns a friendly greeting func Hi(name string) string { - return fmt.Sprintf(\u0026#34;Hi, %s\u0026#34;, name) + return fmt.Sprintf(\u0026#34;Hi, %s!\u0026#34;, name) } 我们在v1分支进行更改因为这与我们稍后在v2版本要做的事情毫无关系。在实际应用中，你或许会直接在 master 分支上干这件事情。无论哪种方式，我们需要修复我们v1分支上的代码并且标记成一个新的发布版本。\n1 2 3 $ git commit -m \u0026#34;Emphasize our friendliness\u0026#34; testmod.go $ git tag v1.0.1 $ git push --tags origin v1 更新模块 在默认情况下，Go 在未经许可之前不会更新模块，这是一件好事因为我们希望软件构建过程是可以预测的。如果 Go 模块每次有新版本发布的时候都会自动更新，我们会回到 Go1.11的一个非常原始的版本。现在，我们需要告诉 Go 来更新模块。\n在老版本的go get，我们可以这样做\ngo get -u来获取minor或者 patch 更新，比如从1.0.0更新到1.0.1或者1.1.0 go get -u=patch 来获取最新的patch更新（会更新到1.0.1但不会更新1.1.0） go get package@version 来更新特定的版本。 在上面所说的措施里面，没有一个可行的举措来更新到最新的major版本，我们稍后会看到，这是有原因的。\n因为我们的程序使用的是1.0.0版本的软件包，我们刚刚创建了1.0.1版本，以下任意一个命令会更新我们的软件包到1.0.1\n1 2 3 $ go get -u $ go get -u=patch $ go get github.com/robteix/testmod@v1.0.1 运行以上任意命令之后，go.mod文件更改为\n1 2 module mod require github.com/robteix/testmod v1.0.1 主版本更新 根据语义化版本的语法，主版本（major version）不同于小版本更新（minors）。主版本可以破坏后向兼容性。Go模块的观点中，主版本是完全不同的软件包。这一点最开始看来非常的奇怪，但是讲得通的：两个版本的库是不兼容的，这是两个完全不同的库。\n让我们创建软件包的一个主要版本，为greeting函数提供一个新的参数，\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package testmod import ( \u0026#34;errors\u0026#34; \u0026#34;fmt\u0026#34; ) // Hi returns a friendly greeting in language lang func Hi(name, lang string) (string, error) { switch lang { case \u0026#34;en\u0026#34;: return fmt.Sprintf(\u0026#34;Hi, %s!\u0026#34;, name), nil case \u0026#34;pt\u0026#34;: return fmt.Sprintf(\u0026#34;Oi, %s!\u0026#34;, name), nil case \u0026#34;es\u0026#34;: return fmt.Sprintf(\u0026#34;¡Hola, %s!\u0026#34;, name), nil case \u0026#34;fr\u0026#34;: return fmt.Sprintf(\u0026#34;Bonjour, %s!\u0026#34;, name), nil default: return \u0026#34;\u0026#34;, errors.New(\u0026#34;unknown language\u0026#34;) } } 使用我们 API 的现有软件在新版本之下不能运行因为他们不会传递一个语言参数而且不会返回一个错误，我们的新 API 不再与1.x 版本兼容。所以是时候更新2.0.0版本。\n我之前提过有些版本有一些具体的特性，这里是具体的例子，Version 2 及以上的版本要修改导入路径，他们现在是不同的库。\n我们通过在导入路径中加入新版本号来实现\n1 module github.com/robteix/testmod/v2 剩下要做的事和之前的差不多，把代码推送到软件仓库，标签为2.0。0版本，创建 v2分支\n1 2 3 4 5 6 $ git commit testmod.go -m \u0026#34;Change Hi to allow multilang\u0026#34; $ git checkout -b v2 # optional but recommended $ echo \u0026#34;module github.com/robteix/testmod/v2\u0026#34; \u0026gt; go.mod $ git commit go.mod -m \u0026#34;Bump version to v2\u0026#34; $ git tag v2.0.0 $ git push --tags origin v2 # or master if we don\u0026#39;t have a branch 主要版本更新 虽然我们发布了软件库的新的、不兼容的版本，但现有的软件不会失效，因为它会继续使用现有的1.0.1版本，go get -u不会更新到2.0.0版本。\n在某些场合，库的使用者可能会想要更新到2.0.0版本因为可能存在需要多语言支持的用户。\n通过适度的修改来实现\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/robteix/testmod/v2\u0026#34; ) func main() { g, err := testmod.Hi(\u0026#34;Roberto\u0026#34;, \u0026#34;pt\u0026#34;) if err != nil { panic(err) } fmt.Println(g) } 之后，当我运行go build的时候，它会下载2.0.0版本。注意到，即使这个导入路径是以“v2”结尾，Go 依然可以正确引用模块名字（“testmod”）\n正如我之前提到的是，主版本从各方面来说都是完全不同的包版本，Go 模块不会连接到两个不同的版本，这意味着我们可以在同一个程序里面使用两个不兼容的库版本。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/robteix/testmod\u0026#34; testmodML \u0026#34;github.com/robteix/testmod/v2\u0026#34; ) func main() { fmt.Println(testmod.Hi(\u0026#34;Roberto\u0026#34;)) g, err := testmodML.Hi(\u0026#34;Roberto\u0026#34;, \u0026#34;pt\u0026#34;) if err != nil { panic(err) } fmt.Println(g) } 这个举措降低了包管理体系里面常见问题的负面影响：在同一软件中依赖了同一个库的不同版本。\n整理 回到那个只用testmod v2.0.0版本的软件，我们再看go.mod的内容，我们注意到\n1 2 3 module mod require github.com/robteix/testmod v1.0.1 require github.com/robteix/testmod/v2 v2.0.0 默认情况下 Go 不会主动移除过时版本的依赖，如果你不再使用并且想进行清理，你可以使用tidy命令\n1 go mod tidy 现在go.mod里面只有我们在使用的软件包版本。\nVendoring 默认情况下 Go 模块忽略vendor/目录。这个构想是为了废除vendoring机制，但如果我们依然想加入vendor依赖的话，依然可以实现。\n1 go mod vendor 它会在你项目的根目录下创建vendor/目录，目录里面是你源代码的所有依赖关系。\n同样地，go build会忽略这个目录下的所有内容，如果你想从这个目录中构建依赖关系，你需要输入\n1 go build -mod vendor 我觉得很多开发者会在自己的机器上面使用正常状态下的go build而在持续集成的环境里使用-mod vendor\n需要再次强调的是，Go 模块已经彻底抛弃了 vendoring 思想，转而使用 Go 模块代理，因为很多人不想直接依赖上游版本控制服务。\n结论 这篇东西多多少少看起来有点劝退的意味，不过我尝试在一篇文章里解释多个概念，事实上 Go 模块是非常透明的，我们只需要像往常一样在代码中导入需要使用的代码然后go命令会帮我们做完剩下的所有事情。\n当我们构建应用的时候，会自动拉取依赖关系。Go 模块同样削弱了$GOPATH的必要性，这玩意是新入门的 Go 开发者的一大绊脚石，他们往往无法理解问什么所有东西都要放到同一个目录中。\n","date":"2018-09-03T15:25:44Z","permalink":"/posts/gomod/","title":"go模块入门"},{"content":"Preface 最近实验室里买了打印机，手账 er 多年以来的为电影手账贴上海报缩略图的心愿终于有机会打成了。\n那么问题来了，去哪找电影海报可以更快更方便呢？每次都是打开网页-\u0026gt;搜索-\u0026gt;图片另存为，太麻烦。于是我盯上了 Alfred，于是就有了这个工具。\n使用 首先配置好文件储存的路径。\n在 Alfred 中输入 dbmv，选择 setting，在配置文件中的img_path输入想要保存图片的目录。\n然后就可以愉快的使用了。\n⌘ + Space 唤醒 Alfred 窗口，输入 dbmv 启动 Workflow。 选择 Movie 输入想要搜索的电影名字 在搜索结果中选择要下载的电影海报， 回车确认，然后在目标路径就会看到下载好的海报了。 下载地址 Releases · gawainx/dbmv\n特别鸣谢 本 Workflow 系基于做了一个豆瓣搜索的 Workflow for Alfred - 海边的石头作品的改写。\n","date":"2018-07-17T21:03:53Z","permalink":"/posts/dbmv/","title":"豆瓣电影海报下载-Workflow"},{"content":"gxd-cli : 一种快速创建多容器工具 通过 docker run 命令行启动容器的时候，配置网络、挂载卷是一件非常麻烦的事，gxd-cli将这些麻烦的工作简化成修改配置文件TOML达成在不需要记忆繁琐的 docker 命令行参数就能快速启动多容器。\n功能列表 创建多容器，创建每个容器过程可以配置一下选项 挂载卷（支持以pwd指代当前路径） 指定容器的网络 自定义容器名 设定容器暴露的端口 创建网络 快速生成模板文件 安装 支持从源码构建，构建之前首先保证系统已经安装golang和dep 步骤如下:\n1 2 3 git clone git@github.com:gawainx/gxd-cli.git dep ensure -update go install 安装完毕后在命令行通过gxd-cli调用。\n项目地址 gawainx/gxd-cli\n","date":"2018-06-27T16:42:27Z","permalink":"/posts/gxd-cli/","title":"gxd-cli is gawainx' docker client"},{"content":"Golang 包依赖管理工具 golang 一直以来一个为人诟病的问题就是没有完善可用的包管理工具（类比 java 的 gradle 和 maven，Python 的 pip，nodejs 的 npm），这与 golang 的追求简约高效的原则有关。golang 1.5版本之后引入了vendor机制，1.8之后终于有了官方的包管理工具，golang/dep。\n相关链接 Golang依赖管理工具：Dep - 乐金明的博客 | Robin Blog Models and Mechanisms · dep golang/dep: Go dependency management tool\n","date":"2018-06-12T21:17:51Z","permalink":"/posts/dep/","title":"golang/dep 包管理（一）原理"},{"content":"Docker client for golang 使用教程（二）：网络 端口绑定 将微服务放到 docker 容器中运行的时候，端口绑定是一个无可避免的问题。在 docker 命令行中，可以通过简单的-p 8080:80解决问题。但在 golang client 中，问题却变得复杂起来。\n首先来看创建容器的函数签名func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (container.ContainerCreateCreatedBody, error),client 把运行配置拆分成了container.Config 和 container.HostConfig ，也就是容器内部设置和宿主机设置两项。\n要实现端口绑定，首先要在容器设置中设定暴露的端口（exposed ports）。\n1 2 3 4 5 6 7 exports := make(nat.PortSet, 10) port, _ := nat.NewPort(\u0026#34;tcp\u0026#34;, \u0026#34;80\u0026#34;) exports[port] = struct{}{} // in config: cli.ContainerCreate(ctx, \u0026amp;container.Config{ ExposedPorts:exports, } 然后，在 host.config 中，设置Host 端口与容器暴露出来的端口的绑定。\n1 2 3 4 5 6 7 8 9 10 11 ports := make(nat.PortMap) pb := make([]nat.PortBinding,0) pb = append(pb,nat.PortBinding{ HostPort:\u0026#34;8080\u0026#34;, }) ports[port] = pb //in Host.config \u0026amp;container.HostConfig{ PortBindings:ports, } 至此，在代码中就实现了端口绑定的操作。然而，如果只执行到这一步，编译器一般会报非常诡异的类型不匹配错误。\n参考go操作docker - 简书的解决方法，删除gopath里面pkg下面docker的vendor里面相应的connections包，然后运行go get github.com/docker/go-connections/nat ，问题解决。\n相关链接 client - GoDoc go操作docker - 简书 ","date":"2018-06-01T11:05:44Z","permalink":"/posts/go-docker-02/","title":"go docker client 使用教程（二）"},{"content":"Docker client for golang 使用教程（一） Docker 官方提供了适用于 golang 的 client，可惜的是网上几乎没有完整可用的使用教程或者例子。在开发gxd-cli的过程中，需要大量使用到这个 SDK，所以便有了这个系列。\n本篇涉及通过代码运行第一个容器，以及如何挂载卷。\n运行第一个容器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 /* *Gawain Open Source Project *Author: Gawain Antarx *Create Date: 2018-May-29 * */ package main import ( \u0026#34;github.com/docker/docker/client\u0026#34; \u0026#34;github.com/docker/docker/api/types\u0026#34; \u0026#34;github.com/docker/docker/api/types/container\u0026#34; \u0026#34;io\u0026#34; \u0026#34;os\u0026#34; \u0026#34;context\u0026#34; \u0026#34;fmt\u0026#34; ) func main() { ctx := context.Background() cli, err := client.NewClientWithOpts(client.WithVersion(\u0026#34;1.37\u0026#34;)) if err != nil { panic(err) } resp, err := cli.ContainerCreate(ctx, \u0026amp;container.Config{ Image: \u0026#34;alpine:latest\u0026#34;, Cmd: []string{\u0026#34;echo\u0026#34;,\u0026#34;hello\u0026#34;}, }, nil, nil, \u0026#34;\u0026#34;) if err != nil { panic(err) } if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil { panic(err) } statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning) select { case err := \u0026lt;-errCh: if err != nil { panic(err) } case \u0026lt;-statusCh: } out, err := cli.ContainerLogs(ctx, resp.ID, types.ContainerLogsOptions{ShowStdout: true}) if err != nil { panic(err) } io.Copy(os.Stdout, out) } 官方使用client.NewEnvClient()来初始化client，在 IDE 中提示这个接口已经过时，推荐使用client.NewClientWithOpts()。要注意的是，直接调用的时候一般会提示 API 版本不匹配，需要加client.WithVersion(\u0026quot;1.37\u0026quot;)作为参数传入。1.37部分可以根据它的错误提示自行修改。\n绑定卷 将自己代码放入容器中运行时最基本的操作，在命令行中通过-v {host vol}:{container vol}实现，在 golang sdk 中，开发者却没有提供这部分的重要说明。通过查阅issue155以及issue1，得到的解决方案如下。\n卷绑定通过client.ContainerStart()里面的参数client.HostConfig结构的Binds传入，传入的类型是[]string，这个字符串序列的中每个字符串的格式{host_vol}:{com_col}，也就是和命令行的一致。\n","date":"2018-05-29T11:05:41Z","permalink":"/posts/go-docker-01/","title":"go docker client 使用教程（一）"},{"content":"LaTeX 排版工具链 这里整理了 $\\LaTeX$ 排版学术论文的工具链。根据自己的实践和大家的留言补充定期更新。\n操作系统，软件等 $\\LaTeX$ 可以在 Windows、Linux 和 macOS 平台上运行。Windows 上可以安装 MikTex，Linux 上有 TeX Live，考虑美观度和排版过程的愉悦度，本人使用的 LaTeX 排版环境大致如下：\nOS : macOS(Darwin) 编辑器：Microsoft VSCode 插件：LaTeX Workshop $\\LaTeX$ 软件：MacTeX 工具包 PDF 阅读工具：skim 模板 IEEE tran bare_jrnl.tex最新版本应该是 v1.7，但在网上好像找不到这个最新的版本，准备整理一个多文件编译的版本放到 GitHub 上。\n欢迎留言补充其它模板。:)\n数学公式 数学符号集\n插入伪代码（算法宏包的相关使用） LaTeX算法排版\n代码 使用listings宏包。LaTeX/Source Code Listings 但这个宏包没有 golang 语法高亮支持的！要使 golang 语法高亮，可添加宏包listings-golang\n图片与表格 首先要理解浮动体的概念，LaTeX 中的浮动体：浮动算法\n学习资源 LaTeX 开源小屋 Begin LaTeX in minutes LaTeX 入门\n","date":"2018-05-21T10:48:35Z","permalink":"/posts/latex-toolchain/","title":"LaTeX排版学术论文工具链"},{"content":"博客迁移腾讯云实战记录 源于年初的一次突发奇想，将博客站点迁移到了腾讯云，五月份终于完成了备案，然后添加了 HTTPS 支援，并完成容器化。在这里把折腾的过程记录下来。\n转移腾讯云 原来的博客是放到 github page 上面，访问 antarx.com 是通过 CNAME 文件解析到 gawainx.github.io，只需要在config.yaml配置好，写完直接hexo d -g完事。\n迁移到腾讯云之后，还想每次部署都这么方便，就免不了完成一些相关工作。\n资源准备 这里准备的资源主要就是服务器的购入，可以根据自己博客的规模需要选择合适的套餐。如果是学生党记得使用学生优惠。 买完服务器就是装系统的事，根据自己的使用习惯自行选择，我用的是 ubuntu16.04. 如果想容器化的话需要安装 Docker。\n安装 git 1 sudo apt-get install git 新建 git 裸仓库 1 2 3 4 5 6 sudo mkdir /var/repo/ sudo chown -R $USER:$USER /var/repo/ sudo chmod -R 755 /var/repo/ cd /var/repo/ git init --bare hexo_static.git 创建存放 hexo 生成的网页的文件夹 1 2 3 sudo mkdir -p /var/www/hexo sudo chown -R $USER:$USER /var/www/hexo sudo chmod -R 755 /var/www/hexo 这两步要注意的是文件夹的 owner 问题，要实现自动部署的话一般新建一个 git 账户然后全权接管部署网页的相关工作，那么上面创建的两个文件夹在后期的 owner 记得要变更为 git 用户。\n创建 git 钩子 在hexo_static.git文件夹的 hooks 目录下面新建钩子文件并写入如下代码，然后将文件变更成可执行文件。\n1 2 3 4 5 6 7 8 vim /var/repo/hexo_static.git/hooks/post-receive # post-receive file content #!/bin/bash git --work-tree=/var/www/hexo --git-dir=/var/repo/hexo_static.git checkout -f chmod +x /var/repo/hexo_static.git/hooks/post-receive 自动部署 所谓自动部署，就是在每次部署的时候不需要键入密码。 在自己电脑上，创建公钥，复制到剪贴板\n1 2 ssh-keygen -t rsa -C \u0026#34;xx@xx.com\u0026#34; pbcopy \u0026lt; ~/.ssh/id_rsa.pub 服务器上，切换到 git 用户（没有的话请新建），输入\n1 2 3 4 5 6 7 su git cd ~ mkdir .ssh vim .ssh/authorized_keys #粘贴自己电脑的 公钥，然后保存 chmod 600 ~/.ssh/authorized_keys chmod 700 ~/.ssh 本地 hexo-git 配置 在config.yaml键入\n1 2 3 4 deploy: type: git repo: git@CVM 云服务器的IP地址:/var/repo/hexo_static branch: master 添加 SSL 证书并容器化 Nginx Linux 配置软件向来复杂，所以毫不犹豫选用 docker 容器进行 Nginx 部署。 输入docker pull nginx拉取 Nginx 镜像。\n首先基于镜像运行一个 nginx 容器，然后将容器里面的/etc/nginx/目录拷贝出来。 这里顺带一提 Nginx 的两个配置文件，nginx.conf和deafult.conf，一般而言修改nginx.conf然后把deafult里面的内容注释掉会比较妥当。\n添加 SSL 证书 在拷贝出来的nginx.conf文件，在http的选型里面，加入以下内容\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 server { listen 443; server_name 你的站点名称; #填写绑定证书的域名 ssl on; ssl_certificate /cert/你的证书.crt; ssl_certificate_key /cert/你的私钥.key; ssl_session_timeout 5m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #按照这个协议配置 ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;#按照这个套件配置 ssl_prefer_server_ciphers on; location / { root /blog; #站点目录 index index.html index.htm; } } 如果强制使用 HTTPS，还要加上以下配置，将80端口的流量重定向到443端口。\n1 2 3 4 5 server { listen 80; server_name antarx.com; rewrite ^(.*)$ https://${server_name}$1 permanent; } 这两个 server 是独立的，都加到http的配置里面。 顺带一提 Nginx 要求的证书是 PEM 格式。\n运行博客容器\n1 docker run -d -p 443:443 -p 80:80 -v `pwd`:/etc/nginx -v /var/www/hexo:/hexo -v \u0026#34;$PWD/cert\u0026#34;:/cert --name ng nginx 这里注意到的是三个挂载卷，分别是配置文件，博客网页文件和证书文件。\n容器运行之后，在腾讯云的域名管理里面添加 DNS 记录，OK，大功告成。\n相关链接 在 Ubuntu 14.04 服务器上部署 Hexo 博客 Hello Hexo之静态博客搭建+自动部署 证书安装指引 Nginx 容器教程（可能无法访问）\n","date":"2018-05-11T17:30:58Z","permalink":"/posts/hexossl/","title":"博客迁移腾讯云小记"},{"content":"Gorequest指南 gorequest为 golang 程序提供了极为简便的方式发起 HTTP 请求。网上关于这个库的中文教程不多，因此把官方的 README 文件翻译过来，结合自己的一些使用经验，希望能为各位 Gopher 提供一些帮助。\n特性 支持发送Get/Post/Put/Head/Delete/Patch/Options 请求 建议的请求头设置 JSON 支持：以 JSON 格式字符串作为函数参数的方式简化传输 JSON 的步骤。 分段支持：分段请求（Multipart Request）的方式发送数据或传输文件 代理：支援通过代理的方式发送请求。 Timeout：为请求设置时间限制 TLS(传输层安全协议)相关设定。 TLSClientConfig - taking control over tls where at least you can disable security check for https\n重定向策略 Cookie：为请求添加 cookie CookieJar - automatic in-memory cookiejar 基本的权限认证。 安装 1 $ go get github.com/parnurzeal/gorequest 文档 参阅Go Doc 后续我会根据自己在开发中使用经验将文档翻译过来。\n使用 GoRequest 的一万个理由？ 通过 GoRequest 可以使工作变得更简单，使可以发起 HTTP 请求这件事更加优雅而充满乐趣。\n不使用本库发起简单 GET 请求：\n1 resp, err := http.Get(\u0026#34;http://example.com/\u0026#34;) 使用 GoRequest\n1 2 request := gorequest.New() resp, body, errs := request.Get(\u0026#34;http://example.com/\u0026#34;).End() 如果你不想重用request，也可以写成下面这样\n1 resp, body, errs := gorequest.New().Get(\u0026#34;http://example.com/\u0026#34;).End() 如果你需要设定 HTTP 头，设定重定向策略等，使用标准库会瞬间使事情变得异常复杂，在发起仅仅一个 GET 请求的过程，你就需要一个 Client，通过一系列不同的命令来设定 HTTP 头（HTTP Headers）。\n1 2 3 4 5 6 7 8 client := \u0026amp;http.Client{ CheckRedirect: redirectPolicyFunc, } req, err := http.NewRequest(\u0026#34;GET\u0026#34;, \u0026#34;http://example.com\u0026#34;, nil) req.Header.Add(\u0026#34;If-None-Match\u0026#34;, `W/\u0026#34;wyzzy\u0026#34;`) resp, err := client.Do(req) 现在，你有更加美妙的方式来完成这件事\n1 2 3 4 5 request := gorequest.New() resp, body, errs := request.Get(\u0026#34;http://example.com\u0026#34;). RedirectPolicy(redirectPolicyFunc). Set(\u0026#34;If-None-Match\u0026#34;, `W/\u0026#34;wyzzy\u0026#34;`). End() 发起 DELETE, HEAD, POST, PUT, PATCH 请求的过程和发起 GET 请求类似。\n1 2 3 4 5 6 request := gorequest.New() resp, body, errs := request.Post(\u0026#34;http://example.com\u0026#34;).End() // PUT -\u0026gt; request.Put(\u0026#34;http://example.com\u0026#34;).End() // DELETE -\u0026gt; request.Delete(\u0026#34;http://example.com\u0026#34;).End() // HEAD -\u0026gt; request.Head(\u0026#34;http://example.com\u0026#34;).End() // ANYTHING -\u0026gt; request.CustomMethod(\u0026#34;TRACE\u0026#34;, \u0026#34;http://example.com\u0026#34;).End() 处理 JSON 用标准库发起 JSON POST ，你需要先将 map 或者 struct格式的数据包装（Marshal）成 JSON 格式的数据，将头参数设定为\u0026rsquo;application/json\u0026rsquo;（必要时还要设定其他头），然后要新建一个http.CLient变量。经过这一系列的步骤，你的代码变得冗长而难以维护\n1 2 3 4 5 6 7 8 9 10 11 m := map[string]interface{}{ \u0026#34;name\u0026#34;: \u0026#34;backy\u0026#34;, \u0026#34;species\u0026#34;: \u0026#34;dog\u0026#34;, } mJson, _ := json.Marshal(m) contentReader := bytes.NewReader(mJson) req, _ := http.NewRequest(\u0026#34;POST\u0026#34;, \u0026#34;http://example.com\u0026#34;, contentReader) req.Header.Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) req.Header.Set(\u0026#34;Notes\u0026#34;,\u0026#34;GoRequest is coming!\u0026#34;) client := \u0026amp;http.Client{} resp, _ := client.Do(req) 至于 GoRequest， JSON 支持是必须的，所以，用这个库你只需要一行代码完成所有工作\n1 2 3 4 5 request := gorequest.New() resp, body, errs := request.Post(\u0026#34;http://example.com\u0026#34;). Set(\u0026#34;Notes\u0026#34;,\u0026#34;gorequst is coming!\u0026#34;). Send(`{\u0026#34;name\u0026#34;:\u0026#34;backy\u0026#34;, \u0026#34;species\u0026#34;:\u0026#34;dog\u0026#34;}`). End() 另外，它同样支持结构体类型。所以，你可以在你的请求中发送不同的数据类型（So, you can have a fun Mix \u0026amp; Match sending the different data types for your request）。\n1 2 3 4 5 6 7 8 9 10 type BrowserVersionSupport struct { Chrome string Firefox string } ver := BrowserVersionSupport{ Chrome: \u0026#34;37.0.2041.6\u0026#34;, Firefox: \u0026#34;30.0\u0026#34; } request := gorequest.New() resp, body, errs := request.Post(\u0026#34;http://version.com/update\u0026#34;). Send(ver). Send(`{\u0026#34;Safari\u0026#34;:\u0026#34;5.1.10\u0026#34;}`). End() Not only for Send() but Query() is also supported. Just give it a try! :)\n回调（Callback） 此外，GoRequest 支持回调函数，这让你可以更加灵活的使用这个库。 下面是回调函数的一个例子\n1 2 3 4 func printStatus(resp gorequest.Response, body string, errs []error){ fmt.Println(resp.Status) } gorequest.New().Get(\u0026#34;http://example.com\u0026#34;).End(printStatus) Multipart/Form-Data 你可以将请求的内容类型设定为multipart来以multipart/form-data的方式发送所有数据。这个特性可以帮助你发送多个文件。 下面是一个例子\n1 2 3 4 gorequest.New().Post(\u0026#34;http://example.com/\u0026#34;). Type(\u0026#34;multipart\u0026#34;). Send(`{\u0026#34;query1\u0026#34;:\u0026#34;test\u0026#34;}`). End() 如果感兴趣可以在文档中查看SendFile函数部分获取更多。\n代理（Proxy） 需要使用代理的时候，可以用 GoRequest Proxy Func 很好的处理。\n1 2 3 4 request := gorequest.New().Proxy(\u0026#34;http://proxy:999\u0026#34;) resp, body, errs := request.Get(\u0026#34;http://example-proxy.com\u0026#34;).End() // To reuse same client with no_proxy, use empty string: resp, body, errs = request.Proxy(\u0026#34;\u0026#34;).Get(\u0026#34;http://example-no-proxy.com\u0026#34;).End() 基本认证 添加基本的认证头信息\n1 2 request := gorequest.New().SetBasicAuth(\u0026#34;username\u0026#34;, \u0026#34;password\u0026#34;) resp, body, errs := request.Get(\u0026#34;http://example-proxy.com\u0026#34;).End() 超时处理（Timeout） 与 time 库结合可以设置成任何的时间限制。\n1 2 request := gorequest.New().Timeout(2*time.Millisecond) resp, body, errs:= request.Get(\u0026#34;http://example.com\u0026#34;).End() Timeout 函数同时设定了连接和 IO 的时间限制。\n以字节方式处理返回体（EndBytes） 1 resp, bodyBytes, errs := gorequest.New().Get(\u0026#34;http://example.com/\u0026#34;).EndBytes() 以结构体的方式处理返回体 假设 URL http://example.com/ 的返回体{\u0026quot;hey\u0026quot;:\u0026quot;you\u0026quot;}。\n1 2 3 4 5 6 7 heyYou struct { Hey string `json:\u0026#34;hey\u0026#34;` } var heyYou heyYou resp, _, errs := gorequest.New().Get(\u0026#34;http://example.com/\u0026#34;).EndStruct(\u0026amp;heyYou) 连续重复请求（Retry） 假设你在得到 BadRequest 或服务器内部错误（InternalServerError）时进行连续三次，间隔五秒的连接尝试\n1 2 3 4 request := gorequest.New() resp, body, errs := request.Get(\u0026#34;http://example.com/\u0026#34;). Retry(3, 5 * time.Second, http.StatusBadRequest, http.StatusInternalServerError). End() 重定向 Redirects can be handled with RedirectPolicy which behaves similarly to net/http Client\u0026rsquo;s CheckRedirect function. Simply specify a function which takes the Request about to be made and a slice of previous Requests in order of oldest first. When this function returns an error, the Request is not made.\n1 2 3 4 5 6 7 8 request := gorequest.New() resp, body, errs := request.Get(\u0026#34;http://example.com/\u0026#34;). RedirectPolicy(func(req Request, via []*Request) error { if req.URL.Scheme != \u0026#34;https\u0026#34; { return http.ErrUseLastResponse } }). End() Debug 模式 For debugging, GoRequest leverages httputil to dump details of every request/response. (Thanks to @dafang).You can just use SetDebug or environment variable GOREQUEST_DEBUG=0|1 to enable/disable debug mode and SetLogger to set your own choice of logger.Thanks to @QuentinPerez, we can see even how gorequest is compared to CURL by using SetCurlCommand.\n注意 gorequest.New()函数应该一次调用，对返回的实例尽可能多次使用。\nCredits Renee French - the creator of Gopher mascot Wisi Mongkhonsrisawat for providing an awesome GoRequest\u0026rsquo;s Gopher image :) License GoRequest is MIT License.\n","date":"2018-05-05T19:19:43Z","permalink":"/posts/gorequest-doc/","title":"gorequest中文文档(非官方)"},{"content":"golang 为微服务的开发带来了无可比拟的便利。使用的时候也自然而言发现一些问题，因为 golang 不像 Java 有 Maven 这样的打包工具，而是直接编译成二进制可执行文件，所以在开发机（macOS）上编译出来的可执行文件是无法在服务器或者 docker 容器里运行的，如果把源代码提交上去服务器编译，又会带来重新下载依赖包的麻烦（golang 的包依赖关系管理方面的缺失是我认为 golang 为数不多的缺点之一）。最近一直在思考有没有类似 Makefile 的方式来解决这件事（如果只想交叉编译的话直接用 go build或者借助 gox 等工具也不是不可以，可还是，不够方便）。直到之前 ing 大神给我推荐了 Hugo 这个静态博客框架，虽然目前因为找不到合适的博客主题没有从 hexo 迁移过去，但看源代码的时候有了一个重要的收获，就是mage 关于 mage 的基本安装和使用详见mage 使用教程(一)\ngolang 交叉编译的基本原理 交叉编译的实现主要是依靠三个参数，CGO_ENABLE,GOOS,GOARCH CGO_ENABLE在源代码中使用 C/C++时必须开启，这样就无法实现交叉编译了，这部分代码还是得具体平台生成特定的代码。因此，交叉编译是在没有混编代码的前提下的。将CGO_ENABLE设为0，表示关闭。 GOOS是目标操作系统，macOS 对应的是darwin，Linux 平台对应的是linux。 GOARCH是目标架构，一般设为amd64 按照如上所述设定要编译参数之后再执行go build即可生成目标平台的代码。\n实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // +build mega // magefile.go func Linux(){ //设置环境变量 var e= make(map[string]string) e[\u0026#34;CGO_ENABLE\u0026#34;] = \u0026#34;0\u0026#34; e[\u0026#34;GOOS\u0026#34;] = \u0026#34;linux\u0026#34; e[\u0026#34;GOARCH\u0026#34;] = \u0026#34;amd64\u0026#34; name := fmt.Sprintf(\u0026#34;%s-linux-%s\u0026#34;,prefix,VERSION) //创建 bin 文件夹 if err := os.Mkdir(\u0026#34;bin\u0026#34;, 0700); err != nil \u0026amp;\u0026amp; !os.IsExist(err) { fmt.Errorf(\u0026#34;failed to create %s: %v\u0026#34;, \u0026#34;bin\u0026#34;, err) os.Exit(1) } path := filepath.Join(\u0026#34;bin\u0026#34;,name) fmt.Println(\u0026#34;Building app for linux...\u0026#34;) //运行构建命令 err := sh.RunWith(e,\u0026#34;go\u0026#34;,\u0026#34;build\u0026#34;,\u0026#34;-o\u0026#34;,path,\u0026#34;main.go\u0026#34;,\u0026#34;utils.go\u0026#34;,\u0026#34;RESTHandler.go\u0026#34;,\u0026#34;module.go\u0026#34;) if err != nil{ fmt.Println(err) return } fmt.Printf(\u0026#34;Sucessfully Built.Output File: %s\\n\u0026#34;,path) } 在magefile所在文件夹输入 mage linux 即可编译生成适用于 Linux 的运行文件。\n","date":"2018-04-23T11:17:36Z","permalink":"/posts/mage2/","title":"使用mage实现交叉编译"},{"content":"Mage 是使用 golang 开发的类 Make的软件构建工具。借助这个工具只需要编写符合 golang 语言规范的代码就可以实现比较复杂的源代码编译。\n安装 安装 mage 之前首先要安装 golang1.7或以上版本。安装好之后，执行以下代码\n1 2 3 go get -u -d github.com/magefile/mage cd $GOPATH/src/github.com/magefile/mage go run bootstrap.go 编译完成之后，名为 mage 的可执行文件放在$GOPATH/bin目录下，将$GOPATH/bin加入到系统路径即可在终端中直接输入mage运行软件。\n第一个 Magefile Magefile 实质是符合 golang 语法的源代码，并且加入了特定的注记，规则如下：\n在包名之前\b加入一行，// +build mage 包名必须为 main 每个可导出函数\b会变成可被 mage 执行的选项（类似 Makefile 的每一个 tag） 每个可导出函数前的注释会被转换成帮助文档 文件名可以但不一定必须是 Magefile.go 在任意目录输入mage -init可以生成Magefile.go 模板代码。 可以输入以下代码实验\b以上规则\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 // +build mage package main import( \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; ) //Build func Build(){ fmt.Println(\u0026#34;Building...\u0026#34;) } //Install func Install(){ log.Println(\u0026#34;Installing...\u0026#34;) } 预备，构建 构建只需要一行代码mage build，程序就会自动执行 Build 函数，完成整个构建过程。\n","date":"2018-04-23T10:24:36Z","permalink":"/posts/mage1/","title":"mage 使用教程(一)"},{"content":"YAML 格式的数据常用来作为配置文件使用, 因此相对来说字段比较固定, 在 golang 中进行解析时跟上一篇提到的解析 json 数据有所区别\n","date":"2018-04-11T17:18:58Z","permalink":"/posts/goyaml/","title":"golang 处理 yaml 格式数据"},{"content":"Golang 中处理 JSON 格式数据主要依赖encoding/json这个库，很多教程（包括 Go 语言圣经）讲 JSON 数据处理时都会定义一个结构体对应于 JSON 数据的各个字段，这种处理方法在 JSON 中字段相对固定时非常实用。但对于字段可能不断变化或者只有一两个字段是固定的时候，如何处理这个问题往往令很多人感到困惑。最近研究 gin 这个库的时候发现一个思路非常值得学习借鉴。\nWhat 说了这么多，其实并不神秘，就是来自 gin 框架源码中的一句关键定义\n1 2 // H is a shortcup for map[string]interface{} type H map[string]interface{} 定义了这个数据结构之后，gin 框架就可以处理几乎所有的 JSON 数据。\n同理，我们在自己的代码中想不受结构体限制灵活处理 JSON 数据时，也可以在程序代码中添加类似的定义\n1 type message map[string]interface{} 从字符串解析 JSON 数据时，只需要\n1 2 3 //body is string message var result mss json.Unmarshal([]byte(body),\u0026amp;result) 包装 JSON 也类似\n1 2 //mes is message type bty,err := json.Marshal(mes) 值得一说的是平时处理 JSON 数据经常出现的反斜杠双引号问题，用这种方法处理时并没有出现。\nWhy 背后原理分析。\n这一行代码之所以这么实用是因为 interface{}这个 golang 中的“万金油”。Golang 中不存在类和对象的概念，因此空接口就成了所有变量的“超类”一样的存在，可以承载一切的变量。\n潜在问题 and Further More 处理 JSON Array 时会出现问题，就是明知它是数组却不能直接用下标进行操作（会提示{}interface 不支持下标，导致编译失败），实际运行时用反射包的reflect.TypeOf查看JSON 数组解析出来的类型明明是[]interface{}，是接口slice，是支持下标操作的。\n原因是 Golang 是静态语言，代码中所有变量的类型都是在编译期确定的，我们所定义的类型中 map 的 value 部分是{}interface，在 运行时接收到需要解析的 JSON 数据之前编译器和我们都不知道它“事实上”是一个 slice。\n这个在项目应用中实际存在的矛盾也解决我学 OOP 语言一直以来的困惑，就是反射是什么？为什么现代编程语言都选择加入反射作为基本特性（Java，C#，还有本文中的 Golang），就是因为我们有在运行时获取某个对象/变量的实际类型的需要。\n由于目前学习还不够深入，不能对反射机制展开深入的探讨，在这里讲一下如何利用类型断言机制解决上面说到的问题。\n1 2 3 4 5 6 cs,ok := result[\u0026#34;containers\u0026#34;].([]interface{}) if ok{ for i,item := range cs{ //do something } } cs,ok := result[\u0026quot;containers\u0026quot;].([]interface{})这一句就是解决问题的关键所在，也就是称为运行时类型断言的机制。a.(Type) 是尝试对a进行类型转换的操作，如果转换成功则返回一个转换成 Type 类型的变量和true指示转换成功，失败则返回false。\n值得一提的是，断言失败不会导致编译失败（要是会导致编译失败也就不能解决上面这个问题了），所以为了代码健壮性需要对转换结果进行判断比较好。\n","date":"2018-03-30T15:20:31Z","permalink":"/posts/gojson/","title":"go 语言中 JSON 数据的处理"},{"content":"Redis 是目前应用比较广泛的数据库。最近的实验室项目中用到它作为实时数据库。把这个过程中学到的一些东西记录下来，权且作为小白的入门参考吧\n基本概念 Redis 是一种基于内存的数据库，这意味着在使用过程中的所有数据都是存放在内存当中的，省去硬盘读写的过程使得对数据库的操作会非常的快，很适合并发。\nRedis 的基本储存单位是key-value，比 mongodb 中的“文档”的概念有着更细的粒度。key部分一般是字符串类型，value部分可以有以下五种类型：\nstring。字符串类型 hash。一系列k-v的集合，适合用来储存对象实例或者 JSON list。简单的字符串列表，也就是说可以通过 list 数据结构在一个key对应的value字段储存多个字符串。这多个字符串是有序排列的。“序”指的是插入的顺序（可以选从头部或者尾部插入） set。String 类型的无序集合。满足唯一性。 有序集合。set 的有序版本 命令行操作 服务器（server 端） 安装 Redis 之后，在命令行窗口输入redis-server即可在本机运行一个radis 的 server 端，默认的端口是6379.\nDocker Redis 服务器端和其他主流数据库一样可以很方便的放到 Docker 容器里运行。\n下载 Redis 镜像\n1 sudo docker pull redis 基于 redis 镜像运行容器\n1 sudo docker run --name redis-server -p 6379:6379 -d redis 镜像内部已经设置了运行redis-server和监听6379端口，所以不需要额外的设置项，只需要在运行容器时将端口暴露出来（与宿主机的6379端口或者自己指定的端口绑定）就可以了\nclient 端 shell 界面下输入redis-cli -h 127.0.0.1 -p 6379启动 redis-client。其中，-h指定服务器的地址，-p指定服务器的端口。\n基本操作命令 注意到，命令的操作字段是不区分大小写的。\nvalue 为 String 类型时。set key value用于为 key 设置值为 value。 get key用于获取键为 key 的值。 value 为 list类型时。lpush key value1 [value2]…用于将一个或者多个值。 LPOP key可以移出并获取列表的第一个元素。这两个操作换成rpush和rpop可以向列表尾部插入元素。LLEN key可以获取 key 对应的列表的长度。LRANGE key start stop用于获取特定key 的指定范围的元素，特别地，LRANGE key 0 -1可以用来遍历列表。 更加详细的命令教程可以参考Redis教程-菜鸟教程\n编程语言支持 Java 在 Java 中主要通过 jedis 数据库驱动实现对 Redis 数据库的操作。\n安装依赖\n1 2 3 4 5 \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;redis.clients\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;jedis\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.9.0\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; JedisPool和 JedisClient jedis 使用 JedisPool 作为一个Redis连接池，用于解决对Redis进行并发访问时会发生连接超时、数据转换错误、阻塞、客户端关闭连接等问题。\n连接池初始化代码如下\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public static JedisPool getPool(String host, int port){ if(pool == null){ JedisPoolConfig config = new JedisPoolConfig(); //设置最大连接数 config.setMaxTotal(100); config.setMaxIdle(5); if(host == null){ pool = new JedisPool(config, \u0026#34;127.0.0.1\u0026#34;, port); }else{ pool = new JedisPool(config, host, port); } } return pool; } 要使用Client 时，使用pool.getResource()方法获取单个JedisClient实例，在此基础上对 Redis 数据库进行操作。\nGolang Go 语言中用来连接 Redis 数据库的库五花八门，主要分两个派系，将对 Redis 的操作封装成方法的，开发者通过调用库的方法实现对 Redis 数据库的操作；另一派是直接将 Redis 命令作为字符串提供给库来实现各种操作的。\n这里以\u0026quot;github.com/garyburd/redigo/redis\u0026quot;为例。\n安装依赖\n1 go get github.com/garyburd/redigo/redis 获取 redis pool实例\n1 2 3 4 5 6 7 8 9 10 11 12 redis.Pool{ MaxIdle:10, MaxActive:15, IdleTimeout:240*time.Second, Dial: func() (redis.Conn, error) { c, err := redis.Dial(\u0026#34;tcp\u0026#34;, redisURL) if err != nil{ return nil, err } return c, err }, } 从 pool 中获取 Client 实例\n1 rClient := redisPool.Get() 执行操作\n1 rClient.Do(\u0026#34;LPUSH\u0026#34;,\u0026#34;key\u0026#34;, \u0026#34;value\u0026#34;) Do 方法用于执行对 Redis 数据库的操作命令。方法的第一个参数字符串命令（就是在 shell 交互时的各种命令。\n","date":"2018-02-25T23:09:10Z","permalink":"/posts/redis/","title":"redis的杂七杂八"},{"content":" 之前机器学习课程布置的大作业是用尽可能多的模型来探索经典的手写数字识别问题。这里分享一下Keras的基本使用\nKeras简介 Keras 是由纯 Python 写成的，调用 TensorFlow 或者 Theano（最新版本还支持 CNTK）进行运算的类库。相比于 TensorFlow，Keras 使用起来更加简洁方便，便于调参，非常适合初学者进行机器学习探索。\n安装 在安装好 anaconda 的前提下，输入conda install keras即可进行安装。\n注意到，安装过程会自动判断机器是否已经安装好了TensorFlow，如果没有的话会自动进行安装。所以，如果想安装 TensorFlow GPU 版本加速计算过程的话，要先手动安装好 TensorFlow 的 GPU 版本，然后再安装 Keras。\n实现单层感知机 核心代码如下\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 batch_size = 128 classes = 10 epoch = 10 img_size = 28 * 28 print(\u0026#39;Loading Data...\u0026#39;) (X_train, y_train),(X_test,y_test) = mnist.load_data() X_train = X_train.reshape(y_train.shape[0], img_size).astype(\u0026#39;float32\u0026#39;) / 255 X_test = X_test.reshape(y_test.shape[0], img_size).astype(\u0026#39;float32\u0026#39;) / 255 #encode labels Y_train = np_utils.to_categorical(y_train,classes) Y_test = np_utils.to_categorical(y_test,classes) model = Sequential([Dense(10, input_shape=(img_size,), activation=\u0026#39;softmax\u0026#39;),]) model.compile(optimizer=\u0026#39;rmsprop\u0026#39;, loss=\u0026#39;mean_absolute_error\u0026#39;, metrics=[\u0026#39;accuracy\u0026#39;]) print(\u0026#34;Training...\u0026#34;) model.fit(X_train, Y_train,batch_size=batch_size, epochs=epoch, verbose=1, validation_data=(X_test,Y_test)) score = model.evaluate(X_test,Y_test,verbose=0) print(\u0026#39;accuracy: {}\u0026#39;.format(score[1])) 前面很大一部分都是进行数据加载和处理，与模型有关的代码只有三行\nmodel = Sequential([Dense(10, input_shape=(img_size,), activation='softmax'),])这一行是模型基本形态的定义，以图像的 size 作为输入，激活函数采用 softmax。\nmodel.compile(optimizer='rmsprop', loss='mean_absolute_error', metrics=['accuracy'])这一行则是对模型的微观参数进行客制化。optimizer指定的是优化策略，rmsprop是一种改进的随机梯度下降策略。loss指的是损失函数。metrics是评估方法，这里用准确率进行评估。\nmodel.fit(X_train, Y_train,batch_size=batch_size, epochs=epoch, verbose=1, validation_data=(X_test,Y_test))这一句是训练过程，指定训练数据，训练轮次（迭代次数），是否输出训练过程，验证数据。\n多层全连接网络 核心代码部分\n1 2 3 4 5 6 7 8 model = Sequential([Dense(512,input_shape=(img_size,)), Activation(\u0026#39;relu\u0026#39;), Dropout(0.2), Dense(512, input_shape=(512,)), Activation(\u0026#39;relu\u0026#39;), Dropout(0.2), Dense(10,input_shape=(512,),activation=\u0026#39;softmax\u0026#39;) ]) 每一个 Dense 都是一个神经元训练层。训练层输出接 ReLU 激活函数层。如此类推。最后一层接单层感知机获取结果。值得注意的是两个 Dropout 层，用于应付过拟合问题，经过 Dropout 层会随机丢弃数据集中一定比率的激活值，同时将剩余的神经元的输出进行放大。\n卷积神经网络 1 2 3 4 5 6 7 8 9 10 11 model = Sequential() model.add(Conv2D(32, kernel_size=(3, 3), activation=\u0026#39;relu\u0026#39;, input_shape=input_shape)) model.add(Conv2D(64, (3, 3), activation=\u0026#39;relu\u0026#39;)) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Dropout(0.25)) model.add(Flatten()) model.add(Dense(128, activation=\u0026#39;relu\u0026#39;)) model.add(Dropout(0.5)) model.add(Dense(num_classes, activation=\u0026#39;softmax\u0026#39;)) 卷积神经网络中主角变成了 Conv2D （卷积层）和 Pooling 层（池化）。\n","date":"2018-01-23T18:42:42Z","permalink":"/posts/aml/","title":"Keras 手写数字识别"},{"content":"2018考研的初试已经结束了，平时看考研群里已经有很多人在讨论在焦虑机试应该怎么复习，作为过来人在这里就随便说说自己的一些经验吧。\n实现说明一下，这篇文章基本就是个扫盲，不能保证你看完文章就能从 A0变 AK，但帮助你脱离新手区，扫除对机试的恐惧，保个底让机试不会成为复试阶段的软肋还是可以的。\n简单介绍 首先得先了解机试的基本情况。按照去年的考试风格，是两个小时四道题的 OJ（Online Judge）形式，也就是在线提交，在线判题返回程序在测试结果集的运行结果。AC（accept）代表正确解题，WA（Wrong Answer）表示错误答案，另外还有超时、超出内存空间等等结果。所有考试前准备的目的，就是为了更多的 AC，或者保底情况，避免 A0。\n编程语言 该用什么语言 北邮 OJ 平台可以用的编程语言有三种，C（gcc4.8），C++（g++ 4.8）和 Java（注意：没有 Python，没有 JavaScript）。另外，C++是不支持 c++11的，Java 只支持到 Java6。\n在这个天煞的背景下，考虑到程序时间限制（1ms）和开发速度（避免无谓的造轮子），用 C + STL 是最理想的选择。\n展开来说，就是用 C 的那一套输入输出（scanf 和 printf），C 与 C++通用的循环控制、选择结构、数组等，在加上 C++独特的“宝具”——STL 标准库，来进行解题。提交的时候编译器选 g++即可。\nSTL 标准库内容非常多，只需要了解 Map，stack，list，queue就够了。\nIDE？不存在的 两个小时做四道题对大脑转数的要求还是挺高的，更何况在那种紧张的气氛和不熟悉的开发环境之下。要保证解题能够快狠准，就需要从现在开始培养一定的针对考试的编程习惯，包括 编辑器和编译器、调试器的使用等等。\n首先，抛弃手上所有的 IDE，包括但不限于 Visual Studio和 Clion，DevC++，或者只在疑难杂症的时候拿它们当单步调试的工具（但也不能依赖）。考试环境只提供了（没有智能提示的）devcpp，（长得贼丑的）CFree两种最“原始”的开发工具，也就拿来当代码高亮，保证括号补全没有基本的语法问题差不多了，很多现代 IDE、开发工具可以做的事它们一概做不了。\n要适应这种艰苦恶劣的考试环境，就得从准备机试的时候开始，把开发工具换成 VScode and g++。VScode 是微软提供的跨平台编辑器，有着漂亮的界面和基本的语法高亮功能，在配置各种插件之前基本可以拿来模拟考场的开发环境，用来编辑代码，而且还能保证练习的时候是比较舒服的。\n编译过程全部转到命令行用 g++完成。\n调试 两个最基本额调试手段：打印大法和单步调试大法。个人推荐第一种。单步调试大法需要掌握 gdb这个 g++配套的调试工具，相对来说比较费时间（无论上手还是在考场上使用），而且比较容易出一些奇奇怪怪的问题。想成为 AK 达人的话，倒是必须掌握的。\n打印大法就是在关键步骤将关键变量输出的方法，简单易行，只要注意提交之前注释掉代码就OK。\n参考教材 注意到，机试是可以带任何纸质打印资料的，一本简介明了的语言参考指南显得非常重要。\n抛弃所有国内教科书，包括但不限于谭浩强，除非你想拿成绩开玩笑在考试的时候验证一下int a++++会不会报错。\n在这里，只推荐K\u0026amp;R The C Programming Language小薄本。将 ANSI C 的所有内容都讲得很透彻而简洁，里面的习题也可以作为入门练手。\n能力层级 这一部分提供平时训练刷题的参考方向。列举我在去年准备的时候看过的一些题型，具体知识和代码在王道机试指南和算法竞赛入门经典介绍的比较详尽。\n基本输入输出 OJ 的输入输出风格可是能玩死不少人的，怎么保证循环接收输入，接收特定符号能退出，每一轮输入怎么界定，怎么输出小数点后三位浮点数，输出的时候删掉无谓信息（比如句子最后的致命空格），等等等等，都是值得关注的内容，也是首先要练习的。所幸 scanf 和 printf 函数在 KR 里面已经介绍得非常详尽，对照着看和练习就行。\n数字和数组处理 数字部分有点像小学数学的找规律填数，也会夹带私货弄些奇形怪状的浮点数处理，数组处理方面典型例子就是找最大最小数，找次小数，奇偶数分离这些。一般都在签到题出现。\n日期问题 闰年问题，星期几问题等等。\n字符串处理 翻转字符串，回文字符串判断，甚至字符串匹配、简易正则表达式识别、字符串搜索都是有可能出现的，活用 std::string 和 char 数组的下标嗯。\n模板题 图论的 D 算法 F 算法，深度优先搜索，矩阵乘法等等。这种基本都是最终 boss 级别，因为很多 ACM 资料都会有典型的算法题目，代码可以直接套用，改改关键变量就可以，所以称为模板题。\n特别提名：模拟题 模拟题，可不是模拟卷子，而是一类型模拟计算机内部操作比如进程调度，死锁识别等的题目，印象最深刻就是去年最后一题算进程完成时间的。\n思维方法 这里介绍一些玄学的东西，也是机试对以后的开发生涯最有帮助的东西\n边界值控制和处理 刚开始接触 OJ 的时候很容易会遇到本地编译没问题，提供的测试数据也能获得预期结果可是提交之后就是 WA 这种百思不得其解的问题，根源便在于边界值考虑不周全，比如整数0，范围的边界，字符串中的空串等等，解决之道便是通过大量的练习，对每个算法题首先花上几秒考虑可能的边界情况和特殊情况，久而久之形成严密的思维。\n时间性能 1ms 的时间限制，看起来非常的充分，那只是还没遇到大规模输入。在那种几万甚至十万级别的数据（OJ 上真的会有），就算是$O(n^3)$的算法，翻车也是随时随地的。\n应对这个问题，得对计算机内部执行过程有最基本的认识，更好一点的得对算法的时间复杂度有认识，优化起来才不会像无头苍蝇一样。\n还是祭出CSAPP，里面对程序优化的介绍比较详细，充分利用 Cache 可以编写更高效程序。\n奇技淫巧 这里特别提名位运算，关键时刻可以省下大量的时间。\n安利Hackers\u0026rsquo; Delight\n基本的算法设计思想 递归，动态规划，不一而足，还是那句话，需要不断的刷题积累经验。\n","date":"2018-01-08T18:13:08Z","permalink":"/posts/buptoj/","title":"网研机试101"},{"content":"","date":"2017-12-06T21:30:36Z","permalink":"/posts/linuxnv/","title":"linuxnv"},{"content":" TensorFlow 是一套开源的机器学习工具。一般来说只用 TensorFlow 的话配置运行环境什么的并没有特别坑的地方，但如果想用到 GPU 加速计算的话配置起来就要费好大一番力气了，还经常遇到各种版本不兼容、找不到依赖关系等问题，让人头疼。而 Docker 刚好是解决开源软件各种依赖关系的神物，NVIDIA 刚好又有工具能让容器用上 GPU 进行计算。\n下面分享配置过程。\n测试环境是 GTX850M+Ubuntu16.04.3+CUDA9.0+GeForce 384.00\n驱动及 CUDA 安装过程参考即将到来的另一篇文章。\n安装 Docker 可以用curl -sSL https://get.daocloud.io/docker | sh这条命令快速安装 Docker，不过，这个安装脚本默认会安装最新版本的 Docker（当前是17.11.0 docker-ce），而 NVIDIA Docker 并不支持这个新版本（跪\n所以要进行一下卸载再降级操作\u0026hellip;\n1 2 3 4 5 6 # uninstall docker sudo apt-get purge docker-ce # 查看软件库中可用的历史版本 sudo apt-cache policy docker-ce # install docker-ce 17.09 sudo apt-get install -y docker-ce=17.09.0~ce-0~ubuntu 值得一提的是17年的某个版本开始，docker 的软件包统一到 docker-ce（社区）和 docker-ee（付费企业版）上面来了，开发使用的主要以 docker-ce 为主，网上很多教程（尤其是2016年、2016年的）说到安装 docker 的软件包名叫例如 lxc-docker docker.io 等的都是老旧版本的。\n安装 NVIDIA-docker Nvidia-Docker是老黄提供的一套在 Docker 上制造跑核弹的工具（。\n简单来说，这套工具提供了一个运行时，用来连接 Docker 容器和物理设备的 GPU 资源，使得 Docker 容器可以直接访问、调用物理机的 GPU 资源进行密集型计算操作。\n安装步骤如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 # Add the package repositories 添加软件仓库 curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | \\ sudo apt-key add - curl -s -L https://nvidia.github.io/nvidia-docker/ubuntu16.04/amd64/nvidia-docker.list | \\ sudo tee /etc/apt/sources.list.d/nvidia-docker.list sudo apt-get update # Install nvidia-docker2 and reload the Docker daemon configuration sudo apt-get install -y nvidia-docker2 sudo pkill -SIGHUP dockerd # Test nvidia-smi with the latest official CUDA image docker run --runtime=nvidia --rm nvidia/cuda nvidia-smi 在 Ubuntu16.04测试通过。\n安装 TensorFlow 的 Docker 镜像 TensorFlow 官方提供了 for Docker 的镜像，里面集成了完整的依赖关系，免去了用pip安装各种包的烦恼。\n镜像包含很多 tag，常用的有下面几个：\ntensorflow/tensorflow:latest，运行环境是 python2.7，仅支持 CPU tensorflow/tensorflow:latest-gpu，运行环境是 python2.7，支持 GPU 计算 tensorflow/tensorflow:latest-py3，运行环境是 python3.5，仅支持 CPU tensorflow/tensorflow:latest-gpu-py3，运行环境是 python2.7，支持 调用GPU 我自己用的是最后一个。\n首先下载镜像下来。\n1 docker pull tensorflow/tensorflow:latest-gpu-py3 然后跑个 python3交互环境试试水\n1 docker run --runtime=nvidia --rm -it tensorflow/tensorflow:latest-gpu-py3 python3 在交互环境下输入import tensorflow as tf，如果没提示依赖库错误则说明安装成功。\n上面的—runtime=nvidia为调用 nvidia-docker 工具包（运行时）而不是标准运行时来运行镜像，只有加了这个选项才能调用 GPU。\n最后跑一下多重感知机训练手写数字识别。输出显示调用 GPU:0进行计算，说明配置一路顺风了。\n","date":"2017-12-06T21:30:22Z","permalink":"/posts/dockertf/","title":"TensorFlow For Docker 初体验"},{"content":"最近忙活了将近一个多月总算把计算语言学布置的最大概率汉语切分作业写完了，虽然中途一波三折，还发生了很多五光十色奇形怪状让人难忘的事情，所幸最后还是比较完整的写了出来，也学到了不少的知识。因此便有了这个系列的文章。\n在这篇给出这系列文章的导航帖汇总。\n至于代码嘛，等交完实验报告再说嗯。。\n最大概率汉语切分算法研究（一）词典构建\n最大概率汉语切分算法研究（二）BiGram语言模型\n最大概率汉语切分算法研究（三）有向无环图（DAG）与最优左近邻词\n最大概率汉语切分算法研究(四）FMM 与 BMM 在分词中的应用\n","date":"2017-11-27T21:48:13Z","permalink":"/posts/mpseg0/","title":"最大概率汉语切分算法研究-(0)-概览"},{"content":"本篇继续讨论对输入句子的处理问题。FMM 和 BMM 是指对输入句子分别找前向最长词和后向最长词，某种程度上来说属于贪心算法的一种，比较惊喜的地方是两者结合常常能获得比较不错的分词效果。\nFMM FMM，可以理解成前向（Forward）最长词，就是对一个句子，每次切分找词的时候，都是从前往后“切出”最长的词和剩下的子句，例如：\n“有意见分歧”这句话，用 FMM 进行切分找第一个词的时候就会切分成：\n有意/见分歧\n只利用 FMM 进行分词时，对每次切分后的子句都反复寻找最长前缀词，直到子句为空。注意到，FMM 得到的切分序列是唯一的。\nBMM BMM，可以理解成后向（Backward）最长词，就是对一个句子进行切分的时候，都是从后往前“切出”最长词和剩下的子句，例如\n“有意见分歧”这句话，用 BMM 切分的时候会分成：\n有意见/分歧\n其中，“分歧”是寻找到的“第一个词”，“有意见”是待切分的子句。\n用 BMM 分词的时候，对每次切分后的子句都反复寻找最长后缀词，直到句子为空。\nFMM 与 BMM 组合分词 只用 FMM 或 BMM 进行分词的话，由于算法本身“贪心”的属性，往往得不到最理想的切分结果。所以在进行汉语切分的时候，可以将两个算法结合使用，用于发现歧义（因为对于没有歧义的句子，FMM 和 BMM 得到的切分结果一定是一致的），也可以将两个算法得到的序列计算整句话的概率（利用 BiGram 模型），选取概率较大者作为最终的切分结果。\n","date":"2017-11-25T18:04:43Z","permalink":"/posts/mpseg4/","title":"最大概率汉语切分算法研究(四）FMM 与 BMM 在分词中的应用"},{"content":"本篇讨论的是对特定输入句子进行的处理过程。基本思想是对特定输入句子从前往后遍历找出所有词构成有向无环图，然后从最后一个词开始往前找每个词的“最优左近邻词”构成一个完整的词语序列。\nDAG DAG，也就是有向无环图，可以用来记录一个句子的不同的切分状态集合，所谓的“向”就是句子从开始到结束的方向。假定句子的开始符为S，以“他们有意见分歧”这个句子为例，可以得到如下的有向无环图：\n在生成一个句子的有向无环图的过程中，利用 BiGram 模型计算所有的转移概率并进行储存。\n之后，从后往前回溯，寻找每个词的最优左近邻词，直到‘S’字符。结束。\n从句子生成 DAG 假设开始标识符为‘S’。\n对于特定的一条输入句子sentence，原始问题可以理解成：由sentence和其前一个词‘S’构建有向无环图。\n对 sentence进行分词操作，假设找到的第一个词为firstWord，输入句子 sentence便可切分为 firstWord + subsentence两部分，其中firstWord作为 subsentence的“前一个词”。原始问题便可归约为由firstWord 和 subsentence构建有向无环图（构建结果作为最终 DAG 的子图）。\n根据上面的分析，构建完整DAG 的过程是一个递归求解的过程，完整的算法步骤描述如下（简记为 Alg-1)：\n输入句子 sentence 以开始标记符“S”和 sentence 开始构建 DAG。（“S”作为整个图的开始结点，“源”点） 对 sentence从前往后找“第一个词”（firstWord），找到第一个词后，将 sentence划分为 firstWord 和 subsentence两部分。firstWord即可作为最终 DAG 的一个结点。 对 subsentence重复第三步的切分操作（寻找第一个词和子句），找到的“第一个词”作为最终 DAG 的结点，对子句继续切分（递归）。 递归结束条件：子句为空。 结束时，将所有最后一个词放入 endSet（结束词集合）里。 在 Alg-1的步骤3中要寻找“第一个词”，要注意的是，这个寻找过程不是一次性的，因为“第一个词”可能有多种情况，譬如，“有意见分歧”这句话中，对于第一个词，存在着 有/意见分歧 和 有意/见分歧 这两种潜在可能的划分，需要一并进行考虑。因此，寻找词的过程是一个循环的过程，设定上限值为15（汉语中基本不存在长度超过15的词），从第一个字开始向前寻找（切片）：\n1 2 for i in range(1,15): word = sentence[:k] 如果“切”出来的 word在词典中，则作为一个子问题，这样才能保证最后生成的 DAG 是考虑了所有可能情况的（考虑了所有的歧义）。\n不过，这个“粗暴”的切分方式也存在着可见的缺陷。它会大幅度增大了有向无环图的规模（最坏情况下，所有的单字都作为图的一个结点）。对于长句子来说会极大的增大了生成时间，而且，这里用递归来生成子图，因此甚至存在递归过深导致堆栈溢出等问题，即使不是这样，也会显著的增加了程序运行时间。\n减少无用递归 经过多次痛苦的测试过程，摸索出减少无用递归的方法可以短暂缓解上面提到的缺陷。\n考虑如下句子：\n因为他们有意见分歧，会议的时间未能确定下来。\n虽然在中文语义上，可以以逗号为分隔直接划分成两个子问题再进行切分生成 DAG，但在计算机中还是视为一个句子进行处理。\n在“有意见分歧，”这里，存在至少两种切分：\n有/意见分歧 和 有意/见分歧\n无论这个子句如何切分，切分之后的两个子句\n意见分歧，···\n见分歧，···\n除了第一个词之外都是重叠的，生成的子图也是一样的，无需重复的递归进行处理。\n基于这种分析，在考虑生成 DAG 函数的关键参数：firstWord和 subsentence，将这两个参数构成一个元组（tuple），用一个集合记录下已经进行了“子图生成”的元组，每次要执行递归前，首先判断关键参数组合是否在集合中，如果在集合中则直接跳过，否则再执行递归过程，执行完毕后将关键参数组合添加到集合中。\n最优左近邻词（bLAW） 考虑词序列\n$$w_1,w_2,w_3,…,w_{i-1},w_i,…,w_n$$$w_{i-1}$即为$w_i$的左近邻词。\n用输入句子生成 DAG 之后，对结束词集合中的每一个词，从后往前回溯，寻找每一个词的最优左近邻词，直到“S”结束，得到潜在的切分序列。如果切分序列不止一个的时候，则计算每个切分句子的概率，取概率较大者作为切分结果序列。\n如何寻找最优左近邻词？ 考虑累计概率计算公式：\n$$P_a(w_i)=P_a (w_{i-1})*P(w_i/w_{i-1})$$对每一个左近邻词（在 DAG 中表示为一个结点的所有前向相邻结点），计算累积概率，取概率较大者作为最优左近邻词。\n潜在问题 以下潜在问题的讨论仅限于本文讨论的 DAG 和寻找左近邻词的算法，并不一定 LAW 这个概念本身可能存在的问题。\n“最优”只考虑了局部的情况，在语料库生成的搭配词典规模受到限制的场合，基于语料库训练出来的 BiGram 模型计算出来的累积概率与实际应用场景中“本该有”的概率存在较大差异，这种差异将直接反映在切分结果上。 生成 DAG 的过程以词本身而不是词在句子中的下标序号作为图的结点，对于一个存在重复词的句子，从后往前找左近邻词时可能进入死循环中。解决办法是每次寻找词的时候将这个词与它“本该”出现在句子中的位置进行比较再决定是不是 LAW 参考资料 jieba中文分词源码分析（三） 王小捷老师计算语言学课程相关课件资料 ","date":"2017-11-25T16:51:09Z","permalink":"/posts/mpseg3/","title":"最大概率汉语切分算法研究（三）有向无环图（DAG）与最优左近邻词"},{"content":"BiGram 语言模型，也就是二元语法模型，起源于 NGram，属于 N = 2的情况。基本思想是当前词依赖于仅前一个词的出现概率。\n模型简介 N 元语法模型属于概率模型的一种。在 Ngram 语言模型的概念里，我们假设当前词出现的概率依赖于前面N-1个单词。\n概率推导 假设有单词序列$w_1,w_2,…,w_n$,将每个单词在它本身位置的出现看成一个独立事件，则这个单词串出现的概率可以表示为$P(w_1,w_2,…,w_n)$\n一般情况下将这个单词序列简记为$w_1^n$，由概率的链式分解规则，有\n$P(w_1^n) = P(w_1)P(w_2|w_1)P(w_3|w_1^2)···P(w_n|w_1^{n-1})$\n在概率推导公式中，令$n=2$，式子可以简化为$P(w_1^n)=P(w_n|w_{n-1})$\n说人话？？？ 上面的概率推导很容易让人一头雾水，以“我喜欢你”这个句子为例，“我喜欢你”这个句子出现的概率，可以转化成“我喜欢”这个子句后面出现“你”这个词的概率，在二元语法模型中，就可以进一步等价成“喜欢”这个词后面出现“你”的概率。\n也就是说，二元语法模型，将一句话出现的可能性，转化成一系列词语搭配出现的可能性。\n模型训练 由频率估计概率的方法，对于给定的语料库，不难得出如下公式：\n$P(w_n|w_{n-1})=\\frac{C(w_{n-1}w_n)}{C(w_n)}$\n还是以“喜欢你”这个句子为例，可以得到：\n$P(你|喜欢)=\\frac{\u0026ldquo;喜欢你\u0026quot;这个搭配在语料库中的出现次数}{所有以“喜欢”开头的双词搭配总数}$\n因此，训练的目标就是对于给定的语料库和词典，统计整个语料库中的所有双词搭配和对于具体的每一个词，统计以该词开头的所有双词搭配数目。也就是一个数数的过程。\n平滑技术 上面提及的 N 元语法的问题在于“数数”这个过程对语料库是强依赖的，而每个特定的语料库都是有限的，肯定无法覆盖汉语中出现的所有双词搭配。所以便有了平滑技术，为“零概率的二元语法”指派非零概率，也就是在计数后进行概率转换之前为计数为0的指派一个非零的计数值。\n在这里使用了最简单的加一平滑，在二元计数矩阵中，归一化计算概率之前为所有的计数加一。\n计算机编程实现 模型表示 在开发分词工具中，采用了 pandas 库提供的 DataFrame 这个二维的数据接口来表示整个模型，index部分是词典中的每一个词，col部分包含了两列，第一列是每个词出现的总数，第二列以字典类型来表示每个词所有可能的“下一个词”和对应出现次数。\n平滑技术的处理 假设词典里面有 $m$个词，按照上面的描述，需要一个$m \\times m$的矩阵来作为二元语法矩阵，加一平滑的时候遍历整个矩阵为每个元素加一。然而，从199801这个语料库中统计出来的词个数已经高达50000+，加载这么高维度的矩阵对于普通计算机来说显然是一笔很大的消耗。\n而在这个模型表示中，只储存了所有出现过的“双词”搭配，将对未出现的搭配“赋值为1”的过程放在了概率计算部分，加快了模型的加载速度。\n参考资料 JMBook-自然语言处理综论\n","date":"2017-11-25T16:51:06Z","permalink":"/posts/mpseg2/","title":"最大概率汉语切分算法研究（二）BiGram语言模型"},{"content":"最近忙活了将近一个多月总算把计算语言学布置的最大概率汉语切分作业写完了，虽然中途一波三折，还发生了很多让人难忘的事情，所幸最后还是比较完整的写了出来，也学到了不少的知识。因此便有了这个系列的文章。\n问题 基于最大概率的汉语切分工具的开发，是要利用计算语言学课上学到的知识，选用合适的模型开发一个汉语分词的工具并且进行代码测试与评估。\n阶段划分 一个完整的分词工具的开发包括以下几个步骤：\n选择合适的语料库 根据语料库构建词典 选择合适的分词模型和平滑技术 语料库切分：训练集和测试集 模型训练 模型测试，包括分词技术调整等，增强代码健壮性 在本项目中选用的语料库是人民日报199801的语料库，分词模型选择2-gram 模型，平滑技术选用了最简单易行的加一平滑，分词技术包括了左近邻词，FMM 和 BMM 等，具体在接下来涉及到了再具体谈。\n本文主要讨论的是从语料库构建词典的问题。\nTrieTree与词典 TrieTree，又名前缀词典树，是一个专门用于构建词典的数据结构，在这个数据结构上实现词语的添加和查找都可以获得非常高的效率。\nTrieTree 的 python 实现 在知乎的一个回答有非常简短的代码可以实现 TrieTree 的添加和查找功能，不过在这里要用 Trie 树构建词典的话还需要小小的修改，将该回答中叶子节点的\u0026rsquo;Exist\u0026rsquo;字段替换成\u0026rsquo;count\u0026rsquo;，用于统计每个词在语料库中出现的次数，每次添加一个词的时候，为后面的概率计算作铺垫。\n语料库分析 人民日报199801的语料库中，对于每一个词都是像泽民/nr这种形式，以/分割，前半部分为词，后半部分为该词的词性，词之间用两个空格进行分割。构建词典的时候，便可以读取语料库文件的每一行，用split()方法分割得到词列表，在每个词中再次执行分割获取每个词，添加到 Trie 树中，每次添加的时候首先进行判断，如果该词已经在树上，那么将该词的\u0026rsquo; count\u0026rsquo;字段计数加一，否则，将词加入到词典树中，并将计数字段设为1。\n词典写入到文件 将词典写入到文件，其实就是遍历 Trie 树，每个包含‘count’键的节点对应于一个词，将这些词写入到文件中的过程。具体代码参考如下\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def foreachTree(TTree,string,file): if \u0026#39;freq\u0026#39; in TTree : if len(TTree) == 1: print(string+\u0026#39; \u0026#39;+str(TTree[\u0026#39;freq\u0026#39;]),file=file) return else: print(string+\u0026#39; \u0026#39;+str(TTree[\u0026#39;freq\u0026#39;]),file=file) for kk in TTree: if kk == \u0026#39;freq\u0026#39;: pass else: foreachTree(TTree[kk],string+kk,file) else: for kk in TTree: foreachTree(TTree[kk],string+kk,file) 对于树这种递归定义的数据结构，解决问题的最方便的方法自然也是递归，由于汉语词汇挂在“树”上的时候，每个节点都是单个“字”，因此需要一个 String 变量作为函数参数，记录前面的遍历状态。\n要注意的地方是判断是否是完整词语的条件是‘count’或者‘freq’是否在该节点对应的字典中，而不是是否为‘叶子节点’。\n","date":"2017-11-25T16:51:02Z","permalink":"/posts/mpseg1/","title":"最大概率汉语切分算法研究（一）词典构建"},{"content":"libsvm是一个高性能的 svm（支持向量机）实现，与 TensorFlow 这些非常热门的机器学习框架不同的地方在于它提供的是可执行文件、类库和针对不同编程语言的接口， 在 macOS 上配置这套接口的时候经常会出现一些奇奇怪怪的问题。\n","date":"2017-11-22T15:18:29Z","permalink":"/posts/macsvm/","title":"macOS 配置 libsvm for Python"},{"content":"前言 WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。\n最近在项目中需要开发响应 WebSocket 的服务器程序来实现向客户端推送视频流的功能，刚好 Javalin 在最新版本中已经添加了对 WebSocket 的基本支持，于是有了这篇文章。\n依赖关系配置（基于 Maven） 和前面一样，使用 Maven 作为项目的包管理工具。要使 Javalin 框架的应用程序完整支持 WebSocket，需要额外添加以下的依赖关系包：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.eclipse.jetty.websocket\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;websocket-servlet\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;9.4.7.v20170914\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.eclipse.jetty.websocket\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;websocket-server\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;9.4.7.v20170914\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.eclipse.jetty.websocket\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;websocket-common\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;9.4.7.v20170914\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.eclipse.jetty.websocket\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;websocket-api\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;9.4.7.v20170914\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 为了避免一些千奇百怪的类、方法错误，建议加入的 jetty 依赖包的版本尽量保持严格一直（然而在目前的开发中还是遇到了奇奇怪怪的方法缺失错误，虽然不影响正常的运行不过看着还是蛮闹心的Orz）\n四个基本方法 和前面介绍的一样，Javalin 主要还是通过 lamada 表达式的方式来实现对特定路径的 WebSocket响应，基本的代码架构如下：\n1 2 3 4 5 6 7 8 9 10 app.ws(\u0026#34;/path\u0026#34;,ws -\u0026gt;{ ws.onConnect(session -\u0026gt; { //do something }); ws.onMessage((session,message) -\u0026gt; { }); ws.onClose(/*insert you lamada expr*/); ws.onError(/*insert you lamada expr*/); }); 四个以on开头的方法是实现 WebSocket 基本功能的方法，每个方法的基本含义如下：\nonConnect( session)用于处理客户端与服务器端的连接事件，用session来指代服务器端与具体某个客户端的连接本身。session中有个getRemoteEndpoint()的方法可以获取表征客户端的“端点”一类的东西，通过这个“端点”，可以随时“主动”的向客户端发送数据。这也是 WebSocket 的优势和魅力所在（一旦建立连接之后，服务器端可以根据具体情况随时主动的向客户端推送信息）。 onMessage(session,message)用于处理收到客户端信息时候的响应，session 的基本含义同上，message 表示来自客户端的信息。 onClose()用于处理连接关闭时的事件。 onError()用于发生错误时的响应。 其他 Javalin 当中对于 WebSocket 的实现，主要是建基于 jetty 对 WebSocket 的支持的基础上进行了二次封装，所以，Javalin目前不支持的一些方法、操作等都可以单独引用 jetty 提供的方法进行实现，只是这时候更加要注意依赖关系等问题了。\n除了绑定 lamada 表达式之外，app.ws()方法也可以将特定路径和实现了 WebSocket 响应的具体的类进行绑定，不过这个我还没有尝试过，理论上来说用起来会更加灵活，以后如果用到了也会写后续文章跟大家分享使用经验。\n","date":"2017-10-28T20:15:17Z","permalink":"/posts/javalin2/","title":"Javalin框架使用指南(2)"},{"content":"Javalin是一款建基于 jetty 的轻量级 RESTful 框架，支援 Java 和 Kotlin 编程语言，非常适合用来部署REST 风格的微服务程序，因为里面对 lamada 表达式的应用可以说是到了出神入化的地步，所以一直都是我最喜欢用的框架。这系列的文章主要是介绍如何在微服务开发中应用这套框架来进行开发。\n安装 Javalin 支持几乎所有常见的项目管理工具，出于一般性，文章中以 Maven 为例进行说明。\n安装框架本体\n在项目的 pom.xml文件中的\u0026lt;dependencies\u0026gt;\u0026lt;/dependencies\u0026gt;标签下粘贴以下代码。\n1 2 3 4 5 \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;io.javalin\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;javalin\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;0.5.4\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 这是官网中介绍的安装方法，要注意到，仅仅粘贴了以上代码段的话，会遇到两个问题：\nmaven 默认的 JVM 环境是 JDK1.6，是不支持 Lamada 表达式这种尤物的，我们需要在项目中添加 Java8支持。\n在pom.xml中粘贴以下代码：\n1 2 3 4 5 6 7 8 9 10 11 12 \u0026lt;build\u0026gt; \u0026lt;plugins\u0026gt; \u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;org.apache.maven.plugins\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;maven-compiler-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;source\u0026gt;1.8\u0026lt;/source\u0026gt; \u0026lt;target\u0026gt;1.8\u0026lt;/target\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/plugin\u0026gt; \u0026lt;/plugins\u0026gt; \u0026lt;/build\u0026gt; 要注意到这段代码和\u0026lt;dependencies\u0026gt;\u0026lt;/dependencies\u0026gt;标签段是并行而不是包含关系的。\n在运行过程中可能会遇到以下错误提示：\n1 SLF4J: Failed to load class \u0026#34;org.slf4j.impl.StaticLoggerBinder\u0026#34;.SLF4J: Defaulting to no-operation (NOP) logger implementationSLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details. ​\n​\n虽然这个错误提示不会影响程序的正常运行，但强迫症总是让人不爽，解决方法是添加这个依赖关系\n1 2 3 4 5 \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.slf4j\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;slf4j-simple\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.7.25\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 经过以上配置之后，就可以跑起第一段程序了\nHelloWorld HelloWorld 是开发者学习新的编程语言、框架所绕不过去的坎，用 Javalin 开发一个 GET 方法访问根路径返回“helloworld”的 REST 服务器非常的简单。示例代码如下：\n1 2 3 4 5 6 7 8 import io.javalin.Javalin; public class HelloWorld { public static void main(String[] args) { Javalin app = Javalin.start(7000); app.get(\u0026#34;/\u0026#34;, ctx -\u0026gt; ctx.result(\u0026#34;Hello World\u0026#34;)); } } 从代码中可以看出，通过一个 lamada 表达式就可以完成指定路径到具体响应方法的绑定操作，还有什么比这个更让 RESTful 微服务开发者拍手称快的呢。\n简单说明一下，框架中用 Javalin 类型的一个对象实例来完成整个 RESTful 风格的服务器的全部操作。上述代码中，首先是实例化了一个名为app的 Javalin类型实体，然后调用了get(String path, lamada expression)，以 lamada 表达式的表示一个具体的响应操作，以字符串的形式表示具体的路径，将两者绑定到一体。方法名称get表示响应 REST 规范中的 GET 请求。\n此外，ctx参数指代的是连接上下文，调用result()方法可以将字符串塞进响应体中进行返回。\nMORE Javalin 框架的安装和第一个服务器程序都可以非常简便的就完成了，但这只是一个开始，通过 Javalin 框架可以完成非常复杂的响应逻辑，而且最近的更新版本中还加入了对 WebSocket 服务器的支持。接下来的文章将会介绍如何进行进一步的开发和遇到的常见问题等。\n1024节日快乐:halloween:\n","date":"2017-10-24T14:28:20Z","permalink":"/posts/javalin1/","title":"Javalin 框架使用指南（一）"},{"content":"前言 上次在vscode配置 c++ 开发环境(一):智能提示文章中介绍了如何在 vscode 中配置 c++语言的智能提示，时间又过了很久，vscode 用的也越来越得心应手，今天终于有机会更新这个系列的第二篇文章，讲讲如何配置includePath来避免不愉快的波浪线和怎么用 vscode 对 cpp 程序进行调试。\n头文件目录配置 在默认情况下，就算是已经将 cpp 开发相关的插件装好，用 VScode 打开一个 cpp 文件的时候，通常还会看到令人不悦的波浪线。\n刚开始的时候我也非常的头疼这个问题，后来在vscode 官方博客的文章中找到了解决方案。\n首先，在使用习惯上，要用 vscode 打开源代码所在的文件夹而不是打开单个文件，比如所，我有个Josephus.cpp源文件在cppcode文件夹下，那么就要在终端上输入code cppcode 用 vscode 打开这个文件夹，再对那个源代码文件进行编辑和处理，而避免直接code Josephus.cpp打开单个文件。（下面的相关配置都是基于这个使用习惯的基础之上的）\n然后，在文件夹下新建.vscode目录（如果该目录已经存在，则可以略过这一步）。\n在.vscode文件夹下，新建c_cpp_properties.jsonjson 配置文件，在文件中粘贴以下代码：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 { \u0026#34;configurations\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;Mac\u0026#34;, \u0026#34;includePath\u0026#34;: [ \u0026#34;/usr/include\u0026#34;, \u0026#34;/usr/local/include\u0026#34;, \u0026#34;/usr/include/c++/4.2.1\u0026#34; ], \u0026#34;browse\u0026#34;: { \u0026#34;limitSymbolsToIncludedHeaders\u0026#34;: true, \u0026#34;databaseFilename\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;path\u0026#34;: [ \u0026#34;/usr/include\u0026#34;, \u0026#34;/usr/local/include\u0026#34;, \u0026#34;${workspaceRoot}\u0026#34; ] }, \u0026#34;intelliSenseMode\u0026#34;: \u0026#34;clang-x64\u0026#34;, \u0026#34;macFrameworkPath\u0026#34;: [ \u0026#34;/System/Library/Frameworks\u0026#34;, \u0026#34;/Library/Frameworks\u0026#34; ] } ], \u0026#34;version\u0026#34;: 3 } 在这个配置文件中，includePath字段是指定 VScode\b 搜索头文件的目录，如果目录中不存在源代码里引用的头文件，则会有波浪线提示。\n所以，将用到的头文件所在的位置都添加到该字段中，就可以解决“波浪线”问题了。\n调试设置 在 Windows 上面用 VS 进行 C++开发的时候，最吸引人的一个功能是完善的调试功能，\b类似的功能在 VScode 上也可以实现。\n首先，在.vscode\b文件夹下新建 launch.json和tasks.json两个配置文件，其中，\blaunch.json文件是配置调试信息，tasks.json是用于配置编译信息。\ntasks.json文件下，粘贴以下代码：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 { \u0026#34;version\u0026#34;: \u0026#34;0.1.0\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;clang++\u0026#34;, \u0026#34;args\u0026#34;: [\u0026#34;-g\u0026#34;,\u0026#34;${file}\u0026#34;,\u0026#34;-o\u0026#34;,\u0026#34;${file}.out\u0026#34;], // 编译命令参数 \u0026#34;problemMatcher\u0026#34;: { \u0026#34;owner\u0026#34;: \u0026#34;cpp\u0026#34;, \u0026#34;fileLocation\u0026#34;: [\u0026#34;relative\u0026#34;, \u0026#34;${workspaceRoot}\u0026#34;], \u0026#34;pattern\u0026#34;: { \u0026#34;regexp\u0026#34;: \u0026#34;^(.*):(\\\\d+):(\\\\d+):\\\\s+(warning|error):\\\\s+(.*)$\u0026#34;, \u0026#34;file\u0026#34;: 1, \u0026#34;line\u0026#34;: 2, \u0026#34;column\u0026#34;: 3, \u0026#34;severity\u0026#34;: 4, \u0026#34;message\u0026#34;: 5 } } } 上述代码中，command字段是编译程序（编译命令），根据个人喜好可以填g++或 clang++（编译 C++），如果想编译 c，也可以填 gcc 或 clang。 args字段是编译时添加的指令，${file}.out\b\b中${file}指的是当前编辑文件的文件名。（如果是在 Windows 下面配置，需要写成${file}.exe）\nlaunch.json文件下，粘贴以下代码：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 { \u0026#34;version\u0026#34;: \u0026#34;0.2.0\u0026#34;, \u0026#34;configurations\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;(lldb) Launch\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;cppdbg\u0026#34;, \u0026#34;request\u0026#34;: \u0026#34;launch\u0026#34;, \u0026#34;program\u0026#34;: \u0026#34;${file}.out\u0026#34;, \u0026#34;args\u0026#34;: [], \u0026#34;stopAtEntry\u0026#34;: false, \u0026#34;cwd\u0026#34;: \u0026#34;${workspaceRoot}\u0026#34;, \u0026#34;environment\u0026#34;: [], \u0026#34;externalConsole\u0026#34;: true, \u0026#34;MIMode\u0026#34;: \u0026#34;lldb\u0026#34; } ] } \u0026quot;program\u0026quot;: \u0026quot;${file}.out\u0026quot;字段表示调试的目标程序。\n两个文件都配置好之后，打开要调试的 cpp 文件，首先选择“任务”-\u0026gt;“运行任务…”,弹出的窗口中选择\bclang++,这一步是编译程序生成.out 文件的过程。\n运行完毕后，选择“调试”-\u0026gt;”\b启动调试“或按 F5快捷键，启动调试。如果调试界面能正常打开，表示配置没有问题，进入调试过程。\n参考网站 如何在VSCode内编译运行C++\n","date":"2017-10-18T14:28:45Z","permalink":"/posts/vscode2/","title":"vscode配置 c++ 开发环境(二):调试和头文件设置"},{"content":"前言 过去一年多时间里一直忙着准备考研、OJ、毕设这些东西，都没时间在微软的技术方面进行更深入的学习。现在很多事情都尘埃落定之后，终于可以重操旧业继续传教之路。因为研究生研究的方向跟 docker 有些关系，自己也觉得这玩意挺有意思的。所以，以后的一大段时间里都会探索.net 跟 docker 的结合的相关应用。这一系列文章如果没有特别说明，都是以 macOS 为主要的开发环境，也算是为微软的跨平台大业添砖加瓦了。\n这篇算是一个起点，探讨在 macOS 环境下进行开发的相关配置。\n安装 Docker 环境 现在在 macOS 上安装 docker 已经没有以前那么复杂了。基本上就是下载 dmg 文件回来拖拖鼠标的事儿。在毕设那段时间的体验中，甚至觉得在 macOS 上安装 Docker 是最为简便快捷，很难出什么幺蛾子的。：）\n下载地址：Docker 官网 经测试现在不用翻墙就可以访问啦。 要下载镜像的话还是配置加速器会比较快一些，我自己是配了阿里云提供的一个加速器地址。注册阿里云账号之后会免费提供。\n下载 dotnet 镜像 运行命令\n1 docker pull microsoft/dotnet 默认下载的是 latest 版本。\n基于镜像运行容器 在开发中我的习惯是将源代码放到本地的文件夹中，然后将这个文件夹挂载到 Docker 容器里面，这样在本地就可以用自己熟悉的环境对源代码各种修改，容器那边只需要重启一下就会自动运行新版本的代码，十分方便。\n运行以下命令：\n1 docker run -it -v `pwd`:/csharp -w /csharp microsoft/dotnet 命令中通过-it开启了实时交互模式，只是因为在家里没梯子\b，下载 dotnet core 的 SDK 贼慢，只好采取此方法。 实际应用的时候\b可以在本地 shell 先建立好项目编辑完源代码直接甩容器里跑最方便的。\n新建项目、编辑代码、运行 运行容器之后，在容器弹出的交互界面中，输入\n1 dotnet new console -o hwapp 命令的基本含义是以控制台为模板新建一个名为hwapp的项目。 在本机，用 vscode 打开工作目录下的 Program.cs 源文件，编辑代码，本次以输出“hello docker‘为例。 编辑完代码，保存之后，在\b容器的终端中运行dotnet run运行项目。\n//忽略第一次那个白痴错误（汗\n后记 到此为止，在 macOS 下用 Docker 运行.net 程序的第一次尝试就完成了。要补充的内容还有很多，比如配置 vscode 开发环境等等工作，也只能等到回校有畅快的开发环境再慢慢补完了。\n","date":"2017-08-27T18:46:52Z","permalink":"/posts/dotnetdocker/","title":"macOS下使用 docker 进行 CSharp 开发（一）"},{"content":"前言 最近在重构之前宇宙 mei毕设项目的注册中心，为了跟 Docker 容器技术无缝衔接将原先的 SQLServer 换成了与前端统一的 mongodb。让人不愉快的是，java 调用 mongodb 时遇到了好些问题，首先是 mongo版本更新3.0.0之后DB 相关的 API 已经废弃不用了，直接导致很多不够新的书籍都不具有参考价值；此外无论官网提供的 API 参考还是网络上很多教程，要么就是甩了一堆天花龙凤的代码，要么就是摸不着头脑的类列表。所以才有了这篇方便理顺思路的文章。\n基本概念 先讲清楚两个后面用到的最重要的两个类：BSON和Document。\nBSON BSON是 JSON 文件的二进制储存格式。在 mongodb 的 Java 驱动中，这种数据类型主要用于建立查询条件。比如说在 mongodb shell 里面.find({\u0026quot;id\u0026quot;:\u0026quot;0001\u0026quot;})这句话里面的{\u0026quot;id\u0026quot;:\u0026quot;0001\u0026quot;}部分对应在 Java 代码里面就是一个 BSON。\n查询条件里面有个很有意思的概念是操作算子/条件算子，具体留到另一篇文章再说。\nDocument 官方翻译是文档，就是对应于一条 JSON 格式数据。在使用中要注意的是这玩意不能用String 类型的 JSON 格式字符串来初始化，只能用 Map 类型或者单一键值对来初始化。\n操作 增（Insert） mongoCollection.insertOne(Document)\n直接插入 Document 类型的数据。要注意的是这个插入默认是无条件的，是可以连续两次插入完全一样的文档（因为在 mongodb 内部是用_id来判断两个文档）\n删（delete） mongoCollection.deleteOne(BSON)\n这个方法可以用来删除满足条件的一条数据，BSON 用于定义条件，比方说，要删除 id 字段为0001的数据，构造的 BSON 对象就是new BasicDBObject(\u0026quot;id\u0026quot;,\u0026quot;0001\u0026quot;)，完整的调用语句就是mongoCollection(new BasicDBObject(\u0026quot;id\u0026quot;,\u0026quot;0001\u0026quot;))。\n查（find） mongoCollection.find()\n默认不加参数时，会返回这个集合中的所有文档，返回类型是一个类似迭代器的东西，要遍历查询结果时可以这样写：\n1 2 3 4 FindIterable\u0026lt;Document\u0026gt; res = mongoCollection.find(); for(Document item : res){ //do something } 在这段代码中的 res，有个.first()方法，返回所有结果的第一个元素，如果结果是空集的时候，方法返回null。有意思的是，就算结果集是空集（找不到任何东西），res 也不会是 null。所以判断查找是否成功对 first()方法判空。\nmongoCollection.find(BSON)\n传入参数是客制化的查找条件（BSON 格式）\n改（update 或 replace） mongoCollection.replace(BSON,Document)\n第一个参数是查询条件，第二个参数是完整的文档，这个方法的作用是将满足条件的文档用传入参数中的文档来进行替换。\n.updateOne(BSON bson1,BSON bson2)\n调用这个方法时，第一个 bson 是查询条件，第二个 bson （bson1）是替换目标，这个方法执行结果是查询满足条件bson1的结果，然后将这些文档的 bson1字段用 bson2代替。\n这个方法执行起来可能会让人很无语，因为按照直觉，我们调用的时候可能更想做的是查找满足条件的文档，然后将里面的其他一些字段用 bson2替换掉。要实现这个功能也不是不可能的，不过要搞个嵌套 BasicDBObject，要用到$set操作算子。示例代码段如下：\n1 2 BasicDBObject opr = new BasicDBObject(\u0026#34;$set\u0026#34;,new BasicDBObject(\u0026#34;status\u0026#34;,\u0026#34;on\u0026#34;)); mongoCollection.updateOne(new BasicDBObject(\u0026#34;id\u0026#34;,\u0026#34;0005\u0026#34;),opr); 这段代码执行的结果是查找 id 是0005的文档，然后把这个文档中的 status 字段设为 on。\n","date":"2017-07-20T16:22:24Z","permalink":"/posts/mongodbj/","title":"Java Mongodb 使用指南（一）增删查改"},{"content":"前言 2016年3月初, 正式确立考研的想法并且选择了跨考计算机科学与技术, 到2017年3月底刘老师的一句“已录取”一锤定音, 过去的整整一年, 就像是一场漫长的战争, 中间有很多事情需要被记录和回忆, 所以就有了这篇. 套用了 Gundam UC 时代番外篇作为标题, 不仅仅是因为时间的巧合, 还是因为考研这件事和UC0079的一年战争一样, 损耗了极大量的时间,精力, 金钱等等等等.\n抉择 在2016年年初,对考研这件事上还是抱着极端反对的态度, 因为这意味着会有大半年的时间放弃很多事情, 只能困在自习室里, 反复的刷题, 看书, 背诵, 更何况通信原理还是我不甚喜欢的科目, 而那个时候看803计算机学科综合, 还是四门极难的天书.\n很庆幸的是, 改变会在三月份发生了. 那时候还想着一边跟父母说要考研, 一边默默去找实习找工作, 但第一次电话面试的时候, 就被狠狠的虐了一把, 操作系统, 计算机网络, 乃至很基本的计算机常识, 缺位的知识实在太多, 毕竟是大二的时候才培养出来的对计算机科学的兴趣, 平时虽然也写过一些洋洋得意的代码, 然而和科班出身的人还是没法比的. 那时候很后悔过去的三年没有好好的学习.\n后悔归后悔, 丢下的东西就要想办法补回来. 在那个时间点上, 弥补的唯一方法就只有考研这条路了, 跨考803, 就可以有至少四个月完整的时间去真正接触计算机科学最基本的那几门学科, 就算最后考不上, 也至少为自己积累一些底蕴.\n另外一个影响我决定的因素, 说起来有些不可思议又顺理成章. 过去三年, 由于某些众所周知的原因, 我一直在阅读各类关于思维, 学习习惯的心理学书籍, 看的书多了自然会手痒想实践,而都大三了也没几门专业课可以让我练手了, 剩下的都是通信网这么既没有兴趣也没什么大用的硬骨头. 于是, 我把目光放到了考研这件事上来, 从时间跨度,外部环境, 学习内容上看,确实也是不可多得的实验素材.\n就这样, 做出了一年战争的决定.\n改变世界的三本书 没有这三本书, 我一定不可能坚持下来到现在, 更别说有看上去还不错的成绩了.\n深入理解计算机系统( CSAPP) 在追求计算机科学的路上绝对值得大书特书的一本书。其实大二的时候就买了,然而因为各种原因没有啃下来, 到了2016年的三月份作出决定之后, 忽然一下子就有了不妨试试的想法, 开始阅读.\n对待这本书最好的方法当然是一边看书一边实践,在当时这么做的话很明显时间是相当不足的.无奈之下我只好采用了《如何阅读一本书》里面提到的快速阅读的方法.以尽可能快的速度通读全本, 记录每一章的重点, 寻找这本书的脉络和骨架.\n即使是这么的阅读,拖延症的我也是花了整整一个月的时间。通读过后, 竟也有醍醐灌顶般的顿悟, 对于计算机学科, 从最开始的\u0026quot;信息=位+上下文\u0026quot;的基本公理，到操作系统的一系列基本概念,乃至最后的服务器编程, 都有了比较清晰的认知, 后来的学习中也慢慢发现，正是这种认知，为803专业课的学习扫清了几乎所有的障碍。除此之外,还在准备机试的时候体会到好些微妙的副作用, 这个以后有机会再说.\n龟书——计算机科学的基础 这是图灵社区一本免费公版的电子书，讲的内容非常之杂而且数学化，从递归思想讲起，到整整一章快五十页的大 O 复杂度推导，到常见的数据结构定义，再到离散数学基础，正则表达式，关系模型，再到最后的一点点数电知识，涉及的领域无孔不入。更加神奇的是，85%的概念都用递归的形式给出或者证明。\n很遗憾，这本书没有看完，但最关键的地方都看了，涉及数据结构的都认真的看了一遍，尤其是大 O 那章，更是多遍过后仍然意犹未尽。\n这本书带来的,是对数据结构概念的认知颠覆。大学的计算机类课程里，除了微机原理之外，唯一不到90分的就只有数据结构了，因为当时教材没选好，上课也不认真听，学的云里雾里，知其然不知其所以然，除了几个可以喃喃自语的弗洛伊德算法，D 算法之外，一无所获。正是这本龟书，展现给我不同的世界，每一种数据结构的由来，都能够结合递归讲得一清二楚。更加是给了我到后来面对笔试算法题“只要是递归能写的我都能做出来”的自信。\n奇特的一生 一本很普通的传记，讲述的一个不那么普通的前苏联昆虫学家的故事。里面贯穿的主题是时间统计法，这个方法给我接下来的几个月复习带来了无尽的启迪，也开始了一段颇为清奇的复习之路。\n流水账模式-初试复习记录 铺垫说了这么多，也是时候到了各种考研经验贴喜闻乐见的所谓时间分配+学习方法型的行文模式了。不过，在这篇默示录里，我并不想把接下来的这些作为经验方法来说，因为我自以为，无论是时间分配还是学习方法，都是极为私人的事情，每个人的思维习惯和自己的紧密相连的，靠模仿是不可能真正成功的（这也是我为什么现在不粉德约科维奇的原因之一）。再者，和北邮人论坛里一众大神的经验,尤其是那位我相当欣赏,佩服的成功考研清华的姑娘相比，我在复习的时候付出的东西也真的太少太少了。\n八月份之前 各种浪，期间看了麻省理工大学的线性代数公开课（这也是值得大书特书的课程，具体放到后面再讲），快速阅读了《现代操作系统》，《计算机网络——自顶向下方法》，顺路背了下红宝书英语单词。\n八月份 回家之后，全面复习正式拉开大幕。（别人都是七月回家八月回校开始复习。。我的奇葩复习之路就这么开始了(⊙﹏⊙)b）\n在家二十五天时间，看完了一遍李永乐数学全书（没怎么做题，单纯记录一下重难点和奇技淫巧。。。），王道四本单科书看了两本半（差计算机组成后半部分和计算机网络全本，做每一节后面的选择题）。英语阅读看张剑的小黄文150篇，结果各种懵逼，只好作罢，继续背单词。\n平均每天复习时间295分钟。\n九月份到十月十五日 回到学校之后，开始了每天直奔图书馆的日子，开始做题之后，发现了自己在数学尤其是微积分方面的严重不足，邂逅了《张宇高数18讲》，开始把每天学习数学的时间都放在了阅读18讲和做660题打基础上面了，一天二十几题，有点低效率。\n专业课还是王道四本书，小题做完然后是极少的一些大题，然后看第二遍。没啥特别好说的。\n英语继续小黄文，每天两篇，平均一篇错俩，一个单元错6到8个，心态略崩。\n开始看政治大纲解析，抱着学累了娱乐的心态，画画重点，做做一千题。\n平均每天学习时间330分钟。\n十月中旬到十一月初 一年战争中最为关键的一段时期，在 UC 时代里面大概是 阿姆罗坐上了RX-78，NewType 开始引起联邦和吉翁的注意。\n在现实里也是这样，在这段时间，最重要的事情是投入时间做张宇《真题大全解30年数学真题》，在数学上收获了极为重要的自信。英语开始研究往年真题，第一遍。专业课的王道四本书二刷也完了，每天写一下阅读感悟之类的，把想了好久才想通的问题，看完特别叹为观止的问题记录一下。\n政治还是老样子。\n平均学习时间每天384分钟。\n十一月 白热化阶段。\n数学每天都在做真题，有时还放放羊，并不能实现每天一套的宏愿，到最后十二月之前也总算能彻彻底底的完成了第一遍。张宇八套卷跟风买了，做了两套多一点，也确实太难，坚持不下去。找到自己薄弱的地方，针对性的看张宇的视频。\n专业课开始撸小蓝书过去的十年408真题，也继续是选择大题拆分完成的思路，规定时间60分钟到75分钟完成选择题做完题分析一下不会的，做错的，也记录一下，顺带王道四本书挑重点三刷。\n英语，真题二刷，这次是规定好时间90分钟完成选择题部分。然后就是开始整理作文模板了。并没有怎么写作文。。太懒 orz\n政治，肖老八套卷上市，也就买回来做了，选择题部分认真做，错的也都不少，做完认真总结和回看大纲解析。风中劲草也上市了，看完了第一遍，记住的东西也并不多。\n平均每天复习458分钟。\n十二月 这个月可就是争夺阿尔巴空的时候了。\n数学没什么力气做四套卷八套卷，每天整理重做三十年真题中的错题，补补张宇的视频。然后看真题大全解的答案解析书。难题啃一啃。最后两周挑几份经典的往年真题重做一下，保持手感。\n专业课做王道的八套模拟题，比408真题难，做下来还是很有收获的。\n英语：练作文，练作文，练作文。翻译适当看看，真题可以再做一遍。\n政治：肖秀荣四套卷，考完出来才会真正感叹准确度可以有多高。\n平均复习时间每天497分钟。\n每天的时间安排 这里说的是复习阶段每天都适用的情况。\n早上雷打不动做数学，无论状态多好或者多不好。一般早上有效复习时间最多150来分钟，八点多快九点的时候到图书馆，十二点吃饭，只有十二月才能经常实现180分钟+的有效复习时间。\n下午是属于专业课和英语的时间，大概是60+90+25的套路，“60+90”是轮换制，比如说今天90分钟英语真题，60分钟专业课那么第二天就是60分钟英语90分钟专业课。25分钟是随心所欲的机动时间。\n晚上属于整理知识点的时间，把白天做错的题目都再过一遍，以及回顾之类的。比较轻松\n学科学习过程记录 相比时间安排这种私人化的东西，针对不同学科的学习方法反而是更加固定一些,毕竟学科的特点都摆在那儿.\n数学 数学一包括完整的微积分部分,概率论和数理统计，线性代数。一个一个来。\n微积分 前面也说了，微积分部分的基础学的相当的不扎实，即是是看完了一遍全书，做题的时候还是被打成筛子了。这里很感谢一本书，《张宇高数十八讲》，如果说全书是极为详尽的知识参考的话，十八讲就是提供了解题最需要的方法论内容。而且张宇的娓娓道来的风格，很适合缺乏解题方法的初学者入手学习。\n线性代数 MIT 线性代数公开课+黄皮线性代数第七版（外国教材）+分阶习题线性代数部分\n公开课是值得大书特书的部分。特别适合那种大一学线性代数最后通过了但还是一脸懵逼的同学。请扔掉所有从行列式入手的线性代数教材，这种从行列式再到矩阵的学习思路是及其反人类，也违反了线性代数学科最基本的原则的。\nMIT公开课从线性方程组，再到矩阵，再到行列式的线索讲述这门极具吸引力的学科，把矩阵，线性空间，线性方程组，向量彻底的联系起来，才能够真正学懂线性代数。里面对于行列式用三大性质+延伸推导运算性质的方式来定义的方法，彻底帮我扫除了大一入学第一周就要面对用双竖线包围的一大堆数和奇奇怪怪的运算性质的恐惧。\n概率论 《概率与统计》一本书就够了，反而没什么好说的。。。\n题集 660题-都是基础题，而且都是填空选择，拿来训练数学思维是相当有帮助的。一开始上手的时候千万不要因为题量大就产生恐惧心理，一天20道30道，坚持下来，就能发达。 30年真题——2017年刚好遇上了数学一放水的时候（习惯把这种年份比作快速硬地的比赛2333333333），所以往年真题的帮助特别大，难度适中，知识点覆盖全面。做的时候配合国誉双栏笔记本记录错题索引，得闲就翻一翻。到最后考试，发现每一道题都能在过去30年真题中找到解题方法。 四套卷八套卷：拿来观光奇技淫巧解题不错。 英语 单词+真题，学习语言的方法不过是足够多的词汇量积累和大量重复的练习，将自己暴露在语言环境当中。\n单词书 红宝书+20天搞定考研词汇+不背单词 app\n题集 其实，只需要真题。\n05-16年真题反复做，标注难词难句，理清结构，阅读题问题分类攻克\n作文 作文写得不大好，就算了不说太多了\n作文素材是一定要积累的。模板要看，但也得会创新，要形成自己的东西，或者至少能将不同的模板搅在一起。\n多总结，按照话题整理出不同的模板，例子，论证方法，这个是必须做的。追求高分的话还可以根据不同的论证方法按照不同行文方式来分类整理一次。\n计算机学科基础综合 这次考试专业课可是帮了大忙，本来还以为跨考的话专业课只要不拖后腿就行，结果貌似来了个网研院最高分，还是值得庆幸的。\n书籍 王道四本单科书+八套模拟题+408真题+803真题，多做几遍，务求看懂。 现代操作系统+计算机网络(自顶向下)，拿来补基础知识用的。数据结构和计算机组成直接在龟书和 CSAPP 上顺带看了，帮助很大。不过国外的教材知识全面，内容充实，适合在特别早的阶段看，6月之后再开始的话时间会很紧，消化不良就不好了。 计算机专业课实际上都是融会贯通的，一切统一于 CSAPP（误。所以803的东西看起来虽然很多，但千万不要产生恐惧心理，要时刻从系统的观点来看待理解学习的知识，会事半功倍。\n政治 背背背背背背\n书籍 大纲解析，肖秀荣精讲精练二选一 风中劲草+1000题（对于攻克选择题帮助会非常大） 肖老师八套卷四套卷：今年全面命中大题，不能不强烈推荐。四套卷大题必背，八套卷的选择题多做几次，大题多看，这儿肖老师的公众号讲得非常好，我就不班门弄斧了。 其实政治考的不算理想,选择题大翻车，所以更加没有什么学习方法可言了，毕竟在几乎整个复习阶段中政治都是拿来放松心情，拿来娱乐用的。\n复试 初试成绩出来之后就开始准备复试了。其实在初试结束彻底 happy 之后马上开始准备是相当好的，毕竟笔试6选4，时间紧任务重。再说诸如编译原理，计算机系统结构，人工智能这些课程，对于热爱计算机学科的人来说绝对是一道饕餮盛宴。\n另外一个就是机试，OJ 的输入输出处理，测试数据覆盖等方面与平时开发写代码是很大的不一样的，等初试成绩出来之后就到kAri OJ 熟悉考试方式，每天水水题，美滋滋。\n至于面试，运气还不错，碰到的题都是有点印象的东西，不至于哑口无言的尴尬局面，老师们也相当的友好。如果本科阶段多做些项目多参加一些竞赛的话会为面试增加更多的资本的。\n后记 能够保研还是千万别考研吧，牺牲的东西实在是太多太多了。\n还是那句话，写下默示录目的就是拿来回忆用的，谈不上什么学习方法，时间分配方法，更加不能算什么经验。里面可能最有用的就是提到的几本书了。学习方法始终都是非常私人的事情，希望本文可以帮助准备考研的每一个人都能找到属于自己的学习方法。\n如果有兴趣和我探讨心智思维相关的内容，或者交流计算机科学相关的东西，欢迎联系。\n微信：antarx75\n感谢 一年战争能够有好不错的成功，光靠自己是完全不可能的，很多的人和事给我带来了无尽的帮助。\n感谢小C 的图书馆座位，让我有四层大桌子的复习环境。\n坐我右边的曹菊苣，平时交流数学，得到了很多的启发。\n计算机学院事实上专业第一，大一就手撸操作系统的99壕，帮我解决了很多803的疑难杂症。\n感谢 Andy Murray 的2016年年终第一，伦敦的终极之战为我的12月打了很多鸡血。\n牛18带来的运气。\n陪伴我刷题的钢笔们：三文堂 ECO，580RB，写乐1031，百乐74，永生698，Lamy Vista，等等等等。\n墨水们：自由极乐，飞虎队，呆米家族，RK 铁胆\n一半以上时间都用来种树的 oneplus3。\n以及一时间可能想不起来的更多人和事。\n","date":"2017-03-27T21:08:12Z","permalink":"/posts/uc2017/","title":"一年战争默示录"},{"content":"题目大意 求给定高度为 n 的 AVL 树最少的结点数模$1e9+7$的值.\nHint: $(a+b)%p = (a%p) + (b%p)$\n难点和解题思路 原题目给出了关于 AVL 树的相关定义,帮助我们理解题目的相关概念. AVL树就是平衡的二叉排序树, 能够保证这棵树在满足二叉排序树基本特性的同时, 每一个非叶子节点的左右子树节点数之差不大于1.\n关于高度为$h$的 AVL 树的最少节点数,其实是有递推公式的, 就是$h[n] = h[n-1]+h[n-2]+1, n\u0026gt;2$\n根据这个递推公式, 就可以将问题从看似复杂的树问题转化为类似求解斐波那契数列的简单问题了.\n还有一个坑点, 就是题目给出的AVL树的高度可能会很高, 从数据规模上看, 斐波那契型的数列通项都是指数型增长的, \u0026ldquo;正常\u0026quot;的数据类型分分钟存不下这么大的数据.\n再仔细看题目, 要求的只是结果模$1e9+7$然后输出, 还给出了余数相关的定理来\u0026quot;暗示\u0026rdquo;, 不由得想到这样的思路, 首先建一个 long long 类型的大数组h[MAX_N], 高度为i 的 AVL 树的节点数对1e9+7取模的结果储存在 h[i]中, 然后用相同的递推关系计算数组的每一个元素. 而且, 还可以将取余操作简化为减法操作, 大大的降低算法的运行时间.\n代码 因为题目的原题和测试数据在考试结束之后就及时关闭了, 所以只能凭借记忆写下来.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include\u0026lt;iostream\u0026gt; #include\u0026lt;cstring\u0026gt; using namespace std; long long w = 1000000007; long long res[100050]; int main(){ int T; cin\u0026gt;\u0026gt;T; memset(res,0,sizeof(res)); res[1] = 1; res[2] = 2; for(int i=3;i\u0026lt;100050;i++){ res[i] = res[i-1]+res[i-2]; if(res[i]\u0026gt;w) res[i]-=w; } while(T--){ int h; cin\u0026gt;\u0026gt;h; cout\u0026lt;\u0026lt;res[h]\u0026lt;\u0026lt;endl; } } 注意事项 long long 类型最好还是使用 cout 来处理读写, 在本机调试时发现,用 printf(\u0026quot;%lld\u0026quot;)处理出现了奇奇怪怪的 bug, 用 cout 就没事了. 数组预处理放在循环以外进行, 否则很有可能超时. 上面的示例代码, 在 BOJ 测试时时间性能是59ms( 上限是1000ms) ","date":"2017-03-27T10:13:00Z","permalink":"/posts/2017c/","title":"2017考研-北邮网研院机试试题 ProblemC"},{"content":"前言 进行 OJ 练习以来做的最复杂的一个题目,A 完之后趁热打铁写个解题报告.\n题目描述 详情访问268.进程管理\n大体的意思就是实现操作系统中进程管理基本的 fork, kill 和查询功能\n输入输出处理 输入 第一行是整数 T, 表示有多少组输入数据.\n第二行是整数 N, 表示本组数据中有多少行命令.\n接下来每一行是一条命令.形式如下\nFORK PID_1 PID_2\nKILL PID\nQUERY PID\n难点一: 同时出现输入组数和操作的数量 这是在之前做的题目中没出现过的.解决办法如下面代码\n1 2 3 4 5 6 7 while(T--){ int n; scanf(\u0026#34;%d\u0026#34;,\u0026amp;n); while(n--){ //do something } } 难点二: 如何应对输入命令的不同格式 从给出的示例可见, FORK 后面有两个整形参数,其他两个命令后面都只有一个参数,这个时候如果只使用scanf()函数处理输入,会因为输入的变量个数不一致而出现奇奇怪怪的问题,所以转而先使用 gets( char*) 获取一整行命令读进 buff 数组,然后用 sscanf(char* source, char* fomat, …)进行处理.\n要注意的是,因为 scanf(\u0026quot;%d\u0026quot;,\u0026amp;n)之后会有个\\n 会塞在缓冲区, 影响 gets() 函数的正常读入,所以要先 getchar() 一下.\n另外, gets() 函数在 c11标准中不再被支持,使用之前要注意运行环境限制.\n输出 当输入命令为 QUERY PID 的形式时会产生输出,如果查询的进程存在则输出 Yes, 否则输出 No.( 注意大小写)\n解题思路和坑点 数据储存 首先要解决数据储存的问题.\n采用 bool 类型数组记录每个进程的存在情况, 并且使用下标记录进程号. map\u0026lt;int,vector\u0026lt;int\u0026gt; \u0026gt;来记录每个进程及其子进程的映射.按照操作系统课本的思想采用链表记录最为恰当,但在 OJ 这种特殊情况下, 自然可以选择更加\u0026quot;偷懒\u0026quot;的方法.更何况 STL 的性能还是相当可靠的. 对于每一条命令,用 sscanf 处理后将指令部分赋值到 string 变量, 方便判断. 坑点一: 0进程不能被删除 题目条件中说到0进程在任何情况下都是存在的,因此输入 KILL 0不会有任何响应.\n坑点二: 子进程的子进程要被删除 比如说如下以来关系0-\u0026gt;1-\u0026gt;2-\u0026gt;3-\u0026gt;4,杀死1进程之后,后继的2,3,4进程都必须被杀死,否则会导致 WA.\n使用递归删除的办法可以确保子进程的子进程会被安全删除.\n不算坑的坑点三 题目中还说到 KILL 指令中如果是不存在或者已经结束的进程,则不采取任何操作.这一点在本题中靠输入来保证了,所以不用在代码中额外处理.否则,需要额外的数组来记录每个进程的出现情况.\n程序代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 #include\u0026lt;cstdio\u0026gt; #include\u0026lt;string\u0026gt; #include\u0026lt;vector\u0026gt; #include\u0026lt;cstring\u0026gt; #include\u0026lt;map\u0026gt; using std::vector; using std::map; using std::string; char buff[20]; char cmd[10];; map\u0026lt;int,vector\u0026lt;int\u0026gt; \u0026gt; relation; bool pid[105]; void killKids(int x){ if(!relation[x].empty()){ //no empty vector\u0026lt;int\u0026gt;::iterator j; for(j=relation[x].begin();j!=relation[x].end();j++){ pid[*j] = false;//kill it killKids(*j);//kill them all } relation[x].clear(); } }// kill kids method int main(){ int T; scanf(\u0026#34;%d\u0026#34;,\u0026amp;T); while(T--){ relation.clear();//init memset(pid,0,sizeof(pid)); pid[0] = true; int n; scanf(\u0026#34;%d\u0026#34;,\u0026amp;n); getchar(); for(int z = n;z\u0026gt;0;z--){ int x,y; gets(buff); if(sscanf(buff,\u0026#34;%s%d%d\u0026#34;,cmd,\u0026amp;x,\u0026amp;y)==3){ //FORK command if(pid[x]){ //x exist relation[x].push_back(y); pid[y] = true; } }else{ string act = cmd; if(act == \u0026#34;QUERY\u0026#34;){ if(pid[x]==1){ puts(\u0026#34;Yes\u0026#34;); }else{ puts(\u0026#34;No\u0026#34;); } }else if(act == \u0026#34;KILL\u0026#34;){ //end all kids if(pid[x]\u0026amp;\u0026amp;(x!=0)){ killKids(x); pid[x] = false;//kill x } } } } } } ","date":"2017-03-11T20:36:09Z","permalink":"/posts/268/","title":"BOJ 解题报告-268进程管理"},{"content":"题目描述 给出两个时间(24小时制)，求第一个时间至少要经过多久才能到达第二个时间。给出的时间一定满足的形式，其中x和y分别代表小时和分钟。0≤x\u0026lt;24,0≤y\u0026lt;60。\n输入输出处理 输入格式hh:mm,也就是在数字时钟中最常见的24小时表示法, 对于这种输入形式, 可以仿照87题中的技巧通过 scanf(\u0026quot;%d:%d\u0026quot;,\u0026amp;hh,\u0026amp;mm)的形式将时间和分钟分别复制到两个整形变量当中.\n输出是最少经过的分钟数, 是一个整形变量,没有太多的技巧.\n思路 因为题目中要求输出的是分钟数,所以可以先将输入的两个时间换算成当天的分钟数,然后计算时间差就是所求.\n要注意的是起始时间大于结束时间的时候, 求出t1-t2要加上24*60(一天的分钟数).\n源代码 1 2 3 4 5 6 7 8 9 10 11 12 13 #include\u0026lt;cstdio\u0026gt; int main(){ int T,x1,y1,x2,y2; scanf(\u0026#34;%d\u0026#34;,\u0026amp;T); while(T--){ scanf(\u0026#34;%d:%d\u0026#34;,\u0026amp;x1,\u0026amp;y1); scanf(\u0026#34;%d:%d\u0026#34;,\u0026amp;x2,\u0026amp;y2); int t1 = x1*60+y1; int t2 = x2*60+y2; int res = ((t2\u0026gt;=t1)?(t2-t1):(t2-t1+24*60)); printf(\u0026#34;%d\\n\u0026#34;,res); } } ","date":"2017-03-07T20:23:30Z","permalink":"/posts/89/","title":"BOJ解题报告(2)-89-统计时间间隔"},{"content":"题目描述 请你计算出第X年Y月Z日是第X年的第几天。其中，1月1日是第一天，1月2日是第二天，以此类推。\n计算时请注意闰年的影响。对于非整百年，年数能整除4是闰年，否则不是闰年；对于整百年，年数能整除400是闰年，否则不是闰年。如1900年和1901年不是闰年，而2000年和2004年是闰年。\n题目链接\n思路 这是个典型的涉及闰年的日期问题, 要求解某一年是第几天.思路很直接,从月份入手,先计算前$ Y-1$个月一共有多少天,然后在加上这个月中这一天之前有多少天,就可以得到结果.\n输入输出处理 在所有 OJ 中,输入输出都是绕不过的坎,很多时候会因为输出处理不好而导致莫名其妙的 WA, 又有很多时候可以从输入格式中获得解题的灵感.\n回到本题,输入格式为 X:Y:Z的形式, X,Y,Z 都是整数, 以冒号隔开.这不由得让我们想到了可以利用 scanf 的格式字段做\u0026quot;手脚\u0026quot;, 直接将 X, Y, Z 读入到整形变量当中进行后续的处理.所以有了以下代码来处理日期的输入.\n1 2 int X,Y,Z; scanf(\u0026#34;%d:%d:%d\u0026#34;,\u0026amp;X,\u0026amp;Y,\u0026amp;Z); 输出方面,只需要输出天数,是一个整形变量,没有什么特别的技巧.\n注意事项 这部分描述题目中的\u0026quot;边沿\u0026quot;情况, 如果忽略这些情况,就会导致 WA\n闰年的处理 题目中也给出了提示.在代码中可以利用宏来判断闰年.\n1 #define ISYEAP(x) ((x%100!=0)\u0026amp;\u0026amp;(x%4==0))||(x%400==0)?1:0 使用宏的时候一定要注意, 编译器在编译的时候会将宏展开之后插到每一个调用宏的地方,为了避免由于运算符优先级问题带来奇奇怪怪的 bug, 建议前后用括号括起来,像这样( ISYEA(X))\n一月份 1月份的时候,天数即为当日的日期,直接输出 Z 字段即可.\n二月份 这也是个容易踩坑的地方,大家都知道闰年要算2月29天,但有时太匆忙的时候,会糊里糊涂把闰年1月2月的日期也会莫名其妙的加上了\u0026quot;1天\u0026quot;,导致 WA. 因此要单独处理.也可以通过构造合理的 if 语句和一月份的情况一起处理.\n对每个月的日子进行预处理 在程序开始之前可以定义一个数组储存每个月的天数, 计算日子时直接根据下标找到天数相加即可,可以减少代码量和运行时间.\n预处理的时候要注意, c 语言的数组下标从0开始, 而月份是从1月开始,所以可以构造一个13个元素的数组并且让第一个元素值为0避免不必要的麻烦.\n代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include \u0026lt;cstdio\u0026gt; #define ISYEAP(x) ((x%100!=0)\u0026amp;\u0026amp;(x%4==0))||(x%400==0)?1:0 int daysOfMonth[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31}; int main(){ int T; scanf(\u0026#34;%d\u0026#34;,\u0026amp;T); while(T--){ int yy,mm,dd; int days=0; scanf(\u0026#34;%d:%d:%d\u0026#34;,\u0026amp;yy,\u0026amp;mm,\u0026amp;dd); if(mm==1){ days = dd;//special situation }else{ for(int i = 1;i\u0026lt;mm;i++){ days+=daysOfMonth[i]; } days+=dd; if(ISYEAP(yy)){ if(mm\u0026gt;2){ days++; } } } printf(\u0026#34;%d\\n\u0026#34;,days); } } ","date":"2017-03-07T19:58:57Z","permalink":"/posts/87-cpp/","title":"BOJ 解题报告(1)-87-日期"},{"content":"#前言 vscode是微软推出的跨平台的编辑器, 开发者可以通过丰富的插件把它定制成最适合自己使用的开发环境. 对于我来说,它最吸引的地方之一就是可以为 Mac 还有 Linux 平台提供了与 Visual Studio 类似的智能提示功能.\n正文 首先, 下载对应系统的 VScode. 如果是 Mac 系统,那么请安装 xcode 命令行工具,如果是 Linux 系统,请安装clang 编译器. 安装 cpptools 和 C++clang 插件.其中, cpptools 可以提供代码检错等功能,而 IntelliSense 功能,是通过 C++clang 插件实现的. 按 command+,,打开用户配置文件 setting.json, 将以下代码拷贝进去. 1 2 3 4 5 6 7 8 9 { \u0026#34;clang.executable\u0026#34;: \u0026#34;/usr/bin/clang++\u0026#34;, \u0026#34;clang.completion.enable\u0026#34;: true, \u0026#34;clang.completion.triggerChars\u0026#34;: [ \u0026#34;.\u0026#34;, \u0026#34;:\u0026#34;, \u0026#34;\u0026gt;\u0026#34;], \u0026#34;clang.cxxflags\u0026#34;: [\u0026#34;-std=c++11\u0026#34;] } 要注意到配置文件是 json 格式,所以,如果之前已经有其它的设置,请按照 json 文件的语法格式将大括号里面的内容拷贝进去. 4. 重启应用程序,即可享受 vscode 带来的智能提示功能了.\n","date":"2017-02-18T18:48:20Z","permalink":"/posts/vscode/","title":"vscode配置 c++ 开发环境(一):智能提示"},{"content":"前言 每次跟其他专业的同学聊天的时候往往会被问到，“你们通信工程这个专业到底是学些啥的”，之前回答这个问题的时候一直都是支支吾吾，实在惭愧。到了这个学期，在几乎所有的专业基础课都学完的时候，总算可以稍稍整理一下这个问题了。\n正文 整理的第一件事，就是明确通信的概念。通信，就是说白了就是实现两点之间的信息传输。所以就字面上看，着眼点并不多，主要在于怎么表示信息和怎么传的问题。\n本文的写作思路根据学校培养方案给出的每个学期开设课程的时间顺序来叙述。这两年多来学的课程庞杂，近乎无所不包，这里先整理出与通信最密切相关的课程来整理一下。\n信号与系统 这是通信工程专业首先接触的一门与“通信”这个概念密切相关的专业基础课。在这门课中，我们首先接触到了“信号”这个概念。\n平时我们现实生活中听到的声音，看到的图像，每一个场景，都是信息。目前受到技术的各种限制，这样的信息是没办法直接传输的（这么传输可能需要无限的带宽，需要极强的抗干扰性能，等等）。目前基于电的形式运作的通信系统只能传输“电”，所以，引入了“电信号”的概念。\n好了，有了电信号的概念，一切就方便了，因为“电”这个东西的很多特性是从19世纪就开始有所研究的。于是，就有了各种用函数表示的确定信号。\n只有函数表示的信号是没有用的，就像在二进制代码中只知道1和0而不了解上下文一样。要怎么对这些信号进行处理呢？这个时候引入了三大变换：傅立叶变换，Laplace变换和Z变换。三个变换的本质都是将时域信号转换成变换域进行分析处理。这些变换一个主要目的就是将时域中繁琐的卷积操作转换为变换域相成操作，为分析运算带来巨大的方便。\n关于变换域概念的理解，是理解系统的一个先决条件。我们都是生活在时域世界，简单来说就是一切按照时间顺序发展和变化的，这样的缺点就是在自己的角度没办法“高屋建瓴”的去分析问题，所以很多小说为了描述整个故事，都会有并行支线，描写同一个时段不同人物的活动，还利用各种叙述方式。变换域也是同样的道理，拿傅立叶变换（时域频域变换）来举例，可以理解成在同一时间点有不同频率的分量同时活动，傅立叶变换的目的，就是把这样一个个“人物”抽离出来进行分析处理。\n说完变换域之后，终于可以到“系统”这个词了。我记得通信原理老师的一句经典名言“通信里面对信号所有的处理都可以理解成滤波器。”滤波器是一个具体的系统，也就是说，“所有系统的本质就是对信号进行各种处理”。这个处理，通常是频域的，对某个频域分量进行操作，比如“砍掉”一部分分量，“强化（增益）”某些分量，等等。\n数字信号处理 DSP，俗称“大山炮”，是通信主线课程的第二门专业基础课，顾名思义，在这门课上，讨论了“数字信号”的分析问题。\n在信号与系统中，所有的信号都是“模拟”的，也就是时域连续，取值连续的信号。模拟信号的缺点就是无法用计算机进行处理。\n数字信号，就是时域离散，而且取值离散的信号。数字信号可以通过模拟信号“取样，保持，量化”得到，在坐标系上表现出来就是一个个离散的点。\n为了对数字信号的分析处理，引入了离散时间傅立叶变换和离散傅立叶变换的概念。DTFT是时域离散，变换域连续的，DFT是时域离散，变换域离散的。考虑到“系统”是对信号在变换域中进行处理，DFT更适合用计算机对信号进行分析处理。\n随机信号分析 这门课讨论的重点是随机信号。这样做的实际背景是在实际通信系统中，信号都是随机的，我们永远不能预测别人下一句话会说什么；另外，在传输过程中会收到噪声的干扰，噪声也是一种随机信号。然而，之前学过的课程都是针对确定信号，也就是有确定数学表达式的信号的。所以我们有必要讨论如何处理随机信号，这样才能讨论信号的传输过程。\n随机信号没有确定的数学表达式，这并不意味着在这些信号面前无计可施。在这门课程中，一个极为重要的地方就是引入平稳随机过程的概念，基本思想就是虽然在某一时间点我们不能确定信号的表达式，但如果以时间差为自变量，就可以将某一类随机过程转变为确定的函数表达式进行讨论了。这类随机过程，就是平稳随机过程。\n讨论平稳随机过程引入了相关函数的概念，从物理意义上来看，这是两个信号表达式的乘积，因此在变换域中不能像以前那样用幅度谱来讨论了，在此引入了功率谱。\n把要讨论的主角介绍完毕之后，就是就此引出的几个相关概念，其中最为挥之不去的就是加性高斯白噪声和匹配滤波器这两个牛鬼蛇神了。\n通信原理 有了前面一系列的铺垫，各种概念定义表达式粉墨登场之后，终于迎来了这个专业基础课的最终“BOSS”，有多重要？之前的几乎所有课程都只讨论了“信”的来龙去脉，这门课着重在“信”的基础上研究“如何通”，讨论了所有情形之下的信息传输过程，把之前涉及的几乎所有相关知识串联起来，打通天地线，实现真正的通信大业。\n因为学习这门课的时候，学院已经把前三章大部分内容抽离到随机信号分析课程了，所以很快就进入了对传输的讨论当中。首先是模拟信号的传输，在讨论中引入了调制和解调的概念，本质上就是实现信号在频域上的搬移，也为以后的讨论打下基调。\n讨论完模拟信号的传输之后，就来到这整本书的重点，数字信号的传输。数字信号的传输包括了基带传输和频带传输。跟模拟信号不同的是，数字信号在基带和频带传输中都要进行调制的。因为理想的数字信号是冲激信号的线性组合，频带带宽无限带，根本没法直接传输，要所以进行脉冲编码调制，将其绝大部分能量集中在受限的带宽（成为“主瓣”）中，使传输成为可能。既然是传输，自然会受到噪声的影响，这里终于轮到“加性高斯白噪声”登场了。这种噪声特殊的地方就是在所有频段有相同的取值（就像Gundam里面GN粒子产生全频段电磁波干扰一样。。。）。这种噪声可以非常好的模拟信道传输过程中受到的“热噪声”等的特性，所以将会是整个讨论信号传输过程中的半个“主角”，在讨论信噪比时都默认收到的噪声是加性高斯白噪声。\n另外就是匹配滤波器的概念。通过匹配滤波器，将接收信号的高斯噪声限值在传输带宽所覆盖的频域之内，使接收信噪比能够达到最大。\n基带传输讨论完毕之后，就是数字信号的频带传输，具体的调制过程和模拟信号的调制过程有异曲同工之妙。真正特别的地方，就是为了提高信道利用效率而引入M进制调制的概念。\n之后，就是在本学期学到通原2中学到的关于信息编码处理和信道编码等内容，由于刚开始学习，所以暂且不表。\nEND 原本以为可以简要介绍，但不知不觉就来了废话一堆，能够把一个专业的核心问题说清楚真的不是什么轻而易举的事情，能够整理下来发现大学两年多也并非一无所获。等有时间了继续把其他光怪陆离的课程整理一下，也是对本科四年的一个交代罢了。\n纰漏之处，还希望留言指正:-)\n","date":"2016-03-08T22:33:57Z","permalink":"/posts/communication/","title":"通信的基础——大学这三年到底学了啥（主线课程篇）"},{"content":"背景 2015年转眼间来到了最后一周。大四考研的周末，在宿舍阅读了知乎上一些关于编程学习的文章，深觉自己也应该写下这一年来自己学习上碰到的坑和经验教训，也列举自己看过的一些书，供以后回顾时参考。\n絮絮叨叨的一些 一言蔽之，2015年是我自己在各方面都大胆探索尝试的一年。年初受知乎上萧大编程入门指南的启发,加上换上了新电脑的推动,开始了自己在编程学习上的系统探索,有人指路总比2014年“盲人摸象”般的窘迫境况好了不少。\n编程语言 随着学习的深入，我逐渐觉得，活跃在很多程序员社区上的所谓编程语言的争论，所谓”***才是最好的语言”的言辞，纯属奇谈怪论。计算机领域门派众多，不同领域会有不同的开发需要，不同的语言配合不同的开发工具在不同的领域自有自己的独到之处。如果还没接触某个领域，对该领域的编程语言，是没有任何质疑的权力的。入门学习编程时，选择一门看上去最讨喜的，又能体现计算机程序开发的精要的语言，学下去变是了。经历过小白阶段之后，更应该先定下自己感兴趣的领域（诸如人工智能，ML，DM，游戏开发，前端，移动开发，嵌入式等等等等），再根据领域的需要学习相应的语言。\n我自己2015年在编程语言上学习的收获，在于以下两门语言的深入学习：\nC# 作为一名自称的“软狗”，不学习微软技术大系.NET Framework是不行的。（逃\nC#作为整个庞大的.NET技术平台的顶梁柱，再配合上自家VS2015的有力驱动，还有MSDN堪称逆天般完善的知识库的支持，自然是入门微软技术学习的首选。在2015年1月就订好了C#的学习计划之后，中间磕磕碰碰，到年末，终于可以说自己基本上是入门了。期间用C#完成了必修课大作业（在此特别感谢上学期教我程序设计实践课程的吴铭老师，让我有摆脱枯燥落后而无聊的MFC的束缚，选择自己喜欢的编程语言完成课程作业的机会），让我对图形界面应用程序的开发有了初步的认知和体会。\nC#带给我的收获，不仅仅是那些语言语法逻辑，背后更是一大类带自动内存管理的”面向对象“语言所共有的知识体系。学习了C#语言之后，对待诸如值类型和引用类型，“引用”的概念，“代码托管”，“虚拟机”，“字节码”，”运行时“等等看起来庞杂的概念，有了基本的认识。这些知识，不仅仅对C#学习有极大的帮助，在其他类似语言诸如Python，哪怕是C#的“死对头”Java语言的学习中，也是会起到不可或缺的重要作用的。考虑到这门语言的学习难度，将其作为”面向对象”语言体系的入门语言来学习，是一个非常明智的选择。\nPython 从去年年底就嚷嚷着想学的语言，在今年年底，终于可以说自己有所进步了。\n对于C++的学习者来说，Python以四空格划分代码区块的“诡异”语法，是初学者在学习上极大的雷区，一不小心就会带来诸多莫名其妙的报错，连”Hello World“都写不出来。在微软俱乐部里面，”学习Python语言需要游标卡尺“这个老梗，也时常成为大家的笑谈。然而，一旦找到一个好看耐“艹”（比如代码高亮，智能提示，自动缩进，PEP 8 代码规范提示\u0026hellip;)的IDE辅助学习之后,无论从学习过程还是成就感上，都会有质的飞跃。经过种种的摸索踩坑，庆幸自己终于找到了这个工具——JetBrains-Pycharm\nPython是一门很活，很活的语言，无论从语法形式上，还是运行调试上，都是如此。在学习Python过程中感触最深的一点，就是很多函数的用法，我都是在Python交互环境中“试”出来的。Python交互环境是我最喜欢Python的一个地方，在这里你可以像Linux Shell那样，输入一条Python指令，就可以获得输出，通过输出可以学习到很多函数的用法，查看某个变量的具体类型等等工作，为后续的编程开发，省了不少力。\nPython最具魔力的地方，是那一堆堆用不完的第三方库。这堆库让Python语言在时下最火的领域——人工智能，机器学习，数据分析，数据挖掘，“你老婆”等有一展拳脚的机会。\n领域方向 2015年我还是挺迷惘的，哪门编程语言都想学，，哪个方向都想尝试，但又怕学而无用，或者碰到了不适合自己的深坑，误了时光。总结下来，自己这一年好像在很多领域都有所接触，但都没深究，近乎一事无成罢了。开荒的领域，在这里还是要提一下的，希望在来年能从中找到一两个自己喜欢的领域深挖下去吧。\nUWP应用开发 UWP，微软在Win10平台上的通用应用，基于C#写后台逻辑，XAML写前台界面。XAML简单易懂的语法可以为初学者带来极大的满足感，然而真正想开发出精美绝伦的通用应用，还是需要深入学习和领悟的。WPF这个庞大、特性丰富的界面框架和C#丰富的高级语言特性的学习，是在这方面成为高手的必经之路。\nPython开发Web网页应用 在这里特指Flask和Django框架相关的开发。\nAndroid应用的开发 是的没错，一个软狗也有过考虑开发Android应用的时候，主要还是“揾食艰难”，想找一些来钱快的领域学学，找点外快什么的。然而到最后不了了之。\n计算机视觉 缘起大二小学期时候的创新实验。当初用OpenCV库搞了个所谓之\u0026quot;人脸识别\u0026quot;的项目，开发过程也不可谓不艰辛，在配置开发环境的时候就走了不少“弯路”，也要摸索很多之前毫无概念的方法。回想起来那段时间其实是自己今年收获最丰富的其中一段时间了。迫使自己重拾了荒废半年的C++，重温了很多基本概念，学习了计算机图像处理的一下核心思想方法。另外，人脸识别也把我带进了模式识别、机器学习的坑。\n机器学习 由“人脸识别”带来的坑，然而学的很皮毛，暂且不表。\n魔法书 总的来说，这年开的坑确实有点多，但又学的很不深入。到年末才渐渐意识到自己之前的心态之浮躁，不禁为之惭愧不已。到年末想投个实习简历，却发现自己近乎空白的项目经历，甚为汗颜orz。\n致那个一直想跳未跳的坑——Gawain Open Sources Project（GOSP） 从高三毕业开始就有想做一个属于自己的Android ROM给自己的手机用。然而前两年收到电脑硬件和移动设备的限制（大一是魅族MX2这个万年锁BL的混蛋，大二时信仰挚爱Lumia930）毫无进展。这年年末在电脑硬件和移动设备都能满足的时候，却在漫长坑多的下载源码和编译时间上打了个退堂鼓。或许真的能发布的时候，已经“猴年马月”之后了。\nAchieve？ 今年如果真的有什么建设的话，就是这个博客了。在这方面幸好没有太大的犹豫，Hexo+ GayHub的部署方式，50美刀五年买到了最喜欢的域名，部署过程还顺路入门了Gayhub和Markdown，之后开发过程上也开始积累写下自己的遇到的问题和解决方法。这里感谢微软MSP项目给予我的动力。微软大法好哦耶！\n一些教训 在学习过程中永远不应该害怕走弯路。编程学习本身就是不断在碰坑中修正的过程。事实上，因为害怕走弯路而盲目模仿前人的所谓”经验\u0026quot;,\u0026ldquo;捷径\u0026quot;的过程，才最弯的弯路。\n切忌浮躁，切忌浮躁，切忌浮躁。重要的事情说三遍！\n实践，永远是检验真理的唯一标准。\n开发工具 这个必须提的。工欲善其事，必先利其器。 一个完整好用的工具链可以省去很多功夫。之前经历过一系列的摸索，从写C++时候的Dev C++，到后来学Python时碰过Vim，敲过Emacs，尝试Eclipse+DevPy，倒腾VS2015+Pytools，还折腾过Sublime，Atom，VSCode编辑器之流。现在总算得到一系列比较完善的开发工具体系。\n操作系统上，一直对MacOS的华丽界面和好用到爆炸的终端心向往之，无奈受阻于高昂的价格。尝试过Ubuntu，却无论如何都未能称心如意。到最后躁动的心皈依Windows 10，用Cygwin + Cmder 打造了界面和灵活性不输水果的终端。也算满足了日常使用的基本需要。\n现在将自己目前觉得最顺眼的工具链列举记录，供以后重装系统换电脑之后快速配置参考。\n操作系统：Windows 10 TH2 Build 10586 终端：Cygwin+Cmder C#开发：宇宙第一IDE VS2015 Community Python开发IDE：Pycharm。强推Monokai配色+Consolas字体 Python交互窗口：使用Cygwin下基于GCC的独立Python环境并独立安装pip。 编辑器SublimeText3。 博客写作Atom + MarkdownPad 今年看过的书 絮絮叨叨说了这么多，到最后应该是整理一下今年看过的几本让我“醍醐灌顶”的著作。\n《编码的奥秘——隐匿在计算机背后的硬件语言》\n上学期学习数字电路与逻辑设计课程，一开始被一大堆的逻辑门电路弄的一头雾水，后来找到了这本“神作”，从最开始的电筒闪亮传输信息的情节开始，循序渐进，以汇编语言收尾，讲述了计算机硬件的编码逻辑的来龙去脉，扫清了学习的迷雾和烦恼。\n《程序是怎样跑起来的》\n日本人写的一本小书，补充了很多计算机的基础知识，讲解了诸如Windows分页文件等系统级别的“冷”知识，也涉及了CPU，内存等“硬”功夫。\n《数学之美》\n吴军老师结合自己在项目研发上的实际经验撰写的一本讲述了目前最火的几个领域上用到的数学知识原理。言简意赅，毫不沉闷。\n最后的最后 写在最后的也不算是对未来的展望了。2016年希望不要摔在同样的坑上。还有就是能成为正式MSP继续传教233333333\n","date":"2015-12-26T14:09:48Z","permalink":"/posts/2015/","title":"“软狗”的2015学习总结"},{"content":"前言 在Windows下配置python的虚拟环境时常常会遇到各种各样的问题。所幸Windows平台下有Cygwin这个虚拟Linux的环境。在cygwin下安装了独立的Python环境后，再安装pip，即可通过pip install virtualenv命令安装Python虚拟环境，之后通过虚拟环境安装Flask，Django等网络开发环境就可以一气呵成了。\n安装步骤 首先，安装Cygwin并在软件包选择页面选择安装Python软件包。 前往Get Pip下载get-pip.py文件到cygwin的home目录. 在Cygwin命令行环境下进行pip的安装。执行python get-pip.py命令即可。 安装完毕之后，关闭Cygwin交互环境再打开，就可以进行虚拟环境的安装了。 pip安装成功的标志 注意: 直接输入pip命令没有提示command not found并不能说明pip被成功安装在cygwin的python运行环境下。如果Windows下也存在Python和pip的话，在Cygwin命令行输入pip会直接调用Windows平台下的pip命令。\n可行的判断方法如下：（以下命令均在Cygwin交互环境下输入）\n命令行输入which pip返回的结果为\u0026rsquo;/usr/bin/pip' 命令行输入pip -V返回的结果为pip 1.5.6 from /usr/lib/python2.7/site-packages (python 2.7) 至此，Cygwin下独立的pip环境已经配置完毕，之后就可以安装Linux下配置Flask，Pyramid或者Django环境的办法愉快的玩耍了。\n","date":"2015-12-25T18:37:31Z","permalink":"/posts/cygpy/","title":"Cygwin安装独立的pip环境"},{"content":"前言 NumPy是python中用于机器学习和科学计算的常用库。opencv for python中cv2.imread('image.jpg')命令读取图像返回的数据类型也是ndarray。今天将ndarray的一些常用属性记录下来，以便以后学习时进行参考。\nndarray常用属性 size: 表示array中拥有的总元素数量。\nshape: 元组类型。该属性描述了ndarray的“形状”信息。shape[0]为行信息，shape[1]为列信息。\nstrides: 元组数据类型该属性返回每一个维度上元素的数量。\nndim: 表示这个ndarray的维度。也可以理解为shape属性返回的元组的数量。\nT:表示ndarray矩阵的转置。\n今天使用opencv做简单的图像处理时接触到的ndarray的相关属性主要就是以上5个。以后再学习过程中遇到的更多有用的属性时会及时做补充。\n","date":"2015-12-23T22:39:36Z","permalink":"/posts/numpy/","title":"numpy学习笔记（1）--ndarray类型常用属性"},{"content":"前言 上次提到会在后续通过实际例子更加深入的谈谈对解释四种四种基本算法设计模式的理解。今天说到的,是利用动态规划思想求解两个字符串的编辑距离。\n正文 问题背景 我们将两个字符串的相似度定义为：将一个字符串转换为另一个字符串时需要付出的代价。转换方法包括插入，删除和替换三种编辑方式。使用对字符串的编辑次数定义为转换的代价。最小的字符串编辑次数就是字符串的编辑距离。 \u0026ndash;《算法的乐趣》\n问题分析 要使用动态规划思想解决这个问题，首先我们需要对问题进行阶段划分，确定边界条件，定义无后效性的几个子状态并且确定状态之间的转移关系，才能在每个子状态中寻找最优解最后得到原问题的解。\n寻找子问题：在本问题中，假设原字符串为source，长度为m，目的字符串为target，长度为n。则问题可以描述为求解source[1..m]转换为target[1..n]所需要的最小编辑次数。注意到，当source的前i个字符和target的前j个字符之间的编辑距离确定之后，不会在后续求解中发生改变，因此此问题的子问题即可定义为求解source的前i个字符和target的前j个字符之间的编辑距离。\n边界条件的确定：当source字符串长度为0时，编辑长度为target的字符串长度n（插入n个字符串）。当target的字符串长度为0时，编辑长度为source的字符串长度（删去m个字符串）。\n关于“备忘录”：动态规划问题的一个特点是使用类似”备忘录“的”表“记录每个状态的相关信息。本问题中用d[i,j]定义为source的前i个字符和target的前j个字符之间的编辑距离，作为每个状态的标志。\n状态之间的转换：以d[i-1,j]+1为删除字符时转移的状态,d[i,j-1]+1为插入字符时转移额状态，d[i-1,j-1]+1为替换字符串时转移的状态。\n子问题的最优解：字符串之间的转换方式并不是唯一的，通过操作若干次添加字符，删除字符，替换字符操作都可以实现字符串转换，每次转换状态时，将三种操作方式中的距离的最小值作为d[i,j]，可以保证每个子问题的解都是最优的，从而保证整个问题的解是最优解。\n具体代码 使用C#语言实现。\nusing System; namespace EditDistance { class Program { public static int MAX_STR_LEN = 100;//initialize the max length of string static void Main(string[] args) { //test code Console.WriteLine(\u0026quot;Please input Source string:\u0026quot;); String src = Console.ReadLine(); Console.WriteLine(\u0026quot;Please input Target string:\u0026quot;); String dist = Console.ReadLine(); Console.WriteLine(EditDistance(src, dist)); } private static int EditDistance(string src, string dist) { int i, j; int[,] d = new int[MAX_STR_LEN,MAX_STR_LEN]; //initialize the margin condition for (i = 0; i \u0026lt;= src.Length; i++) d[i, 0] = i; for (j = 0; j \u0026lt;= dist.Length; j++) d[0, j] = j; for(i = 1;i\u0026lt;=src.Length;i++) { for(j=1;j\u0026lt;=dist.Length;j++) { if(src[i-1]==dist[j-1]) { d[i, j] = d[i - 1, j - 1]; } else { int edIns = d[i, j - 1] + 1; int edDel = d[i - 1, j] + 1; int edRep = d[i - 1, j - 1] + 1; d[i,j]=Math.Min(Math.Min(edIns,edDel),edRep); } } } return d[src.Length, dist.Length]; } } } 运行结果：\n后记 求解字符串的编辑距离问题应该是动态规划的最典型案例，体现了动态规划问题的基本特点。适合用动态规划解决的问题，一般都是可以划分多多个子状态的，每个子状态之间存在状态转移关系，每个状态的解一旦确定，就会用类似“备忘录”的表储存起来。在后续求解时，将会直接使用该状态的解，而且，不会再回溯子状态的时候改变该状态的解。\n另外，边界条件的确定是问题求解时的一个重要步骤。如果忽略了边界条件，或者在求解时没有正确初始化边界条件（比如在本题中对d[i,0]和d[0,j]需要初始化为i和j），将得不到正确的解。\n","date":"2015-12-07T22:41:52Z","permalink":"/posts/dp/","title":"使用动态规划求解字符串编辑距离问题(C#实现）"},{"content":"前言 本文主要介绍通过diskpart命令行的方式制作Windows10引导U盘，制作完毕后，该U盘可以用于安装（包括全新安装和升级安装）Windows10操作系统，也可以在系统无法启动的时候使用该U盘进行修复操作。\n正文 需要的材料 Windows 10 Build 10586 安装镜像。（推荐从北邮人BT或者IT之家搜索相关的镜像。另外32位或者64位的系统镜像均适用本教程。） 容量大于4GB的U盘一个。建议适用USB3.0的U盘以获得较快的安装速度。请提前备份好用户数据。 以管理员账号登录当前系统。 制作步骤 启动当前系统进入桌面环境之后，将U盘插入电脑。在开始菜单中搜索cmd或命令提示符，并右键，选择以管理员身份运行。 在命令提示符窗口中输入diskpart，按回车。 弹出的UAC窗口中选择允许，进入diskpart程序。 在Diskpart程序中，输入list disk查看当前的磁盘列表。 根据容量查看自己的U盘对应的磁盘号。这里假定第一个磁盘为你的U盘。输入select disk 1选中你的U盘。 输入clean，清除U盘内容。 输入create part primary，用于在U盘中创建主分区。大小为U盘的容量大小。 输入format，格式化刚刚创建好的磁盘分区。磁盘格式默认是FAT32。 输入active，激活主分区。这步非常重要，如果忘记输入这条命令，制作的U盘将无法激活。 输入exit，退出程序。 用文件管理器打开下载好的Win10安装ISO镜像文件，将里面的内容全部复制到U盘中。 至此，一个可以在目前绝大部分新的笔记本电脑上启动的Windows10启动U盘已经制作完毕。在BIOS中选择用U盘启动即可进入亲切的Windows10安装界面了。 后记 虽然目前有很多工具可以制作启动U盘，但本文介绍的方式具有独特的优势，在你想更换别的版本的系统比如制作ubuntu启动盘（逃 的时候，只需要把U盘里面的内容全选删除，并且把ubuntu安装镜像的内容完整复制过去就可以了，不用借助任何其他工具再次制作。\n","date":"2015-11-27T17:54:26Z","permalink":"/posts/win-10/","title":"用diskpart打造Win 10安装U盘"},{"content":"前言 最近在阅读一本有趣的算法书，书中作者列举了四种基本的算法设计模式，我总结摘录得到本文。今天先给出文字总结。随着阅读的深入，以后会给出四种设计模式的典型案例。\n正文 贪婪法 - 核心思想： 将原问题划分为多个子情况考虑，在每一个子情况中寻求其局部最优解，然后将局部最优解按照一定的方式（这种方式通常与子问题的划分方式有关）堆叠得到原问题的解。 - 特点： 1. 这种方法不用考虑子问题之间的相互影响（这一点区别于动态规划方法），通俗的说，就是不用“瞻前顾后”。 2. 在每个子问题中都应用了局部最优原则，寻求该子问题的最优解。 - 一般思路： 定义最优解模型-\u0026gt;划分子问题-\u0026gt;定义子问题的最优解结构-\u0026gt;确定局部最优解，并堆叠出全局最优解。 分治法 - 核心思想： 将大问题划分为一系列子规模较小的相同问题（解结构相同，子问题之间相互独立），寻找子问题的解之后将结果合并得到原问题的解。 - 特点： 子问题的划分不一定只有一次，而且往往不止一次，划分的目的是使每一个子问题相互独立而且是容易求解和能够最后组合得到原问题的解。正因为这个特点，分治法与递归思想总是密不可分的。 - 难点： 子问题的划分方式和最后结果的合并。如果用递归去解决分治法问题时，难点就是寻找递归关系式和确定递归终止条件。 - 举例：N点离散傅里叶变换的快速计算 动态规划 - 核心思想： 要解一个给定问题，我们需要解其不同部分，再合并子问题的解以得到原问题的解。 - 特点： 1. 动态规划在查找有很多重叠子问题的情况的最优解时有效。它将问题重新组合成子问题。为了避免多次解决这些子问题，它们的结果都逐渐被计算并被保存，从简单的问题直到整个问题都被解决。因此，动态规划保存递归时的结果，因而不会在解决同样的问题时花费时间。 2. 适用于无后效性的问题。子问题的解一旦确定之后就不会改变，不受之后包含它的更大的问题的求解。 3. 适用于子问题相互重叠的情况。动态规划会创建一张“表”记录每个子问题的计算结果，当再次需要计算此问题时就可以通过查表快速得到结果，简化计算。 - 一般思路： 定义最有子问题-\u0026gt;定义状态-\u0026gt;定义决策和状态转换方程式-\u0026gt;确定边界条件。 - 举例： 斐波那契数列的计算（在计算过程中保存每一步的f(n)）、0-1背包问题 穷举法 - 核心思想： 在解空间之内穷举并测试每个可能的结果。 - 穷举策略： 1. 盲目搜索算法。 2. 启发式搜索（开始搜索时加入了一定的附加条件） 3. 剪枝策略：跳过一些明显不会是最优解的分支的搜索。 - 应用举例：很多NP问题的求解最后都是用了枚举法。 后记 四种基本的设计模式并不是相互对立的。有时一个问题可以用一种或多种的设计模式去解决，而且，附加的条件不同时，应用的设计方法也不同。一个典型例子就是背包问题（多件体积不同，价值不同的物品放入体积一定的背包，求解价值最大的方案），既可以通过穷举所有情况解决，也可以引入价值密度的概念，用贪婪法设计解决(每次都放入价值密度最高的物品），还可以用动态规划解决（还没透彻理解QAQ）。 上面的内容很多都是自己整理书中内容加上自己的一点想法写成的，感觉还是太抽象了，也难免有各种疏漏和错误，欢迎大家留意指正，多多交流。以后补充实际问题结合代码更加深入的分析每一个方法的奥妙。\n","date":"2015-11-16T23:15:01Z","permalink":"/posts/%E5%9B%9B%E7%A7%8D%E5%9F%BA%E6%9C%AC%E7%9A%84%E7%AE%97%E6%B3%95%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/","title":"四种基本的算法设计模式"},{"content":" 基础类型相关 .NET中所有内建类型都继承自System.Object,若同时继承自System.ValueType类则为值类型，否则为引用类型。\n值类型和引用类型的区别如下：\n赋值时，值类型变量会直接获得真实数据的一个副本，而引用类型只会将对象的引用赋值给变量，会造成多个对象指向同一个内存区块（对象实例）的情况。 内存分配时，引用类型对象会在堆上分配内存，而值类型变量会在堆栈上分配内存，运行效率比堆高很多。 装箱拆箱的概念 装箱，指的是CLR需要做额外工作把堆栈上的值类型移动到堆上。 拆箱，指的是把堆中的对象复制到堆栈中，返回其值。 应该注意的是，装箱和拆箱行为，都对应了堆栈上的一系列操作，会造成较大的性能代价。因此减少程装箱拆箱操作，是程序性能优化的一个重点。 避免装箱拆箱操作的思路，在于从两方面避免发生装箱拆箱行为的场合：\n值类型的格式化输出。 System.Object类型的容器。对于这种情况，可以使用泛型技术来避免使用System.Object类型的容器。 原文链接.NET基础拾遗(1)类型语法基础和内存管理基础\n","date":"2015-10-21T16:28:40Z","permalink":"/posts/net%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E4%B8%80/","title":".NET学习笔记（一）"},{"content":"Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.\nQuick Start Create a new post 1 $ hexo new \u0026#34;My New Post\u0026#34; More info: Writing\nRun server 1 $ hexo server More info: Server\nGenerate static files 1 $ hexo generate More info: Generating\nDeploy to remote sites 1 $ hexo deploy More info: Deployment\n","date":"0001-01-01T00:00:00Z","permalink":"/posts/hexo-hello-world/","title":"Hello World"}]