学会玩代码

原文信息

Code Kata--How It Started


http://codekata.pragprog.com/codekata/2007/01/code_katahow_it.html

译文

怎样开始 Code Kata

(这篇文章很长,但很值得一读...)

所有都源于 RubLog 提到的搜索方式(相信我,这不是一篇关于搜索的文章,或者关于位的探讨)。因为我最终想使用基于余弦对比算法来找相似的文章,我建立了一个向量表词汇映射表来记录在博客中文档的词语出现次数。我最终为每一篇文档建立了 1200-1500 位。为了实现一个基本的随机搜索功能,我需要能够按位展示并且在这些向量和一个向量包含的内容之间,然后在最终结果中计算出一位的数量。

我昨晚上有一个非常稀少而珍贵的 45 分钟(我将 Zachary 送到了 karate 课程,那是一个不需要父母的看管的地方),所以我想我可以有充足的时间做这件事情了。首先我尝试用不同的方式存储位向量:结果证明(同时也令我大吃一惊),这种用数组存储位的方式几乎完全可以满足于用做大数的存储位的速度。(另外,更令我高兴的是,在两种呈现方式上的改变,仅仅只需要修改两行代码而已)。因为它控制得非常紧凑,我决定继续使用这个大数的算法。

然后,我在独自一人玩大数的位计算找到了乐趣。这个源代码中使用了非常简单明显的算法:

      max_bit.times { |i| count += word[i] }


让我们来看看这样的速度会有多慢呢?我编写了一个简单的测试实例,用来生成 一百个 1000 位的向量,每一个包含有 20 个左右的随机位数,然后计算循环花费的总时间。对于总共的 100 个向量,花费了将近 4s 的时间。

然后,我尝试使用黑客中 Delight 第五章提到的计数算法“divide-and-conquer”(修改成 Bignum 为 30 位的情况)。

     ((max_bit+29)/30).times do |offset|
       x = (word >> (offset*30)) & 0x3fffffff
       x = x - ((x >> 1) & 0x55555555)
       x = (x & 0x33333333) + ((x >> 2) & 0x33333333)
       x = (x + (x >> 4)) & 0x0f0f0f0f;
       x = x + (x >> 8)
       x = x + (x >> 16)
       count += x & 0x3f
     end


这样运行速度比刚开始的原始算法提升了将近 2.5 倍。

然后,我意识到当我比较这一系列的搜索次数时,仅仅只用到了其中的一小部分字节,向量中的位将变得非常稀少。每一次在循环中每一上面的块将变成很接近 0。所以我添加了这样一个测试用例:

     ((max_bit+29)/30).times do |offset|
       x = (word >> (offset*30)) & 0x3fffffff
       next if x.zero?
       x = x - ((x >> 1) & 0x55555555)
       x = (x & 0x33333333) + ((x >> 2) & 0x33333333)
       x = (x + (x >> 4)) & 0x0f0f0f0f;
       x = x + (x >> 8)
       x = x + (x >> 16)
       count += x & 0x3f
     end


现在,我的代码比最原始的的代码快了将近 7 倍。但是我我的测试正使用的向量包含了 20 位 (超出了1000)。我将它改成定时生成这样的向量:包含 1, 2, 5, 10, 20, 100, 到 900 位。这样将会变得更好:我最终用 1 或 2 个向量中的位等因素来提高了近 15 倍的效率。

但是如果我通过消除零的那块代码,来达到提升速度的,在 bit-twiddling 算法中,我是不是也能够做到同样简单的计算算法呢?我尝试了第三种算法:

     ((max_bit+29)/30).times do |offset|
        x = (word >> (offset*30)) # & 0x3fffffff
        next if x.zero?
        30.times {|i| count += x[i]}
      end


这里内部循环与最原始的计数代码是一样的,但是,我现在的计算位数是 30 作为一块。

这段代码演示了定义这个为 1 位定义摆弄位的代码,并且仅仅比 2 位的代码稍微差劲一点。然后,一旦随着位数的开始增长(过去我给的块大小是 5),效率马上开始变得越来越低了. 在 100 位的时候,它甚至会比原始的计数算法更慢了。

对于我这个特别的应用程序,我能够可能倾向于选择块计算算法。因为这玩弄的 算法在很大的位数目时,将会变得很庞大,并且,我将会需要在以后开始做基于余弦匹配的文档,我抛弃了他。

那么,整个这篇文章的观点是什么呢?

昨天,我发布一篇关于动词重要性博客介绍。它说到:“通常一件事物的真正价值不是事物本身,而是通过这件事物所带来的创造性活动。”Chad Fowler 拣出这一条然后写了这一条非常正确的语句,这句话对演奏家来说是多么准确啊。并且 Brian Marick 在 Chad 的基础上强调了在学习一种创造性活动时“练习”的重要性。

在同一时间,我和 Andy 也讨论过很多他所拥有的的音乐磁带。最原始的设计来帮助演奏家来练习音域和琶音等,这些已经如此流行了以至于他们现在能够压缩一整个曲谱的练习技术。我们正抱怨这个事实:它变得很不像我们之前和其他开发者一样在做的事情:通过参加一些培训课程来帮助他们来练习编程技术。我们仅仅觉得这样课程中的练习并不是程序员实际所做的事情那样。

