Big Code != Big Vocabulary

BigCode与Open-Vocabulary NLM
探讨大规模代码语料库上,开放词汇表神经语言模型(NLM)的必要性和优势,实验证明其在Java、C、Python三种语言上的卓越性能。

Big Code != Big Vocabulary: Open-Vocabulary Models for Source Code

Remark

Conference: ICSE 2020

Full Paper: http://homepages.inf.ed.ac.uk/s1467463/documents/icse20-main-1325.pdf

Artifact: https://github.com/mast-group/OpenVocabCodeNLM

 

Summary

In this paper, the authors motivate the importance of both large vocabularies and out-of-vocabulary issues, providing an experimental study of how to address such issues on a large source code corpora. Firstly, vocabulary modeling choices have been studied. The experimental result shows that the only viable option is an open-vocabulary NLM. All other vocabulary choices result in large vocabularies, high OOV rates, and rare words. Then the authors present a new open-vocabulary NLM for source code. Experiments show that the open-vocabulary NLM can scale to corpora (13,362 projects) that 100 times larger than in previous work, and yields state-of-the-art on three programming languages (Java, C, Python).

 

Introduction

Q: 什么是out-of-vocabulary (OOV) 问题

A: 如果在训练集中没有出现已知的词汇,那么训练好的模型就无法预测它们。

 

Q: 为什么当NLP技术应用于编程语言时会面临词汇表大的问题?

A: 因为与自然语言不同,软件开发人员可以自由地创建他们喜欢的任何标识符,并且可以使它们任意复杂。随着新的标识符名称的激增,代码以远高于自然语言的速度引入新词汇。大型词汇表和词汇量不足问题严重影响了源代码的神经语言模型(NLMs),降低了它们的性能,使它们无法扩展。

 

Q: 为什么NLM无法处理特别大的词汇表?

A: (1) 训练时缓慢;(2) 与测试面临Out-of-vocabulary (OOV);(3) 稀有词汇的embedding接近初始值,因为它们很少被更新。

Dataset

code corpora from Java, C, and Python

 

Modeling Vocabulary

 

(1)过滤不重要的词汇 (数据显示每次过滤都能减小词汇表的大小)

- 非英语单词

- 空格和换行

- 注释

- 字符串文本

结论:做完这一步后词汇量很大,有一百多万个单词。

 

(2)单词分割

虽然开发人员可以随意创建新的标识符,但倾向于遵循约定。因此可以通过某种启发式规则来分割单词

- 分隔符

- 大小写

结论:分词是有效的,但是词汇量仍然很大(一百万个单词)。

 

(3)分割Subword

- 将数字词抽象为字符序列,词汇量小幅度下降,但OOV提高

- 使用Spiral token splitter后,词汇量下降26%,OOV仍然很高

结论:虽然这些策略是有效的,但还远远不够解决词汇表和OOV问题;词汇量仍在数十万范围内。

 

(4)使用Byte-Pair Encoding(BPE)方法分割Subword

- BPE分词法:用数据中不使用的字节替换最常出现的字节,例如把出现频率高的(S1,S2)替换成S1S2

结论:BPE非常有效地缩小了词汇表。大多数词汇都是频繁出现的,改进了embedding。

 

4. 基于subword units的模型

将词汇表缩小了三个数量级

 

Evaluation

作者在Java, C, Python语料库上对比了NLM, n-grams, ML等方法

RQ1:性能评价

A1: Open vocabulary NLMs是对源代码的有效模型,即使是在一个小的语料库上,也能产生最好的性能。

RQ2:大语料库上的表现

A2:开放词汇表NLMs可以扩展到100倍大的语料库,它们可以有效地利用增加的训练数据,而大型n-gram模型不具备这样的能力。

RQ3:不同程序语言上的表现

A3:结果适用于Java、C和Python。

RQ4: Dynamic Adaptation的效果

A4:yields the state of the art

RQ5: Bug的检测能力

(这个不好评价,这种训练方法不一定适合bug检测)

 