跳过回到今天的这个早上。在冲凉的时候,我想到了这个,并且意识到我的这小 45 分钟对位计算的探索是一个非常准确的练习过程。我并不是真正担心这篇博客中提到位计算的搜索算法的效率;在实际中,结果反而相当完美。相反,我仅仅就想玩一下代码和体验一种我之前一直没有体验过的一种技术。我用了一种非常简单的方式尝试了一下,一个可控制的环境,并且我尝试多种不同的变化情况(比我在这里列举的情况还多)。同时,我仍然还存在一些可以玩的地方:我想了解块大小的变化影响情况,或者我想看看是不是存在一些其他更加好的位计算算法。

是什么原因造成了这个练习过程呢?好的,我有一些不会被打断的时间。我有一个很想尝试的简单事情,并且我为这件事情尝试很多次。我每次检查结果并进行总结,以便我能够在下一次中得到提高。这样是没有压力的:这个代码非常有效率的工作。这是很有趣的:我每一次改进一小步来前进,这样使得我能保持持续工作的热情。最终,我得到了相对于开始时更多的知识。

更进一步的分析,正是在因为有自由时间才使我能够练习。如果在压力的情况下,如果有一个戒指时间来传递这个博客搜索功能,然后这村子的优化将已经被接受了,然后这个练习将不会再发生。那么这些 45 分钟的无压力时间能够让我玩起来。

那么怎样让我们在实际中这样做呢?我们怎样才能在任何创造性活动中,帮助开发者来做这样的非常需要的练习呢?我也不太清楚,但是我的勇气尝试告诉我需要做两个主要的事情。

首先要做的事情是,每一次都需要在没有压力的情况下。提供一个绿色时间:在一个不需要担心项目的截止日期。它必须是非常接近于放松状态,因为如果你不是放松状态,你不将从实际的练习中学到任何东西。

第二个要做的事情是:帮助你的伙伴学会怎样和代码一起玩:怎么来制作错误,怎么来提高,怎样来反应,并且怎样来评测。这是非常难的:我们总是被训练做正确的事情,为了拿到很高的分数,而不是提高。我大胆的猜测成功都是来自于实际动手行动。

所以,我今天提供挑战是:看你能否在 45 - 60 分钟内,“玩”一小段代码。你不必考虑效率:或许你可以从结构上考虑,或者内存使用情况上,或者接口定义。在最后,它也不会产生任何影响。体验、评测、提高。

练习。

好词好句

gut:n.勇气 内脏 直觉 肠 adj.本能的,直觉的 vt.毁坏(建筑物等)的内部 取出…的内脏

instantaneously:adv.即刻,突如其来地

bemoaning:v.为(某人或某事)抱怨( bemoan的现在分词 ) 悲悼 为…恸哭 哀叹

tail off:变得越来越少[小] 不了了之

spectrum: n.[物理学]谱,光谱:辐射源,能谱 光谱相片 范围 系列,范围,幅度

particular:adj.特别的 详细的 独有的 挑剔的 n.特色,特点 (可分类,列举的)项目 详细情节,细情,细目 某一事项

sparse:adj.稀疏的 稀少的

marshals:n.元帅( marshal的名词复数 ) 典礼官 执法官 消防局长

twiddling:v.(心不在焉地)捻弄( twiddle的现在分词 )

slightly: adv.轻微地,轻轻地 细长地,苗条地

Often the true value of a thing isn’t the thing itself, but instead is the activity that created it.
通常一件事物的真正价值不是事物本身,而是通过这件事物所带来的创造性活动。

译后感

这文章提到的代码完全看不懂,但不影响文章表达的观点。作为一个优秀的开发者,不应该仅仅只考虑完成任务而已。而应该学会和代码一起“玩”,多练习,思考怎么提高效率。

文中的一些句子可能翻译错误,翻译还是挺难的:比如:

I needed to be able to perform a bitwise AND between each of these vectors and a vector containing the search terms, and then count the number of one-bits in the result.