Some other thoughts

(-) 这篇文章似乎在处理代码的时候没有考虑用程序分析技术将代码处理成中间表示(AST, CFG),而是直接用自然语言处理方法处理程序语言,因此会面临词汇表特别大的问题(从语义的角度看,很多自定义变量仅名字不同,但它们的使用方法是一样的,可以抽象处理)。

(+) 直接用处理自然语言的方法来处理程序语言的好处也很明显,可以直接用于不同程序语言(Java, C, Python)。

这是业务代码: public String findTranslationFromPush(String vocabulary, String language) { if (StringUtils.isEmpty(vocabulary) || StringUtils.isEmpty(language)) { return null; } String currentTranslationCacheKey = buildCacheKey(vocabulary, language); Optional<String> cached = translationCache.getIfPresent(currentTranslationCacheKey); if (cached.isPresent()) { return cached.orElse(null); } List<String> vocabularyList = new ArrayList<>(); vocabularyList.add(vocabulary); NotificationRuleOuterClass.BatchRetrieveVocabularyTranslationsResponse dictionaryGrpc = notificationRuleClient.retrieveVocabularyTranslations(vocabularyList); if (null == dictionaryGrpc) { return null; } //虽然是批量查询,但是由于传入的vocabulary只有一个,因此返回的结果也只有一个,只需要拿第一个即可 TranslationDictionaryDto translationDictionary = convertDictionary(dictionaryGrpc.getResult() .getVocabularyTranslation(0)); if (translationDictionary != null && translationDictionary.getTranslations() != null) { for (MultiLanguageTranslation translation : translationDictionary.getTranslations()) { String translationKey = buildCacheKey(vocabulary, translation.getLocale()); if (translation != null && StringUtils.isNotEmpty(translation.getLocale())) { translationCache.put(translationKey, Optional.ofNullable(translation.getTranslation())); } } // 返回当前语言的翻译 Optional<String> currentLanguageTranslation = translationCache.getIfPresent(currentTranslationCacheKey); return currentLanguageTranslation.isPresent() ? currentLanguageTranslation.orElse(null) : null; } translationCache.put(currentTranslationCacheKey, Optional.empty()); return null; }
最新发布
10-16
<think>好的,我现在需要分析用户提供的业务代码,并根据之前的测试用例问题来找出可能的错误点。用户提到在测试用例中,当gRPC返回null时,期望调用`translationCache.put`但实际没有被调用,原因是缓存键不一致。现在结合业务代码来看,问题可能出在哪里呢? 首先,业务代码中的`findTranslationFromPush`方法,构建缓存键是通过`buildCacheKey(vocabulary, language)`方法。但用户提供的代码中没有显示这个方法的具体实现,所以需要推测。之前的测试用例中,测试期望的键是`testKey.zh_CN`,但实际调用的是`testKey_zh_CN`,这说明`buildCacheKey`可能使用了下划线而不是点号作为分隔符。 接下来,在测试用例`testFindTranslationFromPush_GrpcReturnsNull`中,当gRPC返回null时,业务代码应该调用`translationCache.put(currentTranslationCacheKey, Optional.empty())`。但是测试失败提示没有调用,可能是因为在业务代码中,当dictionaryGrpc为null时直接返回null,而没有执行到put操作。因此,测试用例中模拟的dictionaryGrpc为null的情况下,业务逻辑直接返回,不会执行put,导致验证失败。 此外,测试用例中设置的`when(notificationRuleClient.retrieveVocabularyTranslations(anyList())).thenReturn(null);`导致进入这个分支,但业务代码在这种情况下不会执行任何缓存操作,所以put方法没有被调用,而测试用例却期望调用,这是矛盾的。所以测试用例的预期可能有误,或者业务逻辑需要调整。 另一个问题是,当dictionaryGrpc不为null时,业务代码会遍历翻译结果并缓存每个locale的翻译,然后再次查询当前语言的缓存。如果当前语言的翻译存在则返回,否则返回null。此时才会在最后put一个empty的optional。但只有在translationDictionary为null或者translations为空时才会执行到put操作。 因此,测试用例中当gRPC返回null时,业务逻辑直接返回null,不会执行put。而测试用例期望验证put被调用,所以这个测试用例的预期是错误的,或者业务逻辑需要调整,比如当gRPC返回null时也应缓存空值。 总结问题: 1. 缓存键生成方式与测试用例中的期望不一致,可能使用了下划线而非点号。 2. 测试用例中当gRPC返回null时,业务代码没有执行put操作,但测试期望调用,导致失败。 3. 需要调整测试用例或业务逻辑,确保在gRPC返回null时执行put操作。 接下来需要给出解决方案,包括修复缓存键的生成,调整测试用例的预期,以及可能的业务逻辑调整。</think>### 问题定位与修复方案 #### 1. **缓存键生成规则不一致** - **问题根源**:业务代码中 `buildCacheKey(vocabulary, language)` 使用了 **`_` 作为分隔符**(如 `testKey_zh_CN`),而测试用例期望的是 **`.` 分隔符**(如 `testKey.zh_CN`)。 - **修复方式**:统一键生成逻辑,确保测试与业务代码一致: ```java // 业务代码中修正 buildCacheKey 方法 private String buildCacheKey(String vocabulary, String language) { return vocabulary + "." + language; // ✅ 使用统一的 . 分隔符 } ``` #### 2. **测试用例逻辑与业务逻辑不匹配** - **问题根源**:当 `notificationRuleClient.retrieveVocabularyTranslations(...)` 返回 `null` 时,业务代码直接返回 `null`,**未执行 `translationCache.put(...)`**,但测试用例期望调用该方法。 - **修复方式**:调整业务逻辑,确保在 gRPC 返回 `null` 时仍缓存空值: ```java if (null == dictionaryGrpc) { translationCache.put(currentTranslationCacheKey, Optional.empty()); // ✅ 添加此行 return null; } ``` #### 3. **测试用例参数匹配精度不足** - **问题根源**:测试中使用 `any()` 可能导致参数匹配不精确,掩盖键格式差异问题。 - **修复方式**:使用 `eq()` 明确参数匹配: ```java // 测试用例中修正参数匹配 when(translationCache.getIfPresent(eq(CACHE_KEY))).thenReturn(Optional.empty()); ``` --- ### 完整修复代码示例 #### ✅ 修复业务代码(`MultiLanguageProviderServiceImpl.java`) ```java public String findTranslationFromPush(String vocabulary, String language) { if (StringUtils.isEmpty(vocabulary) || StringUtils.isEmpty(language)) { return null; } String currentTranslationCacheKey = buildCacheKey(vocabulary, language); Optional<String> cached = translationCache.getIfPresent(currentTranslationCacheKey); if (cached.isPresent()) { return cached.orElse(null); } List<String> vocabularyList = new ArrayList<>(); vocabularyList.add(vocabulary); NotificationRuleOuterClass.BatchRetrieveVocabularyTranslationsResponse dictionaryGrpc = notificationRuleClient.retrieveVocabularyTranslations(vocabularyList); if (null == dictionaryGrpc) { translationCache.put(currentTranslationCacheKey, Optional.empty()); // ✅ 补充缓存空值 return null; } // ...(其余代码保持不变) } ``` #### ✅ 修复测试用例(`MultiLanguageProviderServiceImplTest.java`) ```java @Test public void testFindTranslationFromPush_GrpcReturnsNull() { when(translationCache.getIfPresent(eq(CACHE_KEY))).thenReturn(Optional.empty()); when(notificationRuleClient.retrieveVocabularyTranslations(anyList())).thenReturn(null); String result = multiLanguageProviderService.findTranslationFromPush(VOCABULARY, LANGUAGE); assertNull(result); verify(translationCache).put(eq(CACHE_KEY), eq(Optional.empty())); // ✅ 精确验证键值 } ``` --- ### ❓
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值