<think>好的,我现在需要帮用户介绍一下AI贪吃蛇的代码。首先,我得理清楚用户的需求。他们可能对AI如何控制贪吃蛇感兴趣,想了解背后的算法和实现方法。用户可能有一定的编程基础,但可能对强化学习或遗传算法不太熟悉,所以需要解释得通俗易懂。 首先,我应该考虑常见的AI方法,比如强化学习中的Q-learning或者深度Q网络(DQN),还有遗传算法。这些是常用于游戏AI的技术。贪吃蛇的目标是吃食物并避免碰撞,所以AI需要学会做出最佳移动决策。 接下来,我需要分解问题。贪吃蛇的游戏环境通常包括网格、蛇的移动、食物生成和碰撞检测。AI部分需要根据当前状态选择动作,比如上下左右移动。状态可能包括蛇头的位置,食物的位置,以及周围是否有障碍。 然后,选择算法。比如用Q-learning的话,需要定义状态、动作、奖励函数。奖励可以是吃到食物加分,撞墙或自己身体扣分。而遗传算法可能通过不断迭代,选择表现好的个体进行交叉和变异,逐步优化策略。 代码结构方面,可能需要先实现游戏逻辑,再集成AI部分。游戏逻辑包括蛇的移动、食物生成、碰撞检测等。AI部分则需要定义状态输入、决策输出,以及学习机制。 可能遇到的问题包括状态空间过大,导致传统Q-learning难以处理,这时候可能需要用深度Q网络来处理高维状态。另外,训练时间可能较长,需要调整超参数如学习率、折扣因子等。 用户可能需要具体的代码示例,所以应该给出一个简单的示例,比如基于Q-learning的伪代码或关键部分,并解释各部分的作用。同时提醒用户实际实现时的注意事项,比如状态表示、奖励设计等。 最后,确保回答结构清晰,分步骤介绍,让用户能够逐步理解。可能需要比较不同算法的优缺点,帮助用户根据自身情况选择合适的方法。还要检查LaTeX格式是否正确,数学表达式用$包裹,独立公式用$$,避免语法错误。</think>以下是一个基于强化学习(Q-learning)的贪吃蛇AI实现框架及关键技术解析: --- ### 一、游戏环境搭建 ```python import pygame import numpy as np class SnakeEnv: def __init__(self, grid_size=20): self.grid_size = grid_size # 网格尺寸 self.reset() def reset(self): self.snake = [(5, 5)] # 初始蛇身 self.food = self._generate_food() self.direction = (0, 1) # 初始移动方向 self.done = False def _generate_food(self): # 生成不与蛇身重叠的食物坐标 while True: pos = (np.random.randint(1, self.grid_size-1), np.random.randint(1, self.grid_size-1)) if pos not in self.snake: return pos ``` --- ### 二、状态表示设计 使用相对坐标表示状态: $$状态 = [\Delta x_{食物}, \Delta y_{食物}, 前障碍, 左障碍, 右障碍]$$ 具体实现: ```python def get_state(self): head = self.snake[0] # 计算食物相对坐标 dx = self.food[0] - head[0] dy = self.food[1] - head[1] # 检测三个方向的障碍 front = self._check_collision(head, self.direction) left_dir = (-self.direction[1], self.direction[0]) # 左转方向 right_dir = (self.direction[1], -self.direction[0]) # 右转方向 left = self._check_collision(head, left_dir) right = self._check_collision(head, right_dir) return [dx, dy, front, left, right] ``` --- ### 三、Q-learning算法核心 #### 1. Q表初始化 ```python import pandas as pd # 状态维度:dx(-19~19), dy(-19~19), 障碍(0/1)*3 q_table = pd.DataFrame(columns=['left', 'straight', 'right'], dtype=np.float64) ``` #### 2. 动作选择(ε-greedy策略) $$动作选择概率 = \begin{cases} 随机动作 & \text{概率 } \epsilon \\ \arg\max_a Q(s,a) & \text{概率 } 1-\epsilon \end{cases}$$ #### 3. Q值更新公式 $$Q(s,a) \leftarrow Q(s,a) + \alpha [r + \gamma \max_{a'} Q(s',a') - Q(s,a)]$$ --- ### 四、训练流程 ```python alpha = 0.1 # 学习率 gamma = 0.9 # 折扣因子 epsilon = 0.1 # 探索率 for episode in range(1000): state = env.reset() while not env.done: # ε-greedy选择动作 if np.random.uniform() < epsilon: action = np.random.choice(['left', 'straight', 'right']) else: action = q_table.loc[str(state)].idxmax() # 执行动作并获取新状态 reward, next_state, done = env.step(action) # 更新Q值 old_q = q_table.loc[str(state), action] next_max = q_table.loc[str(next_state)].max() new_q = (1 - alpha)*old_q + alpha*(reward + gamma*next_max) q_table.loc[str(state), action] = new_q state = next_state ``` --- ### 五、关键改进方向 1. **状态表示优化**:改用卷积神经网络处理原始图像输入 2. **算法升级**:从Q-learning升级到DQN(深度Q网络) $$Q(s,a;\theta) \approx Q^*(s,a)$$ 3. **奖励函数设计**: - 吃到食物:+10 - 死亡惩罚:-20 - 存活奖励:每步+0.1 --- ### 六、典型参数设置 | 参数 | 推荐值 | 说明 | |-----------|------|---------------------| | 学习率α | 0.1 | 控制新信息的吸收速度 | | 折扣因子γ | 0.9 | 未来奖励的重要性 | | 探索率ε | 0.1 | 平衡探索与利用 | | 网格尺寸 | 20×20 | 平衡复杂度与训练速度 | | 隐藏层神经元数 | 128 | 使用神经网络时的典型配置 | 实际开发建议结合PyTorch/Keras等深度学习框架,使用`gym`环境规范进行标准化开发。完整实现需要约500-800行代码,建议参考OpenAI Gym的API设计模式。
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值